1 /* Lepton EDA Schematic Capture
2 * Copyright (C) 1998-2010 Ales Hvezda
3 * Copyright (C) 1998-2016 gEDA Contributors
4 * Copyright (C) 2017-2021 Lepton EDA Contributors
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 /*! The code in this file is sometimes not obvious, especially
21 * o_select_object (which implements the selection of objects either
22 * when doing a single or multi select)
23 *
24 * Also, there are cases where it looks like there is redundant code, which
25 * could be removed/merged, but I purposely didn't do so to keep the code
26 * readable
27 *
28 * the count == 0 stuff really only applies to when you are coming from a
29 * multi select case
30 */
31 #include <config.h>
32
33 #include <math.h>
34 #include <stdio.h>
35 #ifdef HAVE_STRING_H
36 #include <string.h>
37 #endif
38
39 #include "gschem.h"
40
41 /*! \brief Start the process of selection
42 * \par Function Description
43 * Chooses the way of how to start the selection process. If no
44 * grip was found at the given coordinates the function sets
45 * \a w_current->inside_action in order to force other functions
46 * (\a o_select_motion() or \a o_select_end()) to decide that.
47 * Otherwise, it switches on the GRIPS mode for working with the
48 * grip found.
49 *
50 * The function is intended to be called by pressing the left
51 * mouse button.
52 *
53 * \param [in] w_current The GschemToplevel structure.
54 * \param [in] wx The world X coordinate.
55 * \param [in] wy The world Y coordinate.
56 */
o_select_start(GschemToplevel * w_current,int wx,int wy)57 void o_select_start (GschemToplevel *w_current, int wx, int wy)
58 {
59 /* look for grips or fall through if not enabled */
60 o_grips_start(w_current, wx, wy);
61
62 if (w_current->event_state != GRIPS) {
63 /* now go into normal SELECT */
64 i_action_start (w_current);
65 w_current->first_wx = w_current->second_wx = wx;
66 w_current->first_wy = w_current->second_wy = wy;
67 }
68 }
69
70 /*! \brief End the process of selection
71 * \par Function Description
72 * Finishes the process of selection if the \a o_select_start()
73 * or \a o_select_motion() functions haven't defined other
74 * functions to finish it. In this case the function tries to
75 * find an object under the mouse pointer and select it.
76 *
77 * The function is intended to be called by releasing the left
78 * mouse button.
79 *
80 * \param [in] w_current The GschemToplevel structure.
81 * \param [in] wx The world X coordinate.
82 * \param [in] wy The world Y coordinate.
83 */
o_select_end(GschemToplevel * w_current,int wx,int wy)84 void o_select_end (GschemToplevel *w_current, int wx, int wy)
85 {
86 g_assert (w_current->inside_action != 0);
87
88 /* look for objects to select */
89 o_find_object(w_current, wx, wy, TRUE);
90 i_action_stop (w_current);
91 }
92
93
94 /*! \brief Determine whether objects have to be selected or moved
95 * \par Function Description
96 * Checks if the shift or control keys are pressed, (that means
97 * the user definitely wants to drag out a selection box), or
98 * there are no selected objects under the cursor. In that case
99 * the function starts drawing the selection box. Otherwise, it
100 * looks for the objects that have been or could be selected and
101 * starts moving them.
102 *
103 * The function is intended to be called by motion of the mouse
104 * while the left mouse button is pressed.
105 *
106 * \param [in] w_current The GschemToplevel structure.
107 * \param [in] wx The world X coordinate.
108 * \param [in] wy The world Y coordinate.
109 */
o_select_motion(GschemToplevel * w_current,int wx,int wy)110 void o_select_motion (GschemToplevel *w_current, int wx, int wy)
111 {
112 g_assert (w_current->inside_action != 0);
113
114 /* Check if a mod key is pressed or there is no selected object
115 * under the cursor */
116 if (w_current->SHIFTKEY || w_current->CONTROLKEY
117 || (!o_find_selected_object(w_current, w_current->first_wx, w_current->first_wy)
118 && (!o_find_object(w_current, w_current->first_wx, w_current->first_wy, TRUE)
119 || !o_select_selected(w_current)))) {
120 /* Start drawing a selection box to select objects */
121 o_select_box_start(w_current, wx, wy);
122 } else {
123 /* Start moving the selected object(s) */
124 o_move_start(w_current, w_current->first_wx, w_current->first_wy);
125 }
126 }
127
128 /*! \todo Finish function documentation!!!
129 * \brief
130 * \par Function Description
131 *
132 */
o_select_run_hooks(GschemToplevel * w_current,LeptonObject * o_current,int flag)133 void o_select_run_hooks(GschemToplevel *w_current, LeptonObject *o_current, int flag)
134 {
135 switch (flag) {
136 /* If flag == 0, then we are deselecting something. */
137 case 0:
138 g_run_hook_object (w_current, "%deselect-objects-hook", o_current);
139 break;
140 /* If flag == 1, then we are selecting something. */
141 case 1:
142 g_run_hook_object (w_current, "%select-objects-hook", o_current);
143 break;
144 default:
145 g_assert_not_reached ();
146 }
147 }
148
149 /*! \todo Finish function documentation!!!
150 * \brief
151 * \par Function Description
152 *
153 * \note
154 * type can be either SINGLE meaning selection is a single mouse click
155 * or it can be MULTIPLE meaning selection is a selection box
156 */
o_select_object(GschemToplevel * w_current,LeptonObject * o_current,int type,int count)157 void o_select_object(GschemToplevel *w_current, LeptonObject *o_current,
158 int type, int count)
159 {
160 LeptonToplevel *toplevel = gschem_toplevel_get_toplevel (w_current);
161 int SHIFTKEY;
162 int CONTROLKEY;
163 int removing_obj = 0;
164
165 SHIFTKEY = w_current->SHIFTKEY;
166 CONTROLKEY = w_current->CONTROLKEY;
167
168 #if DEBUG
169 printf("LeptonObject id: %d\n", lepton_object_get_id (o_current));
170 #endif
171
172 switch(o_current->selected) {
173
174 case(FALSE): /* object not selected */
175
176 switch(SHIFTKEY) { /* shift key pressed? */
177
178 case(TRUE): /* shift key pressed */
179 /* just fall through */
180 break;
181
182 case(FALSE):
183
184 /* condition: first object being added */
185 /* condition: control key not pressed */
186 /* condition: for both multiple and single object added */
187 /* result: remove all objects from selection */
188 if (count == 0 && !CONTROLKEY) {
189 o_select_unselect_all(w_current);
190 }
191 break;
192
193 } /* end shift key switch */
194
195 /* object not select, add it to the selection list */
196 o_select_run_hooks( w_current, o_current, 1 );
197 o_selection_add (toplevel->page_current->selection_list, o_current);
198
199 break;
200
201
202 case(TRUE): /* object was already selected */
203
204 switch(SHIFTKEY) { /* shift key pressed ? */
205
206 case(TRUE): /* shift key pressed */
207
208 /* condition: not doing multiple */
209 /* result: remove object from selection */
210 if (type != MULTIPLE) {
211 o_select_run_hooks( w_current, o_current, 0 );
212 o_selection_remove (toplevel->page_current->selection_list,
213 o_current);
214 removing_obj = 1;
215 }
216
217 break;
218
219 case(FALSE): /* shift key not pressed */
220
221 /* condition: doing multiple */
222 /* condition: first object being added */
223 /* condition: control key not pressed */
224 /* 1st result: remove all objects from selection */
225 /* 2nd result: add object to selection */
226 if (type == MULTIPLE && count == 0 && !CONTROLKEY) {
227 o_select_unselect_all (w_current);
228
229 o_select_run_hooks( w_current, o_current, 1 );
230 o_selection_add (toplevel->page_current->selection_list, o_current);
231 }
232
233 /* condition: doing single object add */
234 /* condition: control key not pressed */
235 /* 1st result: remove all objects from selection */
236 /* 2nd result: add object to selection list */
237 if (type == SINGLE && !CONTROLKEY) {
238 o_select_unselect_all (w_current);
239
240 o_select_run_hooks (w_current, o_current, 1);
241 o_selection_add (toplevel->page_current->selection_list,
242 o_current);
243 }
244
245 if (CONTROLKEY) {
246 o_select_run_hooks(w_current, o_current, 0);
247 o_selection_remove (toplevel->page_current->selection_list,
248 o_current);
249 removing_obj = 1;
250 }
251
252 break;
253 }
254 break; /* end object selected switch */
255 }
256
257 /* do the attributes */
258 if (removing_obj) {
259 /* Remove the invisible attributes from the object list as well,
260 * so they don't remain selected without the user knowing.
261 */
262 o_attrib_deselect_invisible (w_current,
263 toplevel->page_current->selection_list,
264 o_current);
265 } else {
266 /* If the type is MULTIPLE (meaning a select box was/is being used), only
267 * select invisible attributes on objects. Otherwise attributes will be
268 * "double selected", causing them to remain unselected if using
269 * invert-selection (CONTROLKEY is pressed)
270 */
271 if( type == MULTIPLE) {
272 o_attrib_select_invisible (w_current,
273 toplevel->page_current->selection_list,
274 o_current);
275 } else {
276 /* Select all attributes of the object for a single click select */
277 o_attrib_add_selected (w_current, toplevel->page_current->selection_list,
278 o_current);
279 }
280 }
281 }
282
283 /*! \todo Finish function documentation!!!
284 * \brief
285 * \par Function Description
286 *
287 */
o_select_box_start(GschemToplevel * w_current,int w_x,int w_y)288 void o_select_box_start(GschemToplevel *w_current, int w_x, int w_y)
289 {
290 g_return_if_fail (w_current != NULL);
291
292 GschemPageView *page_view = gschem_toplevel_get_current_page_view (w_current);
293 g_return_if_fail (page_view != NULL);
294
295 int diff_x, diff_y, dist;
296
297 diff_x = abs(w_current->first_wx - w_x);
298 diff_y = abs(w_current->first_wy - w_y);
299
300 /* if we are still close to the button press location,
301 then don't enter the selection box mode */
302 dist = gschem_page_view_SCREENabs (page_view, MAX(diff_x, diff_y));
303
304 if (dist >= 10) {
305 w_current->second_wx = w_x;
306 w_current->second_wy = w_y;
307
308 i_set_state (w_current, SBOX);
309 i_action_start (w_current);
310 }
311 }
312
313 /*! \todo Finish function documentation!!!
314 * \brief
315 * \par Function Description
316 *
317 */
o_select_box_end(GschemToplevel * w_current,int w_x,int w_y)318 void o_select_box_end(GschemToplevel *w_current, int w_x, int w_y)
319 {
320 g_assert (w_current->inside_action != 0);
321
322 o_select_box_invalidate_rubber (w_current);
323 w_current->rubber_visible = 0;
324
325 o_select_box_search(w_current);
326
327 i_set_state(w_current, SELECT);
328 i_action_stop (w_current);
329 }
330
331 /*! \todo Finish function documentation!!!
332 * \brief
333 * \par Function Description
334 *
335 */
o_select_box_motion(GschemToplevel * w_current,int w_x,int w_y)336 void o_select_box_motion (GschemToplevel *w_current, int w_x, int w_y)
337 {
338 g_assert (w_current->inside_action != 0);
339
340 if (w_current->rubber_visible)
341 o_select_box_invalidate_rubber (w_current);
342
343 w_current->second_wx = w_x;
344 w_current->second_wy = w_y;
345
346 o_select_box_invalidate_rubber (w_current);
347 w_current->rubber_visible = 1;
348 }
349
350 /*! \todo Finish function documentation!!!
351 * \brief
352 * \par Function Description
353 */
o_select_box_invalidate_rubber(GschemToplevel * w_current)354 void o_select_box_invalidate_rubber (GschemToplevel *w_current)
355 {
356 g_return_if_fail (w_current != NULL);
357
358 GschemPageView *page_view = gschem_toplevel_get_current_page_view (w_current);
359 g_return_if_fail (page_view != NULL);
360
361 gschem_page_view_invalidate_world_rect (page_view,
362 w_current->first_wx,
363 w_current->first_wy,
364 w_current->second_wx,
365 w_current->second_wy);
366 }
367
368 /*! \todo Finish function documentation!!!
369 * \brief
370 * \par Function Description
371 *
372 */
o_select_box_draw_rubber(GschemToplevel * w_current,EdaRenderer * renderer)373 void o_select_box_draw_rubber (GschemToplevel *w_current, EdaRenderer *renderer)
374 {
375 o_box_draw_rubber (w_current, renderer);
376 }
377
378 /*! \todo Finish function documentation!!!
379 * \brief
380 * \par Function Description
381 *
382 */
o_select_box_search(GschemToplevel * w_current)383 void o_select_box_search(GschemToplevel *w_current)
384 {
385 LeptonObject *o_current=NULL;
386 int count = 0; /* object count */
387 int SHIFTKEY = w_current->SHIFTKEY;
388 int CONTROLKEY = w_current->CONTROLKEY;
389 int left, right, top, bottom;
390 const GList *iter;
391 gboolean show_hidden_text =
392 gschem_toplevel_get_show_hidden_text (w_current);
393
394 left = MIN(w_current->first_wx, w_current->second_wx);
395 right = MAX(w_current->first_wx, w_current->second_wx);
396 top = MIN(w_current->first_wy, w_current->second_wy);
397 bottom = MAX(w_current->first_wy, w_current->second_wy);
398
399 LeptonPage *active_page = schematic_window_get_active_page (w_current);
400
401 iter = lepton_page_objects (active_page);
402 while (iter != NULL) {
403 o_current = (LeptonObject*) iter->data;
404 /* only select visible objects */
405 if (!lepton_object_is_text (o_current) ||
406 lepton_text_object_is_visible (o_current) ||
407 show_hidden_text)
408 {
409 int cleft, ctop, cright, cbottom;
410
411 if (lepton_object_calculate_visible_bounds (o_current,
412 show_hidden_text,
413 &cleft,
414 &ctop,
415 &cright,
416 &cbottom) &&
417 cleft >= left &&
418 cright <= right &&
419 ctop >= top &&
420 cbottom <= bottom)
421 {
422
423 o_select_object(w_current, o_current, MULTIPLE, count);
424 count++;
425 }
426 }
427 iter = g_list_next (iter);
428 }
429
430 /* if there were no objects to be found in select box, count will be */
431 /* zero, and you need to deselect anything remaining (except when the */
432 /* shift or control keys are pressed) */
433 if (count == 0 && !SHIFTKEY && !CONTROLKEY) {
434 o_select_unselect_all (w_current);
435 }
436 i_update_menus(w_current);
437 }
438
439 /*! \brief Select all nets connected to the current net
440 * \par Depending on the state of the w_current->net_selection_mode variable
441 * and the net_selection_state of the current net this function will either
442 * select the single net, all directly connected nets or all nets connected
443 * with netname labels.
444 * \param [in] w_current GschemToplevel struct.
445 * \param [in] o_net Pointer to a single net object
446 */
o_select_connected_nets(GschemToplevel * w_current,LeptonObject * o_net)447 void o_select_connected_nets(GschemToplevel *w_current, LeptonObject* o_net)
448 {
449 const GList *o_iter;
450 GList *iter1;
451 LeptonObject *o_current;
452 int count=0;
453 gchar* netname;
454
455 GList *netstack = NULL;
456 GList *netnamestack = NULL;
457 GList *netnameiter;
458
459 g_assert (lepton_object_is_net (o_net));
460
461 /* If either SHIFT or CTRL are pressed, behave exactly the same as a
462 * single object selection. This makes it possible to <mouse-1> on
463 * a net segment to select it and then Shift+<mouse-1> on it to
464 * deselect it. */
465 if (w_current->SHIFTKEY || w_current->CONTROLKEY) {
466 o_select_object (w_current, o_net, SINGLE, 0);
467 return;
468 }
469
470 if (!o_net->selected) {
471 w_current->net_selection_state = 1;
472 }
473
474 /* the current net is the startpoint for the stack */
475 netstack = g_list_prepend(netstack, o_net);
476
477 count = 0;
478 while (1) {
479 netnameiter = g_list_last(netnamestack);
480 for (iter1 = g_list_last(netstack);
481 iter1 != NULL;
482 iter1 = g_list_previous(iter1), count++) {
483 o_current = (LeptonObject*) iter1->data;
484 if (lepton_object_is_net (o_current) &&
485 (!o_current->selected || count == 0)) {
486 o_select_object (w_current, o_current, SINGLE, count);
487 if (w_current->net_selection_state > 1) {
488 /* collect nets */
489 netstack = g_list_concat(s_conn_return_others(NULL, o_current), netstack);
490 }
491 if (w_current->net_selection_state > 2) {
492 /* collect netnames */
493 netname = o_attrib_search_object_attribs_by_name (o_current, "netname", 0);
494 if (netname != NULL) {
495 if (g_list_find_custom(netnamestack, netname, (GCompareFunc) strcmp) == NULL) {
496 netnamestack = g_list_append(netnamestack, netname);
497 }
498 else {
499 g_free(netname);
500 }
501 }
502 }
503 }
504 }
505 g_list_free(netstack);
506 netstack = NULL;
507
508 if (netnameiter == g_list_last(netnamestack))
509 break; /* no new netnames in the stack --> finished */
510
511 LeptonPage *active_page = schematic_window_get_active_page (w_current);
512
513 /* get all the nets of the stacked netnames */
514 for (o_iter = lepton_page_objects (active_page);
515 o_iter != NULL;
516 o_iter = g_list_next (o_iter)) {
517 o_current = (LeptonObject*) o_iter->data;
518 LeptonObject *attachment = lepton_object_get_attached_to (o_current);
519
520 if (lepton_object_is_text (o_current)
521 && attachment != NULL)
522 {
523 if (lepton_object_is_net (attachment))
524 {
525 netname = o_attrib_search_object_attribs_by_name (attachment, "netname", 0);
526 if (netname != NULL) {
527 if (g_list_find_custom(netnamestack, netname, (GCompareFunc) strcmp) != NULL) {
528 netstack = g_list_prepend (netstack, attachment);
529 }
530 g_free(netname);
531 }
532 }
533 }
534 }
535 }
536
537 w_current->net_selection_state += 1;
538 if (w_current->net_selection_state > w_current->net_selection_mode)
539 w_current->net_selection_state = 1;
540
541 for (iter1 = netnamestack; iter1 != NULL; iter1 = g_list_next(iter1))
542 g_free(iter1->data);
543 g_list_free(netnamestack);
544 }
545
546 /* This is a wrapper for o_selection_return_first_object */
547 /* This function always looks at the current page selection list */
o_select_return_first_object(GschemToplevel * w_current)548 LeptonObject *o_select_return_first_object(GschemToplevel *w_current)
549 {
550 LeptonToplevel *toplevel = gschem_toplevel_get_toplevel (w_current);
551 if (! (w_current &&
552 toplevel->page_current &&
553 lepton_list_get_glist( toplevel->page_current->selection_list )))
554 return NULL;
555 else
556 return (LeptonObject *)g_list_first( lepton_list_get_glist( toplevel->page_current->selection_list ))->data;
557 }
558
559 /*! \todo Finish function documentation!!!
560 * \brief
561 * \par Function Description
562 *
563 * \return TRUE if the selection list is not empty, otherwise false.
564 * also make sure item is valid
565 */
o_select_selected(GschemToplevel * w_current)566 int o_select_selected(GschemToplevel *w_current)
567 {
568 LeptonToplevel *toplevel = gschem_toplevel_get_toplevel (w_current);
569 if ( lepton_list_get_glist( toplevel->page_current->selection_list )) {
570 return(TRUE);
571 }
572 return(FALSE);
573 }
574
575
576 /*! \todo Finish function documentation!!!
577 * \brief
578 * \par Function Description
579 *
580 */
o_select_unselect_all(GschemToplevel * w_current)581 void o_select_unselect_all(GschemToplevel *w_current)
582 {
583 LeptonToplevel *toplevel = gschem_toplevel_get_toplevel (w_current);
584 LeptonSelection *selection = toplevel->page_current->selection_list;
585 GList *removed = NULL;
586 GList *iter;
587
588 removed = g_list_copy (lepton_list_get_glist (selection));
589 for (iter = removed; iter != NULL; iter = g_list_next (iter)) {
590 o_selection_remove (selection, (LeptonObject *) iter->data);
591 }
592
593 /* Call hooks */
594 if (removed != NULL) {
595 g_run_hook_object_list (w_current, "%deselect-objects-hook", removed);
596 }
597 }
598
599 /*! \brief Selects all visible objects on the current page.
600 * \par Function Description
601 * Clears any existing selection, then selects everything visible and
602 * unlocked on the current page, and any attached attributes whether
603 * visible or invisible..
604 *
605 * \param w_current The current #GschemToplevel structure.
606 */
607 void
o_select_visible_unlocked(GschemToplevel * w_current)608 o_select_visible_unlocked (GschemToplevel *w_current)
609 {
610 const GList *iter;
611 GList *added;
612 gboolean show_hidden_text =
613 gschem_toplevel_get_show_hidden_text (w_current);
614
615 LeptonPage *active_page = schematic_window_get_active_page (w_current);
616 LeptonSelection *selection = active_page->selection_list;
617
618 o_select_unselect_all (w_current);
619 for (iter = lepton_page_objects (active_page);
620 iter != NULL;
621 iter = g_list_next (iter)) {
622 LeptonObject *obj = (LeptonObject *) iter->data;
623
624 /* Skip invisible text objects. */
625 if (lepton_object_is_text (obj) &&
626 !lepton_text_object_is_visible (obj) &&
627 !show_hidden_text)
628 continue;
629
630 /* Skip locked objects. */
631 if (!lepton_object_get_selectable(obj)) continue;
632
633 /* Add object to selection. */
634 /*! \bug We can't call o_select_object() because it
635 * behaves differently depending on the state of
636 * w_current->SHIFTKEY and w_current->CONTROLKEY, which may well
637 * be set if this function is called via a keystroke
638 * (e.g. Ctrl-A). */
639 o_selection_add (selection, obj);
640
641 /* Add any attributes of object to selection as well. */
642 o_attrib_add_selected (w_current, selection, obj);
643 }
644
645 /* Run hooks for all items selected */
646 added = lepton_list_get_glist (selection);
647 if (added != NULL) {
648 g_run_hook_object_list (w_current, "%select-objects-hook", added);
649 }
650 }
651
652 /*! \todo Finish function documentation!!!
653 * \brief
654 * \par Function Description
655 *
656 */
657 void
o_select_move_to_place_list(GschemToplevel * w_current)658 o_select_move_to_place_list(GschemToplevel *w_current)
659 {
660 GList *selection;
661 GList *selection_copy;
662
663 LeptonPage *active_page = schematic_window_get_active_page (w_current);
664
665 /* remove the old place list if it exists */
666 lepton_object_list_delete (active_page->place_list);
667 active_page->place_list = NULL;
668
669 selection = lepton_list_get_glist( active_page->selection_list );
670 selection_copy = g_list_copy( selection );
671 active_page->place_list = selection_copy;
672 }
673