/* * 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. * * SelectedItemState.java * * Created on 10. November 2003, 08:02 */ package interactive; import geometry.planar.FloatPoint; import geometry.planar.IntPoint; import geometry.planar.Point; import geometry.planar.Vector; import java.util.Collection; import java.util.Iterator; import java.util.Set; import java.util.TreeSet; import datastructures.Stoppable; import library.Package; import rules.Net; import autoroute.AutorouteEngine; import board.Component; import board.Connectable; import board.DrillItem; import board.Via; import board.Pin; import board.Item; import board.ObstacleArea; import board.PolylineTrace; import board.RoutingBoard; import board.FixedState; import board.OptViaAlgo; import board.TestLevel; /** * Class implementing actions on the currently selected items. * * @author Alfons Wirtz */ public class SelectedItemState extends InteractiveState { /** * Creates a new SelectedItemState with with the items in p_item_list selected. * Returns null, if p_item_list is empty. */ public static SelectedItemState get_instance(Set<Item> p_item_list, InteractiveState p_parent_state, BoardHandling p_board_handling, Logfile p_logfile) { if (p_item_list.isEmpty()) { return null; } SelectedItemState new_state = new SelectedItemState(p_item_list, p_parent_state, p_board_handling, p_logfile); return new_state; } /** Creates a new instance of SelectedItemState */ private SelectedItemState(Set<Item> p_item_list, InteractiveState p_parent_state, BoardHandling p_board_handling, Logfile p_logfile) { super(p_parent_state, p_board_handling, p_logfile); item_list = p_item_list; } /** * Gets the list of the currently selected items. */ public Collection<Item> get_item_list() { return item_list; } public InteractiveState left_button_clicked(FloatPoint p_location) { return toggle_select(p_location); } public InteractiveState mouse_dragged(FloatPoint p_point) { return SelectItemsInRegionState.get_instance(hdlg.get_current_mouse_position(), this, hdlg, logfile); } /** * Action to be taken when a key is pressed (Shortcut). */ public InteractiveState key_typed(char p_key_char) { InteractiveState result = this; if (p_key_char == 'a') { this.hdlg.autoroute_selected_items(); } else if (p_key_char == 'b') { this.extent_to_whole_components(); } else if (p_key_char == 'd') { result = this.cutout_items(); } else if (p_key_char == 'e') { result = this.extent_to_whole_connections(); } else if (p_key_char == 'f') { this.fix_items(); } else if (p_key_char == 'i') { result = this.info(); } else if (p_key_char == 'm') { result = MoveItemState.get_instance(hdlg.get_current_mouse_position(), item_list, this.return_state, hdlg, logfile); } else if (p_key_char == 'n') { this.extent_to_whole_nets(); } else if (p_key_char == 'p') { this.hdlg.optimize_selected_items(); } else if (p_key_char == 'r') { result = ZoomRegionState.get_instance(hdlg.get_current_mouse_position(), this, hdlg, logfile); } else if (p_key_char == 's') { result = this.extent_to_whole_connected_sets(); } else if (p_key_char == 'u') { this.unfix_items(); } else if (p_key_char == 'v') { this.toggle_clearance_violations(); } else if (p_key_char == 'w') { this.hdlg.zoom_selection(); } else if (p_key_char == java.awt.event.KeyEvent.VK_DELETE) { result = delete_items(); } else { result = super.key_typed(p_key_char); } return result; } /** * fixes all items in this selected set */ public void fix_items() { java.util.Iterator<Item> it = item_list.iterator(); while (it.hasNext()) { Item curr_ob = it.next(); if (curr_ob.get_fixed_state().ordinal() < FixedState.USER_FIXED.ordinal()) { curr_ob.set_fixed_state(FixedState.USER_FIXED); } } if (this.logfile != null) { logfile.start_scope(LogfileScope.FIX_SELECTED_ITEMS); } } /** * unfixes all items in this selected set */ public void unfix_items() { java.util.Iterator<Item> it = item_list.iterator(); while (it.hasNext()) { Item curr_ob = it.next(); curr_ob.unfix(); } if (this.logfile != null) { logfile.start_scope(LogfileScope.UNFIX_SELECTED_ITEMS); } } /** * Makes all items in this selected_set connectable and assigns * them to a new net. */ public InteractiveState assign_items_to_new_net() { RoutingBoard board = hdlg.get_routing_board(); // make the situation restorable by undo board.generate_snapshot(); boolean items_already_connected = false; Net new_net = board.rules.nets.new_net(hdlg.get_locale()); java.util.Iterator<Item> it = item_list.iterator(); while (it.hasNext()) { Item curr_item = it.next(); if (curr_item instanceof ObstacleArea) { board.make_conductive((ObstacleArea) curr_item, new_net.net_number); } else if (curr_item instanceof DrillItem) { if (curr_item.is_connected()) { items_already_connected = true; } else { curr_item.assign_net_no(new_net.net_number); } } } if (items_already_connected) { hdlg.screen_messages.set_status_message(resources.getString("some_items_are_not_changed_because_they_are_already_connected")); } else { hdlg.screen_messages.set_status_message(resources.getString("new_net_created_from_selected_items")); } if (this.logfile != null) { logfile.start_scope(LogfileScope.ASSIGN_SELECTED_TO_NEW_NET); } hdlg.update_ratsnest(); hdlg.repaint(); return this.return_state; } /** * Assigns all items in this selected_set to a new group( new component for example) */ public InteractiveState assign_items_to_new_group() { RoutingBoard board = hdlg.get_routing_board(); board.generate_snapshot(); // Take the gravity point of all item centers for the location of the new component. double gravity_x = 0; double gravity_y = 0; int pin_count = 0; java.util.Iterator<Item> it = item_list.iterator(); while (it.hasNext()) { Item curr_ob = it.next(); if (curr_ob instanceof Via) { FloatPoint curr_center = ((DrillItem) curr_ob).get_center().to_float(); gravity_x += curr_center.x; gravity_y += curr_center.y; ++pin_count; } else { // currently only Vias can be aasigned to a new component it.remove(); } } if (pin_count == 0) { return this.return_state; } gravity_x /= pin_count; gravity_y /= pin_count; Point gravity_point = new IntPoint((int) Math.round(gravity_x), (int) Math.round(gravity_y)); // create a new package Package.Pin[] pin_arr = new Package.Pin[item_list.size()]; it = item_list.iterator(); for (int i = 0; i < pin_arr.length; ++i) { Via curr_via = (Via) it.next(); Vector rel_coor = curr_via.get_center().difference_by(gravity_point); String pin_name = (new Integer(i + 1)).toString(); pin_arr[i] = new Package.Pin(pin_name, curr_via.get_padstack().no, rel_coor, 0); } Package new_package = board.library.packages.add(pin_arr); Component new_component = board.components.add(gravity_point, 0, true, new_package); it = item_list.iterator(); for (int i = 0; i < pin_arr.length; ++i) { Via curr_via = (Via) it.next(); board.remove_item(curr_via); int[] net_no_arr = new int[curr_via.net_count()]; for (int j = 0; j < net_no_arr.length; ++j) { net_no_arr[j] = curr_via.get_net_no(j); } board.insert_pin(new_component.no, i, net_no_arr, curr_via.clearance_class_no(), curr_via.get_fixed_state()); } if (this.logfile != null) { logfile.start_scope(LogfileScope.ASSIGN_SELECTED_TO_NEW_GROUP); } hdlg.repaint(); return this.return_state; } /** * Deletes all unfixed items in this selected set and * pulls tight the neighour traces. */ public InteractiveState delete_items() { hdlg.get_routing_board().generate_snapshot(); // calculate the changed nets for updating the ratsnest Set<Integer> changed_nets = new TreeSet<Integer>(); Iterator<Item> it = item_list.iterator(); while (it.hasNext()) { Item curr_item = it.next(); if (curr_item instanceof Connectable) { for (int i = 0; i < curr_item.net_count(); ++i) { changed_nets.add(curr_item.get_net_no(i)); } } } boolean with_delete_fixed = hdlg.get_routing_board().get_test_level() != TestLevel.RELEASE_VERSION; boolean all_items_removed; if (hdlg.settings.push_enabled) { all_items_removed = hdlg.get_routing_board().remove_items_and_pull_tight(item_list, hdlg.settings.trace_pull_tight_region_width, hdlg.settings.trace_pull_tight_accuracy, with_delete_fixed); } else { all_items_removed = hdlg.get_routing_board().remove_items(item_list, with_delete_fixed); } if (!all_items_removed) { hdlg.screen_messages.set_status_message(resources.getString("some_items_are_fixed_and_could_therefore_not_be_removed")); } if (this.logfile != null) { logfile.start_scope(LogfileScope.DELETE_SELECTED); } for (Integer curr_net_no : changed_nets) { hdlg.update_ratsnest(curr_net_no.intValue()); } hdlg.repaint(); return this.return_state; } /** * Deletes all unfixed items in this selected set inside a rectangle */ public InteractiveState cutout_items() { return CutoutRouteState.get_instance(this.item_list, this.return_state, hdlg, logfile); } /** * Autoroutes the selected items. * If p_stoppable_thread != null, the algorithm can be requestet to terminate. */ public InteractiveState autoroute(Stoppable p_stoppable_thread) { boolean saved_board_read_only = hdlg.is_board_read_only(); hdlg.set_board_read_only(true); if (p_stoppable_thread != null) { String start_message = resources.getString("autoroute") + " " + resources.getString("stop_message"); hdlg.screen_messages.set_status_message(start_message); } Integer not_found_count = 0; Integer found_count = 0; boolean interrupted = false; Collection<Item> autoroute_item_list = new java.util.LinkedList<Item>(); for (Item curr_item : item_list) { if (curr_item instanceof Connectable) { for (int i = 0; i < curr_item.net_count(); ++i) { if (!curr_item.get_unconnected_set(curr_item.get_net_no(i)).isEmpty()) { autoroute_item_list.add(curr_item); } } } } int items_to_go_count = autoroute_item_list.size(); hdlg.screen_messages.set_interactive_autoroute_info(found_count, not_found_count, items_to_go_count); // Empty this.item_list to avoid displaying the seected items. this.item_list = new TreeSet<Item>(); boolean ratsnest_hidden_before = hdlg.get_ratsnest().is_hidden(); if (!ratsnest_hidden_before) { hdlg.get_ratsnest().hide(); } for (Item curr_item : autoroute_item_list) { if (p_stoppable_thread != null && p_stoppable_thread.is_stop_requested()) { interrupted = true; break; } if (curr_item.net_count() != 1) { continue; } boolean contains_plane = false; rules.Net route_net = hdlg.get_routing_board().rules.nets.get(curr_item.get_net_no(0)); if (route_net != null) { contains_plane = route_net.contains_plane(); } int via_costs; if (contains_plane) { via_costs = hdlg.settings.autoroute_settings.get_plane_via_costs(); } else { via_costs = hdlg.settings.autoroute_settings.get_via_costs(); } hdlg.get_routing_board().start_marking_changed_area(); AutorouteEngine.AutorouteResult autoroute_result = hdlg.get_routing_board().autoroute(curr_item, hdlg.settings, via_costs, p_stoppable_thread, null); if (autoroute_result == AutorouteEngine.AutorouteResult.ROUTED) { ++found_count; hdlg.repaint(); } else if (autoroute_result != AutorouteEngine.AutorouteResult.ALREADY_CONNECTED) { ++not_found_count; } --items_to_go_count; hdlg.screen_messages.set_interactive_autoroute_info(found_count, not_found_count, items_to_go_count); } if (p_stoppable_thread != null) { hdlg.screen_messages.clear(); String curr_message; if (interrupted) { curr_message = resources.getString("interrupted"); } else { curr_message = resources.getString("completed"); } String end_message = resources.getString("autoroute") + " " + curr_message + ": " + found_count.toString() + " " + resources.getString("connections_found") + ", " + not_found_count.toString() + " " + resources.getString("connections_not_found"); hdlg.screen_messages.set_status_message(end_message); } hdlg.set_board_read_only(saved_board_read_only); if (this.logfile != null) { logfile.start_scope(LogfileScope.AUTOROUTE_SELECTED); } hdlg.update_ratsnest(); if (!ratsnest_hidden_before) { hdlg.get_ratsnest().show(); } return this.return_state; } /** * Fanouts the pins contained in the selected items. * If p_stoppable_thread != null, the algorithm can be requestet to terminate. */ public InteractiveState fanout(Stoppable p_stoppable_thread) { boolean saved_board_read_only = hdlg.is_board_read_only(); hdlg.set_board_read_only(true); if (p_stoppable_thread != null) { String start_message = resources.getString("fanout") + " " + resources.getString("stop_message"); hdlg.screen_messages.set_status_message(start_message); } Integer not_found_count = 0; Integer found_count = 0; int trace_pull_tight_accuracy = hdlg.settings.trace_pull_tight_accuracy; boolean interrupted = false; Collection<Pin> fanout_list = new java.util.LinkedList<Pin>(); for (Item curr_item : item_list) { if (curr_item instanceof Pin) { fanout_list.add((Pin) curr_item); } } int items_to_go_count = fanout_list.size(); hdlg.screen_messages.set_interactive_autoroute_info(found_count, not_found_count, items_to_go_count); // Empty this.item_list to avoid displaying the seected items. this.item_list = new TreeSet<Item>(); boolean ratsnest_hidden_before = hdlg.get_ratsnest().is_hidden(); if (!ratsnest_hidden_before) { hdlg.get_ratsnest().hide(); } for (Pin curr_pin : fanout_list) { if (p_stoppable_thread != null && p_stoppable_thread.is_stop_requested()) { interrupted = true; break; } hdlg.get_routing_board().start_marking_changed_area(); AutorouteEngine.AutorouteResult autoroute_result = hdlg.get_routing_board().fanout(curr_pin, hdlg.settings, -1, p_stoppable_thread, null); if (autoroute_result == AutorouteEngine.AutorouteResult.ROUTED) { ++found_count; hdlg.repaint(); } else if (autoroute_result != AutorouteEngine.AutorouteResult.ALREADY_CONNECTED) { ++not_found_count; } --items_to_go_count; hdlg.screen_messages.set_interactive_autoroute_info(found_count, not_found_count, items_to_go_count); } if (p_stoppable_thread != null) { hdlg.screen_messages.clear(); String curr_message; if (interrupted) { curr_message = resources.getString("interrupted"); } else { curr_message = resources.getString("completed"); } String end_message = resources.getString("fanout") + " " + curr_message + ": " + found_count.toString() + " " + resources.getString("connections_found") + ", " + not_found_count.toString() + " " + resources.getString("connections_not_found"); hdlg.screen_messages.set_status_message(end_message); } hdlg.set_board_read_only(saved_board_read_only); if (this.logfile != null) { logfile.start_scope(LogfileScope.FANOUT_SELECTED); } hdlg.update_ratsnest(); if (!ratsnest_hidden_before) { hdlg.get_ratsnest().show(); } return this.return_state; } /** * Optimizes the selected items. * If p_stoppable_thread != null, the algorithm can be requestet to terminate. */ public InteractiveState pull_tight(Stoppable p_stoppable_thread) { boolean saved_board_read_only = hdlg.is_board_read_only(); hdlg.set_board_read_only(true); if (p_stoppable_thread != null) { String start_message = resources.getString("pull_tight") + " " + resources.getString("stop_message"); hdlg.screen_messages.set_status_message(start_message); } hdlg.get_routing_board().start_marking_changed_area(); boolean interrupted = false; for (Item curr_item : item_list) { if (p_stoppable_thread != null && p_stoppable_thread.is_stop_requested()) { interrupted = true; break; } if (curr_item.net_count() != 1) { continue; } if (curr_item instanceof PolylineTrace) { PolylineTrace curr_trace = (PolylineTrace) curr_item; boolean something_changed = curr_trace.pull_tight(!hdlg.settings.push_enabled, hdlg.settings.trace_pull_tight_accuracy, p_stoppable_thread); if (!something_changed) { curr_trace.smoothen_end_corners_fork(!hdlg.settings.push_enabled, hdlg.settings.trace_pull_tight_accuracy, p_stoppable_thread); } } else if (curr_item instanceof Via) { OptViaAlgo.opt_via_location(hdlg.get_routing_board(), (Via) curr_item, null, hdlg.settings.trace_pull_tight_accuracy, 10); } } String curr_message; if (hdlg.settings.push_enabled && !interrupted) { hdlg.get_routing_board().opt_changed_area(new int[0], null, hdlg.settings.trace_pull_tight_accuracy, null, p_stoppable_thread, 0); } if (p_stoppable_thread != null) { if (interrupted) { curr_message = resources.getString("interrupted"); } else { curr_message = resources.getString("completed"); } String end_message = resources.getString("pull_tight") + " " + curr_message; hdlg.screen_messages.set_status_message(end_message); } hdlg.set_board_read_only(saved_board_read_only); if (this.logfile != null) { logfile.start_scope(LogfileScope.OPTIMIZE_SELECTED); } hdlg.update_ratsnest(); return this.return_state; } /** * Assigns the input clearance class to the selected items. */ public InteractiveState assign_clearance_class(int p_cl_class_index) { board.BasicBoard routing_board = this.hdlg.get_routing_board(); if (p_cl_class_index < 0 || p_cl_class_index >= routing_board.rules.clearance_matrix.get_class_count()) { return this.return_state; } if (this.logfile != null) { logfile.start_scope(LogfileScope.ASSIGN_CLEARANCE_CLASS); logfile.add_int(p_cl_class_index); } // make the situation restorable by undo routing_board.generate_snapshot(); for (Item curr_item : this.item_list) { if (curr_item.clearance_class_no() == p_cl_class_index) { continue; } curr_item.change_clearance_class(p_cl_class_index); } return this.return_state; } /** * Select also all items belonging to any net of the current selected items. */ public InteractiveState extent_to_whole_nets() { // collect all net numbers of the selected items Set<Integer> curr_net_no_set = new TreeSet<Integer>(); Iterator<Item> it = item_list.iterator(); while (it.hasNext()) { Item curr_item = it.next(); if (curr_item instanceof Connectable) { for (int i = 0; i < curr_item.net_count(); ++i) { curr_net_no_set.add(curr_item.get_net_no(i)); } } } Set<Item> new_selected_items = new TreeSet<Item>(); Iterator<Integer> it2 = curr_net_no_set.iterator(); while (it2.hasNext()) { int curr_net_no = it2.next(); new_selected_items.addAll(hdlg.get_routing_board().get_connectable_items(curr_net_no)); } this.item_list = new_selected_items; if (new_selected_items.isEmpty()) { return this.return_state; } if (this.logfile != null) { logfile.start_scope(LogfileScope.EXTEND_TO_WHOLE_NETS); } filter(); hdlg.repaint(); return this; } /** * Select also all items belonging to any group of the current selected items. */ public InteractiveState extent_to_whole_components() { // collect all group numbers of the selected items Set<Integer> curr_group_no_set = new TreeSet<Integer>(); Iterator<Item> it = item_list.iterator(); while (it.hasNext()) { Item curr_item = it.next(); if (curr_item.get_component_no() > 0) { curr_group_no_set.add(curr_item.get_component_no()); } } Set<Item> new_selected_items = new TreeSet<Item>(); new_selected_items.addAll(item_list); Iterator<Integer> it2 = curr_group_no_set.iterator(); while (it2.hasNext()) { int curr_group_no = it2.next(); new_selected_items.addAll(hdlg.get_routing_board().get_component_items(curr_group_no)); } if (new_selected_items.isEmpty()) { return this.return_state; } this.item_list = new_selected_items; if (this.logfile != null) { logfile.start_scope(LogfileScope.EXTEND_TO_WHOLE_COMPONENTS); } hdlg.repaint(); return this; } /** * Select also all items belonging to any connected set of the current selected items. */ public InteractiveState extent_to_whole_connected_sets() { Set<Item> new_selected_items = new TreeSet<Item>(); Iterator<Item> it = this.item_list.iterator(); while (it.hasNext()) { Item curr_item = it.next(); if (curr_item instanceof Connectable) { new_selected_items.addAll(curr_item.get_connected_set(-1)); } } if (new_selected_items.isEmpty()) { return this.return_state; } this.item_list = new_selected_items; if (this.logfile != null) { logfile.start_scope(LogfileScope.EXTEND_TO_WHOLE_CONNECTED_SETS); } filter(); hdlg.repaint(); return this; } /** * Select also all items belonging to any connection of the current selected items. */ public InteractiveState extent_to_whole_connections() { Set<Item> new_selected_items = new TreeSet<Item>(); Iterator<Item> it = this.item_list.iterator(); while (it.hasNext()) { Item curr_item = it.next(); if (curr_item instanceof Connectable) { new_selected_items.addAll(curr_item.get_connection_items()); } } if (new_selected_items.isEmpty()) { return this.return_state; } this.item_list = new_selected_items; if (this.logfile != null) { logfile.start_scope(LogfileScope.EXTEND_TO_WHOLE_CONNECTIONS); } filter(); hdlg.repaint(); return this; } /** * Picks item at p_point. * Removes it from the selected_items list, if it is already in there, * otherwise adds it to the list. * Returns true (to change to the return_state) if nothing was picked. */ public InteractiveState toggle_select(FloatPoint p_point) { Collection<Item> picked_items = hdlg.pick_items(p_point); boolean state_ended = (picked_items.isEmpty()); if (picked_items.size() == 1) { Item picked_item = picked_items.iterator().next(); if (this.item_list.contains(picked_item)) { this.item_list.remove(picked_item); if (this.item_list.isEmpty()) { state_ended = true; } } else { this.item_list.add(picked_item); } } hdlg.repaint(); InteractiveState result; if (state_ended) { result = this.return_state; } else { result = this; } if (this.logfile != null) { logfile.start_scope(LogfileScope.TOGGLE_SELECT, p_point); } return result; } /** * Shows or hides the clearance violations of the selected items.. */ public void toggle_clearance_violations() { if (clearance_violations == null) { clearance_violations = new ClearanceViolations(this.item_list); Integer violation_count = new Integer(clearance_violations.list.size()); String curr_message = violation_count.toString() + " " + resources.getString("clearance_violations_found"); hdlg.screen_messages.set_status_message(curr_message); } else { clearance_violations = null; hdlg.screen_messages.set_status_message(""); } hdlg.repaint(); } /** * Removes items not selected by the current interactive filter from the selected item list. */ public InteractiveState filter() { item_list = hdlg.settings.item_selection_filter.filter(item_list); InteractiveState result = this; if (item_list.isEmpty()) { result = this.return_state; } hdlg.repaint(); return result; } /** * Prints information about the selected item into a graphical text window. */ public SelectedItemState info() { gui.WindowObjectInfo.display(this.item_list, hdlg.get_panel().board_frame, hdlg.coordinate_transform, new java.awt.Point(100, 100)); return this; } public String get_help_id() { return "SelectedItemState"; } public void draw(java.awt.Graphics p_graphics) { if (item_list == null) { return; } for (board.Item curr_item : item_list) { curr_item.draw(p_graphics, hdlg.graphics_context, hdlg.graphics_context.get_hilight_color(), hdlg.graphics_context.get_hilight_color_intensity()); } if (clearance_violations != null) { clearance_violations.draw(p_graphics, hdlg.graphics_context); } } public javax.swing.JPopupMenu get_popup_menu() { return hdlg.get_panel().popup_menu_select; } public void set_toolbar() { hdlg.get_panel().board_frame.set_select_toolbar(); } public void display_default_message() { hdlg.screen_messages.set_status_message(resources.getString("in_select_item_mode")); } private Set<Item> item_list; private ClearanceViolations clearance_violations = null; }