Replace ToolAction by ToggleActionMenu

Summary:
This replaces ToolAction by a near-drop-in replacement named ToggleActionMenu. The new annotation toolbar already uses this (D15580).

Unlike ToolAction, ToggleActionMenu inherits from KActionMenu to be more flexible.
* Menu can be set from outside, not hard coded.
* Default action for toolbar button is controllable from outside. (Theoretically, the button could trigger //anything// now.)
* KActionMenu instead of KSelectAction:
  - Pluggable in other menus, thus called “Menu”.
  - Doesn’t make the actions exclusive, so //any// actions can be added to the menu.
* ImplicitDefaultAction mode can choose the default action of the toolbar buttons automatically, by looking for the first checked action in the menu.

Toolbar buttons use the default action //of// this menu, not this menu itself as action.

Because the default action is configurable now, D21622 and D21635 (where we tried to fine-tune ToolAction) become obsolete.

Screenshot:
Everything like before, here with mouse_selecttool added to Tools menu to show submenu capability.
{F6884228}

Test Plan:
ToolAction replacement and ImplicitDefaultAction mode:
 * Open Okular and look at toolbar button -> has correct tool selected.
 * Open a document.
 * Look at toolbar button menu -> Correct menu entries (like before, with ToolAction).
 * Select some selection tools through shortcuts and toolbar button -> behaves correctly.
Usage as submenu:
 * Add ToggleActionMenu ("mouse_selecttool") to menubar (..../kxmlgui5/okular/part.rc) -> Submenu looks correctly, has no checkbox attached and so on...
Toolbar buttons:
 * Add diverse other actions to the menu -> still works as before.
 * Add actions when toolbar buttons are already created -> actions are added to existing buttons.
 * setDefaultAction() to some completely unrelated action. -> ToggleActionMenu does not get confused.

Reviewers: simgunz

Reviewed By: simgunz

Subscribers: aacid, ngraham, simgunz, okular-devel

Tags: #okular

Differential Revision: https://phabricator.kde.org/D21971
This commit is contained in:
David Hurka 2020-02-01 19:54:18 +01:00 committed by Albert Astals Cid
parent 5dfbee0a00
commit c6d937ab0d
6 changed files with 323 additions and 136 deletions

View file

@ -395,7 +395,7 @@ set(okularpart_SRCS
ui/thumbnaillist.cpp
ui/toc.cpp
ui/tocmodel.cpp
ui/toolaction.cpp
ui/toggleactionmenu.cpp
ui/videowidget.cpp
ui/layers.cpp
ui/signatureguiutils.cpp

View file

@ -76,7 +76,7 @@
#include "pageviewannotator.h"
#include "pageviewmouseannotation.h"
#include "priorities.h"
#include "toolaction.h"
#include "toggleactionmenu.h"
#include "okmenutitle.h"
#ifdef HAVE_SPEECH
#include "tts.h"
@ -241,6 +241,7 @@ public:
QAction * aSpeakPauseResume;
KActionCollection * actionCollection;
QActionGroup * mouseModeActionGroup;
ToggleActionMenu * aMouseModeMenu;
QAction * aFitWindowToPage;
int setting_viewCols;
@ -705,18 +706,23 @@ void PageView::setupActions( KActionCollection * ac )
d->aMouseMagnifier->setActionGroup( d->mouseModeActionGroup );
d->aMouseMagnifier->setChecked( Okular::Settings::mouseMode() == Okular::Settings::EnumMouseMode::Magnifier );
// Mouse-Mode action menu
d->aMouseModeMenu = new ToggleActionMenu( QIcon(),QString(), this,
ToggleActionMenu::MenuButtonPopup,
ToggleActionMenu::ImplicitDefaultAction );
d->aMouseModeMenu->addAction( d->aMouseSelect );
d->aMouseModeMenu->addAction( d->aMouseTextSelect );
d->aMouseModeMenu->addAction( d->aMouseTableSelect );
d->aMouseModeMenu->suggestDefaultAction( d->aMouseTextSelect );
d->aMouseModeMenu->setText( i18nc( "@action", "Selection Tools" ) );
ac->addAction( QStringLiteral( "mouse_selecttools" ), d->aMouseModeMenu );
d->aToggleAnnotator = new KToggleAction(QIcon::fromTheme( QStringLiteral("draw-freehand") ), i18n("&Review"), this);
ac->addAction(QStringLiteral("mouse_toggle_annotate"), d->aToggleAnnotator );
d->aToggleAnnotator->setCheckable( true );
connect( d->aToggleAnnotator, &QAction::toggled, this, &PageView::slotToggleAnnotator );
ac->setDefaultShortcut(d->aToggleAnnotator, Qt::Key_F6);
ToolAction *ta = new ToolAction( this );
ac->addAction( QStringLiteral("mouse_selecttools"), ta );
ta->addAction( d->aMouseTextSelect );
ta->addAction( d->aMouseSelect );
ta->addAction( d->aMouseTableSelect );
// speak actions
#ifdef HAVE_SPEECH
d->aSpeakDoc = new QAction( QIcon::fromTheme( QStringLiteral("text-speak") ), i18n( "Speak Whole Document" ), this );
@ -1258,6 +1264,8 @@ void PageView::updateActionState( bool haspages, bool documentChanged, bool hasf
if ( d->mouseModeActionGroup )
d->mouseModeActionGroup->setEnabled( haspages );
if ( d->aMouseModeMenu )
d->aMouseModeMenu->setEnabled( haspages );
if ( d->aRotateClockwise )
d->aRotateClockwise->setEnabled( haspages );

152
ui/toggleactionmenu.cpp Normal file
View file

@ -0,0 +1,152 @@
/***************************************************************************
* Copyright (C) 2019 by David Hurka <david.hurka@mailbox.org> *
* *
* Inspired by and replacing toolaction.h by: *
* Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include "toggleactionmenu.h"
#include <QPointer>
#include <QMenu>
ToggleActionMenu::ToggleActionMenu( QObject * parent )
: ToggleActionMenu( QIcon(), QString(), parent )
{
}
ToggleActionMenu::ToggleActionMenu( const QString &text, QObject * parent )
: ToggleActionMenu( QIcon(), text, parent )
{
}
ToggleActionMenu::ToggleActionMenu( const QIcon &icon,
const QString &text,
QObject * parent,
PopupMode popupMode,
MenuLogic logic )
: KActionMenu( icon, text, parent ),
m_defaultAction( nullptr ),
m_suggestedDefaultAction( nullptr ),
m_menuLogic( logic )
{
connect( this, &QAction::changed, this, &ToggleActionMenu::updateButtons );
if ( popupMode == DelayedPopup )
{
setDelayed( true );
}
else
{
setDelayed( false );
}
setStickyMenu( false );
if ( logic & ImplicitDefaultAction )
{
connect( menu(), &QMenu::triggered, this, &ToggleActionMenu::setDefaultAction );
}
}
QWidget * ToggleActionMenu::createWidget( QWidget * parent )
{
QToolButton * button = qobject_cast< QToolButton * >( KActionMenu::createWidget( parent ) );
if ( !button ) {
// This function is used to add a button into the toolbar.
// KActionMenu will plug itself as QToolButton.
// So, if no QToolButton was returned, this was not called the intended way.
return button;
}
// Remove this menu action from the button,
// so it doesn't compose a menu of this menu action and its own menu.
button->removeAction( this );
// The button has lost the menu now, let it use the correct menu.
button->setMenu( menu() );
m_buttons.append( QPointer< QToolButton >( button ) );
// Apply other properties to the button.
updateButtons();
return button;
}
void ToggleActionMenu::setDefaultAction( QAction *action )
{
m_defaultAction = action;
updateButtons();
}
void ToggleActionMenu::suggestDefaultAction( QAction *action )
{
m_suggestedDefaultAction = action;
}
QAction * ToggleActionMenu::checkedAction( QMenu *menu ) const
{
// Look at each action a in the menu whether it is checked.
// If a is a menu, recursively call checkedAction().
for ( QAction * a : menu->actions() )
{
if ( a->isChecked() )
{
return a;
}
else if ( a->menu() )
{
QAction * b = checkedAction( a->menu() );
if ( b )
{
return b;
}
}
}
return nullptr;
}
void ToggleActionMenu::updateButtons()
{
for ( const QPointer< QToolButton > &button : qAsConst( m_buttons ) )
{
if ( button )
{
button->setDefaultAction( defaultAction() );
// Override some properties of the default action,
// where the property of this menu makes more sense.
button->setEnabled( isEnabled() );
if ( delayed() )
{
button->setPopupMode( QToolButton::DelayedPopup );
}
else if ( stickyMenu() )
{
button->setPopupMode( QToolButton::InstantPopup );
}
else
{
button->setPopupMode( QToolButton::MenuButtonPopup );
}
}
}
}
QAction * ToggleActionMenu::defaultAction()
{
if ( ( m_menuLogic & ImplicitDefaultAction ) && !m_defaultAction )
{
m_defaultAction = checkedAction( menu() );
}
if ( !m_defaultAction )
{
m_defaultAction = m_suggestedDefaultAction;
}
return m_defaultAction;
}

155
ui/toggleactionmenu.h Normal file
View file

@ -0,0 +1,155 @@
/***************************************************************************
* Copyright (C) 2019 by David Hurka <david.hurka@mailbox.org> *
* *
* Inspired by and replacing toolaction.h by: *
* Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#ifndef TOGGLEACTIONMENU_H
#define TOGGLEACTIONMENU_H
#include <KActionMenu>
#include <QToolButton>
#include <QSet>
/**
* @brief A KActionMenu, with allows to set the default action of its toolbar buttons.
*
* Usually, a KActionMenu creates toolbar buttons which reflect its own action properties
* (icon, text, tooltip, checked state,...), as it is a QAction itself.
*
* ToggleActionMenu will use its own action properties only when plugged as submenu in another menu.
* The default action of the toolbar buttons can easily be changed with the slot setDefaultAction().
*
* Naming: The user can *Toggle* the checked state of an *Action* by directly clicking the toolbar button,
* but can also open a *Menu*.
*
* @par Intention
* Setting the default action of the toolbar button can be useful for:
* * Providing the most propably needed entry of a menu directly on the menu button.
* * Showing the last used menu entry on the menu button, including its checked state.
* The advantage is that the user often does not need to open the menu,
* and that the toolbar button shows additional information
* like checked state or the user's last selection.
*
* This shall replace the former ToolAction in Okular,
* while beeing flexible enough for other (planned) action menus.
*/
class ToggleActionMenu : public KActionMenu
{
Q_OBJECT
public:
/**
* Defines how the menu behaves.
*/
enum MenuLogic {
DefaultLogic = 0x0,
/**
* Automatically makes the triggered action the default action, even if in a submenu.
* When a toolbar button is constructed,
* the default action is set to the default action set with setDefaultAction() before,
* otherwise to the first checked action in the menu,
* otherwise to the action suggested with suggestDefaultAction().
*/
ImplicitDefaultAction = 0x1
};
enum PopupMode {
DelayedPopup,
MenuButtonPopup
};
explicit ToggleActionMenu( QObject *parent );
ToggleActionMenu( const QString &text, QObject * parent );
/**
* Constructs an empty ToggleActionMenu.
*
* @param icon The icon of this menu, when plugged into another menu.
* @param text The name of this menu, when plugged into another menu.
* @param parent Parent @c QOject.
* @param popupMode The popup mode of the toolbar buttons.
* You will want to use @c DelayedPopup or @c MenuButtonPopup,
* @c InstantPopup would make @c ToggleActionMenu pointless.
* @param logic To define special behaviour of @c ToggleActionMenu,
* to simplify the usage.
*/
ToggleActionMenu( const QIcon &icon,
const QString &text,
QObject *parent,
PopupMode popupMode = MenuButtonPopup,
MenuLogic logic = DefaultLogic
);
QWidget *createWidget( QWidget *parent ) override;
/**
* Returns the current default action of the toolbar buttons.
*
* In ImplicitDefaultAction mode,
* when the default action was not yet set with setDefaultAction(),
* it will determine it from the first checked action in the menu,
* otherwise from the action set with suggestDefaultAction().
*/
QAction *defaultAction();
/**
* Suggests a default action to be used as fallback.
*
* It will be used if the default action is not determined another way.
* This is useful for ImplicitDefaultAction mode,
* when you can not guarrantee that one action in the menu
* will be checked.
*
* @note
* In DefaultLogic mode, or when you already have called setDefaultAction(),
* you have to use setDefaultAction() instead.
*/
void suggestDefaultAction( QAction * action );
public slots:
/**
* Sets the default action of the toolbar buttons.
*
* This action will be triggered by clicking directly on the toolbar buttons.
* It will also set the text, icon, checked state, etc. of the toolbar buttons.
*
* @note
* The default action will not set the enabled state or popup mode of the menu buttons.
* These properties are still set by the corresponding properties of this ToggleActionMenu.
*
* @warning
* The action will not be added to the menu,
* it usually makes sense to addAction() it before to setDefaultAction() it.
*
* @see suggestDefaultAction()
*/
void setDefaultAction( QAction *action );
private:
QAction *m_defaultAction;
QAction *m_suggestedDefaultAction;
QList< QPointer< QToolButton > > m_buttons;
MenuLogic m_menuLogic;
/**
* Returns the first checked action in @p menu and its submenus,
* or nullptr if no action is checked.
*/
QAction *checkedAction( QMenu *menu ) const;
private slots:
/**
* Updates the toolbar buttons, using both the default action and properties of this menu itself.
*
* This ensures that the toolbar buttons reflect e. g. a disabled state of this menu.
*/
void updateButtons();
};
#endif // TOGGLEACTIONMENU_H

View file

@ -1,87 +0,0 @@
/***************************************************************************
* Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include "toolaction.h"
#include <qmenu.h>
#include <qtoolbar.h>
#include <qtoolbutton.h>
#include <KLocalizedString>
ToolAction::ToolAction( QObject *parent )
: KSelectAction( parent )
{
setText( i18n( "Selection Tools" ) );
}
ToolAction::~ToolAction()
{
}
void ToolAction::addAction( QAction *action )
{
bool setDefault = !m_buttons.isEmpty() ? m_buttons.first()->menu()->actions().isEmpty() : false;
for ( QToolButton *button : qAsConst(m_buttons) )
if ( button )
{
button->menu()->addAction( action );
if ( setDefault )
button->setDefaultAction( action );
}
m_actions.append( action );
}
QWidget* ToolAction::createWidget( QWidget *parent )
{
QToolBar *toolBar = qobject_cast< QToolBar * >( parent );
if ( !toolBar )
return nullptr;
QToolButton *button = new QToolButton( toolBar );
button->setAutoRaise( true );
button->setFocusPolicy( Qt::NoFocus );
button->setIconSize( toolBar->iconSize() );
button->setToolButtonStyle( toolBar->toolButtonStyle() );
button->setPopupMode( QToolButton::MenuButtonPopup );
button->setMenu( new QMenu( button ) );
button->setCheckable( true );
connect(toolBar, &QToolBar::iconSizeChanged, button, &QToolButton::setIconSize);
connect(toolBar, &QToolBar::toolButtonStyleChanged, button, &QToolButton::setToolButtonStyle);
connect(button, &QToolButton::triggered, toolBar, &QToolBar::actionTriggered);
connect( button->menu(), &QMenu::triggered, this, &ToolAction::slotNewDefaultAction );
m_buttons.append( button );
if ( !m_actions.isEmpty() )
{
button->setDefaultAction( m_actions.first() );
for ( QAction *action : qAsConst(m_actions) )
{
button->menu()->addAction( action );
if ( action->isChecked() )
button->setDefaultAction( action );
}
button->setToolTip( i18n("Click to use the current selection tool\nClick on the arrow to choose another selection tool") );
}
return button;
}
void ToolAction::slotNewDefaultAction( QAction *action )
{
for ( QToolButton *button : qAsConst(m_buttons) )
if ( button )
{
button->setDefaultAction( action );
button->setToolTip( i18n("Click to use the current selection tool\nClick on the arrow to choose another selection tool") );
}
}
#include "moc_toolaction.cpp"

View file

@ -1,41 +0,0 @@
/***************************************************************************
* Copyright (C) 2004-2006 by Albert Astals Cid <aacid@kde.org> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#ifndef TOOLACTION_H
#define TOOLACTION_H
#include <qlist.h>
#include <qpointer.h>
#include <KSelectAction>
class QToolButton;
class ToolAction : public KSelectAction
{
Q_OBJECT
public:
explicit ToolAction( QObject *parent = nullptr );
~ToolAction() override;
void addAction( QAction *action );
protected:
QWidget* createWidget( QWidget *parent ) override;
private Q_SLOTS:
void slotNewDefaultAction( QAction *action );
private:
QList< QPointer< QToolButton > > m_buttons;
QList< QAction * > m_actions;
};
#endif