Commit fc565edb authored by Ian McInerney's avatar Ian McInerney

Add a palette to the toolbars to display groups of actions

This adds a palette to toolbar items that displays groups
of actions when long-pressed on a button.
parent 57949978
......@@ -43,10 +43,15 @@ void EDA_3D_VIEWER::ReCreateMainToolbar()
wxWindowUpdateLocker dummy( this );
if( m_mainToolBar )
{
m_mainToolBar->ClearToolbar();
}
else
{
m_mainToolBar = new ACTION_TOOLBAR( this, ID_H_TOOLBAR, wxDefaultPosition, wxDefaultSize,
KICAD_AUI_TB_STYLE | wxAUI_TB_HORZ_LAYOUT );
m_mainToolBar->SetAuiManager( &m_auimgr );
}
// Set up toolbar
m_mainToolBar->AddTool( ID_RELOAD3D_BOARD, wxEmptyString,
......
......@@ -22,9 +22,11 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <algorithm>
#include <bitmaps.h>
#include <eda_draw_frame.h>
#include <functional>
#include <math/util.h>
#include <memory>
#include <pgm_base.h>
#include <settings/common_settings.h>
......@@ -33,22 +35,157 @@
#include <tool/tool_event.h>
#include <tool/tool_interactive.h>
#include <tool/tool_manager.h>
#include <widgets/bitmap_button.h>
#include <wx/popupwin.h>
#include <wx/renderer.h>
ACTION_GROUP::ACTION_GROUP( std::string aName, const std::vector<const TOOL_ACTION*>& aActions )
{
wxASSERT_MSG( aActions.size() > 0, "Action groups must have at least one action" );
// The default action is just the first action in the vector
m_actions = aActions;
m_defaultAction = m_actions[0];
m_name = aName;
m_id = ACTION_MANAGER::MakeActionId( m_name );
}
void ACTION_GROUP::SetDefaultAction( const TOOL_ACTION& aDefault )
{
bool valid = std::any_of( m_actions.begin(), m_actions.end(),
[&]( const TOOL_ACTION* aAction ) -> bool
{
// For some reason, we can't compare the actions directly
return aAction->GetId() == aDefault.GetId();
} );
wxASSERT_MSG( valid, "Action must be present in a group to be the default" );
m_defaultAction = &aDefault;
}
#define PALETTE_BORDER 4 // The border around the palette buttons on all sides
#define BUTTON_BORDER 1 // The border on the sides of the buttons that touch other buttons
ACTION_TOOLBAR_PALETTE::ACTION_TOOLBAR_PALETTE( wxWindow* aParent, bool aVertical ) :
wxPopupTransientWindow( aParent, wxBORDER_NONE ),
m_group( nullptr ),
m_isVertical( aVertical ),
m_panel( nullptr ),
m_mainSizer( nullptr ),
m_buttonSizer( nullptr )
{
m_panel = new wxPanel( this, wxID_ANY );
m_panel->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
// This sizer holds the buttons for the actions
m_buttonSizer = new wxBoxSizer( aVertical ? wxVERTICAL : wxHORIZONTAL );
// This sizer holds the other sizer, so that a consistent border is present on all sides
m_mainSizer = new wxBoxSizer( aVertical ? wxVERTICAL : wxHORIZONTAL );
m_mainSizer->Add( m_buttonSizer, wxSizerFlags().Border( wxALL, PALETTE_BORDER ) );
m_panel->SetSizer( m_mainSizer );
Connect( wxEVT_CHAR_HOOK, wxCharEventHandler( ACTION_TOOLBAR_PALETTE::onCharHook ),
NULL, this );
}
void ACTION_TOOLBAR_PALETTE::AddAction( const TOOL_ACTION& aAction )
{
wxBitmap normalBmp = KiScaledBitmap( aAction.GetIcon(), this );
wxBitmap disabledBmp = normalBmp.ConvertToDisabled();
int padding = ( m_buttonSize.GetWidth() - normalBmp.GetWidth() ) / 2;
BITMAP_BUTTON* button = new BITMAP_BUTTON( m_panel, aAction.GetUIId() );
button->SetBitmap( normalBmp );
button->SetDisabledBitmap( disabledBmp );
button->SetPadding( padding );
button->SetToolTip( aAction.GetDescription() );
m_buttons[aAction.GetUIId()] = button;
if( m_isVertical )
m_buttonSizer->Add( button, wxSizerFlags().Border( wxTOP | wxBOTTOM, BUTTON_BORDER ) );
else
m_buttonSizer->Add( button, wxSizerFlags().Border( wxLEFT | wxRIGHT, BUTTON_BORDER ) );
m_buttonSizer->Layout();
}
void ACTION_TOOLBAR_PALETTE::EnableAction( const TOOL_ACTION& aAction, bool aEnable )
{
auto it = m_buttons.find( aAction.GetUIId() );
if( it != m_buttons.end() )
it->second->Enable( aEnable );
}
void ACTION_TOOLBAR_PALETTE::CheckAction( const TOOL_ACTION& aAction, bool aCheck )
{
auto it = m_buttons.find( aAction.GetUIId() );
if( it != m_buttons.end() )
it->second->Check( aCheck );
}
void ACTION_TOOLBAR_PALETTE::Popup( wxWindow* aFocus )
{
m_mainSizer->Fit( m_panel );
SetClientSize( m_panel->GetSize() );
wxPopupTransientWindow::Popup( aFocus );
}
void ACTION_TOOLBAR_PALETTE::onCharHook( wxKeyEvent& aEvent )
{
// Allow the escape key to dismiss this popup
if( aEvent.GetKeyCode() == WXK_ESCAPE )
Dismiss();
else
aEvent.Skip();
}
ACTION_TOOLBAR::ACTION_TOOLBAR( EDA_BASE_FRAME* parent, wxWindowID id, const wxPoint& pos,
const wxSize& size, long style ) :
wxAuiToolBar( parent, id, pos, size, style ),
m_toolManager( parent->GetToolManager() )
m_toolManager( parent->GetToolManager() ),
m_palette( nullptr ),
m_paletteTimer( nullptr ),
m_auiManager( nullptr )
{
m_paletteTimer = new wxTimer( this );
Connect( wxEVT_COMMAND_TOOL_CLICKED, wxAuiToolBarEventHandler( ACTION_TOOLBAR::onToolEvent ),
NULL, this );
Connect( wxEVT_AUITOOLBAR_RIGHT_CLICK, wxAuiToolBarEventHandler( ACTION_TOOLBAR::onToolRightClick ),
NULL, this );
Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( ACTION_TOOLBAR::onMouseEvent ),
NULL, this );
Connect( wxEVT_LEFT_UP, wxMouseEventHandler( ACTION_TOOLBAR::onMouseEvent ),
NULL, this );
Connect( m_paletteTimer->GetId(), wxEVT_TIMER, wxTimerEventHandler( ACTION_TOOLBAR::onTimerDone ),
NULL, this );
}
ACTION_TOOLBAR::~ACTION_TOOLBAR()
{
delete m_paletteTimer;
// Delete all the menus
for( auto it = m_toolMenus.begin(); it != m_toolMenus.end(); it++ )
delete it->second;
......@@ -123,6 +260,75 @@ void ACTION_TOOLBAR::AddToolContextMenu( const TOOL_ACTION& aAction, ACTION_MENU
}
void ACTION_TOOLBAR::AddGroup( ACTION_GROUP* aGroup, bool aIsToggleEntry )
{
int groupId = aGroup->GetUIId();
const TOOL_ACTION* defaultAction = aGroup->GetDefaultAction();
wxWindow* parent = dynamic_cast<wxWindow*>( m_toolManager->GetToolHolder() );
wxASSERT( defaultAction );
m_toolKinds[ groupId ] = aIsToggleEntry;
m_toolActions[ groupId ] = defaultAction;
m_actionGroups[ groupId ] = aGroup;
// Add the main toolbar item representing the group
AddTool( groupId, wxEmptyString, KiScaledBitmap( defaultAction->GetIcon(), parent ),
wxEmptyString, aIsToggleEntry ? wxITEM_CHECK : wxITEM_NORMAL );
// Select the default action
doSelectAction( aGroup, *defaultAction );
}
void ACTION_TOOLBAR::SelectAction( ACTION_GROUP* aGroup, const TOOL_ACTION& aAction )
{
bool valid = std::any_of( aGroup->m_actions.begin(), aGroup->m_actions.end(),
[&]( const TOOL_ACTION* action2 ) -> bool
{
// For some reason, we can't compare the actions directly
return aAction.GetId() == action2->GetId();
} );
if( valid )
doSelectAction( aGroup, aAction );
}
void ACTION_TOOLBAR::doSelectAction( ACTION_GROUP* aGroup, const TOOL_ACTION& aAction )
{
int groupId = aGroup->GetUIId();
wxAuiToolBarItem* item = FindTool( groupId );
wxWindow* parent = dynamic_cast<wxWindow*>( m_toolManager->GetToolHolder() );
if( !item )
return;
// Update the item information
item->SetShortHelp( aAction.GetDescription() );
item->SetBitmap( KiScaledBitmap( aAction.GetIcon(), parent ) );
item->SetDisabledBitmap( item->GetBitmap().ConvertToDisabled() );
// Register a new handler with the new UI conditions
if( m_toolManager )
{
const ACTION_CONDITIONS* cond = m_toolManager->GetActionManager()->GetCondition( aAction );
wxASSERT_MSG( cond, wxString::Format( "Missing UI condition for action %s",
aAction.GetName() ) );
m_toolManager->GetToolHolder()->UnregisterUIUpdateHandler( groupId );
m_toolManager->GetToolHolder()->RegisterUIUpdateHandler( groupId, *cond );
}
// Update the currently selected action
m_toolActions[ groupId ] = &aAction;
Refresh();
}
void ACTION_TOOLBAR::ClearToolbar()
{
// Delete all the menus
......@@ -213,13 +419,22 @@ void ACTION_TOOLBAR::onToolRightClick( wxAuiToolBarEvent& aEvent )
if( toolId == -1 )
return;
const auto it = m_toolMenus.find( aEvent.GetId() );
// Ensure that the ID used maps to a proper tool ID.
// If right-clicked on a group item, this is needed to get the ID of the currently selected
// action, since the event's ID is that of the group.
const auto actionIt = m_toolActions.find( toolId );
if( actionIt != m_toolActions.end() )
toolId = actionIt->second->GetUIId();
// Find the menu for the action
const auto menuIt = m_toolMenus.find( toolId );
if( it == m_toolMenus.end() )
if( menuIt == m_toolMenus.end() )
return;
// Update and show the menu
ACTION_MENU* menu = it->second;
ACTION_MENU* menu = menuIt->second;
SELECTION dummySel;
if( CONDITIONAL_MENU* condMenu = dynamic_cast<CONDITIONAL_MENU*>( menu ) )
......@@ -232,3 +447,194 @@ void ACTION_TOOLBAR::onToolRightClick( wxAuiToolBarEvent& aEvent )
// mouse is not on the toolbar
SetHoverItem( nullptr );
}
// The time (in milliseconds) between pressing the left mouse button and opening the palette
#define PALETTE_OPEN_DELAY 500
void ACTION_TOOLBAR::onMouseEvent( wxMouseEvent& aEvent )
{
wxAuiToolBarItem* item = FindToolByPosition( aEvent.GetX(), aEvent.GetY() );
if( item )
{
// Ensure there is no active palette
if( m_palette )
{
m_palette->Hide();
m_palette->Destroy();
m_palette = nullptr;
}
// Start the timer if it is a left mouse click and the tool clicked is a group
if( aEvent.LeftDown() && ( m_actionGroups.find( item->GetId() ) != m_actionGroups.end() ) )
m_paletteTimer->StartOnce( PALETTE_OPEN_DELAY );
// Stop the timer if there is a left up, because that implies a click happened
if( aEvent.LeftUp() )
m_paletteTimer->Stop();
}
// Skip the event so wx can continue processing the mouse event
aEvent.Skip();
}
void ACTION_TOOLBAR::onPaletteEvent( wxCommandEvent& aEvent )
{
if( !m_palette )
return;
OPT_TOOL_EVENT evt;
ACTION_GROUP* group = m_palette->GetGroup();
// Find the action corresponding to the button press
auto actionIt = std::find_if( group->GetActions().begin(), group->GetActions().end(),
[=]( const TOOL_ACTION* aAction )
{
return aAction->GetUIId() == aEvent.GetId();
} );
if( actionIt != group->GetActions().end() )
{
const TOOL_ACTION* action = *actionIt;
// Dispatch a tool event
evt = action->MakeEvent();
evt->SetHasPosition( false );
m_toolManager->ProcessEvent( *evt );
// Update the main toolbar item with the selected action
doSelectAction( group, *action );
}
// Hide the palette
m_palette->Hide();
m_palette->Destroy();
m_palette = nullptr;
}
void ACTION_TOOLBAR::onTimerDone( wxTimerEvent& aEvent )
{
// We need to search for the tool using the client coordinates
wxPoint mousePos = ScreenToClient( wxGetMousePosition() );
wxWindow* parent = dynamic_cast<wxWindow*>( m_toolManager->GetToolHolder() );
wxAuiToolBarItem* item = FindToolByPosition( mousePos.x, mousePos.y );
wxASSERT( m_auiManager );
wxASSERT( parent );
if( !item )
return;
// The mouse could have been moved off of the button with the group, so we test
// for it again to see if we are still on an item with a group.
const auto it = m_actionGroups.find( item->GetId() );
if( it == m_actionGroups.end() )
return;
ACTION_GROUP* group = it->second;
m_palette = new ACTION_TOOLBAR_PALETTE( parent, true );
// We handle the button events in the toolbar class, so connect the right handler
m_palette->SetGroup( group );
m_palette->Connect( wxEVT_BUTTON, wxCommandEventHandler( ACTION_TOOLBAR::onPaletteEvent ),
NULL, this );
// This button size is used for all buttons on the toolbar
wxRect toolRect = GetToolRect( item->GetId() );
m_palette->SetButtonSize( toolRect );
// Add the actions in the group to the palette and update their state
for( const TOOL_ACTION* action : group->m_actions )
{
wxUpdateUIEvent evt( action->GetUIId() );
parent->ProcessWindowEvent( evt );
m_palette->AddAction( *action );
if( evt.GetSetChecked() )
m_palette->CheckAction( *action, evt.GetChecked() );
if( evt.GetSetEnabled() )
m_palette->EnableAction( *action, evt.GetEnabled() );
}
wxAuiPaneInfo& pane = m_auiManager->GetPane( this );
// The position for the palette window must be in screen coordinates
wxPoint pos( ClientToScreen( toolRect.GetPosition() ) );
// Determine the position of the top left corner of the palette window
switch( pane.dock_direction )
{
case wxAUI_DOCK_TOP:
// Top toolbars need to shift the palette window down by the toolbar padding
pos = ClientToScreen( toolRect.GetBottomLeft() );
pos += wxPoint( 0, GetToolBorderPadding() );
break;
case wxAUI_DOCK_BOTTOM:
// Bottom toolbars need to shift the palette window up by its height (1 button + border + toolbar padding)
pos = ClientToScreen( toolRect.GetTopLeft() );
pos -= wxPoint( GetToolBorderPadding(), 2*PALETTE_BORDER + toolRect.GetHeight() + GetToolBorderPadding() );
break;
case wxAUI_DOCK_LEFT:
// Left toolbars need to shift the palette window up by the toolbar padding
pos = ClientToScreen( toolRect.GetTopRight() );
pos += wxPoint( GetToolBorderPadding(), 0 );
break;
case wxAUI_DOCK_RIGHT:
// Right toolbars need to shift the palette window left by its width (1 button + border + toolbar padding)
pos = ClientToScreen( toolRect.GetTopLeft() );
pos -= wxPoint( 2*PALETTE_BORDER + toolRect.GetHeight() + GetToolBorderPadding(), 0 );
break;
}
m_palette->SetPosition( pos );
m_palette->Popup();
}
void ACTION_TOOLBAR::OnCustomRender(wxDC& aDc, const wxAuiToolBarItem& aItem,
const wxRect& aRect )
{
auto it = m_actionGroups.find( aItem.GetId() );
if( it == m_actionGroups.end() )
return;
// Choose the color to draw the triangle
wxColour clr;
if( aItem.GetState() & wxAUI_BUTTON_STATE_DISABLED )
clr = wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT );
else
clr = wxSystemSettings::GetColour( wxSYS_COLOUR_BTNTEXT );
// Must set both the pen (for the outline) and the brush (for the polygon fill)
aDc.SetPen( wxPen( clr ) );
aDc.SetBrush( wxBrush( clr ) );
// Make the side length of the triangle approximately 1/5th of the bitmap
int sideLength = KiROUND( aRect.height / 5.0 );
// This will create a triangle with its point at the bottom right corner,
// and its other two corners along the right and bottom sides
wxPoint btmRight = aRect.GetBottomRight();
wxPoint topCorner( btmRight.x, btmRight.y - sideLength );
wxPoint btmCorner( btmRight.x - sideLength, btmRight.y );
wxPointList points;
points.Append( &btmRight );
points.Append( &topCorner );
points.Append( &btmCorner );
aDc.DrawPolygon( &points );
}
......@@ -229,10 +229,15 @@ void DISPLAY_FOOTPRINTS_FRAME::ReCreateVToolbar()
void DISPLAY_FOOTPRINTS_FRAME::ReCreateOptToolbar()
{
if( m_optionsToolBar )
{
m_optionsToolBar->ClearToolbar();
}
else
{
m_optionsToolBar = new ACTION_TOOLBAR( this, ID_OPT_TOOLBAR, wxDefaultPosition, wxDefaultSize,
KICAD_AUI_TB_STYLE | wxAUI_TB_VERTICAL );
m_optionsToolBar->SetAuiManager( &m_auimgr );
}
m_optionsToolBar->Add( ACTIONS::selectionTool, ACTION_TOOLBAR::TOGGLE );
m_optionsToolBar->Add( ACTIONS::measureTool, ACTION_TOOLBAR::TOGGLE );
......@@ -264,10 +269,15 @@ void DISPLAY_FOOTPRINTS_FRAME::ReCreateHToolbar()
// So we do not recreate them after clearing the tools.
if( m_mainToolBar )
{
m_mainToolBar->ClearToolbar();
}
else
{
m_mainToolBar = new ACTION_TOOLBAR( this, ID_H_TOOLBAR, wxDefaultPosition, wxDefaultSize,
KICAD_AUI_TB_STYLE | wxAUI_TB_HORZ_LAYOUT );
m_mainToolBar->SetAuiManager( &m_auimgr );
}
m_mainToolBar->Add( ACTIONS::zoomRedraw );
m_mainToolBar->Add( ACTIONS::zoomInCenter );
......
......@@ -35,10 +35,15 @@
void CVPCB_MAINFRAME::ReCreateHToolbar()
{
if( m_mainToolBar )
{
m_mainToolBar->ClearToolbar();
}
else
{
m_mainToolBar = new ACTION_TOOLBAR( this, ID_H_TOOLBAR, wxDefaultPosition, wxDefaultSize,
KICAD_AUI_TB_STYLE | wxAUI_TB_HORZ_LAYOUT );
m_mainToolBar->SetAuiManager( &m_auimgr );
}
m_mainToolBar->Add( CVPCB_ACTIONS::saveAssociations );
......
......@@ -45,10 +45,15 @@
void LIB_EDIT_FRAME::ReCreateVToolbar()
{
if( m_drawToolBar )
{
m_drawToolBar->ClearToolbar();
}
else
{
m_drawToolBar = new ACTION_TOOLBAR( this, ID_V_TOOLBAR, wxDefaultPosition, wxDefaultSize,
KICAD_AUI_TB_STYLE | wxAUI_TB_VERTICAL );
m_drawToolBar->SetAuiManager( &m_auimgr );
}
// Set up toolbar
m_drawToolBar->Add( ACTIONS::selectionTool, ACTION_TOOLBAR::TOGGLE );
......@@ -79,10 +84,15 @@ void LIB_EDIT_FRAME::ReCreateVToolbar()
void LIB_EDIT_FRAME::ReCreateHToolbar()
{
if( m_mainToolBar )
{
m_mainToolBar->ClearToolbar();
}
else
{
m_mainToolBar = new ACTION_TOOLBAR( this, ID_H_TOOLBAR, wxDefaultPosition, wxDefaultSize,
KICAD_AUI_TB_STYLE | wxAUI_TB_HORZ_LAYOUT );
m_mainToolBar->SetAuiManager( &m_auimgr );
}
// Set up toolbar
m_mainToolBar->Add( EE_ACTIONS::newSymbol );
......@@ -137,11 +147,16 @@ void LIB_EDIT_FRAME::ReCreateHToolbar()
void LIB_EDIT_FRAME::ReCreateOptToolbar()
{
if( m_optionsToolBar )
{
m_optionsToolBar->ClearToolbar();
}
else
{
m_optionsToolBar = new ACTION_TOOLBAR( this, ID_OPT_TOOLBAR,
wxDefaultPosition, wxDefaultSize,
KICAD_AUI_TB_STYLE | wxAUI_TB_VERTICAL );
m_optionsToolBar->SetAuiManager( &m_auimgr );
}
m_optionsToolBar->Add( ACTIONS::toggleGrid, ACTION_TOOLBAR::TOGGLE );
m_optionsToolBar->Add( ACTIONS::imperialUnits, ACTION_TOOLBAR::TOGGLE );
......
......@@ -37,11 +37,16 @@
void LIB_VIEW_FRAME::ReCreateHToolbar()
{
if( m_mainToolBar )
{
m_mainToolBar->ClearToolbar();
}
else
{
m_mainToolBar = new ACTION_TOOLBAR( this, ID_H_TOOLBAR,
wxDefaultPosition, wxDefaultSize,
KICAD_AUI_TB_STYLE | wxAUI_TB_HORZ_LAYOUT );
m_mainToolBar->SetAuiManager( &m_auimgr );
}
m_mainToolBar->AddTool( ID_LIBVIEW_SELECT_PART, wxEmptyString,
KiScaledBitmap( add_component_xpm, this ),
......
......@@ -39,11 +39,16 @@
void SCH_EDIT_FRAME::ReCreateHToolbar()
{
if( m_mainToolBar )