diff --git a/applets/kicker/plugin/appsmodel.cpp b/applets/kicker/plugin/appsmodel.cpp index 89537e931..8074cbb98 100644 --- a/applets/kicker/plugin/appsmodel.cpp +++ b/applets/kicker/plugin/appsmodel.cpp @@ -1,693 +1,729 @@ /*************************************************************************** * Copyright (C) 2012 Aurélien Gâteau * * Copyright (C) 2013-2015 by Eike Hein * * * * 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 "appsmodel.h" #include "actionlist.h" #include "rootmodel.h" #include #include #include #include #include #include AppsModel::AppsModel(const QString &entryPath, bool paginate, int pageSize, bool flat, bool sorted, bool separators, QObject *parent) : AbstractModel(parent) +, m_complete(false) , m_paginate(paginate) , m_pageSize(pageSize) , m_deleteEntriesOnDestruction(true) , m_separatorCount(0) , m_showSeparators(separators) , m_showTopLevelItems(false) , m_appletInterface(nullptr) +, m_autoPopulate(true) , m_description(i18n("Applications")) , m_entryPath(entryPath) , m_staticEntryList(false) , m_changeTimer(0) , m_flat(flat) , m_sorted(sorted) , m_appNameFormat(AppEntry::NameOnly) { if (!m_entryPath.isEmpty()) { refresh(); } } AppsModel::AppsModel(const QList entryList, bool deleteEntriesOnDestruction, QObject *parent) : AbstractModel(parent) +, m_complete(false) , m_paginate(false) , m_pageSize(24) , m_deleteEntriesOnDestruction(deleteEntriesOnDestruction) , m_separatorCount(0) , m_showSeparators(false) , m_showTopLevelItems(false) , m_appletInterface(nullptr) +, m_autoPopulate(true) , m_description(i18n("Applications")) , m_entryPath(QString()) , m_staticEntryList(true) , m_changeTimer(0) , m_flat(true) , m_sorted(true) , m_appNameFormat(AppEntry::NameOnly) { foreach(AbstractEntry *suggestedEntry, entryList) { bool found = false; foreach (const AbstractEntry *entry, m_entryList) { if (entry->type() == AbstractEntry::RunnableType && static_cast(entry)->service()->storageId() == static_cast(suggestedEntry)->service()->storageId()) { found = true; break; } } if (!found) { m_entryList << suggestedEntry; } } sortEntries(); } AppsModel::~AppsModel() { if (m_deleteEntriesOnDestruction) { qDeleteAll(m_entryList); } } +bool AppsModel::autoPopulate() const +{ + return m_autoPopulate; +} + +void AppsModel::setAutoPopulate(bool populate) +{ + if (m_autoPopulate != populate) { + m_autoPopulate = populate; + + emit autoPopulateChanged(); + } +} + QString AppsModel::description() const { return m_description; } void AppsModel::setDescription(const QString &text) { if (m_description != text) { m_description = text; emit descriptionChanged(); } } QVariant AppsModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_entryList.count()) { return QVariant(); } const AbstractEntry *entry = m_entryList.at(index.row()); if (role == Qt::DisplayRole) { return entry->name(); } else if (role == Qt::DecorationRole) { return entry->icon(); } else if (role == Kicker::DescriptionRole) { return entry->description(); } else if (role == Kicker::FavoriteIdRole && entry->type() == AbstractEntry::RunnableType) { return entry->id(); } else if (role == Kicker::UrlRole && entry->type() == AbstractEntry::RunnableType) { return entry->url(); } else if (role == Kicker::IsParentRole) { return (entry->type() == AbstractEntry::GroupType); } else if (role == Kicker::IsSeparatorRole) { return (entry->type() == AbstractEntry::SeparatorType); } else if (role == Kicker::HasChildrenRole) { return entry->hasChildren(); } else if (role == Kicker::HasActionListRole) { const AppsModel *appsModel = qobject_cast(entry->childModel()); return entry->hasActions() || (appsModel && !appsModel->hiddenEntries().isEmpty()); } else if (role == Kicker::ActionListRole) { QVariantList actionList = entry->actions(); if (!m_hiddenEntries.isEmpty()) { actionList << Kicker::createSeparatorActionItem(); actionList << Kicker::createActionItem(i18n("Unhide Applications in this Submenu"), "unhideSiblingApplications"); } const AppsModel *appsModel = qobject_cast(entry->childModel()); if (appsModel && !appsModel->hiddenEntries().isEmpty()) { actionList << Kicker::createActionItem(i18n("Unhide Applications in '%1'", entry->name()), "unhideChildApplications"); } return actionList; } return QVariant(); } QModelIndex AppsModel::index(int row, int column, const QModelIndex &parent) const { return hasIndex(row, column, parent) ? createIndex(row, column, m_entryList.at(row)) : QModelIndex(); } int AppsModel::rowCount(const QModelIndex &parent) const { return parent.isValid() ? 0 : m_entryList.count(); } bool AppsModel::trigger(int row, const QString &actionId, const QVariant &argument) { if (row < 0 || row >= m_entryList.count()) { return false; } AbstractEntry *entry = m_entryList.at(row); if (actionId == "hideApplication" && entry->type() == AbstractEntry::RunnableType) { QObject *appletInterface = rootModel()->property("appletInterface").value(); QQmlPropertyMap *appletConfig = qobject_cast(appletInterface->property("configuration").value()); if (appletConfig && appletConfig->contains("hiddenApplications")) { QStringList hiddenApps = appletConfig->value("hiddenApplications").toStringList(); KService::Ptr service = static_cast(entry)->service(); if (!hiddenApps.contains(service->menuId())) { hiddenApps << service->menuId(); appletConfig->insert("hiddenApplications", hiddenApps); QMetaObject::invokeMethod(appletConfig, "valueChanged", Qt::DirectConnection, Q_ARG(QString, "hiddenApplications"), Q_ARG(QVariant, hiddenApps)); refresh(); emit hiddenEntriesChanged(); } } return false; } else if (actionId == "unhideSiblingApplications") { QObject *appletInterface = rootModel()->property("appletInterface").value(); QQmlPropertyMap *appletConfig = qobject_cast(appletInterface->property("configuration").value()); if (appletConfig && appletConfig->contains("hiddenApplications")) { QStringList hiddenApps = appletConfig->value("hiddenApplications").toStringList(); foreach(const QString& app, m_hiddenEntries) { hiddenApps.removeOne(app); } appletConfig->insert("hiddenApplications", hiddenApps); QMetaObject::invokeMethod(appletConfig, "valueChanged", Qt::DirectConnection, Q_ARG(QString, "hiddenApplications"), Q_ARG(QVariant, hiddenApps)); m_hiddenEntries.clear(); refresh(); emit hiddenEntriesChanged(); } return false; } else if (actionId == "unhideChildApplications") { QObject *appletInterface = rootModel()->property("appletInterface").value(); QQmlPropertyMap *appletConfig = qobject_cast(appletInterface->property("configuration").value()); if (entry->type() == AbstractEntry::GroupType && appletConfig && appletConfig->contains("hiddenApplications")) { const AppsModel *appsModel = qobject_cast(entry->childModel()); if (!appsModel) { return false; } QStringList hiddenApps = appletConfig->value("hiddenApplications").toStringList(); foreach(const QString& app, appsModel->hiddenEntries()) { hiddenApps.removeOne(app); } appletConfig->insert("hiddenApplications", hiddenApps); QMetaObject::invokeMethod(appletConfig, "valueChanged", Qt::DirectConnection, Q_ARG(QString, "hiddenApplications"), Q_ARG(QVariant, hiddenApps)); refresh(); emit hiddenEntriesChanged(); } return false; } return entry->run(actionId, argument); } AbstractModel *AppsModel::modelForRow(int row) { if (row < 0 || row >= m_entryList.count()) { return 0; } return m_entryList.at(row)->childModel(); } int AppsModel::rowForModel(AbstractModel *model) { for (int i = 0; i < m_entryList.count(); ++i) { if (m_entryList.at(i)->childModel() == model) { return i; } } return -1; } int AppsModel::separatorCount() const { return m_separatorCount; } bool AppsModel::paginate() const { return m_paginate; } void AppsModel::setPaginate(bool paginate) { if (m_paginate != paginate) { m_paginate = paginate; refresh(); emit paginateChanged(); } } int AppsModel::pageSize() const { return m_pageSize; } void AppsModel::setPageSize(int size) { if (m_pageSize != size) { m_pageSize = size; refresh(); emit pageSizeChanged(); } } bool AppsModel::flat() const { return m_flat; } void AppsModel::setFlat(bool flat) { if (m_flat != flat) { m_flat = flat; refresh(); emit flatChanged(); } } bool AppsModel::sorted() const { return m_sorted; } void AppsModel::setSorted(bool sorted) { if (m_sorted != sorted) { m_sorted = sorted; refresh(); emit sortedChanged(); } } bool AppsModel::showSeparators() const { return m_showSeparators; } void AppsModel::setShowSeparators(bool showSeparators) { if (m_showSeparators != showSeparators) { m_showSeparators = showSeparators; refresh(); emit showSeparatorsChanged(); } } bool AppsModel::showTopLevelItems() const { return m_showTopLevelItems; } void AppsModel::setShowTopLevelItems(bool showTopLevelItems) { if (m_showTopLevelItems != showTopLevelItems) { m_showTopLevelItems = showTopLevelItems; refresh(); emit showTopLevelItemsChanged(); } } int AppsModel::appNameFormat() const { return m_appNameFormat; } void AppsModel::setAppNameFormat(int format) { if (m_appNameFormat != (AppEntry::NameFormat)format) { m_appNameFormat = (AppEntry::NameFormat)format; refresh(); emit appNameFormatChanged(); } } QObject* AppsModel::appletInterface() const { return m_appletInterface; } void AppsModel::setAppletInterface(QObject* appletInterface) { if (m_appletInterface != appletInterface) { m_appletInterface = appletInterface; refresh(); emit appletInterfaceChanged(); } } QStringList AppsModel::hiddenEntries() const { return m_hiddenEntries; } void AppsModel::refresh() { + if (m_complete) { + return; + } + if (m_staticEntryList) { return; } if (rootModel() == this && !m_appletInterface) { return; } beginResetModel(); refreshInternal(); endResetModel(); if (favoritesModel()) { favoritesModel()->refresh(); } emit countChanged(); emit separatorCountChanged(); } void AppsModel::refreshInternal() { if (m_staticEntryList) { return; } if (m_entryList.count()) { qDeleteAll(m_entryList); m_entryList.clear(); emit cleared(); } m_hiddenEntries.clear(); m_separatorCount = 0; if (m_entryPath.isEmpty()) { KServiceGroup::Ptr group = KServiceGroup::root(); if (!group) { return; } bool sortByGenericName = (appNameFormat() == AppEntry::GenericNameOnly || appNameFormat() == AppEntry::GenericNameAndName); KServiceGroup::List list = group->entries(true /* sorted */, true /* excludeNoDisplay */, true /* allowSeparators */, sortByGenericName /* sortByGenericName */); for (KServiceGroup::List::ConstIterator it = list.constBegin(); it != list.constEnd(); it++) { const KSycocaEntry::Ptr p = (*it); if (p->isType(KST_KServiceGroup)) { KServiceGroup::Ptr subGroup(static_cast(p.data())); if (!subGroup->noDisplay() && subGroup->childCount() > 0) { AppGroupEntry *groupEntry = new AppGroupEntry(this, subGroup, m_paginate, m_pageSize, m_flat, m_sorted, m_showSeparators, m_appNameFormat); m_entryList << groupEntry; } } else if (p->isType(KST_KService) && m_showTopLevelItems) { const KService::Ptr service(static_cast(p.data())); if (service->noDisplay()) { continue; } bool found = false; foreach (const AbstractEntry *entry, m_entryList) { if (entry->type() == AbstractEntry::RunnableType && static_cast(entry)->service()->storageId() == service->storageId()) { found = true; } } if (!found) { m_entryList << new AppEntry(this, service, m_appNameFormat); } } else if (p->isType(KST_KServiceSeparator) && m_showSeparators && m_showTopLevelItems) { if (!m_entryList.count()) { continue; } if (m_entryList.last()->type() == AbstractEntry::SeparatorType) { continue; } m_entryList << new SeparatorEntry(this); ++m_separatorCount; } } if (m_entryList.count()) { while (m_entryList.last()->type() == AbstractEntry::SeparatorType) { m_entryList.removeLast(); --m_separatorCount; } } m_changeTimer = new QTimer(this); m_changeTimer->setSingleShot(true); m_changeTimer->setInterval(100); connect(m_changeTimer, SIGNAL(timeout()), this, SLOT(refresh())); connect(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), SLOT(checkSycocaChanges(QStringList))); } else { KServiceGroup::Ptr group = KServiceGroup::group(m_entryPath); processServiceGroup(group); if (m_entryList.count()) { while (m_entryList.last()->type() == AbstractEntry::SeparatorType) { m_entryList.removeLast(); --m_separatorCount; } } if (m_sorted) { sortEntries(); } if (m_paginate) { QList groups; int at = 0; QList page; foreach(AbstractEntry *app, m_entryList) { page.append(app); if (at == (m_pageSize - 1)) { at = 0; AppsModel *model = new AppsModel(page, true, this); groups.append(new GroupEntry(this, QString(), QString(), model)); page.clear(); } else { ++at; } } if (page.count()) { AppsModel *model = new AppsModel(page, true, this); groups.append(new GroupEntry(this, QString(), QString(), model)); } m_entryList = groups; } } } void AppsModel::processServiceGroup(KServiceGroup::Ptr group) { if (!group || !group->isValid()) { return; } bool hasSubGroups = false; foreach(KServiceGroup::Ptr subGroup, group->groupEntries(KServiceGroup::ExcludeNoDisplay)) { if (subGroup->childCount() > 0) { hasSubGroups = true; break; } } bool sortByGenericName = (appNameFormat() == AppEntry::GenericNameOnly || appNameFormat() == AppEntry::GenericNameAndName); KServiceGroup::List list = group->entries(true /* sorted */, true /* excludeNoDisplay */, (!m_flat || (m_flat && !hasSubGroups)) /* allowSeparators */, sortByGenericName /* sortByGenericName */); QStringList hiddenApps; QObject *appletInterface = rootModel()->property("appletInterface").value(); QQmlPropertyMap *appletConfig = qobject_cast(appletInterface->property("configuration").value()); if (appletConfig && appletConfig->contains("hiddenApplications")) { hiddenApps = appletConfig->value("hiddenApplications").toStringList(); } for (KServiceGroup::List::ConstIterator it = list.constBegin(); it != list.constEnd(); it++) { const KSycocaEntry::Ptr p = (*it); if (p->isType(KST_KService)) { const KService::Ptr service(static_cast(p.data())); if (service->noDisplay()) { continue; } if (hiddenApps.contains(service->menuId())) { m_hiddenEntries << service->menuId(); continue; } bool found = false; foreach (const AbstractEntry *entry, m_entryList) { if (entry->type() == AbstractEntry::RunnableType && static_cast(entry)->service()->storageId() == service->storageId()) { found = true; break; } } if (!found) { m_entryList << new AppEntry(this, service, m_appNameFormat); } } else if (p->isType(KST_KServiceSeparator) && m_showSeparators) { if (!m_entryList.count()) { continue; } if (m_entryList.last()->type() == AbstractEntry::SeparatorType) { continue; } m_entryList << new SeparatorEntry(this); ++m_separatorCount; } else if (p->isType(KST_KServiceGroup)) { const KServiceGroup::Ptr subGroup(static_cast(p.data())); if (subGroup->childCount() == 0) { continue; } if (m_flat) { m_sorted = true; const KServiceGroup::Ptr serviceGroup(static_cast(p.data())); processServiceGroup(serviceGroup); } else { AppGroupEntry *groupEntry = new AppGroupEntry(this, subGroup, m_paginate, m_pageSize, m_flat, m_sorted, m_showSeparators, m_appNameFormat); m_entryList << groupEntry; } } } } void AppsModel::sortEntries() { QCollator c; std::sort(m_entryList.begin(), m_entryList.end(), [&c](AbstractEntry* a, AbstractEntry* b) { if (a->type() != b->type()) { return a->type() > b->type(); } else { return c.compare(a->name(), b->name()) < 0; } }); } void AppsModel::checkSycocaChanges(const QStringList &changes) { if (changes.contains("services") || changes.contains("apps") || changes.contains("xdgdata-apps")) { m_changeTimer->start(); } } void AppsModel::entryChanged(AbstractEntry *entry) { int i = m_entryList.indexOf(entry); if (i != -1) { QModelIndex idx = index(i, 0); emit dataChanged(idx, idx); } } + +void AppsModel::classBegin() +{ + +} + +void AppsModel::componentComplete() +{ + m_complete = true; + + if (m_autoPopulate) { + refresh(); + } +} diff --git a/applets/kicker/plugin/appsmodel.h b/applets/kicker/plugin/appsmodel.h index 0123b60b9..0d6b281d2 100644 --- a/applets/kicker/plugin/appsmodel.h +++ b/applets/kicker/plugin/appsmodel.h @@ -1,143 +1,159 @@ /*************************************************************************** * Copyright (C) 2012 Aurélien Gâteau * * Copyright (C) 2013-2015 by Eike Hein * * * * 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 APPSMODEL_H #define APPSMODEL_H #include "abstractmodel.h" #include "appentry.h" +#include + #include class AppGroupEntry; class QTimer; -class AppsModel : public AbstractModel +class AppsModel : public AbstractModel, public QQmlParserStatus { Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(bool autoPopulate READ autoPopulate WRITE setAutoPopulate NOTIFY autoPopulateChanged) Q_PROPERTY(bool paginate READ paginate WRITE setPaginate NOTIFY paginateChanged) Q_PROPERTY(int pageSize READ pageSize WRITE setPageSize NOTIFY pageSizeChanged) Q_PROPERTY(bool flat READ flat WRITE setFlat NOTIFY flatChanged) Q_PROPERTY(bool sorted READ sorted WRITE setSorted NOTIFY sortedChanged) Q_PROPERTY(bool showSeparators READ showSeparators WRITE setShowSeparators NOTIFY showSeparatorsChanged) Q_PROPERTY(bool showTopLevelItems READ showTopLevelItems WRITE setShowTopLevelItems NOTIFY showTopLevelItemsChanged) Q_PROPERTY(int appNameFormat READ appNameFormat WRITE setAppNameFormat NOTIFY appNameFormatChanged) Q_PROPERTY(QObject* appletInterface READ appletInterface WRITE setAppletInterface NOTIFY appletInterfaceChanged); public: explicit AppsModel(const QString &entryPath = QString(), bool paginate = false, int pageSize = 24, bool flat = false, bool sorted = true, bool separators = true, QObject *parent = 0); explicit AppsModel(const QList entryList, bool deleteEntriesOnDestruction, QObject *parent = 0); ~AppsModel(); QString description() const override; void setDescription(const QString &text); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; + bool autoPopulate() const; + void setAutoPopulate(bool populate); + Q_INVOKABLE AbstractModel *modelForRow(int row) override; Q_INVOKABLE int rowForModel(AbstractModel *model) override; int separatorCount() const override; bool paginate() const; void setPaginate(bool paginate); int pageSize() const; void setPageSize(int size); bool flat() const; void setFlat(bool flat); bool sorted() const; void setSorted(bool sorted); bool showSeparators() const; void setShowSeparators(bool showSeparators); bool showTopLevelItems() const; void setShowTopLevelItems(bool showTopLevelItems); int appNameFormat() const; void setAppNameFormat(int format); QObject *appletInterface() const; void setAppletInterface(QObject *appletInterface); QStringList hiddenEntries() const; void entryChanged(AbstractEntry *entry) override; + void classBegin() override; + void componentComplete() override; + Q_SIGNALS: void cleared() const; + void autoPopulateChanged() const; void paginateChanged() const; void pageSizeChanged() const; void flatChanged() const; void sortedChanged() const; void showSeparatorsChanged() const; void showTopLevelItemsChanged() const; void appNameFormatChanged() const; void appletInterfaceChanged() const; void hiddenEntriesChanged() const; protected Q_SLOTS: void refresh() override; protected: void refreshInternal(); + bool m_complete; + bool m_paginate; int m_pageSize; QList m_entryList; bool m_deleteEntriesOnDestruction; int m_separatorCount; bool m_showSeparators; bool m_showTopLevelItems; QObject *m_appletInterface; private Q_SLOTS: void checkSycocaChanges(const QStringList &changes); private: void processServiceGroup(KServiceGroup::Ptr group); void sortEntries(); + bool m_autoPopulate; + QString m_description; QString m_entryPath; bool m_staticEntryList; QTimer *m_changeTimer; bool m_flat; bool m_sorted; AppEntry::NameFormat m_appNameFormat; QStringList m_hiddenEntries; static MenuEntryEditor *m_menuEntryEditor; }; #endif diff --git a/applets/kicker/plugin/rootmodel.cpp b/applets/kicker/plugin/rootmodel.cpp index 30241356a..057c70573 100644 --- a/applets/kicker/plugin/rootmodel.cpp +++ b/applets/kicker/plugin/rootmodel.cpp @@ -1,457 +1,428 @@ /*************************************************************************** * Copyright (C) 2014-2015 by Eike Hein * * * * 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 "rootmodel.h" #include "actionlist.h" #include "kastatsfavoritesmodel.h" #include "recentcontactsmodel.h" #include "recentusagemodel.h" #include "systemmodel.h" #include #include GroupEntry::GroupEntry(AppsModel *parentModel, const QString &name, const QString &iconName, AbstractModel *childModel) : AbstractGroupEntry(parentModel) , m_name(name) , m_iconName(iconName) , m_childModel(childModel) { QObject::connect(parentModel, &RootModel::cleared, childModel, &AbstractModel::deleteLater); QObject::connect(childModel, &AbstractModel::countChanged, [parentModel, this] { if (parentModel) { parentModel->entryChanged(this); } } ); } QString GroupEntry::name() const { return m_name; } QIcon GroupEntry::icon() const { return QIcon::fromTheme(m_iconName, QIcon::fromTheme("unknown")); } bool GroupEntry::hasChildren() const { return m_childModel && m_childModel->count() > 0; } AbstractModel *GroupEntry::childModel() const { return m_childModel; } RootModel::RootModel(QObject *parent) : AppsModel(QString(), parent) -, m_complete(false) , m_favorites(new KAStatsFavoritesModel(this)) , m_systemModel(nullptr) -, m_autoPopulate(true) , m_showAllApps(false) , m_showRecentApps(true) , m_showRecentDocs(true) , m_showRecentContacts(false) , m_recentOrdering(RecentUsageModel::Recent) , m_showPowerSession(true) , m_recentAppsModel(0) , m_recentDocsModel(0) , m_recentContactsModel(0) { } RootModel::~RootModel() { } QVariant RootModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() >= m_entryList.count()) { return QVariant(); } if (role == Kicker::HasActionListRole || role == Kicker::ActionListRole) { const AbstractEntry *entry = m_entryList.at(index.row()); if (entry->type() == AbstractEntry::GroupType) { const GroupEntry *group = static_cast(entry); AbstractModel *model = group->childModel(); if (model == m_recentAppsModel || model == m_recentDocsModel || model == m_recentContactsModel) { if (role == Kicker::HasActionListRole) { return true; } else if (role == Kicker::ActionListRole) { QVariantList actionList; actionList << model->actions(); actionList << Kicker::createSeparatorActionItem(); actionList << Kicker::createActionItem(i18n("Hide %1", group->name()), "hideCategory"); return actionList; } } } } return AppsModel::data(index, role); } bool RootModel::trigger(int row, const QString& actionId, const QVariant& argument) { const AbstractEntry *entry = m_entryList.at(row); if (entry->type() == AbstractEntry::GroupType) { if (actionId == "hideCategory") { AbstractModel *model = entry->childModel(); if (model == m_recentAppsModel) { setShowRecentApps(false); return true; } else if (model == m_recentDocsModel) { setShowRecentDocs(false); return true; } else if (model == m_recentContactsModel) { setShowRecentContacts(false); return true; } } else if (entry->childModel()->hasActions()) { return entry->childModel()->trigger(-1, actionId, QVariant()); } } return AppsModel::trigger(row, actionId, argument); } -bool RootModel::autoPopulate() const -{ - return m_autoPopulate; -} - -void RootModel::setAutoPopulate(bool populate) -{ - if (m_autoPopulate != populate) { - m_autoPopulate = populate; - - emit autoPopulateChanged(); - } -} - bool RootModel::showAllApps() const { return m_showAllApps; } void RootModel::setShowAllApps(bool show) { if (m_showAllApps != show) { m_showAllApps = show; refresh(); emit showAllAppsChanged(); } } bool RootModel::showRecentApps() const { return m_showRecentApps; } void RootModel::setShowRecentApps(bool show) { if (show != m_showRecentApps) { m_showRecentApps = show; refresh(); emit showRecentAppsChanged(); } } bool RootModel::showRecentDocs() const { return m_showRecentDocs; } void RootModel::setShowRecentDocs(bool show) { if (show != m_showRecentDocs) { m_showRecentDocs = show; refresh(); emit showRecentDocsChanged(); } } bool RootModel::showRecentContacts() const { return m_showRecentContacts; } void RootModel::setShowRecentContacts(bool show) { if (show != m_showRecentContacts) { m_showRecentContacts = show; refresh(); emit showRecentContactsChanged(); } } int RootModel::recentOrdering() const { return m_recentOrdering; } void RootModel::setRecentOrdering(int ordering) { if (ordering != m_recentOrdering) { m_recentOrdering = ordering; refresh(); emit recentOrderingChanged(); } } bool RootModel::showPowerSession() const { return m_showPowerSession; } void RootModel::setShowPowerSession(bool show) { if (show != m_showPowerSession) { m_showPowerSession = show; refresh(); emit showPowerSessionChanged(); } } AbstractModel* RootModel::favoritesModel() { return m_favorites; } AbstractModel* RootModel::systemFavoritesModel() { if (m_systemModel) { return m_systemModel->favoritesModel(); } return nullptr; } -void RootModel::classBegin() -{ -} - -void RootModel::componentComplete() -{ - m_complete = true; - - if (m_autoPopulate) { - refresh(); - } -} - void RootModel::refresh() { if (!m_complete) { return; } beginResetModel(); AppsModel::refreshInternal(); AppsModel *allModel = nullptr; m_recentAppsModel = nullptr; m_recentDocsModel = nullptr; m_recentContactsModel = nullptr; if (m_showAllApps) { QList groups; if (m_paginate) { m_favorites = new KAStatsFavoritesModel(this); emit favoritesModelChanged(); QHash appsHash; QList apps; foreach (const AbstractEntry *groupEntry, m_entryList) { AbstractModel *model = groupEntry->childModel(); if (!model) continue; for (int i = 0; i < model->count(); ++i) { GroupEntry *subGroupEntry = static_cast(model->index(i, 0).internalPointer()); AbstractModel *subModel = subGroupEntry->childModel(); for (int j = 0; j < subModel->count(); ++j) { AppEntry *appEntry = static_cast(subModel->index(j, 0).internalPointer()); if (appEntry->name().isEmpty()) { continue; } appsHash.insert(appEntry->service()->menuId(), appEntry); } } } apps = appsHash.values(); QCollator c; std::sort(apps.begin(), apps.end(), [&c](AbstractEntry* a, AbstractEntry* b) { if (a->type() != b->type()) { return a->type() > b->type(); } else { return c.compare(a->name(), b->name()) < 0; } }); int at = 0; QList page; page.reserve(m_pageSize); foreach(AppEntry *app, apps) { page.append(app); if (at == (m_pageSize - 1)) { at = 0; AppsModel *model = new AppsModel(page, false, this); groups.append(new GroupEntry(this, QString(), QString(), model)); page.clear(); } else { ++at; } } if (!page.isEmpty()) { AppsModel *model = new AppsModel(page, false, this); groups.append(new GroupEntry(this, QString(), QString(), model)); } groups.prepend(new GroupEntry(this, QString(), QString(), m_favorites)); } else { QHash> m_categoryHash; foreach (const AbstractEntry *groupEntry, m_entryList) { AbstractModel *model = groupEntry->childModel(); if (!model) continue; for (int i = 0; i < model->count(); ++i) { AbstractEntry *appEntry = static_cast(model->index(i, 0).internalPointer()); if (appEntry->name().isEmpty()) { continue; } const QChar &first = appEntry->name().at(0).toUpper(); m_categoryHash[first.isDigit() ? QStringLiteral("0-9") : first].append(appEntry); } } QHashIterator> i(m_categoryHash); while (i.hasNext()) { i.next(); AppsModel *model = new AppsModel(i.value(), false, this); model->setDescription(i.key()); groups.append(new GroupEntry(this, i.key(), QString(), model)); } } allModel = new AppsModel(groups, true, this); allModel->setDescription(QStringLiteral("KICKER_ALL_MODEL")); // Intentionally no i18n. } int separatorPosition = 0; if (allModel) { m_entryList.prepend(new GroupEntry(this, i18n("All Applications"), QString(), allModel)); ++separatorPosition; } if (m_showRecentContacts) { m_recentContactsModel = new RecentContactsModel(this); m_entryList.prepend(new GroupEntry(this, i18n("Recent Contacts"), QString(), m_recentContactsModel)); ++separatorPosition; } if (m_showRecentDocs) { m_recentDocsModel = new RecentUsageModel(this, RecentUsageModel::OnlyDocs, m_recentOrdering); m_entryList.prepend(new GroupEntry(this, m_recentOrdering == RecentUsageModel::Recent ? i18n("Recent Documents") : i18n("Often Used Documents"), QString(), m_recentDocsModel)); ++separatorPosition; } if (m_showRecentApps) { m_recentAppsModel = new RecentUsageModel(this, RecentUsageModel::OnlyApps, m_recentOrdering); m_entryList.prepend(new GroupEntry(this, m_recentOrdering == RecentUsageModel::Recent ? i18n("Recent Applications") : i18n("Often Used Applications"), QString(), m_recentAppsModel)); ++separatorPosition; } if (m_showSeparators && separatorPosition > 0) { m_entryList.insert(separatorPosition, new SeparatorEntry(this)); ++m_separatorCount; } m_systemModel = new SystemModel(this); if (m_showPowerSession) { m_entryList << new GroupEntry(this, i18n("Power / Session"), QString(), m_systemModel); } endResetModel(); m_favorites->refresh(); emit systemFavoritesModelChanged(); emit countChanged(); emit separatorCountChanged(); emit refreshed(); } diff --git a/applets/kicker/plugin/rootmodel.h b/applets/kicker/plugin/rootmodel.h index ad713a2ef..3101095cd 100644 --- a/applets/kicker/plugin/rootmodel.h +++ b/applets/kicker/plugin/rootmodel.h @@ -1,137 +1,121 @@ /*************************************************************************** * Copyright (C) 2014-2015 by Eike Hein * * * * 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 ROOTMODEL_H #define ROOTMODEL_H #include "appsmodel.h" -#include - class KAStatsFavoritesModel; class RecentContactsModel; class RecentUsageModel; class SystemModel; class RootModel; class GroupEntry : public AbstractGroupEntry { public: GroupEntry(AppsModel *parentModel, const QString &name, const QString &iconName, AbstractModel *childModel); QIcon icon() const override; QString name() const override; bool hasChildren() const override; AbstractModel *childModel() const override; private: QString m_name; QString m_iconName; QPointer m_childModel; }; -class RootModel : public AppsModel, public QQmlParserStatus +class RootModel : public AppsModel { Q_OBJECT - Q_INTERFACES(QQmlParserStatus) - - Q_PROPERTY(bool autoPopulate READ autoPopulate WRITE setAutoPopulate NOTIFY autoPopulateChanged) Q_PROPERTY(QObject* systemFavoritesModel READ systemFavoritesModel NOTIFY systemFavoritesModelChanged) Q_PROPERTY(bool showAllApps READ showAllApps WRITE setShowAllApps NOTIFY showAllAppsChanged) Q_PROPERTY(bool showRecentApps READ showRecentApps WRITE setShowRecentApps NOTIFY showRecentAppsChanged) Q_PROPERTY(bool showRecentDocs READ showRecentDocs WRITE setShowRecentDocs NOTIFY showRecentDocsChanged) Q_PROPERTY(bool showRecentContacts READ showRecentContacts WRITE setShowRecentContacts NOTIFY showRecentContactsChanged) Q_PROPERTY(int recentOrdering READ recentOrdering WRITE setRecentOrdering NOTIFY recentOrderingChanged) Q_PROPERTY(bool showPowerSession READ showPowerSession WRITE setShowPowerSession NOTIFY showPowerSessionChanged) public: explicit RootModel(QObject *parent = 0); ~RootModel(); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Q_INVOKABLE bool trigger(int row, const QString &actionId, const QVariant &argument) override; - bool autoPopulate() const; - void setAutoPopulate(bool populate); - bool showAllApps() const; void setShowAllApps(bool show); bool showRecentApps() const; void setShowRecentApps(bool show); bool showRecentDocs() const; void setShowRecentDocs(bool show); bool showRecentContacts() const; void setShowRecentContacts(bool show); int recentOrdering() const; void setRecentOrdering(int ordering); bool showPowerSession() const; void setShowPowerSession(bool show); AbstractModel* favoritesModel() override; AbstractModel* systemFavoritesModel(); - void classBegin() override; - void componentComplete() override; - Q_SIGNALS: void refreshed() const; void systemFavoritesModelChanged() const; - void autoPopulateChanged() const; void showAllAppsChanged() const; void showRecentAppsChanged() const; void showRecentDocsChanged() const; void showRecentContactsChanged() const; void showPowerSessionChanged() const; void recentOrderingChanged() const; void recentAppsModelChanged() const; protected Q_SLOTS: void refresh() override; private: - bool m_complete; - KAStatsFavoritesModel *m_favorites; SystemModel *m_systemModel; - bool m_autoPopulate; - bool m_showAllApps; bool m_showRecentApps; bool m_showRecentDocs; bool m_showRecentContacts; int m_recentOrdering; bool m_showPowerSession; RecentUsageModel *m_recentAppsModel; RecentUsageModel *m_recentDocsModel; RecentContactsModel *m_recentContactsModel; }; #endif diff --git a/applets/kickoff/package/contents/ui/FullRepresentation.qml b/applets/kickoff/package/contents/ui/FullRepresentation.qml index b8b950ced..7113e76d8 100644 --- a/applets/kickoff/package/contents/ui/FullRepresentation.qml +++ b/applets/kickoff/package/contents/ui/FullRepresentation.qml @@ -1,620 +1,622 @@ /* Copyright (C) 2011 Martin Gräßlin Copyright (C) 2012 Gregor Taetzner Copyright (C) 2012 Marco Martin Copyright (C) 2013 2014 David Edmundson Copyright 2014 Sebastian Kügler 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. */ import QtQuick 2.3 import org.kde.plasma.plasmoid 2.0 import QtQuick.Layouts 1.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 import org.kde.plasma.private.kicker 0.1 as Kicker Item { id: root Layout.minimumWidth: units.gridUnit * 26 Layout.minimumHeight: units.gridUnit * 34 property string previousState property bool switchTabsOnHover: plasmoid.configuration.switchTabsOnHover property Item currentView: mainTabGroup.currentTab.decrementCurrentIndex ? mainTabGroup.currentTab : mainTabGroup.currentTab.item property KickoffButton firstButton: null property var configMenuItems property QtObject globalFavorites: rootModelFavorites state: "Normal" onFocusChanged: { header.input.forceActiveFocus(); } function switchToInitial() { if (firstButton != null) { mainTabGroup.currentTab = firstButton.tab; tabBar.currentTab = firstButton; header.query = "" header.state = "hint"; root.state = "Normal"; } } Kicker.DragHelper { id: dragHelper dragIconSize: units.iconSizes.medium onDropped: kickoff.dragSource = null } Kicker.AppsModel { id: rootModel + autoPopulate: false + appletInterface: plasmoid appNameFormat: plasmoid.configuration.showAppsByName ? 0 : 1 flat: false sorted: plasmoid.configuration.alphaSort showSeparators: false favoritesModel: Kicker.KAStatsFavoritesModel { id: rootModelFavorites favorites: plasmoid.configuration.favorites onFavoritesChanged: { plasmoid.configuration.favorites = favorites; } } Component.onCompleted: { favoritesModel.initForClient("org.kde.plasma.kickoff.favorites.instance-" + plasmoid.id) if (!plasmoid.configuration.favoritesPortedToKAstats) { favoritesModel.portOldFavorites(plasmoid.configuration.favorites); plasmoid.configuration.favoritesPortedToKAstats = true; } rootModel.refresh(); } } Kicker.RunnerModel { id: runnerModel appletInterface: plasmoid runners: { var runners = ["services", "places", "desktopsessions", "PowerDevil"]; if (plasmoid.configuration.useExtraRunners) { runners = runners.concat(plasmoid.configuration.runners); } return runners; } mergeResults: true favoritesModel: globalFavorites } PlasmaCore.DataSource { id: pmSource engine: "powermanagement" connectedSources: ["PowerDevil"] } PlasmaCore.Svg { id: lineSvg imagePath: "widgets/line" } PlasmaCore.Svg { id: arrowsSvg imagePath: "widgets/arrows" size: "16x16" } Timer { id: clickTimer property Item pendingButton interval: 250 onTriggered: pendingButton.clicked() } Header { id: header } Item { id: mainArea anchors.topMargin: mainTabGroup.state == "top" ? units.smallSpacing : 0 PlasmaComponents.TabGroup { id: mainTabGroup currentTab: favoritesPage anchors { fill: parent } //pages FavoritesView { id: favoritesPage } PlasmaExtras.ConditionalLoader { id: applicationsPage when: mainTabGroup.currentTab == applicationsPage source: Qt.resolvedUrl("ApplicationsView.qml") } PlasmaExtras.ConditionalLoader { id: systemPage when: mainTabGroup.currentTab == systemPage source: Qt.resolvedUrl("ComputerView.qml") } PlasmaExtras.ConditionalLoader { id: recentlyUsedPage when: mainTabGroup.currentTab == recentlyUsedPage source: Qt.resolvedUrl("RecentlyUsedView.qml") } PlasmaExtras.ConditionalLoader { id: oftenUsedPage when: mainTabGroup.currentTab == oftenUsedPage source: Qt.resolvedUrl("OftenUsedView.qml") } PlasmaExtras.ConditionalLoader { id: leavePage when: mainTabGroup.currentTab == leavePage source: Qt.resolvedUrl("LeaveView.qml") } PlasmaExtras.ConditionalLoader { id: searchPage when: root.state == "Search" //when: mainTabGroup.currentTab == searchPage || root.state == "Search" source: Qt.resolvedUrl("SearchView.qml") } state: { switch (plasmoid.location) { case PlasmaCore.Types.LeftEdge: return LayoutMirroring.enabled ? "right" : "left"; case PlasmaCore.Types.TopEdge: return "top"; case PlasmaCore.Types.RightEdge: return LayoutMirroring.enabled ? "left" : "right"; case PlasmaCore.Types.BottomEdge: default: return "bottom"; } } states: [ State { name: "left" AnchorChanges { target: header anchors { left: root.left top: undefined right: root.right bottom: root.bottom } } PropertyChanges { target: header width: header.implicitWidth } AnchorChanges { target: mainArea anchors { left: tabBar.right top: root.top right: root.right bottom: header.top } } AnchorChanges { target: tabBar anchors { left: root.left top: root.top right: undefined bottom: header.top } } }, State { name: "top" AnchorChanges { target: header anchors { left: root.left top: undefined right: root.right bottom: root.bottom } } PropertyChanges { target: header height: header.implicitHeight } AnchorChanges { target: mainArea anchors { left: root.left top: tabBar.bottom right: root.right bottom: header.top } } AnchorChanges { target: tabBar anchors { left: root.left top: root.top right: root.right bottom: undefined } } }, State { name: "right" AnchorChanges { target: header anchors { left: root.left top: undefined right: root.right bottom: root.bottom } } PropertyChanges { target: header width: header.implicitWidth } AnchorChanges { target: mainArea anchors { left: root.left top: root.top right: tabBar.left bottom: header.top } } AnchorChanges { target: tabBar anchors { left: undefined top: root.top right: root.right bottom: header.top } } }, State { name: "bottom" AnchorChanges { target: header anchors { left: root.left top: root.top right: root.right bottom: undefined } } PropertyChanges { target: header height: header.implicitHeight } AnchorChanges { target: mainArea anchors { left: root.left top: header.bottom right: root.right bottom: tabBar.top } } AnchorChanges { target: tabBar anchors { left: root.left top: undefined right: root.right bottom: root.bottom } } } ] } // mainTabGroup } PlasmaComponents.TabBar { id: tabBar property int count: 5 // updated in createButtons() width: { if (!visible) { return 0; } switch (plasmoid.location) { case PlasmaCore.Types.LeftEdge: case PlasmaCore.Types.RightEdge: return units.gridUnit * 5; default: return 0; } } height: { if (!visible) { return 0; } switch (plasmoid.location) { case PlasmaCore.Types.LeftEdge: case PlasmaCore.Types.RightEdge: return 0; default: return units.gridUnit * 5; } } Behavior on width { NumberAnimation { duration: units.longDuration; easing.type: Easing.InQuad; } enabled: plasmoid.expanded } Behavior on height { NumberAnimation { duration: units.longDuration; easing.type: Easing.InQuad; } enabled: plasmoid.expanded } tabPosition: { switch (plasmoid.location) { case PlasmaCore.Types.TopEdge: return Qt.TopEdge; case PlasmaCore.Types.LeftEdge: return Qt.LeftEdge; case PlasmaCore.Types.RightEdge: return Qt.RightEdge; default: return Qt.BottomEdge; } } onCurrentTabChanged: header.input.forceActiveFocus(); Connections { target: plasmoid onExpandedChanged: { if(menuItemsChanged()) { createButtons(); } if (!expanded) { switchToInitial(); } } } } // tabBar Keys.forwardTo: [tabBar.layout] Keys.onPressed: { if (mainTabGroup.currentTab == applicationsPage) { if (event.key != Qt.Key_Tab) { root.state = "Applications"; } } switch(event.key) { case Qt.Key_Up: { currentView.decrementCurrentIndex(); event.accepted = true; break; } case Qt.Key_Down: { currentView.incrementCurrentIndex(); event.accepted = true; break; } case Qt.Key_Left: { if (header.input.focus && header.state == "query") { break; } if (!currentView.deactivateCurrentIndex()) { if (root.state == "Applications") { mainTabGroup.currentTab = firstButton.tab; tabBar.currentTab = firstButton; } root.state = "Normal" } event.accepted = true; break; } case Qt.Key_Right: { if (header.input.focus && header.state == "query") { break; } currentView.activateCurrentIndex(); event.accepted = true; break; } case Qt.Key_Tab: { root.state == "Applications" ? root.state = "Normal" : root.state = "Applications"; event.accepted = true; break; } case Qt.Key_Enter: case Qt.Key_Return: { currentView.activateCurrentIndex(1); event.accepted = true; break; } case Qt.Key_Escape: { if (header.state != "query") { plasmoid.expanded = false; } else { header.query = ""; } event.accepted = true; break; } case Qt.Key_Menu: { currentView.openContextMenu(); event.accepted = true; break; } default: if (!header.input.focus) { header.input.forceActiveFocus(); } } } states: [ State { name: "Normal" PropertyChanges { target: root Keys.forwardTo: [tabBar.layout] } PropertyChanges { target: tabBar visible: tabBar.count > 1 } }, State { name: "Applications" PropertyChanges { target: root Keys.forwardTo: [root] } PropertyChanges { target: tabBar visible: tabBar.count > 1 } }, State { name: "Search" PropertyChanges { target: tabBar visible: false } PropertyChanges { target: mainTabGroup currentTab: searchPage } PropertyChanges { target: root Keys.forwardTo: [root] } } ] // states function getButtonDefinition(name) { switch(name) { case "bookmark": return {id: "bookmarkButton", tab: favoritesPage, iconSource: "bookmarks", text: i18n("Favorites")}; case "application": return {id: "applicationButton", tab: applicationsPage, iconSource: "applications-other", text: i18n("Applications")}; case "computer": return {id: "computerButton", tab: systemPage, iconSource: pmSource.data["PowerDevil"] && pmSource.data["PowerDevil"]["Is Lid Present"] ? "computer-laptop" : "computer", text: i18n("Computer")}; case "used": return {id: "usedButton", tab: recentlyUsedPage, iconSource: "view-history", text: i18n("History")}; case "oftenUsed": return {id: "usedButton", tab: oftenUsedPage, iconSource: "office-chart-pie", text: i18n("Often Used")}; case "leave": return {id: "leaveButton", tab: leavePage, iconSource: "system-log-out", text: i18n("Leave")}; } } Component { id: kickoffButton KickoffButton {} } Component.onCompleted: { createButtons(); } function getEnabled(configuration) { var res = []; for(var i = 0; i < configuration.length; i++) { var confItemName = configuration[i].substring(0, configuration[i].indexOf(":")); var confItemEnabled = configuration[i].substring(configuration[i].length-1) == "t"; if(confItemEnabled) { res.push(confItemName); } } return res; } function createButtons() { configMenuItems = plasmoid.configuration.menuItems; var menuItems = getEnabled(plasmoid.configuration.menuItems); tabBar.count = menuItems.length // remove old menu items for(var i = tabBar.layout.children.length -1; i >= 0; i--) { if(tabBar.layout.children[i].objectName == "KickoffButton") { tabBar.layout.children[i].destroy(); } } for (var i = 0; i < menuItems.length; i++) { var props = getButtonDefinition(menuItems[i]); var button = kickoffButton.createObject(tabBar.layout, props); if(i == 0) { firstButton = button; switchToInitial(); } } } function menuItemsChanged() { if(configMenuItems.length != plasmoid.configuration.menuItems.length) { return true; } for(var i = 0; i < configMenuItems.length; i++) { if(configMenuItems[i] != plasmoid.configuration.menuItems[i]) { return true; } } return false; } }