diff --git a/sidebar/SidebarMode.cpp b/sidebar/SidebarMode.cpp index ebacd1be..39436d35 100644 --- a/sidebar/SidebarMode.cpp +++ b/sidebar/SidebarMode.cpp @@ -1,612 +1,626 @@ /************************************************************************** * Copyright (C) 2009 by Ben Cooksley * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License * * as published by the Free Software Foundation; either version 2 * * of the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * * 02110-1301, USA. * ***************************************************************************/ #include "SidebarMode.h" #include "MenuItem.h" #include "MenuModel.h" #include "ModuleView.h" #include "MenuProxyModel.h" #include "BaseData.h" #include "ToolTips/tooltipmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KAStats = KActivities::Stats; using namespace KAStats; using namespace KAStats::Terms; K_PLUGIN_FACTORY( SidebarModeFactory, registerPlugin(); ) FocusHackWidget::FocusHackWidget(QWidget *parent) : QWidget(parent) {} FocusHackWidget::~FocusHackWidget() {} void FocusHackWidget::focusNext() { focusNextChild(); } void FocusHackWidget::focusPrevious() { focusNextPrevChild(false); } class SubcategoryModel : public QStandardItemModel { public: explicit SubcategoryModel(QAbstractItemModel *parentModel, QObject *parent = nullptr) : QStandardItemModel(parent), m_parentModel(parentModel) {} void setParentIndex(const QModelIndex &activeModule) { blockSignals(true); //make the view receive a single signal when the new subcategory is loaded, //never make the view believe there are zero items if this is not the final count //this avoids the brief flash it had clear(); const int subRows = m_parentModel->rowCount(activeModule); if ( subRows > 1) { for (int i = 0; i < subRows; ++i) { const QModelIndex& index = m_parentModel->index(i, 0, activeModule); QStandardItem *item = new QStandardItem(m_parentModel->data(index, Qt::DecorationRole).value(), m_parentModel->data(index, Qt::DisplayRole).toString()); item->setData(index.data(Qt::UserRole), Qt::UserRole); appendRow(item); } } blockSignals(false); beginResetModel(); endResetModel(); } private: QAbstractItemModel *m_parentModel; }; class MostUsedModel : public QSortFilterProxyModel { public: explicit MostUsedModel(QObject *parent = nullptr) : QSortFilterProxyModel (parent) { sort(0, Qt::DescendingOrder); setSortRole(ResultModel::ScoreRole); setDynamicSortFilter(true); //prepare default items m_defaultModel = new QStandardItemModel(this); QStandardItem *item = new QStandardItem(); item->setData(QUrl(QStringLiteral("kcm:kcm_lookandfeel.desktop")), ResultModel::ResourceRole); m_defaultModel->appendRow(item); item = new QStandardItem(); item->setData(QUrl(QStringLiteral("kcm:user_manager.desktop")), ResultModel::ResourceRole); m_defaultModel->appendRow(item); item = new QStandardItem(); item->setData(QUrl(QStringLiteral("kcm:screenlocker.desktop")), ResultModel::ResourceRole); m_defaultModel->appendRow(item); item = new QStandardItem(); item->setData(QUrl(QStringLiteral("kcm:powerdevilprofilesconfig.desktop")), ResultModel::ResourceRole); m_defaultModel->appendRow(item); item = new QStandardItem(); item->setData(QUrl(QStringLiteral("kcm:kcm_kscreen.desktop")), ResultModel::ResourceRole); m_defaultModel->appendRow(item); } void setResultModel(ResultModel *model) { if (m_resultModel == model) { return; } auto updateModel = [this]() { if (m_resultModel->rowCount() >= 5) { setSourceModel(m_resultModel); } else { setSourceModel(m_defaultModel); } }; m_resultModel = model; connect(m_resultModel, &QAbstractItemModel::rowsInserted, this, updateModel); connect(m_resultModel, &QAbstractItemModel::rowsRemoved, this, updateModel); updateModel(); } QHash roleNames() const override { QHash roleNames; roleNames.insert(Qt::DisplayRole, "display"); roleNames.insert(Qt::DecorationRole, "decoration"); roleNames.insert(ResultModel::ScoreRole, "score"); return roleNames; } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { MenuItem *mi; const QString desktopName = QSortFilterProxyModel::data(index, ResultModel::ResourceRole).toUrl().path(); if (m_menuItems.contains(desktopName)) { mi = m_menuItems.value(desktopName); } else { mi = new MenuItem(false, nullptr); const_cast(this)->m_menuItems.insert(desktopName, mi); KService::Ptr service = KService::serviceByStorageId(desktopName); if (!service || !service->isValid()) { qWarning()<forgetResource(QStringLiteral("kcm:") % desktopName); return QVariant(); } mi->setService(service); } switch (role) { case Qt::UserRole: return QVariant::fromValue(mi); case Qt::DisplayRole: if (mi->service() && mi->service()->isValid()) { return mi->service()->name(); } else { return QVariant(); } case Qt::DecorationRole: if (mi->service() && mi->service()->isValid()) { return mi->service()->icon(); } else { return QVariant(); } case ResultModel::ScoreRole: return QSortFilterProxyModel::data(index, ResultModel::ScoreRole).toInt(); default: return QVariant(); } } private: QHash m_menuItems; QStandardItemModel *m_defaultModel; ResultModel *m_resultModel; }; class SidebarMode::Private { public: Private() : quickWidget( nullptr ), moduleView( nullptr ), collection( nullptr ), activeCategory( -1 ), activeSubCategory( -1 ) {} virtual ~Private() { delete aboutIcon; } ToolTipManager *toolTipManager = nullptr; ToolTipManager *subCategoryToolTipManager = nullptr; + ToolTipManager *mostUsedToolTipManager = nullptr; QQuickWidget * quickWidget = nullptr; KPackage::Package package; SubcategoryModel * subCategoryModel = nullptr; MostUsedModel * mostUsedModel = nullptr; FocusHackWidget * mainWidget = nullptr; QQuickWidget * placeHolderWidget = nullptr; QHBoxLayout * mainLayout = nullptr; KDeclarative::KDeclarative kdeclarative; MenuProxyModel * categorizedModel = nullptr; MenuProxyModel * searchModel = nullptr; KAboutData * aboutIcon = nullptr; ModuleView * moduleView = nullptr; KActionCollection *collection = nullptr; QPersistentModelIndex activeCategoryIndex; int activeCategory; int activeSubCategory; bool m_actionMenuVisible = false; void setActionMenuVisible(SidebarMode* sidebarMode, const bool &actionMenuVisible) { if (m_actionMenuVisible == actionMenuVisible) { return; } m_actionMenuVisible = actionMenuVisible; emit sidebarMode->actionMenuVisibleChanged(); } }; SidebarMode::SidebarMode( QObject *parent, const QVariantList& ) : BaseMode( parent ) , d( new Private() ) { qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); d->aboutIcon = new KAboutData( QStringLiteral("SidebarView"), i18n( "Sidebar View" ), QStringLiteral("1.0"), i18n( "Provides a categorized sidebar for control modules." ), KAboutLicense::GPL, i18n( "(c) 2017, Marco Martin" ) ); d->aboutIcon->addAuthor( i18n( "Marco Martin" ), i18n( "Author" ), QStringLiteral("mart@kde.org") ); d->aboutIcon->addAuthor( i18n( "Ben Cooksley" ), i18n( "Author" ), QStringLiteral("bcooksley@kde.org") ); d->aboutIcon->addAuthor( i18n( "Mathias Soeken" ), i18n( "Developer" ), QStringLiteral("msoeken@informatik.uni-bremen.de") ); qmlRegisterType(); } SidebarMode::~SidebarMode() { delete d; } KAboutData * SidebarMode::aboutData() { return d->aboutIcon; } ModuleView * SidebarMode::moduleView() const { return d->moduleView; } QWidget * SidebarMode::mainWidget() { if( !d->quickWidget ) { initWidget(); } return d->mainWidget; } QAbstractItemModel * SidebarMode::categoryModel() const { return d->searchModel; } QAbstractItemModel * SidebarMode::subCategoryModel() const { return d->subCategoryModel; } QAbstractItemModel * SidebarMode::mostUsedModel() const { return d->mostUsedModel; } QList SidebarMode::views() const { QList list; //list.append( d->categoryView ); return list; } void SidebarMode::initEvent() { MenuModel * model = new MenuModel( rootItem(), this ); foreach( MenuItem * child, rootItem()->children() ) { model->addException( child ); } d->categorizedModel = new MenuProxyModel( this ); d->categorizedModel->setCategorizedModel( true ); d->categorizedModel->setSourceModel( model ); d->categorizedModel->sort( 0 ); d->categorizedModel->setFilterHighlightsEntries( false ); d->searchModel = new MenuProxyModel( this ); d->searchModel->setFilterHighlightsEntries( false ); d->searchModel->setSourceModel( d->categorizedModel ); connect( d->searchModel, &MenuProxyModel::filterRegExpChanged, this, [this] () { if (d->activeCategoryIndex.isValid() && d->activeCategoryIndex.row() >= 0) { d->subCategoryModel->setParentIndex( d->activeCategoryIndex ); emit activeCategoryChanged(); } }); d->mostUsedModel = new MostUsedModel( this ); d->subCategoryModel = new SubcategoryModel( d->searchModel, this ); d->mainWidget = new FocusHackWidget(); d->mainWidget->installEventFilter(this); d->mainLayout = new QHBoxLayout(d->mainWidget); d->mainLayout->setContentsMargins(0, 0, 0, 0); d->moduleView = new ModuleView( d->mainWidget ); connect( d->moduleView, &ModuleView::moduleChanged, this, &SidebarMode::moduleLoaded ); d->quickWidget = nullptr; moduleView()->setFaceType(KPageView::Plain); } QAction *SidebarMode::action(const QString &name) const { if (!d->collection) { return nullptr; } return d->collection->action(name); } QString SidebarMode::actionIconName(const QString &name) const { if (QAction *a = action(name)) { return a->icon().name(); } return QString(); } void SidebarMode::requestToolTip(int index, const QRectF &rect) { if (showToolTips()) { d->toolTipManager->requestToolTip(d->searchModel->index(index, 0), rect.toRect()); } } void SidebarMode::requestSubCategoryToolTip(int index, const QRectF &rect) { if (showToolTips()) { d->subCategoryToolTipManager->requestToolTip(d->subCategoryModel->index(index, 0), rect.toRect()); } } +void SidebarMode::requestMostUsedToolTip(int index, const QRectF &rect) +{ + if (showToolTips()) { + d->mostUsedToolTipManager->requestToolTip(d->mostUsedModel->index(index, 0), rect.toRect()); + } +} + void SidebarMode::hideToolTip() { d->toolTipManager->hideToolTip(); } void SidebarMode::hideSubCategoryToolTip() { d->subCategoryToolTipManager->hideToolTip(); } +void SidebarMode::hideMostUsedToolTip() +{ + d->mostUsedToolTipManager->hideToolTip(); +} + void SidebarMode::loadMostUsed(int index) { const QModelIndex idx = d->mostUsedModel->index(index, 0); d->moduleView->closeModules(); d->moduleView->loadModule( idx ); } void SidebarMode::showActionMenu(const QPoint &position) { QMenu *menu = new QMenu(); connect(menu, &QMenu::aboutToHide, this, [this] () { d->setActionMenuVisible(this, false); } ); menu->setAttribute(Qt::WA_DeleteOnClose); const QStringList actionList { QStringLiteral("configure"), QStringLiteral("help_contents"), QStringLiteral("help_about_app"), QStringLiteral("help_about_kde") }; for (const QString &actionName : actionList) { menu->addAction(d->collection->action(actionName)); } menu->popup(position); d->setActionMenuVisible(this, true); } void SidebarMode::changeModule( const QModelIndex& activeModule ) { d->moduleView->closeModules(); const int subRows = d->searchModel->rowCount(activeModule); if ( subRows < 2) { d->moduleView->loadModule( activeModule ); } else { d->moduleView->loadModule( d->searchModel->index(0, 0, activeModule) ); } d->subCategoryModel->setParentIndex( activeModule ); } void SidebarMode::moduleLoaded() { d->placeHolderWidget->hide(); d->moduleView->show(); } int SidebarMode::activeCategory() const { return d->searchModel->mapFromSource(d->searchModel->sourceModel()->index(d->activeCategory, 0)).row(); } void SidebarMode::setActiveCategory(int cat) { const QModelIndex idx = d->searchModel->index(cat, 0); const int newCategoryRow = d->searchModel->mapToSource(idx).row(); if (d->activeCategory == newCategoryRow) { return; } if( !d->moduleView->resolveChanges() ) { return; } d->activeCategoryIndex = idx; d->activeCategory = newCategoryRow; changeModule(idx); d->activeSubCategory = 0; emit activeCategoryChanged(); emit activeSubCategoryChanged(); } void SidebarMode::setActiveSubCategory(int cat) { if (d->activeSubCategory == cat) { return; } if( !d->moduleView->resolveChanges() ) { return; } d->activeSubCategory = cat; d->moduleView->closeModules(); d->moduleView->loadModule( d->subCategoryModel->index(cat, 0) ); emit activeSubCategoryChanged(); } int SidebarMode::width() const { return d->mainWidget->width(); } bool SidebarMode::actionMenuVisible() const { return d->m_actionMenuVisible; } int SidebarMode::activeSubCategory() const { return d->activeSubCategory; } void SidebarMode::initWidget() { // Create the widgets if (!KMainWindow::memberList().isEmpty()) { KXmlGuiWindow *mainWindow = qobject_cast(KMainWindow::memberList().first()); if (mainWindow) { d->collection = mainWindow->actionCollection(); } } d->quickWidget = new QQuickWidget(d->mainWidget); d->quickWidget->quickWindow()->setTitle(i18n("Sidebar")); d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); d->quickWidget->engine()->rootContext()->setContextProperty(QStringLiteral("systemsettings"), this); d->package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KPackage/GenericQML")); d->package.setPath(QStringLiteral("org.kde.systemsettings.sidebar")); d->kdeclarative.setDeclarativeEngine(d->quickWidget->engine()); d->kdeclarative.setupEngine(d->quickWidget->engine()); d->kdeclarative.setupContext(); d->quickWidget->setSource(QUrl::fromLocalFile(d->package.filePath("mainscript"))); if (!d->quickWidget->rootObject()) { for (const auto &err : d->quickWidget->errors()) { qWarning() << err.toString(); } qFatal("Fatal error while loading the sidebar view qml component"); } const int rootImplicitWidth = d->quickWidget->rootObject()->property("implicitWidth").toInt(); if (rootImplicitWidth != 0) { d->quickWidget->setFixedWidth(rootImplicitWidth); } else { d->quickWidget->setFixedWidth(240); } connect(d->quickWidget->rootObject(), &QQuickItem::implicitWidthChanged, this, [this]() { const int rootImplicitWidth = d->quickWidget->rootObject()->property("implicitWidth").toInt(); if (rootImplicitWidth != 0) { d->quickWidget->setFixedWidth(rootImplicitWidth); } else { d->quickWidget->setFixedWidth(240); } }); connect(d->quickWidget->rootObject(), SIGNAL(focusNextRequest()), d->mainWidget, SLOT(focusNext())); connect(d->quickWidget->rootObject(), SIGNAL(focusPreviousRequest()), d->mainWidget, SLOT(focusPrevious())); d->quickWidget->installEventFilter(this); - d->toolTipManager = new ToolTipManager(d->searchModel, d->quickWidget); - d->subCategoryToolTipManager = new ToolTipManager(d->subCategoryModel, d->quickWidget); - d->placeHolderWidget = new QQuickWidget(d->mainWidget); d->placeHolderWidget->quickWindow()->setTitle(i18n("Most Used")); d->placeHolderWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); d->placeHolderWidget->engine()->rootContext()->setContextObject(new KLocalizedContext(d->placeHolderWidget)); d->placeHolderWidget->engine()->rootContext()->setContextProperty(QStringLiteral("systemsettings"), this); d->placeHolderWidget->setSource(QUrl::fromLocalFile(d->package.filePath("ui", QStringLiteral("introPage.qml")))); connect(d->placeHolderWidget->rootObject(), SIGNAL(focusNextRequest()), d->mainWidget, SLOT(focusNext())); connect(d->placeHolderWidget->rootObject(), SIGNAL(focusPreviousRequest()), d->mainWidget, SLOT(focusPrevious())); d->placeHolderWidget->installEventFilter(this); d->mainLayout->addWidget( d->quickWidget ); d->moduleView->hide(); d->mainLayout->addWidget( d->moduleView ); d->mainLayout->addWidget( d->placeHolderWidget ); emit changeToolBarItems(BaseMode::NoItems); + d->toolTipManager = new ToolTipManager(d->searchModel, d->quickWidget, ToolTipManager::ToolTipPosition::Right); + d->subCategoryToolTipManager = new ToolTipManager(d->subCategoryModel, d->quickWidget, ToolTipManager::ToolTipPosition::Right); + d->mostUsedToolTipManager = new ToolTipManager(d->mostUsedModel, d->placeHolderWidget, ToolTipManager::ToolTipPosition::BottomCenter); + d->mostUsedModel->setResultModel(new ResultModel( AllResources | Agent(QStringLiteral("org.kde.systemsettings")) | HighScoredFirst | Limit(5), this)); } bool SidebarMode::eventFilter(QObject* watched, QEvent* event) { //FIXME: those are all workarounds around the QQuickWidget brokeness if ((watched == d->quickWidget || watched == d->placeHolderWidget) && event->type() == QEvent::KeyPress) { //allow tab navigation inside the qquickwidget QKeyEvent *ke = static_cast(event); QQuickWidget *qqw = static_cast(watched); if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) { QCoreApplication::sendEvent(qqw->quickWindow(), event); return true; } } else if ((watched == d->quickWidget || watched == d->placeHolderWidget) && event->type() == QEvent::FocusIn) { QFocusEvent *fe = static_cast(event); QQuickWidget *qqw = static_cast(watched); if (qqw && qqw->rootObject()) { if (fe->reason() == Qt::TabFocusReason) { QMetaObject::invokeMethod(qqw->rootObject(), "focusFirstChild"); } else if (fe->reason() == Qt::BacktabFocusReason) { QMetaObject::invokeMethod(qqw->rootObject(), "focusLastChild"); } } } else if (watched == d->quickWidget && event->type() == QEvent::Leave) { QCoreApplication::sendEvent(d->quickWidget->quickWindow(), event); } else if (watched == d->mainWidget && event->type() == QEvent::Resize) { emit widthChanged(); } else if (watched == d->mainWidget && event->type() == QEvent::Show) { emit changeToolBarItems(BaseMode::NoItems); } return BaseMode::eventFilter(watched, event); } void SidebarMode::giveFocus() { d->quickWidget->setFocus(); } #include "SidebarMode.moc" diff --git a/sidebar/SidebarMode.h b/sidebar/SidebarMode.h index eb894e0f..bca5f293 100644 --- a/sidebar/SidebarMode.h +++ b/sidebar/SidebarMode.h @@ -1,108 +1,110 @@ /*************************************************************************** * Copyright (C) 2009 by Ben Cooksley * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * ***************************************************************************/ #ifndef SIDEBARMODE_H #define SIDEBARMODE_H #include "BaseMode.h" #include class ModuleView; class KAboutData; class QModelIndex; class QAbstractItemView; class QAbstractItemModel; class QAction; class FocusHackWidget : public QWidget { Q_OBJECT public: explicit FocusHackWidget(QWidget *parent = nullptr); ~FocusHackWidget() override; public Q_SLOTS: void focusNext(); void focusPrevious(); }; class SidebarMode : public BaseMode { Q_OBJECT Q_PROPERTY(QAbstractItemModel *categoryModel READ categoryModel CONSTANT) Q_PROPERTY(QAbstractItemModel *subCategoryModel READ subCategoryModel CONSTANT) Q_PROPERTY(QAbstractItemModel *mostUsedModel READ mostUsedModel CONSTANT) Q_PROPERTY(int activeCategory READ activeCategory WRITE setActiveCategory NOTIFY activeCategoryChanged) Q_PROPERTY(int activeSubCategory READ activeSubCategory WRITE setActiveSubCategory NOTIFY activeSubCategoryChanged) Q_PROPERTY(int width READ width NOTIFY widthChanged) Q_PROPERTY(bool actionMenuVisible READ actionMenuVisible NOTIFY actionMenuVisibleChanged) public: SidebarMode(QObject * parent, const QVariantList& ); ~SidebarMode() override; QWidget * mainWidget() override; void initEvent() override; void giveFocus() override; KAboutData * aboutData() override; ModuleView * moduleView() const override; QAbstractItemModel *categoryModel() const; QAbstractItemModel *subCategoryModel() const; QAbstractItemModel *mostUsedModel() const; int activeCategory() const; void setActiveCategory(int cat); int activeSubCategory() const; void setActiveSubCategory(int cat); int width() const; bool actionMenuVisible() const; Q_INVOKABLE QAction *action(const QString &name) const; // QML doesn't understand QIcon, otherwise we could get it from the QAction itself Q_INVOKABLE QString actionIconName(const QString &name) const; Q_INVOKABLE void requestToolTip(int index, const QRectF &rect); Q_INVOKABLE void requestSubCategoryToolTip(int index, const QRectF &rect); + Q_INVOKABLE void requestMostUsedToolTip(int index, const QRectF &rect); Q_INVOKABLE void hideToolTip(); Q_INVOKABLE void hideSubCategoryToolTip(); + Q_INVOKABLE void hideMostUsedToolTip(); Q_INVOKABLE void loadMostUsed(int index); Q_INVOKABLE void showActionMenu(const QPoint &position); protected: QList views() const override; bool eventFilter(QObject* watched, QEvent* event) override; private Q_SLOTS: void changeModule( const QModelIndex& activeModule ); void moduleLoaded(); void initWidget(); Q_SIGNALS: void activeCategoryChanged(); void activeSubCategoryChanged(); void widthChanged(); void actionMenuVisibleChanged(); private: class Private; Private *const d; }; #endif diff --git a/sidebar/ToolTips/tooltipmanager.cpp b/sidebar/ToolTips/tooltipmanager.cpp index 49d53897..7b2af637 100644 --- a/sidebar/ToolTips/tooltipmanager.cpp +++ b/sidebar/ToolTips/tooltipmanager.cpp @@ -1,235 +1,238 @@ /******************************************************************************* * Copyright (C) 2008 by Konstantin Heil * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * *******************************************************************************/ #include "tooltipmanager.h" #include "MenuItem.h" #include #include #include #include #include #include #include #include #include #include #include #include class IconLoaderSingleton { public: IconLoaderSingleton() = default; KIconLoader self; }; Q_GLOBAL_STATIC(IconLoaderSingleton, privateIconLoaderSelf) class ToolTipManager::Private { public: Private() : tooltip(nullptr), view(nullptr), timer(nullptr), delay(300) { } KToolTipWidget *tooltip; QWidget* view; QAbstractItemModel *model; QTimer* timer; QModelIndex item; QRect itemRect; int delay; + ToolTipPosition toolTipPosition; }; -ToolTipManager::ToolTipManager(QAbstractItemModel *model, QWidget* parent) +ToolTipManager::ToolTipManager(QAbstractItemModel *model, QWidget* parent, ToolTipPosition toolTipPosition) : QObject(parent) , d(new ToolTipManager::Private) { d->view = parent; d->model = model; + d->toolTipPosition = toolTipPosition; d->tooltip = new KToolTipWidget(d->view); d->tooltip->setHideDelay(0); d->timer = new QTimer(this); d->timer->setSingleShot(true); connect(d->timer, &QTimer::timeout, this, &ToolTipManager::prepareToolTip); d->view->installEventFilter(this); } ToolTipManager::~ToolTipManager() { delete d; } bool ToolTipManager::eventFilter(QObject* watched, QEvent* event) { if (watched == d->view) { switch (event->type()) { case QEvent::Leave: case QEvent::MouseButtonPress: hideToolTip(); break; default: break; } } return QObject::eventFilter(watched, event); } void ToolTipManager::requestToolTip(const QModelIndex& index, const QRect &rect) { // only request a tooltip for the name column and when no selection or // drag & drop operation is done (indicated by the left mouse button) if ( !(QApplication::mouseButtons() & Qt::LeftButton) ) { d->tooltip->hide(); d->itemRect = rect; const QPoint pos = d->view->mapToGlobal(d->itemRect.topLeft()); d->itemRect.moveTo(pos); d->item = index; d->timer->start(d->delay); } else { hideToolTip(); } } void ToolTipManager::hideToolTip() { d->timer->stop(); d->tooltip->hideLater(); } void ToolTipManager::prepareToolTip() { // item may have gone away since we're triggered by a timer MenuItem * menuItem = d->model->data( d->item, Qt::UserRole ).value(); if (menuItem) { showToolTip( d->item ); } } void ToolTipManager::showToolTip( const QModelIndex &menuItem ) { if (QApplication::mouseButtons() & Qt::LeftButton) { return; } QWidget * tip = createTipContent( menuItem ); - - if (qApp->isRightToLeft()) { - d->tooltip->showAt(d->itemRect.topLeft() - QPoint(d->tooltip->width(), 0), tip, d->view->nativeParentWidget()->windowHandle()); + if(d->toolTipPosition == ToolTipPosition::BottomCenter) { + d->tooltip->showBelow(d->itemRect, tip, d->view->nativeParentWidget()->windowHandle()); } else { - d->tooltip->showAt(d->itemRect.topRight(), tip, d->view->nativeParentWidget()->windowHandle()); + if (qApp->isRightToLeft()) { + d->tooltip->showAt(d->itemRect.topLeft() - QPoint(d->tooltip->width(), 0), tip, d->view->nativeParentWidget()->windowHandle()); + } else { + d->tooltip->showAt(d->itemRect.topRight(), tip, d->view->nativeParentWidget()->windowHandle()); + } } - connect(d->tooltip, &KToolTipWidget::hidden, tip, &QObject::deleteLater); - } QWidget * ToolTipManager::createTipContent( QModelIndex item ) { const QSize dialogIconSize = QSize(IconSize(KIconLoader::Dialog), IconSize(KIconLoader::Dialog)); const QSize toolbarIconSize = QSize(IconSize(KIconLoader::MainToolbar), IconSize(KIconLoader::MainToolbar)); QWidget * tipContent = new QWidget(); QGridLayout* tipLayout = new QGridLayout(); tipLayout->setAlignment( Qt::AlignLeft ); QLayout * primaryLine = generateToolTipLine( &item, tipContent, dialogIconSize, true ); primaryLine->setAlignment( Qt::AlignLeft ); tipLayout->addLayout( primaryLine, 0, 0, Qt::AlignLeft ); for ( int done = 0; d->model->rowCount( item ) > done; done = 1 + done ) { QModelIndex childItem = d->model->index( done, 0, item ); QLayout * subLine = generateToolTipLine( &childItem, tipContent, toolbarIconSize, false ); subLine->setAlignment( Qt::AlignLeft ); tipLayout->addLayout( subLine, done + 2, 0, Qt::AlignLeft ); } tipLayout->setVerticalSpacing( tipContent->fontMetrics().height() / 3 ); tipContent->setLayout( tipLayout ); if( d->model->rowCount( item ) > 0 ) { QFrame * separatorLine = new QFrame( tipContent ); separatorLine->setFrameStyle( QFrame::HLine ); tipLayout->addWidget( separatorLine, 1, 0 ); } return tipContent; } QLayout * ToolTipManager::generateToolTipLine( QModelIndex * item, QWidget * toolTip, QSize iconSize, bool comment ) { // Get MenuItem MenuItem * menuItem = d->model->data( *item, Qt::UserRole ).value(); QString text = menuItem->name(); if ( comment ) { text = QStringLiteral( "%1" ).arg( menuItem->name() ); } // Generate text if ( comment ) { text += QStringLiteral("
"); if ( !menuItem->service()->comment().isEmpty() ) { text += menuItem->service()->comment(); } else { int childCount = d->model->rowCount( *item ); text += i18np( "Contains 1 item", "Contains %1 items", childCount ); } } QLabel * textLabel = new QLabel( toolTip ); textLabel->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); textLabel->setForegroundRole(QPalette::ToolTipText); textLabel->setText( text ); // Get icon QPalette pal = textLabel->palette(); for (auto state : { QPalette::Active, QPalette::Inactive, QPalette::Disabled }) { pal.setBrush(state, QPalette::WindowText, pal.toolTipText()); pal.setBrush(state, QPalette::Window, pal.toolTipBase()); } privateIconLoaderSelf->self.setCustomPalette(pal); QIcon icon = KDE::icon(menuItem->service()->icon(), &privateIconLoaderSelf->self); QLabel * iconLabel = new QLabel( toolTip ); iconLabel->setPixmap( icon.pixmap(iconSize) ); iconLabel->setMaximumSize( iconSize ); // Generate layout QHBoxLayout * layout = new QHBoxLayout(); layout->setSpacing( textLabel->fontMetrics().height() / 3 ); layout->setAlignment( Qt::AlignLeft ); layout->addWidget( iconLabel, Qt::AlignLeft ); layout->addWidget( textLabel, Qt::AlignLeft ); return layout; } diff --git a/sidebar/ToolTips/tooltipmanager.h b/sidebar/ToolTips/tooltipmanager.h index fc3fdece..f9080960 100644 --- a/sidebar/ToolTips/tooltipmanager.h +++ b/sidebar/ToolTips/tooltipmanager.h @@ -1,74 +1,78 @@ /******************************************************************************* * Copyright (C) 2008 by Konstantin Heil * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * *******************************************************************************/ #ifndef TOOLTIPMANAGER_H #define TOOLTIPMANAGER_H #include #include #include class QLayout; /** * @brief Manages the tooltips for an item view. * * When hovering an item, a tooltip is shown after * a short timeout. The tooltip is hidden again when the * viewport is hovered or the item view has been left. */ class ToolTipManager : public QObject { Q_OBJECT public: + + enum ToolTipPosition { BottomCenter, Right }; + /** * Standard constructor. The ToolTipManager will start handling ToolTip events on the provided * view immediately. * * @param parent The view which will have the tooltips displayed for. + * @param toolTipPosition The position of the tooltip. */ - explicit ToolTipManager(QAbstractItemModel *model, QWidget* parent); + explicit ToolTipManager(QAbstractItemModel *model, QWidget* parent, ToolTipManager::ToolTipPosition toolTipPosition); ~ToolTipManager() override; public Q_SLOTS: /** * Hides the currently shown tooltip. Invoking this method is * only needed when the tooltip should be hidden although * an item is hovered. */ void hideToolTip(); void requestToolTip(const QModelIndex& index, const QRect &rect); protected: bool eventFilter(QObject* watched, QEvent* event) override; private Q_SLOTS: void prepareToolTip(); private: void showToolTip( const QModelIndex &menuItem ); QWidget * createTipContent( QModelIndex item ); QLayout * generateToolTipLine( QModelIndex * item, QWidget * toolTip, QSize iconSize, bool comment ); class Private; ToolTipManager::Private* d; }; #endif diff --git a/sidebar/package/contents/ui/IntroIcon.qml b/sidebar/package/contents/ui/IntroIcon.qml index a09eb6e1..03e6b591 100644 --- a/sidebar/package/contents/ui/IntroIcon.qml +++ b/sidebar/package/contents/ui/IntroIcon.qml @@ -1,88 +1,91 @@ /* Copyright (c) 2017 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.5 import QtQuick.Controls 2.5 as QQC2 import QtQuick.Layouts 1.1 import org.kde.kirigami 2.5 as Kirigami MouseArea { id: item property alias icon: iconItem.source property alias text: label.text property string module property int iconSize: Kirigami.Units.iconSizes.huge Layout.minimumWidth: Kirigami.Units.iconSizes.medium Layout.minimumHeight: column.implicitHeight cursorShape: Qt.PointingHandCursor Layout.fillWidth: true Layout.alignment: Qt.AlignTop activeFocusOnTab: true + hoverEnabled: true onClicked: systemsettings.loadMostUsed(index); - + onEntered: systemsettings.requestMostUsedToolTip(index, item.mapToItem(root, 0, Kirigami.Units.largeSpacing, width, height)); + onExited: systemsettings.hideMostUsedToolTip(); + Keys.onTabPressed: { if (index < (mostUsedRepeater.count-1)) { event.accepted = false; } else { root.focusNextRequest(); } } Keys.onBacktabPressed: { if (index > 0) { event.accepted = false; } else { root.focusPreviousRequest(); } } Kirigami.Separator { anchors{ left: parent.left right: parent.right bottom: parent.bottom } visible: item.activeFocus color: Kirigami.Theme.highlightColor } ColumnLayout { id: column width: parent.width Kirigami.Icon { id: iconItem Layout.alignment: Qt.AlignHCenter Layout.minimumWidth: item.iconSize Layout.minimumHeight: Layout.minimumWidth height: width } QQC2.Label { id: label Layout.fillWidth: true Layout.maximumWidth: item.width Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter wrapMode: Text.Wrap } } Accessible.role: Accessible.Button Accessible.name: label.text Accessible.description: i18n("Most used module number %1", index+1) Accessible.onPressAction: systemsettings.loadMostUsed(index); }