/*
* Copyright (C) 2014 Alfons Wirtz
* website www.freerouting.net
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License at <http://www.gnu.org/licenses/>
* for more details.
*
* MoveComponentState.java
*
* Created on 11. Mai 2005, 06:34
*/
package interactive;
import java.util.Collection;
import java.util.LinkedList;
import java.util.Set;
import java.util.TreeSet;
import geometry.planar.FloatPoint;
import geometry.planar.IntPoint;
import geometry.planar.Vector;
import geometry.planar.Point;
import library.BoardLibrary;
import board.Component;
import board.Item;
import board.Via;
import board.ClearanceViolation;
import board.LayerStructure;
/**
*
* @author Alfons Wirtz
*/
public class MoveItemState extends InteractiveState
{
/**
* Returns a new instance of MoveComponentState, or null, if the items of p_itemlist do not belong
* to a single component.
*/
public static MoveItemState get_instance(FloatPoint p_location, Collection<Item> p_item_list,
InteractiveState p_parent_state, BoardHandling p_board_handling, Logfile p_logfile)
{
java.util.ResourceBundle resources = java.util.ResourceBundle.getBundle("interactive.resources.InteractiveState", p_board_handling.get_locale());
if (p_item_list.isEmpty())
{
p_board_handling.screen_messages.set_status_message(resources.getString("move_component_failed_because_no_item_selected"));
return null;
}
// extend p_item_list to full components
Set<Item> item_list = new TreeSet<Item>();
Set<Component> component_list = new TreeSet<Component>();
board.BasicBoard routing_board = p_board_handling.get_routing_board();
Component grid_snap_component = null;
for (Item curr_item : p_item_list)
{
if (curr_item.get_component_no() > 0)
{
Component curr_component = routing_board.components.get(curr_item.get_component_no());
if (curr_component == null)
{
System.out.println("MoveComponentState.get_instance inconsistant component number");
return null;
}
if (grid_snap_component == null &&
(p_board_handling.settings.horizontal_component_grid > 0 ||
p_board_handling.settings.horizontal_component_grid > 0))
{
grid_snap_component = curr_component;
}
if (!component_list.contains(curr_component))
{
java.util.Collection<Item> component_items = routing_board.get_component_items(curr_component.no);
for (Item curr_component_item : component_items)
{
component_list.add(curr_component);
item_list.add(curr_component_item);
}
}
}
else
{
item_list.add(curr_item);
}
}
Set<Item> fixed_items = new TreeSet<Item>();
Set<Item> obstacle_items = new TreeSet<Item>();
Set<Item> add_items = new TreeSet<Item>();
boolean move_ok = true;
for (Item curr_item : item_list)
{
if (curr_item.is_user_fixed())
{
p_board_handling.screen_messages.set_status_message(resources.getString("some_items_cannot_be_moved_because_they_are_fixed"));
move_ok = false;
obstacle_items.add(curr_item);
fixed_items.add(curr_item);
}
else if (curr_item.is_connected())
{
// Check if the whole connected set is inside the selected items,
// and add the items of the connected set to the move list in this case.
// Conduction areas are ignored, because otherwise components with
// pins contacted to a plane could never be moved.
boolean item_movable = true;
Collection<Item> contacts = curr_item.get_connected_set(-1, true);
{
for (Item curr_contact : contacts)
{
if (curr_contact instanceof board.ConductionArea)
{
continue;
}
if (curr_contact.is_user_fixed())
{
item_movable = false;
fixed_items.add(curr_contact);
}
else if (curr_contact.get_component_no() != 0)
{
Component curr_component = routing_board.components.get(curr_contact.get_component_no());
if (!component_list.contains(curr_component))
{
item_movable = false;
}
}
if (item_movable)
{
add_items.add(curr_contact);
}
else
{
obstacle_items.add(curr_contact);
}
}
}
if (!item_movable)
{
move_ok = false;
}
}
}
if (!move_ok)
{
if (p_parent_state instanceof SelectedItemState)
{
if (fixed_items.size() > 0)
{
((SelectedItemState)p_parent_state).get_item_list().addAll(fixed_items);
p_board_handling.screen_messages.set_status_message(resources.getString("please_unfix_selected_items_before_moving"));
}
else
{
((SelectedItemState)p_parent_state).get_item_list().addAll(obstacle_items);
p_board_handling.screen_messages.set_status_message(resources.getString("please_unroute_or_extend_selection_before_moving"));
}
}
return null;
}
item_list.addAll(add_items);
return new MoveItemState(p_location, item_list, component_list, grid_snap_component,
p_parent_state.return_state, p_board_handling, p_logfile);
}
/** Creates a new instance of MoveComponentState */
private MoveItemState(FloatPoint p_location, Set<Item> p_item_list, Set<Component> p_component_list,
Component p_first_component, InteractiveState p_parent_state, BoardHandling p_board_handling, Logfile p_logfile)
{
super(p_parent_state, p_board_handling, p_logfile);
this.component_list = p_component_list;
this.grid_snap_component = p_first_component;
this.current_position = p_location.round();
this.previous_position = current_position;
if (logfile != null)
{
logfile.start_scope(LogfileScope.MOVE_ITEMS, p_location);
}
board.BasicBoard routing_board = hdlg.get_routing_board();
this.observers_activated = !hdlg.get_routing_board().observers_active();
if (this.observers_activated)
{
hdlg.get_routing_board().start_notify_observers();
}
// make the situation restorable by undo
routing_board.generate_snapshot();
for (Item curr_item : p_item_list)
{
routing_board.remove_item(curr_item);
}
this.net_items_list = new LinkedList<NetItems>();
this.item_list = new TreeSet<Item>();
for (Item curr_item : p_item_list)
{
// Copy the items in p_item_list, because otherwise the undo algorithm will not work.
Item copied_item = curr_item.copy(0);
for (int i = 0; i < curr_item.net_count(); ++i)
{
add_to_net_items_list(copied_item, curr_item.get_net_no(i));
}
this.item_list.add(copied_item);
}
}
private void add_to_net_items_list(Item p_item, int p_net_no)
{
for ( NetItems curr_items : this.net_items_list)
{
if (curr_items.net_no == p_net_no)
{
// list for p_net_no exists already
curr_items.items.add(p_item) ;
return;
}
}
Collection<Item> new_item_list = hdlg.get_routing_board().get_connectable_items(p_net_no);
new_item_list.add(p_item);
NetItems new_net_items = new NetItems(p_net_no, new_item_list);
this.net_items_list.add(new_net_items);
}
public InteractiveState mouse_moved()
{
super.mouse_moved();
move(hdlg.get_current_mouse_position());
if (logfile != null)
{
logfile.add_corner(this.current_position.to_float());
}
return this;
}
public InteractiveState process_logfile_point(FloatPoint p_point)
{
move(p_point);
return this;
}
public InteractiveState left_button_clicked(FloatPoint p_location)
{
return this.complete();
}
public InteractiveState complete()
{
for (Item curr_item : this.item_list)
{
if (curr_item.clearance_violation_count() > 0)
{
hdlg.screen_messages.set_status_message(resources.getString("insertion_failed_because_of_obstacles"));
return this;
}
}
board.BasicBoard routing_board = hdlg.get_routing_board();
for (Item curr_item : this.item_list)
{
routing_board.insert_item(curr_item);
}
// let the observers syncronize the moving
for (Component curr_component : this.component_list)
{
routing_board.communication.observers.notify_moved(curr_component);
}
for (NetItems curr_net_items : this.net_items_list)
{
this.hdlg.update_ratsnest(curr_net_items.net_no);
}
if (logfile != null)
{
logfile.start_scope(LogfileScope.COMPLETE_SCOPE);
}
hdlg.screen_messages.set_status_message(resources.getString("move_completed"));
hdlg.repaint();
return this.return_state;
}
public InteractiveState cancel()
{
hdlg.get_routing_board().undo(null);
for (NetItems curr_net_items : this.net_items_list)
{
this.hdlg.update_ratsnest(curr_net_items.net_no);
}
if (logfile != null)
{
logfile.start_scope(LogfileScope.CANCEL_SCOPE);
}
return this.return_state;
}
public InteractiveState mouse_wheel_moved(int p_rotation)
{
if (hdlg.settings.zoom_with_wheel)
{
super.mouse_wheel_moved(p_rotation);
}
else
{
this.rotate(-p_rotation);
}
return this;
}
/**
* Changes the position of the items in the list to p_new_location.
*/
private void move(FloatPoint p_new_position)
{
current_position = p_new_position.round();
if (!current_position.equals(previous_position))
{
Vector translate_vector = current_position.difference_by(previous_position);
if (this.grid_snap_component != null)
{
translate_vector = adjust_to_placement_grid(translate_vector);
}
board.Components components = hdlg.get_routing_board().components;
for (Component curr_component : this.component_list)
{
components.move(curr_component.no, translate_vector);
}
this.clearance_violations = new java.util.LinkedList<ClearanceViolation>();
for (Item curr_item : this.item_list)
{
curr_item.translate_by(translate_vector);
this.clearance_violations.addAll(curr_item.clearance_violations());
}
previous_position = current_position;
for (NetItems curr_net_items : this.net_items_list)
{
this.hdlg.update_ratsnest(curr_net_items.net_no, curr_net_items.items);
}
hdlg.repaint();
}
}
private Vector adjust_to_placement_grid(Vector p_vector)
{
Point new_component_location = this.grid_snap_component.get_location().translate_by(p_vector);
IntPoint rounded_component_location =
new_component_location.to_float().round_to_grid(hdlg.settings.horizontal_component_grid,
hdlg.settings.vertical_component_grid);
Vector adjustment = rounded_component_location.difference_by(new_component_location);
Vector result = p_vector.add(adjustment);
this.current_position = this.previous_position.translate_by(result).to_float().round();
return p_vector.add(adjustment);
}
/**
* Turns the items in the list by p_factor times 90 degree around the current position.
*/
public void turn_90_degree(int p_factor)
{
if (p_factor == 0)
{
return;
}
board.Components components = hdlg.get_routing_board().components;
for (Component curr_component : this.component_list)
{
components.turn_90_degree(curr_component.no, p_factor, current_position);
}
this.clearance_violations = new java.util.LinkedList<ClearanceViolation>();
for (Item curr_item : this.item_list)
{
curr_item.turn_90_degree(p_factor, current_position);
this.clearance_violations.addAll(curr_item.clearance_violations());
}
for (NetItems curr_net_items : this.net_items_list)
{
this.hdlg.update_ratsnest(curr_net_items.net_no, curr_net_items.items);
}
if (logfile != null)
{
logfile.start_scope(LogfileScope.TURN_90_DEGREE, p_factor);
}
hdlg.repaint();
}
public void rotate(double p_angle_in_degree)
{
if (p_angle_in_degree == 0)
{
return;
}
board.Components components = hdlg.get_routing_board().components;
for (Component curr_component : this.component_list)
{
components.rotate(curr_component.no, p_angle_in_degree, this.current_position);
}
this.clearance_violations = new java.util.LinkedList<ClearanceViolation>();
FloatPoint float_position = this.current_position.to_float();
for (Item curr_item : this.item_list)
{
curr_item.rotate_approx(p_angle_in_degree, float_position);
this.clearance_violations.addAll(curr_item.clearance_violations());
}
for (NetItems curr_net_items : this.net_items_list)
{
this.hdlg.update_ratsnest(curr_net_items.net_no, curr_net_items.items);
}
if (logfile != null)
{
logfile.start_scope(LogfileScope.ROTATE, (int) p_angle_in_degree);
}
hdlg.repaint();
}
/**
* Turns the items in the list by p_factor times 90 degree around the current position.
*/
public void turn_45_degree(int p_factor)
{
if (p_factor % 2 == 0)
{
turn_90_degree(p_factor / 2);
}
else
{
rotate(p_factor * 45);
}
}
/**
* Changes the placement side of the items in the list.
*/
public void change_placement_side()
{
// Check, that all items can be mirrored
LayerStructure layer_structure = hdlg.get_routing_board().layer_structure;
BoardLibrary board_library = hdlg.get_routing_board().library;
boolean placement_side_changable = true;
for(Item curr_item : item_list)
{
if (curr_item instanceof Via)
{
if (board_library.get_mirrored_via_padstack(((Via)curr_item).get_padstack()) == null)
{
placement_side_changable = false;
break;
}
}
else if (curr_item.first_layer() == curr_item.last_layer())
{
int new_layer_no = hdlg.get_layer_count() - curr_item.first_layer() - 1;
if (!layer_structure.arr[new_layer_no].is_signal)
{
placement_side_changable = false;
break;
}
}
}
if (!placement_side_changable)
{
hdlg.screen_messages.set_status_message(resources.getString("cannot_change_placement_side"));
return;
}
board.Components components = hdlg.get_routing_board().components;
for (Component curr_component : this.component_list)
{
components.change_side(curr_component.no, current_position);
}
this.clearance_violations = new java.util.LinkedList<ClearanceViolation>();
for (Item curr_item : this.item_list)
{
curr_item.change_placement_side(current_position);
this.clearance_violations.addAll(curr_item.clearance_violations());
}
for (NetItems curr_net_items : this.net_items_list)
{
this.hdlg.update_ratsnest(curr_net_items.net_no, curr_net_items.items);
}
if (logfile != null)
{
logfile.start_scope(LogfileScope.CHANGE_PLACEMENT_SIDE);
}
hdlg.repaint();
}
public void reset_rotation()
{
Component component_to_reset = null;
for (Component curr_component : this.component_list)
{
if (component_to_reset == null)
{
component_to_reset = curr_component;
}
else if (component_to_reset.get_rotation_in_degree() != curr_component.get_rotation_in_degree())
{
hdlg.screen_messages.set_status_message(resources.getString("unable_to_reset_components_with_different_rotations"));
return;
}
}
if (component_to_reset == null)
{
return;
}
double rotation = component_to_reset.get_rotation_in_degree();
if (!hdlg.get_routing_board().components.get_flip_style_rotate_first() || component_to_reset.placed_on_front())
{
rotation = 360 - rotation;
}
rotate(rotation);
}
/**
* Action to be taken when a key is pressed (Shortcut).
*/
public InteractiveState key_typed(char p_key_char)
{
InteractiveState curr_return_state = this;
if (p_key_char == '+')
{
turn_90_degree(1);
}
else if (p_key_char == '*')
{
turn_90_degree(2);
}
else if (p_key_char == '-')
{
turn_90_degree(3);
}
else if (p_key_char == '/')
{
change_placement_side();
}
else if (p_key_char == 'r')
{
hdlg.settings.set_zoom_with_wheel(false);
}
else if (p_key_char == 'z')
{
hdlg.settings.set_zoom_with_wheel(true);
}
else
{
curr_return_state = super.key_typed(p_key_char);
}
return curr_return_state;
}
public javax.swing.JPopupMenu get_popup_menu()
{
return hdlg.get_panel().popup_menu_move;
}
public String get_help_id()
{
return "MoveItemState";
}
public void draw(java.awt.Graphics p_graphics)
{
if (this.item_list == null)
{
return;
}
for (Item curr_item : this.item_list)
{
curr_item.draw(p_graphics, hdlg.graphics_context);
}
if (this.clearance_violations != null)
{
java.awt.Color draw_color = hdlg.graphics_context.get_violations_color();
for (ClearanceViolation curr_violation : this.clearance_violations)
{
hdlg.graphics_context.fill_area(curr_violation.shape, p_graphics, draw_color, 1);
}
}
}
private final Set<Item> item_list;
private final Set<Component> component_list;
/**
* In case of a component grid the first component is aligned to this grid.
*/
private final Component grid_snap_component;
private IntPoint current_position;
private IntPoint previous_position;
private Collection<ClearanceViolation> clearance_violations;
private final Collection<NetItems> net_items_list;
private boolean observers_activated = false;
private static class NetItems
{
NetItems(int p_net_no, Collection<Item> p_items)
{
net_no = p_net_no;
items = p_items;
}
final int net_no;
final Collection<Item> items;
}
}