kdelibs/kdeui/widgets/ktoolbar.cpp
2014-11-13 01:04:59 +02:00

1435 lines
47 KiB
C++

/* This file is part of the KDE libraries
Copyright
(C) 2000 Reginald Stadlbauer (reggie@kde.org)
(C) 1997, 1998 Stephan Kulow (coolo@kde.org)
(C) 1997, 1998 Mark Donohoe (donohoe@kde.org)
(C) 1997, 1998 Sven Radej (radej@kde.org)
(C) 1997, 1998 Matthias Ettrich (ettrich@kde.org)
(C) 1999 Chris Schlaeger (cs@kde.org)
(C) 1999 Kurt Granroth (granroth@kde.org)
(C) 2005-2006 Hamish Rodda (rodda@kde.org)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "ktoolbar.h"
#include <config.h>
#include <QtCore/QPointer>
#include <QtGui/QDesktopWidget>
#include <QtGui/QFrame>
#include <QtGui/QLayout>
#include <QtGui/QMouseEvent>
#include <QtGui/QToolButton>
#include <QtXml/QDomElement>
#include <kaction.h>
#include <kactioncollection.h>
#include <kapplication.h>
#include <kauthorized.h>
#include <kconfig.h>
#include <kdebug.h>
#include <kedittoolbar.h>
#include <kglobalsettings.h>
#include <kguiitem.h>
#include <kicon.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kxmlguiwindow.h>
#include <kmenu.h>
#include <kstandardaction.h>
#include <ktoggleaction.h>
#include <kxmlguifactory.h>
#include <kconfiggroup.h>
/*
Toolbar settings (e.g. icon size or toolButtonStyle)
=====================================================
We have the following stack of settings (in order of priority) :
- user-specified settings (loaded/saved in KConfig)
- developer-specified settings in the XMLGUI file (if using xmlgui) (cannot change at runtime)
- KDE-global default (user-configurable; can change at runtime)
and when switching between kparts, they are saved as xml in memory,
which, in the unlikely case of no-kmainwindow-autosaving, could be
different from the user-specified settings saved in KConfig and would have
priority over it.
So, in summary, without XML:
Global config / User settings (loaded/saved in kconfig)
and with XML:
Global config / App-XML attributes / User settings (loaded/saved in kconfig)
And all those settings (except the KDE-global defaults) have to be stored in memory
since we cannot retrieve them at random points in time, not knowing the xml document
nor config file that holds these settings. Hence the iconSizeSettings and toolButtonStyleSettings arrays.
For instance, if you change the KDE-global default, whether this makes a change
on a given toolbar depends on whether there are settings at Level_AppXML or Level_UserSettings.
Only if there are no settings at those levels, should the change of KDEDefault make a difference.
*/
enum SettingLevel { Level_KDEDefault, Level_AppXML, Level_UserSettings,
NSettingLevels };
enum { Unset = -1 };
class KToolBar::Private
{
public:
Private(KToolBar *qq)
: q(qq),
isMainToolBar(false),
#ifndef KDE_NO_DEPRECATED
enableContext(true),
#endif
unlockedMovable(true),
contextOrient(0),
contextMode(0),
contextSize(0),
contextButtonTitle(0),
contextShowText(0),
contextButtonAction(0),
contextTop(0),
contextLeft(0),
contextRight(0),
contextBottom(0),
contextIcons(0),
contextTextRight(0),
contextText(0),
contextTextUnder(0),
contextLockAction(0),
dropIndicatorAction(0),
context(0),
dragAction(0)
{
}
void slotAppearanceChanged();
void slotContextAboutToShow();
void slotContextAboutToHide();
void slotContextLeft();
void slotContextRight();
void slotContextShowText();
void slotContextTop();
void slotContextBottom();
void slotContextIcons();
void slotContextText();
void slotContextTextRight();
void slotContextTextUnder();
void slotContextIconSize();
void slotLockToolBars(bool lock);
void init(bool readConfig = true, bool isMainToolBar = false);
QString getPositionAsString() const;
KMenu *contextMenu(const QPoint &globalPos);
void setLocked(bool locked);
void adjustSeparatorVisibility();
void loadKDESettings();
void applyCurrentSettings();
QAction *findAction(const QString &actionName, KXMLGUIClient **client = 0) const;
static Qt::ToolButtonStyle toolButtonStyleFromString(const QString& style);
static QString toolButtonStyleToString(Qt::ToolButtonStyle);
static Qt::ToolBarArea positionFromString(const QString& position);
KToolBar *q;
bool isMainToolBar : 1;
#ifndef KDE_NO_DEPRECATED
bool enableContext : 1;
#endif
bool unlockedMovable : 1;
static bool s_editable;
static bool s_locked;
QSet<KXMLGUIClient *> xmlguiClients;
QMenu* contextOrient;
QMenu* contextMode;
QMenu* contextSize;
QAction* contextButtonTitle;
QAction* contextShowText;
QAction* contextButtonAction;
QAction* contextTop;
QAction* contextLeft;
QAction* contextRight;
QAction* contextBottom;
QAction* contextIcons;
QAction* contextTextRight;
QAction* contextText;
QAction* contextTextUnder;
KToggleAction* contextLockAction;
QMap<QAction*,int> contextIconSizes;
class IntSetting
{
public:
IntSetting() {
for (int level = 0; level < NSettingLevels; ++level) {
values[level] = Unset;
}
}
int currentValue() const {
int val = Unset;
for (int level = 0; level < NSettingLevels; ++level) {
if (values[level] != Unset)
val = values[level];
}
return val;
}
// Default value as far as the user is concerned is kde-global + app-xml.
// If currentValue()==defaultValue() then nothing to write into kconfig.
int defaultValue() const {
int val = Unset;
for (int level = 0; level < Level_UserSettings; ++level) {
if (values[level] != Unset)
val = values[level];
}
return val;
}
QString toString() const {
QString str;
for (int level = 0; level < NSettingLevels; ++level) {
str += QString::number(values[level]) + ' ';
}
return str;
}
int& operator[](int index) { return values[index]; }
private:
int values[NSettingLevels];
};
IntSetting iconSizeSettings;
IntSetting toolButtonStyleSettings; // either Qt::ToolButtonStyle or -1, hence "int".
QList<QAction*> actionsBeingDragged;
QAction* dropIndicatorAction;
KMenu* context;
KAction* dragAction;
QPoint dragStartPosition;
};
bool KToolBar::Private::s_editable = false;
bool KToolBar::Private::s_locked = true;
void KToolBar::Private::init(bool readConfig, bool _isMainToolBar)
{
isMainToolBar = _isMainToolBar;
loadKDESettings();
// also read in our configurable settings (for non-xmlgui toolbars)
// KDE5: we can probably remove this, if people save settings then they load them too, e.g. using KMainWindow's autosave.
if (readConfig) {
KConfigGroup cg(KGlobal::config(), QString());
q->applySettings(cg);
}
if (q->mainWindow()) {
// Get notified when settings change
connect(q, SIGNAL(allowedAreasChanged(Qt::ToolBarAreas)),
q->mainWindow(), SLOT(setSettingsDirty()));
connect(q, SIGNAL(iconSizeChanged(QSize)),
q->mainWindow(), SLOT(setSettingsDirty()));
connect(q, SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)),
q->mainWindow(), SLOT(setSettingsDirty()));
connect(q, SIGNAL(movableChanged(bool)),
q->mainWindow(), SLOT(setSettingsDirty()));
connect(q, SIGNAL(orientationChanged(Qt::Orientation)),
q->mainWindow(), SLOT(setSettingsDirty()));
}
if (!KAuthorized::authorize("movable_toolbars"))
q->setMovable(false);
else
q->setMovable(!KToolBar::toolBarsLocked());
connect(q, SIGNAL(movableChanged(bool)),
q, SLOT(slotMovableChanged(bool)));
q->setAcceptDrops(true);
connect(KGlobalSettings::self(), SIGNAL(toolbarAppearanceChanged(int)),
q, SLOT(slotAppearanceChanged()));
connect(KIconLoader::global(), SIGNAL(iconLoaderSettingsChanged()),
q, SLOT(slotAppearanceChanged()));
}
QString KToolBar::Private::getPositionAsString() const
{
// get all of the stuff to save
switch (q->mainWindow()->toolBarArea(const_cast<KToolBar*>(q))) {
case Qt::BottomToolBarArea:
return "Bottom";
case Qt::LeftToolBarArea:
return "Left";
case Qt::RightToolBarArea:
return "Right";
case Qt::TopToolBarArea:
default:
return "Top";
}
}
KMenu *KToolBar::Private::contextMenu(const QPoint &globalPos)
{
if (!context) {
context = new KMenu(q);
contextButtonTitle = context->addTitle(i18nc("@title:menu", "Show Text"));
contextShowText = context->addAction(QString(), q, SLOT(slotContextShowText()));
context->addTitle(i18nc("@title:menu", "Toolbar Settings"));
contextOrient = new KMenu(i18nc("Toolbar orientation", "Orientation"), context);
contextTop = contextOrient->addAction(i18nc("toolbar position string", "Top"), q, SLOT(slotContextTop()));
contextTop->setChecked(true);
contextLeft = contextOrient->addAction(i18nc("toolbar position string", "Left"), q, SLOT(slotContextLeft()));
contextRight = contextOrient->addAction(i18nc("toolbar position string", "Right"), q, SLOT(slotContextRight()));
contextBottom = contextOrient->addAction(i18nc("toolbar position string", "Bottom"), q, SLOT(slotContextBottom()));
QActionGroup* positionGroup = new QActionGroup(contextOrient);
foreach (QAction* action, contextOrient->actions()) {
action->setActionGroup(positionGroup);
action->setCheckable(true);
}
contextMode = new KMenu(i18n("Text Position"), context);
contextIcons = contextMode->addAction(i18n("Icons Only"), q, SLOT(slotContextIcons()));
contextText = contextMode->addAction(i18n("Text Only"), q, SLOT(slotContextText()));
contextTextRight = contextMode->addAction(i18n("Text Alongside Icons"), q, SLOT(slotContextTextRight()));
contextTextUnder = contextMode->addAction(i18n("Text Under Icons"), q, SLOT(slotContextTextUnder()));
QActionGroup* textGroup = new QActionGroup(contextMode);
foreach (QAction* action, contextMode->actions()) {
action->setActionGroup(textGroup);
action->setCheckable(true);
}
contextSize = new KMenu(i18n("Icon Size"), context);
contextIconSizes.insert(contextSize->addAction(i18nc("@item:inmenu Icon size", "Default"), q, SLOT(slotContextIconSize())),
iconSizeSettings.defaultValue());
// Query the current theme for available sizes
KIconTheme *theme = KIconLoader::global()->theme();
QList<int> avSizes;
if (theme) {
avSizes = theme->querySizes(isMainToolBar ? KIconLoader::MainToolbar : KIconLoader::Toolbar);
}
qSort(avSizes);
if (avSizes.count() < 10) {
// Fixed or threshold type icons
foreach (int it, avSizes) {
QString text;
if (it < 19)
text = i18n("Small (%1x%2)", it, it);
else if (it < 25)
text = i18n("Medium (%1x%2)", it, it);
else if (it < 35)
text = i18n("Large (%1x%2)", it, it);
else
text = i18n("Huge (%1x%2)", it, it);
// save the size in the contextIconSizes map
contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it);
}
} else {
// Scalable icons.
const int progression[] = { 16, 22, 32, 48, 64, 96, 128, 192, 256 };
for (uint i = 0; i < 9; i++) {
foreach (int it, avSizes) {
if (it >= progression[ i ]) {
QString text;
if (it < 19)
text = i18n("Small (%1x%2)", it, it);
else if (it < 25)
text = i18n("Medium (%1x%2)", it, it);
else if (it < 35)
text = i18n("Large (%1x%2)", it, it);
else
text = i18n("Huge (%1x%2)", it, it);
// save the size in the contextIconSizes map
contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it);
break;
}
}
}
}
QActionGroup* sizeGroup = new QActionGroup(contextSize);
foreach (QAction* action, contextSize->actions()) {
action->setActionGroup(sizeGroup);
action->setCheckable(true);
}
if (!q->toolBarsLocked() && !q->isMovable())
unlockedMovable = false;
delete contextLockAction;
contextLockAction = new KToggleAction(KIcon("system-lock-screen"), i18n("Lock Toolbar Positions"), q);
contextLockAction->setChecked(q->toolBarsLocked());
connect(contextLockAction, SIGNAL(toggled(bool)), q, SLOT(slotLockToolBars(bool)));
// Now add the actions to the menu
context->addMenu(contextMode);
context->addMenu(contextSize);
context->addMenu(contextOrient);
context->addSeparator();
connect(context, SIGNAL(aboutToShow()), q, SLOT(slotContextAboutToShow()));
}
contextButtonAction = q->actionAt(q->mapFromGlobal(globalPos));
if (contextButtonAction) {
contextShowText->setText(contextButtonAction->text());
contextShowText->setIcon(contextButtonAction->icon());
contextShowText->setCheckable(true);
}
contextOrient->menuAction()->setVisible(!q->toolBarsLocked());
// Unplugging a submenu from abouttohide leads to the popupmenu floating around
// So better simply call that code from after exec() returns (DF)
//connect(context, SIGNAL(aboutToHide()), this, SLOT(slotContextAboutToHide()));
return context;
}
void KToolBar::Private::setLocked(bool locked)
{
if (unlockedMovable)
q->setMovable(!locked);
}
void KToolBar::Private::adjustSeparatorVisibility()
{
bool visibleNonSeparator = false;
int separatorToShow = -1;
for (int index = 0; index < q->actions().count(); ++index) {
QAction* action = q->actions()[ index ];
if (action->isSeparator()) {
if (visibleNonSeparator) {
separatorToShow = index;
visibleNonSeparator = false;
} else {
action->setVisible(false);
}
} else if (!visibleNonSeparator) {
if (action->isVisible()) {
visibleNonSeparator = true;
if (separatorToShow != -1) {
q->actions()[ separatorToShow ]->setVisible(true);
separatorToShow = -1;
}
}
}
}
if (separatorToShow != -1)
q->actions()[ separatorToShow ]->setVisible(false);
}
Qt::ToolButtonStyle KToolBar::Private::toolButtonStyleFromString(const QString & _style)
{
QString style = _style.toLower();
if (style == "textbesideicon" || style == "icontextright")
return Qt::ToolButtonTextBesideIcon;
else if (style == "textundericon" || style == "icontextbottom")
return Qt::ToolButtonTextUnderIcon;
else if (style == "textonly")
return Qt::ToolButtonTextOnly;
else
return Qt::ToolButtonIconOnly;
}
QString KToolBar::Private::toolButtonStyleToString(Qt::ToolButtonStyle style)
{
switch(style)
{
case Qt::ToolButtonIconOnly:
default:
return "IconOnly";
case Qt::ToolButtonTextBesideIcon:
return "TextBesideIcon";
case Qt::ToolButtonTextOnly:
return "TextOnly";
case Qt::ToolButtonTextUnderIcon:
return "TextUnderIcon";
}
}
Qt::ToolBarArea KToolBar::Private::positionFromString(const QString& position)
{
Qt::ToolBarArea newposition = Qt::TopToolBarArea;
if (position == QLatin1String("left")) {
newposition = Qt::LeftToolBarArea;
} else if (position == QLatin1String("bottom")) {
newposition = Qt::BottomToolBarArea;
} else if (position == QLatin1String("right")) {
newposition = Qt::RightToolBarArea;
}
return newposition;
}
// Global setting was changed
void KToolBar::Private::slotAppearanceChanged()
{
loadKDESettings();
applyCurrentSettings();
}
void KToolBar::Private::loadKDESettings()
{
iconSizeSettings[Level_KDEDefault] = q->iconSizeDefault();
if (isMainToolBar) {
toolButtonStyleSettings[Level_KDEDefault] = q->toolButtonStyleSetting();
} else {
const QString fallBack = toolButtonStyleToString(Qt::ToolButtonTextBesideIcon);
/**
TODO: if we get complaints about text beside icons on small screens,
try the following code out on such systems - aseigo.
// if we are on a small screen with a non-landscape ratio, then
// we revert to text under icons since width is probably not our
// friend in such cases
QDesktopWidget *desktop = QApplication::desktop();
QRect screenGeom = desktop->screenGeometry(desktop->primaryScreen());
qreal ratio = screenGeom.width() / qreal(screenGeom.height());
if (screenGeom.width() < 1024 && ratio <= 1.4) {
fallBack = "TextUnderIcon";
}
**/
KConfigGroup group(KGlobal::config(), "Toolbar style");
const QString value = group.readEntry("ToolButtonStyleOtherToolbars", fallBack);
toolButtonStyleSettings[Level_KDEDefault] = KToolBar::Private::toolButtonStyleFromString(value);
}
}
// Call this after changing something in d->iconSizeSettings or d->toolButtonStyleSettings
void KToolBar::Private::applyCurrentSettings()
{
//kDebug() << q->objectName() << "iconSizeSettings:" << iconSizeSettings.toString() << "->" << iconSizeSettings.currentValue();
const int currentIconSize = iconSizeSettings.currentValue();
q->setIconSize(QSize(currentIconSize, currentIconSize));
//kDebug() << q->objectName() << "toolButtonStyleSettings:" << toolButtonStyleSettings.toString() << "->" << toolButtonStyleSettings.currentValue();
q->setToolButtonStyle(static_cast<Qt::ToolButtonStyle>(toolButtonStyleSettings.currentValue()));
// And remember to save the new look later
KMainWindow *kmw = q->mainWindow();
if (kmw)
kmw->setSettingsDirty();
}
QAction *KToolBar::Private::findAction(const QString &actionName, KXMLGUIClient **clientOut) const
{
foreach (KXMLGUIClient* client, xmlguiClients) {
QAction* action = client->actionCollection()->action(actionName);
if (action) {
if (clientOut) {
*clientOut = client;
}
return action;
}
}
return 0;
}
void KToolBar::Private::slotContextAboutToShow()
{
/**
* The idea here is to reuse the "static" part of the menu to save time.
* But the "Toolbars" action is dynamic (can be a single action or a submenu)
* and ToolBarHandler::setupActions() deletes it, so better not keep it around.
* So we currently plug/unplug the last two actions of the menu.
* Another way would be to keep around the actions and plug them all into a (new each time) popupmenu.
*/
KXmlGuiWindow *kmw = qobject_cast<KXmlGuiWindow *>(q->mainWindow());
// try to find "configure toolbars" action
QAction *configureAction = 0;
const char* actionName = KStandardAction::name(KStandardAction::ConfigureToolbars);
configureAction = findAction(actionName);
if (!configureAction && kmw) {
configureAction = kmw->actionCollection()->action(actionName);
}
if (configureAction) {
context->addAction(configureAction);
}
context->addAction(contextLockAction);
if (kmw) {
kmw->setupToolbarMenuActions();
// Only allow hiding a toolbar if the action is also plugged somewhere else (e.g. menubar)
QAction *tbAction = kmw->toolBarMenuAction();
if (!q->toolBarsLocked() && tbAction && tbAction->associatedWidgets().count() > 0)
context->addAction(tbAction);
}
KEditToolBar::setGlobalDefaultToolBar(q->QObject::objectName().toLatin1().constData());
// Check the actions that should be checked
switch (q->toolButtonStyle()) {
case Qt::ToolButtonIconOnly:
default:
contextIcons->setChecked(true);
break;
case Qt::ToolButtonTextBesideIcon:
contextTextRight->setChecked(true);
break;
case Qt::ToolButtonTextOnly:
contextText->setChecked(true);
break;
case Qt::ToolButtonTextUnderIcon:
contextTextUnder->setChecked(true);
break;
}
QMapIterator< QAction*, int > it = contextIconSizes;
while (it.hasNext()) {
it.next();
if (it.value() == q->iconSize().width()) {
it.key()->setChecked(true);
break;
}
}
switch (q->mainWindow()->toolBarArea(q)) {
case Qt::BottomToolBarArea:
contextBottom->setChecked(true);
break;
case Qt::LeftToolBarArea:
contextLeft->setChecked(true);
break;
case Qt::RightToolBarArea:
contextRight->setChecked(true);
break;
default:
case Qt::TopToolBarArea:
contextTop->setChecked(true);
break;
}
const bool showButtonSettings = contextButtonAction
&& !contextShowText->text().isEmpty()
&& contextTextRight->isChecked();
contextButtonTitle->setVisible(showButtonSettings);
contextShowText->setVisible(showButtonSettings);
if (showButtonSettings) {
contextShowText->setChecked(contextButtonAction->priority() >= QAction::NormalPriority);
}
}
void KToolBar::Private::slotContextAboutToHide()
{
// We have to unplug whatever slotContextAboutToShow plugged into the menu.
// Unplug the toolbar menu action
KXmlGuiWindow *kmw = qobject_cast<KXmlGuiWindow *>(q->mainWindow());
if (kmw && kmw->toolBarMenuAction()) {
if (kmw->toolBarMenuAction()->associatedWidgets().count() > 1) {
context->removeAction(kmw->toolBarMenuAction());
}
}
// Unplug the configure toolbars action too, since it's afterwards anyway
QAction *configureAction = 0;
const char* actionName = KStandardAction::name(KStandardAction::ConfigureToolbars);
configureAction = findAction(actionName);
if (!configureAction && kmw) {
configureAction = kmw->actionCollection()->action(actionName);
}
if (configureAction) {
context->removeAction(configureAction);
}
context->removeAction(contextLockAction);
}
void KToolBar::Private::slotContextLeft()
{
q->mainWindow()->addToolBar(Qt::LeftToolBarArea, q);
}
void KToolBar::Private::slotContextRight()
{
q->mainWindow()->addToolBar(Qt::RightToolBarArea, q);
}
void KToolBar::Private::slotContextShowText()
{
Q_ASSERT(contextButtonAction);
const QAction::Priority priority = contextShowText->isChecked()
? QAction::NormalPriority : QAction::LowPriority;
contextButtonAction->setPriority(priority);
// Find to which xml file and componentData the action belongs to
KComponentData componentData;
QString filename;
KXMLGUIClient *client;
if (findAction(contextButtonAction->objectName(), &client)) {
componentData = client->componentData();
filename = client->xmlFile();
}
if (filename.isEmpty()) {
componentData = KGlobal::mainComponent();
filename = componentData.componentName() + "ui.rc";
}
// Save the priority state of the action
const QString configFile = KXMLGUIFactory::readConfigFile(filename, componentData);
QDomDocument document;
document.setContent(configFile);
QDomElement elem = KXMLGUIFactory::actionPropertiesElement(document);
QDomElement actionElem = KXMLGUIFactory::findActionByName(elem, contextButtonAction->objectName(), true);
actionElem.setAttribute("priority", priority);
KXMLGUIFactory::saveConfigFile(document, filename, componentData);
}
void KToolBar::Private::slotContextTop()
{
q->mainWindow()->addToolBar(Qt::TopToolBarArea, q);
}
void KToolBar::Private::slotContextBottom()
{
q->mainWindow()->addToolBar(Qt::BottomToolBarArea, q);
}
void KToolBar::Private::slotContextIcons()
{
q->setToolButtonStyle(Qt::ToolButtonIconOnly);
toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle();
}
void KToolBar::Private::slotContextText()
{
q->setToolButtonStyle(Qt::ToolButtonTextOnly);
toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle();
}
void KToolBar::Private::slotContextTextUnder()
{
q->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle();
}
void KToolBar::Private::slotContextTextRight()
{
q->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle();
}
void KToolBar::Private::slotContextIconSize()
{
QAction* action = qobject_cast<QAction*>(q->sender());
if (action && contextIconSizes.contains(action)) {
const int iconSize = contextIconSizes.value(action);
q->setIconDimensions(iconSize);
}
}
void KToolBar::Private::slotLockToolBars(bool lock)
{
q->setToolBarsLocked(lock);
}
KToolBar::KToolBar(QWidget *parent, bool isMainToolBar, bool readConfig)
: QToolBar(parent),
d(new Private(this))
{
d->init(readConfig, isMainToolBar);
// KToolBar is auto-added to the top area of the main window if parent is a QMainWindow
if (QMainWindow* mw = qobject_cast<QMainWindow*>(parent))
mw->addToolBar(this);
}
KToolBar::KToolBar(const QString& objectName, QWidget *parent, bool readConfig)
: QToolBar(parent),
d(new Private(this))
{
setObjectName(objectName);
// mainToolBar -> isMainToolBar = true -> buttonStyle is configurable
// others -> isMainToolBar = false -> ### hardcoded default for buttonStyle !!! should be configurable? -> hidden key added
d->init(readConfig, objectName == "mainToolBar");
// KToolBar is auto-added to the top area of the main window if parent is a QMainWindow
if (QMainWindow* mw = qobject_cast<QMainWindow*>(parent))
mw->addToolBar(this);
}
KToolBar::KToolBar(const QString& objectName, QMainWindow* parent, Qt::ToolBarArea area,
bool newLine, bool isMainToolBar, bool readConfig)
: QToolBar(parent),
d(new Private(this))
{
setObjectName(objectName);
d->init(readConfig, isMainToolBar);
if (newLine)
mainWindow()->addToolBarBreak(area);
mainWindow()->addToolBar(area, this);
if (newLine)
mainWindow()->addToolBarBreak(area);
}
KToolBar::~KToolBar()
{
delete d->contextLockAction;
delete d;
}
#ifndef KDE_NO_DEPRECATED
void KToolBar::setContextMenuEnabled(bool enable)
{
d->enableContext = enable;
}
#endif
#ifndef KDE_NO_DEPRECATED
bool KToolBar::contextMenuEnabled() const
{
return d->enableContext;
}
#endif
void KToolBar::saveSettings(KConfigGroup &cg)
{
Q_ASSERT(!cg.name().isEmpty());
if (cg.hasKey("Hidden")) {
cg.deleteEntry("Hidden"); // remove old key to avoid bugs from the compat code in applySettings. KDE5: remove.
}
const int currentIconSize = iconSize().width();
//kDebug() << objectName() << currentIconSize << d->iconSizeSettings.toString() << "defaultValue=" << d->iconSizeSettings.defaultValue();
if (!cg.hasDefault("IconSize") && currentIconSize == d->iconSizeSettings.defaultValue()) {
cg.revertToDefault("IconSize");
d->iconSizeSettings[Level_UserSettings] = Unset;
} else {
cg.writeEntry("IconSize", currentIconSize);
d->iconSizeSettings[Level_UserSettings] = currentIconSize;
}
const Qt::ToolButtonStyle currentToolButtonStyle = toolButtonStyle();
if (!cg.hasDefault("ToolButtonStyle") && currentToolButtonStyle == d->toolButtonStyleSettings.defaultValue()) {
cg.revertToDefault("ToolButtonStyle");
d->toolButtonStyleSettings[Level_UserSettings] = Unset;
} else {
cg.writeEntry("ToolButtonStyle", d->toolButtonStyleToString(currentToolButtonStyle));
d->toolButtonStyleSettings[Level_UserSettings] = currentToolButtonStyle;
}
}
#ifndef KDE_NO_DEPRECATED
void KToolBar::setXMLGUIClient(KXMLGUIClient *client)
{
d->xmlguiClients.clear();
d->xmlguiClients << client;
}
#endif
void KToolBar::addXMLGUIClient( KXMLGUIClient *client )
{
d->xmlguiClients << client;
}
void KToolBar::removeXMLGUIClient( KXMLGUIClient *client )
{
d->xmlguiClients.remove(client);
}
void KToolBar::contextMenuEvent(QContextMenuEvent* event)
{
#ifndef KDE_NO_DEPRECATED
if (mainWindow() && d->enableContext) {
QPointer<KToolBar> guard(this);
const QPoint globalPos = event->globalPos();
d->contextMenu(globalPos)->exec(globalPos);
// "Configure Toolbars" recreates toolbars, so we might not exist anymore.
if (guard) {
d->slotContextAboutToHide();
}
return;
}
#endif
QToolBar::contextMenuEvent(event);
}
Qt::ToolButtonStyle KToolBar::toolButtonStyleSetting()
{
KConfigGroup group(KGlobal::config(), "Toolbar style");
const QString fallback = Private::toolButtonStyleToString(Qt::ToolButtonTextBesideIcon);
return KToolBar::Private::toolButtonStyleFromString(group.readEntry("ToolButtonStyle", fallback));
}
void KToolBar::loadState(const QDomElement &element)
{
QMainWindow *mw = mainWindow();
if (!mw)
return;
{
QDomNode textNode = element.namedItem("text");
QByteArray text;
QByteArray context;
if (textNode.isElement())
{
QDomElement textElement = textNode.toElement();
text = textElement.text().toUtf8();
context = textElement.attribute("context").toUtf8();
}
else
{
textNode = element.namedItem("Text");
if (textNode.isElement())
{
QDomElement textElement = textNode.toElement();
text = textElement.text().toUtf8();
context = textElement.attribute("context").toUtf8();
}
}
QString i18nText;
if (!text.isEmpty() && !context.isEmpty())
i18nText = i18nc(context, text);
else if (!text.isEmpty())
i18nText = i18n(text);
if (!i18nText.isEmpty())
setWindowTitle(i18nText);
}
/*
This method is called in order to load toolbar settings from XML.
However this can be used in two rather different cases:
- for the initial loading of the app's XML. In that case the settings
are only the defaults (Level_AppXML), the user's KConfig settings will override them
- for later re-loading when switching between parts in KXMLGUIFactory.
In that case the XML contains the final settings, not the defaults.
We do need the defaults, and the toolbar might have been completely
deleted and recreated meanwhile. So we store the app-default settings
into the XML.
*/
bool loadingAppDefaults = true;
if (element.hasAttribute("tempXml")) {
// this isn't the first time, so the app-xml defaults have been saved into the (in-memory) XML
loadingAppDefaults = false;
const QString iconSizeDefault = element.attribute("iconSizeDefault");
if (!iconSizeDefault.isEmpty()) {
d->iconSizeSettings[Level_AppXML] = iconSizeDefault.toInt();
}
const QString toolButtonStyleDefault = element.attribute("toolButtonStyleDefault");
if (!toolButtonStyleDefault.isEmpty()) {
d->toolButtonStyleSettings[Level_AppXML] = d->toolButtonStyleFromString(toolButtonStyleDefault);
}
} else {
// loading app defaults
bool newLine = false;
QString attrNewLine = element.attribute("newline").toLower();
if (!attrNewLine.isEmpty())
newLine = attrNewLine == "true";
if (newLine && mw)
mw->insertToolBarBreak(this);
}
int newIconSize = -1;
if (element.hasAttribute("iconSize")) {
bool ok;
newIconSize = element.attribute("iconSize").trimmed().toInt(&ok);
if (!ok)
newIconSize = -1;
}
if (newIconSize != -1)
d->iconSizeSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = newIconSize;
const QString newToolButtonStyle = element.attribute("iconText");
if (!newToolButtonStyle.isEmpty())
d->toolButtonStyleSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = d->toolButtonStyleFromString(newToolButtonStyle);
bool hidden = false;
{
QString attrHidden = element.attribute("hidden").toLower();
if (!attrHidden.isEmpty())
hidden = attrHidden == "true";
}
Qt::ToolBarArea pos = Qt::NoToolBarArea;
{
QString attrPosition = element.attribute("position").toLower();
if (!attrPosition.isEmpty())
pos = KToolBar::Private::positionFromString(attrPosition);
}
if (pos != Qt::NoToolBarArea)
mw->addToolBar(pos, this);
setVisible(!hidden);
d->applyCurrentSettings();
}
// Called when switching between xmlgui clients, in order to find any unsaved settings
// again when switching back to the current xmlgui client.
void KToolBar::saveState(QDomElement &current) const
{
Q_ASSERT(!current.isNull());
current.setAttribute("tempXml", "true");
current.setAttribute("noMerge", "1");
current.setAttribute("position", d->getPositionAsString().toLower());
current.setAttribute("hidden", isHidden() ? "true" : "false");
const int currentIconSize = iconSize().width();
if (currentIconSize == d->iconSizeSettings.defaultValue())
current.removeAttribute("iconSize");
else
current.setAttribute("iconSize", iconSize().width());
if (toolButtonStyle() == d->toolButtonStyleSettings.defaultValue())
current.removeAttribute("iconText");
else
current.setAttribute("iconText", d->toolButtonStyleToString(toolButtonStyle()));
// Note: if this method is used by more than KXMLGUIBuilder, e.g. to save XML settings to *disk*,
// then the stuff below shouldn't always be done. This is not the case currently though.
if (d->iconSizeSettings[Level_AppXML] != Unset) {
current.setAttribute("iconSizeDefault", d->iconSizeSettings[Level_AppXML]);
}
if (d->toolButtonStyleSettings[Level_AppXML] != Unset) {
const Qt::ToolButtonStyle bs = static_cast<Qt::ToolButtonStyle>(d->toolButtonStyleSettings[Level_AppXML]);
current.setAttribute("toolButtonStyleDefault", d->toolButtonStyleToString(bs));
}
}
// called by KMainWindow::applyMainWindowSettings to read from the user settings
void KToolBar::applySettings(const KConfigGroup &cg, bool forceGlobal)
{
Q_ASSERT(!cg.name().isEmpty());
Q_UNUSED(forceGlobal); // KDE5: remove
// a small leftover from kde3: separate bool for hidden/shown. But it's also part of saveMainWindowSettings,
// it is not really useful anymore, except in the unlikely case where someone would call this by hand.
// KDE5: remove the block below
if (cg.hasKey("Hidden")) {
const bool hidden = cg.readEntry("Hidden", false);
if (hidden)
hide();
else {
show();
}
}
if (cg.hasKey("IconSize")) {
d->iconSizeSettings[Level_UserSettings] = cg.readEntry("IconSize", 0);
}
if (cg.hasKey("ToolButtonStyle")) {
d->toolButtonStyleSettings[Level_UserSettings] = d->toolButtonStyleFromString(cg.readEntry("ToolButtonStyle", QString()));
}
d->applyCurrentSettings();
}
KMainWindow * KToolBar::mainWindow() const
{
return qobject_cast<KMainWindow*>(const_cast<QObject*>(parent()));
}
void KToolBar::setIconDimensions(int size)
{
QToolBar::setIconSize(QSize(size, size));
d->iconSizeSettings[Level_UserSettings] = size;
}
int KToolBar::iconSizeDefault() const
{
return KIconLoader::global()->currentSize(d->isMainToolBar ? KIconLoader::MainToolbar : KIconLoader::Toolbar);
}
void KToolBar::slotMovableChanged(bool movable)
{
if (movable && !KAuthorized::authorize("movable_toolbars"))
setMovable(false);
}
void KToolBar::dragEnterEvent(QDragEnterEvent *event)
{
if (toolBarsEditable() && event->proposedAction() & (Qt::CopyAction | Qt::MoveAction) &&
event->mimeData()->hasFormat("application/x-kde-action-list")) {
QByteArray data = event->mimeData()->data("application/x-kde-action-list");
QDataStream stream(data);
QStringList actionNames;
stream >> actionNames;
foreach (const QString& actionName, actionNames) {
foreach (KActionCollection* ac, KActionCollection::allCollections()) {
QAction* newAction = ac->action(actionName.toLatin1().constData());
if (newAction) {
d->actionsBeingDragged.append(newAction);
break;
}
}
}
if (d->actionsBeingDragged.count()) {
QAction* overAction = actionAt(event->pos());
QFrame* dropIndicatorWidget = new QFrame(this);
dropIndicatorWidget->resize(8, height() - 4);
dropIndicatorWidget->setFrameShape(QFrame::VLine);
dropIndicatorWidget->setLineWidth(3);
d->dropIndicatorAction = insertWidget(overAction, dropIndicatorWidget);
insertAction(overAction, d->dropIndicatorAction);
event->acceptProposedAction();
return;
}
}
QToolBar::dragEnterEvent(event);
}
void KToolBar::dragMoveEvent(QDragMoveEvent *event)
{
if (toolBarsEditable())
forever {
if (d->dropIndicatorAction) {
QAction* overAction = 0L;
foreach (QAction* action, actions()) {
// want to make it feel that half way across an action you're dropping on the other side of it
QWidget* widget = widgetForAction(action);
if (event->pos().x() < widget->pos().x() + (widget->width() / 2)) {
overAction = action;
break;
}
}
if (overAction != d->dropIndicatorAction) {
// Check to see if the indicator is already in the right spot
int dropIndicatorIndex = actions().indexOf(d->dropIndicatorAction);
if (dropIndicatorIndex + 1 < actions().count()) {
if (actions()[ dropIndicatorIndex + 1 ] == overAction)
break;
} else if (!overAction) {
break;
}
insertAction(overAction, d->dropIndicatorAction);
}
event->accept();
return;
}
break;
}
QToolBar::dragMoveEvent(event);
}
void KToolBar::dragLeaveEvent(QDragLeaveEvent *event)
{
// Want to clear this even if toolBarsEditable was changed mid-drag (unlikey)
delete d->dropIndicatorAction;
d->dropIndicatorAction = 0L;
d->actionsBeingDragged.clear();
if (toolBarsEditable()) {
event->accept();
return;
}
QToolBar::dragLeaveEvent(event);
}
void KToolBar::dropEvent(QDropEvent *event)
{
if (toolBarsEditable()) {
foreach (QAction* action, d->actionsBeingDragged) {
if (actions().contains(action))
removeAction(action);
insertAction(d->dropIndicatorAction, action);
}
}
// Want to clear this even if toolBarsEditable was changed mid-drag (unlikey)
delete d->dropIndicatorAction;
d->dropIndicatorAction = 0L;
d->actionsBeingDragged.clear();
if (toolBarsEditable()) {
event->accept();
return;
}
QToolBar::dropEvent(event);
}
void KToolBar::mousePressEvent(QMouseEvent *event)
{
if (toolBarsEditable() && event->button() == Qt::LeftButton) {
if (KAction* action = qobject_cast<KAction*>(actionAt(event->pos()))) {
d->dragAction = action;
d->dragStartPosition = event->pos();
event->accept();
return;
}
}
QToolBar::mousePressEvent(event);
}
void KToolBar::mouseMoveEvent(QMouseEvent *event)
{
if (!toolBarsEditable() || !d->dragAction)
return QToolBar::mouseMoveEvent(event);
if ((event->pos() - d->dragStartPosition).manhattanLength() < QApplication::startDragDistance()) {
event->accept();
return;
}
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
QByteArray data;
{
QDataStream stream(&data, QIODevice::WriteOnly);
QStringList actionNames;
actionNames << d->dragAction->objectName();
stream << actionNames;
}
mimeData->setData("application/x-kde-action-list", data);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->start(Qt::MoveAction);
if (dropAction == Qt::MoveAction)
// Only remove from this toolbar if it was moved to another toolbar
// Otherwise the receiver moves it.
if (drag->target() != this)
removeAction(d->dragAction);
d->dragAction = 0L;
event->accept();
}
void KToolBar::mouseReleaseEvent(QMouseEvent *event)
{
// Want to clear this even if toolBarsEditable was changed mid-drag (unlikey)
if (d->dragAction) {
d->dragAction = 0L;
event->accept();
return;
}
QToolBar::mouseReleaseEvent(event);
}
bool KToolBar::eventFilter(QObject * watched, QEvent * event)
{
// Generate context menu events for disabled buttons too...
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
if (me->buttons() & Qt::RightButton)
if (QWidget* ww = qobject_cast<QWidget*>(watched))
if (ww->parent() == this && !ww->isEnabled())
QCoreApplication::postEvent(this, new QContextMenuEvent(QContextMenuEvent::Mouse, me->pos(), me->globalPos()));
} else if (event->type() == QEvent::ParentChange) {
// Make sure we're not leaving stale event filters around,
// when a child is reparented somewhere else
if (QWidget* ww = qobject_cast<QWidget*>(watched)) {
if (!this->isAncestorOf(ww)) {
// New parent is not a subwidget - remove event filter
ww->removeEventFilter(this);
foreach (QWidget* child, ww->findChildren<QWidget*>())
child->removeEventFilter(this);
}
}
}
QToolButton* tb;
if ((tb = qobject_cast<QToolButton*>(watched))) {
const QList<QAction*> tbActions = tb->actions();
if (!tbActions.isEmpty()) {
// Handle MMB on toolbar buttons
if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
if (me->button() == Qt::MidButton /*&&
act->receivers(SIGNAL(triggered(Qt::MouseButtons,Qt::KeyboardModifiers)))*/) {
QAction* act = tbActions.first();
if (me->type() == QEvent::MouseButtonPress)
tb->setDown(act->isEnabled());
else {
tb->setDown(false);
if (act->isEnabled()) {
QMetaObject::invokeMethod(act, "triggered", Qt::DirectConnection,
Q_ARG(Qt::MouseButtons, me->button()),
Q_ARG(Qt::KeyboardModifiers, QApplication::keyboardModifiers()));
}
}
}
}
// CJK languages use more verbose accelerator marker: they add a Latin
// letter in parenthesis, and put accelerator on that. Hence, the default
// removal of ampersand only may not be enough there, instead the whole
// parenthesis construct should be removed. Use KLocale's method to do this.
if (event->type() == QEvent::Show || event->type() == QEvent::Paint || event->type() == QEvent::EnabledChange) {
QAction *act = tb->defaultAction();
if (act) {
const QString text = KGlobal::locale()->removeAcceleratorMarker(act->iconText().isEmpty() ? act->text() : act->iconText());
const QString toolTip = KGlobal::locale()->removeAcceleratorMarker(act->toolTip());
// Filtering messages requested by translators (scripting).
tb->setText(i18nc("@action:intoolbar Text label of toolbar button", "%1", text));
tb->setToolTip(i18nc("@info:tooltip Tooltip of toolbar button", "%1", toolTip));
}
}
}
}
// Redirect mouse events to the toolbar when drag + drop editing is enabled
if (toolBarsEditable()) {
if (QWidget* ww = qobject_cast<QWidget*>(watched)) {
switch (event->type()) {
case QEvent::MouseButtonPress: {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(),
me->button(), me->buttons(), me->modifiers());
mousePressEvent(&newEvent);
return true;
}
case QEvent::MouseMove: {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(),
me->button(), me->buttons(), me->modifiers());
mouseMoveEvent(&newEvent);
return true;
}
case QEvent::MouseButtonRelease: {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(),
me->button(), me->buttons(), me->modifiers());
mouseReleaseEvent(&newEvent);
return true;
}
default:
break;
}
}
}
return QToolBar::eventFilter(watched, event);
}
void KToolBar::actionEvent(QActionEvent * event)
{
if (event->type() == QEvent::ActionRemoved) {
QWidget* widget = widgetForAction(event->action());
if (widget) {
widget->removeEventFilter(this);
foreach (QWidget* child, widget->findChildren<QWidget*>())
child->removeEventFilter(this);
}
}
QToolBar::actionEvent(event);
if (event->type() == QEvent::ActionAdded) {
QWidget* widget = widgetForAction(event->action());
if (widget) {
widget->installEventFilter(this);
foreach (QWidget* child, widget->findChildren<QWidget*>())
child->installEventFilter(this);
// Center widgets that do not have any use for more space. See bug 165274
if (!(widget->sizePolicy().horizontalPolicy() & QSizePolicy::GrowFlag)
// ... but do not center when using text besides icon in vertical toolbar. See bug 243196
&& !(orientation() == Qt::Vertical && toolButtonStyle() == Qt::ToolButtonTextBesideIcon)) {
const int index = layout()->indexOf(widget);
if (index != -1) {
layout()->itemAt(index)->setAlignment(Qt::AlignJustify);
}
}
}
}
d->adjustSeparatorVisibility();
}
bool KToolBar::toolBarsEditable()
{
return KToolBar::Private::s_editable;
}
void KToolBar::setToolBarsEditable(bool editable)
{
if (KToolBar::Private::s_editable != editable) {
KToolBar::Private::s_editable = editable;
}
}
void KToolBar::setToolBarsLocked(bool locked)
{
if (KToolBar::Private::s_locked != locked) {
KToolBar::Private::s_locked = locked;
foreach (KMainWindow* mw, KMainWindow::memberList()) {
foreach (KToolBar* toolbar, mw->findChildren<KToolBar*>()) {
toolbar->d->setLocked(locked);
}
}
}
}
bool KToolBar::toolBarsLocked()
{
return KToolBar::Private::s_locked;
}
#include "ktoolbar.moc"