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