Advance Wayland and KDE package bring-up
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
add_library(KF6ConfigWidgets)
|
||||
add_library(KF6::ConfigWidgets ALIAS KF6ConfigWidgets)
|
||||
|
||||
set_target_properties(KF6ConfigWidgets PROPERTIES
|
||||
VERSION ${KCONFIGWIDGETS_VERSION}
|
||||
SOVERSION ${KCONFIGWIDGETS_SOVERSION}
|
||||
EXPORT_NAME ConfigWidgets
|
||||
)
|
||||
|
||||
target_sources(KF6ConfigWidgets PRIVATE
|
||||
kcodecaction.cpp
|
||||
kcolorschememenu.cpp
|
||||
kcommandbar.cpp
|
||||
kcommandbarmodel_p.cpp
|
||||
kconfigdialog.cpp
|
||||
kconfigviewstatesaver.cpp
|
||||
kconfigdialogmanager.cpp
|
||||
khelpclient.cpp
|
||||
khamburgermenu.cpp
|
||||
khamburgermenuhelpers.cpp
|
||||
klanguagebutton.cpp
|
||||
klanguagename.cpp
|
||||
kopenaction.cpp
|
||||
krecentfilesaction.cpp
|
||||
kstandardaction.cpp
|
||||
kstylemanager.cpp
|
||||
|
||||
kcodecaction.h
|
||||
kcommandbar.h
|
||||
kcommandbarmodel_p.h
|
||||
kconfigdialog.h
|
||||
kconfigviewstatesaver.h
|
||||
kconfigdialogmanager.h
|
||||
khelpclient.h
|
||||
khamburgermenu.h
|
||||
khamburgermenuhelpers_p.h
|
||||
klanguagebutton.h
|
||||
klanguagename.h
|
||||
kopenaction_p.h
|
||||
krecentfilesaction.h
|
||||
kstandardaction.h
|
||||
kstylemanager.h
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(KF6ConfigWidgets
|
||||
HEADER kconfigwidgets_debug.h
|
||||
IDENTIFIER KCONFIG_WIDGETS_LOG
|
||||
CATEGORY_NAME kf.configwidgets
|
||||
OLD_CATEGORY_NAMES kf5.kconfigwidgets
|
||||
DESCRIPTION "KConfigWidgets"
|
||||
EXPORT KCONFIGWIDGETS
|
||||
)
|
||||
|
||||
ecm_generate_export_header(KF6ConfigWidgets
|
||||
BASE_NAME KConfigWidgets
|
||||
GROUP_BASE_NAME KF
|
||||
VERSION ${KF_VERSION}
|
||||
USE_VERSION_HEADER
|
||||
DEPRECATED_BASE_VERSION 0
|
||||
DEPRECATION_VERSIONS
|
||||
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
|
||||
)
|
||||
|
||||
target_include_directories(KF6ConfigWidgets INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR_KF}/KConfigWidgets>")
|
||||
|
||||
target_link_libraries(KF6ConfigWidgets
|
||||
PUBLIC
|
||||
KF6::WidgetsAddons # For K*Action, KPage*, KViewStateSerializer, KAcceleratorManager, K*GuiItem
|
||||
KF6::ConfigGui # KStandardAction uses KStandardShortcut
|
||||
KF6::ColorScheme
|
||||
PRIVATE
|
||||
Qt6::GuiPrivate # KStyleManager::initStyle
|
||||
KF6::CoreAddons # KAboutData, KFuzzymatcher
|
||||
KF6::GuiAddons # KColorScheme uses KColorUtils
|
||||
KF6::I18n # For action and widget texts
|
||||
KF6::Codecs # KCodecActions uses KCharsets, KEncodingProber
|
||||
)
|
||||
|
||||
if (HAVE_DBUS)
|
||||
target_link_libraries(KF6ConfigWidgets
|
||||
PRIVATE
|
||||
Qt::DBus # KRecentFilesAction to send call to ActivityManager
|
||||
)
|
||||
target_compile_definitions(KF6ConfigWidgets PRIVATE -DHAVE_QTDBUS=1)
|
||||
else()
|
||||
target_compile_definitions(KF6ConfigWidgets PRIVATE -DHAVE_QTDBUS=0)
|
||||
endif()
|
||||
|
||||
ecm_generate_headers(KConfigWidgets_HEADERS
|
||||
HEADER_NAMES
|
||||
KCodecAction
|
||||
KColorSchemeMenu
|
||||
KCommandBar
|
||||
KConfigDialog
|
||||
KConfigViewStateSaver
|
||||
KConfigDialogManager
|
||||
KHamburgerMenu
|
||||
KHelpClient
|
||||
KLanguageButton
|
||||
KLanguageName
|
||||
KRecentFilesAction
|
||||
KViewStateMaintainer
|
||||
KStandardAction
|
||||
KStyleManager
|
||||
|
||||
REQUIRED_HEADERS KConfigWidgets_HEADERS
|
||||
)
|
||||
|
||||
install(TARGETS KF6ConfigWidgets EXPORT KF6ConfigWidgetsTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/kconfigwidgets_export.h
|
||||
${KConfigWidgets_HEADERS}
|
||||
DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/KConfigWidgets COMPONENT Devel
|
||||
)
|
||||
|
||||
install( FILES entry.desktop DESTINATION ${KDE_INSTALL_LOCALEDIR}/en_US RENAME kf6_entry.desktop )
|
||||
|
||||
ecm_qt_install_logging_categories(
|
||||
EXPORT KCONFIGWIDGETS
|
||||
FILE kconfigwidgets.categories
|
||||
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
|
||||
)
|
||||
|
||||
if(BUILD_DESIGNERPLUGIN)
|
||||
add_subdirectory(designer)
|
||||
endif()
|
||||
|
||||
if(BUILD_QCH)
|
||||
ecm_add_qch(
|
||||
KF6ConfigWidgets_QCH
|
||||
NAME KConfigWidgets
|
||||
BASE_NAME KF6ConfigWidgets
|
||||
VERSION ${KF_VERSION}
|
||||
ORG_DOMAIN org.kde
|
||||
SOURCES # using only public headers, to cover only public API
|
||||
${KConfigWidgets_HEADERS}
|
||||
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
|
||||
IMAGE_DIRS "${CMAKE_SOURCE_DIR}/docs/pics"
|
||||
LINK_QCHS
|
||||
KF6Codecs_QCH
|
||||
KF6WidgetsAddons_QCH
|
||||
KF6Config_QCH
|
||||
KF6Auth_QCH
|
||||
INCLUDE_DIRS
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
BLANK_MACROS
|
||||
KCONFIGWIDGETS_EXPORT
|
||||
KCONFIGWIDGETS_DEPRECATED
|
||||
KCONFIGWIDGETS_DEPRECATED_EXPORT
|
||||
"KCONFIGWIDGETS_DEPRECATED_VERSION(x, y, t)"
|
||||
"KCONFIGWIDGETS_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
|
||||
"KCONFIGWIDGETS_ENUMERATOR_DEPRECATED_VERSION(x, y, t)"
|
||||
"KCONFIGWIDGETS_ENUMERATOR_DEPRECATED_VERSION_BELATED(x, y, xt, yt, t)"
|
||||
PREDEFINED_MACROS
|
||||
"KCONFIGWIDGETS_ENABLE_DEPRECATED_SINCE(x, y)=1"
|
||||
"KCONFIGWIDGETS_BUILD_DEPRECATED_SINCE(x, y)=1"
|
||||
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
|
||||
COMPONENT Devel
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Invoke the extractrc script on all .ui, .rc, and .kcfg files in the sources.
|
||||
# The results are stored in a pseudo .cpp file to be picked up by xgettext.
|
||||
lst=`find . -name \*.rc -o -name \*.ui -o -name \*.kcfg`
|
||||
if [ -n "$lst" ] ; then
|
||||
$EXTRACTRC $lst >> rc.cpp
|
||||
fi
|
||||
|
||||
# Extract strings from all source files.
|
||||
# If your framework depends on KI18n, use $XGETTEXT. If it uses Qt translation
|
||||
# system, use $EXTRACT_TR_STRINGS.
|
||||
$XGETTEXT `find . -name \*.cpp -o -name \*.h -o -name \*.qml` -o $podir/kconfigwidgets6.pot
|
||||
@@ -0,0 +1,18 @@
|
||||
include(ECMAddQtDesignerPlugin)
|
||||
|
||||
ecm_qtdesignerplugin_widget(KLanguageButton
|
||||
TOOLTIP "Language Button (KF6)"
|
||||
WHATSTHIS "A pushbutton for language selection from a popup list."
|
||||
GROUP "Buttons (KF6)"
|
||||
)
|
||||
|
||||
ecm_add_qtdesignerplugin(kconfigwidgetswidgets
|
||||
NAME KPlottingWidgets
|
||||
OUTPUT_NAME kconfigwidgets6widgets
|
||||
WIDGETS
|
||||
KLanguageButton
|
||||
LINK_LIBRARIES
|
||||
KF6::ConfigWidgets
|
||||
INSTALL_DESTINATION "${KDE_INSTALL_QTPLUGINDIR}/designer"
|
||||
COMPONENT Devel
|
||||
)
|
||||
@@ -0,0 +1,59 @@
|
||||
[KCM Locale]
|
||||
Name=US English
|
||||
Name[ar]=الإنجليزية الأمريكية
|
||||
Name[az]=US İngiliscə
|
||||
Name[be]=Англійская (ЗША)
|
||||
Name[bg]=Английски US
|
||||
Name[bs]=Američki engleski
|
||||
Name[ca]=Anglès US
|
||||
Name[ca@valencia]=Anglés US
|
||||
Name[cs]=US Angličtina
|
||||
Name[da]=US Engelsk
|
||||
Name[de]=US-Englisch
|
||||
Name[el]=US English
|
||||
Name[en_GB]=US English
|
||||
Name[eo]=Usona Angla
|
||||
Name[es]=Inglés de EE. UU.
|
||||
Name[et]=USA inglise
|
||||
Name[eu]=AEBtako ingelesa
|
||||
Name[fi]=Amerikanenglanti
|
||||
Name[fr]=Anglais (États-Unis)
|
||||
Name[gd]=Beurla (SA)
|
||||
Name[gl]=Inglés americano
|
||||
Name[he]=אנגלית ארה"ב
|
||||
Name[hi]=यूएस अंग्रेज़ी
|
||||
Name[hu]=Angol (amerikai)
|
||||
Name[ia]=Anglese de S.U.A.
|
||||
Name[id]=Inggris US
|
||||
Name[is]=Bandarísk enska
|
||||
Name[it]=Inglese US
|
||||
Name[ka]=ინგლისური (აშშ)
|
||||
Name[ko]=미국 영어
|
||||
Name[lt]=JAV anglų
|
||||
Name[lv]=ASV angļu
|
||||
Name[nb]=Engelsk (USA)
|
||||
Name[nl]=VS Engels
|
||||
Name[nn]=Engelsk (USA)
|
||||
Name[pa]=ਅਮਰੀਕੀ ਅੰਗਰੇਜ਼ੀ
|
||||
Name[pl]=Angielski amerykański
|
||||
Name[pt]=Inglês dos EUA
|
||||
Name[pt_BR]=Inglês dos EUA
|
||||
Name[ro]=Engleză SUA
|
||||
Name[ru]=Английский (США)
|
||||
Name[sa]=अमेरिकी आङ्ग्लभाषा
|
||||
Name[sk]=Americká angličtina
|
||||
Name[sl]=ameriško angleško
|
||||
Name[sr]=амерички енглески
|
||||
Name[sr@ijekavian]=амерички енглески
|
||||
Name[sr@ijekavianlatin]=američki engleski
|
||||
Name[sr@latin]=američki engleski
|
||||
Name[sv]=Amerikansk engelska
|
||||
Name[ta]=US ஆங்கிலம்
|
||||
Name[tg]=Англисии ИМА
|
||||
Name[tok]=toki Inli pi ma Mewika
|
||||
Name[tr]=Amerikan İngilizcesi
|
||||
Name[uk]=англійська (США)
|
||||
Name[vi]=Tiếng Anh Mĩ
|
||||
Name[x-test]=xxUS Englishxx
|
||||
Name[zh_CN]=美式英语
|
||||
Name[zh_TW]=美式英文
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2003 Jason Keirstead <jason@keirstead.org>
|
||||
SPDX-FileCopyrightText: 2006 Michel Hermier <michel.hermier@gmail.com>
|
||||
SPDX-FileCopyrightText: 2007 Nick Shaforostoff <shafff@ukr.net>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "kcodecaction.h"
|
||||
#include "kconfigwidgets_debug.h"
|
||||
|
||||
#include <KCharsets>
|
||||
#include <KEncodingProber>
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <QMenu>
|
||||
#include <QVariant>
|
||||
|
||||
class KCodecActionPrivate
|
||||
{
|
||||
public:
|
||||
KCodecActionPrivate(KCodecAction *parent)
|
||||
: q(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void init(bool);
|
||||
|
||||
void subActionTriggered(QAction *);
|
||||
|
||||
KCodecAction *const q;
|
||||
QAction *defaultAction = nullptr;
|
||||
QAction *currentSubAction = nullptr;
|
||||
};
|
||||
|
||||
KCodecAction::KCodecAction(QObject *parent, bool showAutoOptions)
|
||||
: KSelectAction(parent)
|
||||
, d(new KCodecActionPrivate(this))
|
||||
{
|
||||
d->init(showAutoOptions);
|
||||
}
|
||||
|
||||
KCodecAction::KCodecAction(const QString &text, QObject *parent, bool showAutoOptions)
|
||||
: KSelectAction(text, parent)
|
||||
, d(new KCodecActionPrivate(this))
|
||||
{
|
||||
d->init(showAutoOptions);
|
||||
}
|
||||
|
||||
KCodecAction::KCodecAction(const QIcon &icon, const QString &text, QObject *parent, bool showAutoOptions)
|
||||
: KSelectAction(icon, text, parent)
|
||||
, d(new KCodecActionPrivate(this))
|
||||
{
|
||||
d->init(showAutoOptions);
|
||||
}
|
||||
|
||||
KCodecAction::~KCodecAction() = default;
|
||||
|
||||
void KCodecActionPrivate::init(bool showAutoOptions)
|
||||
{
|
||||
q->setToolBarMode(KSelectAction::MenuMode);
|
||||
defaultAction = q->addAction(i18nc("Encodings menu", "Default"));
|
||||
|
||||
const auto lstEncodings = KCharsets::charsets()->encodingsByScript();
|
||||
for (const QStringList &encodingsForScript : lstEncodings) {
|
||||
KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), q);
|
||||
if (showAutoOptions) {
|
||||
KEncodingProber::ProberType scri = KEncodingProber::proberTypeForName(encodingsForScript.at(0));
|
||||
if (scri != KEncodingProber::None) {
|
||||
tmp->addAction(i18nc("Encodings menu", "Autodetect"))->setData(QVariant((uint)scri));
|
||||
tmp->menu()->addSeparator();
|
||||
}
|
||||
}
|
||||
for (int i = 1; i < encodingsForScript.size(); ++i) {
|
||||
tmp->addAction(encodingsForScript.at(i));
|
||||
}
|
||||
q->connect(tmp, &KSelectAction::actionTriggered, q, [this](QAction *action) {
|
||||
subActionTriggered(action);
|
||||
});
|
||||
tmp->setCheckable(true);
|
||||
q->addAction(tmp);
|
||||
}
|
||||
q->setCurrentItem(0);
|
||||
}
|
||||
|
||||
void KCodecAction::slotActionTriggered(QAction *action)
|
||||
{
|
||||
// we don't want to emit any signals from top-level items
|
||||
// except for the default one
|
||||
if (action == d->defaultAction) {
|
||||
Q_EMIT defaultItemTriggered();
|
||||
}
|
||||
}
|
||||
|
||||
void KCodecActionPrivate::subActionTriggered(QAction *action)
|
||||
{
|
||||
if (currentSubAction == action) {
|
||||
return;
|
||||
}
|
||||
currentSubAction = action;
|
||||
Q_EMIT q->textTriggered(action->text());
|
||||
Q_EMIT q->codecNameTriggered(action->text().toUtf8());
|
||||
}
|
||||
|
||||
QString KCodecAction::currentCodecName() const
|
||||
{
|
||||
return d->currentSubAction->text();
|
||||
}
|
||||
|
||||
bool KCodecAction::setCurrentCodec(const QString &codecName)
|
||||
{
|
||||
if (codecName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < actions().size(); ++i) {
|
||||
if (actions().at(i)->menu()) {
|
||||
for (int j = 0; j < actions().at(i)->menu()->actions().size(); ++j) {
|
||||
if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) {
|
||||
continue;
|
||||
}
|
||||
if (codecName == actions().at(i)->menu()->actions().at(j)->text()) {
|
||||
d->currentSubAction = actions().at(i)->menu()->actions().at(j);
|
||||
d->currentSubAction->trigger();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#include "moc_kcodecaction.cpp"
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2003 Jason Keirstead <jason@keirstead.org>
|
||||
SPDX-FileCopyrightText: 2003-2006 Michel Hermier <michel.hermier@gmail.com>
|
||||
SPDX-FileCopyrightText: 2007 Nick Shaforostoff <shafff@ukr.net>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
#ifndef KCODECACTION_H
|
||||
#define KCODECACTION_H
|
||||
|
||||
#include <kconfigwidgets_export.h>
|
||||
|
||||
#include <KSelectAction>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* @class KCodecAction kcodecaction.h KCodecAction
|
||||
*
|
||||
* @short Action for selecting one of several text codecs..
|
||||
*
|
||||
* This action shows up a submenu with a list of the available codecs on the system.
|
||||
*/
|
||||
class KCONFIGWIDGETS_EXPORT KCodecAction : public KSelectAction
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString codecName READ currentCodecName WRITE setCurrentCodec)
|
||||
|
||||
public:
|
||||
explicit KCodecAction(QObject *parent, bool showAutoOptions = false);
|
||||
|
||||
KCodecAction(const QString &text, QObject *parent, bool showAutoOptions = false);
|
||||
|
||||
KCodecAction(const QIcon &icon, const QString &text, QObject *parent, bool showAutoOptions = false);
|
||||
|
||||
~KCodecAction() override;
|
||||
|
||||
public:
|
||||
QString currentCodecName() const;
|
||||
bool setCurrentCodec(const QString &codecName);
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* Emitted when a codec was selected
|
||||
*
|
||||
* @param name the name of the selected encoding.
|
||||
*
|
||||
* Note that textTriggered(const QString &) is emitted too (as defined in KSelectAction).
|
||||
*
|
||||
* @since 5.103
|
||||
*/
|
||||
void codecNameTriggered(const QByteArray &name);
|
||||
|
||||
/**
|
||||
* Emitted when the 'Default' codec action is triggered.
|
||||
*/
|
||||
void defaultItemTriggered();
|
||||
|
||||
protected Q_SLOTS:
|
||||
void slotActionTriggered(QAction *) override;
|
||||
|
||||
protected:
|
||||
using KSelectAction::actionTriggered;
|
||||
|
||||
private:
|
||||
friend class KCodecActionPrivate;
|
||||
std::unique_ptr<class KCodecActionPrivate> const d;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
|
||||
SPDX-FileCopyrightText: 2023 David Redondo <kde@david-redondo.de>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "kcolorschememenu.h"
|
||||
|
||||
#include <KActionMenu>
|
||||
#include <KColorSchemeManager>
|
||||
#include <KColorSchemeModel>
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <QActionGroup>
|
||||
#include <QIcon>
|
||||
#include <QMenu>
|
||||
|
||||
constexpr int defaultSchemeRow = 0;
|
||||
|
||||
KActionMenu *KColorSchemeMenu::createMenu(KColorSchemeManager *manager, QObject *parent)
|
||||
{
|
||||
// Be careful here when connecting to signals. The menu can outlive the manager
|
||||
KActionMenu *menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("preferences-desktop-color")), i18n("Color Scheme"), parent);
|
||||
QActionGroup *group = new QActionGroup(menu);
|
||||
QObject::connect(group, &QActionGroup::triggered, manager, [manager](QAction *action) {
|
||||
const QString schemePath = action->data().toString();
|
||||
if (schemePath.isEmpty()) {
|
||||
// Reset to default
|
||||
manager->activateScheme(QModelIndex());
|
||||
} else {
|
||||
// Software linking KXmlGui gets its static KCheckAccelerators object deploy
|
||||
// KAcceleratorManager on the whole UI automatically, which adds accelerators
|
||||
// also to the texts of this menu's actions.
|
||||
// So they have to be remove here in case
|
||||
// (hoping that no original names have them also in case no accelerators were added)
|
||||
// See also KColorSchemeManager::saveSchemeToConfigFile(const QString &schemeName) const.
|
||||
const QString schemeName = KLocalizedString::removeAcceleratorMarker(action->text());
|
||||
manager->activateScheme(manager->indexForScheme(schemeName));
|
||||
}
|
||||
});
|
||||
const auto model = manager->model();
|
||||
for (int i = 0; i < model->rowCount(); ++i) {
|
||||
QModelIndex index = model->index(i, 0);
|
||||
QAction *action = new QAction(index.data(KColorSchemeModel::NameRole).toString(), menu);
|
||||
action->setData(index.data(KColorSchemeModel::PathRole));
|
||||
action->setActionGroup(group);
|
||||
action->setCheckable(true);
|
||||
if (index.data(KColorSchemeModel::IdRole).toString() == manager->activeSchemeId()) {
|
||||
action->setChecked(true);
|
||||
}
|
||||
menu->addAction(action);
|
||||
QObject::connect(menu->menu(), &QMenu::aboutToShow, model, [action, index] {
|
||||
if (action->icon().isNull()) {
|
||||
action->setIcon(index.data(KColorSchemeModel::IconRole).value<QIcon>());
|
||||
}
|
||||
});
|
||||
}
|
||||
const auto groupActions = group->actions();
|
||||
if (!group->checkedAction()) {
|
||||
// If no (valid) color scheme has been selected we select the default one
|
||||
groupActions[defaultSchemeRow]->setChecked(true);
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2023 David Redondo <kde@david-redondo.de>
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KCOLORSCHEMEMENU_H
|
||||
#define KCOLORSCHEMEMENU_H
|
||||
|
||||
class KActionMenu;
|
||||
class KColorSchemeManager;
|
||||
class QObject;
|
||||
|
||||
#include <kconfigwidgets_export.h>
|
||||
|
||||
/**
|
||||
* A menu for switching color schemes
|
||||
*/
|
||||
namespace KColorSchemeMenu
|
||||
{
|
||||
/**
|
||||
* Creates a KActionMenu populated with all the available color schemes.
|
||||
* All actions are in an action group and when one of the actions is triggered the scheme
|
||||
* referenced by this action is activated.
|
||||
*
|
||||
* @since 5.107
|
||||
*/
|
||||
KCONFIGWIDGETS_EXPORT KActionMenu *createMenu(KColorSchemeManager *manager, QObject *parent = nullptr);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,760 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
#include "kcommandbar.h"
|
||||
#include "kcommandbarmodel_p.h"
|
||||
#include "kconfigwidgets_debug.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QCoreApplication>
|
||||
#include <QGraphicsOpacityEffect>
|
||||
#include <QHeaderView>
|
||||
#include <QKeyEvent>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMainWindow>
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
#include <QPointer>
|
||||
#include <QScreen>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStatusBar>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QTextLayout>
|
||||
#include <QToolBar>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <KConfigGroup>
|
||||
#include <KFuzzyMatcher>
|
||||
#include <KLocalizedString>
|
||||
#include <KSharedConfig>
|
||||
|
||||
static QRect getCommandBarBoundingRect(KCommandBar *commandBar)
|
||||
{
|
||||
QWidget *parentWidget = commandBar->parentWidget();
|
||||
Q_ASSERT(parentWidget);
|
||||
|
||||
const QMainWindow *mainWindow = qobject_cast<const QMainWindow *>(parentWidget);
|
||||
if (!mainWindow) {
|
||||
return parentWidget->geometry();
|
||||
}
|
||||
|
||||
QRect boundingRect = mainWindow->contentsRect();
|
||||
|
||||
// exclude the menu bar from the bounding rect
|
||||
if (const QWidget *menuWidget = mainWindow->menuWidget()) {
|
||||
if (!menuWidget->isHidden()) {
|
||||
boundingRect.setTop(boundingRect.top() + menuWidget->height());
|
||||
}
|
||||
}
|
||||
|
||||
// exclude the status bar from the bounding rect
|
||||
if (const QStatusBar *statusBar = mainWindow->findChild<QStatusBar *>()) {
|
||||
if (!statusBar->isHidden()) {
|
||||
boundingRect.setBottom(boundingRect.bottom() - statusBar->height());
|
||||
}
|
||||
}
|
||||
|
||||
// exclude any undocked toolbar from the bounding rect
|
||||
const QList<QToolBar *> toolBars = mainWindow->findChildren<QToolBar *>();
|
||||
for (QToolBar *toolBar : toolBars) {
|
||||
if (toolBar->isHidden() || toolBar->isFloating()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (mainWindow->toolBarArea(toolBar)) {
|
||||
case Qt::TopToolBarArea:
|
||||
boundingRect.setTop(std::max(boundingRect.top(), toolBar->geometry().bottom()));
|
||||
break;
|
||||
case Qt::RightToolBarArea:
|
||||
boundingRect.setRight(std::min(boundingRect.right(), toolBar->geometry().left()));
|
||||
break;
|
||||
case Qt::BottomToolBarArea:
|
||||
boundingRect.setBottom(std::min(boundingRect.bottom(), toolBar->geometry().top()));
|
||||
break;
|
||||
case Qt::LeftToolBarArea:
|
||||
boundingRect.setLeft(std::max(boundingRect.left(), toolBar->geometry().right()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return boundingRect;
|
||||
}
|
||||
|
||||
// BEGIN CommandBarFilterModel
|
||||
class CommandBarFilterModel final : public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
CommandBarFilterModel(QObject *parent = nullptr)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
connect(this, &CommandBarFilterModel::modelAboutToBeReset, this, [this]() {
|
||||
m_hasActionsWithIcons = false;
|
||||
});
|
||||
}
|
||||
|
||||
bool hasActionsWithIcons() const
|
||||
{
|
||||
return m_hasActionsWithIcons;
|
||||
}
|
||||
|
||||
Q_SLOT void setFilterString(const QString &string)
|
||||
{
|
||||
// MUST reset the model here, we want to repopulate
|
||||
// invalidateFilter() will not work here
|
||||
beginResetModel();
|
||||
m_pattern = string;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override
|
||||
{
|
||||
const int scoreLeft = sourceLeft.data(KCommandBarModel::Score).toInt();
|
||||
const int scoreRight = sourceRight.data(KCommandBarModel::Score).toInt();
|
||||
if (scoreLeft == scoreRight) {
|
||||
const QString textLeft = sourceLeft.data().toString();
|
||||
const QString textRight = sourceRight.data().toString();
|
||||
|
||||
return textRight.localeAwareCompare(textLeft) < 0;
|
||||
}
|
||||
|
||||
return scoreLeft < scoreRight;
|
||||
}
|
||||
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
|
||||
{
|
||||
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
|
||||
bool accept = false;
|
||||
if (m_pattern.isEmpty()) {
|
||||
accept = true;
|
||||
} else {
|
||||
const QString row = index.data(Qt::DisplayRole).toString();
|
||||
KFuzzyMatcher::Result resAction = KFuzzyMatcher::match(m_pattern, row);
|
||||
sourceModel()->setData(index, resAction.score, KCommandBarModel::Score);
|
||||
accept = resAction.matched;
|
||||
}
|
||||
|
||||
if (accept && !m_hasActionsWithIcons) {
|
||||
m_hasActionsWithIcons |= !index.data(Qt::DecorationRole).isNull();
|
||||
}
|
||||
|
||||
return accept;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_pattern;
|
||||
mutable bool m_hasActionsWithIcons = false;
|
||||
};
|
||||
// END CommandBarFilterModel
|
||||
|
||||
class CommandBarStyleDelegate final : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
CommandBarStyleDelegate(QObject *parent = nullptr)
|
||||
: QStyledItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints a single item's text
|
||||
*/
|
||||
static void
|
||||
paintItemText(QPainter *p, const QString &textt, const QRect &rect, const QStyleOptionViewItem &options, QList<QTextLayout::FormatRange> formats)
|
||||
{
|
||||
QString text = options.fontMetrics.elidedText(textt, Qt::ElideRight, rect.width());
|
||||
|
||||
// set formats and font
|
||||
QTextLayout textLayout(text, options.font);
|
||||
formats.append(textLayout.formats());
|
||||
textLayout.setFormats(formats);
|
||||
|
||||
// set alignment, rtls etc
|
||||
QTextOption textOption;
|
||||
textOption.setTextDirection(options.direction);
|
||||
textOption.setAlignment(QStyle::visualAlignment(options.direction, options.displayAlignment));
|
||||
textLayout.setTextOption(textOption);
|
||||
|
||||
// layout the text
|
||||
textLayout.beginLayout();
|
||||
|
||||
QTextLine line = textLayout.createLine();
|
||||
if (!line.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int lineWidth = rect.width();
|
||||
line.setLineWidth(lineWidth);
|
||||
line.setPosition(QPointF(0, 0));
|
||||
|
||||
textLayout.endLayout();
|
||||
|
||||
/**
|
||||
* get "Y" so that we can properly V-Center align the text in row
|
||||
*/
|
||||
const int y = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignVCenter, textLayout.boundingRect().size().toSize(), rect).y();
|
||||
|
||||
// draw the text
|
||||
const QPointF pos(rect.x(), y);
|
||||
textLayout.draw(p, pos);
|
||||
}
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override
|
||||
{
|
||||
painter->save();
|
||||
|
||||
/**
|
||||
* Draw everything, (widget, icon etc) except the text
|
||||
*/
|
||||
QStyleOptionViewItem option = opt;
|
||||
initStyleOption(&option, index);
|
||||
option.text.clear(); // clear old text
|
||||
QStyle *style = option.widget->style();
|
||||
style->drawControl(QStyle::CE_ItemViewItem, &option, painter, option.widget);
|
||||
|
||||
const int hMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, &option, option.widget);
|
||||
|
||||
QRect textRect = option.rect;
|
||||
|
||||
const CommandBarFilterModel *model = static_cast<const CommandBarFilterModel *>(index.model());
|
||||
if (model->hasActionsWithIcons()) {
|
||||
const int iconWidth = option.decorationSize.width() + (hMargin * 2);
|
||||
if (option.direction == Qt::RightToLeft) {
|
||||
textRect.adjust(0, 0, -iconWidth, 0);
|
||||
} else {
|
||||
textRect.adjust(iconWidth, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
const QString original = index.data().toString();
|
||||
QStringView str = original;
|
||||
int componentIdx = original.indexOf(QLatin1Char(':'));
|
||||
int actionNameStart = 0;
|
||||
if (componentIdx > 0) {
|
||||
actionNameStart = componentIdx + 2;
|
||||
// + 2 because there is a space after colon
|
||||
str = str.mid(actionNameStart);
|
||||
}
|
||||
|
||||
QList<QTextLayout::FormatRange> formats;
|
||||
if (componentIdx > 0) {
|
||||
QTextCharFormat gray;
|
||||
gray.setForeground(option.palette.placeholderText());
|
||||
formats.append({0, componentIdx, gray});
|
||||
}
|
||||
|
||||
QTextCharFormat fmt;
|
||||
fmt.setForeground(option.palette.link());
|
||||
fmt.setFontWeight(QFont::Bold);
|
||||
|
||||
/**
|
||||
* Highlight matches from fuzzy matcher
|
||||
*/
|
||||
const auto fmtRanges = KFuzzyMatcher::matchedRanges(m_filterString, str);
|
||||
QTextCharFormat f;
|
||||
f.setForeground(option.palette.link());
|
||||
formats.reserve(formats.size() + fmtRanges.size());
|
||||
std::transform(fmtRanges.begin(), fmtRanges.end(), std::back_inserter(formats), [f, actionNameStart](const KFuzzyMatcher::Range &fr) {
|
||||
return QTextLayout::FormatRange{fr.start + actionNameStart, fr.length, f};
|
||||
});
|
||||
|
||||
textRect.adjust(hMargin, 0, -hMargin, 0);
|
||||
paintItemText(painter, original, textRect, option, std::move(formats));
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
public Q_SLOTS:
|
||||
void setFilterString(const QString &text)
|
||||
{
|
||||
m_filterString = text;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_filterString;
|
||||
};
|
||||
|
||||
class ShortcutStyleDelegate final : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
ShortcutStyleDelegate(QObject *parent = nullptr)
|
||||
: QStyledItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
|
||||
{
|
||||
// draw background
|
||||
option.widget->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter);
|
||||
|
||||
const QString shortcutString = index.data().toString();
|
||||
if (shortcutString.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ShortcutSegments shortcutSegments = splitShortcut(shortcutString);
|
||||
if (shortcutSegments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct Button {
|
||||
int textWidth;
|
||||
QString text;
|
||||
};
|
||||
|
||||
// compute the width of each shortcut segment
|
||||
QList<Button> btns;
|
||||
btns.reserve(shortcutSegments.count());
|
||||
const int hMargin = horizontalMargin(option);
|
||||
for (const QString &text : shortcutSegments) {
|
||||
int textWidth = option.fontMetrics.horizontalAdvance(text);
|
||||
textWidth += 2 * hMargin;
|
||||
btns.append({textWidth, text});
|
||||
}
|
||||
|
||||
int textHeight = option.fontMetrics.lineSpacing();
|
||||
// this happens on gnome so we manually decrease the height a bit
|
||||
if (textHeight == option.rect.height()) {
|
||||
textHeight -= 4;
|
||||
}
|
||||
|
||||
const int y = option.rect.y() + (option.rect.height() - textHeight) / 2;
|
||||
int x;
|
||||
if (option.direction == Qt::RightToLeft) {
|
||||
x = option.rect.x() + hMargin;
|
||||
} else {
|
||||
x = option.rect.right() - shortcutDrawingWidth(option, shortcutSegments, hMargin) - hMargin;
|
||||
}
|
||||
|
||||
painter->save();
|
||||
painter->setPen(option.palette.buttonText().color());
|
||||
painter->setRenderHint(QPainter::Antialiasing);
|
||||
for (int i = 0, n = btns.count(); i < n; ++i) {
|
||||
const Button &button = btns.at(i);
|
||||
|
||||
QRect outputRect(x, y, button.textWidth, textHeight);
|
||||
|
||||
// an even element indicates that it is a key
|
||||
if (i % 2 == 0) {
|
||||
painter->save();
|
||||
painter->setPen(Qt::NoPen);
|
||||
|
||||
// draw rounded rect shadow
|
||||
auto shadowRect = outputRect.translated(0, 1);
|
||||
painter->setBrush(option.palette.shadow());
|
||||
painter->drawRoundedRect(shadowRect, 3.0, 3.0);
|
||||
|
||||
// draw rounded rect itself
|
||||
painter->setBrush(option.palette.window());
|
||||
painter->drawRoundedRect(outputRect, 3.0, 3.0);
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
// draw shortcut segment
|
||||
painter->drawText(outputRect, Qt::AlignCenter, button.text);
|
||||
|
||||
x += outputRect.width();
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override
|
||||
{
|
||||
if (index.isValid() && index.column() == KCommandBarModel::Column_Shortcut) {
|
||||
const QString shortcut = index.data().toString();
|
||||
if (!shortcut.isEmpty()) {
|
||||
const ShortcutSegments shortcutSegments = splitShortcut(shortcut);
|
||||
if (!shortcutSegments.isEmpty()) {
|
||||
const int hMargin = horizontalMargin(option);
|
||||
int width = shortcutDrawingWidth(option, shortcutSegments, hMargin);
|
||||
|
||||
// add left and right margins
|
||||
width += 2 * hMargin;
|
||||
|
||||
return QSize(width, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QStyledItemDelegate::sizeHint(option, index);
|
||||
}
|
||||
|
||||
private:
|
||||
using ShortcutSegments = QStringList;
|
||||
|
||||
// split shortcut into segments i.e. will return
|
||||
// ["Ctrl", "+", "A", ", ", "Ctrl", "+", "K"] for "Ctrl+A, Ctrl+K"
|
||||
// twice as fast as using regular expressions
|
||||
static ShortcutSegments splitShortcut(const QString &shortcut)
|
||||
{
|
||||
ShortcutSegments segments;
|
||||
if (!shortcut.isEmpty()) {
|
||||
const int shortcutLength = shortcut.length();
|
||||
int start = 0;
|
||||
for (int i = 0; i < shortcutLength; ++i) {
|
||||
const QChar c = shortcut.at(i);
|
||||
if (c == QLatin1Char('+')) {
|
||||
if (i > start) {
|
||||
segments << shortcut.mid(start, i - start);
|
||||
}
|
||||
segments << shortcut.at(i);
|
||||
start = i + 1;
|
||||
} else if (c == QLatin1Char(',')) {
|
||||
if (i > start) {
|
||||
segments << shortcut.mid(start, i - start);
|
||||
start = i;
|
||||
}
|
||||
const int j = i + 1;
|
||||
if (j < shortcutLength && shortcut.at(j) == QLatin1Char(' ')) {
|
||||
segments << shortcut.mid(start, j - start + 1);
|
||||
i = j;
|
||||
} else {
|
||||
segments << shortcut.at(i);
|
||||
}
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
if (start < shortcutLength) {
|
||||
segments << shortcut.mid(start);
|
||||
}
|
||||
|
||||
// check we have successfully parsed the shortcut
|
||||
if (segments.isEmpty()) {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << "Splitting shortcut failed" << shortcut;
|
||||
}
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
// returns the width needed to draw the shortcut
|
||||
static int shortcutDrawingWidth(const QStyleOptionViewItem &option, const ShortcutSegments &shortcutSegments, int hMargin)
|
||||
{
|
||||
int width = 0;
|
||||
if (!shortcutSegments.isEmpty()) {
|
||||
width = option.fontMetrics.horizontalAdvance(shortcutSegments.join(QString()));
|
||||
|
||||
// add left and right margins for each segment
|
||||
width += shortcutSegments.count() * 2 * hMargin;
|
||||
}
|
||||
|
||||
return width;
|
||||
}
|
||||
|
||||
int horizontalMargin(const QStyleOptionViewItem &option) const
|
||||
{
|
||||
return option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option) + 2;
|
||||
}
|
||||
};
|
||||
|
||||
// BEGIN KCommandBarPrivate
|
||||
class KCommandBarPrivate
|
||||
{
|
||||
public:
|
||||
QTreeView m_treeView;
|
||||
QLineEdit m_lineEdit;
|
||||
KCommandBarModel m_model;
|
||||
CommandBarFilterModel m_proxyModel;
|
||||
|
||||
/**
|
||||
* selects first item in treeview
|
||||
*/
|
||||
void reselectFirst()
|
||||
{
|
||||
const QModelIndex index = m_proxyModel.index(0, 0);
|
||||
m_treeView.setCurrentIndex(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* blocks signals before clearing line edit to ensure
|
||||
* we don't trigger filtering / sorting
|
||||
*/
|
||||
void clearLineEdit()
|
||||
{
|
||||
const QSignalBlocker blocker(m_lineEdit);
|
||||
m_lineEdit.clear();
|
||||
}
|
||||
|
||||
void slotReturnPressed(KCommandBar *q);
|
||||
|
||||
void setLastUsedActions();
|
||||
|
||||
QStringList lastUsedActions() const;
|
||||
};
|
||||
|
||||
void KCommandBarPrivate::slotReturnPressed(KCommandBar *q)
|
||||
{
|
||||
auto act = m_proxyModel.data(m_treeView.currentIndex(), Qt::UserRole).value<QAction *>();
|
||||
if (act) {
|
||||
// if the action is a menu, we take all its actions
|
||||
// and reload our dialog with these instead.
|
||||
if (auto menu = act->menu()) {
|
||||
auto menuActions = menu->actions();
|
||||
KCommandBar::ActionGroup ag;
|
||||
|
||||
// if there are no actions, trigger load actions
|
||||
// this happens with some menus that are loaded on demand
|
||||
if (menuActions.size() == 0) {
|
||||
Q_EMIT menu->aboutToShow();
|
||||
ag.actions = menu->actions();
|
||||
}
|
||||
|
||||
QString groupName = KLocalizedString::removeAcceleratorMarker(act->text());
|
||||
ag.name = groupName;
|
||||
|
||||
m_model.refresh({ag});
|
||||
reselectFirst();
|
||||
/**
|
||||
* We want the "textChanged" signal here
|
||||
* so that proxy model triggers filtering again
|
||||
* so don't use d->clearLineEdit()
|
||||
*/
|
||||
m_lineEdit.clear();
|
||||
return;
|
||||
} else {
|
||||
m_model.actionTriggered(act->text());
|
||||
q->hide();
|
||||
act->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
clearLineEdit();
|
||||
q->hide();
|
||||
q->deleteLater();
|
||||
}
|
||||
|
||||
void KCommandBarPrivate::setLastUsedActions()
|
||||
{
|
||||
auto cfg = KSharedConfig::openStateConfig();
|
||||
KConfigGroup cg(cfg, QStringLiteral("General"));
|
||||
|
||||
QStringList actionNames = cg.readEntry(QStringLiteral("CommandBarLastUsedActions"), QStringList());
|
||||
|
||||
return m_model.setLastUsedActions(actionNames);
|
||||
}
|
||||
|
||||
QStringList KCommandBarPrivate::lastUsedActions() const
|
||||
{
|
||||
return m_model.lastUsedActions();
|
||||
}
|
||||
// END KCommandBarPrivate
|
||||
|
||||
// BEGIN KCommandBar
|
||||
KCommandBar::KCommandBar(QWidget *parent)
|
||||
: QFrame(parent)
|
||||
, d(new KCommandBarPrivate)
|
||||
{
|
||||
setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
|
||||
setProperty("_breeze_force_frame", true);
|
||||
|
||||
QGraphicsDropShadowEffect *e = new QGraphicsDropShadowEffect(this);
|
||||
e->setColor(palette().color(QPalette::Dark));
|
||||
e->setOffset(0, 4);
|
||||
e->setBlurRadius(48);
|
||||
setGraphicsEffect(e);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout();
|
||||
layout->setSpacing(0);
|
||||
layout->setContentsMargins(QMargins());
|
||||
setLayout(layout);
|
||||
|
||||
layout->addWidget(&d->m_lineEdit);
|
||||
d->m_lineEdit.setClearButtonEnabled(true);
|
||||
d->m_lineEdit.addAction(QIcon::fromTheme(QStringLiteral("search")), QLineEdit::LeadingPosition);
|
||||
d->m_lineEdit.setFrame(false);
|
||||
d->m_lineEdit.setTextMargins(QMargins() + style()->pixelMetric(QStyle::PM_ButtonMargin));
|
||||
setFocusProxy(&d->m_lineEdit);
|
||||
|
||||
layout->addWidget(&d->m_treeView);
|
||||
d->m_treeView.setTextElideMode(Qt::ElideLeft);
|
||||
d->m_treeView.setUniformRowHeights(true);
|
||||
d->m_treeView.setProperty("_breeze_borders_sides", QVariant::fromValue(QFlags(Qt::TopEdge)));
|
||||
|
||||
CommandBarStyleDelegate *delegate = new CommandBarStyleDelegate(this);
|
||||
ShortcutStyleDelegate *del = new ShortcutStyleDelegate(this);
|
||||
d->m_treeView.setItemDelegateForColumn(KCommandBarModel::Column_Command, delegate);
|
||||
d->m_treeView.setItemDelegateForColumn(KCommandBarModel::Column_Shortcut, del);
|
||||
|
||||
connect(&d->m_lineEdit, &QLineEdit::returnPressed, this, [this]() {
|
||||
d->slotReturnPressed(this);
|
||||
});
|
||||
connect(&d->m_lineEdit, &QLineEdit::textChanged, &d->m_proxyModel, &CommandBarFilterModel::setFilterString);
|
||||
connect(&d->m_lineEdit, &QLineEdit::textChanged, delegate, &CommandBarStyleDelegate::setFilterString);
|
||||
connect(&d->m_lineEdit, &QLineEdit::textChanged, this, [this]() {
|
||||
d->m_treeView.viewport()->update();
|
||||
d->reselectFirst();
|
||||
});
|
||||
connect(&d->m_treeView, &QTreeView::clicked, this, [this]() {
|
||||
d->slotReturnPressed(this);
|
||||
});
|
||||
|
||||
d->m_proxyModel.setSourceModel(&d->m_model);
|
||||
d->m_treeView.setSortingEnabled(true);
|
||||
d->m_treeView.setModel(&d->m_proxyModel);
|
||||
|
||||
d->m_treeView.header()->setMinimumSectionSize(0);
|
||||
d->m_treeView.header()->setStretchLastSection(false);
|
||||
d->m_treeView.header()->setSectionResizeMode(KCommandBarModel::Column_Command, QHeaderView::Stretch);
|
||||
d->m_treeView.header()->setSectionResizeMode(KCommandBarModel::Column_Shortcut, QHeaderView::ResizeToContents);
|
||||
|
||||
parent->installEventFilter(this);
|
||||
d->m_treeView.installEventFilter(this);
|
||||
d->m_lineEdit.installEventFilter(this);
|
||||
|
||||
d->m_treeView.setHeaderHidden(true);
|
||||
d->m_treeView.setRootIsDecorated(false);
|
||||
d->m_treeView.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
d->m_treeView.setSelectionMode(QTreeView::SingleSelection);
|
||||
|
||||
QLabel *placeholderLabel = new QLabel;
|
||||
placeholderLabel->setAlignment(Qt::AlignCenter);
|
||||
placeholderLabel->setTextInteractionFlags(Qt::NoTextInteraction);
|
||||
placeholderLabel->setWordWrap(true);
|
||||
// To match the size of a level 2 Heading/KTitleWidget
|
||||
QFont placeholderLabelFont = placeholderLabel->font();
|
||||
placeholderLabelFont.setPointSize(qRound(placeholderLabelFont.pointSize() * 1.3));
|
||||
placeholderLabel->setFont(placeholderLabelFont);
|
||||
// Match opacity of QML placeholder label component
|
||||
QGraphicsOpacityEffect *opacityEffect = new QGraphicsOpacityEffect(placeholderLabel);
|
||||
opacityEffect->setOpacity(0.5);
|
||||
placeholderLabel->setGraphicsEffect(opacityEffect);
|
||||
|
||||
QHBoxLayout *placeholderLayout = new QHBoxLayout;
|
||||
placeholderLayout->addWidget(placeholderLabel);
|
||||
d->m_treeView.setLayout(placeholderLayout);
|
||||
|
||||
connect(&d->m_proxyModel, &CommandBarFilterModel::modelReset, this, [this, placeholderLabel]() {
|
||||
if (d->m_proxyModel.rowCount() > 0) {
|
||||
placeholderLabel->hide();
|
||||
} else {
|
||||
if (d->m_model.rowCount() == 0) {
|
||||
placeholderLabel->setText(i18n("No commands to display"));
|
||||
} else {
|
||||
placeholderLabel->setText(i18n("No commands matching the filter"));
|
||||
}
|
||||
placeholderLabel->show();
|
||||
}
|
||||
});
|
||||
|
||||
setHidden(true);
|
||||
|
||||
// Migrate last used action config to new location
|
||||
KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("General"));
|
||||
if (cg.hasKey("CommandBarLastUsedActions")) {
|
||||
const QStringList actionNames = cg.readEntry("CommandBarLastUsedActions", QStringList());
|
||||
|
||||
KConfigGroup stateCg(KSharedConfig::openStateConfig(), QStringLiteral("General"));
|
||||
stateCg.writeEntry(QStringLiteral("CommandBarLastUsedActions"), actionNames);
|
||||
|
||||
cg.deleteEntry(QStringLiteral("CommandBarLastUsedActions"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destructor defined here to make unique_ptr work
|
||||
*/
|
||||
KCommandBar::~KCommandBar()
|
||||
{
|
||||
auto lastUsedActions = d->lastUsedActions();
|
||||
auto cfg = KSharedConfig::openStateConfig();
|
||||
KConfigGroup cg(cfg, QStringLiteral("General"));
|
||||
cg.writeEntry("CommandBarLastUsedActions", lastUsedActions);
|
||||
|
||||
// Explicitly remove installed event filters of children of d-pointer
|
||||
// class, otherwise while KCommandBar is being torn down, an event could
|
||||
// fire and the eventFilter() accesses d, which would cause a crash
|
||||
// bug 452527
|
||||
d->m_treeView.removeEventFilter(this);
|
||||
d->m_lineEdit.removeEventFilter(this);
|
||||
}
|
||||
|
||||
void KCommandBar::setActions(const QList<ActionGroup> &actions)
|
||||
{
|
||||
// First set last used actions in the model
|
||||
d->setLastUsedActions();
|
||||
|
||||
d->m_model.refresh(actions);
|
||||
d->reselectFirst();
|
||||
|
||||
show();
|
||||
setFocus();
|
||||
}
|
||||
|
||||
void KCommandBar::show()
|
||||
{
|
||||
const QRect boundingRect = getCommandBarBoundingRect(this);
|
||||
|
||||
static constexpr int minWidth = 500;
|
||||
const int maxWidth = boundingRect.width();
|
||||
const int preferredWidth = maxWidth / 2.4;
|
||||
|
||||
static constexpr int minHeight = 250;
|
||||
const int maxHeight = boundingRect.height();
|
||||
const int preferredHeight = maxHeight / 2;
|
||||
|
||||
const QSize size{std::min(maxWidth, std::max(preferredWidth, minWidth)), std::min(maxHeight, std::max(preferredHeight, minHeight))};
|
||||
|
||||
setFixedSize(size);
|
||||
|
||||
// set the position to the top-center of the parent
|
||||
// just below the menubar/toolbar (if any)
|
||||
const QPoint position{boundingRect.center().x() - size.width() / 2, boundingRect.y() + 6};
|
||||
move(position);
|
||||
|
||||
QWidget::show();
|
||||
}
|
||||
|
||||
bool KCommandBar::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::KeyPress || event->type() == QEvent::ShortcutOverride) {
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
if (obj == &d->m_lineEdit) {
|
||||
const int key = keyEvent->key();
|
||||
const bool forward2list = (key == Qt::Key_Up) || (key == Qt::Key_Down) || (key == Qt::Key_PageUp) || (key == Qt::Key_PageDown);
|
||||
if (forward2list) {
|
||||
QCoreApplication::sendEvent(&d->m_treeView, event);
|
||||
return true;
|
||||
}
|
||||
} else if (obj == &d->m_treeView) {
|
||||
const int key = keyEvent->key();
|
||||
const bool forward2input = (key != Qt::Key_Up) && (key != Qt::Key_Down) && (key != Qt::Key_PageUp) && (key != Qt::Key_PageDown)
|
||||
&& (key != Qt::Key_Tab) && (key != Qt::Key_Backtab);
|
||||
if (forward2input) {
|
||||
QCoreApplication::sendEvent(&d->m_lineEdit, event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyEvent->key() == Qt::Key_Escape) {
|
||||
hide();
|
||||
deleteLater();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// hide on focus out, if neither input field nor list have focus!
|
||||
else if (event->type() == QEvent::FocusOut && isVisible() && !(d->m_lineEdit.hasFocus() || d->m_treeView.hasFocus())) {
|
||||
d->clearLineEdit();
|
||||
deleteLater();
|
||||
hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
// handle resizing
|
||||
if (parent() == obj && event->type() == QEvent::Resize) {
|
||||
show();
|
||||
}
|
||||
|
||||
return QWidget::eventFilter(obj, event);
|
||||
}
|
||||
// END KCommandBar
|
||||
|
||||
#include "moc_kcommandbar.cpp"
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
#ifndef KCOMMANDBAR_H
|
||||
#define KCOMMANDBAR_H
|
||||
|
||||
#include "kconfigwidgets_export.h"
|
||||
|
||||
#include <QFrame>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* @class KCommandBar kcommandbar.h KCommandBar
|
||||
*
|
||||
* @short A hud style menu which allows listing and executing actions
|
||||
*
|
||||
* KCommandBar takes as input a list of QActions and then shows them
|
||||
* in a "list-like-view" with a filter line edit. This allows quickly
|
||||
* executing an action.
|
||||
*
|
||||
* Usage is simple. Just create a KCommandBar instance when you need it
|
||||
* and throw it away once the user is done with it. You can store it as
|
||||
* a class member as well but there is little benefit in that. Example:
|
||||
*
|
||||
* @code
|
||||
* void slotOpenCommandBar()
|
||||
* {
|
||||
* // `this` is important, you must pass a parent widget
|
||||
* // here. Ideally it will be your mainWindow
|
||||
* KCommandBar *bar = new KCommandBar(this);
|
||||
*
|
||||
* // Load actions into the command bar
|
||||
* // These actions can be from your menus / toolbars etc
|
||||
* QList<ActionGroup> actionGroups = ...;
|
||||
* bar->setActions(actionGroups);
|
||||
*
|
||||
* // Show
|
||||
* bar->show();
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @since 5.83
|
||||
* @author Waqar Ahmed <waqar.17a@gmail.com>
|
||||
*/
|
||||
class KCONFIGWIDGETS_EXPORT KCommandBar : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* Represents a list of action that belong to the same group.
|
||||
* For example:
|
||||
* - All actions under the menu "File" or "Tool"
|
||||
*/
|
||||
struct ActionGroup {
|
||||
QString name;
|
||||
QList<QAction *> actions;
|
||||
};
|
||||
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* @p parent is used to determine position and size of the
|
||||
* command bar. It *must* not be @c nullptr.
|
||||
*/
|
||||
explicit KCommandBar(QWidget *parent);
|
||||
~KCommandBar() override;
|
||||
|
||||
/**
|
||||
* @p actions is a list of {GroupName, QAction}. Group name can be the name
|
||||
* of the component/menu where a QAction lives, for example in a menu "File -> Open File",
|
||||
* "File" should be the GroupName.
|
||||
*/
|
||||
void setActions(const QList<ActionGroup> &actions);
|
||||
|
||||
public Q_SLOTS:
|
||||
void show();
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<class KCommandBarPrivate> const d;
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(KCommandBar::ActionGroup, Q_RELOCATABLE_TYPE);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
#include "kcommandbarmodel_p.h"
|
||||
#include "kcommandbar.h" // For ActionGroup
|
||||
#include "kconfigwidgets_debug.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
QString KCommandBarModel::Item::displayName() const
|
||||
{
|
||||
const QString group = KLocalizedString::removeAcceleratorMarker(groupName);
|
||||
const QString command = KLocalizedString::removeAcceleratorMarker(action->text());
|
||||
|
||||
return group + QStringLiteral(": ") + command;
|
||||
}
|
||||
|
||||
KCommandBarModel::KCommandBarModel(QObject *parent)
|
||||
: QAbstractTableModel(parent)
|
||||
{
|
||||
m_clearHistoryAction = new QAction(tr("Clear History"), this);
|
||||
m_clearHistoryAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history")));
|
||||
connect(m_clearHistoryAction, &QAction::triggered, this, [this]() {
|
||||
m_lastTriggered.clear();
|
||||
});
|
||||
}
|
||||
|
||||
void fillRows(QList<KCommandBarModel::Item> &rows, const QString &title, const QList<QAction *> &actions, std::unordered_set<QAction *> &uniqueActions)
|
||||
{
|
||||
for (const auto &action : actions) {
|
||||
// We don't want diabled actions
|
||||
if (!action->isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is this action actually a menu?
|
||||
if (auto menu = action->menu()) {
|
||||
auto menuActionList = menu->actions();
|
||||
|
||||
// Empty? => Maybe the menu loads action on aboutToShow()?
|
||||
if (menuActionList.isEmpty()) {
|
||||
Q_EMIT menu->aboutToShow();
|
||||
menuActionList = menu->actions();
|
||||
}
|
||||
|
||||
const QString menuTitle = menu->title();
|
||||
fillRows(rows, menuTitle, menuActionList, uniqueActions);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (action->text().isEmpty() && !action->isSeparator()) {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << "Action" << action << "in group" << title << "has no text";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (uniqueActions.insert(action).second) {
|
||||
rows.push_back(KCommandBarModel::Item{title, action, -1});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KCommandBarModel::refresh(const QList<KCommandBar::ActionGroup> &actionGroups)
|
||||
{
|
||||
int totalActions = std::accumulate(actionGroups.begin(), actionGroups.end(), 0, [](int a, const KCommandBar::ActionGroup &ag) {
|
||||
return a + ag.actions.count();
|
||||
});
|
||||
++totalActions; // for m_clearHistoryAction
|
||||
|
||||
QList<Item> temp_rows;
|
||||
std::unordered_set<QAction *> uniqueActions;
|
||||
temp_rows.reserve(totalActions);
|
||||
for (const auto &ag : actionGroups) {
|
||||
const auto &agActions = ag.actions;
|
||||
fillRows(temp_rows, ag.name, agActions, uniqueActions);
|
||||
}
|
||||
|
||||
temp_rows.push_back({tr("Command Bar"), m_clearHistoryAction, -1});
|
||||
|
||||
/**
|
||||
* For each action in last triggered actions,
|
||||
* - Find it in the actions
|
||||
* - Use the score variable to set its score
|
||||
*
|
||||
* Items in m_lastTriggered are stored in descending order
|
||||
* by their usage i.e., the first item in the vector is the most
|
||||
* recently invoked action.
|
||||
*
|
||||
* Here we traverse them in reverse order, i.e., from least recent to
|
||||
* most recent and then assign a score to them in a way that most recent
|
||||
* ends up having the highest score. Thus when proxy model does the sorting
|
||||
* later, most recent item will end up on the top
|
||||
*/
|
||||
int score = 0;
|
||||
std::for_each(m_lastTriggered.crbegin(), m_lastTriggered.crend(), [&score, &temp_rows](const QString &act) {
|
||||
auto it = std::find_if(temp_rows.begin(), temp_rows.end(), [act](const KCommandBarModel::Item &i) {
|
||||
return i.action->text() == act;
|
||||
});
|
||||
if (it != temp_rows.end()) {
|
||||
it->score = score++;
|
||||
}
|
||||
});
|
||||
|
||||
beginResetModel();
|
||||
m_rows = std::move(temp_rows);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant KCommandBarModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto &entry = m_rows[index.row()];
|
||||
const int col = index.column();
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
if (col == Column_Command) {
|
||||
return entry.displayName();
|
||||
}
|
||||
Q_ASSERT(col == Column_Shortcut);
|
||||
return entry.action->shortcut().toString();
|
||||
case Qt::DecorationRole:
|
||||
if (col == Column_Command) {
|
||||
return entry.action->icon();
|
||||
}
|
||||
break;
|
||||
case Qt::ToolTipRole: {
|
||||
QString toolTip = entry.displayName();
|
||||
if (!entry.action->shortcut().isEmpty()) {
|
||||
toolTip += QLatin1Char('\n');
|
||||
toolTip += entry.action->shortcut().toString();
|
||||
}
|
||||
return toolTip;
|
||||
}
|
||||
case Qt::UserRole: {
|
||||
return QVariant::fromValue(entry.action);
|
||||
}
|
||||
case Role::Score:
|
||||
return entry.score;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void KCommandBarModel::actionTriggered(const QString &name)
|
||||
{
|
||||
if (m_lastTriggered.size() == 6) {
|
||||
m_lastTriggered.pop_back();
|
||||
}
|
||||
m_lastTriggered.push_front(name);
|
||||
}
|
||||
|
||||
QStringList KCommandBarModel::lastUsedActions() const
|
||||
{
|
||||
return m_lastTriggered;
|
||||
}
|
||||
|
||||
void KCommandBarModel::setLastUsedActions(const QStringList &actionNames)
|
||||
{
|
||||
m_lastTriggered = actionNames;
|
||||
|
||||
while (m_lastTriggered.size() > 6) {
|
||||
m_lastTriggered.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_kcommandbarmodel_p.cpp"
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Waqar Ahmed <waqar.17a@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
#ifndef KCOMMANDBARMODEL_H
|
||||
#define KCOMMANDBARMODEL_H
|
||||
|
||||
#include "kcommandbar.h"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QList>
|
||||
|
||||
class QAction;
|
||||
|
||||
class KCommandBarModel final : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct Item {
|
||||
QString displayName() const;
|
||||
|
||||
QString groupName;
|
||||
QAction *action;
|
||||
int score;
|
||||
};
|
||||
|
||||
KCommandBarModel(QObject *parent = nullptr);
|
||||
|
||||
enum Role { Score = Qt::UserRole + 1 };
|
||||
|
||||
enum Column { Column_Command, Column_Shortcut, Column_Count };
|
||||
|
||||
/**
|
||||
* Resets the model
|
||||
*
|
||||
* If you are using last Used actions functionality, make sure
|
||||
* to set the last used actions before calling this function
|
||||
*/
|
||||
void refresh(const QList<KCommandBar::ActionGroup> &actionGroups);
|
||||
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return m_rows.size();
|
||||
}
|
||||
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return Column_Count;
|
||||
}
|
||||
|
||||
/**
|
||||
* reimplemented function to update score that is calculated by KFuzzyMatcher
|
||||
*/
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override
|
||||
{
|
||||
if (!index.isValid())
|
||||
return false;
|
||||
if (role == Role::Score) {
|
||||
auto row = index.row();
|
||||
m_rows[row].score = value.toInt();
|
||||
}
|
||||
return QAbstractTableModel::setData(index, value, role);
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
/**
|
||||
* action with name @p name was triggered, store it in m_lastTriggered
|
||||
*/
|
||||
void actionTriggered(const QString &name);
|
||||
|
||||
/**
|
||||
* last used actions
|
||||
* max = 6;
|
||||
*/
|
||||
QStringList lastUsedActions() const;
|
||||
|
||||
/**
|
||||
* incoming lastUsedActions
|
||||
*
|
||||
* should be set before calling refresh()
|
||||
*/
|
||||
void setLastUsedActions(const QStringList &actionNames);
|
||||
|
||||
private:
|
||||
QList<Item> m_rows;
|
||||
|
||||
/**
|
||||
* Last triggered actions by user
|
||||
*
|
||||
* Ordered descending i.e., least recently invoked
|
||||
* action is at the end
|
||||
*/
|
||||
QStringList m_lastTriggered;
|
||||
|
||||
QAction *m_clearHistoryAction;
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(KCommandBarModel::Item, Q_RELOCATABLE_TYPE);
|
||||
|
||||
#endif // KCOMMANDBARMODEL_H
|
||||
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2003 Benjamin C Meyer <ben+kdelibs at meyerhome dot net>
|
||||
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
|
||||
SPDX-FileCopyrightText: 2004 Michael Brade <brade@kde.org>
|
||||
SPDX-FileCopyrightText: 2021 Ahmad Samir <a.samirh78@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "kconfigdialog.h"
|
||||
|
||||
#include <KCoreConfigSkeleton>
|
||||
#include <KLocalizedString>
|
||||
#include <KPageWidgetModel>
|
||||
#include <kconfigdialogmanager.h>
|
||||
#include <khelpclient.h>
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QIcon>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QScrollBar>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <vector>
|
||||
|
||||
class KConfigDialogPrivate
|
||||
{
|
||||
public:
|
||||
KConfigDialogPrivate(const QString &name, KCoreConfigSkeleton *config, KConfigDialog *qq)
|
||||
: q(qq)
|
||||
{
|
||||
const QString dialogName = !name.isEmpty() ? name : QString::asprintf("SettingsDialog-%p", static_cast<void *>(q));
|
||||
|
||||
q->setObjectName(dialogName);
|
||||
q->setWindowTitle(i18nc("@title:window", "Configure"));
|
||||
q->setFaceType(KPageDialog::List);
|
||||
s_openDialogs.push_back({dialogName, q});
|
||||
|
||||
QDialogButtonBox *buttonBox = q->buttonBox();
|
||||
buttonBox->setStandardButtons(QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel
|
||||
| QDialogButtonBox::Help);
|
||||
QObject::connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, q, &KConfigDialog::updateSettings);
|
||||
QObject::connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, q, &KConfigDialog::updateSettings);
|
||||
QObject::connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, q, [this]() {
|
||||
updateButtons();
|
||||
});
|
||||
QObject::connect(buttonBox->button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, q, &KConfigDialog::updateWidgets);
|
||||
QObject::connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, q, &KConfigDialog::updateWidgetsDefault);
|
||||
QObject::connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, q, [this]() {
|
||||
updateButtons();
|
||||
});
|
||||
QObject::connect(buttonBox->button(QDialogButtonBox::Help), &QAbstractButton::clicked, q, &KConfigDialog::showHelp);
|
||||
|
||||
QObject::connect(q, &KPageDialog::pageRemoved, q, &KConfigDialog::onPageRemoved);
|
||||
|
||||
manager = new KConfigDialogManager(q, config);
|
||||
setupManagerConnections(manager);
|
||||
|
||||
if (QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply)) {
|
||||
applyButton->setEnabled(false);
|
||||
};
|
||||
}
|
||||
|
||||
KPageWidgetItem *addPageInternal(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header);
|
||||
|
||||
void setupManagerConnections(KConfigDialogManager *manager);
|
||||
|
||||
void updateApplyButton();
|
||||
void updateDefaultsButton();
|
||||
void updateButtons();
|
||||
void settingsChangedSlot();
|
||||
|
||||
KConfigDialog *const q;
|
||||
QString mAnchor;
|
||||
QString mHelpApp;
|
||||
bool shown = false;
|
||||
KConfigDialogManager *manager = nullptr;
|
||||
|
||||
struct WidgetManager {
|
||||
QWidget *widget = nullptr;
|
||||
KConfigDialogManager *manager = nullptr;
|
||||
};
|
||||
std::vector<WidgetManager> m_managerForPage;
|
||||
|
||||
/**
|
||||
* The list of existing dialogs.
|
||||
*/
|
||||
struct OpenDialogInfo {
|
||||
QString dialogName;
|
||||
KConfigDialog *dialog = nullptr;
|
||||
};
|
||||
static std::vector<OpenDialogInfo> s_openDialogs;
|
||||
};
|
||||
|
||||
std::vector<KConfigDialogPrivate::OpenDialogInfo> KConfigDialogPrivate::s_openDialogs;
|
||||
|
||||
KConfigDialog::KConfigDialog(QWidget *parent, const QString &name, KCoreConfigSkeleton *config)
|
||||
: KPageDialog(parent)
|
||||
, d(new KConfigDialogPrivate(name, config, this))
|
||||
{
|
||||
}
|
||||
|
||||
KConfigDialog::~KConfigDialog()
|
||||
{
|
||||
auto &openDlgs = KConfigDialogPrivate::s_openDialogs;
|
||||
const QString currentObjectName = objectName();
|
||||
auto it = std::find_if(openDlgs.cbegin(), openDlgs.cend(), [=](const KConfigDialogPrivate::OpenDialogInfo &info) {
|
||||
return currentObjectName == info.dialogName;
|
||||
});
|
||||
|
||||
if (it != openDlgs.cend()) {
|
||||
openDlgs.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
KPageWidgetItem *KConfigDialog::addPage(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header, bool manage)
|
||||
{
|
||||
Q_ASSERT(page);
|
||||
if (!page) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KPageWidgetItem *item = d->addPageInternal(page, itemName, pixmapName, header);
|
||||
if (manage) {
|
||||
d->manager->addWidget(page);
|
||||
}
|
||||
|
||||
if (d->shown && manage) {
|
||||
// update the default button if the dialog is shown
|
||||
QPushButton *defaultButton = buttonBox()->button(QDialogButtonBox::RestoreDefaults);
|
||||
if (defaultButton) {
|
||||
bool is_default = defaultButton->isEnabled() && d->manager->isDefault();
|
||||
defaultButton->setEnabled(!is_default);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
KPageWidgetItem *KConfigDialog::addPage(QWidget *page, KCoreConfigSkeleton *config, const QString &itemName, const QString &pixmapName, const QString &header)
|
||||
{
|
||||
Q_ASSERT(page);
|
||||
if (!page) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KPageWidgetItem *item = d->addPageInternal(page, itemName, pixmapName, header);
|
||||
auto *manager = new KConfigDialogManager(page, config);
|
||||
d->m_managerForPage.push_back({page, manager});
|
||||
d->setupManagerConnections(manager);
|
||||
|
||||
if (d->shown) {
|
||||
// update the default button if the dialog is shown
|
||||
QPushButton *defaultButton = buttonBox()->button(QDialogButtonBox::RestoreDefaults);
|
||||
if (defaultButton) {
|
||||
const bool is_default = defaultButton->isEnabled() && manager->isDefault();
|
||||
defaultButton->setEnabled(!is_default);
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
KPageWidgetItem *KConfigDialogPrivate::addPageInternal(QWidget *page, const QString &itemName, const QString &pixmapName, const QString &header)
|
||||
{
|
||||
QWidget *frame = new QWidget(q);
|
||||
QVBoxLayout *boxLayout = new QVBoxLayout(frame);
|
||||
boxLayout->setContentsMargins(0, 0, 0, 0);
|
||||
boxLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QScrollArea *scroll = new QScrollArea(q);
|
||||
scroll->setFrameShape(QFrame::NoFrame);
|
||||
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
scroll->setWidget(page);
|
||||
scroll->setWidgetResizable(true);
|
||||
scroll->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
|
||||
if (page->minimumSizeHint().height() > scroll->sizeHint().height() - 2) {
|
||||
if (page->sizeHint().width() < scroll->sizeHint().width() + 2) {
|
||||
// QScrollArea is planning only a vertical scroll bar,
|
||||
// try to avoid the horizontal one by reserving space for the vertical one.
|
||||
// Currently KPageViewPrivate::_k_modelChanged() queries the minimumSizeHint().
|
||||
// We can only set the minimumSize(), so this approach relies on QStackedWidget size calculation.
|
||||
scroll->setMinimumWidth(scroll->sizeHint().width() + qBound(0, scroll->verticalScrollBar()->sizeHint().width(), 200) + 4);
|
||||
}
|
||||
}
|
||||
|
||||
boxLayout->addWidget(scroll);
|
||||
KPageWidgetItem *item = new KPageWidgetItem(frame, itemName);
|
||||
item->setHeader(header);
|
||||
if (!pixmapName.isEmpty()) {
|
||||
item->setIcon(QIcon::fromTheme(pixmapName));
|
||||
}
|
||||
|
||||
q->KPageDialog::addPage(item);
|
||||
return item;
|
||||
}
|
||||
|
||||
void KConfigDialogPrivate::setupManagerConnections(KConfigDialogManager *manager)
|
||||
{
|
||||
q->connect(manager, qOverload<>(&KConfigDialogManager::settingsChanged), q, [this]() {
|
||||
settingsChangedSlot();
|
||||
});
|
||||
q->connect(manager, &KConfigDialogManager::widgetModified, q, [this]() {
|
||||
updateButtons();
|
||||
});
|
||||
|
||||
QDialogButtonBox *buttonBox = q->buttonBox();
|
||||
q->connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, manager, &KConfigDialogManager::updateSettings);
|
||||
q->connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, manager, &KConfigDialogManager::updateSettings);
|
||||
q->connect(buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, manager, &KConfigDialogManager::updateWidgets);
|
||||
q->connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, manager, &KConfigDialogManager::updateWidgetsDefault);
|
||||
}
|
||||
|
||||
void KConfigDialogPrivate::updateApplyButton()
|
||||
{
|
||||
QPushButton *applyButton = q->buttonBox()->button(QDialogButtonBox::Apply);
|
||||
if (!applyButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool hasManagerChanged = std::any_of(m_managerForPage.cbegin(), m_managerForPage.cend(), [](const WidgetManager &widgetManager) {
|
||||
return widgetManager.manager->hasChanged();
|
||||
});
|
||||
|
||||
applyButton->setEnabled(manager->hasChanged() || q->hasChanged() || hasManagerChanged);
|
||||
}
|
||||
|
||||
void KConfigDialogPrivate::updateDefaultsButton()
|
||||
{
|
||||
QPushButton *restoreDefaultsButton = q->buttonBox()->button(QDialogButtonBox::RestoreDefaults);
|
||||
if (!restoreDefaultsButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool isManagerDefaulted = std::all_of(m_managerForPage.cbegin(), m_managerForPage.cend(), [](const WidgetManager &widgetManager) {
|
||||
return widgetManager.manager->isDefault();
|
||||
});
|
||||
|
||||
restoreDefaultsButton->setDisabled(manager->isDefault() && q->isDefault() && isManagerDefaulted);
|
||||
}
|
||||
|
||||
void KConfigDialog::onPageRemoved(KPageWidgetItem *item)
|
||||
{
|
||||
auto it = std::find_if(d->m_managerForPage.cbegin(), d->m_managerForPage.cend(), [item](const KConfigDialogPrivate::WidgetManager &wm) {
|
||||
return item->widget()->isAncestorOf(wm.widget);
|
||||
});
|
||||
|
||||
if (it != d->m_managerForPage.cend()) { // There is a manager for this page, so remove it
|
||||
delete it->manager;
|
||||
d->m_managerForPage.erase(it);
|
||||
d->updateButtons();
|
||||
}
|
||||
}
|
||||
|
||||
KConfigDialog *KConfigDialog::exists(const QString &name)
|
||||
{
|
||||
auto &openDlgs = KConfigDialogPrivate::s_openDialogs;
|
||||
auto it = std::find_if(openDlgs.cbegin(), openDlgs.cend(), [name](const KConfigDialogPrivate::OpenDialogInfo &info) {
|
||||
return name == info.dialogName;
|
||||
});
|
||||
|
||||
return it != openDlgs.cend() ? it->dialog : nullptr;
|
||||
}
|
||||
|
||||
bool KConfigDialog::showDialog(const QString &name)
|
||||
{
|
||||
KConfigDialog *dialog = exists(name);
|
||||
if (dialog) {
|
||||
dialog->show();
|
||||
}
|
||||
return (dialog != nullptr);
|
||||
}
|
||||
|
||||
void KConfigDialogPrivate::updateButtons()
|
||||
{
|
||||
static bool only_once = false;
|
||||
if (only_once) {
|
||||
return;
|
||||
}
|
||||
only_once = true;
|
||||
|
||||
updateApplyButton();
|
||||
updateDefaultsButton();
|
||||
|
||||
Q_EMIT q->widgetModified();
|
||||
only_once = false;
|
||||
}
|
||||
|
||||
void KConfigDialogPrivate::settingsChangedSlot()
|
||||
{
|
||||
// Update the buttons
|
||||
updateButtons();
|
||||
Q_EMIT q->settingsChanged(q->objectName());
|
||||
}
|
||||
|
||||
void KConfigDialog::showEvent(QShowEvent *e)
|
||||
{
|
||||
if (!d->shown) {
|
||||
updateWidgets();
|
||||
d->manager->updateWidgets();
|
||||
for (auto [widget, manager] : d->m_managerForPage) {
|
||||
manager->updateWidgets();
|
||||
}
|
||||
|
||||
d->updateApplyButton();
|
||||
d->updateDefaultsButton();
|
||||
|
||||
d->shown = true;
|
||||
}
|
||||
KPageDialog::showEvent(e);
|
||||
}
|
||||
|
||||
void KConfigDialog::updateSettings()
|
||||
{
|
||||
}
|
||||
|
||||
void KConfigDialog::updateWidgets()
|
||||
{
|
||||
}
|
||||
|
||||
void KConfigDialog::updateWidgetsDefault()
|
||||
{
|
||||
}
|
||||
|
||||
bool KConfigDialog::hasChanged()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool KConfigDialog::isDefault()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void KConfigDialog::updateButtons()
|
||||
{
|
||||
d->updateButtons();
|
||||
}
|
||||
|
||||
void KConfigDialog::settingsChangedSlot()
|
||||
{
|
||||
d->settingsChangedSlot();
|
||||
}
|
||||
|
||||
void KConfigDialog::setHelp(const QString &anchor, const QString &appname)
|
||||
{
|
||||
d->mAnchor = anchor;
|
||||
d->mHelpApp = appname;
|
||||
}
|
||||
|
||||
void KConfigDialog::showHelp()
|
||||
{
|
||||
KHelpClient::invokeHelp(d->mAnchor, d->mHelpApp);
|
||||
}
|
||||
|
||||
#include "moc_kconfigdialog.cpp"
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2003 Benjamin C Meyer <ben+kdelibs at meyerhome dot net>
|
||||
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KCONFIGDIALOG_H
|
||||
#define KCONFIGDIALOG_H
|
||||
|
||||
#include <KPageDialog>
|
||||
#include <memory>
|
||||
|
||||
#include "kconfigwidgets_export.h"
|
||||
|
||||
class KConfig;
|
||||
class KCoreConfigSkeleton;
|
||||
class KConfigDialogManager;
|
||||
|
||||
/**
|
||||
* @class KConfigDialog kconfigdialog.h KConfigDialog
|
||||
*
|
||||
* \short Standard %KDE configuration dialog class
|
||||
*
|
||||
* The KConfigDialog class provides an easy and uniform means of displaying
|
||||
* a settings dialog using KPageDialog, KConfigDialogManager and a
|
||||
* KConfigSkeleton derived settings class.
|
||||
*
|
||||
* KConfigDialog handles the enabling and disabling of buttons, creation
|
||||
* of the dialog, and deletion of the widgets. Because of
|
||||
* KConfigDialogManager, this class also manages: restoring
|
||||
* the settings, resetting them to the default values, and saving them. This
|
||||
* requires that the names of the widgets corresponding to configuration entries
|
||||
* have to have the same name plus an additional "kcfg_" prefix. For example the
|
||||
* widget named "kcfg_MyOption" would be associated with the configuration entry
|
||||
* "MyOption".
|
||||
*
|
||||
* Here is an example usage of KConfigDialog:
|
||||
*
|
||||
* @code
|
||||
* void KCoolApp::showSettings(){
|
||||
* if (KConfigDialog::showDialog(QStringLiteral("settings"))) {
|
||||
* return;
|
||||
* }
|
||||
* KConfigDialog *dialog = new KConfigDialog(this, QStringLiteral("settings"), MySettings::self());
|
||||
* dialog->setFaceType(KPageDialog::List);
|
||||
* dialog->addPage(new General(0, "General"), i18n("General"));
|
||||
* dialog->addPage(new Appearance(0, "Style"), i18n("Appearance"));
|
||||
* connect(dialog, &KConfigDialog::settingsChanged, mainWidget, &Bar::loadSettings);
|
||||
* connect(dialog, &KConfigDialog::settingsChanged, this, &Foo::loadSettings);
|
||||
* dialog->show();
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Other than the above code, each class that has settings in the dialog should
|
||||
* have a loadSettings() type slot to read settings and perform any
|
||||
* necessary changes.
|
||||
*
|
||||
* For dialog appearance options (like buttons, default button, ...) please see
|
||||
* @ref KPageDialog.
|
||||
*
|
||||
* @see KConfigSkeleton
|
||||
* @author Waldo Bastian <bastian@kde.org>
|
||||
*/
|
||||
class KCONFIGWIDGETS_EXPORT KConfigDialog : public KPageDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* A widget in the dialog was modified.
|
||||
*/
|
||||
void widgetModified();
|
||||
|
||||
/**
|
||||
* One or more of the settings have been permanently changed such as if
|
||||
* the user clicked on the Apply or Ok button.
|
||||
* @param dialogName the name of the dialog.
|
||||
*/
|
||||
void settingsChanged(const QString &dialogName);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @param parent - The parent of this object. Even though the class
|
||||
* deletes itself the parent should be set so the dialog can be centered
|
||||
* with the application on the screen.
|
||||
*
|
||||
* @param name - The name of this object. The name is used in determining if
|
||||
* there can be more than one dialog at a time. Use names such as:
|
||||
* "Font Settings" or "Color Settings" and not just "Settings" in
|
||||
* applications where there is more than one dialog.
|
||||
*
|
||||
* @param config - Config object containing settings.
|
||||
*/
|
||||
KConfigDialog(QWidget *parent, const QString &name, KCoreConfigSkeleton *config);
|
||||
|
||||
/**
|
||||
* Deconstructor, removes name from the list of open dialogs.
|
||||
* Deletes private class.
|
||||
* @see exists()
|
||||
*/
|
||||
~KConfigDialog() override;
|
||||
|
||||
/**
|
||||
* Adds page to the dialog and to KConfigDialogManager. When an
|
||||
* application is done adding pages show() should be called to
|
||||
* display the dialog.
|
||||
* @param page - Pointer to the page that is to be added to the dialog.
|
||||
* This object is reparented.
|
||||
* @param itemName - Name of the page.
|
||||
* @param pixmapName - Name of the icon that should be used, if needed, when
|
||||
* displaying the page. The string may either be the name of a themed
|
||||
* icon (e.g. "document-save"), which the internal icon loader will be
|
||||
* used to retrieve, or an absolute path to the pixmap on disk.
|
||||
* @param header - Header text use in the list modes. Ignored in Tabbed
|
||||
* mode. If empty, the itemName text is used when needed.
|
||||
* @param manage - Whether KConfigDialogManager should manage the page or not.
|
||||
* @returns The KPageWidgetItem associated with the page.
|
||||
*/
|
||||
KPageWidgetItem *
|
||||
addPage(QWidget *page, const QString &itemName, const QString &pixmapName = QString(), const QString &header = QString(), bool manage = true);
|
||||
|
||||
/**
|
||||
* Adds page to the dialog that is managed by a custom KConfigDialogManager.
|
||||
* This is useful for dialogs that contain settings spread over more than
|
||||
* one configuration file and thus have/need more than one KConfigSkeleton.
|
||||
* When an application is done adding pages show() should be called to
|
||||
* display the dialog.
|
||||
* @param page - Pointer to the page that is to be added to the dialog.
|
||||
* This object is reparented.
|
||||
* @param config - Config object containing corresponding settings.
|
||||
* @param itemName - Name of the page.
|
||||
* @param pixmapName - Name of the icon that should be used, if needed, when
|
||||
* displaying the page. The string may either be the name of a themed
|
||||
* icon (e.g. "document-save"), which the internal icon loader will be
|
||||
* used to retrieve, or an absolute path to the pixmap on disk.
|
||||
* @param header - Header text use in the list modes. Ignored in Tabbed
|
||||
* mode. If empty, the itemName text is used when needed.
|
||||
* @returns The KPageWidgetItem associated with the page.
|
||||
*/
|
||||
KPageWidgetItem *
|
||||
addPage(QWidget *page, KCoreConfigSkeleton *config, const QString &itemName, const QString &pixmapName = QString(), const QString &header = QString());
|
||||
|
||||
/**
|
||||
* See if a dialog with the name 'name' already exists.
|
||||
* @see showDialog()
|
||||
* @param name - Dialog name to look for.
|
||||
* @return Pointer to widget or NULL if it does not exist.
|
||||
*/
|
||||
static KConfigDialog *exists(const QString &name);
|
||||
|
||||
/**
|
||||
* Attempts to show the dialog with the name 'name'.
|
||||
* @see exists()
|
||||
* @param name - The name of the dialog to show.
|
||||
* @return True if the dialog 'name' exists and was shown.
|
||||
*/
|
||||
static bool showDialog(const QString &name);
|
||||
|
||||
protected Q_SLOTS:
|
||||
/**
|
||||
* Update the settings from the dialog.
|
||||
* Virtual function for custom additions.
|
||||
*
|
||||
* Example use: User clicks Ok or Apply button in a configure dialog.
|
||||
*/
|
||||
virtual void updateSettings();
|
||||
|
||||
/**
|
||||
* Update the dialog based on the settings.
|
||||
* Virtual function for custom additions.
|
||||
*
|
||||
* Example use: Initialisation of dialog.
|
||||
* Example use: User clicks Reset button in a configure dialog.
|
||||
*/
|
||||
virtual void updateWidgets();
|
||||
|
||||
/**
|
||||
* Update the dialog based on the default settings.
|
||||
* Virtual function for custom additions.
|
||||
*
|
||||
* Example use: User clicks Defaults button in a configure dialog.
|
||||
*/
|
||||
virtual void updateWidgetsDefault();
|
||||
|
||||
/**
|
||||
* Updates the Apply and Default buttons.
|
||||
* Connect to this slot if you implement your own hasChanged()
|
||||
* or isDefault() methods for widgets not managed by KConfig.
|
||||
* @since 4.3
|
||||
*/
|
||||
void updateButtons();
|
||||
|
||||
/**
|
||||
* Some setting was changed. Emit the signal with the dialogs name.
|
||||
* Connect to this slot if there are widgets not managed by KConfig.
|
||||
* @since 4.3
|
||||
*/
|
||||
void settingsChangedSlot();
|
||||
|
||||
/**
|
||||
* Sets the help path and topic.
|
||||
*
|
||||
* The HTML file will be found using the X-DocPath entry in the application's desktop file.
|
||||
* It can be either a relative path, or a website URL.
|
||||
*
|
||||
* @param anchor This has to be a defined anchor in your
|
||||
* docbook sources or website. If empty the main index
|
||||
* is loaded.
|
||||
* @param appname This allows you to specify the .desktop file to get the help path from.
|
||||
* If empty the QCoreApplication::applicationName() is used.
|
||||
*/
|
||||
void setHelp(const QString &anchor, const QString &appname = QString());
|
||||
|
||||
/**
|
||||
* Displays help for this config dialog.
|
||||
* @since 5.0
|
||||
*/
|
||||
virtual void showHelp();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Returns whether the current state of the dialog is
|
||||
* different from the current configuration.
|
||||
* Virtual function for custom additions.
|
||||
*/
|
||||
virtual bool hasChanged();
|
||||
|
||||
/**
|
||||
* Returns whether the current state of the dialog is
|
||||
* the same as the default configuration.
|
||||
*/
|
||||
virtual bool isDefault();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
void showEvent(QShowEvent *e) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
/**
|
||||
* Slot which cleans up the KConfigDialogManager of the page.
|
||||
* */
|
||||
KCONFIGWIDGETS_NO_EXPORT void onPageRemoved(KPageWidgetItem *item);
|
||||
|
||||
private:
|
||||
friend class KConfigDialogPrivate;
|
||||
std::unique_ptr<class KConfigDialogPrivate> const d;
|
||||
|
||||
Q_DISABLE_COPY(KConfigDialog)
|
||||
};
|
||||
|
||||
#endif // KCONFIGDIALOG_H
|
||||
@@ -0,0 +1,651 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2003 Benjamin C Meyer <ben+kdelibs at meyerhome dot net>
|
||||
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
|
||||
SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau <kossebau@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Kevin Ottens <kevin.ottens@enioka.com>
|
||||
SPDX-FileCopyrightText: 2020 Cyril Rossi <cyril.rossi@enioka.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "kconfigdialogmanager.h"
|
||||
#include "kconfigdialogmanager_p.h"
|
||||
#include "kconfigwidgets_debug.h"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QLayout>
|
||||
#include <QMetaObject>
|
||||
#include <QMetaProperty>
|
||||
#include <QRadioButton>
|
||||
#include <QTimer>
|
||||
|
||||
#include <KConfigSkeleton>
|
||||
|
||||
typedef QHash<QString, QByteArray> MyHash;
|
||||
Q_GLOBAL_STATIC(MyHash, s_propertyMap)
|
||||
Q_GLOBAL_STATIC(MyHash, s_changedMap)
|
||||
|
||||
KConfigDialogManager::KConfigDialogManager(QWidget *parent, KCoreConfigSkeleton *conf)
|
||||
: QObject(parent)
|
||||
, d(new KConfigDialogManagerPrivate(this))
|
||||
{
|
||||
d->m_conf = conf;
|
||||
d->m_dialog = parent;
|
||||
init(true);
|
||||
}
|
||||
|
||||
KConfigDialogManager::~KConfigDialogManager() = default;
|
||||
|
||||
// KF6: Drop this and get signals only from metaObject and/or widget's dynamic properties kcfg_property/kcfg_propertyNotify
|
||||
void KConfigDialogManager::initMaps()
|
||||
{
|
||||
if (s_propertyMap()->isEmpty()) {
|
||||
s_propertyMap()->insert(QStringLiteral("KButtonGroup"), "current");
|
||||
s_propertyMap()->insert(QStringLiteral("KColorButton"), "color");
|
||||
s_propertyMap()->insert(QStringLiteral("KColorCombo"), "color");
|
||||
s_propertyMap()->insert(QStringLiteral("KKeySequenceWidget"), "keySequence");
|
||||
}
|
||||
|
||||
if (s_changedMap()->isEmpty()) {
|
||||
// Qt
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
s_changedMap()->insert(QStringLiteral("QCheckBox"), SIGNAL(checkStateChanged(Qt::CheckState)));
|
||||
#else
|
||||
s_changedMap()->insert(QStringLiteral("QCheckBox"), SIGNAL(stateChanged(int)));
|
||||
#endif
|
||||
s_changedMap()->insert(QStringLiteral("QPushButton"), SIGNAL(clicked(bool)));
|
||||
s_changedMap()->insert(QStringLiteral("QRadioButton"), SIGNAL(toggled(bool)));
|
||||
s_changedMap()->insert(QStringLiteral("QGroupBox"), SIGNAL(toggled(bool)));
|
||||
s_changedMap()->insert(QStringLiteral("QComboBox"), SIGNAL(activated(int)));
|
||||
s_changedMap()->insert(QStringLiteral("QDateEdit"), SIGNAL(dateChanged(QDate)));
|
||||
s_changedMap()->insert(QStringLiteral("QTimeEdit"), SIGNAL(timeChanged(QTime)));
|
||||
s_changedMap()->insert(QStringLiteral("QDateTimeEdit"), SIGNAL(dateTimeChanged(QDateTime)));
|
||||
s_changedMap()->insert(QStringLiteral("QDial"), SIGNAL(valueChanged(int)));
|
||||
s_changedMap()->insert(QStringLiteral("QDoubleSpinBox"), SIGNAL(valueChanged(double)));
|
||||
s_changedMap()->insert(QStringLiteral("QLineEdit"), SIGNAL(textChanged(QString)));
|
||||
s_changedMap()->insert(QStringLiteral("QSlider"), SIGNAL(valueChanged(int)));
|
||||
s_changedMap()->insert(QStringLiteral("QSpinBox"), SIGNAL(valueChanged(int)));
|
||||
s_changedMap()->insert(QStringLiteral("QTextEdit"), SIGNAL(textChanged()));
|
||||
s_changedMap()->insert(QStringLiteral("QTextBrowser"), SIGNAL(sourceChanged(QString)));
|
||||
s_changedMap()->insert(QStringLiteral("QPlainTextEdit"), SIGNAL(textChanged()));
|
||||
s_changedMap()->insert(QStringLiteral("QTabWidget"), SIGNAL(currentChanged(int)));
|
||||
|
||||
// KDE
|
||||
s_changedMap()->insert(QStringLiteral("KComboBox"), SIGNAL(activated(int)));
|
||||
s_changedMap()->insert(QStringLiteral("KFontComboBox"), SIGNAL(activated(int)));
|
||||
s_changedMap()->insert(QStringLiteral("KFontRequester"), SIGNAL(fontSelected(QFont)));
|
||||
s_changedMap()->insert(QStringLiteral("KFontChooser"), SIGNAL(fontSelected(QFont)));
|
||||
s_changedMap()->insert(QStringLiteral("KColorCombo"), SIGNAL(activated(QColor)));
|
||||
s_changedMap()->insert(QStringLiteral("KColorButton"), SIGNAL(changed(QColor)));
|
||||
s_changedMap()->insert(QStringLiteral("KDatePicker"), SIGNAL(dateSelected(QDate)));
|
||||
s_changedMap()->insert(QStringLiteral("KDateWidget"), SIGNAL(changed(QDate)));
|
||||
s_changedMap()->insert(QStringLiteral("KDateTimeWidget"), SIGNAL(valueChanged(QDateTime)));
|
||||
s_changedMap()->insert(QStringLiteral("KEditListWidget"), SIGNAL(changed()));
|
||||
s_changedMap()->insert(QStringLiteral("KListWidget"), SIGNAL(itemSelectionChanged()));
|
||||
s_changedMap()->insert(QStringLiteral("KLineEdit"), SIGNAL(textChanged(QString)));
|
||||
s_changedMap()->insert(QStringLiteral("KRestrictedLine"), SIGNAL(textChanged(QString)));
|
||||
s_changedMap()->insert(QStringLiteral("KTextEdit"), SIGNAL(textChanged()));
|
||||
s_changedMap()->insert(QStringLiteral("KUrlRequester"), SIGNAL(textChanged(QString)));
|
||||
s_changedMap()->insert(QStringLiteral("KUrlComboRequester"), SIGNAL(textChanged(QString)));
|
||||
s_changedMap()->insert(QStringLiteral("KUrlComboBox"), SIGNAL(urlActivated(QUrl)));
|
||||
s_changedMap()->insert(QStringLiteral("KButtonGroup"), SIGNAL(changed(int)));
|
||||
}
|
||||
}
|
||||
|
||||
QHash<QString, QByteArray> *KConfigDialogManager::propertyMap()
|
||||
{
|
||||
initMaps();
|
||||
return s_propertyMap();
|
||||
}
|
||||
|
||||
void KConfigDialogManager::init(bool trackChanges)
|
||||
{
|
||||
initMaps();
|
||||
d->trackChanges = trackChanges;
|
||||
|
||||
// Go through all of the children of the widgets and find all known widgets
|
||||
(void)parseChildren(d->m_dialog, trackChanges);
|
||||
}
|
||||
|
||||
void KConfigDialogManager::addWidget(QWidget *widget)
|
||||
{
|
||||
(void)parseChildren(widget, true);
|
||||
}
|
||||
|
||||
void KConfigDialogManager::setupWidget(QWidget *widget, KConfigSkeletonItem *item)
|
||||
{
|
||||
QVariant minValue = item->minValue();
|
||||
if (minValue.isValid()) {
|
||||
// KSelector is using this property
|
||||
if (widget->metaObject()->indexOfProperty("minValue") != -1) {
|
||||
widget->setProperty("minValue", minValue);
|
||||
}
|
||||
if (widget->metaObject()->indexOfProperty("minimum") != -1) {
|
||||
widget->setProperty("minimum", minValue);
|
||||
}
|
||||
}
|
||||
QVariant maxValue = item->maxValue();
|
||||
if (maxValue.isValid()) {
|
||||
// KSelector is using this property
|
||||
if (widget->metaObject()->indexOfProperty("maxValue") != -1) {
|
||||
widget->setProperty("maxValue", maxValue);
|
||||
}
|
||||
if (widget->metaObject()->indexOfProperty("maximum") != -1) {
|
||||
widget->setProperty("maximum", maxValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (widget->whatsThis().isEmpty()) {
|
||||
QString whatsThis = item->whatsThis();
|
||||
if (!whatsThis.isEmpty()) {
|
||||
widget->setWhatsThis(whatsThis);
|
||||
}
|
||||
}
|
||||
|
||||
if (widget->toolTip().isEmpty()) {
|
||||
QString toolTip = item->toolTip();
|
||||
if (!toolTip.isEmpty()) {
|
||||
widget->setToolTip(toolTip);
|
||||
}
|
||||
}
|
||||
|
||||
// If it is a QGroupBox with only autoExclusive buttons
|
||||
// and has no custom property and the config item type
|
||||
// is an integer, assume we want to save the index like we did with
|
||||
// KButtonGroup instead of if it is checked or not
|
||||
QGroupBox *gb = qobject_cast<QGroupBox *>(widget);
|
||||
if (gb && getCustomProperty(gb).isEmpty()) {
|
||||
const KConfigSkeletonItem *item = d->m_conf->findItem(widget->objectName().mid(5));
|
||||
if (item->property().userType() == QMetaType::Int) {
|
||||
QObjectList children = gb->children();
|
||||
children.removeAll(gb->layout());
|
||||
const QList<QAbstractButton *> buttons = gb->findChildren<QAbstractButton *>();
|
||||
bool allAutoExclusiveDirectChildren = true;
|
||||
for (QAbstractButton *button : buttons) {
|
||||
allAutoExclusiveDirectChildren = allAutoExclusiveDirectChildren && button->autoExclusive() && button->parent() == gb;
|
||||
}
|
||||
if (allAutoExclusiveDirectChildren) {
|
||||
d->allExclusiveGroupBoxes << widget;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!item->isEqual(property(widget))) {
|
||||
setProperty(widget, item->property());
|
||||
}
|
||||
|
||||
d->updateWidgetIndicator(item->name(), widget);
|
||||
}
|
||||
|
||||
bool KConfigDialogManager::parseChildren(const QWidget *widget, bool trackChanges)
|
||||
{
|
||||
bool valueChanged = false;
|
||||
const QList<QObject *> listOfChildren = widget->children();
|
||||
if (listOfChildren.isEmpty()) { //?? XXX
|
||||
return valueChanged;
|
||||
}
|
||||
|
||||
const QMetaMethod onWidgetModifiedSlot = metaObject()->method(metaObject()->indexOfSlot("onWidgetModified()"));
|
||||
Q_ASSERT(onWidgetModifiedSlot.isValid() && metaObject()->indexOfSlot("onWidgetModified()") >= 0);
|
||||
|
||||
for (QObject *object : listOfChildren) {
|
||||
if (!object->isWidgetType()) {
|
||||
continue; // Skip non-widgets
|
||||
}
|
||||
|
||||
QWidget *childWidget = static_cast<QWidget *>(object);
|
||||
|
||||
QString widgetName = childWidget->objectName();
|
||||
bool bParseChildren = true;
|
||||
bool bSaveInsideGroupBox = d->insideGroupBox;
|
||||
|
||||
if (widgetName.startsWith(QLatin1String("kcfg_"))) {
|
||||
// This is one of our widgets!
|
||||
QString configId = widgetName.mid(5);
|
||||
KConfigSkeletonItem *item = d->m_conf->findItem(configId);
|
||||
if (item) {
|
||||
d->knownWidget.insert(configId, childWidget);
|
||||
|
||||
setupWidget(childWidget, item);
|
||||
|
||||
if (trackChanges) {
|
||||
bool changeSignalFound = false;
|
||||
|
||||
if (d->allExclusiveGroupBoxes.contains(childWidget)) {
|
||||
const QList<QAbstractButton *> buttons = childWidget->findChildren<QAbstractButton *>();
|
||||
for (QAbstractButton *button : buttons) {
|
||||
connect(button, &QAbstractButton::toggled, this, [this] {
|
||||
d->onWidgetModified();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray propertyChangeSignal = getCustomPropertyChangedSignal(childWidget);
|
||||
if (propertyChangeSignal.isEmpty()) {
|
||||
propertyChangeSignal = getUserPropertyChangedSignal(childWidget);
|
||||
}
|
||||
|
||||
if (propertyChangeSignal.isEmpty()) {
|
||||
// get the change signal from the meta object
|
||||
const QMetaObject *metaObject = childWidget->metaObject();
|
||||
QByteArray userproperty = getCustomProperty(childWidget);
|
||||
if (userproperty.isEmpty()) {
|
||||
userproperty = getUserProperty(childWidget);
|
||||
}
|
||||
if (!userproperty.isEmpty()) {
|
||||
const int indexOfProperty = metaObject->indexOfProperty(userproperty.constData());
|
||||
if (indexOfProperty != -1) {
|
||||
const QMetaProperty property = metaObject->property(indexOfProperty);
|
||||
const QMetaMethod notifySignal = property.notifySignal();
|
||||
if (notifySignal.isValid()) {
|
||||
connect(childWidget, notifySignal, this, onWidgetModifiedSlot);
|
||||
changeSignalFound = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << "Don't know how to monitor widget" << childWidget->metaObject()->className() << "for changes!";
|
||||
}
|
||||
} else {
|
||||
connect(childWidget, propertyChangeSignal.constData(), this, SLOT(onWidgetModified()));
|
||||
changeSignalFound = true;
|
||||
}
|
||||
|
||||
if (changeSignalFound) {
|
||||
QComboBox *cb = qobject_cast<QComboBox *>(childWidget);
|
||||
if (cb && cb->isEditable()) {
|
||||
connect(cb, &QComboBox::editTextChanged, this, &KConfigDialogManager::widgetModified);
|
||||
}
|
||||
}
|
||||
}
|
||||
QGroupBox *gb = qobject_cast<QGroupBox *>(childWidget);
|
||||
if (!gb) {
|
||||
bParseChildren = false;
|
||||
} else {
|
||||
d->insideGroupBox = true;
|
||||
}
|
||||
} else {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << "A widget named" << widgetName << "was found but there is no setting named" << configId;
|
||||
}
|
||||
} else if (QLabel *label = qobject_cast<QLabel *>(childWidget)) {
|
||||
QWidget *buddy = label->buddy();
|
||||
if (!buddy) {
|
||||
continue;
|
||||
}
|
||||
QString buddyName = buddy->objectName();
|
||||
if (buddyName.startsWith(QLatin1String("kcfg_"))) {
|
||||
// This is one of our widgets!
|
||||
QString configId = buddyName.mid(5);
|
||||
d->buddyWidget.insert(configId, childWidget);
|
||||
}
|
||||
}
|
||||
// kf5: commented out to reduce debug output
|
||||
// #ifndef NDEBUG
|
||||
// else if (!widgetName.isEmpty() && trackChanges)
|
||||
// {
|
||||
// QHash<QString, QByteArray>::const_iterator changedIt = s_changedMap()->constFind(childWidget->metaObject()->className());
|
||||
// if (changedIt != s_changedMap()->constEnd())
|
||||
// {
|
||||
// if ((!d->insideGroupBox || !qobject_cast<QRadioButton*>(childWidget)) &&
|
||||
// !qobject_cast<QGroupBox*>(childWidget) &&!qobject_cast<QTabWidget*>(childWidget) )
|
||||
// qCDebug(KCONFIG_WIDGETS_LOG) << "Widget '" << widgetName << "' (" << childWidget->metaObject()->className() << ") remains unmanaged.";
|
||||
// }
|
||||
// }
|
||||
// #endif
|
||||
|
||||
if (bParseChildren) {
|
||||
// this widget is not known as something we can store.
|
||||
// Maybe we can store one of its children.
|
||||
valueChanged |= parseChildren(childWidget, trackChanges);
|
||||
}
|
||||
d->insideGroupBox = bSaveInsideGroupBox;
|
||||
}
|
||||
return valueChanged;
|
||||
}
|
||||
|
||||
void KConfigDialogManager::updateWidgets()
|
||||
{
|
||||
bool changed = false;
|
||||
bool bSignalsBlocked = signalsBlocked();
|
||||
blockSignals(true);
|
||||
|
||||
QWidget *widget;
|
||||
QHashIterator<QString, QWidget *> it(d->knownWidget);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
widget = it.value();
|
||||
|
||||
KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
|
||||
if (!item) {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << "The setting" << it.key() << "has disappeared!";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!item->isEqual(property(widget))) {
|
||||
setProperty(widget, item->property());
|
||||
// qCDebug(KCONFIG_WIDGETS_LOG) << "The setting" << it.key() << "[" << widget->className() << "] has changed";
|
||||
changed = true;
|
||||
}
|
||||
if (item->isImmutable()) {
|
||||
widget->setEnabled(false);
|
||||
QWidget *buddy = d->buddyWidget.value(it.key(), nullptr);
|
||||
if (buddy) {
|
||||
buddy->setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
blockSignals(bSignalsBlocked);
|
||||
|
||||
if (changed) {
|
||||
QTimer::singleShot(0, this, &KConfigDialogManager::widgetModified);
|
||||
d->updateAllWidgetIndicators();
|
||||
}
|
||||
}
|
||||
|
||||
void KConfigDialogManager::updateWidgetsDefault()
|
||||
{
|
||||
bool bUseDefaults = d->m_conf->useDefaults(true);
|
||||
updateWidgets();
|
||||
d->m_conf->useDefaults(bUseDefaults);
|
||||
d->updateAllWidgetIndicators();
|
||||
}
|
||||
|
||||
void KConfigDialogManager::setDefaultsIndicatorsVisible(bool enabled)
|
||||
{
|
||||
d->setDefaultsIndicatorsVisible(enabled);
|
||||
}
|
||||
|
||||
void KConfigDialogManager::updateSettings()
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
QWidget *widget;
|
||||
QHashIterator<QString, QWidget *> it(d->knownWidget);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
widget = it.value();
|
||||
|
||||
KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
|
||||
if (!item) {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << "The setting" << it.key() << "has disappeared!";
|
||||
continue;
|
||||
}
|
||||
|
||||
QVariant fromWidget = property(widget);
|
||||
if (!item->isEqual(fromWidget)) {
|
||||
item->setProperty(fromWidget);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
d->m_conf->save();
|
||||
Q_EMIT settingsChanged();
|
||||
d->updateAllWidgetIndicators();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray KConfigDialogManager::getUserProperty(const QWidget *widget) const
|
||||
{
|
||||
MyHash *map = s_propertyMap();
|
||||
const QMetaObject *metaObject = widget->metaObject();
|
||||
const QString className(QLatin1String(metaObject->className()));
|
||||
auto it = map->find(className);
|
||||
if (it == map->end()) {
|
||||
const QMetaProperty userProp = metaObject->userProperty();
|
||||
if (userProp.isValid()) {
|
||||
it = map->insert(className, userProp.name());
|
||||
// qCDebug(KCONFIG_WIDGETS_LOG) << "class name: '" << className
|
||||
//<< " 's USER property: " << metaProperty.name() << endl;
|
||||
} else {
|
||||
return QByteArray(); // no USER property
|
||||
}
|
||||
}
|
||||
|
||||
const QComboBox *cb = qobject_cast<const QComboBox *>(widget);
|
||||
if (cb) {
|
||||
const char *qcomboUserPropertyName = cb->QComboBox::metaObject()->userProperty().name();
|
||||
const int qcomboUserPropertyIndex = qcomboUserPropertyName ? cb->QComboBox::metaObject()->indexOfProperty(qcomboUserPropertyName) : -1;
|
||||
const char *widgetUserPropertyName = metaObject->userProperty().name();
|
||||
const int widgetUserPropertyIndex = widgetUserPropertyName ? cb->metaObject()->indexOfProperty(widgetUserPropertyName) : -1;
|
||||
|
||||
// no custom user property set on subclass of QComboBox?
|
||||
if (qcomboUserPropertyIndex == widgetUserPropertyIndex) {
|
||||
return QByteArray(); // use the q/kcombobox special code
|
||||
}
|
||||
}
|
||||
|
||||
return it != map->end() ? it.value() : QByteArray{};
|
||||
}
|
||||
|
||||
QByteArray KConfigDialogManager::getCustomProperty(const QWidget *widget) const
|
||||
{
|
||||
QVariant prop(widget->property("kcfg_property"));
|
||||
if (prop.isValid()) {
|
||||
if (!prop.canConvert<QByteArray>()) {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << "kcfg_property on" << widget->metaObject()->className() << "is not of type ByteArray";
|
||||
} else {
|
||||
return prop.toByteArray();
|
||||
}
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray KConfigDialogManager::getUserPropertyChangedSignal(const QWidget *widget) const
|
||||
{
|
||||
const QString className = QLatin1String(widget->metaObject()->className());
|
||||
auto changedIt = s_changedMap()->constFind(className);
|
||||
|
||||
if (changedIt == s_changedMap()->constEnd()) {
|
||||
// If the class name of the widget wasn't in the monitored widgets map, then look for
|
||||
// it again using the super class name. This fixes a problem with using QtRuby/Korundum
|
||||
// widgets with KConfigXT where 'Qt::Widget' wasn't being seen a the real deal, even
|
||||
// though it was a 'QWidget'.
|
||||
if (widget->metaObject()->superClass()) {
|
||||
const QString parentClassName = QLatin1String(widget->metaObject()->superClass()->className());
|
||||
changedIt = s_changedMap()->constFind(parentClassName);
|
||||
}
|
||||
}
|
||||
|
||||
return (changedIt == s_changedMap()->constEnd()) ? QByteArray() : *changedIt;
|
||||
}
|
||||
|
||||
QByteArray KConfigDialogManager::getCustomPropertyChangedSignal(const QWidget *widget) const
|
||||
{
|
||||
QVariant prop(widget->property("kcfg_propertyNotify"));
|
||||
if (prop.isValid()) {
|
||||
if (!prop.canConvert<QByteArray>()) {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << "kcfg_propertyNotify on" << widget->metaObject()->className() << "is not of type ByteArray";
|
||||
} else {
|
||||
return prop.toByteArray();
|
||||
}
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
void KConfigDialogManager::setProperty(QWidget *w, const QVariant &v)
|
||||
{
|
||||
if (d->allExclusiveGroupBoxes.contains(w)) {
|
||||
const QList<QAbstractButton *> buttons = w->findChildren<QAbstractButton *>();
|
||||
if (v.toInt() < buttons.count()) {
|
||||
buttons[v.toInt()]->setChecked(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray userproperty = getCustomProperty(w);
|
||||
if (userproperty.isEmpty()) {
|
||||
userproperty = getUserProperty(w);
|
||||
}
|
||||
if (userproperty.isEmpty()) {
|
||||
QComboBox *cb = qobject_cast<QComboBox *>(w);
|
||||
if (cb) {
|
||||
if (cb->isEditable()) {
|
||||
int i = cb->findText(v.toString());
|
||||
if (i != -1) {
|
||||
cb->setCurrentIndex(i);
|
||||
} else {
|
||||
cb->setEditText(v.toString());
|
||||
}
|
||||
} else {
|
||||
cb->setCurrentIndex(v.toInt());
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (userproperty.isEmpty()) {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << w->metaObject()->className() << "widget not handled!";
|
||||
return;
|
||||
}
|
||||
|
||||
w->setProperty(userproperty.constData(), v);
|
||||
}
|
||||
|
||||
QVariant KConfigDialogManager::property(QWidget *w) const
|
||||
{
|
||||
if (d->allExclusiveGroupBoxes.contains(w)) {
|
||||
const QList<QAbstractButton *> buttons = w->findChildren<QAbstractButton *>();
|
||||
for (int i = 0; i < buttons.count(); ++i) {
|
||||
if (buttons[i]->isChecked()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
QByteArray userproperty = getCustomProperty(w);
|
||||
if (userproperty.isEmpty()) {
|
||||
userproperty = getUserProperty(w);
|
||||
}
|
||||
if (userproperty.isEmpty()) {
|
||||
QComboBox *cb = qobject_cast<QComboBox *>(w);
|
||||
if (cb) {
|
||||
if (cb->isEditable()) {
|
||||
return QVariant(cb->currentText());
|
||||
} else {
|
||||
return QVariant(cb->currentIndex());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (userproperty.isEmpty()) {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << w->metaObject()->className() << "widget not handled!";
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
return w->property(userproperty.constData());
|
||||
}
|
||||
|
||||
bool KConfigDialogManager::hasChanged() const
|
||||
{
|
||||
QWidget *widget;
|
||||
QHashIterator<QString, QWidget *> it(d->knownWidget);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
widget = it.value();
|
||||
|
||||
KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
|
||||
if (!item) {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << "The setting" << it.key() << "has disappeared!";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!item->isEqual(property(widget))) {
|
||||
// qCDebug(KCONFIG_WIDGETS_LOG) << "Widget for '" << it.key() << "' has changed.";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool KConfigDialogManager::isDefault() const
|
||||
{
|
||||
QWidget *widget;
|
||||
QHashIterator<QString, QWidget *> it(d->knownWidget);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
widget = it.value();
|
||||
|
||||
KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
|
||||
if (!item) {
|
||||
qCWarning(KCONFIG_WIDGETS_LOG) << "The setting" << it.key() << "has disappeared!";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (property(widget) != item->getDefault()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
KConfigDialogManagerPrivate::KConfigDialogManagerPrivate(KConfigDialogManager *qq)
|
||||
: q(qq)
|
||||
, insideGroupBox(false)
|
||||
, defaultsIndicatorsVisible(false)
|
||||
{
|
||||
}
|
||||
|
||||
void KConfigDialogManagerPrivate::setDefaultsIndicatorsVisible(bool enabled)
|
||||
{
|
||||
if (defaultsIndicatorsVisible != enabled) {
|
||||
defaultsIndicatorsVisible = enabled;
|
||||
updateAllWidgetIndicators();
|
||||
}
|
||||
}
|
||||
|
||||
void KConfigDialogManagerPrivate::onWidgetModified()
|
||||
{
|
||||
const auto widget = qobject_cast<QWidget *>(q->sender());
|
||||
Q_ASSERT(widget);
|
||||
|
||||
const QLatin1String prefix("kcfg_");
|
||||
QString configId = widget->objectName();
|
||||
if (configId.startsWith(prefix)) {
|
||||
configId.remove(0, prefix.size());
|
||||
updateWidgetIndicator(configId, widget);
|
||||
} else {
|
||||
auto *parent = qobject_cast<QWidget *>(widget->parent());
|
||||
Q_ASSERT(parent);
|
||||
configId = parent->objectName();
|
||||
Q_ASSERT(configId.startsWith(prefix));
|
||||
configId.remove(0, prefix.size());
|
||||
updateWidgetIndicator(configId, parent);
|
||||
}
|
||||
|
||||
Q_EMIT q->widgetModified();
|
||||
}
|
||||
|
||||
void KConfigDialogManagerPrivate::updateWidgetIndicator(const QString &configId, QWidget *widget)
|
||||
{
|
||||
const auto item = m_conf->findItem(configId);
|
||||
Q_ASSERT(item);
|
||||
|
||||
const auto widgetValue = q->property(widget);
|
||||
const auto defaultValue = item->getDefault();
|
||||
|
||||
const auto defaulted = widgetValue == defaultValue;
|
||||
|
||||
if (allExclusiveGroupBoxes.contains(widget)) {
|
||||
const QList<QAbstractButton *> buttons = widget->findChildren<QAbstractButton *>();
|
||||
for (int i = 0; i < buttons.count(); i++) {
|
||||
const auto value = widgetValue.toInt() == i && !defaulted && defaultsIndicatorsVisible;
|
||||
buttons.at(i)->setProperty("_kde_highlight_neutral", value);
|
||||
buttons.at(i)->update();
|
||||
}
|
||||
} else {
|
||||
widget->setProperty("_kde_highlight_neutral", !defaulted && defaultsIndicatorsVisible);
|
||||
widget->update();
|
||||
}
|
||||
}
|
||||
|
||||
void KConfigDialogManagerPrivate::updateAllWidgetIndicators()
|
||||
{
|
||||
QHashIterator<QString, QWidget *> it(knownWidget);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
updateWidgetIndicator(it.key(), it.value());
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_kconfigdialogmanager.cpp"
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2003 Benjamin C Meyer <ben+kdelibs at meyerhome dot net>
|
||||
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KCONFIGDIALOGMANAGER_H
|
||||
#define KCONFIGDIALOGMANAGER_H
|
||||
|
||||
#include <kconfigwidgets_export.h>
|
||||
|
||||
#include <QHash>
|
||||
#include <QObject>
|
||||
#include <memory>
|
||||
|
||||
class KConfigDialogManagerPrivate;
|
||||
|
||||
class KCoreConfigSkeleton;
|
||||
class KConfigSkeleton;
|
||||
class KConfigSkeletonItem;
|
||||
class QWidget;
|
||||
|
||||
/**
|
||||
* @class KConfigDialogManager kconfigdialogmanager.h KConfigDialogManager
|
||||
*
|
||||
* @short Provides a means of automatically retrieving,
|
||||
* saving and resetting KConfigSkeleton based settings in a dialog.
|
||||
*
|
||||
* The KConfigDialogManager class provides a means of automatically
|
||||
* retrieving, saving and resetting basic settings.
|
||||
* It also can emit signals when settings have been changed
|
||||
* (settings were saved) or modified (the user changes a checkbox
|
||||
* from on to off).
|
||||
*
|
||||
* The object names of the widgets to be managed have to correspond to the names of the
|
||||
* configuration entries in the KConfigSkeleton object plus an additional
|
||||
* "kcfg_" prefix. For example a widget with the object name "kcfg_MyOption"
|
||||
* would be associated to the configuration entry "MyOption".
|
||||
*
|
||||
* The widget classes of Qt and KDE Frameworks are supported out of the box,
|
||||
* for other widgets see below:
|
||||
*
|
||||
* @par Using Custom Widgets
|
||||
* @parblock
|
||||
* Custom widget classes are supported if they have a Q_PROPERTY defined for the
|
||||
* property representing the value edited by the widget. By default the property
|
||||
* is used for which "USER true" is set. For using another property, see below.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* A class ColorEditWidget is used in the settings UI to select a color. The
|
||||
* color value is set and read as type QColor. For that it has a definition of
|
||||
* the value property similar to this:
|
||||
* \code
|
||||
* Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged USER true)
|
||||
* \endcode
|
||||
* And of course it has the definition and implementation of the respective
|
||||
* read & write methods and the notify signal.
|
||||
* This class then can be used directly with KConfigDialogManager and does not need
|
||||
* further setup.
|
||||
* @endparblock
|
||||
*
|
||||
* @par Using Other Properties than The USER Property
|
||||
* @parblock
|
||||
* To use a widget's property that is not the USER property, the property to use
|
||||
* can be selected by setting onto the widget instance a property with the key
|
||||
* "kcfg_property" and as the value the name of the property:
|
||||
* \code
|
||||
* ColorEditWidget *myWidget = new ColorEditWidget;
|
||||
* myWidget->setProperty("kcfg_property", QByteArray("redColorPart"));
|
||||
* \endcode
|
||||
* This selection of the property to use is just valid for this widget instance.
|
||||
* When using a UI file, the "kcfg_property" property can also be set using Qt Designer.
|
||||
* @endparblock
|
||||
*
|
||||
* @par Configuring Classes to use Other Properties Globally
|
||||
* @parblock
|
||||
* Alternatively a non-USER property can be defined for a widget class globally
|
||||
* by registering it for the class in the KConfigDialogManager::propertyMap().
|
||||
* This global registration has lower priority than any "kcfg_property" property
|
||||
* set on a class instance though, so the latter overrules this global setting.
|
||||
* Note: setting the property in the propertyMap affects any instances of that
|
||||
* widget class in the current application, so use only when needed and prefer
|
||||
* instead the "kcfg_property" property. Especially with software with many
|
||||
* libraries and 3rd-party plugins in one process there is a chance of
|
||||
* conflicting settings.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* If the ColorEditWidget has another property redColor defined by
|
||||
* \code
|
||||
* Q_PROPERTY(int redColorPart READ redColorPart WRITE setRedColorPart NOTIFY redColorPartChanged)
|
||||
* \endcode
|
||||
* and this one should be used in the settings, call somewhere in the code before
|
||||
* using the settings:
|
||||
* \code
|
||||
* KConfigDialogManager::propertyMap()->insert("ColorEditWidget", QByteArray("redColorPart"));
|
||||
* \endcode
|
||||
* @endparblock
|
||||
*
|
||||
* @par Using Different Signals than The NOTIFY Signal
|
||||
* @parblock
|
||||
* If some non-default signal should be used, e.g. because the property to use does not
|
||||
* have a NOTIFY setting, for a given widget instance the signal to use can be set
|
||||
* by a property with the key "kcfg_propertyNotify" and as the value the signal signature.
|
||||
* This will take priority over the signal noted by NOTIFY for the chosen property
|
||||
* as well as the content of KConfigDialogManager::changedMap(). Since 5.32.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* If for a class OtherColorEditWidget there was no NOTIFY set on the USER property,
|
||||
* but some signal colorSelected(QColor) defined which would be good enough to reflect
|
||||
* the settings change, defined by
|
||||
* \code
|
||||
* Q_PROPERTY(QColor color READ color WRITE setColor USER true)
|
||||
* Q_SIGNALS:
|
||||
* void colorSelected(const QColor &color);
|
||||
* \endcode
|
||||
* the signal to use would be defined by this:
|
||||
* \code
|
||||
* OtherColorEditWidget *myWidget = new OtherColorEditWidget;
|
||||
* myWidget->setProperty("kcfg_propertyNotify", QByteArray(SIGNAL(colorSelected(QColor))));
|
||||
* \endcode
|
||||
* @endparblock
|
||||
*
|
||||
* @author Benjamin C Meyer <ben+kdelibs at meyerhome dot net>
|
||||
* @author Waldo Bastian <bastian@kde.org>
|
||||
*/
|
||||
class KCONFIGWIDGETS_EXPORT KConfigDialogManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* One or more of the settings have been saved (such as when the user
|
||||
* clicks on the Apply button). This is only emitted by updateSettings()
|
||||
* whenever one or more setting were changed and consequently saved.
|
||||
*/
|
||||
void settingsChanged(); // clazy:exclude=overloaded-signal
|
||||
|
||||
/**
|
||||
* If retrieveSettings() was told to track changes then if
|
||||
* any known setting was changed this signal will be emitted. Note
|
||||
* that a settings can be modified several times and might go back to the
|
||||
* original saved state. hasChanged() will tell you if anything has
|
||||
* actually changed from the saved values.
|
||||
*/
|
||||
void widgetModified();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructor.
|
||||
* @param parent Dialog widget to manage
|
||||
* @param conf Object that contains settings
|
||||
*/
|
||||
KConfigDialogManager(QWidget *parent, KCoreConfigSkeleton *conf);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~KConfigDialogManager() override;
|
||||
|
||||
/**
|
||||
* Add additional widgets to manage
|
||||
* @param widget Additional widget to manage, including all its children
|
||||
*/
|
||||
void addWidget(QWidget *widget);
|
||||
|
||||
/**
|
||||
* Returns whether the current state of the known widgets are
|
||||
* different from the state in the config object.
|
||||
*/
|
||||
bool hasChanged() const;
|
||||
|
||||
/**
|
||||
* Returns whether the current state of the known widgets are
|
||||
* the same as the default state in the config object.
|
||||
*/
|
||||
bool isDefault() const;
|
||||
|
||||
/**
|
||||
* Retrieve the map between widgets class names and the
|
||||
* USER properties used for the configuration values.
|
||||
*/
|
||||
static QHash<QString, QByteArray> *propertyMap();
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* Traverse the specified widgets, saving the settings of all known
|
||||
* widgets in the settings object.
|
||||
*
|
||||
* Example use: User clicks Ok or Apply button in a configure dialog.
|
||||
*/
|
||||
void updateSettings();
|
||||
|
||||
/**
|
||||
* Traverse the specified widgets, sets the state of all known
|
||||
* widgets according to the state in the settings object.
|
||||
*
|
||||
* Example use: Initialisation of dialog.
|
||||
* Example use: User clicks Reset button in a configure dialog.
|
||||
*/
|
||||
void updateWidgets();
|
||||
|
||||
/**
|
||||
* Traverse the specified widgets, sets the state of all known
|
||||
* widgets according to the default state in the settings object.
|
||||
*
|
||||
* Example use: User clicks Defaults button in a configure dialog.
|
||||
*/
|
||||
void updateWidgetsDefault();
|
||||
|
||||
/**
|
||||
* Show or hide an indicator when settings have changed from their default value.
|
||||
* Update all widgets to show or hide the indicator accordingly.
|
||||
*
|
||||
* @since 5.73
|
||||
*/
|
||||
void setDefaultsIndicatorsVisible(bool enabled);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @param trackChanges - If any changes by the widgets should be tracked
|
||||
* set true. This causes the emitting the modified() signal when
|
||||
* something changes.
|
||||
* TODO: @return bool - True if any setting was changed from the default.
|
||||
*/
|
||||
void init(bool trackChanges);
|
||||
|
||||
/**
|
||||
* Recursive function that finds all known children.
|
||||
* Goes through the children of widget and if any are known and not being
|
||||
* ignored, stores them in currentGroup. Also checks if the widget
|
||||
* should be disabled because it is set immutable.
|
||||
* @param widget - Parent of the children to look at.
|
||||
* @param trackChanges - If true then tracks any changes to the children of
|
||||
* widget that are known.
|
||||
* @return bool - If a widget was set to something other than its default.
|
||||
*/
|
||||
bool parseChildren(const QWidget *widget, bool trackChanges);
|
||||
|
||||
/**
|
||||
* Finds the USER property name using Qt's MetaProperty system, and caches
|
||||
* it in the property map (the cache could be retrieved by propertyMap() ).
|
||||
*/
|
||||
QByteArray getUserProperty(const QWidget *widget) const;
|
||||
|
||||
/**
|
||||
* Find the property to use for a widget by querying the "kcfg_property"
|
||||
* property of the widget. Like a widget can use a property other than the
|
||||
* USER property.
|
||||
* @since 4.3
|
||||
*/
|
||||
QByteArray getCustomProperty(const QWidget *widget) const;
|
||||
|
||||
/**
|
||||
* Finds the changed signal of the USER property using Qt's MetaProperty system.
|
||||
* @since 5.32
|
||||
*/
|
||||
QByteArray getUserPropertyChangedSignal(const QWidget *widget) const;
|
||||
|
||||
/**
|
||||
* Find the changed signal of the property to use for a widget by querying
|
||||
* the "kcfg_propertyNotify" property of the widget. Like a widget can use a
|
||||
* property change signal other than the one for USER property, if there even is one.
|
||||
* @since 5.32
|
||||
*/
|
||||
QByteArray getCustomPropertyChangedSignal(const QWidget *widget) const;
|
||||
|
||||
/**
|
||||
* Set a property
|
||||
*/
|
||||
void setProperty(QWidget *w, const QVariant &v);
|
||||
|
||||
/**
|
||||
* Retrieve a property
|
||||
*/
|
||||
QVariant property(QWidget *w) const;
|
||||
|
||||
/**
|
||||
* Setup secondary widget properties
|
||||
*/
|
||||
void setupWidget(QWidget *widget, KConfigSkeletonItem *item);
|
||||
|
||||
/**
|
||||
* Initializes the property maps
|
||||
*/
|
||||
static void initMaps();
|
||||
|
||||
private:
|
||||
/**
|
||||
* KConfigDialogManager KConfigDialogManagerPrivate class.
|
||||
*/
|
||||
std::unique_ptr<KConfigDialogManagerPrivate> const d;
|
||||
friend class KConfigDialogManagerPrivate;
|
||||
|
||||
Q_DISABLE_COPY(KConfigDialogManager)
|
||||
Q_PRIVATE_SLOT(d, void onWidgetModified())
|
||||
};
|
||||
|
||||
#endif // KCONFIGDIALOGMANAGER_H
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2003 Benjamin C Meyer <ben+kdelibs at meyerhome dot net>
|
||||
SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
|
||||
SPDX-FileCopyrightText: 2017 Friedrich W. H. Kossebau <kossebau@kde.org>
|
||||
SPDX-FileCopyrightText: 2020 Kevin Ottens <kevin.ottens@enioka.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KCONFIGDIALOGMANAGER_P_H
|
||||
#define KCONFIGDIALOGMANAGER_P_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QSet>
|
||||
#include <QString>
|
||||
|
||||
class QWidget;
|
||||
class KConfigDialogManager;
|
||||
class KCoreConfigSkeleton;
|
||||
|
||||
class KConfigDialogManagerPrivate
|
||||
{
|
||||
public:
|
||||
KConfigDialogManagerPrivate(KConfigDialogManager *qq);
|
||||
|
||||
void setDefaultsIndicatorsVisible(bool enabled);
|
||||
|
||||
void onWidgetModified();
|
||||
void updateWidgetIndicator(const QString &configId, QWidget *widget);
|
||||
void updateAllWidgetIndicators();
|
||||
|
||||
public:
|
||||
KConfigDialogManager *const q;
|
||||
|
||||
/**
|
||||
* KConfigSkeleton object used to store settings
|
||||
*/
|
||||
KCoreConfigSkeleton *m_conf = nullptr;
|
||||
|
||||
/**
|
||||
* Dialog being managed
|
||||
*/
|
||||
QWidget *m_dialog = nullptr;
|
||||
|
||||
QHash<QString, QWidget *> knownWidget;
|
||||
QHash<QString, QWidget *> buddyWidget;
|
||||
QSet<QWidget *> allExclusiveGroupBoxes;
|
||||
bool insideGroupBox : 1;
|
||||
bool trackChanges : 1;
|
||||
bool defaultsIndicatorsVisible : 1;
|
||||
};
|
||||
|
||||
#endif // KCONFIGDIALOGMANAGER_P_H
|
||||
@@ -0,0 +1,47 @@
|
||||
|
||||
#include "kconfigviewstatesaver.h"
|
||||
|
||||
#include <KConfigGroup>
|
||||
|
||||
static const char selectionKey[] = "Selection";
|
||||
static const char expansionKey[] = "Expansion";
|
||||
static const char currentKey[] = "Current";
|
||||
static const char scrollStateHorizontalKey[] = "HorizontalScroll";
|
||||
static const char scrollStateVerticalKey[] = "VerticalScroll";
|
||||
|
||||
KConfigViewStateSaver::KConfigViewStateSaver(QObject *parent)
|
||||
: KViewStateSerializer(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void KConfigViewStateSaver::restoreState(const KConfigGroup &configGroup)
|
||||
{
|
||||
restoreSelection(configGroup.readEntry(selectionKey, QStringList()));
|
||||
restoreCurrentItem(configGroup.readEntry(currentKey, QString()));
|
||||
restoreExpanded(configGroup.readEntry(expansionKey, QStringList()));
|
||||
restoreScrollState(configGroup.readEntry(scrollStateVerticalKey, -1), configGroup.readEntry(scrollStateHorizontalKey, -1));
|
||||
|
||||
KViewStateSerializer::restoreState();
|
||||
}
|
||||
|
||||
void KConfigViewStateSaver::saveState(KConfigGroup &configGroup)
|
||||
{
|
||||
if (selectionModel()) {
|
||||
configGroup.writeEntry(selectionKey, selectionKeys());
|
||||
configGroup.writeEntry(currentKey, currentIndexKey());
|
||||
}
|
||||
|
||||
if (view()) {
|
||||
QStringList expansion = expansionKeys();
|
||||
|
||||
configGroup.writeEntry(expansionKey, expansion);
|
||||
}
|
||||
|
||||
if (view()) {
|
||||
QPair<int, int> _scrollState = scrollState();
|
||||
configGroup.writeEntry(scrollStateVerticalKey, _scrollState.first);
|
||||
configGroup.writeEntry(scrollStateHorizontalKey, _scrollState.second);
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_kconfigviewstatesaver.cpp"
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
#ifndef KCONFIGVIEWSTATESAVER_H
|
||||
#define KCONFIGVIEWSTATESAVER_H
|
||||
|
||||
#include <KViewStateSerializer>
|
||||
|
||||
#include "kconfigwidgets_export.h"
|
||||
|
||||
class KConfigGroup;
|
||||
|
||||
/**
|
||||
* @class KConfigViewStateSaver kconfigviewstatesaver.h KConfigViewStateSaver
|
||||
*
|
||||
* @brief Base class for saving and restoring state in QTreeViews and QItemSelectionModels using KConfig as storage
|
||||
*/
|
||||
class KCONFIGWIDGETS_EXPORT KConfigViewStateSaver : public KViewStateSerializer
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit KConfigViewStateSaver(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
Saves the state to the @p configGroup
|
||||
*/
|
||||
void saveState(KConfigGroup &configGroup);
|
||||
|
||||
/**
|
||||
Restores the state from the @p configGroup
|
||||
*/
|
||||
void restoreState(const KConfigGroup &configGroup);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,473 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2021 Felix Ernst <fe.a.ernst@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "khamburgermenu.h"
|
||||
#include "khamburgermenu_p.h"
|
||||
|
||||
#include "khamburgermenuhelpers_p.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
#include <QStyle>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
|
||||
#include <algorithm>
|
||||
#include <forward_list>
|
||||
#include <unordered_set>
|
||||
|
||||
KHamburgerMenu::KHamburgerMenu(QObject *parent)
|
||||
: QWidgetAction{parent}
|
||||
, d_ptr{new KHamburgerMenuPrivate(this)}
|
||||
{
|
||||
}
|
||||
|
||||
KHamburgerMenuPrivate::KHamburgerMenuPrivate(KHamburgerMenu *qq)
|
||||
: q_ptr{qq}
|
||||
, m_listeners{new ListenerContainer(this)}
|
||||
{
|
||||
q_ptr->setPriority(QAction::LowPriority);
|
||||
connect(q_ptr, &QAction::changed, this, &KHamburgerMenuPrivate::slotActionChanged);
|
||||
connect(q_ptr, &QAction::triggered, this, &KHamburgerMenuPrivate::slotActionTriggered);
|
||||
}
|
||||
|
||||
KHamburgerMenu::~KHamburgerMenu() = default;
|
||||
|
||||
KHamburgerMenuPrivate::~KHamburgerMenuPrivate() = default;
|
||||
|
||||
void KHamburgerMenu::setMenuBar(QMenuBar *menuBar)
|
||||
{
|
||||
Q_D(KHamburgerMenu);
|
||||
d->setMenuBar(menuBar);
|
||||
}
|
||||
|
||||
void KHamburgerMenuPrivate::setMenuBar(QMenuBar *menuBar)
|
||||
{
|
||||
if (m_menuBar) {
|
||||
m_menuBar->removeEventFilter(m_listeners->get<VisibilityChangesListener>());
|
||||
m_menuBar->removeEventFilter(m_listeners->get<AddOrRemoveActionListener>());
|
||||
}
|
||||
m_menuBar = menuBar;
|
||||
updateVisibility();
|
||||
if (m_menuBar) {
|
||||
m_menuBar->installEventFilter(m_listeners->get<VisibilityChangesListener>());
|
||||
m_menuBar->installEventFilter(m_listeners->get<AddOrRemoveActionListener>());
|
||||
}
|
||||
}
|
||||
|
||||
QMenuBar *KHamburgerMenu::menuBar() const
|
||||
{
|
||||
Q_D(const KHamburgerMenu);
|
||||
return d->menuBar();
|
||||
}
|
||||
|
||||
QMenuBar *KHamburgerMenuPrivate::menuBar() const
|
||||
{
|
||||
return m_menuBar;
|
||||
}
|
||||
|
||||
void KHamburgerMenu::setMenuBarAdvertised(bool advertise)
|
||||
{
|
||||
Q_D(KHamburgerMenu);
|
||||
d->setMenuBarAdvertised(advertise);
|
||||
}
|
||||
|
||||
void KHamburgerMenuPrivate::setMenuBarAdvertised(bool advertise)
|
||||
{
|
||||
m_advertiseMenuBar = advertise;
|
||||
}
|
||||
|
||||
bool KHamburgerMenu::menuBarAdvertised() const
|
||||
{
|
||||
Q_D(const KHamburgerMenu);
|
||||
return d->menuBarAdvertised();
|
||||
}
|
||||
|
||||
bool KHamburgerMenuPrivate::menuBarAdvertised() const
|
||||
{
|
||||
return m_advertiseMenuBar;
|
||||
}
|
||||
|
||||
void KHamburgerMenu::setShowMenuBarAction(QAction *showMenuBarAction)
|
||||
{
|
||||
Q_D(KHamburgerMenu);
|
||||
d->setShowMenuBarAction(showMenuBarAction);
|
||||
}
|
||||
|
||||
void KHamburgerMenuPrivate::setShowMenuBarAction(QAction *showMenuBarAction)
|
||||
{
|
||||
m_showMenuBarAction = showMenuBarAction;
|
||||
}
|
||||
|
||||
void KHamburgerMenu::addToMenu(QMenu *menu)
|
||||
{
|
||||
Q_D(KHamburgerMenu);
|
||||
d->insertIntoMenuBefore(menu, nullptr);
|
||||
}
|
||||
|
||||
void KHamburgerMenu::insertIntoMenuBefore(QMenu *menu, QAction *before)
|
||||
{
|
||||
Q_D(KHamburgerMenu);
|
||||
d->insertIntoMenuBefore(menu, before);
|
||||
}
|
||||
|
||||
void KHamburgerMenuPrivate::insertIntoMenuBefore(QMenu *menu, QAction *before)
|
||||
{
|
||||
Q_CHECK_PTR(menu);
|
||||
Q_Q(KHamburgerMenu);
|
||||
if (!m_menuAction) {
|
||||
m_menuAction = new QAction(this);
|
||||
m_menuAction->setText(i18nc("@action:inmenu General purpose menu", "&Menu"));
|
||||
m_menuAction->setIcon(q->icon());
|
||||
m_menuAction->setMenu(m_actualMenu.get());
|
||||
}
|
||||
updateVisibility(); // Sets the appropriate visibility of m_menuAction.
|
||||
|
||||
menu->insertAction(before, m_menuAction);
|
||||
connect(menu, &QMenu::aboutToShow, this, [this, menu, q]() {
|
||||
if (m_menuAction->isVisible()) {
|
||||
Q_EMIT q->aboutToShowMenu();
|
||||
hideActionsOf(menu);
|
||||
resetMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void KHamburgerMenu::hideActionsOf(QWidget *widget)
|
||||
{
|
||||
Q_D(KHamburgerMenu);
|
||||
d->hideActionsOf(widget);
|
||||
}
|
||||
|
||||
void KHamburgerMenuPrivate::hideActionsOf(QWidget *widget)
|
||||
{
|
||||
Q_CHECK_PTR(widget);
|
||||
m_widgetsWithActionsToBeHidden.remove(nullptr);
|
||||
if (listContainsWidget(m_widgetsWithActionsToBeHidden, widget)) {
|
||||
return;
|
||||
}
|
||||
m_widgetsWithActionsToBeHidden.emplace_front(QPointer<const QWidget>(widget));
|
||||
if (QMenu *menu = qobject_cast<QMenu *>(widget)) {
|
||||
// QMenus are normally hidden. This will avoid redundancy with their actions anyways.
|
||||
menu->installEventFilter(m_listeners->get<AddOrRemoveActionListener>());
|
||||
notifyMenuResetNeeded();
|
||||
} else {
|
||||
// Only avoid redundancy when the widget is visible.
|
||||
widget->installEventFilter(m_listeners->get<VisibleActionsChangeListener>());
|
||||
if (widget->isVisible()) {
|
||||
notifyMenuResetNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KHamburgerMenu::showActionsOf(QWidget *widget)
|
||||
{
|
||||
Q_D(KHamburgerMenu);
|
||||
d->showActionsOf(widget);
|
||||
}
|
||||
|
||||
void KHamburgerMenuPrivate::showActionsOf(QWidget *widget)
|
||||
{
|
||||
Q_CHECK_PTR(widget);
|
||||
m_widgetsWithActionsToBeHidden.remove(widget);
|
||||
widget->removeEventFilter(m_listeners->get<AddOrRemoveActionListener>());
|
||||
widget->removeEventFilter(m_listeners->get<VisibleActionsChangeListener>());
|
||||
if (isWidgetActuallyVisible(widget)) {
|
||||
notifyMenuResetNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
QWidget *KHamburgerMenu::createWidget(QWidget *parent)
|
||||
{
|
||||
Q_D(KHamburgerMenu);
|
||||
return d->createWidget(parent);
|
||||
}
|
||||
|
||||
QWidget *KHamburgerMenuPrivate::createWidget(QWidget *parent)
|
||||
{
|
||||
if (qobject_cast<QMenu *>(parent)) {
|
||||
qDebug(
|
||||
"Adding a KHamburgerMenu directly to a QMenu. "
|
||||
"This will look odd. Use addToMenu() instead.");
|
||||
}
|
||||
Q_Q(KHamburgerMenu);
|
||||
|
||||
auto toolButton = new QToolButton(parent);
|
||||
// Set appearance
|
||||
toolButton->setDefaultAction(q);
|
||||
toolButton->setMenu(m_actualMenu.get());
|
||||
toolButton->setAttribute(Qt::WidgetAttribute::WA_CustomWhatsThis);
|
||||
toolButton->setPopupMode(QToolButton::InstantPopup);
|
||||
updateButtonStyle(toolButton);
|
||||
if (const QToolBar *toolbar = qobject_cast<QToolBar *>(parent)) {
|
||||
connect(toolbar, &QToolBar::toolButtonStyleChanged, toolButton, &QToolButton::setToolButtonStyle);
|
||||
}
|
||||
|
||||
setToolButtonVisible(toolButton, !isMenuBarVisible(m_menuBar));
|
||||
|
||||
// Make sure the menu will be ready in time
|
||||
toolButton->installEventFilter(m_listeners->get<ButtonPressListener>());
|
||||
|
||||
hideActionsOf(parent);
|
||||
return toolButton;
|
||||
}
|
||||
|
||||
QAction *KHamburgerMenuPrivate::actionWithExclusivesFrom(QAction *from, QWidget *parent, std::unordered_set<const QAction *> &nonExclusives) const
|
||||
{
|
||||
Q_CHECK_PTR(from);
|
||||
if (nonExclusives.count(from) > 0) {
|
||||
return nullptr; // The action is non-exclusive/already visible elsewhere.
|
||||
}
|
||||
if (!from->menu() || from->menu()->isEmpty()) {
|
||||
return from; // The action is exclusive and doesn't have a menu.
|
||||
}
|
||||
std::unique_ptr<QAction> menuActionWithExclusives(new QAction(from->icon(), from->text(), parent));
|
||||
std::unique_ptr<QMenu> menuWithExclusives(new QMenu(parent));
|
||||
const auto fromMenuActions = from->menu()->actions();
|
||||
for (QAction *action : fromMenuActions) {
|
||||
QAction *actionWithExclusives = actionWithExclusivesFrom(action, menuWithExclusives.get(), nonExclusives);
|
||||
if (actionWithExclusives) {
|
||||
menuWithExclusives->addAction(actionWithExclusives);
|
||||
}
|
||||
}
|
||||
if (menuWithExclusives->isEmpty()) {
|
||||
return nullptr; // "from" has a menu that contains zero exclusive actions.
|
||||
// There is a chance that "from" is an exclusive action itself and should
|
||||
// therefore be returned instead but that is unlikely for an action that has a menu().
|
||||
// This fringe case is the only one that can't be correctly covered because we can
|
||||
// not know or assume that activating the action does something or if it is nothing
|
||||
// but a container for a menu.
|
||||
}
|
||||
menuActionWithExclusives->setMenu(menuWithExclusives.release());
|
||||
return menuActionWithExclusives.release();
|
||||
}
|
||||
|
||||
std::unique_ptr<QMenu> KHamburgerMenuPrivate::newMenu()
|
||||
{
|
||||
std::unique_ptr<QMenu> menu(new QMenu());
|
||||
Q_Q(const KHamburgerMenu);
|
||||
|
||||
// Make sure we notice if the q->menu() is changed or replaced in the future.
|
||||
if (q->menu() != m_lastUsedMenu) {
|
||||
q->menu()->installEventFilter(m_listeners->get<AddOrRemoveActionListener>());
|
||||
|
||||
if (m_lastUsedMenu && !listContainsWidget(m_widgetsWithActionsToBeHidden, m_lastUsedMenu)) {
|
||||
m_lastUsedMenu->removeEventFilter(m_listeners->get<AddOrRemoveActionListener>());
|
||||
}
|
||||
m_lastUsedMenu = q->menu();
|
||||
}
|
||||
|
||||
if (!q->menu() && !m_menuBar) {
|
||||
return menu; // empty menu
|
||||
}
|
||||
|
||||
if (!q->menu()) {
|
||||
// We have nothing else to work with so let's just add the menuBar contents.
|
||||
const auto menuBarActions = m_menuBar->actions();
|
||||
for (QAction *menuAction : menuBarActions) {
|
||||
menu->addAction(menuAction);
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
// Collect actions which shouldn't be added to the menu
|
||||
std::unordered_set<const QAction *> visibleActions;
|
||||
m_widgetsWithActionsToBeHidden.remove(nullptr);
|
||||
for (const QWidget *widget : m_widgetsWithActionsToBeHidden) {
|
||||
if (qobject_cast<const QMenu *>(widget) || isWidgetActuallyVisible(widget)) {
|
||||
// avoid redundancy with menus even when they are not actually visible.
|
||||
visibleActions.reserve(visibleActions.size() + widget->actions().size());
|
||||
const auto widgetActions = widget->actions();
|
||||
for (QAction *action : widgetActions) {
|
||||
visibleActions.insert(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Populate the menu
|
||||
const auto menuActions = q->menu()->actions();
|
||||
for (QAction *action : menuActions) {
|
||||
if (visibleActions.count(action) == 0) {
|
||||
menu->addAction(action);
|
||||
visibleActions.insert(action);
|
||||
}
|
||||
}
|
||||
// Add the last two menu actions
|
||||
if (m_menuBar) {
|
||||
connect(menu.get(), &QMenu::aboutToShow, this, [this]() {
|
||||
if (m_menuBar->actions().last()->icon().isNull()) {
|
||||
m_helpIconIsSet = false;
|
||||
m_menuBar->actions().last()->setIcon(QIcon::fromTheme(QStringLiteral("help-contents"))); // set "Help" menu icon
|
||||
} else {
|
||||
m_helpIconIsSet = true; // if the "Help" icon was set by the application, we want to leave it untouched
|
||||
}
|
||||
});
|
||||
connect(menu.get(), &QMenu::aboutToHide, this, [this]() {
|
||||
if (m_menuBar->actions().last()->icon().name() == QStringLiteral("help-contents") && !m_helpIconIsSet) {
|
||||
m_menuBar->actions().last()->setIcon(QIcon());
|
||||
}
|
||||
});
|
||||
menu->addAction(m_menuBar->actions().last()); // add "Help" menu
|
||||
visibleActions.insert(m_menuBar->actions().last());
|
||||
if (m_advertiseMenuBar) {
|
||||
menu->addSeparator();
|
||||
m_menuBarAdvertisementMenu = newMenuBarAdvertisementMenu(visibleActions);
|
||||
menu->addAction(m_menuBarAdvertisementMenu->menuAction());
|
||||
}
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
std::unique_ptr<QMenu> KHamburgerMenuPrivate::newMenuBarAdvertisementMenu(std::unordered_set<const QAction *> &visibleActions)
|
||||
{
|
||||
std::unique_ptr<QMenu> advertiseMenuBarMenu(new QMenu());
|
||||
m_showMenuBarWithAllActionsText = i18nc("@action:inmenu A menu item that advertises and enables the menubar", "Show &Menubar with All Actions");
|
||||
connect(advertiseMenuBarMenu.get(), &QMenu::aboutToShow, this, [this]() {
|
||||
if (m_showMenuBarAction) {
|
||||
m_showMenuBarText = m_showMenuBarAction->text();
|
||||
m_showMenuBarAction->setText(m_showMenuBarWithAllActionsText);
|
||||
}
|
||||
});
|
||||
connect(advertiseMenuBarMenu.get(), &QMenu::aboutToHide, this, [this]() {
|
||||
if (m_showMenuBarAction && m_showMenuBarAction->text() == m_showMenuBarWithAllActionsText) {
|
||||
m_showMenuBarAction->setText(m_showMenuBarText);
|
||||
}
|
||||
});
|
||||
if (m_showMenuBarAction) {
|
||||
advertiseMenuBarMenu->addAction(m_showMenuBarAction);
|
||||
visibleActions.insert(m_showMenuBarAction);
|
||||
}
|
||||
QAction *section = advertiseMenuBarMenu->addSeparator();
|
||||
|
||||
const auto menuBarActions = m_menuBar->actions();
|
||||
for (QAction *menuAction : menuBarActions) {
|
||||
QAction *menuActionWithExclusives = actionWithExclusivesFrom(menuAction, advertiseMenuBarMenu.get(), visibleActions);
|
||||
if (menuActionWithExclusives) {
|
||||
advertiseMenuBarMenu->addAction(menuActionWithExclusives);
|
||||
}
|
||||
}
|
||||
advertiseMenuBarMenu->setIcon(QIcon::fromTheme(QStringLiteral("view-more-symbolic")));
|
||||
advertiseMenuBarMenu->setTitle(i18nc("@action:inmenu A menu text advertising its contents (more features).", "More"));
|
||||
section->setText(i18nc("@action:inmenu A section heading advertising the contents of the menu bar", "More Actions"));
|
||||
return advertiseMenuBarMenu;
|
||||
}
|
||||
|
||||
void KHamburgerMenuPrivate::resetMenu()
|
||||
{
|
||||
Q_Q(KHamburgerMenu);
|
||||
if (!m_menuResetNeeded && m_actualMenu && m_lastUsedMenu == q->menu()) {
|
||||
return;
|
||||
}
|
||||
m_menuResetNeeded = false;
|
||||
|
||||
m_actualMenu = newMenu();
|
||||
|
||||
const auto createdWidgets = q->createdWidgets();
|
||||
for (auto widget : createdWidgets) {
|
||||
static_cast<QToolButton *>(widget)->setMenu(m_actualMenu.get());
|
||||
}
|
||||
if (m_menuAction) {
|
||||
m_menuAction->setMenu(m_actualMenu.get());
|
||||
}
|
||||
}
|
||||
|
||||
void KHamburgerMenuPrivate::updateVisibility()
|
||||
{
|
||||
Q_Q(KHamburgerMenu);
|
||||
/** The visibility of KHamburgerMenu should be opposite to the visibility of m_menuBar.
|
||||
* Exception: We only consider a visible m_menuBar as actually visible if it is not a native
|
||||
* menu bar because native menu bars can come in many shapes and sizes which don't necessarily
|
||||
* have the same usability benefits as a traditional in-window menu bar.
|
||||
* KDE applications normally allow the user to remove any actions from their toolbar(s) anyway. */
|
||||
const bool menuBarVisible = isMenuBarVisible(m_menuBar);
|
||||
|
||||
const auto createdWidgets = q->createdWidgets();
|
||||
for (auto widget : createdWidgets) {
|
||||
setToolButtonVisible(widget, !menuBarVisible);
|
||||
}
|
||||
|
||||
if (!m_menuAction) {
|
||||
if (menuBarVisible && m_actualMenu) {
|
||||
m_actualMenu.release()->deleteLater(); // might as well free up some memory
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// The m_menuAction acts as a fallback if both the m_menuBar and all createdWidgets() on the UI
|
||||
// are currently hidden. Only then should the m_menuAction ever be visible in a QMenu.
|
||||
if (menuBarVisible || (m_menuBar && m_menuBar->isNativeMenuBar()) // See [1] below.
|
||||
|| std::any_of(createdWidgets.cbegin(), createdWidgets.cend(), isWidgetActuallyVisible)) {
|
||||
m_menuAction->setVisible(false);
|
||||
return;
|
||||
}
|
||||
m_menuAction->setVisible(true);
|
||||
|
||||
// [1] While the m_menuAction can be used as a normal menu by users that don't mind invoking a
|
||||
// QMenu to access any menu actions, its primary use really is that of a fallback.
|
||||
// Therefore the existence of a native menu bar (no matter what shape or size it might have)
|
||||
// is enough reason for us to hide m_menuAction.
|
||||
}
|
||||
|
||||
void KHamburgerMenuPrivate::slotActionChanged()
|
||||
{
|
||||
Q_Q(KHamburgerMenu);
|
||||
const auto createdWidgets = q->createdWidgets();
|
||||
for (auto widget : createdWidgets) {
|
||||
auto toolButton = static_cast<QToolButton *>(widget);
|
||||
updateButtonStyle(toolButton);
|
||||
}
|
||||
}
|
||||
|
||||
void KHamburgerMenuPrivate::slotActionTriggered()
|
||||
{
|
||||
if (isMenuBarVisible(m_menuBar)) {
|
||||
const auto menuBarActions = m_menuBar->actions();
|
||||
for (const auto action : menuBarActions) {
|
||||
if (action->isEnabled() && !action->isSeparator()) {
|
||||
m_menuBar->setActiveAction(m_menuBar->actions().constFirst());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Q_Q(KHamburgerMenu);
|
||||
const auto createdWidgets = q->createdWidgets();
|
||||
for (auto widget : createdWidgets) {
|
||||
if (isWidgetActuallyVisible(widget) && widget->isActiveWindow()) {
|
||||
auto toolButton = static_cast<QToolButton *>(widget);
|
||||
m_listeners->get<ButtonPressListener>()->prepareHamburgerButtonForPress(toolButton);
|
||||
toolButton->pressed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Q_EMIT q->aboutToShowMenu();
|
||||
resetMenu();
|
||||
prepareParentlessMenuForShowing(m_actualMenu.get(), nullptr);
|
||||
m_actualMenu->popup(QCursor::pos());
|
||||
}
|
||||
|
||||
void KHamburgerMenuPrivate::updateButtonStyle(QToolButton *hamburgerMenuButton) const
|
||||
{
|
||||
Q_Q(const KHamburgerMenu);
|
||||
Qt::ToolButtonStyle buttonStyle = Qt::ToolButtonFollowStyle;
|
||||
if (QToolBar *toolbar = qobject_cast<QToolBar *>(hamburgerMenuButton->parent())) {
|
||||
buttonStyle = toolbar->toolButtonStyle();
|
||||
}
|
||||
if (buttonStyle == Qt::ToolButtonFollowStyle) {
|
||||
buttonStyle = static_cast<Qt::ToolButtonStyle>(hamburgerMenuButton->style()->styleHint(QStyle::SH_ToolButtonStyle));
|
||||
}
|
||||
if (buttonStyle == Qt::ToolButtonTextBesideIcon && q->priority() < QAction::NormalPriority) {
|
||||
hamburgerMenuButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
||||
} else {
|
||||
hamburgerMenuButton->setToolButtonStyle(buttonStyle);
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_khamburgermenu.cpp"
|
||||
#include "moc_khamburgermenu_p.cpp"
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2021 Felix Ernst <fe.a.ernst@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef KHamburgerMenu_H
|
||||
#define KHamburgerMenu_H
|
||||
|
||||
#include <kconfigwidgets_export.h>
|
||||
|
||||
#include <QWidgetAction>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class KHamburgerMenuPrivate;
|
||||
|
||||
class QMenuBar;
|
||||
|
||||
/**
|
||||
* @class KHamburgerMenu khamburgermenu.h KHamburgerMenu
|
||||
*
|
||||
* @short A menu that substitutes a menu bar when necessary
|
||||
*
|
||||
* Allowing users to toggle the visibility of the menu bar and/or toolbars,
|
||||
* while pretty/"simple by default", can lead to various grave usability issues.
|
||||
* This class makes it easy to prevent all of them.
|
||||
*
|
||||
* Simply add a KHamburgerMenu to your UI (typically to a QToolBar) and make
|
||||
* it aware of a QMenuBar like this:
|
||||
*
|
||||
* \code
|
||||
* auto hamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, actionCollection());
|
||||
* toolBar()->addAction(hamburgerMenu);
|
||||
* hamburgerMenu->hideActionsOf(toolBar());
|
||||
* // after the QMenuBar has been initialised
|
||||
* hamburgerMenu->setMenuBar(menuBar()); // line not needed if there is no QMenuBar
|
||||
* \endcode
|
||||
*
|
||||
* The added menu button will only be visible when the QMenuBar is hidden.
|
||||
* With this minimal initialisation it will contain the contents of the menu bar.
|
||||
* If a user (also) hides the container the KHamburgerMenu was added to they
|
||||
* might find themselves without a way to get a menu back. To prevent this, it is
|
||||
* recommended to add the hamburgerMenu to prominent context menus like the one
|
||||
* of your central widget preferably at the first position. Simply write:
|
||||
*
|
||||
* \code
|
||||
* hamburgerMenu->addActionToMenu(contextMenu);
|
||||
* \endcode
|
||||
*
|
||||
* The added menu will only be visible if the QMenuBar is hidden and the
|
||||
* hamburgerMenu->createdWidgets() are all invisible to the user.
|
||||
*
|
||||
* **Populating the KHamburgerMenu**
|
||||
*
|
||||
* This is easy:
|
||||
*
|
||||
* \code
|
||||
* auto menu = new QMenu(this);
|
||||
* menu->addAction(action);
|
||||
* // Add actions, separators, etc. like usual.
|
||||
* hamburgerMenu->setMenu(menu);
|
||||
* \endcode
|
||||
*
|
||||
* You probably do not want this to happen on startup. Therefore KHamburgerMenu
|
||||
* provides the signal aboutToShowMenu that you can connect to a function containing
|
||||
* the previous statements.
|
||||
*
|
||||
* \code
|
||||
* connect(hamburgerMenu, &KHamburgerMenu::aboutToShowMenu,
|
||||
* this, &MainWindow::updateHamburgerMenu);
|
||||
* // You might want to disconnect the signal after initial creation if the contents never change.
|
||||
* \endcode
|
||||
*
|
||||
* **Deciding what to put on the hamburger menu**
|
||||
*
|
||||
* 1. Be sure to add all of the most important actions. Actions which are already
|
||||
* visible on QToolBars, etc. will not show up in the hamburgerMenu. To manage
|
||||
* which containers KHamburgerMenu should watch for redundancy use
|
||||
* hideActionsOf(QWidget *) and showActionsOf(QWidget *).
|
||||
* When a KHamburgerMenu is added to a widget, hideActionsOf(that widget)
|
||||
* will automatically be called.
|
||||
* 2. Do not worry about adding all actions the application has to offer.
|
||||
* The KHamburgerMenu will automatically have a section advertising excluded
|
||||
* actions which can be found in the QMenuBar. There will also be the
|
||||
* showMenuBarAction if you set it with setShowMenuBarAction().
|
||||
* 3. Do not worry about the help menu. KHamburgerMenu will automatically contain
|
||||
* a help menu as the second to last item (if you set a QMenuBar which is
|
||||
* expected to have the help menu as the last action).
|
||||
*
|
||||
* **Open menu by shortcut**
|
||||
*
|
||||
* For visually impaired users it is important to have a consistent way to open a general-purpose
|
||||
* menu. Triggering the keyboard shortcut bound to KHamburgerMenu will always open a menu.
|
||||
* - If setMenuBar() was called and that menu bar is visible, the shortcut will open the first menu
|
||||
* of that menu bar.
|
||||
* - Otherwise, if there is a visible KHamburgerMenu button in the user interface, that menu will
|
||||
* open.
|
||||
* - Otherwise, KHamburgerMenu's menu will open at the mouse cursor position.
|
||||
*
|
||||
* @since 5.81
|
||||
*/
|
||||
class KCONFIGWIDGETS_EXPORT KHamburgerMenu : public QWidgetAction
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DECLARE_PRIVATE(KHamburgerMenu)
|
||||
|
||||
public:
|
||||
explicit KHamburgerMenu(QObject *parent);
|
||||
|
||||
~KHamburgerMenu() override;
|
||||
|
||||
/**
|
||||
* Associates this KHamburgerMenu with @p menuBar. The KHamburgerMenu will from now
|
||||
* on only be visible when @p menuBar is hidden.
|
||||
* (Menu bars with QMenuBar::isNativeMenuBar() == true are considered hidden.)
|
||||
*
|
||||
* Furthermore the KHamburgerMenu will have the help menu from the @p menuBar added
|
||||
* at the end. There will also be a special sub-menu advertising actions which are
|
||||
* only available in the menu bar unless advertiseMenuBar(false) was called.
|
||||
*
|
||||
* @param menuBar The QMenuBar the KHamburgerMenu should be associated with.
|
||||
* This can be set to nullptr.
|
||||
*/
|
||||
void setMenuBar(QMenuBar *menuBar);
|
||||
|
||||
/** @see setMenuBar() */
|
||||
QMenuBar *menuBar() const;
|
||||
|
||||
/**
|
||||
* By default the KHamburgerMenu contains a special sub-menu that advertises actions
|
||||
* of the menu bar which would otherwise not be visible or discoverable for the user.
|
||||
* This method removes or re-adds that sub-menu.
|
||||
*
|
||||
* @param advertise sets whether the special sub-menu that advertises menu bar only
|
||||
* actions should exist.
|
||||
*/
|
||||
void setMenuBarAdvertised(bool advertise);
|
||||
|
||||
/** @see setMenuBarAdvertised() */
|
||||
bool menuBarAdvertised() const;
|
||||
|
||||
/**
|
||||
* Adds the @p showMenuBarAction as the first item of the sub-menu which advertises actions
|
||||
* from the menu bar.
|
||||
* @see setMenuBarAdvertised()
|
||||
*/
|
||||
void setShowMenuBarAction(QAction *showMenuBarAction);
|
||||
|
||||
/**
|
||||
* Adds this KHamburgerMenu to @p menu.
|
||||
* It will only be visible in the menu if both the menu bar and all of this
|
||||
* QWidgetAction's createdWidgets() are invisible.
|
||||
* If it is visible in the menu, then opening the menu emits the aboutToShowMenu
|
||||
* signal.
|
||||
*
|
||||
* @param menu The menu this KHamburgerMenu is supposed to appear in.
|
||||
*/
|
||||
void addToMenu(QMenu *menu);
|
||||
|
||||
/**
|
||||
* Inserts this KHamburgerMenu to @p menu's list of actions, before the action @p before.
|
||||
* It will only be visible in the menu if both the menu bar and all of this
|
||||
* QWidgetAction's createdWidgets() are invisible.
|
||||
* If it is visible in the menu, then opening the menu emits the aboutToShowMenu
|
||||
* signal.
|
||||
*
|
||||
* @param before The action before which KHamburgerMenu should be inserted.
|
||||
* @param menu The menu this KHamburgerMenu is supposed to appear in.
|
||||
*
|
||||
* @see QWidget::insertAction(), QMenu::insertMenu()
|
||||
*
|
||||
* @since 5.99
|
||||
*/
|
||||
void insertIntoMenuBefore(QMenu *menu, QAction *before);
|
||||
|
||||
/**
|
||||
* Adds @p widget to a list of widgets that should be monitored for their actions().
|
||||
* If the widget is a QMenu, its actions will be treated as known to the user.
|
||||
* If the widget isn't a QMenu, its actions will only be treated as known to the user
|
||||
* when the widget is actually visible.
|
||||
* @param widget A widget that contains actions which should not show up in the
|
||||
* KHamburgerMenu redundantly.
|
||||
*/
|
||||
void hideActionsOf(QWidget *widget);
|
||||
|
||||
/**
|
||||
* Reverses a hideActionsOf(widget) method call.
|
||||
* @see hideActionsOf()
|
||||
*/
|
||||
void showActionsOf(QWidget *widget);
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* This signal is emitted when a hamburger menu button is about to be pressed down.
|
||||
* It is also emitted when a QMenu that contains a visible KHamburgerMenu emits
|
||||
* QMenu::aboutToShow.
|
||||
*/
|
||||
void aboutToShowMenu();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @see QWidgetAction::createWidget
|
||||
*/
|
||||
QWidget *createWidget(QWidget *parent) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<KHamburgerMenuPrivate> const d_ptr;
|
||||
};
|
||||
|
||||
#endif // KHamburgerMenu_H
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2021 Felix Ernst <fe.a.ernst@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef KHamburgerMenu_P_H
|
||||
#define KHamburgerMenu_P_H
|
||||
|
||||
#include "khamburgermenu.h"
|
||||
|
||||
#include <QWidgetAction>
|
||||
|
||||
#include <QPointer>
|
||||
#include <QScopedPointer>
|
||||
|
||||
#include <forward_list>
|
||||
#include <unordered_set>
|
||||
|
||||
class ListenerContainer;
|
||||
|
||||
class QMenu;
|
||||
class QMenuBar;
|
||||
class QToolButton;
|
||||
|
||||
/**
|
||||
* The private class of KHamburgerMenu used for the PIMPL idiom.
|
||||
* \internal
|
||||
*/
|
||||
class KHamburgerMenuPrivate : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DECLARE_PUBLIC(KHamburgerMenu)
|
||||
|
||||
public:
|
||||
explicit KHamburgerMenuPrivate(KHamburgerMenu *qq);
|
||||
|
||||
~KHamburgerMenuPrivate() override;
|
||||
|
||||
/** @see KHamburgerMenu::setMenuBar() */
|
||||
void setMenuBar(QMenuBar *menuBar);
|
||||
|
||||
/** @see KHamburgerMenu::setMenuBar() */
|
||||
QMenuBar *menuBar() const;
|
||||
|
||||
/** @see KHamburgerMenu::setMenuBarAdvertised() */
|
||||
void setMenuBarAdvertised(bool advertise);
|
||||
|
||||
/** @see KHamburgerMenu::setMenuBarAdvertised() */
|
||||
bool menuBarAdvertised() const;
|
||||
|
||||
/** @see KHamburgerMenu::setShowMenuBarAction() */
|
||||
void setShowMenuBarAction(QAction *showMenuBarAction);
|
||||
|
||||
/** @see KHamburgerMenu::insertIntoMenuBefore() */
|
||||
void insertIntoMenuBefore(QMenu *menu, QAction *before);
|
||||
|
||||
/** @see KHamburgerMenu::hideActionsOf() */
|
||||
void hideActionsOf(QWidget *widget);
|
||||
|
||||
/** @see KHamburgerMenu::showActionsOf() */
|
||||
void showActionsOf(QWidget *widget);
|
||||
|
||||
/** @see KHamburgerMenu::createWidget() */
|
||||
QWidget *createWidget(QWidget *parent);
|
||||
|
||||
/**
|
||||
* This method only returns exclusive actions. The idea is to remove any @p nonExclusives
|
||||
* from @p from and all of its sub-menus. This means a copy of @p from is created when
|
||||
* necessary that has a menu() that only contains the exclusive actions from @p from.
|
||||
* @param from the action this method extracts the exclusive actions from.
|
||||
* @param parent the widget that is to become the parent if a copy of @p from needs
|
||||
* to be created.
|
||||
* @param nonExclusives the actions which will not be anywhere within the returned action.
|
||||
* @return either nullptr, @p from unchanged or a copy of @p from without the @p nonExclusives.
|
||||
* In the last case, the caller gets ownership of this new copy with parent @p parent.
|
||||
*/
|
||||
QAction *actionWithExclusivesFrom(QAction *from, QWidget *parent, std::unordered_set<const QAction *> &nonExclusives) const;
|
||||
|
||||
/**
|
||||
* @return a new menu with all actions from KHamburgerMenu::menu() which aren't
|
||||
* exempted from being displayed (@see hideActionsOf()).
|
||||
* Next adds the help menu.
|
||||
* At last adds a special sub-menu by calling newMenuBarAdvertisementMenu() if this step
|
||||
* was not explicitly set to be skipped (@see KHamburgerMenu::setMenuBarAdvertised()).
|
||||
*/
|
||||
std::unique_ptr<QMenu> newMenu();
|
||||
|
||||
/**
|
||||
* @return a special sub-menu that advertises actions of the menu bar which would otherwise
|
||||
* not be visible or discoverable for the user
|
||||
* @see KHamburgerMenu::setMenuBarAdvertised()
|
||||
*/
|
||||
std::unique_ptr<QMenu> newMenuBarAdvertisementMenu(std::unordered_set<const QAction *> &visibleActions);
|
||||
|
||||
/** @see resetMenu() */
|
||||
inline void notifyMenuResetNeeded()
|
||||
{
|
||||
m_menuResetNeeded = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing if m_menuResetNeeded is false.
|
||||
* Otherwise deletes m_actualMenu and creates a newMenu() in its place. This new menu
|
||||
* is then set to be used whenever the hamburger menu is opened.
|
||||
* @see newMenu()
|
||||
*/
|
||||
void resetMenu();
|
||||
|
||||
/**
|
||||
* Sets the correct visibility for KHamburgerMenu buttons based on the visibility of the
|
||||
* menu bar (@see setMenuBar()).
|
||||
* Also sets the correct visibility of the menu item (@see addToMenu()) based on the visibility
|
||||
* of the menu bar and of the KHamburgerMenu buttons.
|
||||
*/
|
||||
void updateVisibility();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Makes the KHamburgerMenu buttons change style just like other toolbuttons would
|
||||
* when their associated action changes.
|
||||
*/
|
||||
void slotActionChanged();
|
||||
|
||||
/**
|
||||
* When m_menuBar->isVisible(): Opens the first menu of the menu bar.
|
||||
* Otherwise it acts the same way as clicking on a visible KHamburgerMenu button.
|
||||
* If no KHamburgerMenu button is visible, open its menu anyway.
|
||||
*
|
||||
* @note The action triggered signal is not emitted when a normal button that contains a menu
|
||||
* is pressed. So this slot will effectively only be called by keyboard shortcut.
|
||||
*/
|
||||
void slotActionTriggered();
|
||||
|
||||
/**
|
||||
* Updates the style of @p hamburgerMenuButton based on its parent's style and q->priority().
|
||||
*/
|
||||
void updateButtonStyle(QToolButton *hamburgerMenuButton) const;
|
||||
|
||||
public:
|
||||
KHamburgerMenu *const q_ptr;
|
||||
|
||||
protected:
|
||||
/** @see newMenu(). Do not confuse this menu with QAction::menu(). */
|
||||
std::unique_ptr<QMenu> m_actualMenu;
|
||||
/** @see KHamburgerMenu::setMenuBarAdvertised() */
|
||||
bool m_advertiseMenuBar = true;
|
||||
/** @see newMenuBarAdvertisementMenu() */
|
||||
std::unique_ptr<QMenu> m_menuBarAdvertisementMenu;
|
||||
/** @see KHamburgerMenu::hideActionsOf() */
|
||||
std::forward_list<QPointer<const QWidget>> m_widgetsWithActionsToBeHidden;
|
||||
/** The menu that was used as a base when newMenu() was last called. With this we
|
||||
* make sure to reset the m_actualMenu if the q->menu() has been changed or replaced. */
|
||||
QPointer<QMenu> m_lastUsedMenu;
|
||||
/** Makes sure there are no redundant event listeners of the same class. */
|
||||
std::unique_ptr<ListenerContainer> m_listeners;
|
||||
/** The action that is put into QMenus to represent the KHamburgerMenu.
|
||||
* @see KHamburgerMenu::addToMenu() */
|
||||
QPointer<QAction> m_menuAction;
|
||||
/** @see KHamburgerMenu::setMenuBar() */
|
||||
QPointer<QMenuBar> m_menuBar;
|
||||
/** @see resetMenu() */
|
||||
bool m_menuResetNeeded = false;
|
||||
/** @see KHamburgerMenu::setShowMenuBarAction */
|
||||
QPointer<QAction> m_showMenuBarAction;
|
||||
/** Keeps track of changes to the "Show Menubar" button text. */
|
||||
QString m_showMenuBarText;
|
||||
QString m_showMenuBarWithAllActionsText;
|
||||
/** Identifies if the application set an icon for "Help" menu. */
|
||||
bool m_helpIconIsSet = false;
|
||||
};
|
||||
|
||||
#endif // KHamburgerMenu_P_H
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2021 Felix Ernst <fe.a.ernst@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "khamburgermenuhelpers_p.h"
|
||||
|
||||
#include "khamburgermenu.h"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QGuiApplication>
|
||||
#include <QMenu>
|
||||
#include <QMenuBar>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
#include <QWidget>
|
||||
#include <QWindow>
|
||||
|
||||
ListenerContainer::ListenerContainer(KHamburgerMenuPrivate *hamburgerMenuPrivate)
|
||||
: QObject{hamburgerMenuPrivate}
|
||||
, m_listeners{std::vector<std::unique_ptr<QObject>>(4)}
|
||||
{
|
||||
}
|
||||
|
||||
ListenerContainer::~ListenerContainer()
|
||||
{
|
||||
}
|
||||
|
||||
bool AddOrRemoveActionListener::eventFilter(QObject * /*watched*/, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::ActionAdded || event->type() == QEvent::ActionRemoved) {
|
||||
static_cast<KHamburgerMenuPrivate *>(parent())->notifyMenuResetNeeded();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ButtonPressListener::prepareHamburgerButtonForPress(QObject *button)
|
||||
{
|
||||
Q_ASSERT(qobject_cast<QToolButton *>(button));
|
||||
|
||||
auto hamburgerMenuPrivate = static_cast<KHamburgerMenuPrivate *>(parent());
|
||||
auto q = static_cast<KHamburgerMenu *>(hamburgerMenuPrivate->q_ptr);
|
||||
Q_EMIT q->aboutToShowMenu();
|
||||
hamburgerMenuPrivate->resetMenu(); // This menu never has a parent which can be
|
||||
// problematic because it can lead to situations in which the QMenu itself is
|
||||
// treated like its own window.
|
||||
// To avoid this we set a sane transientParent() now even if it already has one
|
||||
// because the menu might be opened from another window this time.
|
||||
const auto watchedButton = static_cast<QToolButton *>(button);
|
||||
auto menu = watchedButton->menu();
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
prepareParentlessMenuForShowing(menu, watchedButton);
|
||||
}
|
||||
|
||||
bool ButtonPressListener::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::KeyPress || event->type() == QEvent::MouseButtonPress) {
|
||||
prepareHamburgerButtonForPress(watched);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VisibleActionsChangeListener::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Show || event->type() == QEvent::Hide) {
|
||||
if (!event->spontaneous()) {
|
||||
static_cast<KHamburgerMenuPrivate *>(parent())->notifyMenuResetNeeded();
|
||||
}
|
||||
} else if (event->type() == QEvent::ActionAdded || event->type() == QEvent::ActionRemoved) {
|
||||
Q_ASSERT_X(qobject_cast<QWidget *>(watched), "VisibileActionsChangeListener", "The watched QObject is expected to be a QWidget.");
|
||||
if (static_cast<QWidget *>(watched)->isVisible()) {
|
||||
static_cast<KHamburgerMenuPrivate *>(parent())->notifyMenuResetNeeded();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VisibilityChangesListener::eventFilter(QObject * /*watched*/, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Show || event->type() == QEvent::Hide) {
|
||||
if (!event->spontaneous()) {
|
||||
static_cast<KHamburgerMenuPrivate *>(parent())->updateVisibility();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isMenuBarVisible(const QMenuBar *menuBar)
|
||||
{
|
||||
return menuBar && (menuBar->isVisible() && !menuBar->isNativeMenuBar());
|
||||
}
|
||||
|
||||
bool isWidgetActuallyVisible(const QWidget *widget)
|
||||
{
|
||||
Q_CHECK_PTR(widget);
|
||||
if (widget->width() < 1 || widget->height() < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool actuallyVisible = widget->isVisible();
|
||||
const QWidget *ancestorWidget = widget->parentWidget();
|
||||
while (actuallyVisible && ancestorWidget) {
|
||||
actuallyVisible = ancestorWidget->isVisible();
|
||||
ancestorWidget = ancestorWidget->parentWidget();
|
||||
}
|
||||
return actuallyVisible;
|
||||
}
|
||||
|
||||
void prepareParentlessMenuForShowing(QMenu *menu, const QWidget *surrogateParent)
|
||||
{
|
||||
Q_CHECK_PTR(menu);
|
||||
// ensure polished so the style can change the surfaceformat of the window which is
|
||||
// not possible once the window has been created
|
||||
menu->ensurePolished();
|
||||
menu->winId(); // trigger being a native widget already, to ensure windowHandle created
|
||||
// generic code if not known if the available parent widget is a native widget or not
|
||||
|
||||
if (surrogateParent) {
|
||||
auto parentWindowHandle = surrogateParent->windowHandle();
|
||||
if (!parentWindowHandle) {
|
||||
parentWindowHandle = surrogateParent->nativeParentWidget()->windowHandle();
|
||||
}
|
||||
menu->windowHandle()->setTransientParent(parentWindowHandle);
|
||||
return;
|
||||
}
|
||||
|
||||
menu->windowHandle()->setTransientParent(qGuiApp->focusWindow());
|
||||
// Worst case: The menu's transientParent is now still nullptr in which case it might open as
|
||||
// its own window.
|
||||
}
|
||||
|
||||
void setToolButtonVisible(QWidget *toolButton, bool visible)
|
||||
{
|
||||
toolButton->setVisible(visible);
|
||||
// setVisible() unfortunately has no effect for QWidgetActions on toolbars,
|
||||
// so we work around this by using setMaximumSize().
|
||||
if (qobject_cast<QToolBar *>(toolButton->parent())) {
|
||||
if (visible) {
|
||||
toolButton->setMaximumSize(QSize(9999999, 9999999));
|
||||
toolButton->setFocusPolicy(Qt::TabFocus);
|
||||
} else {
|
||||
toolButton->setMaximumSize(QSize(0, 0));
|
||||
toolButton->setFocusPolicy(Qt::NoFocus); // We don't want focus on invisible items.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool listContainsWidget(const std::forward_list<QPointer<const QWidget>> &list, const QWidget *widget)
|
||||
{
|
||||
for (const auto &item : list) {
|
||||
if (widget == item) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#include "moc_khamburgermenuhelpers_p.cpp"
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
This file is part of the KDE project
|
||||
SPDX-FileCopyrightText: 2021 Felix Ernst <fe.a.ernst@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef KHAMBURGERMENUHELPERS_P_H
|
||||
#define KHAMBURGERMENUHELPERS_P_H
|
||||
|
||||
#include "khamburgermenu_p.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
class QFont;
|
||||
class QMenuBar;
|
||||
class QWidget;
|
||||
|
||||
/**
|
||||
* @brief Makes sure there are no redundant event listeners.
|
||||
*
|
||||
* Functionally identical event listeners are needed throughout khamburgermenu.cpp.
|
||||
* This class makes sure only one of each is created when needed and then reused.
|
||||
* This also simplifies the removal of event listeners.
|
||||
* \internal
|
||||
*/
|
||||
class ListenerContainer : private QObject
|
||||
{
|
||||
public:
|
||||
explicit ListenerContainer(KHamburgerMenuPrivate *hamburgerMenuPrivate);
|
||||
~ListenerContainer() override;
|
||||
|
||||
/**
|
||||
* @return an object of class @p Listener with the same parent as ListenerContainer.
|
||||
*/
|
||||
template<class Listener>
|
||||
Listener *get()
|
||||
{
|
||||
for (auto &i : m_listeners) {
|
||||
if (auto existingListener = qobject_cast<Listener *>(i.get())) {
|
||||
return existingListener;
|
||||
}
|
||||
}
|
||||
|
||||
KHamburgerMenuPrivate *hamburgerMenuPrivate = static_cast<KHamburgerMenuPrivate *>(parent());
|
||||
m_listeners.push_back(std::unique_ptr<QObject>(new Listener(hamburgerMenuPrivate)));
|
||||
return static_cast<Listener *>(m_listeners.back().get());
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<std::unique_ptr<QObject>> m_listeners;
|
||||
};
|
||||
|
||||
/**
|
||||
* When an action is added or removed, calls KHamburgerMenuPrivate::notifyMenuResetNeeded().
|
||||
* \internal
|
||||
*/
|
||||
class AddOrRemoveActionListener : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
inline AddOrRemoveActionListener(QObject *parent)
|
||||
: QObject{parent} {};
|
||||
|
||||
bool eventFilter(QObject * /*watched*/, QEvent *event) override;
|
||||
|
||||
friend class ListenerContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* When the button is pressed, emits KHamburgerMenu::aboutToShowMenu(), then calls
|
||||
* KHamburgerMenuPrivate::resetMenu() (which will only actually reset the menu if
|
||||
* a menu reset is needed).
|
||||
* \internal
|
||||
*/
|
||||
class ButtonPressListener : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* Makes sure the button can show the expected menu in the expected way when pressed.
|
||||
* A button that hasn't been prepared yet will have no menu at all because the menu is only
|
||||
* created when it is needed.
|
||||
*/
|
||||
void prepareHamburgerButtonForPress(QObject *button);
|
||||
|
||||
protected:
|
||||
inline ButtonPressListener(QObject *parent)
|
||||
: QObject{parent} {};
|
||||
|
||||
/** Calls prepareButtonForPress() when an event that presses the button is detected. */
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
friend class ListenerContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* When either
|
||||
* - the visibility of the widget changes or
|
||||
* - actions are added or removed from the widget while it isVisible()
|
||||
* calls KHamburgerMenuPrivate::notifyMenuResetNeeded().
|
||||
* \internal
|
||||
*/
|
||||
class VisibleActionsChangeListener : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
inline VisibleActionsChangeListener(QObject *parent)
|
||||
: QObject{parent} {};
|
||||
|
||||
/**
|
||||
* Listen for events that potentially lead to a change in user-visible actions.
|
||||
* Examples: Adding an action or hiding a toolbar.
|
||||
*/
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
||||
friend class ListenerContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* When the visibility of the widget changes calls KHamburgerMenuPrivate::updateVisibility().
|
||||
* \internal
|
||||
*/
|
||||
class VisibilityChangesListener : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
inline VisibilityChangesListener(QObject *parent)
|
||||
: QObject{parent} {};
|
||||
|
||||
bool eventFilter(QObject * /*watched*/, QEvent *event) override;
|
||||
|
||||
friend class ListenerContainer;
|
||||
};
|
||||
|
||||
/*
|
||||
* We only consider a visible m_menuBar as actually visible if it is not a native
|
||||
* menu bar because native menu bars can come in many shapes and sizes which don't necessarily
|
||||
* have the same usability benefits as a traditional in-window menu bar.
|
||||
*/
|
||||
bool isMenuBarVisible(const QMenuBar *menuBar);
|
||||
|
||||
/**
|
||||
* Is the widget and all of its ancestors visible?
|
||||
*/
|
||||
bool isWidgetActuallyVisible(const QWidget *widget);
|
||||
|
||||
/*
|
||||
* Call this on menus that don't have a parent or don't want to belong to a singular parent() so
|
||||
* those menus won't be treated like their own separate windows.
|
||||
* @param menu Any menu. Though calling this doesn't make sense if the menu has a parent().
|
||||
* @param surrogateParent The widget that is logically closest to be considered a parent at this
|
||||
* point in time. Pass nullptr if this function is supposed to guess.
|
||||
*/
|
||||
void prepareParentlessMenuForShowing(QMenu *menu, const QWidget *surrogateParent);
|
||||
|
||||
/**
|
||||
* Use this instead of QWidget::isVisible() to work around a peculiarity of QToolBar/QToolButton.
|
||||
*/
|
||||
void setToolButtonVisible(QWidget *toolButton, bool visible);
|
||||
|
||||
/**
|
||||
* Does the @p list contain the @p widget?
|
||||
*/
|
||||
bool listContainsWidget(const std::forward_list<QPointer<const QWidget>> &list, const QWidget *widget);
|
||||
|
||||
#endif // KHAMBURGERMENUHELPERS_P_H
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "khelpclient.h"
|
||||
|
||||
#include <KDesktopFile>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDesktopServices>
|
||||
#include <QDirIterator>
|
||||
#include <QUrl>
|
||||
|
||||
void KHelpClient::invokeHelp(const QString &anchor, const QString &_appname)
|
||||
{
|
||||
QString appname;
|
||||
if (_appname.isEmpty()) {
|
||||
appname = QCoreApplication::instance()->applicationName();
|
||||
} else {
|
||||
appname = _appname;
|
||||
}
|
||||
|
||||
// Look for the .desktop file of the application
|
||||
|
||||
// was:
|
||||
// KService::Ptr service(KService::serviceByDesktopName(appname));
|
||||
// if (service)
|
||||
// docPath = service->docPath();
|
||||
// but we don't want to depend on KService here.
|
||||
|
||||
QString docPath;
|
||||
const QStringList desktopDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
|
||||
for (const QString &dir : desktopDirs) {
|
||||
QDirIterator it(dir, QStringList() << appname + QLatin1String(".desktop"), QDir::NoFilter, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||
while (it.hasNext()) {
|
||||
const QString desktopPath(it.next());
|
||||
KDesktopFile desktopFile(desktopPath);
|
||||
docPath = desktopFile.readDocPath();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// docPath could be a path or a full URL, I think.
|
||||
|
||||
QUrl url;
|
||||
if (!docPath.isEmpty()) {
|
||||
url = QUrl(QStringLiteral("help:/")).resolved(QUrl(docPath));
|
||||
} else {
|
||||
if (!anchor.isEmpty()) {
|
||||
if (anchor.contains(QLatin1Char('#'))) {
|
||||
url = QUrl(QStringLiteral("help:/%1/%2").arg(appname, anchor));
|
||||
} else {
|
||||
url = QUrl(QStringLiteral("help:/%1/%2.html").arg(appname, anchor));
|
||||
}
|
||||
} else {
|
||||
url = QUrl(QStringLiteral("help:/%1/index.html").arg(appname));
|
||||
}
|
||||
}
|
||||
|
||||
// launch khelpcenter, or a browser for URIs not handled by khelpcenter
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 2012 David Faure <faure@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef KHELPCLIENT_H
|
||||
#define KHELPCLIENT_H
|
||||
|
||||
#include "kconfigwidgets_export.h"
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
* @namespace KHelpClient
|
||||
* Provides utility functions for access to help manuals.
|
||||
*/
|
||||
namespace KHelpClient
|
||||
{
|
||||
/**
|
||||
* Invokes the KHelpCenter HTML help viewer from docbook sources.
|
||||
*
|
||||
* The HTML file will be found using the X-DocPath entry in the application's desktop file.
|
||||
* It can be either a relative path, or a website URL.
|
||||
*
|
||||
* @param anchor This has to be a defined anchor in your
|
||||
* docbook sources or website. If empty the main index
|
||||
* is loaded.
|
||||
* @param appname This allows you to specify the .desktop file to get the help path from.
|
||||
* If empty the QCoreApplication::applicationName() is used.
|
||||
* @since 5.0
|
||||
*/
|
||||
KCONFIGWIDGETS_EXPORT void invokeHelp(const QString &anchor = QString(), const QString &appname = QString());
|
||||
}
|
||||
|
||||
#endif /* KHELPCLIENT_H */
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 1999-2003 Hans Petter Bieker <bieker@kde.org>
|
||||
SPDX-FileCopyrightText: 2007 David Jarvie <djarvie@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "klanguagebutton.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLocale>
|
||||
#include <QMenu>
|
||||
#include <QPushButton>
|
||||
|
||||
#include <KConfig>
|
||||
#include <KConfigGroup>
|
||||
|
||||
static void checkInsertPos(QMenu *popup, const QString &str, int &index)
|
||||
{
|
||||
if (index != -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int a = 0;
|
||||
const QList<QAction *> actions = popup->actions();
|
||||
int b = actions.count();
|
||||
|
||||
while (a < b) {
|
||||
int w = (a + b) / 2;
|
||||
QAction *ac = actions[w];
|
||||
int j = str.localeAwareCompare(ac->text());
|
||||
if (j > 0) {
|
||||
a = w + 1;
|
||||
} else {
|
||||
b = w;
|
||||
}
|
||||
}
|
||||
|
||||
index = a; // it doesn't really matter ... a == b here.
|
||||
|
||||
Q_ASSERT(a == b);
|
||||
}
|
||||
|
||||
class KLanguageButtonPrivate
|
||||
{
|
||||
public:
|
||||
explicit KLanguageButtonPrivate(KLanguageButton *parent);
|
||||
~KLanguageButtonPrivate()
|
||||
{
|
||||
delete button;
|
||||
delete popup;
|
||||
}
|
||||
void setCurrentItem(QAction *);
|
||||
void clear();
|
||||
QAction *findAction(const QString &data) const;
|
||||
|
||||
QPushButton *button = nullptr;
|
||||
QStringList ids;
|
||||
QMenu *popup = nullptr;
|
||||
QString current;
|
||||
QString locale;
|
||||
bool staticText : 1;
|
||||
bool showCodes : 1;
|
||||
};
|
||||
|
||||
KLanguageButton::KLanguageButton(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, d(new KLanguageButtonPrivate(this))
|
||||
{
|
||||
}
|
||||
|
||||
KLanguageButton::KLanguageButton(const QString &text, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, d(new KLanguageButtonPrivate(this))
|
||||
{
|
||||
setText(text);
|
||||
}
|
||||
|
||||
KLanguageButtonPrivate::KLanguageButtonPrivate(KLanguageButton *parent)
|
||||
: button(new QPushButton(parent))
|
||||
, popup(new QMenu(parent))
|
||||
, locale(QLocale::system().name())
|
||||
, staticText(false)
|
||||
, showCodes(false)
|
||||
{
|
||||
QHBoxLayout *layout = new QHBoxLayout(parent);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(button);
|
||||
|
||||
parent->setFocusProxy(button);
|
||||
parent->setFocusPolicy(button->focusPolicy());
|
||||
|
||||
button->setMenu(popup);
|
||||
|
||||
QObject::connect(popup, &QMenu::triggered, parent, &KLanguageButton::slotTriggered);
|
||||
QObject::connect(popup, &QMenu::hovered, parent, &KLanguageButton::slotHovered);
|
||||
}
|
||||
|
||||
KLanguageButton::~KLanguageButton() = default;
|
||||
|
||||
void KLanguageButton::setText(const QString &text)
|
||||
{
|
||||
d->staticText = true;
|
||||
d->button->setText(text);
|
||||
}
|
||||
|
||||
void KLanguageButton::setLocale(const QString &locale)
|
||||
{
|
||||
d->locale = locale;
|
||||
}
|
||||
|
||||
void KLanguageButton::showLanguageCodes(bool show)
|
||||
{
|
||||
d->showCodes = show;
|
||||
}
|
||||
|
||||
static QString nameFromEntryFile(const QString &entryFile)
|
||||
{
|
||||
const KConfig entry(entryFile, KConfig::SimpleConfig);
|
||||
const KConfigGroup group(&entry, QStringLiteral("KCM Locale"));
|
||||
return group.readEntry("Name", QString());
|
||||
}
|
||||
|
||||
void KLanguageButton::insertLanguage(const QString &languageCode, const QString &name, int index)
|
||||
{
|
||||
QString text;
|
||||
bool showCodes = d->showCodes;
|
||||
if (name.isEmpty()) {
|
||||
const QString entryFile =
|
||||
QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("locale/") + languageCode + QLatin1String("/kf6_entry.desktop"));
|
||||
if (QFile::exists(entryFile)) {
|
||||
text = nameFromEntryFile(entryFile);
|
||||
}
|
||||
|
||||
if (text.isEmpty()) {
|
||||
text = languageCode;
|
||||
QLocale locale(languageCode);
|
||||
if (locale != QLocale::c()) {
|
||||
text = locale.nativeLanguageName();
|
||||
// For some languages the native name might be empty.
|
||||
// In this case use the non native language name as fallback.
|
||||
// See: QTBUG-51323
|
||||
text = text.isEmpty() ? QLocale::languageToString(locale.language()) : text;
|
||||
} else {
|
||||
showCodes = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
text = name;
|
||||
}
|
||||
if (showCodes) {
|
||||
text += QLatin1String(" (") + languageCode + QLatin1Char(')');
|
||||
}
|
||||
|
||||
checkInsertPos(d->popup, text, index);
|
||||
QAction *a = new QAction(QIcon(), text, this);
|
||||
a->setData(languageCode);
|
||||
if (index >= 0 && index < d->popup->actions().count() - 1) {
|
||||
d->popup->insertAction(d->popup->actions()[index], a);
|
||||
} else {
|
||||
d->popup->addAction(a);
|
||||
}
|
||||
d->ids.append(languageCode);
|
||||
}
|
||||
|
||||
void KLanguageButton::insertSeparator(int index)
|
||||
{
|
||||
if (index >= 0 && index < d->popup->actions().count() - 1) {
|
||||
d->popup->insertSeparator(d->popup->actions()[index]);
|
||||
} else {
|
||||
d->popup->addSeparator();
|
||||
}
|
||||
}
|
||||
|
||||
void KLanguageButton::loadAllLanguages()
|
||||
{
|
||||
const QStringList localeDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("locale"), QStandardPaths::LocateDirectory);
|
||||
for (const QString &localeDir : localeDirs) {
|
||||
const QStringList entries = QDir(localeDir).entryList(QDir::Dirs, QDir::Name);
|
||||
for (const QString &d : entries) {
|
||||
const QString entryFile = localeDir + QLatin1Char('/') + d + QStringLiteral("/kf6_entry.desktop");
|
||||
if (QFile::exists(entryFile)) {
|
||||
insertLanguage(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d->ids.removeDuplicates();
|
||||
setCurrentItem(d->locale);
|
||||
}
|
||||
|
||||
void KLanguageButton::slotTriggered(QAction *a)
|
||||
{
|
||||
// qCDebug(KCONFIG_WIDGETS_LOG) << "slotTriggered" << index;
|
||||
if (!a) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->setCurrentItem(a);
|
||||
|
||||
// Forward event from popup menu as if it was emitted from this widget:
|
||||
Q_EMIT activated(d->current);
|
||||
}
|
||||
|
||||
void KLanguageButton::slotHovered(QAction *a)
|
||||
{
|
||||
// qCDebug(KCONFIG_WIDGETS_LOG) << "slotHovered" << index;
|
||||
|
||||
Q_EMIT highlighted(a->data().toString());
|
||||
}
|
||||
|
||||
int KLanguageButton::count() const
|
||||
{
|
||||
return d->ids.count();
|
||||
}
|
||||
|
||||
void KLanguageButton::clear()
|
||||
{
|
||||
d->clear();
|
||||
}
|
||||
|
||||
void KLanguageButtonPrivate::clear()
|
||||
{
|
||||
ids.clear();
|
||||
popup->clear();
|
||||
|
||||
if (!staticText) {
|
||||
button->setText(QString());
|
||||
}
|
||||
}
|
||||
|
||||
bool KLanguageButton::contains(const QString &languageCode) const
|
||||
{
|
||||
return d->ids.contains(languageCode);
|
||||
}
|
||||
|
||||
QString KLanguageButton::current() const
|
||||
{
|
||||
return d->current.isEmpty() ? QStringLiteral("en") : d->current;
|
||||
}
|
||||
|
||||
QAction *KLanguageButtonPrivate::findAction(const QString &data) const
|
||||
{
|
||||
const auto listActions = popup->actions();
|
||||
for (QAction *a : listActions) {
|
||||
if (!a->data().toString().compare(data)) {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void KLanguageButton::setCurrentItem(const QString &languageCode)
|
||||
{
|
||||
if (d->ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
QAction *a;
|
||||
if (d->ids.indexOf(languageCode) < 0) {
|
||||
a = d->findAction(d->ids[0]);
|
||||
} else {
|
||||
a = d->findAction(languageCode);
|
||||
}
|
||||
if (a) {
|
||||
d->setCurrentItem(a);
|
||||
}
|
||||
}
|
||||
|
||||
void KLanguageButtonPrivate::setCurrentItem(QAction *a)
|
||||
{
|
||||
if (!a->data().isValid()) {
|
||||
return;
|
||||
}
|
||||
current = a->data().toString();
|
||||
|
||||
if (!staticText) {
|
||||
button->setText(a->text());
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_klanguagebutton.cpp"
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
klangbutton.h - Button with language selection drop down menu.
|
||||
Derived from the KLangCombo class by Hans Petter Bieker.
|
||||
|
||||
SPDX-FileCopyrightText: 1999-2003 Hans Petter Bieker <bieker@kde.org>
|
||||
SPDX-FileCopyrightText: 2001 Martijn Klingens <klingens@kde.org>
|
||||
SPDX-FileCopyrightText: 2007 David Jarvie <djarvie@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KLANGUAGEBUTTON_H
|
||||
#define KLANGUAGEBUTTON_H
|
||||
|
||||
#include "kconfigwidgets_export.h"
|
||||
#include <QWidget>
|
||||
#include <memory>
|
||||
|
||||
class QAction;
|
||||
class KLanguageButtonPrivate;
|
||||
|
||||
/**
|
||||
* @class KLanguageButton klanguagebutton.h KLanguageButton
|
||||
*
|
||||
* KLanguageButton is a pushbutton which allows a language to be selected from
|
||||
* a popup list.
|
||||
*
|
||||
* Languages are identified by their ISO 639-1 codes, e.g. en, pt_BR.
|
||||
*
|
||||
* \image html klanguagebutton.png "KDE Language Selection Widget"
|
||||
*
|
||||
* @author Hans Petter Bieker <bieker@kde.org>, Martijn Klingens <klingens@kde.org>,
|
||||
* David Jarvie <djarvie@kde.org>
|
||||
*/
|
||||
class KCONFIGWIDGETS_EXPORT KLanguageButton : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructs a button whose text is determined by the current language
|
||||
* in the popup list.
|
||||
*
|
||||
* @param parent the parent of the button
|
||||
*/
|
||||
explicit KLanguageButton(QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Constructs a button with static text.
|
||||
*
|
||||
* @param text the text of the button
|
||||
* @param parent the parent of the button
|
||||
*/
|
||||
explicit KLanguageButton(const QString &text, QWidget *parent = nullptr);
|
||||
|
||||
/**
|
||||
* Deconstructor
|
||||
*/
|
||||
~KLanguageButton() override;
|
||||
|
||||
/**
|
||||
* Sets the locale to display language names. By default, QLocale::system().name() is used.
|
||||
*
|
||||
* @param locale locale to use
|
||||
*/
|
||||
void setLocale(const QString &locale);
|
||||
|
||||
/**
|
||||
* Sets a static button text.
|
||||
*
|
||||
* @param text button text
|
||||
*/
|
||||
void setText(const QString &text);
|
||||
|
||||
/**
|
||||
* Specifies whether language codes should be shown alongside language names
|
||||
* in the popup. Calling this method does not affect any previously
|
||||
* inserted language texts, so it should normally be called before
|
||||
* populating the list.
|
||||
*
|
||||
* @param show true to show codes, false to hide codes
|
||||
*/
|
||||
void showLanguageCodes(bool show);
|
||||
|
||||
/**
|
||||
* Load all known languages into the popup list.
|
||||
* The current language in the list is set to the default language for the
|
||||
* current locale (as modified by setLocale()).
|
||||
*/
|
||||
void loadAllLanguages();
|
||||
|
||||
/**
|
||||
* Inserts a language into the combo box.
|
||||
* Normally the display name of the language is obtained automatically, but
|
||||
* if either the language code does not exist, or there are special display
|
||||
* requirements, the name of the language can be specified in @p name.
|
||||
*
|
||||
* @param languageCode the code for the language
|
||||
* @param name language name. If empty, the name is obtained automatically.
|
||||
* @param index the insertion position, or -1 to insert in alphabetical order
|
||||
*/
|
||||
void insertLanguage(const QString &languageCode, const QString &name = QString(), int index = -1);
|
||||
|
||||
/**
|
||||
* Inserts a separator item into the combo box. A negative index will append the item.
|
||||
*
|
||||
* @param index the insertion position
|
||||
*/
|
||||
void insertSeparator(int index = -1);
|
||||
|
||||
/**
|
||||
* Returns the number of items in the combo box.
|
||||
*/
|
||||
int count() const;
|
||||
|
||||
/**
|
||||
* Removes all combobox items.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Returns the language code of the combobox's current item.
|
||||
*
|
||||
* @return the current item's language code
|
||||
*/
|
||||
QString current() const;
|
||||
|
||||
/**
|
||||
* Checks whether the specified language is in the popup list.
|
||||
*
|
||||
* @param languageCode the language's code
|
||||
* @return true if in the list
|
||||
*/
|
||||
bool contains(const QString &languageCode) const;
|
||||
|
||||
/**
|
||||
* Sets a given language to be the current item.
|
||||
*
|
||||
* @param languageCode the language's code
|
||||
*/
|
||||
void setCurrentItem(const QString &languageCode);
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* This signal is emitted when a new item is activated.
|
||||
*
|
||||
* @param languageCode code of the activated language
|
||||
*/
|
||||
void activated(const QString &languageCode);
|
||||
/**
|
||||
* This signal is emitted when a new item is highlighted.
|
||||
*
|
||||
* @param languageCode code of the highlighted language
|
||||
*/
|
||||
void highlighted(const QString &languageCode);
|
||||
|
||||
private Q_SLOTS:
|
||||
KCONFIGWIDGETS_NO_EXPORT void slotTriggered(QAction *action);
|
||||
KCONFIGWIDGETS_NO_EXPORT void slotHovered(QAction *action);
|
||||
|
||||
private:
|
||||
friend class KLanguageButtonPrivate;
|
||||
std::unique_ptr<KLanguageButtonPrivate> const d;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 1999-2003 Hans Petter Bieker <bieker@kde.org>
|
||||
SPDX-FileCopyrightText: 2007 David Jarvie <software@astrojar.org.uk>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "klanguagename.h"
|
||||
|
||||
#include <KConfig>
|
||||
#include <KConfigGroup>
|
||||
|
||||
#include <QDir>
|
||||
#include <QLocale>
|
||||
|
||||
QString KLanguageName::nameForCode(const QString &code)
|
||||
{
|
||||
const QStringList parts = QLocale().name().split(QLatin1Char('_'));
|
||||
return nameForCodeInLocale(code, parts.at(0));
|
||||
}
|
||||
|
||||
static std::tuple<QString, QString> namesFromEntryFile(const QString &realCode, const QString &realOutputCode)
|
||||
{
|
||||
const QString entryFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
|
||||
QStringLiteral("locale") + QLatin1Char('/') + realCode + QStringLiteral("/kf6_entry.desktop"));
|
||||
|
||||
if (!entryFile.isEmpty()) {
|
||||
KConfig entry(entryFile, KConfig::SimpleConfig);
|
||||
entry.setLocale(realOutputCode);
|
||||
const KConfigGroup group(&entry, QStringLiteral("KCM Locale"));
|
||||
const QString name = group.readEntry("Name");
|
||||
|
||||
entry.setLocale(QStringLiteral("en_US"));
|
||||
const QString englishName = group.readEntry("Name");
|
||||
return std::make_tuple(name, englishName);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QString KLanguageName::nameForCodeInLocale(const QString &code, const QString &outputCode)
|
||||
{
|
||||
const QString realCode = code == QLatin1String("en") ? QStringLiteral("en_US") : code;
|
||||
const QString realOutputCode = outputCode == QLatin1String("en") ? QStringLiteral("en_US") : outputCode;
|
||||
|
||||
const std::tuple<QString, QString> nameAndEnglishName = namesFromEntryFile(realCode, realOutputCode);
|
||||
const QString name = std::get<0>(nameAndEnglishName);
|
||||
const QString englishName = std::get<1>(nameAndEnglishName);
|
||||
|
||||
if (!name.isEmpty()) {
|
||||
// KConfig doesn't have a way to say it didn't find the entry in
|
||||
// realOutputCode. When it doesn't find it in the locale you ask for, it just returns the english version
|
||||
// so we compare the returned name against the english version, if they are different we return it, if they
|
||||
// are equal we defer to QLocale (with a final fallback to name/englishName if QLocale doesn't know about it)
|
||||
if (name != englishName || realOutputCode == QLatin1String("en_US")) {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
const QLocale locale(realCode);
|
||||
if (locale != QLocale::c()) {
|
||||
if (realCode == realOutputCode) {
|
||||
return locale.nativeLanguageName();
|
||||
}
|
||||
return QLocale::languageToString(locale.language());
|
||||
}
|
||||
|
||||
// We get here if QLocale doesn't know about realCode (at the time of writing this happens for crh, csb, hne, mai) and name and englishName are the same.
|
||||
// So what we do here is return name, which can be either empty if the KDE side doesn't know about the code, or otherwise will be the name/englishName
|
||||
return name;
|
||||
}
|
||||
|
||||
QStringList KLanguageName::allLanguageCodes()
|
||||
{
|
||||
QStringList systemLangList;
|
||||
const QStringList localeDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("locale"), QStandardPaths::LocateDirectory);
|
||||
for (const QString &localeDir : localeDirs) {
|
||||
const QStringList entries = QDir(localeDir).entryList(QDir::Dirs);
|
||||
auto languageExists = [&localeDir](const QString &language) {
|
||||
return QFile::exists(localeDir + QLatin1Char('/') + language + QLatin1String("/kf6_entry.desktop"));
|
||||
};
|
||||
std::copy_if(entries.begin(), entries.end(), std::back_inserter(systemLangList), languageExists);
|
||||
}
|
||||
if (localeDirs.count() > 1) {
|
||||
systemLangList.removeDuplicates();
|
||||
}
|
||||
return systemLangList;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 1999-2003 Hans Petter Bieker <bieker@kde.org>
|
||||
SPDX-FileCopyrightText: 2001 Martijn Klingens <klingens@kde.org>
|
||||
SPDX-FileCopyrightText: 2007 David Jarvie <software@astrojar.org.uk>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KLANGUAGENAME_H
|
||||
#define KLANGUAGENAME_H
|
||||
|
||||
#include "kconfigwidgets_export.h"
|
||||
|
||||
#include <QStringList>
|
||||
|
||||
class QString;
|
||||
|
||||
/**
|
||||
* @class KLanguageName klanguagename.h KLanguageName
|
||||
*
|
||||
* KLanguageName is a helper namespace that returns the name of a given language code.
|
||||
*
|
||||
* @since 5.55
|
||||
*
|
||||
*/
|
||||
namespace KLanguageName
|
||||
{
|
||||
/**
|
||||
* Returns the name of the given language code in the current locale.
|
||||
*
|
||||
* If it can't be found in the current locale it returns the name in English.
|
||||
*
|
||||
* It it can't be found in English either it returns an empty QString.
|
||||
*
|
||||
* @param code code (ISO 639-1) of the language whose name is wanted.
|
||||
*/
|
||||
KCONFIGWIDGETS_EXPORT QString nameForCode(const QString &code);
|
||||
|
||||
/**
|
||||
* Returns the name of the given language code in the other given locale code.
|
||||
*
|
||||
* If it can't be found in the given locale it returns the name in English.
|
||||
*
|
||||
* It it can't be found in English either it returns an empty QString.
|
||||
*
|
||||
* @param code code (ISO 639-1) of the language whose name is wanted.
|
||||
* @param outputLocale code (ISO 639-1) of the language in which we want the name in.
|
||||
*/
|
||||
KCONFIGWIDGETS_EXPORT QString nameForCodeInLocale(const QString &code, const QString &outputLocale);
|
||||
|
||||
/**
|
||||
* Returns the list of language codes found on the system.
|
||||
*
|
||||
* @since 5.74
|
||||
*/
|
||||
KCONFIGWIDGETS_EXPORT QStringList allLanguageCodes();
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#include "kopenaction_p.h"
|
||||
|
||||
#include <KRecentFilesAction>
|
||||
#include <KStandardAction>
|
||||
|
||||
#include <QMenu>
|
||||
#include <QPointer>
|
||||
|
||||
class KOpenActionPrivate
|
||||
{
|
||||
public:
|
||||
KOpenActionPrivate(KOpenAction *q);
|
||||
|
||||
void updatePopupMode();
|
||||
void onPopupMenuAboutToShow();
|
||||
|
||||
KOpenAction *q;
|
||||
QPointer<KRecentFilesAction> recentFilesAction;
|
||||
};
|
||||
|
||||
KOpenActionPrivate::KOpenActionPrivate(KOpenAction *q)
|
||||
: q(q)
|
||||
{
|
||||
}
|
||||
|
||||
KOpenAction::~KOpenAction() = default;
|
||||
|
||||
void KOpenActionPrivate::updatePopupMode()
|
||||
{
|
||||
if (recentFilesAction && recentFilesAction->isEnabled()) {
|
||||
q->setPopupMode(KToolBarPopupAction::MenuButtonPopup);
|
||||
} else {
|
||||
q->setPopupMode(KToolBarPopupAction::NoPopup);
|
||||
}
|
||||
}
|
||||
|
||||
void KOpenActionPrivate::onPopupMenuAboutToShow()
|
||||
{
|
||||
q->popupMenu()->clear();
|
||||
|
||||
if (recentFilesAction) {
|
||||
// Using the menu explicitly rather than the actions so we also get the "Forget..." entry.
|
||||
if (auto *recentMenu = recentFilesAction->menu()) {
|
||||
// Trigger a menu update.
|
||||
Q_EMIT recentMenu->aboutToShow();
|
||||
|
||||
const auto actions = recentMenu->actions();
|
||||
for (QAction *action : actions) {
|
||||
q->popupMenu()->addAction(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KOpenAction::KOpenAction(QObject *parent)
|
||||
: KOpenAction(QIcon(), QString(), parent)
|
||||
{
|
||||
}
|
||||
|
||||
KOpenAction::KOpenAction(const QIcon &icon, const QString &text, QObject *parent)
|
||||
: KToolBarPopupAction(icon, text, parent)
|
||||
, d(new KOpenActionPrivate(this))
|
||||
{
|
||||
setPopupMode(KToolBarPopupAction::NoPopup);
|
||||
connect(popupMenu(), &QMenu::aboutToShow, this, [this] {
|
||||
d->onPopupMenuAboutToShow();
|
||||
});
|
||||
}
|
||||
|
||||
QWidget *KOpenAction::createWidget(QWidget *parentWidget)
|
||||
{
|
||||
if (!d->recentFilesAction) {
|
||||
// Find the accompanying file_open_recent action.
|
||||
QAction *action = nullptr;
|
||||
|
||||
if (parent() && parent()->inherits("KActionCollection")) {
|
||||
const QString openRecentActionId = KStandardAction::name(KStandardAction::OpenRecent);
|
||||
QMetaObject::invokeMethod(parent(), "action", Q_RETURN_ARG(QAction *, action), Q_ARG(QString, openRecentActionId));
|
||||
}
|
||||
|
||||
d->recentFilesAction = qobject_cast<KRecentFilesAction *>(action);
|
||||
if (d->recentFilesAction) {
|
||||
connect(d->recentFilesAction.data(), &QAction::enabledChanged, this, [this] {
|
||||
d->updatePopupMode();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
d->updatePopupMode();
|
||||
|
||||
return KToolBarPopupAction::createWidget(parentWidget);
|
||||
}
|
||||
|
||||
#include "moc_kopenaction_p.cpp"
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Kai Uwe Broulik <kde@broulik.de>
|
||||
*
|
||||
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
|
||||
#ifndef KOPENACTION_H
|
||||
#define KOPENACTION_H
|
||||
|
||||
#include <KToolBarPopupAction>
|
||||
#include <kconfigwidgets_export.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
class KOpenActionPrivate;
|
||||
|
||||
// TODO export only for for unittests?
|
||||
class KCONFIGWIDGETS_EXPORT KOpenAction : public KToolBarPopupAction
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit KOpenAction(QObject *parent = nullptr);
|
||||
KOpenAction(const QIcon &icon, const QString &text, QObject *parent);
|
||||
~KOpenAction() override;
|
||||
|
||||
protected:
|
||||
QWidget *createWidget(QWidget *parent) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<KOpenActionPrivate> const d;
|
||||
};
|
||||
|
||||
#endif // KOPENACTION_H
|
||||
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <reggie@kde.org>
|
||||
SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Nicolas Hadacek <haadcek@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Michael Koch <koch@kde.org>
|
||||
SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@kde.org>
|
||||
SPDX-FileCopyrightText: 2002 Ellis Whitehead <ellis@kde.org>
|
||||
SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
|
||||
SPDX-FileCopyrightText: 2003 Andras Mantia <amantia@kde.org>
|
||||
SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#include "krecentfilesaction.h"
|
||||
#include "krecentfilesaction_p.h"
|
||||
|
||||
#include <QActionGroup>
|
||||
#include <QDir>
|
||||
#include <QGuiApplication>
|
||||
#include <QMenu>
|
||||
#include <QMimeDatabase>
|
||||
#include <QMimeType>
|
||||
#include <QScreen>
|
||||
|
||||
#if HAVE_QTDBUS
|
||||
#include <QDBusConnectionInterface>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusMessage>
|
||||
#endif
|
||||
|
||||
#include <KConfig>
|
||||
#include <KConfigGroup>
|
||||
#include <KLocalizedString>
|
||||
#include <KShell>
|
||||
|
||||
#include <set>
|
||||
|
||||
KRecentFilesAction::KRecentFilesAction(QObject *parent)
|
||||
: KSelectAction(parent)
|
||||
, d_ptr(new KRecentFilesActionPrivate(this))
|
||||
{
|
||||
Q_D(KRecentFilesAction);
|
||||
d->init();
|
||||
}
|
||||
|
||||
KRecentFilesAction::KRecentFilesAction(const QString &text, QObject *parent)
|
||||
: KSelectAction(parent)
|
||||
, d_ptr(new KRecentFilesActionPrivate(this))
|
||||
{
|
||||
Q_D(KRecentFilesAction);
|
||||
d->init();
|
||||
|
||||
// Want to keep the ampersands
|
||||
setText(text);
|
||||
}
|
||||
|
||||
KRecentFilesAction::KRecentFilesAction(const QIcon &icon, const QString &text, QObject *parent)
|
||||
: KSelectAction(parent)
|
||||
, d_ptr(new KRecentFilesActionPrivate(this))
|
||||
{
|
||||
Q_D(KRecentFilesAction);
|
||||
d->init();
|
||||
|
||||
setIcon(icon);
|
||||
// Want to keep the ampersands
|
||||
setText(text);
|
||||
}
|
||||
|
||||
void KRecentFilesActionPrivate::init()
|
||||
{
|
||||
Q_Q(KRecentFilesAction);
|
||||
delete q->menu();
|
||||
q->setMenu(new QMenu());
|
||||
q->setToolBarMode(KSelectAction::MenuMode);
|
||||
m_noEntriesAction = q->menu()->addAction(i18n("No Entries"));
|
||||
m_noEntriesAction->setObjectName(QStringLiteral("no_entries"));
|
||||
m_noEntriesAction->setEnabled(false);
|
||||
clearSeparator = q->menu()->addSeparator();
|
||||
clearSeparator->setVisible(false);
|
||||
clearSeparator->setObjectName(QStringLiteral("separator"));
|
||||
clearAction = q->menu()->addAction(QIcon::fromTheme(QStringLiteral("edit-clear-history")), i18n("Clear List"), q, &KRecentFilesAction::clear);
|
||||
clearAction->setObjectName(QStringLiteral("clear_action"));
|
||||
clearAction->setVisible(false);
|
||||
q->setEnabled(false);
|
||||
q->connect(q, &KSelectAction::actionTriggered, q, [this](QAction *action) {
|
||||
urlSelected(action);
|
||||
});
|
||||
|
||||
q->connect(q->menu(), &QMenu::aboutToShow, q, [q] {
|
||||
std::vector<RecentActionInfo> &recentActions = q->d_ptr->m_recentActions;
|
||||
// Set icons lazily based on the mimetype
|
||||
for (auto action : recentActions) {
|
||||
if (action.action->icon().isNull()) {
|
||||
if (!action.mimeType.isValid()) {
|
||||
action.mimeType = QMimeDatabase().mimeTypeForFile(action.url.path(), QMimeDatabase::MatchExtension);
|
||||
}
|
||||
|
||||
if (!action.mimeType.isDefault()) {
|
||||
action.action->setIcon(QIcon::fromTheme(action.mimeType.iconName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
KRecentFilesAction::~KRecentFilesAction() = default;
|
||||
|
||||
void KRecentFilesActionPrivate::urlSelected(QAction *action)
|
||||
{
|
||||
Q_Q(KRecentFilesAction);
|
||||
|
||||
auto it = findByAction(action);
|
||||
|
||||
Q_ASSERT(it != m_recentActions.cend()); // Should never happen
|
||||
|
||||
const QUrl url = it->url; // BUG: 461448; see iterator invalidation rules
|
||||
Q_EMIT q->urlSelected(url);
|
||||
}
|
||||
|
||||
// TODO: remove this helper function, it will crash if you use it in a loop
|
||||
void KRecentFilesActionPrivate::removeAction(std::vector<RecentActionInfo>::iterator it)
|
||||
{
|
||||
Q_Q(KRecentFilesAction);
|
||||
delete q->KSelectAction::removeAction(it->action);
|
||||
m_recentActions.erase(it);
|
||||
}
|
||||
|
||||
int KRecentFilesAction::maxItems() const
|
||||
{
|
||||
Q_D(const KRecentFilesAction);
|
||||
return d->m_maxItems;
|
||||
}
|
||||
|
||||
void KRecentFilesAction::setMaxItems(int maxItems)
|
||||
{
|
||||
Q_D(KRecentFilesAction);
|
||||
// set new maxItems
|
||||
d->m_maxItems = std::max(maxItems, 0);
|
||||
|
||||
// Remove all excess items, oldest (i.e. first added) first
|
||||
const int difference = static_cast<int>(d->m_recentActions.size()) - d->m_maxItems;
|
||||
if (difference > 0) {
|
||||
auto beginIt = d->m_recentActions.begin();
|
||||
auto endIt = d->m_recentActions.begin() + difference;
|
||||
for (auto it = beginIt; it < endIt; ++it) {
|
||||
// Remove the action from the menus, action groups ...etc
|
||||
delete KSelectAction::removeAction(it->action);
|
||||
}
|
||||
d->m_recentActions.erase(beginIt, endIt);
|
||||
}
|
||||
}
|
||||
|
||||
static QString titleWithSensibleWidth(const QString &nameValue, const QString &value)
|
||||
{
|
||||
// Calculate 3/4 of screen geometry, we do not want
|
||||
// action titles to be bigger than that
|
||||
// Since we do not know in which screen we are going to show
|
||||
// we choose the min of all the screens
|
||||
int maxWidthForTitles = INT_MAX;
|
||||
const auto screens = QGuiApplication::screens();
|
||||
for (QScreen *screen : screens) {
|
||||
maxWidthForTitles = qMin(maxWidthForTitles, screen->availableGeometry().width() * 3 / 4);
|
||||
}
|
||||
const QFontMetrics fontMetrics = QFontMetrics(QFont());
|
||||
|
||||
QString title = nameValue + QLatin1String(" [") + value + QLatin1Char(']');
|
||||
const int nameWidth = fontMetrics.boundingRect(title).width();
|
||||
if (nameWidth > maxWidthForTitles) {
|
||||
// If it does not fit, try to cut only the whole path, though if the
|
||||
// name is too long (more than 3/4 of the whole text) we cut it a bit too
|
||||
const int nameValueMaxWidth = maxWidthForTitles * 3 / 4;
|
||||
QString cutNameValue;
|
||||
QString cutValue;
|
||||
if (nameWidth > nameValueMaxWidth) {
|
||||
cutNameValue = fontMetrics.elidedText(nameValue, Qt::ElideMiddle, nameValueMaxWidth);
|
||||
cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameValueMaxWidth);
|
||||
} else {
|
||||
cutNameValue = nameValue;
|
||||
cutValue = fontMetrics.elidedText(value, Qt::ElideMiddle, maxWidthForTitles - nameWidth);
|
||||
}
|
||||
title = cutNameValue + QLatin1String(" [") + cutValue + QLatin1Char(']');
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
void KRecentFilesAction::addUrl(const QUrl &url, const QString &name)
|
||||
{
|
||||
addUrl(url, name, QString());
|
||||
}
|
||||
|
||||
void KRecentFilesAction::addUrl(const QUrl &url, const QString &name, const QString &mimeTypeStr)
|
||||
{
|
||||
Q_D(KRecentFilesAction);
|
||||
|
||||
// ensure we never add items if we want none
|
||||
if (d->m_maxItems == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.isLocalFile() && url.toLocalFile().startsWith(QDir::tempPath())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove url if it already exists in the list
|
||||
removeUrl(url);
|
||||
|
||||
// Remove oldest item if already maxItems in list
|
||||
Q_ASSERT(d->m_maxItems > 0);
|
||||
if (static_cast<int>(d->m_recentActions.size()) == d->m_maxItems) {
|
||||
d->removeAction(d->m_recentActions.begin());
|
||||
}
|
||||
|
||||
const QString pathOrUrl(url.toDisplayString(QUrl::PreferLocalFile));
|
||||
const QString tmpName = !name.isEmpty() ? name : url.fileName();
|
||||
#ifdef Q_OS_WIN
|
||||
const QString file = url.isLocalFile() ? QDir::toNativeSeparators(pathOrUrl) : pathOrUrl;
|
||||
#else
|
||||
const QString file = pathOrUrl;
|
||||
#endif
|
||||
|
||||
d->m_noEntriesAction->setVisible(false);
|
||||
d->clearSeparator->setVisible(true);
|
||||
d->clearAction->setVisible(true);
|
||||
setEnabled(true);
|
||||
// add file to list
|
||||
const QString title = titleWithSensibleWidth(tmpName, KShell::tildeCollapse(file));
|
||||
|
||||
#if HAVE_QTDBUS
|
||||
static bool isKdeSession = qgetenv("XDG_CURRENT_DESKTOP") == "KDE";
|
||||
if (isKdeSession) {
|
||||
const QDBusConnection bus = QDBusConnection::sessionBus();
|
||||
if (bus.isConnected()
|
||||
&& bus.interface()->isServiceRegistered(QStringLiteral("org.kde.ActivityManager"))
|
||||
// skip files in hidden directories
|
||||
&& !url.path().contains(QStringLiteral("/."))) {
|
||||
const static QString activityService = QStringLiteral("org.kde.ActivityManager");
|
||||
const static QString activityResources = QStringLiteral("/ActivityManager/Resources");
|
||||
const static QString activityResouceInferface = QStringLiteral("org.kde.ActivityManager.Resources");
|
||||
QMimeType mimeType;
|
||||
if (!mimeTypeStr.isEmpty()) {
|
||||
mimeType = QMimeDatabase().mimeTypeForName(mimeTypeStr);
|
||||
} else {
|
||||
mimeType = QMimeDatabase().mimeTypeForFile(url.path(), QMimeDatabase::MatchExtension);
|
||||
}
|
||||
|
||||
const auto urlString = url.toString(QUrl::PreferLocalFile);
|
||||
QDBusMessage message =
|
||||
QDBusMessage::createMethodCall(activityService, activityResources, activityResouceInferface, QStringLiteral("RegisterResourceEvent"));
|
||||
message.setArguments({qApp->desktopFileName(), uint(0) /* WinId */, urlString, uint(0) /* eventType Accessed */});
|
||||
bus.asyncCall(message);
|
||||
|
||||
message = QDBusMessage::createMethodCall(activityService, activityResources, activityResouceInferface, QStringLiteral("RegisterResourceMimetype"));
|
||||
message.setArguments({urlString, mimeType.name()});
|
||||
bus.asyncCall(message);
|
||||
|
||||
message = QDBusMessage::createMethodCall(activityService, activityResources, activityResouceInferface, QStringLiteral("RegisterResourceTitle"));
|
||||
message.setArguments({urlString, url.fileName()});
|
||||
bus.asyncCall(message);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
QAction *action = new QAction(title, selectableActionGroup());
|
||||
addAction(action, url, tmpName, QMimeType());
|
||||
}
|
||||
|
||||
void KRecentFilesAction::addAction(QAction *action, const QUrl &url, const QString &name, const QMimeType &mimeType)
|
||||
{
|
||||
Q_D(KRecentFilesAction);
|
||||
menu()->insertAction(menu()->actions().value(0), action);
|
||||
d->m_recentActions.push_back({action, url, name, mimeType});
|
||||
}
|
||||
|
||||
QAction *KRecentFilesAction::removeAction(QAction *action)
|
||||
{
|
||||
Q_D(KRecentFilesAction);
|
||||
auto it = d->findByAction(action);
|
||||
Q_ASSERT(it != d->m_recentActions.cend());
|
||||
d->m_recentActions.erase(it);
|
||||
return KSelectAction::removeAction(action);
|
||||
}
|
||||
|
||||
void KRecentFilesAction::removeUrl(const QUrl &url)
|
||||
{
|
||||
Q_D(KRecentFilesAction);
|
||||
|
||||
auto it = d->findByUrl(url);
|
||||
|
||||
if (it != d->m_recentActions.cend()) {
|
||||
d->removeAction(it);
|
||||
};
|
||||
}
|
||||
|
||||
QList<QUrl> KRecentFilesAction::urls() const
|
||||
{
|
||||
Q_D(const KRecentFilesAction);
|
||||
|
||||
QList<QUrl> list;
|
||||
list.reserve(d->m_recentActions.size());
|
||||
|
||||
using Info = KRecentFilesActionPrivate::RecentActionInfo;
|
||||
// Reverse order to match how the actions appear in the menu
|
||||
std::transform(d->m_recentActions.crbegin(), d->m_recentActions.crend(), std::back_inserter(list), [](const Info &info) {
|
||||
return info.url;
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void KRecentFilesAction::clear()
|
||||
{
|
||||
clearEntries();
|
||||
Q_EMIT recentListCleared();
|
||||
}
|
||||
|
||||
void KRecentFilesAction::clearEntries()
|
||||
{
|
||||
Q_D(KRecentFilesAction);
|
||||
KSelectAction::clear();
|
||||
d->m_recentActions.clear();
|
||||
d->m_noEntriesAction->setVisible(true);
|
||||
d->clearSeparator->setVisible(false);
|
||||
d->clearAction->setVisible(false);
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
void KRecentFilesAction::loadEntries(const KConfigGroup &_config)
|
||||
{
|
||||
Q_D(KRecentFilesAction);
|
||||
clearEntries();
|
||||
|
||||
QString key;
|
||||
QString value;
|
||||
QString nameKey;
|
||||
QString nameValue;
|
||||
QString title;
|
||||
QUrl url;
|
||||
|
||||
KConfigGroup cg = _config;
|
||||
// "<default>" means the group was constructed with an empty name
|
||||
if (cg.name() == QLatin1String("<default>")) {
|
||||
cg = KConfigGroup(cg.config(), QStringLiteral("RecentFiles"));
|
||||
}
|
||||
|
||||
std::set<QUrl> seenUrls;
|
||||
|
||||
bool thereAreEntries = false;
|
||||
// read file list
|
||||
for (int i = 1; i <= d->m_maxItems; i++) {
|
||||
key = QStringLiteral("File%1").arg(i);
|
||||
value = cg.readPathEntry(key, QString());
|
||||
if (value.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
url = QUrl::fromUserInput(value);
|
||||
|
||||
auto [it, isNewUrl] = seenUrls.insert(url);
|
||||
// Don't restore if this url has already been restored (e.g. broken config)
|
||||
if (!isNewUrl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// convert to backslashes
|
||||
if (url.isLocalFile()) {
|
||||
value = QDir::toNativeSeparators(value);
|
||||
}
|
||||
#endif
|
||||
|
||||
nameKey = QStringLiteral("Name%1").arg(i);
|
||||
nameValue = cg.readPathEntry(nameKey, url.fileName());
|
||||
title = titleWithSensibleWidth(nameValue, KShell::tildeCollapse(value));
|
||||
if (!value.isNull()) {
|
||||
thereAreEntries = true;
|
||||
addAction(new QAction(title, selectableActionGroup()), url, nameValue);
|
||||
}
|
||||
}
|
||||
if (thereAreEntries) {
|
||||
d->m_noEntriesAction->setVisible(false);
|
||||
d->clearSeparator->setVisible(true);
|
||||
d->clearAction->setVisible(true);
|
||||
setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
void KRecentFilesAction::saveEntries(const KConfigGroup &_cg)
|
||||
{
|
||||
Q_D(KRecentFilesAction);
|
||||
|
||||
KConfigGroup cg = _cg;
|
||||
// "<default>" means the group was constructed with an empty name
|
||||
if (cg.name() == QLatin1String("<default>")) {
|
||||
cg = KConfigGroup(cg.config(), QStringLiteral("RecentFiles"));
|
||||
}
|
||||
|
||||
cg.deleteGroup();
|
||||
|
||||
// write file list
|
||||
int i = 1;
|
||||
for (const auto &[action, url, shortName, _] : d->m_recentActions) {
|
||||
cg.writePathEntry(QStringLiteral("File%1").arg(i), url.toDisplayString(QUrl::PreferLocalFile));
|
||||
cg.writePathEntry(QStringLiteral("Name%1").arg(i), shortName);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_krecentfilesaction.cpp"
|
||||
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <reggie@kde.org>
|
||||
SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Nicolas Hadacek <haadcek@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Michael Koch <koch@kde.org>
|
||||
SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@kde.org>
|
||||
SPDX-FileCopyrightText: 2002 Ellis Whitehead <ellis@kde.org>
|
||||
SPDX-FileCopyrightText: 2003 Andras Mantia <amantia@kde.org>
|
||||
SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#ifndef KRECENTFILESACTION_H
|
||||
#define KRECENTFILESACTION_H
|
||||
|
||||
#include <KSelectAction>
|
||||
#include <QUrl>
|
||||
#include <kconfigwidgets_export.h>
|
||||
|
||||
#include <QMimeType>
|
||||
|
||||
class KConfigGroup;
|
||||
class KRecentFilesActionPrivate;
|
||||
|
||||
/**
|
||||
* @class KRecentFilesAction krecentfilesaction.h KRecentFilesAction
|
||||
*
|
||||
* @short Recent files action
|
||||
*
|
||||
* This class is an action to handle a recent files submenu.
|
||||
* The best way to create the action is to use KStandardAction::openRecent.
|
||||
* Then you simply need to call loadEntries on startup, saveEntries
|
||||
* on shutdown, addURL when your application loads/saves a file.
|
||||
*
|
||||
* @author Michael Koch
|
||||
*/
|
||||
class KCONFIGWIDGETS_EXPORT KRecentFilesAction : public KSelectAction
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int maxItems READ maxItems WRITE setMaxItems)
|
||||
Q_DECLARE_PRIVATE(KRecentFilesAction)
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructs an action with the specified parent.
|
||||
*
|
||||
* @param parent The parent of this action.
|
||||
*/
|
||||
explicit KRecentFilesAction(QObject *parent);
|
||||
|
||||
/**
|
||||
* Constructs an action with text; a shortcut may be specified by
|
||||
* the ampersand character (e.g. \"&Option\" creates a shortcut with key \e O )
|
||||
*
|
||||
* This is the most common KAction used when you do not have a
|
||||
* corresponding icon (note that it won't appear in the current version
|
||||
* of the "Edit ToolBar" dialog, because an action needs an icon to be
|
||||
* plugged in a toolbar...).
|
||||
*
|
||||
* @param text The text that will be displayed.
|
||||
* @param parent The parent of this action.
|
||||
*/
|
||||
KRecentFilesAction(const QString &text, QObject *parent);
|
||||
|
||||
/**
|
||||
* Constructs an action with text and an icon; a shortcut may be specified by
|
||||
* the ampersand character (e.g. \"&Option\" creates a shortcut with key \e O )
|
||||
*
|
||||
* This is the other common KAction used. Use it when you
|
||||
* \e do have a corresponding icon.
|
||||
*
|
||||
* @param icon The icon to display.
|
||||
* @param text The text that will be displayed.
|
||||
* @param parent The parent of this action.
|
||||
*/
|
||||
KRecentFilesAction(const QIcon &icon, const QString &text, QObject *parent);
|
||||
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
~KRecentFilesAction() override;
|
||||
|
||||
/**
|
||||
* Adds \a action to the list of URLs, with \a url and title \a name.
|
||||
*
|
||||
* Do not use addAction(QAction*), as no url will be associated, and
|
||||
* consequently urlSelected() will not be emitted when \a action is selected.
|
||||
*/
|
||||
void addAction(QAction *action, const QUrl &url, const QString &name, const QMimeType &mimeType = QMimeType());
|
||||
|
||||
/**
|
||||
* Reimplemented for internal reasons.
|
||||
*/
|
||||
QAction *removeAction(QAction *action) override;
|
||||
|
||||
/**
|
||||
* Returns the maximum of items in the recent files list.
|
||||
*/
|
||||
int maxItems() const;
|
||||
|
||||
/**
|
||||
* Sets the maximum of items in the recent files list.
|
||||
* The default for this value is 10 set in the constructor.
|
||||
*
|
||||
* If this value is lesser than the number of items currently
|
||||
* in the recent files list the last items are deleted until
|
||||
* the number of items are equal to the new maximum.
|
||||
*
|
||||
* Negative values will be normalized to 0.
|
||||
*/
|
||||
void setMaxItems(int maxItems);
|
||||
|
||||
/**
|
||||
* Loads the recent files entries from a given KConfigGroup object.
|
||||
* You can provide the name of the group used to load the entries.
|
||||
* If the groupname is empty, entries are loaded from a group called 'RecentFiles'.
|
||||
* Local file entries that do not exist anymore are not restored.
|
||||
*
|
||||
*/
|
||||
void loadEntries(const KConfigGroup &config);
|
||||
|
||||
/**
|
||||
* Saves the current recent files entries to a given KConfigGroup object.
|
||||
* You can provide the name of the group used to load the entries.
|
||||
* If the groupname is empty, entries are saved to a group called 'RecentFiles'.
|
||||
*
|
||||
*/
|
||||
void saveEntries(const KConfigGroup &config);
|
||||
|
||||
/**
|
||||
* Add URL to the recent files list. This will enable this action.
|
||||
*
|
||||
* @param url The URL of the file
|
||||
* @param name The user visible pretty name that appears before the URL
|
||||
*
|
||||
* @note URLs corresponding to local files in the temporary directory
|
||||
* (see @ref QDir::tempPath()) are automatically ignored by this method.
|
||||
*/
|
||||
void addUrl(const QUrl &url, const QString &name = QString());
|
||||
/**
|
||||
* Add URL to the recent files list. This will enable this action.
|
||||
*
|
||||
* @param url The URL of the file
|
||||
* @param name The user visible pretty name that appears before the URL
|
||||
* @param mimeType Specify the mimeType of the file
|
||||
*
|
||||
* @note URLs corresponding to local files in the temporary directory
|
||||
* (see @ref QDir::tempPath()) are automatically ignored by this method.
|
||||
*
|
||||
* @since 6.9
|
||||
*/
|
||||
void addUrl(const QUrl &url, const QString &name, const QString &mimeType);
|
||||
|
||||
/**
|
||||
* Remove an URL from the recent files list.
|
||||
*
|
||||
* @param url The URL of the file
|
||||
*/
|
||||
void removeUrl(const QUrl &url);
|
||||
|
||||
/**
|
||||
* Retrieve a list of all URLs in the recent files list.
|
||||
*/
|
||||
QList<QUrl> urls() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* Clears the recent files list.
|
||||
* Note that there is also an action shown to the user for clearing the list.
|
||||
*/
|
||||
virtual void clear();
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
* This signal gets emitted when the user selects an URL.
|
||||
*
|
||||
* @param url The URL that the user selected.
|
||||
*/
|
||||
void urlSelected(const QUrl &url);
|
||||
|
||||
/**
|
||||
* This signal gets emitted when the user clear list.
|
||||
* So when user store url in specific config file it can saveEntry.
|
||||
* @since 4.3
|
||||
*/
|
||||
void recentListCleared();
|
||||
|
||||
private:
|
||||
// Internal
|
||||
KCONFIGWIDGETS_NO_EXPORT void clearEntries();
|
||||
// Don't warn about the virtual overload. As the comment of the other
|
||||
// addAction() says, addAction( QAction* ) should not be used.
|
||||
using KSelectAction::addAction;
|
||||
|
||||
private:
|
||||
std::unique_ptr<KRecentFilesActionPrivate> const d_ptr;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 1999 Reginald Stadlbauer <reggie@kde.org>
|
||||
SPDX-FileCopyrightText: 1999 Simon Hausmann <hausmann@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Nicolas Hadacek <haadcek@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Kurt Granroth <granroth@kde.org>
|
||||
SPDX-FileCopyrightText: 2000 Michael Koch <koch@kde.org>
|
||||
SPDX-FileCopyrightText: 2001 Holger Freyther <freyther@kde.org>
|
||||
SPDX-FileCopyrightText: 2002 Ellis Whitehead <ellis@kde.org>
|
||||
SPDX-FileCopyrightText: 2002 Joseph Wenninger <jowenn@kde.org>
|
||||
SPDX-FileCopyrightText: 2003 Andras Mantia <amantia@kde.org>
|
||||
SPDX-FileCopyrightText: 2005-2006 Hamish Rodda <rodda@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#ifndef KRECENTFILESACTION_P_H
|
||||
#define KRECENTFILESACTION_P_H
|
||||
|
||||
#include "krecentfilesaction.h"
|
||||
|
||||
class KRecentFilesActionPrivate
|
||||
{
|
||||
Q_DECLARE_PUBLIC(KRecentFilesAction)
|
||||
|
||||
public:
|
||||
explicit KRecentFilesActionPrivate(KRecentFilesAction *parent)
|
||||
: q_ptr(parent)
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~KRecentFilesActionPrivate()
|
||||
{
|
||||
}
|
||||
|
||||
void init();
|
||||
|
||||
void urlSelected(QAction *);
|
||||
|
||||
int m_maxItems = 10;
|
||||
|
||||
struct RecentActionInfo {
|
||||
QAction *action = nullptr;
|
||||
QUrl url;
|
||||
QString shortName;
|
||||
QMimeType mimeType;
|
||||
};
|
||||
std::vector<RecentActionInfo> m_recentActions;
|
||||
|
||||
std::vector<RecentActionInfo>::iterator findByUrl(const QUrl &url)
|
||||
{
|
||||
return std::find_if(m_recentActions.begin(), m_recentActions.end(), [&url](const RecentActionInfo &info) {
|
||||
return info.url == url;
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<RecentActionInfo>::iterator findByAction(const QAction *action)
|
||||
{
|
||||
return std::find_if(m_recentActions.begin(), m_recentActions.end(), [action](const RecentActionInfo &info) {
|
||||
return info.action == action;
|
||||
});
|
||||
}
|
||||
|
||||
void removeAction(std::vector<RecentActionInfo>::iterator it);
|
||||
|
||||
QAction *m_noEntriesAction = nullptr;
|
||||
QAction *clearSeparator = nullptr;
|
||||
QAction *clearAction = nullptr;
|
||||
|
||||
KRecentFilesAction *const q_ptr;
|
||||
};
|
||||
|
||||
#endif // KRECENTFILESACTION_P_H
|
||||
@@ -0,0 +1,731 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 1999, 2000 Kurt Granroth <granroth@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#include "kstandardaction.h"
|
||||
#include "kopenaction_p.h"
|
||||
#include "kstandardaction_p.h"
|
||||
#include "moc_kstandardaction_p.cpp"
|
||||
|
||||
#include <KAboutData>
|
||||
#include <KAcceleratorManager>
|
||||
#include <KLocalizedString>
|
||||
#include <KStandardShortcutWatcher>
|
||||
#include <QGuiApplication>
|
||||
#include <QLayout>
|
||||
#include <QMainWindow>
|
||||
#include <QMenuBar>
|
||||
|
||||
namespace KStandardAction
|
||||
{
|
||||
AutomaticAction::AutomaticAction(const QIcon &icon,
|
||||
const QString &text,
|
||||
KStandardShortcut::StandardShortcut standardShortcut,
|
||||
const char *slot,
|
||||
QObject *parent)
|
||||
: QAction(parent)
|
||||
{
|
||||
setText(text);
|
||||
setIcon(icon);
|
||||
|
||||
const QList<QKeySequence> shortcut = KStandardShortcut::shortcut(standardShortcut);
|
||||
setShortcuts(shortcut);
|
||||
setProperty("defaultShortcuts", QVariant::fromValue(shortcut));
|
||||
connect(KStandardShortcut::shortcutWatcher(),
|
||||
&KStandardShortcut::StandardShortcutWatcher::shortcutChanged,
|
||||
this,
|
||||
[standardShortcut, this](KStandardShortcut::StandardShortcut id, const QList<QKeySequence> &newShortcut) {
|
||||
if (id != standardShortcut) {
|
||||
return;
|
||||
}
|
||||
setShortcuts(newShortcut);
|
||||
setProperty("defaultShortcuts", QVariant::fromValue(newShortcut));
|
||||
});
|
||||
|
||||
connect(this, SIGNAL(triggered()), this, slot);
|
||||
}
|
||||
|
||||
QStringList stdNames()
|
||||
{
|
||||
return internal_stdNames();
|
||||
}
|
||||
|
||||
QList<StandardAction> actionIds()
|
||||
{
|
||||
QList<StandardAction> result;
|
||||
|
||||
for (uint i = 0; g_rgActionInfo[i].id != ActionNone; i++) {
|
||||
result.append(g_rgActionInfo[i].id);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
KStandardShortcut::StandardShortcut shortcutForActionId(StandardAction id)
|
||||
{
|
||||
const KStandardActionInfo *pInfo = infoPtr(id);
|
||||
return (pInfo) ? pInfo->idAccel : KStandardShortcut::AccelNone;
|
||||
}
|
||||
|
||||
class ShowMenubarActionFilter : public QObject
|
||||
{
|
||||
public:
|
||||
ShowMenubarActionFilter(QAction *parent)
|
||||
: QObject(parent)
|
||||
, wasNative(false)
|
||||
, wasChecked(false)
|
||||
, wasVisible(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool eventFilter(QObject * /*watched*/, QEvent *e) override
|
||||
{
|
||||
if (e->type() == QEvent::Show) {
|
||||
updateAction();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void updateAction()
|
||||
{
|
||||
bool allMenuBarsNative = true;
|
||||
bool hasAnyMenuBar = false;
|
||||
const auto lstWidget = qApp->topLevelWidgets();
|
||||
for (QWidget *w : lstWidget) {
|
||||
QMainWindow *mw = qobject_cast<QMainWindow *>(w);
|
||||
if (mw) {
|
||||
mw->installEventFilter(this); // this is just in case a new main window appeared
|
||||
// if we were filtering it already it is almost a noop
|
||||
if (mw->layout() && mw->layout()->menuBar()) {
|
||||
QMenuBar *mb = qobject_cast<QMenuBar *>(mw->layout()->menuBar());
|
||||
if (mb) {
|
||||
hasAnyMenuBar = true;
|
||||
if (!mb->isNativeMenuBar()) {
|
||||
allMenuBarsNative = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasAnyMenuBar) {
|
||||
return;
|
||||
}
|
||||
|
||||
QAction *showMenubarAction = static_cast<QAction *>(parent());
|
||||
if (allMenuBarsNative && !wasNative) {
|
||||
wasNative = true;
|
||||
wasChecked = showMenubarAction->isChecked();
|
||||
wasVisible = showMenubarAction->isVisible();
|
||||
|
||||
showMenubarAction->setChecked(true);
|
||||
showMenubarAction->setVisible(false);
|
||||
} else if (!allMenuBarsNative && wasNative) {
|
||||
showMenubarAction->setChecked(wasChecked);
|
||||
showMenubarAction->setVisible(wasVisible);
|
||||
}
|
||||
}
|
||||
|
||||
bool wasNative;
|
||||
bool wasChecked;
|
||||
bool wasVisible;
|
||||
};
|
||||
|
||||
QAction *_k_createInternal(StandardAction id, QObject *parent)
|
||||
{
|
||||
static bool stdNamesInitialized = false;
|
||||
|
||||
if (!stdNamesInitialized) {
|
||||
KAcceleratorManager::addStandardActionNames(stdNames());
|
||||
stdNamesInitialized = true;
|
||||
}
|
||||
|
||||
QAction *pAction = nullptr;
|
||||
const KStandardActionInfo *pInfo = infoPtr(id);
|
||||
|
||||
// qCDebug(KCONFIG_WIDGETS_LOG) << "KStandardAction::create( " << id << "=" << (pInfo ? pInfo->psName : (const char*)0) << ", " << parent << " )"; // ellis
|
||||
|
||||
if (pInfo) {
|
||||
QString sLabel;
|
||||
QString iconName = pInfo->psIconName.toString();
|
||||
|
||||
switch (id) {
|
||||
case Back:
|
||||
sLabel = i18nc("go back", "&Back");
|
||||
if (QGuiApplication::isRightToLeft()) {
|
||||
iconName = QStringLiteral("go-next");
|
||||
}
|
||||
break;
|
||||
|
||||
case Forward:
|
||||
sLabel = i18nc("go forward", "&Forward");
|
||||
if (QGuiApplication::isRightToLeft()) {
|
||||
iconName = QStringLiteral("go-previous");
|
||||
}
|
||||
break;
|
||||
|
||||
case Home:
|
||||
sLabel = i18nc("home page", "&Home");
|
||||
break;
|
||||
case Preferences:
|
||||
case AboutApp:
|
||||
case HelpContents: {
|
||||
QString appDisplayName = QGuiApplication::applicationDisplayName();
|
||||
if (appDisplayName.isEmpty()) {
|
||||
appDisplayName = QCoreApplication::applicationName();
|
||||
}
|
||||
sLabel = pInfo->psLabel.subs(appDisplayName).toString();
|
||||
} break;
|
||||
default:
|
||||
sLabel = pInfo->psLabel.toString();
|
||||
}
|
||||
|
||||
if (QGuiApplication::isRightToLeft()) {
|
||||
switch (id) {
|
||||
case Prior:
|
||||
iconName = QStringLiteral("go-next-view-page");
|
||||
break;
|
||||
case Next:
|
||||
iconName = QStringLiteral("go-previous-view-page");
|
||||
break;
|
||||
case FirstPage:
|
||||
iconName = QStringLiteral("go-last-view-page");
|
||||
break;
|
||||
case LastPage:
|
||||
iconName = QStringLiteral("go-first-view-page");
|
||||
break;
|
||||
case DocumentBack:
|
||||
iconName = QStringLiteral("go-next");
|
||||
break;
|
||||
case DocumentForward:
|
||||
iconName = QStringLiteral("go-previous");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (id == Donate) {
|
||||
const QString currencyCode = QLocale().currencySymbol(QLocale::CurrencyIsoCode).toLower();
|
||||
if (!currencyCode.isEmpty()) {
|
||||
iconName = QStringLiteral("help-donate-%1").arg(currencyCode);
|
||||
}
|
||||
}
|
||||
|
||||
QIcon icon = iconName.isEmpty() ? QIcon() : QIcon::fromTheme(iconName);
|
||||
|
||||
switch (id) {
|
||||
case Open:
|
||||
pAction = new KOpenAction(parent);
|
||||
break;
|
||||
case OpenRecent:
|
||||
pAction = new KRecentFilesAction(parent);
|
||||
break;
|
||||
case ShowMenubar: {
|
||||
pAction = new KToggleAction(parent);
|
||||
pAction->setWhatsThis(
|
||||
i18n("Show Menubar<p>"
|
||||
"Shows the menubar again after it has been hidden</p>"));
|
||||
pAction->setChecked(true);
|
||||
|
||||
ShowMenubarActionFilter *mf = new ShowMenubarActionFilter(pAction);
|
||||
const auto lstWidget = qApp->topLevelWidgets();
|
||||
for (QWidget *w : lstWidget) {
|
||||
if (qobject_cast<QMainWindow *>(w)) {
|
||||
w->installEventFilter(mf);
|
||||
}
|
||||
}
|
||||
mf->updateAction();
|
||||
break;
|
||||
}
|
||||
case ShowToolbar:
|
||||
pAction = new KToggleAction(parent);
|
||||
pAction->setChecked(true);
|
||||
break;
|
||||
case ShowStatusbar:
|
||||
pAction = new KToggleAction(parent);
|
||||
pAction->setWhatsThis(
|
||||
i18n("Show Statusbar<p>"
|
||||
"Shows the statusbar, which is the bar at the bottom of the window used for status information.</p>"));
|
||||
pAction->setChecked(true);
|
||||
break;
|
||||
case FullScreen:
|
||||
pAction = new KToggleFullScreenAction(parent);
|
||||
pAction->setCheckable(true);
|
||||
break;
|
||||
// Same as default, but with the app icon
|
||||
case AboutApp: {
|
||||
pAction = new QAction(parent);
|
||||
icon = qApp->windowIcon();
|
||||
break;
|
||||
}
|
||||
case HamburgerMenu: {
|
||||
pAction = new KHamburgerMenu(parent);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
pAction = new QAction(parent);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set the text before setting the MenuRole, as on OS X setText will do some heuristic role guessing.
|
||||
// This ensures user menu items get the intended role out of the list below.
|
||||
pAction->setText(sLabel);
|
||||
|
||||
switch (id) {
|
||||
case Quit:
|
||||
pAction->setMenuRole(QAction::QuitRole);
|
||||
break;
|
||||
|
||||
case Preferences:
|
||||
pAction->setMenuRole(QAction::PreferencesRole);
|
||||
break;
|
||||
|
||||
case AboutApp:
|
||||
pAction->setMenuRole(QAction::AboutRole);
|
||||
break;
|
||||
|
||||
default:
|
||||
pAction->setMenuRole(QAction::NoRole);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!pInfo->psToolTip.isEmpty()) {
|
||||
pAction->setToolTip(pInfo->psToolTip.toString());
|
||||
}
|
||||
pAction->setIcon(icon);
|
||||
|
||||
QList<QKeySequence> cut = KStandardShortcut::shortcut(pInfo->idAccel);
|
||||
if (!cut.isEmpty()) {
|
||||
// emulate KActionCollection::setDefaultShortcuts to allow the use of "configure shortcuts"
|
||||
pAction->setShortcuts(cut);
|
||||
pAction->setProperty("defaultShortcuts", QVariant::fromValue(cut));
|
||||
}
|
||||
pAction->connect(KStandardShortcut::shortcutWatcher(),
|
||||
&KStandardShortcut::StandardShortcutWatcher::shortcutChanged,
|
||||
pAction,
|
||||
[pAction, shortcut = pInfo->idAccel](KStandardShortcut::StandardShortcut id, const QList<QKeySequence> &newShortcut) {
|
||||
if (id != shortcut) {
|
||||
return;
|
||||
}
|
||||
pAction->setShortcuts(newShortcut);
|
||||
pAction->setProperty("defaultShortcuts", QVariant::fromValue(newShortcut));
|
||||
});
|
||||
|
||||
pAction->setObjectName(pInfo->psName.toString());
|
||||
}
|
||||
|
||||
if (pAction && parent && parent->inherits("KActionCollection")) {
|
||||
QMetaObject::invokeMethod(parent, "addAction", Q_ARG(QString, pAction->objectName()), Q_ARG(QAction *, pAction));
|
||||
}
|
||||
|
||||
return pAction;
|
||||
}
|
||||
|
||||
QAction *create(StandardAction id, const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
QAction *pAction = _k_createInternal(id, parent);
|
||||
if (recvr && slot) {
|
||||
if (id == OpenRecent) {
|
||||
// FIXME QAction port: probably a good idea to find a cleaner way to do this
|
||||
// Open Recent is a special case - provide the selected URL
|
||||
QObject::connect(pAction, SIGNAL(urlSelected(QUrl)), recvr, slot);
|
||||
} else if (id == ConfigureToolbars) { // #200815
|
||||
QObject::connect(pAction, SIGNAL(triggered(bool)), recvr, slot, Qt::QueuedConnection);
|
||||
} else {
|
||||
QObject::connect(pAction, SIGNAL(triggered(bool)), recvr, slot);
|
||||
}
|
||||
}
|
||||
return pAction;
|
||||
}
|
||||
|
||||
QString name(StandardAction id)
|
||||
{
|
||||
const KStandardActionInfo *pInfo = infoPtr(id);
|
||||
return (pInfo) ? pInfo->psName.toString() : QString();
|
||||
}
|
||||
|
||||
QAction *openNew(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(New, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *open(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Open, recvr, slot, parent);
|
||||
}
|
||||
|
||||
KRecentFilesAction *openRecent(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return (KRecentFilesAction *)KStandardAction::create(OpenRecent, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *save(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Save, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *saveAs(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(SaveAs, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *revert(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Revert, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *print(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Print, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *printPreview(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(PrintPreview, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *close(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Close, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *mail(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Mail, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *quit(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Quit, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *undo(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Undo, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *redo(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Redo, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *cut(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Cut, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *copy(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Copy, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *paste(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Paste, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *clear(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Clear, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *selectAll(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(SelectAll, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *deselect(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Deselect, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *find(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Find, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *findNext(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(FindNext, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *findPrev(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(FindPrev, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *replace(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Replace, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *actualSize(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(ActualSize, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *fitToPage(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(FitToPage, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *fitToWidth(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(FitToWidth, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *fitToHeight(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(FitToHeight, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *zoomIn(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(ZoomIn, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *zoomOut(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(ZoomOut, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *zoom(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Zoom, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *redisplay(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Redisplay, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *up(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Up, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *back(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Back, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *forward(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Forward, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *home(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Home, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *prior(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Prior, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *next(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Next, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *goTo(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Goto, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *gotoPage(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(GotoPage, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *gotoLine(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(GotoLine, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *firstPage(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(FirstPage, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *lastPage(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(LastPage, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *documentBack(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(DocumentBack, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *documentForward(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(DocumentForward, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *addBookmark(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(AddBookmark, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *editBookmarks(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(EditBookmarks, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *spelling(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Spelling, recvr, slot, parent);
|
||||
}
|
||||
|
||||
static QAction *buildAutomaticAction(QObject *parent, StandardAction id, const char *slot)
|
||||
{
|
||||
const KStandardActionInfo *p = infoPtr(id);
|
||||
if (!p) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AutomaticAction *action = new AutomaticAction(QIcon::fromTheme(p->psIconName.toString()), p->psLabel.toString(), p->idAccel, slot, parent);
|
||||
|
||||
action->setObjectName(p->psName.toString());
|
||||
if (!p->psToolTip.isEmpty()) {
|
||||
action->setToolTip(p->psToolTip.toString());
|
||||
}
|
||||
|
||||
if (parent && parent->inherits("KActionCollection")) {
|
||||
QMetaObject::invokeMethod(parent, "addAction", Q_ARG(QString, action->objectName()), Q_ARG(QAction *, action));
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
QAction *cut(QObject *parent)
|
||||
{
|
||||
return buildAutomaticAction(parent, Cut, SLOT(cut()));
|
||||
}
|
||||
|
||||
QAction *copy(QObject *parent)
|
||||
{
|
||||
return buildAutomaticAction(parent, Copy, SLOT(copy()));
|
||||
}
|
||||
|
||||
QAction *paste(QObject *parent)
|
||||
{
|
||||
return buildAutomaticAction(parent, Paste, SLOT(paste()));
|
||||
}
|
||||
|
||||
QAction *clear(QObject *parent)
|
||||
{
|
||||
return buildAutomaticAction(parent, Clear, SLOT(clear()));
|
||||
}
|
||||
|
||||
QAction *selectAll(QObject *parent)
|
||||
{
|
||||
return buildAutomaticAction(parent, SelectAll, SLOT(selectAll()));
|
||||
}
|
||||
|
||||
KToggleAction *showMenubar(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
QAction *ret = KStandardAction::create(ShowMenubar, recvr, slot, parent);
|
||||
Q_ASSERT(qobject_cast<KToggleAction *>(ret));
|
||||
return static_cast<KToggleAction *>(ret);
|
||||
}
|
||||
|
||||
KToggleAction *showStatusbar(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
QAction *ret = KStandardAction::create(ShowStatusbar, recvr, slot, parent);
|
||||
Q_ASSERT(qobject_cast<KToggleAction *>(ret));
|
||||
return static_cast<KToggleAction *>(ret);
|
||||
}
|
||||
|
||||
KToggleFullScreenAction *fullScreen(const QObject *recvr, const char *slot, QWidget *window, QObject *parent)
|
||||
{
|
||||
KToggleFullScreenAction *ret;
|
||||
ret = static_cast<KToggleFullScreenAction *>(KStandardAction::create(FullScreen, recvr, slot, parent));
|
||||
ret->setWindow(window);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QAction *keyBindings(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(KeyBindings, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *preferences(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Preferences, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *configureToolbars(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(ConfigureToolbars, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *configureNotifications(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(ConfigureNotifications, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *helpContents(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(HelpContents, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *whatsThis(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(WhatsThis, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *reportBug(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(ReportBug, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *switchApplicationLanguage(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(SwitchApplicationLanguage, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *aboutApp(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(AboutApp, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *aboutKDE(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(AboutKDE, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *deleteFile(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(DeleteFile, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *renameFile(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(RenameFile, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *moveToTrash(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(MoveToTrash, recvr, slot, parent);
|
||||
}
|
||||
|
||||
QAction *donate(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return KStandardAction::create(Donate, recvr, slot, parent);
|
||||
}
|
||||
|
||||
KHamburgerMenu *hamburgerMenu(const QObject *recvr, const char *slot, QObject *parent)
|
||||
{
|
||||
return static_cast<KHamburgerMenu *>(KStandardAction::create(HamburgerMenu, recvr, slot, parent));
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
This file is part of the KDE libraries
|
||||
SPDX-FileCopyrightText: 1999, 2000 Kurt Granroth <granroth@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-only
|
||||
*/
|
||||
|
||||
#ifndef KSTANDARDACTION_PRIVATE_H
|
||||
#define KSTANDARDACTION_PRIVATE_H
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
|
||||
#include <KLazyLocalizedString>
|
||||
#include <KLocalizedString>
|
||||
#include <KStandardShortcut>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace KStandardAction
|
||||
{
|
||||
|
||||
// Helper class for storing raw data in static tables which can be used for QString instance
|
||||
// creation at runtime without copying/converting to new memalloc'ed memory, as well as avoiding
|
||||
// that way storing the strings directly as QStrings resulting in non-constexpr init code on
|
||||
// library loading
|
||||
struct RawStringData {
|
||||
template<std::size_t StringSize>
|
||||
constexpr inline RawStringData(const char16_t (&_data)[StringSize])
|
||||
: data(_data)
|
||||
, size(std::char_traits<char16_t>::length(_data))
|
||||
{
|
||||
}
|
||||
constexpr inline RawStringData(std::nullptr_t)
|
||||
{
|
||||
}
|
||||
constexpr inline RawStringData() = default;
|
||||
|
||||
inline QString toString() const
|
||||
{
|
||||
if (!data) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
return Qt::Literals::StringLiterals::operator""_s(data, size);
|
||||
}
|
||||
|
||||
private:
|
||||
const char16_t *const data = nullptr;
|
||||
const std::size_t size = 0;
|
||||
};
|
||||
|
||||
struct KStandardActionInfo {
|
||||
StandardAction id;
|
||||
KStandardShortcut::StandardShortcut idAccel;
|
||||
const RawStringData psName;
|
||||
const KLazyLocalizedString psLabel;
|
||||
const KLazyLocalizedString psToolTip;
|
||||
const RawStringData psIconName;
|
||||
};
|
||||
// clang-format off
|
||||
static const KStandardActionInfo g_rgActionInfo[] = {
|
||||
{ New, KStandardShortcut::New, u"file_new", kli18n("&New"), kli18n("Create new document"), u"document-new" },
|
||||
{ Open, KStandardShortcut::Open, u"file_open", kli18n("&Open…"), kli18n("Open an existing document"), u"document-open" },
|
||||
{ OpenRecent, KStandardShortcut::AccelNone, u"file_open_recent", kli18n("Open &Recent"), kli18n("Open a document which was recently opened"), u"document-open-recent" },
|
||||
{ Save, KStandardShortcut::Save, u"file_save", kli18n("&Save"), kli18n("Save document"), u"document-save" },
|
||||
{ SaveAs, KStandardShortcut::SaveAs, u"file_save_as", kli18n("Save &As…"), kli18n("Save document under a new name"), u"document-save-as" },
|
||||
{ Revert, KStandardShortcut::Revert, u"file_revert", kli18n("Re&vert"), kli18n("Revert unsaved changes made to document"), u"document-revert" },
|
||||
{ Close, KStandardShortcut::Close, u"file_close", kli18n("&Close"), kli18n("Close document"), u"document-close" },
|
||||
{ Print, KStandardShortcut::Print, u"file_print", kli18n("&Print…"), kli18n("Print document"), u"document-print" },
|
||||
{ PrintPreview, KStandardShortcut::PrintPreview, u"file_print_preview", kli18n("Print Previe&w"), kli18n("Show a print preview of document"), u"document-print-preview" },
|
||||
{ Mail, KStandardShortcut::Mail, u"file_mail", kli18n("&Mail…"), kli18n("Send document by mail"), u"mail-send" },
|
||||
{ Quit, KStandardShortcut::Quit, u"file_quit", kli18n("&Quit"), kli18n("Quit application"), u"application-exit" },
|
||||
|
||||
{ Undo, KStandardShortcut::Undo, u"edit_undo", kli18n("&Undo"), kli18n("Undo last action"), u"edit-undo" },
|
||||
{ Redo, KStandardShortcut::Redo, u"edit_redo", kli18n("Re&do"), kli18n("Redo last undone action"), u"edit-redo" },
|
||||
{ Cut, KStandardShortcut::Cut, u"edit_cut", kli18n("Cu&t"), kli18n("Cut selection to clipboard"), u"edit-cut" },
|
||||
{ Copy, KStandardShortcut::Copy, u"edit_copy", kli18n("&Copy"), kli18n("Copy selection to clipboard"), u"edit-copy" },
|
||||
{ Paste, KStandardShortcut::Paste, u"edit_paste", kli18n("&Paste"), kli18n("Paste clipboard content"), u"edit-paste" },
|
||||
{ Clear, KStandardShortcut::Clear, u"edit_clear", kli18n("C&lear"), {}, u"edit-clear" },
|
||||
{ SelectAll, KStandardShortcut::SelectAll, u"edit_select_all", kli18n("Select &All"), {}, u"edit-select-all" },
|
||||
{ Deselect, KStandardShortcut::Deselect, u"edit_deselect", kli18n("Dese&lect"), {}, u"edit-select-none" },
|
||||
{ Find, KStandardShortcut::Find, u"edit_find", kli18n("&Find…"), {}, u"edit-find" },
|
||||
{ FindNext, KStandardShortcut::FindNext, u"edit_find_next", kli18n("Find &Next"), {}, u"go-down-search" },
|
||||
{ FindPrev, KStandardShortcut::FindPrev, u"edit_find_prev", kli18n("Find Pre&vious"), {}, u"go-up-search" },
|
||||
{ Replace, KStandardShortcut::Replace, u"edit_replace", kli18n("&Replace…"), {}, u"edit-find-replace" },
|
||||
|
||||
{ ActualSize, KStandardShortcut::ActualSize, u"view_actual_size", kli18n("Zoom to &Actual Size"), kli18n("View document at its actual size"), u"zoom-original" },
|
||||
{ FitToPage, KStandardShortcut::FitToPage, u"view_fit_to_page", kli18n("&Fit to Page"), kli18n("Zoom to fit page in window"), u"zoom-fit-page" },
|
||||
{ FitToWidth, KStandardShortcut::FitToWidth, u"view_fit_to_width", kli18n("Fit to Page &Width"), kli18n("Zoom to fit page width in window"), u"zoom-fit-width" },
|
||||
{ FitToHeight, KStandardShortcut::FitToHeight, u"view_fit_to_height", kli18n("Fit to Page &Height"), kli18n("Zoom to fit page height in window"), u"zoom-fit-height" },
|
||||
{ ZoomIn, KStandardShortcut::ZoomIn, u"view_zoom_in", kli18n("Zoom &In"), {}, u"zoom-in" },
|
||||
{ ZoomOut, KStandardShortcut::ZoomOut, u"view_zoom_out", kli18n("Zoom &Out"), {}, u"zoom-out" },
|
||||
{ Zoom, KStandardShortcut::Zoom, u"view_zoom", kli18n("&Zoom…"), kli18n("Select zoom level"), u"zoom" },
|
||||
{ Redisplay, KStandardShortcut::Reload, u"view_redisplay", kli18n("&Refresh"), kli18n("Refresh document"), u"view-refresh" },
|
||||
|
||||
{ Up, KStandardShortcut::Up, u"go_up", kli18n("&Up"), kli18n("Go up"), u"go-up" },
|
||||
// The following three have special i18n() needs for sLabel
|
||||
{ Back, KStandardShortcut::Back, u"go_back", {}, {}, u"go-previous" },
|
||||
{ Forward, KStandardShortcut::Forward, u"go_forward", {}, {}, u"go-next" },
|
||||
{ Home, KStandardShortcut::Home, u"go_home", {}, {}, u"go-home" },
|
||||
{ Prior, KStandardShortcut::Prior, u"go_previous", kli18n("&Previous Page"), kli18n("Go to previous page"), u"go-previous-view-page" },
|
||||
{ Next, KStandardShortcut::Next, u"go_next", kli18n("&Next Page"), kli18n("Go to next page"), u"go-next-view-page" },
|
||||
{ Goto, KStandardShortcut::Goto, u"go_goto", kli18n("&Go To…"), {}, {} },
|
||||
{ GotoPage, KStandardShortcut::GotoPage, u"go_goto_page", kli18n("&Go to Page…"), {}, u"go-jump" },
|
||||
{ GotoLine, KStandardShortcut::GotoLine, u"go_goto_line", kli18n("&Go to Line…"), {}, {} },
|
||||
{ FirstPage, KStandardShortcut::Begin, u"go_first", kli18n("&First Page"), kli18n("Go to first page"), u"go-first-view-page" },
|
||||
{ LastPage, KStandardShortcut::End, u"go_last", kli18n("&Last Page"), kli18n("Go to last page"), u"go-last-view-page" },
|
||||
{ DocumentBack, KStandardShortcut::DocumentBack, u"go_document_back", kli18n("&Back"), kli18n("Go back in document"), u"go-previous" },
|
||||
{ DocumentForward, KStandardShortcut::DocumentForward, u"go_document_forward", kli18n("&Forward"), kli18n("Go forward in document"), u"go-next" },
|
||||
|
||||
{ AddBookmark, KStandardShortcut::AddBookmark, u"bookmark_add", kli18n("&Add Bookmark"), {}, u"bookmark-new" },
|
||||
{ EditBookmarks, KStandardShortcut::EditBookmarks, u"bookmark_edit", kli18n("&Edit Bookmarks…"), {}, u"bookmarks-organize" },
|
||||
|
||||
{ Spelling, KStandardShortcut::Spelling, u"tools_spelling", kli18n("&Spelling…"), kli18n("Check spelling in document"), u"tools-check-spelling" },
|
||||
|
||||
{ ShowMenubar, KStandardShortcut::ShowMenubar, u"options_show_menubar", kli18n("Show &Menubar"), kli18n("Show or hide menubar"), u"show-menu" },
|
||||
{ ShowToolbar, KStandardShortcut::ShowToolbar, u"options_show_toolbar", kli18n("Show &Toolbar"), kli18n("Show or hide toolbar"), {} },
|
||||
{ ShowStatusbar, KStandardShortcut::ShowStatusbar, u"options_show_statusbar", kli18n("Show St&atusbar"), kli18n("Show or hide statusbar"), {} },
|
||||
{ FullScreen, KStandardShortcut::FullScreen, u"fullscreen", kli18n("F&ull Screen Mode"), {}, u"view-fullscreen" },
|
||||
{ KeyBindings, KStandardShortcut::KeyBindings, u"options_configure_keybinding", kli18n("Configure Keyboard S&hortcuts…"), {}, u"configure-shortcuts" },
|
||||
{ Preferences, KStandardShortcut::Preferences, u"options_configure", kli18n("&Configure %1…"), {}, u"configure" },
|
||||
{ ConfigureToolbars, KStandardShortcut::ConfigureToolbars, u"options_configure_toolbars", kli18n("Configure Tool&bars…"), {}, u"configure-toolbars" },
|
||||
{ ConfigureNotifications, KStandardShortcut::ConfigureNotifications, u"options_configure_notifications", kli18n("Configure &Notifications…"), {}, u"preferences-desktop-notification" },
|
||||
|
||||
// the idea here is that Contents is used in menus, and Help in dialogs, so both share the same
|
||||
// shortcut
|
||||
{ HelpContents, KStandardShortcut::Help, u"help_contents", kli18n("%1 &Handbook"), {}, u"help-contents" },
|
||||
{ WhatsThis, KStandardShortcut::WhatsThis, u"help_whats_this", kli18n("What's &This?"), {}, u"help-contextual" },
|
||||
{ ReportBug, KStandardShortcut::ReportBug, u"help_report_bug", kli18n("&Report Bug…"), {}, u"tools-report-bug" },
|
||||
{ SwitchApplicationLanguage, KStandardShortcut::SwitchApplicationLanguage, u"switch_application_language", kli18n("Configure &Language…"), {}, u"preferences-desktop-locale" },
|
||||
{ AboutApp, KStandardShortcut::AccelNone, u"help_about_app", kli18n("&About %1"), {}, nullptr },
|
||||
{ AboutKDE, KStandardShortcut::AccelNone, u"help_about_kde", kli18n("About &KDE"), {}, u"kde" },
|
||||
{ DeleteFile, KStandardShortcut::DeleteFile, u"deletefile", kli18n("&Delete"), {}, u"edit-delete" },
|
||||
{ RenameFile, KStandardShortcut::RenameFile, u"renamefile", kli18n("&Rename…"), {}, u"edit-rename" },
|
||||
{ MoveToTrash, KStandardShortcut::MoveToTrash, u"movetotrash", kli18n("&Move to Trash"), {}, u"trash-empty" },
|
||||
{ Donate, KStandardShortcut::Donate, u"help_donate", kli18n("&Donate"), {}, u"help-donate"},
|
||||
{ HamburgerMenu, KStandardShortcut::OpenMainMenu, u"hamburger_menu", kli18n("Open &Menu"), {}, u"application-menu" },
|
||||
{ ActionNone, KStandardShortcut::AccelNone, nullptr, {}, {}, nullptr }
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
inline const KStandardActionInfo *infoPtr(StandardAction id)
|
||||
{
|
||||
for (uint i = 0; g_rgActionInfo[i].id != ActionNone; i++) {
|
||||
if (g_rgActionInfo[i].id == id) {
|
||||
return &g_rgActionInfo[i];
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static inline QStringList internal_stdNames()
|
||||
{
|
||||
QStringList result;
|
||||
|
||||
for (uint i = 0; g_rgActionInfo[i].id != ActionNone; i++)
|
||||
if (!g_rgActionInfo[i].psLabel.isEmpty()) {
|
||||
if (QByteArrayView(g_rgActionInfo[i].psLabel.untranslatedText()).contains("%1"))
|
||||
// Prevents KLocalizedString::toString() from complaining about unsubstituted placeholder.
|
||||
{
|
||||
result.append(g_rgActionInfo[i].psLabel.subs(QString()).toString());
|
||||
} else {
|
||||
result.append(g_rgActionInfo[i].psLabel.toString());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class AutomaticAction : public QAction
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AutomaticAction(const QIcon &icon, const QString &text, KStandardShortcut::StandardShortcut standardShortcut, const char *slot, QObject *parent);
|
||||
public Q_SLOTS:
|
||||
inline void cut()
|
||||
{
|
||||
invokeEditSlot("cut");
|
||||
}
|
||||
inline void copy()
|
||||
{
|
||||
invokeEditSlot("copy");
|
||||
}
|
||||
inline void paste()
|
||||
{
|
||||
invokeEditSlot("paste");
|
||||
}
|
||||
inline void clear()
|
||||
{
|
||||
invokeEditSlot("clear");
|
||||
}
|
||||
inline void selectAll()
|
||||
{
|
||||
invokeEditSlot("selectAll");
|
||||
}
|
||||
|
||||
void invokeEditSlot(const char *slot)
|
||||
{
|
||||
if (qApp->focusWidget()) {
|
||||
QMetaObject::invokeMethod(qApp->focusWidget(), slot);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2024 Christoph Cullmann <cullmann@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "kstylemanager.h"
|
||||
|
||||
#include <KActionMenu>
|
||||
#include <KConfigGroup>
|
||||
#include <KLocalizedString>
|
||||
#include <KSharedConfig>
|
||||
|
||||
#include <QAction>
|
||||
#include <QActionGroup>
|
||||
#include <QApplication>
|
||||
#include <QStyle>
|
||||
#include <QStyleFactory>
|
||||
|
||||
#include <private/qguiapplication_p.h>
|
||||
#include <qpa/qplatformtheme.h>
|
||||
|
||||
void KStyleManager::initStyle()
|
||||
{
|
||||
// do nothing if we have the proper platform theme already
|
||||
if (QGuiApplicationPrivate::platformTheme() && QGuiApplicationPrivate::platformTheme()->name() == QLatin1String("kde")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get config, with fallback to kdeglobals
|
||||
const auto config = KSharedConfig::openConfig();
|
||||
|
||||
// enforce the style configured by the user, with kdeglobals fallback
|
||||
// if not set or the style is not there, use Breeze
|
||||
QString styleToUse = KConfigGroup(config, QStringLiteral("KDE")).readEntry("widgetStyle", QString());
|
||||
if (styleToUse.isEmpty() || !QApplication::setStyle(styleToUse)) {
|
||||
styleToUse = QStringLiteral("breeze");
|
||||
QApplication::setStyle(styleToUse);
|
||||
}
|
||||
}
|
||||
|
||||
QAction *KStyleManager::createConfigureAction(QObject *parent)
|
||||
{
|
||||
// if we are running with our application theme, just return a disabled & hidden action
|
||||
// there we just follow the Plasma theme
|
||||
if (QGuiApplicationPrivate::platformTheme() && QGuiApplicationPrivate::platformTheme()->name() == QLatin1String("kde")) {
|
||||
QAction *a = new QAction(parent);
|
||||
a->setEnabled(false);
|
||||
a->setVisible(false);
|
||||
return a;
|
||||
}
|
||||
|
||||
// else: provide style chooser
|
||||
|
||||
// get config, without fallback to kdeglobals
|
||||
const auto config = KSharedConfig::openConfig(QString(), KConfig::NoGlobals);
|
||||
const QString styleWeUse = KConfigGroup(config, QStringLiteral("KDE")).readEntry("widgetStyle", QString());
|
||||
|
||||
// build menu with default to reset setting and all known styles
|
||||
KActionMenu *menu = new KActionMenu(QIcon::fromTheme(QStringLiteral("preferences-desktop-theme-applications")), i18n("Application Style"), parent);
|
||||
QActionGroup *group = new QActionGroup(menu);
|
||||
const QStringList styles = QStringList() << QString() << QStyleFactory::keys();
|
||||
for (const QString &style : styles) {
|
||||
QAction *action = new QAction(style.isEmpty() ? i18n("Default") : style, menu);
|
||||
action->setData(style);
|
||||
action->setActionGroup(group);
|
||||
action->setCheckable(true);
|
||||
if (style.toLower() == styleWeUse.toLower()) {
|
||||
action->setChecked(true);
|
||||
}
|
||||
menu->addAction(action);
|
||||
}
|
||||
|
||||
// toggle style change
|
||||
QObject::connect(group, &QActionGroup::triggered, group, [](QAction *action) {
|
||||
const QString styleToUse = action->data().toString();
|
||||
const auto config = KSharedConfig::openConfig();
|
||||
if (styleToUse.isEmpty()) {
|
||||
KConfigGroup(config, QStringLiteral("KDE")).deleteEntry("widgetStyle");
|
||||
} else {
|
||||
KConfigGroup(config, QStringLiteral("KDE")).writeEntry("widgetStyle", styleToUse);
|
||||
}
|
||||
initStyle();
|
||||
});
|
||||
|
||||
return menu;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2024 Christoph Cullmann <cullmann@kde.org>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KSTYLEMANAGER_H
|
||||
#define KSTYLEMANAGER_H
|
||||
|
||||
#include <kconfigwidgets_export.h>
|
||||
|
||||
class QAction;
|
||||
class QObject;
|
||||
|
||||
/**
|
||||
* Manager for Qt widget styles
|
||||
*/
|
||||
namespace KStyleManager
|
||||
{
|
||||
|
||||
/**
|
||||
* Enforces the style configured by the user with fallback to the Breeze style.
|
||||
*
|
||||
* (following the settings in the application configuration with fallback to kdeglobals)
|
||||
*
|
||||
* Must be called after one has constructed the QApplication.
|
||||
*
|
||||
* If the application is already using the KDE platform theme, the style will not
|
||||
* be touched and the platform theme will ensure proper styling.
|
||||
*
|
||||
* @since 6.3
|
||||
*/
|
||||
KCONFIGWIDGETS_EXPORT void initStyle();
|
||||
|
||||
/**
|
||||
* Creates an action to configure the current used style.
|
||||
*
|
||||
* If the application is running using the KDE platform theme, this will
|
||||
* just return a disabled and hidden action, else it will provide an action to
|
||||
* show a menu to select the style to use for this applications.
|
||||
*
|
||||
* @since 6.3
|
||||
*/
|
||||
KCONFIGWIDGETS_EXPORT QAction *createConfigureAction(QObject *parent = nullptr);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
|
||||
SPDX-FileContributor: Stephen Kelly <stephen@kdab.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef KVIEWSTATEMAINTAINER_H
|
||||
#define KVIEWSTATEMAINTAINER_H
|
||||
|
||||
#include <KViewStateMaintainerBase>
|
||||
|
||||
#include <QAbstractItemView>
|
||||
|
||||
#include <KConfigGroup>
|
||||
|
||||
/**
|
||||
* @class KViewStateMaintainer kviewstatemaintainer.h KViewStateMaintainer
|
||||
*
|
||||
* @brief Encapsulates the maintenance of state between resets of QAbstractItemModel
|
||||
*
|
||||
* @code
|
||||
* m_collectionViewStateMaintainer = new KViewStateMaintainer<Akonadi::ETMViewStateSaver>(KSharedConfig::openConfig()->group("collectionView"));
|
||||
* m_collectionViewStateMaintainer->setView(m_collectionView);
|
||||
*
|
||||
* m_collectionCheckStateMaintainer = new KViewStateMaintainer<Akonadi::ETMViewStateSaver>(KSharedConfig::openConfig()->group("collectionCheckState"));
|
||||
* m_collectionCheckStateMaintainer->setSelectionModel(m_checkableProxy->selectionModel());
|
||||
* @endcode
|
||||
*
|
||||
* @see KConfigViewStateSaver
|
||||
*/
|
||||
template<typename StateSaver>
|
||||
class KViewStateMaintainer : public KViewStateMaintainerBase
|
||||
{
|
||||
typedef StateSaver StateRestorer;
|
||||
|
||||
public:
|
||||
KViewStateMaintainer(const KConfigGroup &configGroup, QObject *parent = nullptr)
|
||||
: KViewStateMaintainerBase(parent)
|
||||
, m_configGroup(configGroup)
|
||||
{
|
||||
}
|
||||
|
||||
/* reimp */ void saveState()
|
||||
{
|
||||
StateSaver saver;
|
||||
saver.setView(view());
|
||||
saver.setSelectionModel(selectionModel());
|
||||
saver.saveState(m_configGroup);
|
||||
m_configGroup.sync();
|
||||
}
|
||||
|
||||
/* reimp */ void restoreState()
|
||||
{
|
||||
StateRestorer *restorer = new StateRestorer;
|
||||
restorer->setView(view());
|
||||
restorer->setSelectionModel(selectionModel());
|
||||
restorer->restoreState(m_configGroup);
|
||||
}
|
||||
|
||||
private:
|
||||
KConfigGroup m_configGroup;
|
||||
};
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user