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