You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

430 lines
12 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2021-2023 KiCad Developers, see AUTHORS.TXT for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 3
  9. * of the License, or (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, you may find one here:
  18. * http://www.gnu.org/licenses/gpl-3.0.html
  19. * or you may search the http://www.gnu.org website for the version 3 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <bitmaps.h>
  24. #include <widgets/ui_common.h>
  25. #include <collector.h>
  26. #include <tool/tool_manager.h>
  27. #include <tool/actions.h>
  28. #include <tool/selection_tool.h>
  29. #include <view/view.h>
  30. #include <eda_draw_frame.h>
  31. SELECTION_TOOL::SELECTION_TOOL( const std::string& aName ) :
  32. TOOL_INTERACTIVE( aName ),
  33. m_additive( false ),
  34. m_subtractive( false ),
  35. m_exclusive_or( false ),
  36. m_multiple( false ),
  37. m_skip_heuristics( false ),
  38. m_highlight_modifier( false ),
  39. m_drag_additive( false ),
  40. m_drag_subtractive( false ),
  41. m_canceledMenu( false )
  42. {
  43. }
  44. void SELECTION_TOOL::setModifiersState( bool aShiftState, bool aCtrlState, bool aAltState )
  45. {
  46. // Set the configuration of m_additive, m_subtractive, m_exclusive_or from the state of
  47. // modifier keys SHIFT and CTRL
  48. // ALT key cannot be used on MSW because of a conflict with the system menu
  49. m_subtractive = aCtrlState && aShiftState;
  50. m_additive = !aCtrlState && aShiftState;
  51. if( ctrlClickHighlights() )
  52. {
  53. m_exclusive_or = false;
  54. m_highlight_modifier = aCtrlState && !aShiftState;
  55. }
  56. else
  57. {
  58. m_exclusive_or = aCtrlState && !aShiftState;
  59. m_highlight_modifier = false;
  60. }
  61. // Drag is more forgiving and allows either Ctrl+Drag or Shift+Drag to add to the selection
  62. // Note, however that we cannot provide disambiguation at the same time as the box selection
  63. m_drag_additive = ( aCtrlState || aShiftState ) && !aAltState;
  64. m_drag_subtractive = aCtrlState && aShiftState && !aAltState;
  65. // While the ALT key has some conflicts under MSW (and some flavors of Linux WMs), it remains
  66. // useful for users who only use tap-click rather than holding the button. We disable it for
  67. // windows because it flashes the disambiguation menu without showing data
  68. #ifndef __WINDOWS__
  69. m_skip_heuristics = aAltState;
  70. #else
  71. m_skip_heuristics = false;
  72. #endif
  73. }
  74. bool SELECTION_TOOL::hasModifier()
  75. {
  76. return m_subtractive || m_additive || m_exclusive_or;
  77. }
  78. int SELECTION_TOOL::UpdateMenu( const TOOL_EVENT& aEvent )
  79. {
  80. ACTION_MENU* actionMenu = aEvent.Parameter<ACTION_MENU*>();
  81. CONDITIONAL_MENU* conditionalMenu = dynamic_cast<CONDITIONAL_MENU*>( actionMenu );
  82. if( conditionalMenu )
  83. conditionalMenu->Evaluate( selection() );
  84. if( actionMenu )
  85. actionMenu->UpdateAll();
  86. return 0;
  87. }
  88. int SELECTION_TOOL::AddItemToSel( const TOOL_EVENT& aEvent )
  89. {
  90. AddItemToSel( aEvent.Parameter<EDA_ITEM*>() );
  91. selection().SetIsHover( false );
  92. return 0;
  93. }
  94. void SELECTION_TOOL::AddItemToSel( EDA_ITEM* aItem, bool aQuietMode )
  95. {
  96. if( aItem )
  97. {
  98. select( aItem );
  99. // Inform other potentially interested tools
  100. if( !aQuietMode )
  101. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  102. }
  103. }
  104. int SELECTION_TOOL::ReselectItem( const TOOL_EVENT& aEvent )
  105. {
  106. RemoveItemFromSel( aEvent.Parameter<EDA_ITEM*>() );
  107. selection().SetIsHover( false );
  108. AddItemToSel( aEvent.Parameter<EDA_ITEM*>() );
  109. selection().SetIsHover( false );
  110. return 0;
  111. }
  112. int SELECTION_TOOL::AddItemsToSel( const TOOL_EVENT& aEvent )
  113. {
  114. AddItemsToSel( aEvent.Parameter<EDA_ITEMS*>(), false );
  115. selection().SetIsHover( false );
  116. return 0;
  117. }
  118. void SELECTION_TOOL::AddItemsToSel( EDA_ITEMS* aList, bool aQuietMode )
  119. {
  120. if( aList )
  121. {
  122. for( EDA_ITEM* item : *aList )
  123. select( item );
  124. // Inform other potentially interested tools
  125. if( !aQuietMode )
  126. m_toolMgr->ProcessEvent( EVENTS::SelectedEvent );
  127. }
  128. }
  129. int SELECTION_TOOL::RemoveItemFromSel( const TOOL_EVENT& aEvent )
  130. {
  131. RemoveItemFromSel( aEvent.Parameter<EDA_ITEM*>() );
  132. selection().SetIsHover( false );
  133. return 0;
  134. }
  135. void SELECTION_TOOL::RemoveItemFromSel( EDA_ITEM* aItem, bool aQuietMode )
  136. {
  137. if( aItem )
  138. {
  139. unselect( aItem );
  140. // Inform other potentially interested tools
  141. if( !aQuietMode )
  142. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  143. }
  144. }
  145. int SELECTION_TOOL::RemoveItemsFromSel( const TOOL_EVENT& aEvent )
  146. {
  147. RemoveItemsFromSel( aEvent.Parameter<EDA_ITEMS*>(), false );
  148. selection().SetIsHover( false );
  149. return 0;
  150. }
  151. void SELECTION_TOOL::RemoveItemsFromSel( EDA_ITEMS* aList, bool aQuietMode )
  152. {
  153. if( aList )
  154. {
  155. for( EDA_ITEM* item : *aList )
  156. unselect( item );
  157. // Inform other potentially interested tools
  158. if( !aQuietMode )
  159. m_toolMgr->ProcessEvent( EVENTS::UnselectedEvent );
  160. }
  161. }
  162. void SELECTION_TOOL::RemoveItemsFromSel( std::vector<KIID>* aList, bool aQuietMode )
  163. {
  164. EDA_ITEMS removeItems;
  165. for( EDA_ITEM* item : selection() )
  166. {
  167. if( alg::contains( *aList, item->m_Uuid ) )
  168. removeItems.push_back( item );
  169. }
  170. RemoveItemsFromSel( &removeItems, aQuietMode );
  171. }
  172. void SELECTION_TOOL::BrightenItem( EDA_ITEM* aItem )
  173. {
  174. highlight( aItem, BRIGHTENED );
  175. }
  176. void SELECTION_TOOL::UnbrightenItem( EDA_ITEM* aItem )
  177. {
  178. unhighlight( aItem, BRIGHTENED );
  179. }
  180. void SELECTION_TOOL::onDisambiguationExpire( wxTimerEvent& aEvent )
  181. {
  182. // If there is a multiple selection then it's more likely that we're seeing a paused drag
  183. // than a long-click.
  184. if( selection().GetSize() >= 2 && !hasModifier() )
  185. return;
  186. // If another tool has since started running then we don't want to interrupt
  187. if( !getEditFrame<EDA_DRAW_FRAME>()->ToolStackIsEmpty() )
  188. return;
  189. m_toolMgr->ProcessEvent( EVENTS::DisambiguatePoint );
  190. }
  191. int SELECTION_TOOL::SelectionMenu( const TOOL_EVENT& aEvent )
  192. {
  193. COLLECTOR* collector = aEvent.Parameter<COLLECTOR*>();
  194. if( !doSelectionMenu( collector ) )
  195. collector->m_MenuCancelled = true;
  196. return 0;
  197. }
  198. bool SELECTION_TOOL::doSelectionMenu( COLLECTOR* aCollector )
  199. {
  200. UNITS_PROVIDER* unitsProvider = getEditFrame<EDA_DRAW_FRAME>();
  201. EDA_ITEM* current = nullptr;
  202. SELECTION highlightGroup;
  203. bool selectAll = false;
  204. bool expandSelection = false;
  205. highlightGroup.SetLayer( LAYER_SELECT_OVERLAY );
  206. getView()->Add( &highlightGroup );
  207. do
  208. {
  209. /// The user has requested the full, non-limited list of selection items
  210. if( expandSelection )
  211. aCollector->Combine();
  212. expandSelection = false;
  213. int limit = std::min( 100, aCollector->GetCount() );
  214. ACTION_MENU menu( true );
  215. for( int i = 0; i < limit; ++i )
  216. {
  217. EDA_ITEM* item = ( *aCollector )[i];
  218. wxString menuText;
  219. if( i < 9 )
  220. {
  221. #ifdef __WXMAC__
  222. menuText = wxString::Format( "%s\t%d",
  223. item->GetItemDescription( unitsProvider ),
  224. i + 1 );
  225. #else
  226. menuText = wxString::Format( "&%d %s\t%d",
  227. i + 1,
  228. item->GetItemDescription( unitsProvider ),
  229. i + 1 );
  230. #endif
  231. }
  232. else
  233. {
  234. menuText = item->GetItemDescription( unitsProvider );
  235. }
  236. menu.Add( menuText, i + 1, item->GetMenuImage() );
  237. }
  238. menu.AppendSeparator();
  239. menu.Add( _( "Select &All\tA" ), limit + 1, BITMAPS::INVALID_BITMAP );
  240. if( !expandSelection && aCollector->HasAdditionalItems() )
  241. menu.Add( _( "&Expand Selection\tE" ), limit + 2, BITMAPS::INVALID_BITMAP );
  242. if( aCollector->m_MenuTitle.Length() )
  243. {
  244. menu.SetTitle( aCollector->m_MenuTitle );
  245. menu.SetIcon( BITMAPS::info );
  246. menu.DisplayTitle( true );
  247. }
  248. else
  249. {
  250. menu.DisplayTitle( false );
  251. }
  252. SetContextMenu( &menu, CMENU_NOW );
  253. while( TOOL_EVENT* evt = Wait() )
  254. {
  255. if( evt->Action() == TA_CHOICE_MENU_UPDATE )
  256. {
  257. if( selectAll )
  258. {
  259. for( int i = 0; i < aCollector->GetCount(); ++i )
  260. unhighlight( ( *aCollector )[i], BRIGHTENED, &highlightGroup );
  261. }
  262. else if( current )
  263. {
  264. unhighlight( current, BRIGHTENED, &highlightGroup );
  265. }
  266. int id = *evt->GetCommandId();
  267. // User has pointed an item, so show it in a different way
  268. if( id > 0 && id <= limit )
  269. {
  270. current = ( *aCollector )[id - 1];
  271. highlight( current, BRIGHTENED, &highlightGroup );
  272. }
  273. else
  274. {
  275. current = nullptr;
  276. }
  277. // User has pointed on the "Select All" option
  278. if( id == limit + 1 )
  279. {
  280. for( int i = 0; i < aCollector->GetCount(); ++i )
  281. highlight( ( *aCollector )[i], BRIGHTENED, &highlightGroup );
  282. selectAll = true;
  283. }
  284. else
  285. {
  286. selectAll = false;
  287. }
  288. }
  289. else if( evt->Action() == TA_CHOICE_MENU_CHOICE )
  290. {
  291. if( selectAll )
  292. {
  293. for( int i = 0; i < aCollector->GetCount(); ++i )
  294. unhighlight( ( *aCollector )[i], BRIGHTENED, &highlightGroup );
  295. }
  296. else if( current )
  297. {
  298. unhighlight( current, BRIGHTENED, &highlightGroup );
  299. }
  300. std::optional<int> id = evt->GetCommandId();
  301. // User has selected the "Select All" option
  302. if( id == limit + 1 )
  303. {
  304. selectAll = true;
  305. current = nullptr;
  306. }
  307. // User has selected the "Expand Selection" option
  308. else if( id == limit + 2 )
  309. {
  310. selectAll = false;
  311. current = nullptr;
  312. expandSelection = true;
  313. }
  314. // User has selected an item, so this one will be returned
  315. else if( id && ( *id > 0 ) && ( *id <= limit ) )
  316. {
  317. selectAll = false;
  318. current = ( *aCollector )[*id - 1];
  319. }
  320. // User has cancelled the menu (either by <esc> or clicking out of it)
  321. else
  322. {
  323. selectAll = false;
  324. current = nullptr;
  325. }
  326. }
  327. else if( evt->Action() == TA_CHOICE_MENU_CLOSED )
  328. {
  329. break;
  330. }
  331. getView()->UpdateItems();
  332. getEditFrame<EDA_DRAW_FRAME>()->GetCanvas()->Refresh();
  333. }
  334. } while( expandSelection );
  335. getView()->Remove( &highlightGroup );
  336. if( selectAll )
  337. {
  338. return true;
  339. }
  340. else if( current )
  341. {
  342. aCollector->Empty();
  343. aCollector->Append( current );
  344. return true;
  345. }
  346. return false;
  347. }