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.

849 lines
27 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. delete m_paletteTimer;
  171. // Clear all the maps keeping track of our items on the toolbar
  172. m_toolMenus.clear();
  173. m_actionGroups.clear();
  174. m_toolCancellable.clear();
  175. m_toolKinds.clear();
  176. m_toolActions.clear();
  177. }
  178. void ACTION_TOOLBAR::Add( const TOOL_ACTION& aAction, bool aIsToggleEntry, bool aIsCancellable )
  179. {
  180. wxASSERT( GetParent() );
  181. wxASSERT_MSG( !( aIsCancellable && !aIsToggleEntry ), wxS( "aIsCancellable requires aIsToggleEntry" ) );
  182. int toolId = aAction.GetUIId();
  183. AddTool( toolId, wxEmptyString, KiBitmapBundle( aAction.GetIcon() ),
  184. KiDisabledBitmapBundle( aAction.GetIcon() ),
  185. aIsToggleEntry ? wxITEM_CHECK : wxITEM_NORMAL,
  186. aAction.GetButtonTooltip(), wxEmptyString, nullptr );
  187. m_toolKinds[ toolId ] = aIsToggleEntry;
  188. m_toolActions[ toolId ] = &aAction;
  189. m_toolCancellable[ toolId ] = aIsCancellable;
  190. }
  191. void ACTION_TOOLBAR::AddButton( const TOOL_ACTION& aAction )
  192. {
  193. int toolId = aAction.GetUIId();
  194. AddTool( toolId, wxEmptyString, KiBitmapBundle( aAction.GetIcon() ),
  195. KiDisabledBitmapBundle( aAction.GetIcon() ), wxITEM_NORMAL,
  196. aAction.GetButtonTooltip(), wxEmptyString, nullptr );
  197. m_toolKinds[ toolId ] = false;
  198. m_toolActions[ toolId ] = &aAction;
  199. }
  200. void ACTION_TOOLBAR::AddScaledSeparator( wxWindow* aWindow )
  201. {
  202. int scale = KiIconScale( aWindow );
  203. if( scale > 4 )
  204. AddSpacer( 16 * ( scale - 4 ) / 4 );
  205. AddSeparator();
  206. if( scale > 4 )
  207. AddSpacer( 16 * ( scale - 4 ) / 4 );
  208. }
  209. void ACTION_TOOLBAR::AddToolContextMenu( const TOOL_ACTION& aAction,
  210. std::unique_ptr<ACTION_MENU> aMenu )
  211. {
  212. int toolId = aAction.GetUIId();
  213. m_toolMenus[toolId] = std::move( aMenu );
  214. }
  215. void ACTION_TOOLBAR::AddGroup( ACTION_GROUP* aGroup, bool aIsToggleEntry )
  216. {
  217. int groupId = aGroup->GetUIId();
  218. const TOOL_ACTION* defaultAction = aGroup->GetDefaultAction();
  219. wxASSERT( GetParent() );
  220. wxASSERT( defaultAction );
  221. m_toolKinds[ groupId ] = aIsToggleEntry;
  222. m_toolActions[ groupId ] = defaultAction;
  223. m_actionGroups[ groupId ] = aGroup;
  224. // Add the main toolbar item representing the group
  225. AddTool( groupId, wxEmptyString, KiBitmapBundle( defaultAction->GetIcon() ),
  226. KiDisabledBitmapBundle( defaultAction->GetIcon() ),
  227. aIsToggleEntry ? wxITEM_CHECK : wxITEM_NORMAL,
  228. wxEmptyString, wxEmptyString, nullptr );
  229. // Select the default action
  230. doSelectAction( aGroup, *defaultAction );
  231. }
  232. void ACTION_TOOLBAR::SelectAction( ACTION_GROUP* aGroup, const TOOL_ACTION& aAction )
  233. {
  234. bool valid = std::any_of( aGroup->m_actions.begin(), aGroup->m_actions.end(),
  235. [&]( const TOOL_ACTION* action2 ) -> bool
  236. {
  237. // For some reason, we can't compare the actions directly
  238. return aAction.GetId() == action2->GetId();
  239. } );
  240. if( valid )
  241. doSelectAction( aGroup, aAction );
  242. }
  243. void ACTION_TOOLBAR::doSelectAction( ACTION_GROUP* aGroup, const TOOL_ACTION& aAction )
  244. {
  245. wxASSERT( GetParent() );
  246. int groupId = aGroup->GetUIId();
  247. wxAuiToolBarItem* item = FindTool( groupId );
  248. if( !item )
  249. return;
  250. // Update the item information
  251. item->SetShortHelp( aAction.GetTooltip() );
  252. item->SetBitmap( KiBitmapBundle( aAction.GetIcon() ) );
  253. item->SetDisabledBitmap( KiDisabledBitmapBundle( aAction.GetIcon() ) );
  254. // Register a new handler with the new UI conditions
  255. if( m_toolManager )
  256. {
  257. const ACTION_CONDITIONS* cond = m_toolManager->GetActionManager()->GetCondition( aAction );
  258. wxASSERT_MSG( cond, wxString::Format( "Missing UI condition for action %s",
  259. aAction.GetName() ) );
  260. m_toolManager->GetToolHolder()->UnregisterUIUpdateHandler( groupId );
  261. m_toolManager->GetToolHolder()->RegisterUIUpdateHandler( groupId, *cond );
  262. }
  263. // Update the currently selected action
  264. m_toolActions[ groupId ] = &aAction;
  265. Refresh();
  266. }
  267. void ACTION_TOOLBAR::UpdateControlWidth( int aID )
  268. {
  269. wxAuiToolBarItem* item = FindTool( aID );
  270. wxASSERT_MSG( item, wxString::Format( "No toolbar item found for ID %d", aID ) );
  271. // The control on the toolbar is stored inside the window field of the item
  272. wxControl* control = dynamic_cast<wxControl*>( item->GetWindow() );
  273. wxASSERT_MSG( control, wxString::Format( "No control located in toolbar item with ID %d", aID ) );
  274. // Update the size the item has stored using the best size of the control
  275. wxSize bestSize = control->GetBestSize();
  276. item->SetMinSize( bestSize );
  277. // Update the sizer item sizes
  278. // This is a bit convoluted because there are actually 2 sizers that need to be updated:
  279. // 1. The main sizer that is used for the entire toolbar (this sizer item can be found in the
  280. // toolbar item)
  281. if( wxSizerItem* szrItem = item->GetSizerItem() )
  282. szrItem->SetMinSize( bestSize );
  283. // 2. The controls have a second sizer that allows for padding above/below the control with
  284. // stretch space, so we also need to update the sizer item for the control in that sizer with
  285. // the new size. We let wx do the search for us, since SetItemMinSize is recursive and will
  286. // locate the control on that sizer.
  287. if( m_sizer )
  288. {
  289. m_sizer->SetItemMinSize( control, bestSize );
  290. // Now actually update the toolbar with the new sizes
  291. m_sizer->Layout();
  292. }
  293. }
  294. void ACTION_TOOLBAR::ClearToolbar()
  295. {
  296. // Clear all the maps keeping track of our items on the toolbar
  297. m_toolMenus.clear();
  298. m_actionGroups.clear();
  299. m_toolCancellable.clear();
  300. m_toolKinds.clear();
  301. m_toolActions.clear();
  302. // Remove the actual tools from the toolbar
  303. Clear();
  304. }
  305. void ACTION_TOOLBAR::SetToolBitmap( const TOOL_ACTION& aAction, const wxBitmap& aBitmap )
  306. {
  307. int toolId = aAction.GetUIId();
  308. wxAuiToolBar::SetToolBitmap( toolId, aBitmap );
  309. // Set the disabled bitmap: we use the disabled bitmap version of aBitmap.
  310. wxAuiToolBarItem* tb_item = wxAuiToolBar::FindTool( toolId );
  311. if( tb_item )
  312. {
  313. tb_item->SetDisabledBitmap(
  314. aBitmap.ConvertToDisabled( KIPLATFORM::UI::IsDarkTheme() ? 70 : 255 ) );
  315. }
  316. }
  317. void ACTION_TOOLBAR::Toggle( const TOOL_ACTION& aAction, bool aState )
  318. {
  319. int toolId = aAction.GetUIId();
  320. if( m_toolKinds[ toolId ] )
  321. ToggleTool( toolId, aState );
  322. else
  323. EnableTool( toolId, aState );
  324. }
  325. void ACTION_TOOLBAR::Toggle( const TOOL_ACTION& aAction, bool aEnabled, bool aChecked )
  326. {
  327. int toolId = aAction.GetUIId();
  328. EnableTool( toolId, aEnabled );
  329. ToggleTool( toolId, aEnabled && aChecked );
  330. }
  331. void ACTION_TOOLBAR::onToolEvent( wxAuiToolBarEvent& aEvent )
  332. {
  333. int id = aEvent.GetId();
  334. wxEventType type = aEvent.GetEventType();
  335. OPT_TOOL_EVENT evt;
  336. bool handled = false;
  337. if( m_toolManager && type == wxEVT_COMMAND_TOOL_CLICKED && id >= TOOL_ACTION::GetBaseUIId() )
  338. {
  339. const auto actionIt = m_toolActions.find( id );
  340. // The toolbar item is toggled before the event is sent, so we check for it not being
  341. // toggled to see if it was toggled originally
  342. if( m_toolCancellable[id] && !GetToolToggled( id ) )
  343. {
  344. // Send a cancel event
  345. m_toolManager->CancelTool();
  346. handled = true;
  347. }
  348. else if( actionIt != m_toolActions.end() )
  349. {
  350. // Dispatch a tool event
  351. evt = actionIt->second->MakeEvent();
  352. evt->SetHasPosition( false );
  353. m_toolManager->ProcessEvent( *evt );
  354. m_toolManager->GetToolHolder()->RefreshCanvas();
  355. handled = true;
  356. }
  357. }
  358. // Skip the event if we don't handle it
  359. if( !handled )
  360. aEvent.Skip();
  361. }
  362. void ACTION_TOOLBAR::onToolRightClick( wxAuiToolBarEvent& aEvent )
  363. {
  364. int toolId = aEvent.GetToolId();
  365. // This means the event was not on a button
  366. if( toolId == -1 )
  367. return;
  368. // Ensure that the ID used maps to a proper tool ID.
  369. // If right-clicked on a group item, this is needed to get the ID of the currently selected
  370. // action, since the event's ID is that of the group.
  371. const auto actionIt = m_toolActions.find( toolId );
  372. if( actionIt != m_toolActions.end() )
  373. toolId = actionIt->second->GetUIId();
  374. // Find the menu for the action
  375. const auto menuIt = m_toolMenus.find( toolId );
  376. if( menuIt == m_toolMenus.end() )
  377. return;
  378. // Update and show the menu
  379. std::unique_ptr<ACTION_MENU>& owningMenu = menuIt->second;
  380. // Get the actual menu pointer to show it
  381. ACTION_MENU* menu = owningMenu.get();
  382. SELECTION dummySel;
  383. if( CONDITIONAL_MENU* condMenu = dynamic_cast<CONDITIONAL_MENU*>( menu ) )
  384. condMenu->Evaluate( dummySel );
  385. menu->UpdateAll();
  386. PopupMenu( menu );
  387. // Remove hovered item when the menu closes, otherwise it remains hovered even if the
  388. // mouse is not on the toolbar
  389. SetHoverItem( nullptr );
  390. }
  391. // The time (in milliseconds) between pressing the left mouse button and opening the palette
  392. #define PALETTE_OPEN_DELAY 500
  393. void ACTION_TOOLBAR::onMouseClick( wxMouseEvent& aEvent )
  394. {
  395. wxAuiToolBarItem* item = FindToolByPosition( aEvent.GetX(), aEvent.GetY() );
  396. if( item )
  397. {
  398. // Ensure there is no active palette
  399. if( m_palette )
  400. {
  401. m_palette->Hide();
  402. m_palette->Destroy();
  403. m_palette = nullptr;
  404. }
  405. // Start the popup conditions if it is a left mouse click and the tool clicked is a group
  406. if( aEvent.LeftDown() && ( m_actionGroups.find( item->GetId() ) != m_actionGroups.end() ) )
  407. m_paletteTimer->StartOnce( PALETTE_OPEN_DELAY );
  408. // Clear the popup conditions if it is a left up, because that implies a click happened
  409. if( aEvent.LeftUp() )
  410. m_paletteTimer->Stop();
  411. }
  412. // Skip the event so wx can continue processing the mouse event
  413. aEvent.Skip();
  414. }
  415. void ACTION_TOOLBAR::onItemDrag( wxAuiToolBarEvent& aEvent )
  416. {
  417. int toolId = aEvent.GetToolId();
  418. if( m_actionGroups.find( toolId ) != m_actionGroups.end() )
  419. {
  420. wxAuiToolBarItem* item = FindTool( toolId );
  421. // Use call after because opening the palette from a mouse handler
  422. // creates a weird mouse state that causes problems on OSX.
  423. CallAfter( &ACTION_TOOLBAR::popupPalette, item );
  424. // Don't skip this event since we are handling it
  425. return;
  426. }
  427. // Skip since we don't care about it
  428. aEvent.Skip();
  429. }
  430. void ACTION_TOOLBAR::onTimerDone( wxTimerEvent& aEvent )
  431. {
  432. // We need to search for the tool using the client coordinates
  433. wxPoint mousePos = ScreenToClient( KIPLATFORM::UI::GetMousePosition() );
  434. wxAuiToolBarItem* item = FindToolByPosition( mousePos.x, mousePos.y );
  435. if( item )
  436. popupPalette( item );
  437. }
  438. void ACTION_TOOLBAR::onPaletteEvent( wxCommandEvent& aEvent )
  439. {
  440. if( !m_palette )
  441. return;
  442. OPT_TOOL_EVENT evt;
  443. ACTION_GROUP* group = m_palette->GetGroup();
  444. // Find the action corresponding to the button press
  445. auto actionIt = std::find_if( group->GetActions().begin(), group->GetActions().end(),
  446. [&]( const TOOL_ACTION* aAction )
  447. {
  448. return aAction->GetUIId() == aEvent.GetId();
  449. } );
  450. if( actionIt != group->GetActions().end() )
  451. {
  452. const TOOL_ACTION* action = *actionIt;
  453. // Dispatch a tool event
  454. evt = action->MakeEvent();
  455. evt->SetHasPosition( false );
  456. m_toolManager->ProcessEvent( *evt );
  457. m_toolManager->GetToolHolder()->RefreshCanvas();
  458. // Update the main toolbar item with the selected action
  459. doSelectAction( group, *action );
  460. }
  461. // Hide the palette
  462. m_palette->Hide();
  463. m_palette->Destroy();
  464. m_palette = nullptr;
  465. }
  466. void ACTION_TOOLBAR::popupPalette( wxAuiToolBarItem* aItem )
  467. {
  468. // Clear all popup conditions
  469. m_paletteTimer->Stop();
  470. wxWindow* toolParent = dynamic_cast<wxWindow*>( m_toolManager->GetToolHolder() );
  471. wxASSERT( GetParent() );
  472. wxASSERT( m_auiManager );
  473. wxASSERT( toolParent );
  474. // Ensure the item we are using for the palette has a group associated with it.
  475. const auto it = m_actionGroups.find( aItem->GetId() );
  476. if( it == m_actionGroups.end() )
  477. return;
  478. ACTION_GROUP* group = it->second;
  479. wxAuiPaneInfo& pane = m_auiManager->GetPane( this );
  480. // We use the size of the toolbar items for our palette buttons
  481. wxRect toolRect = GetToolRect( aItem->GetId() );
  482. // The position for the palette window must be in screen coordinates
  483. wxPoint pos( ClientToScreen( toolRect.GetPosition() ) );
  484. // True for vertical buttons, false for horizontal
  485. bool dir = true;
  486. size_t numActions = group->m_actions.size();
  487. // The size of the palette in the long dimension
  488. int paletteLongDim = ( 2 * PALETTE_BORDER ) // The border on all sides of the buttons
  489. + ( BUTTON_BORDER ) // The border on the start of the buttons
  490. + ( numActions * BUTTON_BORDER ) // The other button borders
  491. + ( numActions * toolRect.GetHeight() ); // The size of the buttons
  492. // Determine the position of the top left corner of the palette window
  493. switch( pane.dock_direction )
  494. {
  495. case wxAUI_DOCK_TOP:
  496. // Top toolbars need to shift the palette window down by the toolbar padding
  497. dir = true; // Buttons are vertical in the palette
  498. pos = ClientToScreen( toolRect.GetBottomLeft() );
  499. pos += wxPoint( -PALETTE_BORDER, // Shift left to align the button edges
  500. m_bottomPadding ); // Shift down to move away from the toolbar
  501. break;
  502. case wxAUI_DOCK_BOTTOM:
  503. // Bottom toolbars need to shift the palette window up by its height (all buttons +
  504. // border + toolbar padding)
  505. dir = true; // Buttons are vertical in the palette
  506. pos = ClientToScreen( toolRect.GetTopLeft() );
  507. pos += wxPoint( -PALETTE_BORDER, // Shift left to align the button
  508. // Shift up by the entire length of the palette.
  509. -( paletteLongDim + m_topPadding ) );
  510. break;
  511. case wxAUI_DOCK_LEFT:
  512. // Left toolbars need to shift the palette window up by the toolbar padding
  513. dir = false; // Buttons are horizontal in the palette
  514. pos = ClientToScreen( toolRect.GetTopRight() );
  515. pos += wxPoint( m_rightPadding, // Shift right to move away from the toolbar
  516. -( PALETTE_BORDER ) ); // Shift up to align the button tops
  517. break;
  518. case wxAUI_DOCK_RIGHT:
  519. // Right toolbars need to shift the palette window left by its width (all buttons +
  520. // border + toolbar padding)
  521. dir = false; // Buttons are horizontal in the palette
  522. pos = ClientToScreen( toolRect.GetTopLeft() );
  523. // Shift left by the entire length of the palette.
  524. pos += wxPoint( -( paletteLongDim + m_leftPadding ),
  525. -( PALETTE_BORDER ) ); // Shift up to align the button
  526. break;
  527. }
  528. m_palette = new ACTION_TOOLBAR_PALETTE( GetParent(), dir );
  529. // We handle the button events in the toolbar class, so connect the right handler
  530. m_palette->SetGroup( group );
  531. m_palette->SetButtonSize( toolRect );
  532. m_palette->Connect( wxEVT_BUTTON, wxCommandEventHandler( ACTION_TOOLBAR::onPaletteEvent ),
  533. nullptr, this );
  534. // Add the actions in the group to the palette and update their enabled state
  535. // We purposely don't check items in the palette
  536. for( const TOOL_ACTION* action : group->m_actions )
  537. {
  538. wxUpdateUIEvent evt( action->GetUIId() );
  539. toolParent->ProcessWindowEvent( evt );
  540. m_palette->AddAction( *action );
  541. if( evt.GetSetEnabled() )
  542. m_palette->EnableAction( *action, evt.GetEnabled() );
  543. }
  544. // Release the mouse to ensure the first click will be recognized in the palette
  545. ReleaseMouse();
  546. m_palette->SetPosition( pos );
  547. m_palette->Popup();
  548. // Clear the mouse state on the toolbar because otherwise wxWidgets gets confused
  549. // and won't properly display any highlighted items after the palette is closed.
  550. // (This is the equivalent of calling the DoResetMouseState() private function)
  551. RefreshOverflowState();
  552. SetHoverItem( nullptr );
  553. SetPressedItem( nullptr );
  554. m_dragging = false;
  555. m_tipItem = nullptr;
  556. m_actionPos = wxPoint( -1, -1 );
  557. m_actionItem = nullptr;
  558. }
  559. void ACTION_TOOLBAR::OnCustomRender(wxDC& aDc, const wxAuiToolBarItem& aItem, const wxRect& aRect )
  560. {
  561. auto it = m_actionGroups.find( aItem.GetId() );
  562. if( it == m_actionGroups.end() )
  563. return;
  564. // Choose the color to draw the triangle
  565. wxColour clr;
  566. if( aItem.GetState() & wxAUI_BUTTON_STATE_DISABLED )
  567. clr = wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
  568. else
  569. clr = wxSystemSettings::GetColour( wxSYS_COLOUR_BTNTEXT );
  570. // Must set both the pen (for the outline) and the brush (for the polygon fill)
  571. aDc.SetPen( wxPen( clr ) );
  572. aDc.SetBrush( wxBrush( clr ) );
  573. // Make the side length of the triangle approximately 1/5th of the bitmap
  574. int sideLength = KiROUND( aRect.height / 5.0 );
  575. // This will create a triangle with its point at the bottom right corner,
  576. // and its other two corners along the right and bottom sides
  577. wxPoint btmRight = aRect.GetBottomRight();
  578. wxPoint topCorner( btmRight.x, btmRight.y - sideLength );
  579. wxPoint btmCorner( btmRight.x - sideLength, btmRight.y );
  580. wxPointList points;
  581. points.Append( &btmRight );
  582. points.Append( &topCorner );
  583. points.Append( &btmCorner );
  584. aDc.DrawPolygon( &points );
  585. }
  586. bool ACTION_TOOLBAR::KiRealize()
  587. {
  588. #if wxCHECK_VERSION( 3, 3, 0 )
  589. return Realize();
  590. #else
  591. wxClientDC dc( this );
  592. if( !dc.IsOk() )
  593. return false;
  594. // calculate hint sizes for both horizontal and vertical
  595. // in the order that leaves toolbar in correct final state
  596. // however, skip calculating alternate orientations if we don't need them due to window style
  597. bool retval = true;
  598. if( m_orientation == wxHORIZONTAL )
  599. {
  600. if( !( GetWindowStyle() & wxAUI_TB_HORIZONTAL ) )
  601. {
  602. m_vertHintSize = GetSize();
  603. retval = RealizeHelper( dc, false );
  604. }
  605. if( retval && RealizeHelper( dc, true ) )
  606. {
  607. m_horzHintSize = GetSize();
  608. }
  609. else
  610. {
  611. retval = false;
  612. }
  613. }
  614. else
  615. {
  616. if( !( GetWindowStyle() & wxAUI_TB_VERTICAL ) )
  617. {
  618. m_horzHintSize = GetSize();
  619. retval = RealizeHelper( dc, true );
  620. }
  621. if( retval && RealizeHelper( dc, false ) )
  622. {
  623. m_vertHintSize = GetSize();
  624. }
  625. else
  626. {
  627. retval = false;
  628. }
  629. }
  630. Refresh( false );
  631. return retval;
  632. #endif
  633. }
  634. void ACTION_TOOLBAR::onThemeChanged( wxSysColourChangedEvent &aEvent )
  635. {
  636. GetBitmapStore()->ThemeChanged();
  637. RefreshBitmaps();
  638. aEvent.Skip();
  639. }
  640. void ACTION_TOOLBAR::RefreshBitmaps()
  641. {
  642. for( const std::pair<int, const TOOL_ACTION*> pair : m_toolActions )
  643. {
  644. wxAuiToolBarItem* tool = FindTool( pair.first );
  645. tool->SetBitmap( KiBitmapBundle( pair.second->GetIcon() ) );
  646. tool->SetDisabledBitmap( KiDisabledBitmapBundle( pair.second->GetIcon() ) );
  647. }
  648. Refresh();
  649. }