1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2017 Jon Evans <jon@craftyjon.com>
5  * Copyright (C) 2017-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation, either version 3 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <limits>
22 #include <functional>
23 using namespace std::placeholders;
24 
25 #include <bitmaps.h>
26 #include <eda_item.h>
27 #include <gerber_collectors.h>
28 #include <class_draw_panel_gal.h>
29 #include <string_utils.h>
30 #include <view/view.h>
31 #include <view/view_group.h>
32 #include <painter.h>
33 #include <tool/tool_event.h>
34 #include <tool/tool_manager.h>
35 #include <gerbview_id.h>
36 #include <gerbview_painter.h>
37 #include "gerbview_selection_tool.h"
38 #include "gerbview_actions.h"
39 
40 
41 class HIGHLIGHT_MENU : public ACTION_MENU
42 {
43 public:
HIGHLIGHT_MENU()44     HIGHLIGHT_MENU() :
45         ACTION_MENU( true )
46     {
47         SetIcon( BITMAPS::net_highlight_schematic );
48         SetTitle( _( "Highlight" ) );
49     }
50 
51 private:
52 
update()53     void update() override
54     {
55         bool addSeparator = false;
56 
57         Clear();
58 
59         const auto& selection = getToolManager()->GetTool<GERBVIEW_SELECTION_TOOL>()->GetSelection();
60 
61         if( selection.Size() == 1 )
62         {
63             auto item = static_cast<GERBER_DRAW_ITEM*>( selection[0] );
64             const auto& net_attr = item->GetNetAttributes();
65 
66             if( ( net_attr.m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_PAD ) ||
67                 ( net_attr.m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_CMP ) )
68             {
69                 auto menuEntry = Add( GERBVIEW_ACTIONS::highlightComponent );
70                 menuEntry->SetItemLabel( wxString::Format( _( "Highlight Items of Component '%s'" ),
71                                                            net_attr.m_Cmpref ) );
72                 addSeparator = true;
73             }
74 
75             if( ( net_attr.m_NetAttribType & GBR_NETLIST_METADATA::GBR_NETINFO_NET ) )
76             {
77                 auto menuEntry = Add( GERBVIEW_ACTIONS::highlightNet );
78                 menuEntry->SetItemLabel( wxString::Format( _( "Highlight Items of Net '%s'" ),
79                                                            UnescapeString( net_attr.m_Netname ) ) );
80                 addSeparator = true;
81             }
82 
83             D_CODE* apertDescr = item->GetDcodeDescr();
84 
85             if( apertDescr && !apertDescr->m_AperFunction.IsEmpty() )
86             {
87                 auto menuEntry = Add( GERBVIEW_ACTIONS::highlightAttribute );
88                 menuEntry->SetItemLabel( wxString::Format( _( "Highlight Aperture Type '%s'" ),
89                                                            apertDescr->m_AperFunction ) );
90                 addSeparator = true;
91             }
92 
93             if( apertDescr )
94             {
95                 auto menuEntry = Add( GERBVIEW_ACTIONS::highlightDCode );
96                 menuEntry->SetItemLabel( wxString::Format( _( "Highlight DCode D%d" ),
97                                                            apertDescr->m_Num_Dcode ) );
98                 addSeparator = true;
99             }
100         }
101 
102         if( addSeparator )
103             AppendSeparator();
104 
105         Add( GERBVIEW_ACTIONS::highlightClear );
106     }
107 
create() const108     ACTION_MENU* create() const override
109     {
110         return new HIGHLIGHT_MENU();
111     }
112 };
113 
114 
GERBVIEW_SELECTION_TOOL()115 GERBVIEW_SELECTION_TOOL::GERBVIEW_SELECTION_TOOL() :
116         TOOL_INTERACTIVE( "gerbview.InteractiveSelection" ),
117         m_frame( nullptr )
118 {
119     m_preliminary = true;
120 }
121 
122 
UpdateMenu(const TOOL_EVENT & aEvent)123 int GERBVIEW_SELECTION_TOOL::UpdateMenu( const TOOL_EVENT& aEvent )
124 {
125     ACTION_MENU*      actionMenu = aEvent.Parameter<ACTION_MENU*>();
126     CONDITIONAL_MENU* conditionalMenu = dynamic_cast<CONDITIONAL_MENU*>( actionMenu );
127 
128     if( conditionalMenu )
129         conditionalMenu->Evaluate( m_selection );
130 
131     if( actionMenu )
132         actionMenu->UpdateAll();
133 
134     return 0;
135 }
136 
137 
~GERBVIEW_SELECTION_TOOL()138 GERBVIEW_SELECTION_TOOL::~GERBVIEW_SELECTION_TOOL()
139 {
140     getView()->Remove( &m_selection );
141 }
142 
143 
Init()144 bool GERBVIEW_SELECTION_TOOL::Init()
145 {
146     auto selectMenu = std::make_shared<HIGHLIGHT_MENU>();
147     selectMenu->SetTool( this );
148     m_menu.AddSubMenu( selectMenu );
149 
150     auto& menu = m_menu.GetMenu();
151 
152     menu.AddMenu( selectMenu.get() );
153     menu.AddSeparator( 1000 );
154 
155     getEditFrame<GERBVIEW_FRAME>()->AddStandardSubMenus( m_menu );
156 
157     return true;
158 }
159 
160 
Reset(RESET_REASON aReason)161 void GERBVIEW_SELECTION_TOOL::Reset( RESET_REASON aReason )
162 {
163     m_frame = getEditFrame<GERBVIEW_FRAME>();
164     m_preliminary = true;
165 
166     if( aReason == TOOL_BASE::MODEL_RELOAD )
167     {
168         // Remove pointers to the selected items from containers
169         // without changing their properties (as they are already deleted
170         // while a new file is loaded)
171         m_selection.Clear();
172         getView()->GetPainter()->GetSettings()->SetHighlight( false );
173     }
174     else
175     {
176         // Restore previous properties of selected items and remove them from containers
177         clearSelection();
178     }
179 
180     // Reinsert the VIEW_GROUP, in case it was removed from the VIEW
181     getView()->Remove( &m_selection );
182     getView()->Add( &m_selection );
183 }
184 
185 
Main(const TOOL_EVENT & aEvent)186 int GERBVIEW_SELECTION_TOOL::Main( const TOOL_EVENT& aEvent )
187 {
188     // Main loop: keep receiving events
189     while( TOOL_EVENT* evt = Wait() )
190     {
191         if( m_frame->ToolStackIsEmpty() )
192             m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
193 
194         // on left click, a selection is made, depending on modifiers ALT, SHIFT, CTRL:
195         setModifiersState( evt->Modifier( MD_SHIFT ), evt->Modifier( MD_CTRL ),
196                            evt->Modifier( MD_ALT ) );
197 
198         // single click? Select single object
199         if( evt->IsClick( BUT_LEFT ) )
200         {
201             selectPoint( evt->Position() );
202         }
203         else if( evt->IsClick( BUT_RIGHT ) )
204         {
205             // right click? if there is any object - show the context menu
206             if( m_selection.Empty() )
207             {
208                 selectPoint( evt->Position() );
209                 m_selection.SetIsHover( true );
210             }
211 
212             m_menu.ShowContextMenu( m_selection );
213         }
214         else if( evt->IsDblClick( BUT_MIDDLE ) )
215         {
216             // Middle double click?  Do zoom to fit
217             m_toolMgr->RunAction( ACTIONS::zoomFitScreen, true );
218         }
219         else if( evt->IsCancel() || evt->Action() == TA_UNDO_REDO_PRE )
220         {
221             clearSelection();
222         }
223         else
224         {
225             evt->SetPassEvent();
226         }
227     }
228 
229     return 0;
230 }
231 
232 
GetSelection()233 GERBVIEW_SELECTION& GERBVIEW_SELECTION_TOOL::GetSelection()
234 {
235     return m_selection;
236 }
237 
238 
selectPoint(const VECTOR2I & aWhere,bool aOnDrag)239 bool GERBVIEW_SELECTION_TOOL::selectPoint( const VECTOR2I& aWhere, bool aOnDrag )
240 {
241     EDA_ITEM* item = nullptr;
242     GERBER_COLLECTOR collector;
243     EDA_ITEM* model = getModel<EDA_ITEM>();
244 
245     collector.Collect( model, GERBER_COLLECTOR::AllItems, wxPoint( aWhere.x, aWhere.y ) );
246 
247     // Remove unselectable items
248     for( int i = collector.GetCount() - 1; i >= 0; --i )
249     {
250         if( !selectable( collector[i] ) )
251             collector.Remove( i );
252     }
253 
254     if( collector.GetCount() > 1 )
255     {
256         if( aOnDrag )
257             Wait( TOOL_EVENT( TC_ANY, TA_MOUSE_UP, BUT_LEFT ) );
258 
259         item = disambiguationMenu( &collector );
260 
261         if( item )
262         {
263             collector.Empty();
264             collector.Append( item );
265         }
266     }
267 
268     if( !m_additive && !m_subtractive && !m_exclusive_or )
269         clearSelection();
270 
271     if( collector.GetCount() == 1 )
272     {
273         item = collector[ 0 ];
274 
275         if( m_subtractive || ( m_exclusive_or && item->IsSelected() ) )
276         {
277             unselect( item );
278             m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
279             return false;
280         }
281         else
282         {
283             select( item );
284             m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
285             return true;
286         }
287     }
288 
289     return false;
290 }
291 
292 
selectCursor(bool aSelectAlways)293 bool GERBVIEW_SELECTION_TOOL::selectCursor( bool aSelectAlways )
294 {
295     if( aSelectAlways || m_selection.Empty() )
296     {
297         clearSelection();
298         selectPoint( getViewControls()->GetCursorPosition( false ) );
299     }
300 
301     return !m_selection.Empty();
302 }
303 
304 
setTransitions()305 void GERBVIEW_SELECTION_TOOL::setTransitions()
306 {
307     Go( &GERBVIEW_SELECTION_TOOL::UpdateMenu,     ACTIONS::updateMenu.MakeEvent() );
308     Go( &GERBVIEW_SELECTION_TOOL::Main,           GERBVIEW_ACTIONS::selectionActivate.MakeEvent() );
309     Go( &GERBVIEW_SELECTION_TOOL::ClearSelection, GERBVIEW_ACTIONS::selectionClear.MakeEvent() );
310     Go( &GERBVIEW_SELECTION_TOOL::SelectItem,     GERBVIEW_ACTIONS::selectItem.MakeEvent() );
311     Go( &GERBVIEW_SELECTION_TOOL::UnselectItem,   GERBVIEW_ACTIONS::unselectItem.MakeEvent() );
312 }
313 
314 
ClearSelection(const TOOL_EVENT & aEvent)315 int GERBVIEW_SELECTION_TOOL::ClearSelection( const TOOL_EVENT& aEvent )
316 {
317     clearSelection();
318 
319     return 0;
320 }
321 
322 
SelectItems(const TOOL_EVENT & aEvent)323 int GERBVIEW_SELECTION_TOOL::SelectItems( const TOOL_EVENT& aEvent )
324 {
325     std::vector<EDA_ITEM*>* items = aEvent.Parameter<std::vector<EDA_ITEM*>*>();
326 
327     if( items )
328     {
329         // Perform individual selection of each item before processing the event.
330         for( EDA_ITEM* item : *items )
331             select( item );
332 
333         m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
334     }
335 
336     return 0;
337 }
338 
339 
SelectItem(const TOOL_EVENT & aEvent)340 int GERBVIEW_SELECTION_TOOL::SelectItem( const TOOL_EVENT& aEvent )
341 {
342     // Check if there is an item to be selected
343     EDA_ITEM* item = aEvent.Parameter<EDA_ITEM*>();
344 
345     if( item )
346     {
347         select( item );
348 
349         // Inform other potentially interested tools
350         m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
351     }
352 
353     return 0;
354 }
355 
356 
UnselectItems(const TOOL_EVENT & aEvent)357 int GERBVIEW_SELECTION_TOOL::UnselectItems( const TOOL_EVENT& aEvent )
358 {
359     std::vector<EDA_ITEM*>* items = aEvent.Parameter<std::vector<EDA_ITEM*>*>();
360 
361     if( items )
362     {
363         // Perform individual unselection of each item before processing the event
364         for( EDA_ITEM* item : *items )
365             unselect( item );
366 
367         m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
368     }
369 
370     return 0;
371 }
372 
373 
UnselectItem(const TOOL_EVENT & aEvent)374 int GERBVIEW_SELECTION_TOOL::UnselectItem( const TOOL_EVENT& aEvent )
375 {
376     // Check if there is an item to be selected
377     EDA_ITEM* item = aEvent.Parameter<EDA_ITEM*>();
378 
379     if( item )
380     {
381         unselect( item );
382 
383         // Inform other potentially interested tools
384         m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
385     }
386 
387     return 0;
388 }
389 
390 
clearSelection()391 void GERBVIEW_SELECTION_TOOL::clearSelection()
392 {
393     if( m_selection.Empty() )
394         return;
395 
396     for( EDA_ITEM* item : m_selection )
397         unselectVisually( item );
398 
399     m_selection.Clear();
400 
401     // Inform other potentially interested tools
402     m_toolMgr->ProcessEvent( EVENTS::ClearedEvent );
403 }
404 
405 
disambiguationMenu(GERBER_COLLECTOR * aCollector)406 EDA_ITEM* GERBVIEW_SELECTION_TOOL::disambiguationMenu( GERBER_COLLECTOR* aCollector )
407 {
408     EDA_ITEM* current = nullptr;
409     KIGFX::VIEW_GROUP highlightGroup;
410     ACTION_MENU menu( true );
411 
412     highlightGroup.SetLayer( LAYER_SELECT_OVERLAY );
413     getView()->Add( &highlightGroup );
414 
415     int limit = std::min( 10, aCollector->GetCount() );
416 
417     for( int i = 0; i < limit; ++i )
418     {
419         wxString text;
420         EDA_ITEM* item = ( *aCollector )[i];
421         text = item->GetSelectMenuText( m_frame->GetUserUnits() );
422         menu.Add( text, i + 1, item->GetMenuImage() );
423     }
424 
425     if( aCollector->m_MenuTitle.Length() )
426     {
427         menu.SetTitle( aCollector->m_MenuTitle );
428         menu.SetIcon( BITMAPS::info );
429         menu.DisplayTitle( true );
430     }
431     else
432     {
433         menu.DisplayTitle( false );
434     }
435 
436     SetContextMenu( &menu, CMENU_NOW );
437 
438     while( TOOL_EVENT* evt = Wait() )
439     {
440         if( evt->Action() == TA_CHOICE_MENU_UPDATE )
441         {
442             if( current )
443             {
444                 current->ClearBrightened();
445                 getView()->Hide( current, false );
446                 highlightGroup.Remove( current );
447                 getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
448             }
449 
450             int id = *evt->GetCommandId();
451 
452             // User has pointed an item, so show it in a different way
453             if( id > 0 && id <= limit )
454             {
455                 current = ( *aCollector )[id - 1];
456                 current->SetBrightened();
457                 getView()->Hide( current, true );
458                 highlightGroup.Add( current );
459                 getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
460             }
461             else
462             {
463                 current = nullptr;
464             }
465         }
466         else if( evt->Action() == TA_CHOICE_MENU_CHOICE )
467         {
468             OPT<int> id = evt->GetCommandId();
469 
470             // User has selected an item, so this one will be returned
471             if( id && ( *id > 0 ) )
472                 current = ( *aCollector )[*id - 1];
473             else
474                 current = nullptr;
475 
476             break;
477         }
478     }
479 
480     if( current && current->IsBrightened() )
481     {
482         current->ClearBrightened();
483         getView()->Hide( current, false );
484         getView()->MarkTargetDirty( KIGFX::TARGET_OVERLAY );
485     }
486 
487     getView()->Remove( &highlightGroup );
488 
489     return current;
490 }
491 
492 
selectable(const EDA_ITEM * aItem) const493 bool GERBVIEW_SELECTION_TOOL::selectable( const EDA_ITEM* aItem ) const
494 {
495     GERBVIEW_FRAME*         frame = getEditFrame<GERBVIEW_FRAME>();
496     const GERBER_DRAW_ITEM* item = static_cast<const GERBER_DRAW_ITEM*>( aItem );
497     int                     layer = item->GetLayer();
498 
499 
500     if( item->GetLayerPolarity() )
501     {
502         // Don't allow selection of invisible negative items
503         auto rs = static_cast<KIGFX::GERBVIEW_RENDER_SETTINGS*>( getView()->GetPainter()->GetSettings() );
504 
505         if( !rs->IsShowNegativeItems() )
506             return false;
507     }
508 
509     // We do not want to select items that are in the background
510     if( frame->GetDisplayOptions().m_HighContrastMode && layer != frame->GetActiveLayer() )
511         return false;
512 
513     return frame->IsLayerVisible( layer );
514 }
515 
516 
select(EDA_ITEM * aItem)517 void GERBVIEW_SELECTION_TOOL::select( EDA_ITEM* aItem )
518 {
519     if( aItem->IsSelected() )
520         return;
521 
522     m_selection.Add( aItem );
523     getView()->Add( &m_selection, std::numeric_limits<int>::max() );
524     selectVisually( aItem );
525 }
526 
527 
unselect(EDA_ITEM * aItem)528 void GERBVIEW_SELECTION_TOOL::unselect( EDA_ITEM* aItem )
529 {
530     if( !aItem->IsSelected() )
531         return;
532 
533     unselectVisually( aItem );
534     m_selection.Remove( aItem );
535 
536     if( m_selection.Empty() )
537         getView()->Remove( &m_selection );
538 }
539 
540 
selectVisually(EDA_ITEM * aItem)541 void GERBVIEW_SELECTION_TOOL::selectVisually( EDA_ITEM* aItem )
542 {
543     // Move the item's layer to the front
544     int layer = static_cast<GERBER_DRAW_ITEM*>( aItem )->GetLayer();
545     m_frame->SetActiveLayer( layer, true );
546 
547     // Hide the original item, so it is shown only on overlay
548     aItem->SetSelected();
549     getView()->Hide( aItem, true );
550 
551     getView()->Update( &m_selection );
552 }
553 
554 
unselectVisually(EDA_ITEM * aItem)555 void GERBVIEW_SELECTION_TOOL::unselectVisually( EDA_ITEM* aItem )
556 {
557     // Restore original item visibility
558     aItem->ClearSelected();
559     getView()->Hide( aItem, false );
560     getView()->Update( aItem, KIGFX::ALL );
561 
562     getView()->Update( &m_selection );
563 }
564