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.

865 lines
28 KiB

3 years ago
3 years ago
3 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019 CERN
  5. * Copyright (C) 2019-2021 KiCad Developers, see CHANGELOG.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <algorithm>
  25. #include <advanced_config.h>
  26. #include <bitmaps.h>
  27. #include <bitmap_store.h>
  28. #include <eda_draw_frame.h>
  29. #include <functional>
  30. #include <kiplatform/ui.h>
  31. #include <math/util.h>
  32. #include <memory>
  33. #include <pgm_base.h>
  34. #include <settings/common_settings.h>
  35. #include <tool/action_toolbar.h>
  36. #include <tool/actions.h>
  37. #include <tool/tool_action.h>
  38. #include <tool/tool_event.h>
  39. #include <tool/tool_interactive.h>
  40. #include <tool/tool_manager.h>
  41. #include <widgets/bitmap_button.h>
  42. #include <widgets/wx_aui_art_providers.h>
  43. #include <wx/popupwin.h>
  44. #include <wx/renderer.h>
  45. #include <wx/sizer.h>
  46. #include <wx/dcclient.h>
  47. #include <wx/settings.h>
  48. ACTION_GROUP::ACTION_GROUP( const std::string& aName,
  49. const std::vector<const TOOL_ACTION*>& aActions )
  50. {
  51. wxASSERT_MSG( aActions.size() > 0, wxS( "Action groups must have at least one action" ) );
  52. // The default action is just the first action in the vector
  53. m_actions = aActions;
  54. m_defaultAction = m_actions[0];
  55. m_name = aName;
  56. m_id = ACTION_MANAGER::MakeActionId( m_name );
  57. }
  58. int ACTION_GROUP::GetUIId() const
  59. {
  60. return m_id + TOOL_ACTION::GetBaseUIId();
  61. }
  62. void ACTION_GROUP::SetDefaultAction( const TOOL_ACTION& aDefault )
  63. {
  64. bool valid = std::any_of( m_actions.begin(), m_actions.end(),
  65. [&]( const TOOL_ACTION* aAction ) -> bool
  66. {
  67. // For some reason, we can't compare the actions directly
  68. return aAction->GetId() == aDefault.GetId();
  69. } );
  70. wxASSERT_MSG( valid, wxS( "Action must be present in a group to be the default" ) );
  71. m_defaultAction = &aDefault;
  72. }
  73. #define PALETTE_BORDER 4 // The border around the palette buttons on all sides
  74. #define BUTTON_BORDER 1 // The border on the sides of the buttons that touch other buttons
  75. ACTION_TOOLBAR_PALETTE::ACTION_TOOLBAR_PALETTE( wxWindow* aParent, bool aVertical ) :
  76. wxPopupTransientWindow( aParent, wxBORDER_NONE ),
  77. m_group( nullptr ),
  78. m_isVertical( aVertical ),
  79. m_panel( nullptr ),
  80. m_mainSizer( nullptr ),
  81. m_buttonSizer( nullptr )
  82. {
  83. m_panel = new wxPanel( this, wxID_ANY );
  84. m_panel->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
  85. // This sizer holds the buttons for the actions
  86. m_buttonSizer = new wxBoxSizer( aVertical ? wxVERTICAL : wxHORIZONTAL );
  87. // This sizer holds the other sizer, so that a consistent border is present on all sides
  88. m_mainSizer = new wxBoxSizer( aVertical ? wxVERTICAL : wxHORIZONTAL );
  89. m_mainSizer->Add( m_buttonSizer, wxSizerFlags().Border( wxALL, PALETTE_BORDER ) );
  90. m_panel->SetSizer( m_mainSizer );
  91. Connect( wxEVT_CHAR_HOOK, wxCharEventHandler( ACTION_TOOLBAR_PALETTE::onCharHook ),
  92. nullptr, this );
  93. }
  94. void ACTION_TOOLBAR_PALETTE::AddAction( const TOOL_ACTION& aAction )
  95. {
  96. wxBitmapBundle normalBmp = KiBitmapBundle( aAction.GetIcon() );
  97. int bmpWidth = normalBmp.GetPreferredBitmapSizeFor( this ).GetWidth();
  98. int padding = ( m_buttonSize.GetWidth() - bmpWidth ) / 2;
  99. int size = Pgm().GetCommonSettings()->m_Appearance.toolbar_icon_size;
  100. wxSize bmSize( size, size );
  101. bmSize *= KIPLATFORM::UI::GetPixelScaleFactor( m_parent );
  102. BITMAP_BUTTON* button = new BITMAP_BUTTON( m_panel, aAction.GetUIId(), wxDefaultPosition,
  103. bmSize );
  104. button->SetIsToolbarButton();
  105. button->SetBitmap( normalBmp );
  106. button->SetDisabledBitmap( KiDisabledBitmapBundle( aAction.GetIcon() ) );
  107. button->SetPadding( padding );
  108. button->SetToolTip( aAction.GetButtonTooltip() );
  109. button->AcceptDragInAsClick();
  110. button->SetBitmapCentered();
  111. m_buttons[aAction.GetUIId()] = button;
  112. if( m_isVertical )
  113. m_buttonSizer->Add( button, wxSizerFlags().Border( wxTOP | wxBOTTOM, BUTTON_BORDER ) );
  114. else
  115. m_buttonSizer->Add( button, wxSizerFlags().Border( wxLEFT | wxRIGHT, BUTTON_BORDER ) );
  116. m_buttonSizer->Layout();
  117. }
  118. void ACTION_TOOLBAR_PALETTE::EnableAction( const TOOL_ACTION& aAction, bool aEnable )
  119. {
  120. auto it = m_buttons.find( aAction.GetUIId() );
  121. if( it != m_buttons.end() )
  122. it->second->Enable( aEnable );
  123. }
  124. void ACTION_TOOLBAR_PALETTE::CheckAction( const TOOL_ACTION& aAction, bool aCheck )
  125. {
  126. auto it = m_buttons.find( aAction.GetUIId() );
  127. if( it != m_buttons.end() )
  128. it->second->Check( aCheck );
  129. }
  130. void ACTION_TOOLBAR_PALETTE::Popup( wxWindow* aFocus )
  131. {
  132. m_mainSizer->Fit( m_panel );
  133. SetClientSize( m_panel->GetSize() );
  134. wxPopupTransientWindow::Popup( aFocus );
  135. }
  136. void ACTION_TOOLBAR_PALETTE::onCharHook( wxKeyEvent& aEvent )
  137. {
  138. // Allow the escape key to dismiss this popup
  139. if( aEvent.GetKeyCode() == WXK_ESCAPE )
  140. Dismiss();
  141. else
  142. aEvent.Skip();
  143. }
  144. ACTION_TOOLBAR::ACTION_TOOLBAR( EDA_BASE_FRAME* parent, wxWindowID id, const wxPoint& pos,
  145. const wxSize& size, long style ) :
  146. wxAuiToolBar( parent, id, pos, size, style ),
  147. m_paletteTimer( nullptr ),
  148. m_auiManager( nullptr ),
  149. m_toolManager( parent->GetToolManager() ),
  150. m_palette( nullptr )
  151. {
  152. m_paletteTimer = new wxTimer( this );
  153. SetArtProvider( new WX_AUI_TOOLBAR_ART );
  154. Connect( wxEVT_COMMAND_TOOL_CLICKED, wxAuiToolBarEventHandler( ACTION_TOOLBAR::onToolEvent ),
  155. nullptr, this );
  156. Connect( wxEVT_AUITOOLBAR_RIGHT_CLICK,
  157. wxAuiToolBarEventHandler( ACTION_TOOLBAR::onToolRightClick ),
  158. nullptr, this );
  159. Connect( wxEVT_AUITOOLBAR_BEGIN_DRAG, wxAuiToolBarEventHandler( ACTION_TOOLBAR::onItemDrag ),
  160. nullptr, this );
  161. Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( ACTION_TOOLBAR::onMouseClick ), nullptr, this );
  162. Connect( wxEVT_LEFT_UP, wxMouseEventHandler( ACTION_TOOLBAR::onMouseClick ), nullptr, this );
  163. Connect( m_paletteTimer->GetId(), wxEVT_TIMER,
  164. wxTimerEventHandler( ACTION_TOOLBAR::onTimerDone ), nullptr, this );
  165. Bind( wxEVT_SYS_COLOUR_CHANGED,
  166. wxSysColourChangedEventHandler( ACTION_TOOLBAR::onThemeChanged ), this );
  167. }
  168. ACTION_TOOLBAR::~ACTION_TOOLBAR()
  169. {
  170. Disconnect( wxEVT_COMMAND_TOOL_CLICKED, wxAuiToolBarEventHandler( ACTION_TOOLBAR::onToolEvent ),
  171. nullptr, this );
  172. Disconnect( wxEVT_AUITOOLBAR_RIGHT_CLICK,
  173. wxAuiToolBarEventHandler( ACTION_TOOLBAR::onToolRightClick ), nullptr, this );
  174. Disconnect( wxEVT_AUITOOLBAR_BEGIN_DRAG, wxAuiToolBarEventHandler( ACTION_TOOLBAR::onItemDrag ),
  175. nullptr, this );
  176. Disconnect( wxEVT_LEFT_DOWN, wxMouseEventHandler( ACTION_TOOLBAR::onMouseClick ), nullptr,
  177. this );
  178. Disconnect( wxEVT_LEFT_UP, wxMouseEventHandler( ACTION_TOOLBAR::onMouseClick ), nullptr, this );
  179. Disconnect( m_paletteTimer->GetId(), wxEVT_TIMER,
  180. wxTimerEventHandler( ACTION_TOOLBAR::onTimerDone ), nullptr, this );
  181. Unbind( wxEVT_SYS_COLOUR_CHANGED,
  182. wxSysColourChangedEventHandler( ACTION_TOOLBAR::onThemeChanged ), this );
  183. delete m_paletteTimer;
  184. // Clear all the maps keeping track of our items on the toolbar
  185. m_toolMenus.clear();
  186. m_actionGroups.clear();
  187. m_toolCancellable.clear();
  188. m_toolKinds.clear();
  189. m_toolActions.clear();
  190. }
  191. void ACTION_TOOLBAR::Add( const TOOL_ACTION& aAction, bool aIsToggleEntry, bool aIsCancellable )
  192. {
  193. wxASSERT( GetParent() );
  194. wxASSERT_MSG( !( aIsCancellable && !aIsToggleEntry ), wxS( "aIsCancellable requires aIsToggleEntry" ) );
  195. int toolId = aAction.GetUIId();
  196. AddTool( toolId, wxEmptyString, KiBitmapBundle( aAction.GetIcon() ),
  197. KiDisabledBitmapBundle( aAction.GetIcon() ),
  198. aIsToggleEntry ? wxITEM_CHECK : wxITEM_NORMAL,
  199. aAction.GetButtonTooltip(), wxEmptyString, nullptr );
  200. m_toolKinds[ toolId ] = aIsToggleEntry;
  201. m_toolActions[ toolId ] = &aAction;
  202. m_toolCancellable[ toolId ] = aIsCancellable;
  203. }
  204. void ACTION_TOOLBAR::AddButton( const TOOL_ACTION& aAction )
  205. {
  206. int toolId = aAction.GetUIId();
  207. AddTool( toolId, wxEmptyString, KiBitmapBundle( aAction.GetIcon() ),
  208. KiDisabledBitmapBundle( aAction.GetIcon() ), wxITEM_NORMAL,
  209. aAction.GetButtonTooltip(), wxEmptyString, nullptr );
  210. m_toolKinds[ toolId ] = false;
  211. m_toolActions[ toolId ] = &aAction;
  212. }
  213. void ACTION_TOOLBAR::AddScaledSeparator( wxWindow* aWindow )
  214. {
  215. int scale = KiIconScale( aWindow );
  216. if( scale > 4 )
  217. AddSpacer( 16 * ( scale - 4 ) / 4 );
  218. AddSeparator();
  219. if( scale > 4 )
  220. AddSpacer( 16 * ( scale - 4 ) / 4 );
  221. }
  222. void ACTION_TOOLBAR::AddToolContextMenu( const TOOL_ACTION& aAction,
  223. std::unique_ptr<ACTION_MENU> aMenu )
  224. {
  225. int toolId = aAction.GetUIId();
  226. m_toolMenus[toolId] = std::move( aMenu );
  227. }
  228. void ACTION_TOOLBAR::AddGroup( ACTION_GROUP* aGroup, bool aIsToggleEntry )
  229. {
  230. int groupId = aGroup->GetUIId();
  231. const TOOL_ACTION* defaultAction = aGroup->GetDefaultAction();
  232. wxASSERT( GetParent() );
  233. wxASSERT( defaultAction );
  234. m_toolKinds[ groupId ] = aIsToggleEntry;
  235. m_toolActions[ groupId ] = defaultAction;
  236. m_actionGroups[ groupId ] = aGroup;
  237. // Add the main toolbar item representing the group
  238. AddTool( groupId, wxEmptyString, KiBitmapBundle( defaultAction->GetIcon() ),
  239. KiDisabledBitmapBundle( defaultAction->GetIcon() ),
  240. aIsToggleEntry ? wxITEM_CHECK : wxITEM_NORMAL,
  241. wxEmptyString, wxEmptyString, nullptr );
  242. // Select the default action
  243. doSelectAction( aGroup, *defaultAction );
  244. }
  245. void ACTION_TOOLBAR::SelectAction( ACTION_GROUP* aGroup, const TOOL_ACTION& aAction )
  246. {
  247. bool valid = std::any_of( aGroup->m_actions.begin(), aGroup->m_actions.end(),
  248. [&]( const TOOL_ACTION* action2 ) -> bool
  249. {
  250. // For some reason, we can't compare the actions directly
  251. return aAction.GetId() == action2->GetId();
  252. } );
  253. if( valid )
  254. doSelectAction( aGroup, aAction );
  255. }
  256. void ACTION_TOOLBAR::doSelectAction( ACTION_GROUP* aGroup, const TOOL_ACTION& aAction )
  257. {
  258. wxASSERT( GetParent() );
  259. int groupId = aGroup->GetUIId();
  260. wxAuiToolBarItem* item = FindTool( groupId );
  261. if( !item )
  262. return;
  263. // Update the item information
  264. item->SetShortHelp( aAction.GetTooltip() );
  265. item->SetBitmap( KiBitmapBundle( aAction.GetIcon() ) );
  266. item->SetDisabledBitmap( KiDisabledBitmapBundle( aAction.GetIcon() ) );
  267. // Register a new handler with the new UI conditions
  268. if( m_toolManager )
  269. {
  270. const ACTION_CONDITIONS* cond = m_toolManager->GetActionManager()->GetCondition( aAction );
  271. wxASSERT_MSG( cond, wxString::Format( "Missing UI condition for action %s",
  272. aAction.GetName() ) );
  273. m_toolManager->GetToolHolder()->UnregisterUIUpdateHandler( groupId );
  274. m_toolManager->GetToolHolder()->RegisterUIUpdateHandler( groupId, *cond );
  275. }
  276. // Update the currently selected action
  277. m_toolActions[ groupId ] = &aAction;
  278. Refresh();
  279. }
  280. void ACTION_TOOLBAR::UpdateControlWidth( int aID )
  281. {
  282. wxAuiToolBarItem* item = FindTool( aID );
  283. wxASSERT_MSG( item, wxString::Format( "No toolbar item found for ID %d", aID ) );
  284. // The control on the toolbar is stored inside the window field of the item
  285. wxControl* control = dynamic_cast<wxControl*>( item->GetWindow() );
  286. wxASSERT_MSG( control, wxString::Format( "No control located in toolbar item with ID %d", aID ) );
  287. // Update the size the item has stored using the best size of the control
  288. control->InvalidateBestSize();
  289. wxSize bestSize = control->GetBestSize();
  290. item->SetMinSize( bestSize );
  291. // Update the sizer item sizes
  292. // This is a bit convoluted because there are actually 2 sizers that need to be updated:
  293. // 1. The main sizer that is used for the entire toolbar (this sizer item can be found in the
  294. // toolbar item)
  295. if( wxSizerItem* szrItem = item->GetSizerItem() )
  296. szrItem->SetMinSize( bestSize );
  297. // 2. The controls have a second sizer that allows for padding above/below the control with
  298. // stretch space, so we also need to update the sizer item for the control in that sizer with
  299. // the new size. We let wx do the search for us, since SetItemMinSize is recursive and will
  300. // locate the control on that sizer.
  301. if( m_sizer )
  302. {
  303. m_sizer->SetItemMinSize( control, bestSize );
  304. // Now actually update the toolbar with the new sizes
  305. m_sizer->Layout();
  306. }
  307. }
  308. void ACTION_TOOLBAR::ClearToolbar()
  309. {
  310. // Clear all the maps keeping track of our items on the toolbar
  311. m_toolMenus.clear();
  312. m_actionGroups.clear();
  313. m_toolCancellable.clear();
  314. m_toolKinds.clear();
  315. m_toolActions.clear();
  316. // Remove the actual tools from the toolbar
  317. Clear();
  318. }
  319. void ACTION_TOOLBAR::SetToolBitmap( const TOOL_ACTION& aAction, const wxBitmap& aBitmap )
  320. {
  321. int toolId = aAction.GetUIId();
  322. wxAuiToolBar::SetToolBitmap( toolId, aBitmap );
  323. // Set the disabled bitmap: we use the disabled bitmap version of aBitmap.
  324. wxAuiToolBarItem* tb_item = wxAuiToolBar::FindTool( toolId );
  325. if( tb_item )
  326. {
  327. tb_item->SetDisabledBitmap(
  328. aBitmap.ConvertToDisabled( KIPLATFORM::UI::IsDarkTheme() ? 70 : 255 ) );
  329. }
  330. }
  331. void ACTION_TOOLBAR::Toggle( const TOOL_ACTION& aAction, bool aState )
  332. {
  333. int toolId = aAction.GetUIId();
  334. if( m_toolKinds[ toolId ] )
  335. ToggleTool( toolId, aState );
  336. else
  337. EnableTool( toolId, aState );
  338. }
  339. void ACTION_TOOLBAR::Toggle( const TOOL_ACTION& aAction, bool aEnabled, bool aChecked )
  340. {
  341. int toolId = aAction.GetUIId();
  342. EnableTool( toolId, aEnabled );
  343. ToggleTool( toolId, aEnabled && aChecked );
  344. }
  345. void ACTION_TOOLBAR::onToolEvent( wxAuiToolBarEvent& aEvent )
  346. {
  347. int id = aEvent.GetId();
  348. wxEventType type = aEvent.GetEventType();
  349. OPT_TOOL_EVENT evt;
  350. bool handled = false;
  351. if( m_toolManager && type == wxEVT_COMMAND_TOOL_CLICKED && id >= TOOL_ACTION::GetBaseUIId() )
  352. {
  353. const auto actionIt = m_toolActions.find( id );
  354. // The toolbar item is toggled before the event is sent, so we check for it not being
  355. // toggled to see if it was toggled originally
  356. if( m_toolCancellable[id] && !GetToolToggled( id ) )
  357. {
  358. // Send a cancel event
  359. m_toolManager->CancelTool();
  360. handled = true;
  361. }
  362. else if( actionIt != m_toolActions.end() )
  363. {
  364. // Dispatch a tool event
  365. evt = actionIt->second->MakeEvent();
  366. evt->SetHasPosition( false );
  367. m_toolManager->ProcessEvent( *evt );
  368. m_toolManager->GetToolHolder()->RefreshCanvas();
  369. handled = true;
  370. }
  371. }
  372. // Skip the event if we don't handle it
  373. if( !handled )
  374. aEvent.Skip();
  375. }
  376. void ACTION_TOOLBAR::onToolRightClick( wxAuiToolBarEvent& aEvent )
  377. {
  378. int toolId = aEvent.GetToolId();
  379. // This means the event was not on a button
  380. if( toolId == -1 )
  381. return;
  382. // Ensure that the ID used maps to a proper tool ID.
  383. // If right-clicked on a group item, this is needed to get the ID of the currently selected
  384. // action, since the event's ID is that of the group.
  385. const auto actionIt = m_toolActions.find( toolId );
  386. if( actionIt != m_toolActions.end() )
  387. toolId = actionIt->second->GetUIId();
  388. // Find the menu for the action
  389. const auto menuIt = m_toolMenus.find( toolId );
  390. if( menuIt == m_toolMenus.end() )
  391. return;
  392. // Update and show the menu
  393. std::unique_ptr<ACTION_MENU>& owningMenu = menuIt->second;
  394. // Get the actual menu pointer to show it
  395. ACTION_MENU* menu = owningMenu.get();
  396. SELECTION dummySel;
  397. if( CONDITIONAL_MENU* condMenu = dynamic_cast<CONDITIONAL_MENU*>( menu ) )
  398. condMenu->Evaluate( dummySel );
  399. menu->UpdateAll();
  400. PopupMenu( menu );
  401. // Remove hovered item when the menu closes, otherwise it remains hovered even if the
  402. // mouse is not on the toolbar
  403. SetHoverItem( nullptr );
  404. }
  405. // The time (in milliseconds) between pressing the left mouse button and opening the palette
  406. #define PALETTE_OPEN_DELAY 500
  407. void ACTION_TOOLBAR::onMouseClick( wxMouseEvent& aEvent )
  408. {
  409. wxAuiToolBarItem* item = FindToolByPosition( aEvent.GetX(), aEvent.GetY() );
  410. if( item )
  411. {
  412. // Ensure there is no active palette
  413. if( m_palette )
  414. {
  415. m_palette->Hide();
  416. m_palette->Destroy();
  417. m_palette = nullptr;
  418. }
  419. // Start the popup conditions if it is a left mouse click and the tool clicked is a group
  420. if( aEvent.LeftDown() && ( m_actionGroups.find( item->GetId() ) != m_actionGroups.end() ) )
  421. m_paletteTimer->StartOnce( PALETTE_OPEN_DELAY );
  422. // Clear the popup conditions if it is a left up, because that implies a click happened
  423. if( aEvent.LeftUp() )
  424. m_paletteTimer->Stop();
  425. }
  426. // Skip the event so wx can continue processing the mouse event
  427. aEvent.Skip();
  428. }
  429. void ACTION_TOOLBAR::onItemDrag( wxAuiToolBarEvent& aEvent )
  430. {
  431. int toolId = aEvent.GetToolId();
  432. if( m_actionGroups.find( toolId ) != m_actionGroups.end() )
  433. {
  434. wxAuiToolBarItem* item = FindTool( toolId );
  435. // Use call after because opening the palette from a mouse handler
  436. // creates a weird mouse state that causes problems on OSX.
  437. CallAfter( &ACTION_TOOLBAR::popupPalette, item );
  438. // Don't skip this event since we are handling it
  439. return;
  440. }
  441. // Skip since we don't care about it
  442. aEvent.Skip();
  443. }
  444. void ACTION_TOOLBAR::onTimerDone( wxTimerEvent& aEvent )
  445. {
  446. // We need to search for the tool using the client coordinates
  447. wxPoint mousePos = ScreenToClient( KIPLATFORM::UI::GetMousePosition() );
  448. wxAuiToolBarItem* item = FindToolByPosition( mousePos.x, mousePos.y );
  449. if( item )
  450. popupPalette( item );
  451. }
  452. void ACTION_TOOLBAR::onPaletteEvent( wxCommandEvent& aEvent )
  453. {
  454. if( !m_palette )
  455. return;
  456. OPT_TOOL_EVENT evt;
  457. ACTION_GROUP* group = m_palette->GetGroup();
  458. // Find the action corresponding to the button press
  459. auto actionIt = std::find_if( group->GetActions().begin(), group->GetActions().end(),
  460. [&]( const TOOL_ACTION* aAction )
  461. {
  462. return aAction->GetUIId() == aEvent.GetId();
  463. } );
  464. if( actionIt != group->GetActions().end() )
  465. {
  466. const TOOL_ACTION* action = *actionIt;
  467. // Dispatch a tool event
  468. evt = action->MakeEvent();
  469. evt->SetHasPosition( false );
  470. m_toolManager->ProcessEvent( *evt );
  471. m_toolManager->GetToolHolder()->RefreshCanvas();
  472. // Update the main toolbar item with the selected action
  473. doSelectAction( group, *action );
  474. }
  475. // Hide the palette
  476. m_palette->Hide();
  477. m_palette->Destroy();
  478. m_palette = nullptr;
  479. }
  480. void ACTION_TOOLBAR::popupPalette( wxAuiToolBarItem* aItem )
  481. {
  482. // Clear all popup conditions
  483. m_paletteTimer->Stop();
  484. wxWindow* toolParent = dynamic_cast<wxWindow*>( m_toolManager->GetToolHolder() );
  485. wxASSERT( GetParent() );
  486. wxASSERT( m_auiManager );
  487. wxASSERT( toolParent );
  488. // Ensure the item we are using for the palette has a group associated with it.
  489. const auto it = m_actionGroups.find( aItem->GetId() );
  490. if( it == m_actionGroups.end() )
  491. return;
  492. ACTION_GROUP* group = it->second;
  493. wxAuiPaneInfo& pane = m_auiManager->GetPane( this );
  494. // We use the size of the toolbar items for our palette buttons
  495. wxRect toolRect = GetToolRect( aItem->GetId() );
  496. // The position for the palette window must be in screen coordinates
  497. wxPoint pos( ClientToScreen( toolRect.GetPosition() ) );
  498. // True for vertical buttons, false for horizontal
  499. bool dir = true;
  500. size_t numActions = group->m_actions.size();
  501. // The size of the palette in the long dimension
  502. int paletteLongDim = ( 2 * PALETTE_BORDER ) // The border on all sides of the buttons
  503. + ( BUTTON_BORDER ) // The border on the start of the buttons
  504. + ( numActions * BUTTON_BORDER ) // The other button borders
  505. + ( numActions * toolRect.GetHeight() ); // The size of the buttons
  506. // Determine the position of the top left corner of the palette window
  507. switch( pane.dock_direction )
  508. {
  509. case wxAUI_DOCK_TOP:
  510. // Top toolbars need to shift the palette window down by the toolbar padding
  511. dir = true; // Buttons are vertical in the palette
  512. pos = ClientToScreen( toolRect.GetBottomLeft() );
  513. pos += wxPoint( -PALETTE_BORDER, // Shift left to align the button edges
  514. m_bottomPadding ); // Shift down to move away from the toolbar
  515. break;
  516. case wxAUI_DOCK_BOTTOM:
  517. // Bottom toolbars need to shift the palette window up by its height (all buttons +
  518. // border + toolbar padding)
  519. dir = true; // Buttons are vertical in the palette
  520. pos = ClientToScreen( toolRect.GetTopLeft() );
  521. pos += wxPoint( -PALETTE_BORDER, // Shift left to align the button
  522. // Shift up by the entire length of the palette.
  523. -( paletteLongDim + m_topPadding ) );
  524. break;
  525. case wxAUI_DOCK_LEFT:
  526. // Left toolbars need to shift the palette window up by the toolbar padding
  527. dir = false; // Buttons are horizontal in the palette
  528. pos = ClientToScreen( toolRect.GetTopRight() );
  529. pos += wxPoint( m_rightPadding, // Shift right to move away from the toolbar
  530. -( PALETTE_BORDER ) ); // Shift up to align the button tops
  531. break;
  532. case wxAUI_DOCK_RIGHT:
  533. // Right toolbars need to shift the palette window left by its width (all buttons +
  534. // border + toolbar padding)
  535. dir = false; // Buttons are horizontal in the palette
  536. pos = ClientToScreen( toolRect.GetTopLeft() );
  537. // Shift left by the entire length of the palette.
  538. pos += wxPoint( -( paletteLongDim + m_leftPadding ),
  539. -( PALETTE_BORDER ) ); // Shift up to align the button
  540. break;
  541. }
  542. m_palette = new ACTION_TOOLBAR_PALETTE( GetParent(), dir );
  543. // We handle the button events in the toolbar class, so connect the right handler
  544. m_palette->SetGroup( group );
  545. m_palette->SetButtonSize( toolRect );
  546. m_palette->Connect( wxEVT_BUTTON, wxCommandEventHandler( ACTION_TOOLBAR::onPaletteEvent ),
  547. nullptr, this );
  548. // Add the actions in the group to the palette and update their enabled state
  549. // We purposely don't check items in the palette
  550. for( const TOOL_ACTION* action : group->m_actions )
  551. {
  552. wxUpdateUIEvent evt( action->GetUIId() );
  553. toolParent->ProcessWindowEvent( evt );
  554. m_palette->AddAction( *action );
  555. if( evt.GetSetEnabled() )
  556. m_palette->EnableAction( *action, evt.GetEnabled() );
  557. }
  558. // Release the mouse to ensure the first click will be recognized in the palette
  559. ReleaseMouse();
  560. m_palette->SetPosition( pos );
  561. m_palette->Popup();
  562. // Clear the mouse state on the toolbar because otherwise wxWidgets gets confused
  563. // and won't properly display any highlighted items after the palette is closed.
  564. // (This is the equivalent of calling the DoResetMouseState() private function)
  565. RefreshOverflowState();
  566. SetHoverItem( nullptr );
  567. SetPressedItem( nullptr );
  568. m_dragging = false;
  569. m_tipItem = nullptr;
  570. m_actionPos = wxPoint( -1, -1 );
  571. m_actionItem = nullptr;
  572. }
  573. void ACTION_TOOLBAR::OnCustomRender(wxDC& aDc, const wxAuiToolBarItem& aItem, const wxRect& aRect )
  574. {
  575. auto it = m_actionGroups.find( aItem.GetId() );
  576. if( it == m_actionGroups.end() )
  577. return;
  578. // Choose the color to draw the triangle
  579. wxColour clr;
  580. if( aItem.GetState() & wxAUI_BUTTON_STATE_DISABLED )
  581. clr = wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
  582. else
  583. clr = wxSystemSettings::GetColour( wxSYS_COLOUR_BTNTEXT );
  584. // Must set both the pen (for the outline) and the brush (for the polygon fill)
  585. aDc.SetPen( wxPen( clr ) );
  586. aDc.SetBrush( wxBrush( clr ) );
  587. // Make the side length of the triangle approximately 1/5th of the bitmap
  588. int sideLength = KiROUND( aRect.height / 5.0 );
  589. // This will create a triangle with its point at the bottom right corner,
  590. // and its other two corners along the right and bottom sides
  591. wxPoint btmRight = aRect.GetBottomRight();
  592. wxPoint topCorner( btmRight.x, btmRight.y - sideLength );
  593. wxPoint btmCorner( btmRight.x - sideLength, btmRight.y );
  594. wxPointList points;
  595. points.Append( &btmRight );
  596. points.Append( &topCorner );
  597. points.Append( &btmCorner );
  598. aDc.DrawPolygon( &points );
  599. }
  600. bool ACTION_TOOLBAR::KiRealize()
  601. {
  602. #if wxCHECK_VERSION( 3, 3, 0 )
  603. return Realize();
  604. #else
  605. wxClientDC dc( this );
  606. if( !dc.IsOk() )
  607. return false;
  608. // calculate hint sizes for both horizontal and vertical
  609. // in the order that leaves toolbar in correct final state
  610. // however, skip calculating alternate orientations if we don't need them due to window style
  611. bool retval = true;
  612. if( m_orientation == wxHORIZONTAL )
  613. {
  614. if( !( GetWindowStyle() & wxAUI_TB_HORIZONTAL ) )
  615. {
  616. m_vertHintSize = GetSize();
  617. retval = RealizeHelper( dc, false );
  618. }
  619. if( retval && RealizeHelper( dc, true ) )
  620. {
  621. m_horzHintSize = GetSize();
  622. }
  623. else
  624. {
  625. retval = false;
  626. }
  627. }
  628. else
  629. {
  630. if( !( GetWindowStyle() & wxAUI_TB_VERTICAL ) )
  631. {
  632. m_horzHintSize = GetSize();
  633. retval = RealizeHelper( dc, true );
  634. }
  635. if( retval && RealizeHelper( dc, false ) )
  636. {
  637. m_vertHintSize = GetSize();
  638. }
  639. else
  640. {
  641. retval = false;
  642. }
  643. }
  644. Refresh( false );
  645. return retval;
  646. #endif
  647. }
  648. void ACTION_TOOLBAR::onThemeChanged( wxSysColourChangedEvent &aEvent )
  649. {
  650. GetBitmapStore()->ThemeChanged();
  651. RefreshBitmaps();
  652. aEvent.Skip();
  653. }
  654. void ACTION_TOOLBAR::RefreshBitmaps()
  655. {
  656. for( const std::pair<int, const TOOL_ACTION*> pair : m_toolActions )
  657. {
  658. wxAuiToolBarItem* tool = FindTool( pair.first );
  659. tool->SetBitmap( KiBitmapBundle( pair.second->GetIcon() ) );
  660. tool->SetDisabledBitmap( KiDisabledBitmapBundle( pair.second->GetIcon() ) );
  661. }
  662. Refresh();
  663. }