ukui-menu/0000775000175000017500000000000015160463365011430 5ustar fengfengukui-menu/src/0000775000175000017500000000000015160463365012217 5ustar fengfengukui-menu/src/windows/0000775000175000017500000000000015160463365013711 5ustar fengfengukui-menu/src/windows/menu-main-window.cpp0000664000175000017500000005237715160463365017626 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include #include #include "menu-main-window.h" #include "settings.h" #include "context-menu-manager.h" #include "security-function-control.h" #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include #else #include #endif #include #include #include #include #include #include #define UKUI_PANEL_SETTING "org.ukui.panel.settings" #define UKUI_PANEL_POSITION_KEY "panelposition" #define UKUI_PANEL_SIZE_KEY "panelsize" #define UKUI_PANEL_TYPE_KEY "paneltype" #define UKUI_DATA_ISLAND_POSITION_KEY "dataislandposition" #define UKUI_TOPBAR_SIZE_KEY "topbarsize" #define UKUI_PANEL_LENGTH_KEY "panellength" namespace UkuiMenu { void WindowModule::defineModule(const char *uri, int versionMajor, int versionMinor) { #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) qmlRegisterRevision(uri, versionMajor, versionMinor); qmlRegisterRevision(uri, versionMajor, versionMinor); #else #if QT_VERSION >= QT_VERSION_CHECK(5, 3, 0) qmlRegisterRevision(uri, versionMajor, versionMinor); qmlRegisterRevision(uri, versionMajor, versionMinor); #else #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) qmlRegisterRevision(uri, versionMajor, versionMinor); qmlRegisterRevision(uri, versionMajor, versionMinor); #else qmlRegisterRevision(uri, versionMajor, versionMinor); #endif #endif #endif } // ====== PanelSettings ====== // static std::once_flag onceFlag; static PanelGSettings* g_instance = nullptr; static GSettings * m_settings = nullptr; static GSettingsSchema * m_schema = nullptr; static const char *m_panelLengthKey = "panellength"; PanelGSettings *PanelGSettings::instance() { std::call_once(onceFlag, [ & ] { g_instance = new PanelGSettings(); }); return g_instance; } int PanelGSettings::getPanelLength(QString screenName) { if (!m_settings || !m_schema) return -1; if (!isKeysContain(m_panelLengthKey)) return -1; QMap map = getPanelLengthMap(); if (!map.contains(screenName)) { return -1; } return map.value(screenName).toInt(); } PanelGSettings::~PanelGSettings() { if (m_settings) { g_object_unref(m_settings); } if (m_schema) { g_settings_schema_unref(m_schema); } g_instance = nullptr; } PanelGSettings::PanelGSettings(QObject *parent) : QObject(parent) { GSettingsSchemaSource *source; source = g_settings_schema_source_get_default(); m_schema = g_settings_schema_source_lookup(source, "org.ukui.panel.settings", true); if (!m_schema) { m_settings = nullptr; return; } m_settings = g_settings_new_with_path("org.ukui.panel.settings", "/org/ukui/panel/settings/"); } bool PanelGSettings::isKeysContain(const char *key) { if (!m_settings || !m_schema) return false; gchar **keys = g_settings_schema_list_keys(m_schema); if (g_strv_contains(keys, key)) { g_strfreev(keys); return true; } else { g_strfreev(keys); return false; } } QMap PanelGSettings::getPanelLengthMap() { GVariant *gvalue = g_settings_get_value(m_settings, m_panelLengthKey); GVariantIter iter; QMap map; const gchar *key; size_t str_len; GVariant *val = NULL; g_variant_iter_init (&iter, gvalue); QVariant qvar; while (g_variant_iter_next (&iter, "{&sv}", &key, &val)) { if (g_variant_is_of_type(val, G_VARIANT_TYPE_UINT32)) { qvar = QVariant::fromValue(static_cast(g_variant_get_uint32(val))); map.insert(key, qvar); } } g_variant_unref(gvalue); return map; } // ====== WindowGeometryHelper ====== // WindowGeometryHelper::WindowGeometryHelper(QObject *parent) : QObject(parent) { initPanelSetting(); // initScreenMonitor(); updateGeometry(); connect(MenuSetting::instance(), &MenuSetting::changed, this, [this] (const QString& key) { if (key == MENU_WIDTH || key == MENU_HEIGHT) { updateGeometry(); } }); } void WindowGeometryHelper::updateGeometry() { if (!m_primaryScreen) { return; } QRect screenRect = m_primaryScreen->geometry(), normalMaskRect, fullRect; int width = MenuSetting::instance()->get(MENU_WIDTH).toInt(); int height = MenuSetting::instance()->get(MENU_HEIGHT).toInt(); int margin = MenuSetting::instance()->get(MENU_MARGIN).toInt(); //上: 1, 下: 0, 左: 2, 右: 3 switch (m_panelPos) { default: case 0: { fullRect.setTopLeft(screenRect.topLeft()); fullRect.setSize({screenRect.width(), screenRect.height() - m_panelSize}); QSize normalSize(qMin(fullRect.width() - margin*2, width), qMin(fullRect.height() - margin*2, height)); normalMaskRect.setTopLeft({margin, fullRect.height() - normalSize.height() - margin}); normalMaskRect.setSize(normalSize); break; } case 1: { fullRect.setTopLeft({screenRect.x(), screenRect.y() + m_panelSize}); fullRect.setSize({screenRect.width(), screenRect.height() - m_panelSize}); normalMaskRect.setTopLeft({margin, margin}); normalMaskRect.setSize({qMin(fullRect.width() - margin*2, width), qMin(fullRect.height() - margin*2, height)}); break; } case 2: { fullRect.setTopLeft({screenRect.x() + m_panelSize, screenRect.y()}); fullRect.setSize({screenRect.width() - m_panelSize, screenRect.height()}); normalMaskRect.setTopLeft({margin, margin}); normalMaskRect.setSize({qMin(fullRect.width() - margin*2, width), qMin(fullRect.height() - margin*2, height)}); break; } case 3: { fullRect.setTopLeft(screenRect.topLeft()); fullRect.setSize({screenRect.width() - m_panelSize, screenRect.height()}); QSize normalSize(qMin(fullRect.width() - margin*2, width), qMin(fullRect.height() - margin*2, height)); normalMaskRect.setTopLeft({fullRect.width() - normalSize.width() - margin, margin}); normalMaskRect.setSize(normalSize); break; } } m_normalGeometry = normalMaskRect; m_fullScreenGeometry = fullRect; Q_EMIT geometryChanged(); } void WindowGeometryHelper::initPanelSetting() { if (!MenuSetting::instance()->get(FOLLOW_UKUI_PANEL).toBool()) { return; } const QByteArray id(UKUI_PANEL_SETTING); if (QGSettings::isSchemaInstalled(id)) { QGSettings *setting = new QGSettings(id, QByteArray(), this); QStringList keys = setting->keys(); if (keys.contains(UKUI_PANEL_POSITION_KEY)) { m_panelPos = setting->get(UKUI_PANEL_POSITION_KEY).toInt(); } if (keys.contains(UKUI_PANEL_SIZE_KEY)) { m_panelSize = setting->get(UKUI_PANEL_SIZE_KEY).toInt(); } connect(setting, &QGSettings::changed, this, [this, setting] (const QString& key) { if (key == UKUI_PANEL_POSITION_KEY || key == UKUI_PANEL_SIZE_KEY) { m_panelPos = setting->get(UKUI_PANEL_POSITION_KEY).toInt(); m_panelSize = setting->get(UKUI_PANEL_SIZE_KEY).toInt(); updateGeometry(); } }); } } void WindowGeometryHelper::initScreenMonitor() { updatePrimaryScreen(QGuiApplication::primaryScreen()); connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, [this] (QScreen *screen) { updatePrimaryScreen(screen); updateGeometry(); }); } void WindowGeometryHelper::updatePrimaryScreen(QScreen *screen) { if (!screen) { return; } if (m_primaryScreen) { m_primaryScreen->disconnect(this); } m_primaryScreen = screen; connect(m_primaryScreen, &QScreen::geometryChanged, this, &WindowGeometryHelper::updateGeometry); } const QRect &WindowGeometryHelper::fullScreenGeometry() { return m_fullScreenGeometry; } const QRect &WindowGeometryHelper::normalGeometry() { return m_normalGeometry; } const int WindowGeometryHelper::getPanelPos() { return m_panelPos; } //======MenuWindow======// MenuWindow::MenuWindow(QWindow *parent) : QQuickView(parent) { init(); } MenuWindow::MenuWindow(QQmlEngine *engine, QWindow *parent) : QQuickView(engine, parent) { init(); } void MenuWindow::init() { initPanelSetting(); connect(MenuSetting::instance(), &MenuSetting::changed, this, [this] (const QString& key) { if (key == MENU_WIDTH || key == MENU_HEIGHT) { updateGeometry(); } }); m_screen = QGuiApplication::primaryScreen(); setScreen(m_screen); connect(m_screen, &QScreen::geometryChanged, this, &MenuWindow::updateGeometry, Qt::UniqueConnection); connect(qGuiApp, &QGuiApplication::layoutDirectionChanged, this, &MenuWindow::updateGeometry, Qt::UniqueConnection); connect(qGuiApp, &QGuiApplication::screenRemoved, this, [&](QScreen *qScreen) { if(qScreen == screen()) { activeMenuWindow(false); } }, Qt::UniqueConnection); setTitle(QCoreApplication::applicationName()); setResizeMode(SizeRootObjectToView); setColor("transparent"); //setWindowState(Qt::WindowMaximized); setFlags(flags() | Qt::Window | Qt::FramelessWindowHint); m_windowProxy = new UkuiQuick::WindowProxy(this); m_windowProxy->setWindowType(UkuiQuick::WindowType::SystemWindow); // 访问窗口api rootContext()->setContextProperty("mainWindow", this); rootContext()->setContextProperty("isLiteMode", GlobalSetting::instance()->get(GlobalSetting::IsLiteMode)); connect(GlobalSetting::instance(), &GlobalSetting::styleChanged, this , [this] (const GlobalSetting::Key& key) { if (key == GlobalSetting::EffectEnabled) { Q_EMIT effectEnabledChanged(); } else if (key == GlobalSetting::Transparency) { Q_EMIT transparencyChanged(); } else if (key == GlobalSetting::IsLiteMode) { rootContext()->setContextProperty("isLiteMode", GlobalSetting::instance()->get(key)); } }); updateGeometry(); ContextMenuManager::instance()->setMainWindow(this); } void MenuWindow::initPanelSetting() { if (!MenuSetting::instance()->get(FOLLOW_UKUI_PANEL).toBool()) { return; } const QByteArray id(UKUI_PANEL_SETTING); if (QGSettings::isSchemaInstalled(id)) { m_setting = new QGSettings(id, QByteArray(), this); QStringList keys = m_setting->keys(); if (keys.contains(UKUI_PANEL_POSITION_KEY)) { m_panelPos = m_setting->get(UKUI_PANEL_POSITION_KEY).toInt(); } if (keys.contains(UKUI_PANEL_SIZE_KEY)) { m_panelSize = m_setting->get(UKUI_PANEL_SIZE_KEY).toInt(); } if (keys.contains(UKUI_PANEL_TYPE_KEY)) { m_panelType = m_setting->get(UKUI_PANEL_TYPE_KEY).toInt(); } else { m_panelType = 0; } if (keys.contains(UKUI_DATA_ISLAND_POSITION_KEY)) { m_dataIslandPosition = m_setting->get(UKUI_DATA_ISLAND_POSITION_KEY).toInt(); } if (keys.contains(UKUI_TOPBAR_SIZE_KEY)) { m_topbarSize = m_setting->get(UKUI_TOPBAR_SIZE_KEY).toInt(); } connect(m_setting, &QGSettings::changed, this, [this] (const QString& key) { if (key == UKUI_PANEL_POSITION_KEY) { m_panelPos = m_setting->get(UKUI_PANEL_POSITION_KEY).toInt(); updateGeometry(); } else if (key == UKUI_PANEL_SIZE_KEY) { m_panelSize = m_setting->get(key).toInt(); updateGeometry(); } else if (key == UKUI_PANEL_TYPE_KEY) { m_panelType = m_setting->get(UKUI_PANEL_TYPE_KEY).toInt(); updateGeometry(); } else if (key == UKUI_DATA_ISLAND_POSITION_KEY) { m_dataIslandPosition = m_setting->get(UKUI_DATA_ISLAND_POSITION_KEY).toInt(); updateGeometry(); } else if (key == UKUI_TOPBAR_SIZE_KEY) { m_topbarSize = m_setting->get(UKUI_TOPBAR_SIZE_KEY).toInt(); updateGeometry(); } else if (key == UKUI_PANEL_LENGTH_KEY) { updateGeometry(); } }); } } void MenuWindow::updateGeometry() { updateCurrentScreenGeometry(); if (m_fullScreenGeometry.isEmpty()) return; setMinimumSize(m_fullScreenGeometry.size()); setMaximumSize(m_fullScreenGeometry.size()); setGeometry(m_fullScreenGeometry); m_windowProxy->setPosition(m_fullScreenGeometry.topLeft()); updateGeometryOfMask(); changeWindowBlurRegion( m_maskGeometry.x(), m_maskGeometry.y(), m_maskGeometry.width(), m_maskGeometry.height(), 16); } bool MenuWindow::isFullScreen() const { return m_isFullScreen; } /** * beforeFullScreenChanged -> (qml)onWidthChanged -> fullScreenChanged * @param isFullScreen */ void MenuWindow::setFullScreen(bool isFullScreen) { if (m_isFullScreen == isFullScreen) { return; } Q_EMIT beforeFullScreenChanged(); m_isFullScreen = isFullScreen; updateGeometryOfMask(); // 更新contentItem尺寸 QEvent event(QEvent::Resize); QCoreApplication::sendEvent(this, &event); Q_EMIT fullScreenChanged(); } void MenuWindow::changeWindowBlurRegion(qreal x, qreal y, qreal w, qreal h, qreal radius) { QPainterPath path; path.addRoundedRect(x, y, w, h, radius, radius); m_windowProxy->setBlurRegion(true, QRegion(path.toFillPolygon().toPolygon())); } void MenuWindow::exitFullScreen() { Q_EMIT beforeFullScreenExited(); } void MenuWindow::exposeEvent(QExposeEvent *event) { QQuickView::exposeEvent(event); } void MenuWindow::focusOutEvent(QFocusEvent *event) { // void QQuickWindow::focusOutEvent(QFocusEvent *ev) { Q_D(QQuickWindow); if (d->contentItem) d->contentItem->setFocus(false, ev->reason()); } if (event->reason() == Qt::PopupFocusReason) { return; } QQuickView::focusOutEvent(event); this->setVisible(false); } bool MenuWindow::event(QEvent *event) { if (event->type() == QEvent::Show) { if (QX11Info::isPlatformX11()) { requestActivate(); } } else if (event->type() == QEvent::MouseButtonPress) { ContextMenuManager::instance()->closeMenu(); } return QQuickView::event(event); } bool MenuWindow::effectEnabled() const { return GlobalSetting::instance()->get(GlobalSetting::EffectEnabled).toBool(); } double MenuWindow::transparency() const { return GlobalSetting::instance()->get(GlobalSetting::Transparency).toDouble(); } int MenuWindow::panelPos() const { return m_panelPos; } bool MenuWindow::editMode() const { return m_editMode; } void MenuWindow::setEditMode(bool mode) { if (mode == m_editMode) { return; } m_editMode = mode; Q_EMIT editModeChanged(); } void MenuWindow::updateGeometryOfMask() { if (m_isFullScreen) { m_maskGeometry = QRect(0, 0, m_fullScreenGeometry.width(), m_fullScreenGeometry.height()); } else { m_maskGeometry = m_normalGeometry; } setMask(m_maskGeometry); } void MenuWindow::updateCurrentScreenGeometry() { if (!m_screen || m_screen->geometry().isEmpty()) { return; } // qt的可用区域有问题,暂时使用任务栏尺寸计算 //QRect normalMaskRect, fullRect = screen()->availableGeometry(); QRect normalMaskRect, fullRect = m_screen->geometry(); int width = MenuSetting::instance()->get(MENU_WIDTH).toInt(); int height = MenuSetting::instance()->get(MENU_HEIGHT).toInt(); int margin = MenuSetting::instance()->get(MENU_MARGIN).toInt(); UkuiQuick::WindowProxy::SlideFromEdge slideFromEdge = UkuiQuick::WindowProxy::NoEdge; bool isMirrored = qGuiApp->layoutDirection() == Qt::LayoutDirection::RightToLeft; if (m_panelType == 0) { //经典任务栏 //上: 1, 下: 0, 左: 2, 右: 3 switch (m_panelPos) { default: case 0: { fullRect.adjust(0, 0, 0, -m_panelSize); QSize normalSize(qMin(fullRect.width() - margin*2, width), qMin(fullRect.height() - margin*2, height)); if (isMirrored) { normalMaskRect.setTopLeft({fullRect.width() - margin - width, fullRect.height() - normalSize.height() - margin}); } else { normalMaskRect.setTopLeft({margin, fullRect.height() - normalSize.height() - margin}); } normalMaskRect.setSize(normalSize); slideFromEdge = UkuiQuick::WindowProxy::BottomEdge; break; } case 1: { fullRect.adjust(0, m_panelSize, 0, 0); QSize normalSize(qMin(fullRect.width() - margin*2, width), qMin(fullRect.height() - margin*2, height)); if (isMirrored) { normalMaskRect.setTopLeft({fullRect.width() - margin - width, margin}); } else { normalMaskRect.setTopLeft({margin, margin}); } normalMaskRect.setSize(normalSize); slideFromEdge = UkuiQuick::WindowProxy::TopEdge; break; } case 2: { fullRect.adjust(m_panelSize, 0, 0, 0); normalMaskRect.setTopLeft({margin, margin}); normalMaskRect.setSize({qMin(fullRect.width() - margin*2, width), qMin(fullRect.height() - margin*2, height)}); slideFromEdge = UkuiQuick::WindowProxy::LeftEdge; break; } case 3: { fullRect.adjust(0, 0, -m_panelSize, 0); QSize normalSize(qMin(fullRect.width() - margin*2, width), qMin(fullRect.height() - margin*2, height)); normalMaskRect.setTopLeft({fullRect.width() - normalSize.width() - margin, margin}); normalMaskRect.setSize(normalSize); slideFromEdge = UkuiQuick::WindowProxy::RightEdge; break; } } } else if (m_panelType == 1) { //三岛任务栏 if (m_dataIslandPosition == 0) { fullRect.adjust(0, 0, 0, -m_panelSize); QSize normalSize(qMin(fullRect.width() - margin*2, width), qMin(fullRect.height() - margin*2, height)); if (isMirrored) { normalMaskRect.setTopLeft({fullRect.width() - (fullRect.width() - PanelGSettings::instance()->getPanelLength(m_screen->name())) / 2 - width , fullRect.height() - normalSize.height() - margin}); } else { normalMaskRect.setTopLeft({(fullRect.width() - PanelGSettings::instance()->getPanelLength(m_screen->name())) / 2, fullRect.height() - normalSize.height() - margin}); } normalMaskRect.setSize(normalSize); slideFromEdge = UkuiQuick::WindowProxy::BottomEdge; } else if (m_dataIslandPosition == 1) { fullRect.adjust(0, m_topbarSize, 0, 0); QSize normalSize(qMin(fullRect.width() - margin*2, width), qMin(fullRect.height() - margin*2, height)); if (isMirrored) { normalMaskRect.setTopLeft({fullRect.width() - margin - width, margin}); } else { normalMaskRect.setTopLeft({margin, margin}); } normalMaskRect.setSize(normalSize); slideFromEdge = UkuiQuick::WindowProxy::TopEdge; } } m_normalGeometry = normalMaskRect; m_fullScreenGeometry = fullRect; m_windowProxy->slideWindow(slideFromEdge, 0); Q_EMIT panelPosChanged(); Q_EMIT normalRectChanged(); } QRect MenuWindow::normalRect() const { return m_normalGeometry; } void MenuWindow::activeMenuWindow(bool active) { if (active == isVisible()) { return; } if (active) { if (SecurityFunctionControl::instance()->disable()) return; if (m_screen != UkuiQuick::WindowProxy::currentScreen()) { if (m_screen) { m_screen->disconnect(this); } m_screen = UkuiQuick::WindowProxy::currentScreen(); this->setScreen(m_screen); connect(m_screen, &QScreen::geometryChanged, this, &MenuWindow::updateGeometry, Qt::UniqueConnection); updateGeometry(); } } setVisible(active); } } // UkuiMenu ukui-menu/src/windows/menu-main-window.h0000664000175000017500000001054015160463365017255 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_MENU_MAIN_WINDOW_H #define UKUI_MENU_MENU_MAIN_WINDOW_H #include #include #include #include #include #include #include namespace UkuiMenu { class WindowModule final { public: static void defineModule(const char *uri, int versionMajor, int versionMinor); }; class PanelGSettings : public QObject { Q_OBJECT public: static PanelGSettings *instance(); int getPanelLength(QString screenName); ~PanelGSettings(); private: PanelGSettings(QObject *parent = nullptr); bool isKeysContain(const char* key); QMap getPanelLengthMap(); }; class WindowGeometryHelper final : public QObject { Q_OBJECT public: explicit WindowGeometryHelper(QObject *parent = nullptr); const QRect &normalGeometry(); const QRect &fullScreenGeometry(); const int getPanelPos(); Q_SIGNALS: void geometryChanged(); private Q_SLOTS: void updateGeometry(); void updatePrimaryScreen(QScreen *screen); private: void initPanelSetting(); void initScreenMonitor(); private: // 任务栏位置与屏幕:上: 1, 下: 0, 左: 2, 右: 3, 如果为其他值,则说明任务栏不存在 int m_panelPos{4}; int m_panelSize{0}; QScreen *m_primaryScreen{nullptr}; QRect m_normalGeometry; QRect m_fullScreenGeometry; }; class MenuWindow : public QQuickView { Q_OBJECT Q_PROPERTY(bool isFullScreen READ isFullScreen WRITE setFullScreen NOTIFY fullScreenChanged) Q_PROPERTY(bool effectEnabled READ effectEnabled NOTIFY effectEnabledChanged) Q_PROPERTY(bool editMode READ editMode WRITE setEditMode NOTIFY editModeChanged) Q_PROPERTY(double transparency READ transparency NOTIFY transparencyChanged) Q_PROPERTY(int panelPos READ panelPos NOTIFY panelPosChanged) Q_PROPERTY(QRect normalRect READ normalRect NOTIFY normalRectChanged) public: explicit MenuWindow(QWindow *parent = nullptr); MenuWindow(QQmlEngine* engine, QWindow *parent); bool isFullScreen() const; void setFullScreen(bool isFullScreen); bool editMode() const; void setEditMode(bool mode); bool effectEnabled() const; double transparency() const; int panelPos() const; QRect normalRect() const; void activeMenuWindow(bool active); Q_INVOKABLE void changeWindowBlurRegion(qreal x, qreal y, qreal w, qreal h, qreal radius = 8); Q_INVOKABLE void exitFullScreen(); Q_SIGNALS: void geometryChanged(); void effectEnabledChanged(); void transparencyChanged(); void fullScreenChanged(); void beforeFullScreenChanged(); void beforeFullScreenExited(); void panelPosChanged(); void editModeChanged(); void normalRectChanged(); protected: void exposeEvent(QExposeEvent *event) override; void focusOutEvent(QFocusEvent *event) override; bool event(QEvent *event) override; private: void init(); void initPanelSetting(); void updateGeometry(); void updateGeometryOfMask(); void updateCurrentScreenGeometry(); private: // 任务栏位置与屏幕:上: 1, 下: 0, 左: 2, 右: 3, 如果为其他值,则说明任务栏不存在 int m_panelPos{4}; int m_panelSize{48}; int m_panelType{0}; int m_dataIslandPosition{0}; int m_topbarSize{0}; bool m_editMode {false}; bool m_isFullScreen{false}; QRect m_maskGeometry = QRect(0,0,0,0); QRect m_normalGeometry = QRect(0,0,0,0); QRect m_fullScreenGeometry = QRect(0,0,0,0); QGSettings *m_setting {nullptr}; UkuiQuick::WindowProxy *m_windowProxy {nullptr}; QPointer m_screen {nullptr}; }; } // UkuiMenu #endif //UKUI_MENU_MENU_MAIN_WINDOW_H ukui-menu/src/extension/0000775000175000017500000000000015160463353014230 5ustar fengfengukui-menu/src/extension/context-menu-extension.cpp0000664000175000017500000000150615160463353021376 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "context-menu-extension.h" namespace UkuiMenu { int ContextMenuExtension::index() const { return -1; } } // UkuiMenu ukui-menu/src/extension/widget-extension.h0000664000175000017500000000425315160463353017702 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_WIDGET_EXTENSION_H #define UKUI_MENU_WIDGET_EXTENSION_H #include #include namespace UkuiMenu { class WidgetMetadata { Q_GADGET public: enum Key { Id = 0, Icon, Name, Tooltip, Version, Description, Main, Type, Flag, Data }; Q_ENUM(Key) enum TypeValue { Widget = 0x01, /**> 显示在插件区域 */ Button = 0x02, /**> 显示在侧边栏 */ AppList = 0x04 /**> 显示在应用列表,默认会显示在全屏界面 */ }; Q_ENUM(TypeValue) Q_DECLARE_FLAGS(Types, TypeValue) Q_FLAGS(Types) enum FlagValue { OnlySmallScreen = 0x01, OnlyFullScreen = 0x02, Normal = OnlySmallScreen | OnlyFullScreen }; Q_ENUM(FlagValue) Q_DECLARE_FLAGS(Flags, FlagValue) Q_FLAGS(Flags) }; typedef QMap MetadataMap; class WidgetExtension : public QObject { Q_OBJECT public: explicit WidgetExtension(QObject *parent = nullptr); virtual int index() const; virtual MetadataMap metadata() const = 0; // 兼容老版本 virtual QVariantMap data(); virtual void receive(const QVariantMap &data); Q_SIGNALS: void dataUpdated(); }; } // UkuiMenu Q_DECLARE_METATYPE(UkuiMenu::MetadataMap) Q_DECLARE_METATYPE(UkuiMenu::WidgetMetadata::TypeValue) Q_DECLARE_METATYPE(UkuiMenu::WidgetMetadata::FlagValue) #endif //UKUI_MENU_WIDGET_EXTENSION_H ukui-menu/src/extension/widget-extension-model.cpp0000664000175000017500000000661715160463353021341 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "widget-extension-model.h" #include "menu-extension-loader.h" #include namespace UkuiMenu { WidgetExtensionModel::WidgetExtensionModel(QObject *parent) : QAbstractListModel(parent) { m_widgets = MenuExtensionLoader::instance()->widgets(); for (int i = 0; i < m_widgets.size(); ++i) { connect(m_widgets.at(i), &WidgetExtension::dataUpdated, this, [i, this] { Q_EMIT dataChanged(QAbstractListModel::index(i), QAbstractListModel::index(i), {WidgetMetadata::Data}); }); } } QModelIndex WidgetExtensionModel::parent(const QModelIndex &child) const { return {}; } int WidgetExtensionModel::rowCount(const QModelIndex &parent) const { return m_widgets.count(); } int WidgetExtensionModel::columnCount(const QModelIndex &parent) const { return 1; } QVariant WidgetExtensionModel::data(const QModelIndex &index, int role) const { if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { return {}; } int row = index.row(); WidgetExtension *widget = m_widgets.at(row); auto key = WidgetMetadata::Key(role); switch (key) { case WidgetMetadata::Id: case WidgetMetadata::Icon: case WidgetMetadata::Name: case WidgetMetadata::Tooltip: case WidgetMetadata::Version: case WidgetMetadata::Description: case WidgetMetadata::Main: case WidgetMetadata::Type: case WidgetMetadata::Flag: return widget->metadata().value(key, {}); case WidgetMetadata::Data: return widget->data(); default: break; } return {}; } QHash WidgetExtensionModel::roleNames() const { QHash hash; hash.insert(WidgetMetadata::Id, "id"); hash.insert(WidgetMetadata::Icon, "icon"); hash.insert(WidgetMetadata::Name, "name"); hash.insert(WidgetMetadata::Tooltip, "tooltip"); hash.insert(WidgetMetadata::Version, "version"); hash.insert(WidgetMetadata::Description, "description"); hash.insert(WidgetMetadata::Main, "main"); hash.insert(WidgetMetadata::Type, "type"); hash.insert(WidgetMetadata::Flag, "flag"); hash.insert(WidgetMetadata::Data, "data"); return hash; } WidgetExtension *WidgetExtensionModel::widgetAt(int index) const { if (index < 0 || index >= m_widgets.count()) { return nullptr; } return m_widgets.at(index); } void WidgetExtensionModel::notify(int index, const QVariantMap &data) const { WidgetExtension *widget = widgetAt(index); if (widget) { widget->receive(data); } } WidgetExtensionModel *WidgetExtensionModel::instance() { static WidgetExtensionModel model; return &model; } } // UkuiMenu ukui-menu/src/extension/context-menu-manager.h0000664000175000017500000000312315160463353020436 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_CONTEXT_MENU_MANAGER_H #define UKUI_MENU_CONTEXT_MENU_MANAGER_H #include #include #include "context-menu-extension.h" class QWindow; class MenuManagerPrivate; namespace UkuiMenu { class ContextMenuManager : public QObject { Q_OBJECT public: static ContextMenuManager *instance(); ~ContextMenuManager() override; Q_INVOKABLE void showMenu(const QString &appid, MenuInfo::Location location, const QString& lid = QString(), const QPoint &point = QPoint()); void showMenu(const DataEntity &data, MenuInfo::Location location, const QString& lid = QString(), const QPoint &point = QPoint()); void setMainWindow(QWindow *mainWindow); bool closeMenu(); private: ContextMenuManager(); inline QPoint checkPoint(const QPoint &rawPoint); private: MenuManagerPrivate *d {nullptr}; }; } // UkuiMenu #endif //UKUI_MENU_CONTEXT_MENU_MANAGER_H ukui-menu/src/extension/context-menu-extension.h0000664000175000017500000000407215160463353021044 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_CONTEXT_MENU_EXTENSION_H #define UKUI_MENU_CONTEXT_MENU_EXTENSION_H #include #include #include "data-entity.h" namespace UkuiMenu { class MenuInfo { Q_GADGET public: enum Location { AppList = 0, /**< 小屏幕的应用列表 */ Extension, /**< 扩展页 */ FolderPage, /**< 文件夹页面 */ FullScreen, /**< 全屏应用列表 */ Folder, /**< 应用组页面 */ }; Q_ENUM(Location) }; /** * @class ContextMenuExtension * * 开始菜单应用列表的上下文菜单扩展插件 */ class ContextMenuExtension { public: virtual ~ContextMenuExtension() = default; /** * 控制菜单项显示在哪个位置 * 对于第三方项目们应该选择-1,或者大于1000的值 * @return -1:表示随机放在最后 */ virtual int index() const; /** * 根据data生成action,或者子菜单 * * @param data app信息 * @param parent action最终显示的QMenu * @param location 请求菜单的位置 * @param locationId 位置的描述信息,可选的值有:all,category,letterSort和favorite等插件的id * @return */ virtual QList actions(const DataEntity &data, QMenu *parent, const MenuInfo::Location &location, const QString &locationId) = 0; }; } // UkuiMenu #endif //UKUI_MENU_CONTEXT_MENU_EXTENSION_H ukui-menu/src/extension/menu-extension-plugin.cpp0000664000175000017500000000160115160463353021204 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "menu-extension-plugin.h" UkuiMenu::MenuExtensionPlugin::MenuExtensionPlugin(QObject *parent) : QObject(parent) { } UkuiMenu::MenuExtensionPlugin::~MenuExtensionPlugin() = default; ukui-menu/src/extension/menu-extension-plugin.h0000664000175000017500000000347615160463353020665 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_MENU_EXTENSION_PLUGIN_H #define UKUI_MENU_MENU_EXTENSION_PLUGIN_H #define UKUI_MENU_EXTENSION_I_FACE_TYPE "UKUI_MENU_EXTENSION" #define UKUI_MENU_EXTENSION_I_FACE_IID "org.ukui.menu.extension" #define UKUI_MENU_EXTENSION_I_FACE_VERSION "1.0.2" #include namespace UkuiMenu { class WidgetExtension; class ContextMenuExtension; class Q_DECL_EXPORT MenuExtensionPlugin : public QObject { Q_OBJECT public: explicit MenuExtensionPlugin(QObject *parent = nullptr); ~MenuExtensionPlugin() override; /** * 插件的唯一id,会被用于区分插件 * @return 唯一id */ virtual QString id() = 0; /** * 创建一个Widget扩展 * @return 返回nullptr代表不生产此插件 */ virtual WidgetExtension *createWidgetExtension() = 0; /** * 创建上下文菜单扩展 * @return 返回nullptr代表不生产此插件 */ virtual ContextMenuExtension *createContextMenuExtension() = 0; }; } // UkuiMenu Q_DECLARE_INTERFACE(UkuiMenu::MenuExtensionPlugin, UKUI_MENU_EXTENSION_I_FACE_IID) #endif //UKUI_MENU_MENU_EXTENSION_PLUGIN_H ukui-menu/src/extension/widget-model.cpp0000664000175000017500000000426315160463353017322 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "widget-model.h" #include "widget-extension-model.h" #include #include namespace UkuiMenu { // ====== WidgetModel ====== // WidgetModel::WidgetModel(QObject *parent) : QSortFilterProxyModel(parent) { init(); } WidgetMetadata::Types WidgetModel::types() const { return m_types; } void WidgetModel::setTypes(WidgetMetadata::Types types) { if (m_types == types) { return; } m_types = types; invalidateFilter(); } WidgetMetadata::Flags WidgetModel::flags() const { return m_flags; } void WidgetModel::setFlags(WidgetMetadata::Flags flags) { if (m_flags == flags) { return; } m_flags = flags; invalidateFilter(); } bool WidgetModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { QModelIndex index = sourceModel()->index(source_row, 0, source_parent); bool acceptFlag = m_types.testFlag(index.data(WidgetMetadata::Type).value()); if (acceptFlag) { return m_flags & index.data(WidgetMetadata::Flag).value(); } return false; } void WidgetModel::init() { QSortFilterProxyModel::setSourceModel(WidgetExtensionModel::instance()); // invalidateFilter(); } void WidgetModel::send(int index, const QVariantMap &data) { auto sourceModel = qobject_cast(QSortFilterProxyModel::sourceModel()); if (sourceModel) { sourceModel->notify(index, data); } } } // UkuiMenu ukui-menu/src/extension/favorite/0000775000175000017500000000000015160463365016052 5ustar fengfengukui-menu/src/extension/favorite/favorite-widget.h0000664000175000017500000000235715160463353021327 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_FAVORITE_WIDGET_H #define UKUI_MENU_FAVORITE_WIDGET_H #include "../widget-extension.h" #include "folder-model.h" namespace UkuiMenu { class FavoriteWidget : public WidgetExtension { Q_OBJECT public: explicit FavoriteWidget(QObject *parent = nullptr); int index() const override; MetadataMap metadata() const override; QVariantMap data() override; void receive(const QVariantMap &data) override; private: MetadataMap m_metadata; QVariantMap m_data; }; } // UkuiMenu #endif //UKUI_MENU_FAVORITE_WIDGET_H ukui-menu/src/extension/favorite/app-favorite-model.cpp0000664000175000017500000004314315160463365022256 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "favorites-config.h" #include "app-favorite-model.h" #include "favorite-folder-helper.h" #include "../context-menu-manager.h" #include #include #include #include #include #include namespace UkuiMenu { class Q_DECL_HIDDEN AppFavoritesModel::Private { public: explicit Private(AppFavoritesModel *q = nullptr); ~Private(); void init(); QPersistentModelIndex getIndexFromAppId(const QString &id) const; void addFavoriteApp(const QPersistentModelIndex &modelIndex, const int &index = 0); void removeFavoriteApp(const QString &appId); void onAppRemoved(const QModelIndex &parent, int first, int last); void onAppUpdated(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()); void updateFavoritesApps(const QModelIndex &sourceIndex); void onFolderAdded(const int &folderId, int order); void onFolderDeleted(const int &folderId, const QStringList &apps); void onFolderAppChanged(const int &folderId, const QString &appId, bool isAdded); QVector m_favoritesApps; // 已收藏应用在baseModel的对应index QVector m_folders; // 应用组的唯一Id QVector m_favoritesFiles; // 收藏文件夹的唯一路径 BasicAppModel *m_sourceModel = nullptr; QStringList m_items; private: AppFavoritesModel *q = nullptr; }; AppFavoritesModel::Private::Private(AppFavoritesModel* q) : q(q) { } AppFavoritesModel::Private::~Private() { } void AppFavoritesModel::Private::init() { m_items = FavoritesConfig::instance().getConfig(); QSet existingItems(m_items.begin(), m_items.end()); QStringList favoritesNeedsAdd; m_favoritesApps.clear(); for (int i = 0; i < m_sourceModel->rowCount(QModelIndex()); i++) { const auto index = m_sourceModel->index(i, 0); const QString id = index.data(DataEntity::Id).toString(); const QString fullId = APP_ID_SCHEME + id; const int favorite = index.data(DataEntity::Favorite).toInt(); if (favorite > 0) { QPersistentModelIndex persistentIndex(index); m_favoritesApps.append(persistentIndex); if (!existingItems.contains(fullId) && !FavoriteFolderHelper::instance()->containApp(id)) { favoritesNeedsAdd.append(fullId); existingItems.insert(fullId); } // 处理配置中有,但数据库为未收藏的情况 } else if (existingItems.contains(fullId)) { QPersistentModelIndex persistentIndex(index); m_favoritesApps.append(persistentIndex); m_sourceModel->databaseInterface()->fixAppToFavorite(id, 1); } } // 如果有新增收藏项,更新配置 if (!favoritesNeedsAdd.isEmpty()) { m_items.append(favoritesNeedsAdd); FavoritesConfig::instance().sync(m_items); } QVector foldersId; for (const auto &folder : FavoriteFolderHelper::instance()->folderData()) { foldersId.append(folder.getId()); if (!m_items.contains(FOLDER_ID_SCHEME + QString::number(folder.getId()))) { m_items.insert(m_items.count(), FOLDER_ID_SCHEME + QString::number(folder.getId())); FavoritesConfig::instance().sync(m_items); } } m_folders.swap(foldersId); } void AppFavoritesModel::Private::onAppUpdated(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { for (int row = topLeft.row(); row <= bottomRight.row(); row++) { if (roles.contains(DataEntity::Favorite)) { updateFavoritesApps(m_sourceModel->index(row, 0, QModelIndex())); } else { auto index = m_sourceModel->index(row, 0); int favoriteIndex = m_items.indexOf(APP_ID_SCHEME + index.data(DataEntity::Id).toString()); Q_EMIT q->dataChanged(q->index(favoriteIndex, 0, QModelIndex()), q->index(favoriteIndex, 0, QModelIndex()), roles); } } } void AppFavoritesModel::Private::updateFavoritesApps(const QModelIndex &sourceIndex) { auto id = sourceIndex.data(DataEntity::Id).toString(); auto favorite = sourceIndex.data(DataEntity::Favorite).toInt(); if (id.isEmpty()) { return; } QPersistentModelIndex index(sourceIndex); if (favorite > 0 && !m_favoritesApps.contains(index)) { addFavoriteApp(index, m_items.count()); } else if (favorite == 0) { if (FavoriteFolderHelper::instance()->containApp(id)) { FavoriteFolderHelper::instance()->removeAppFromFolder(id); } m_favoritesApps.removeOne(index); removeFavoriteApp(index.data(DataEntity::Id).toString()); } } void AppFavoritesModel::Private::onAppRemoved(const QModelIndex &parent, int first, int last) { for (int row = first; row <= last; ++row) { QModelIndex index = m_sourceModel->index(row, 0, {}); if (index.data(DataEntity::Favorite).toInt() > 0) { QString appId = index.data(DataEntity::Id).toString(); if (FavoriteFolderHelper::instance()->containApp(appId)) { FavoriteFolderHelper::instance()->removeAppFromFolder(appId); } QPersistentModelIndex modelIndex(index); m_favoritesApps.removeOne(modelIndex); removeFavoriteApp(index.data(DataEntity::Id).toString()); } } } void AppFavoritesModel::Private::addFavoriteApp(const QPersistentModelIndex &modelIndex, const int &index) { if (!m_favoritesApps.contains(modelIndex) && modelIndex.isValid()) { m_favoritesApps.append(modelIndex); } auto id = APP_ID_SCHEME + modelIndex.data(DataEntity::Id).toString(); if (!m_items.contains(id)) { q->beginInsertRows(QModelIndex(), index, index); m_items.insert(index, id); q->endInsertRows(); FavoritesConfig::instance().sync(m_items); } } void AppFavoritesModel::Private::removeFavoriteApp(const QString &appId) { int index = m_items.indexOf(APP_ID_SCHEME + appId); if (index > -1 && index < m_items.count()) { q->beginRemoveRows(QModelIndex(), index, index); m_items.takeAt(index); q->endRemoveRows(); FavoritesConfig::instance().sync(m_items); } } void AppFavoritesModel::Private::onFolderAdded(const int &folderId, int order) { if (!m_folders.contains(folderId)) { m_folders.append(folderId); FavoritesFolder folder; FavoriteFolderHelper::instance()->getFolderFromId(folderId, folder); for (auto app : folder.getApps()) { removeFavoriteApp(app); } order = std::max(0, order); q->beginInsertRows(QModelIndex(), order, order); m_items.insert(order, FOLDER_ID_SCHEME + QString::number(folderId)); q->endInsertRows(); FavoritesConfig::instance().sync(m_items); } else { qWarning() << "Favorites Add New Folder Error: " << folderId << "Already Have!"; } } void AppFavoritesModel::Private::onFolderDeleted(const int &folderId, const QStringList &apps) { if (m_folders.contains(folderId)) { m_folders.removeOne(folderId); int index = m_items.indexOf(FOLDER_ID_SCHEME + QString::number(folderId)); q->beginRemoveRows(QModelIndex(), index, index); m_items.takeAt(index); q->endRemoveRows(); FavoritesConfig::instance().sync(m_items); for (int i = 0; i < apps.count(); i++) { QPersistentModelIndex modelIndex(m_sourceModel->index(m_sourceModel->indexOfApp(apps.at(i)), 0)); addFavoriteApp(modelIndex, index + i); } } else { qWarning() << "Favorites Remove Folder Error: " << folderId << "Does Not Exist!"; } } void AppFavoritesModel::Private::onFolderAppChanged(const int &folderId, const QString &appId, bool isAdded) { // 添加、移除应用到应用组 if (isAdded) { removeFavoriteApp(appId); } else { QPersistentModelIndex modelIndex(m_sourceModel->index(m_sourceModel->indexOfApp(appId), 0)); addFavoriteApp(modelIndex); } int row = m_items.indexOf(FOLDER_ID_SCHEME + QString::number(folderId)); Q_EMIT q->dataChanged(q->index(row), q->index(row), QVector{DataEntity::Icon}); } QPersistentModelIndex AppFavoritesModel::Private::getIndexFromAppId(const QString &id) const { auto modelIndex = std::find_if(m_favoritesApps.constBegin(), m_favoritesApps.constEnd(), [&id] (const QPersistentModelIndex &index) { return index.data(DataEntity::Id).toString() == id; }); if (modelIndex == m_favoritesApps.constEnd()) { return {}; } return *modelIndex; } AppFavoritesModel &AppFavoritesModel::instance() { static AppFavoritesModel appFavoritesModel; return appFavoritesModel; } AppFavoritesModel::AppFavoritesModel(QObject *parent) : QAbstractListModel(parent), d(new Private(this)) { d->m_sourceModel = BasicAppModel::instance(); d->init(); connect(BasicAppModel::instance(), &BasicAppModel::dataChanged, this, [&](const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { d->onAppUpdated(topLeft, bottomRight, roles); }); connect(BasicAppModel::instance(), &BasicAppModel::rowsAboutToBeRemoved, this, [&](const QModelIndex&parent, int first, int last) { d->onAppRemoved(parent, first, last); }); connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderAdded, this, [&] (int folderId, int order) { d->onFolderAdded(folderId, order); }); connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderToBeDeleted, this, [&](int folderId, const QStringList& apps) { d->onFolderDeleted(folderId, apps); }); connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderAppChanged, this, [&] (int folderId, const QString& app, bool isAdded) { d->onFolderAppChanged(folderId, app, isAdded); }); connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderAppsChanged, this, [&] (int folderId) { int row = d->m_items.indexOf(FOLDER_ID_SCHEME + QString::number(folderId)); Q_EMIT dataChanged(index(row), index(row), QVector{DataEntity::Icon}); }); connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderNameChanged, this, [&] (int folderId) { int row = d->m_items.indexOf(FOLDER_ID_SCHEME + QString::number(folderId)); Q_EMIT dataChanged(index(row), index(row), QVector{DataEntity::Name}); }); } AppFavoritesModel::~AppFavoritesModel() { if (d) { delete d; d = nullptr; } } QHash AppFavoritesModel::roleNames() const { QHash names; names.insert(DataEntity::Id, "id"); names.insert(DataEntity::Icon, "icon"); names.insert(DataEntity::Name, "name"); names.insert(DataEntity::Type, "type"); names.insert(DataEntity::DesktopName, "desktopName"); return names; } int AppFavoritesModel::rowCount(const QModelIndex &parent) const { return d->m_items.count(); } QVariant AppFavoritesModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= rowCount()) { return {}; } QString id = d->m_items.at(index.row()); // 检查并提取不同前缀后的ID if (id.startsWith(APP_ID_SCHEME)) { auto appModelIndex = d->getIndexFromAppId(id.mid(6)); return appModelIndex.data(role); } else if (id.startsWith(FOLDER_ID_SCHEME)) { return folderDataFromId(id.mid(9), role); } else if (id.startsWith(FILE_ID_SCHEME)) { return fileDataFromUrl(id.mid(7), role); } else { return {}; } } QVariant AppFavoritesModel::folderDataFromId(const QString &folderId, int role) const { FavoritesFolder folder; if (!FavoriteFolderHelper::instance()->getFolderFromId(folderId.toInt(), folder)) { return {}; } switch (role) { case DataEntity::Id: return QString::number(folder.getId()); case DataEntity::Icon: return FavoriteFolderHelper::folderIcon(folder); case DataEntity::Name: return folder.getName(); case DataEntity::Type: return DataType::Folder; default: break; } return {}; } QVariant AppFavoritesModel::fileDataFromUrl(const QString &url, int role) const { switch (role) { case DataEntity::Id: return url; case DataEntity::Icon: { QMimeDatabase mimeDatabase; return mimeDatabase.mimeTypeForFile(url).iconName(); } case DataEntity::Name: return QUrl(url).fileName(); case DataEntity::Type: return DataType::Files; default: break; } return {}; } void AppFavoritesModel::changeFileState(const QString &url, const bool &favorite) { if (url.isEmpty()) { return; } QString fileId; if (url.startsWith(FILE_ID_SCHEME)) { fileId = url; } else { fileId = FILE_ID_SCHEME + url; } if (favorite) { d->m_favoritesFiles.append(fileId); d->m_items.append(fileId); FavoritesConfig::instance().sync(d->m_items); } else { d->m_favoritesFiles.removeAll(fileId); d->m_items.removeAll(fileId); FavoritesConfig::instance().sync(d->m_items); } } int AppFavoritesModel::getOrderById(const QString& id) { return d->m_items.indexOf(id); } void AppFavoritesModel::openMenu(const int& row) { if (row < 0 || row >= rowCount()) { return; } if (data(index(row, 0), DataEntity::Type).value() == UkuiMenu::DataType::Normal) { ContextMenuManager::instance()->showMenu(data(index(row, 0), DataEntity::Entity).value(), MenuInfo::Extension, "favorite"); } else { DataEntity appData; appData.setId(data(index(row, 0), DataEntity::Id).toString()); appData.setFavorite(data(index(row, 0), DataEntity::Favorite).toInt()); appData.setType(data(index(row, 0), DataEntity::Type).value()); ContextMenuManager::instance()->showMenu(appData, MenuInfo::Extension, "favorite"); } } void AppFavoritesModel::addAppToFavorites(const QString &id, int index) { if (d->getIndexFromAppId(id).isValid() || FavoriteFolderHelper::instance()->containApp(id)) { qWarning() << "This application is already included in the favorite apps!"; return; } if (index == -1) { index = rowCount(); } QPersistentModelIndex modelIndex(d->m_sourceModel->index(d->m_sourceModel->indexOfApp(id), 0)); d->addFavoriteApp(modelIndex, index); d->m_sourceModel->databaseInterface()->fixAppToFavorite(id, 1); } void AppFavoritesModel::removeAppFromFavorites(const QString &id) { if (id.isEmpty()) { return; } d->removeFavoriteApp(id); d->m_sourceModel->databaseInterface()->fixAppToFavorite(id, 0); } void AppFavoritesModel::exchangedAppsOrder(int indexFrom, int indexTo) { indexFrom = qMin(indexFrom, d->m_items.size() -1); indexTo = qMin(indexTo, d->m_items.size() -1); if (indexFrom == indexTo || indexFrom < 0 || indexTo < 0) { return; } if (indexFrom < indexTo) { beginMoveRows(QModelIndex(), indexFrom, indexFrom, QModelIndex(), indexTo + 1); } else { beginMoveRows(QModelIndex(), indexFrom, indexFrom, QModelIndex(), indexTo); } d->m_items.move(indexFrom, indexTo); endMoveRows(); FavoritesConfig::instance().sync(d->m_items); } void AppFavoritesModel::addAppsToNewFolder(const QString& idFrom, const QString& idTo) { if (idFrom == idTo) { return; } if (d->getIndexFromAppId(idFrom).isValid() && d->getIndexFromAppId(idTo).isValid()) { FavoriteFolderHelper::instance()->addAppsToNewFolder(idFrom, idTo, ""); } } void AppFavoritesModel::addAppToFolder(const QString& appId, const QString& folderId) { if (folderId == "") { FavoriteFolderHelper::instance()->addAppToNewFolder(appId, ""); return; } FavoriteFolderHelper::instance()->addAppToFolder(appId, folderId.toInt()); } void AppFavoritesModel::clearFavorites() { // 先处理所有应用组中的应用,从应用组中移除 QSet allFolderApps; for (const auto &folder : FavoriteFolderHelper::instance()->folderData()) { for (const auto &appId : folder.getApps()) { allFolderApps.insert(appId); } } for (const auto &appId : allFolderApps) { FavoriteFolderHelper::instance()->removeAppFromFolder(appId); } // 再处理独立应用(此时包含从应用组移出的应用) auto favApps = d->m_favoritesApps; for (const auto &appIndex : favApps) { QString id = appIndex.data(DataEntity::Id).toString(); removeAppFromFavorites(id); } d->m_favoritesFiles.clear(); } QString AppFavoritesModel::getDesktopFile(const QString& dragSourceUrl) { QUrl url = QUrl(dragSourceUrl); return url.isValid() ? url.path() : ""; } } // UkuiMenu #include "moc_app-favorite-model.cpp" ukui-menu/src/extension/favorite/favorite-extension-plugin.h0000664000175000017500000000216415160463353023350 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_FAVORITE_EXTENSION_PLUGIN_H #define UKUI_MENU_FAVORITE_EXTENSION_PLUGIN_H #include "../menu-extension-plugin.h" namespace UkuiMenu { class FavoriteExtensionPlugin : public MenuExtensionPlugin { public: QString id() override; WidgetExtension *createWidgetExtension() override; ContextMenuExtension *createContextMenuExtension() override; }; } // UkuiMenu #endif //UKUI_MENU_FAVORITE_EXTENSION_PLUGIN_H ukui-menu/src/extension/favorite/favorite-folder-helper.h0000664000175000017500000000641315160463365022574 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_FAVORITE_FOLDER_HELPER_H #define UKUI_MENU_FAVORITE_FOLDER_HELPER_H #include #include #include #include #include #include namespace UkuiMenu { #define FOLDER_MAX_ICON_NUM 16 class FavoriteFolderHelper; class FavoritesFolder { Q_GADGET Q_PROPERTY(int id READ getId WRITE setId) Q_PROPERTY(QString name READ getName) Q_PROPERTY(QStringList apps READ getApps) friend class FavoriteFolderHelper; public: void setId(int folderId) {id = folderId;} int getId() const { return id; } QString getName() const { return name; } QStringList getApps() const { return apps; } QStringList getDisplayApps() const; private: int id; // 文件夹唯一Id,文件夹排序值 QString name; // 名称 QStringList apps; // 应用列表 }; class FavoriteFolderHelper : public QObject { Q_OBJECT public: static FavoriteFolderHelper *instance(); static QVariantList folderIcon(const FavoritesFolder &folder); ~FavoriteFolderHelper() override; bool getFolderFromId(const int& folderId, FavoritesFolder& folder); bool containsFolder(const int& folderId); bool containApp(const QString& appId); bool deleteFolder(const int& folderId); QList folderData(); void addAppToFolder(const QString& appId, const int& folderId); void addAppToNewFolder(const QString& appId, const QString& folderName); void addAppsToNewFolder(const QString& idFrom, const QString& idTo, const QString& folderName); void removeAppFromFolder(const QString& appId); void renameFolder(const int& folderId, const QString& folderName); void exchangedOrder(const int &indexFrom, const int &indexTo, const int& folderId); void forceSync(); QStringList appsInFolders(); Q_SIGNALS: void folderAppsChanged(int folderId); // 应用组内部顺序改变 void folderNameChanged(int folderId); // 应用组名称改变 void folderAdded(int folderId, const int& order); // 添加应用组 void folderToBeDeleted(int folderId, const QStringList& apps); // 解散应用组 void folderAppChanged(int folderId, const QString& app, bool isAdded = true); // 应用组内app添加、删除 private: FavoriteFolderHelper(); void readData(); void saveData(); void insertFolder(const FavoritesFolder& folder); // TODO 配置文件监听 private: QMutex m_mutex; //TODO 指针 QMap m_folders; static QString s_folderConfigFile; }; } // UkuiMenu #endif //UKUI_MENU_FAVORITE_FOLDER_HELPER_H ukui-menu/src/extension/favorite/favorite-context-menu.h0000664000175000017500000000262515160463353022470 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_FAVORITE_CONTEXT_MENU_H #define UKUI_MENU_FAVORITE_CONTEXT_MENU_H #include "../context-menu-extension.h" namespace UkuiMenu { class FavoriteContextMenu : public ContextMenuExtension { public: int index() const override; QList actions(const DataEntity &data, QMenu *parent, const MenuInfo::Location &location, const QString &locationId) override; private: void appendActionsForAppsInFolder(QObject *parent, const QString &appId, QList &list); void appendActionsForAppsOutsideFolder(QObject *parent, const QString &appId, QList &list); }; } // UkuiMenu #endif //UKUI_MENU_FAVORITE_CONTEXT_MENU_H ukui-menu/src/extension/favorite/favorite-filter-model.h0000664000175000017500000000375315160463353022430 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: youdiansaodongxi * */ #ifndef FAVORITEFILTERMODEL_H #define FAVORITEFILTERMODEL_H #include namespace UkuiMenu { class AppFavoritesModel; class FavoriteFilterModel : public QSortFilterProxyModel { Q_OBJECT public: static FavoriteFilterModel *instance(); QVariant data(const QModelIndex &index, int role) const override; Q_INVOKABLE void openMenu(const int &row); Q_INVOKABLE void addAppToFavorites(const QString &id, int index = -1); Q_INVOKABLE void removeAppFromFavorites(const QString &id); Q_INVOKABLE void exchangedAppsOrder(int indexFrom, int indexTo); Q_INVOKABLE void addAppsToNewFolder(const QString &idFrom, const QString &idTo); Q_INVOKABLE void addAppToFolder(const QString &appId, const QString& folderId); Q_INVOKABLE void clearFavorites(); Q_INVOKABLE QString getDesktopFile(const QString &dragSourceUrl); Q_INVOKABLE void addAppFromFolder(const QString& appId, int order = -1); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; private Q_SLOTS: void onSecurityControlChanged(); private: FavoriteFilterModel(QObject *parent = nullptr); AppFavoritesModel *m_sourceModel = nullptr; }; } // UkuiMenu #endif // FAVORITEFILTERMODEL_H ukui-menu/src/extension/favorite/favorites-config.h0000664000175000017500000000266615160463353021477 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_FAVORITES_CONFIG_H #define UKUI_MENU_FAVORITES_CONFIG_H #include #include #include static const QString APP_ID_SCHEME = "app://"; static const QString FILE_ID_SCHEME = "file://"; static const QString FOLDER_ID_SCHEME = "folder://"; namespace UkuiMenu { class FavoritesConfig : public QObject { Q_OBJECT public: static FavoritesConfig &instance(); void sync(const QStringList &list); QStringList getConfig(); private: explicit FavoritesConfig(QObject *parent = nullptr); void initConfig(); QStringList updateInvalidUrlList(QStringList &list); static QString s_favoritesConfigFile; QStringList m_configList; }; } // UkuiMenu #endif //UKUI_MENU_FAVORITES_CONFIG_H ukui-menu/src/extension/favorite/favorite-context-menu.cpp0000664000175000017500000001216115160463365023022 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "favorite-context-menu.h" #include "app-favorite-model.h" #include "event-track.h" #include "basic-app-model.h" #include "favorite-folder-helper.h" namespace UkuiMenu { int FavoriteContextMenu::index() const { return 512; } QList FavoriteContextMenu::actions(const DataEntity &data, QMenu *parent, const MenuInfo::Location &location, const QString &locationId) { if (!parent) { return {}; } QList list; if (data.type() == DataType::Folder) { list << new QAction(QIcon::fromTheme("app-group-disband-symbolic"), QObject::tr("Dissolve folder"), parent); QObject::connect(list.last(), &QAction::triggered, parent, [data] { FavoriteFolderHelper::instance()->deleteFolder(data.id().toInt()); }); return list; } switch (location) { case MenuInfo::AppList: case MenuInfo::FullScreen: case MenuInfo::FolderPage: { if (data.favorite() == 0) { list << new QAction(QIcon::fromTheme("non-starred-symbolic"), QObject::tr("Add to favorite"), parent); QObject::connect(list.last(), &QAction::triggered, parent, [data] { AppFavoritesModel::instance().addAppToFavorites(data.id()); BasicAppModel::instance()->databaseInterface()->updateApLaunchedState(data.id(), true); //BasicAppModel::instance()->databaseInterface()->fixAppToFavorite(data.id(), 1); EventTrack::instance()->sendDefaultEvent("fix_to_favorite", "Right-click Menu"); }); } else if (data.favorite() > 0) { list << new QAction(QIcon::fromTheme("ukui-cancel-star-symbolic"), QObject::tr("Remove from favorite"), parent); QObject::connect(list.last(), &QAction::triggered, parent, [data] { AppFavoritesModel::instance().removeAppFromFavorites(data.id()); BasicAppModel::instance()->databaseInterface()->fixAppToFavorite(data.id(), 0); EventTrack::instance()->sendDefaultEvent("remove_from_favorite", "Right-click Menu"); }); } break; } case MenuInfo::Extension: { appendActionsForAppsOutsideFolder(parent, data.id(), list); QAction *action = new QAction(QIcon::fromTheme("ukui-cancel-star-symbolic"), QObject::tr("Remove from favorite"), parent); QObject::connect(action, &QAction::triggered, parent, [data] { AppFavoritesModel::instance().removeAppFromFavorites(data.id()); EventTrack::instance()->sendDefaultEvent("remove_from_favorite", "Right-click Menu"); }); list << action; break; } case MenuInfo::Folder: { appendActionsForAppsInFolder(parent, data.id(), list); break; } default: break; } return list; } void FavoriteContextMenu::appendActionsForAppsInFolder(QObject *parent, const QString &appId, QList &list) { FavoritesFolder folder; list << new QAction(QIcon::fromTheme("app-group-moveout-symbolic"), QObject::tr("Remove from folder"), parent); QObject::connect(list.last(), &QAction::triggered, parent, [appId] { FavoriteFolderHelper::instance()->removeAppFromFolder(appId); }); } void FavoriteContextMenu::appendActionsForAppsOutsideFolder(QObject *parent, const QString &appId, QList &list) { QAction *action = new QAction(QIcon::fromTheme("app-group-movein-symbolic"), QObject::tr("Move to folder"), parent); QMenu *subMenu = new QMenu(); QAction *subAction = new QAction(QObject::tr("Create a new folder"), parent); QObject::connect(subAction, &QAction::triggered, parent, [appId] { AppFavoritesModel::instance().addAppToFolder(appId, ""); }); subMenu->addAction(subAction); for (auto folder : FavoriteFolderHelper::instance()->folderData()) { QString folderName = folder.getName(); int folderId = folder.getId(); QAction *subAction = new QAction(QObject::tr("Add to \"%1\"").arg(folderName), parent); QObject::connect(subAction, &QAction::triggered, parent, [appId, folderId] { AppFavoritesModel::instance().addAppToFolder(appId, QString::number(folderId)); }); subMenu->addAction(subAction); } action->setMenu(subMenu); list << action; } } // UkuiMenu ukui-menu/src/extension/favorite/favorite-filter-model.cpp0000664000175000017500000000753215160463365022765 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: youdiansaodongxi * */ #include "favorite-filter-model.h" #include "app-favorite-model.h" #include "favorites-config.h" #include "security-function-control.h" #include "favorite-folder-helper.h" #include namespace UkuiMenu { FavoriteFilterModel *FavoriteFilterModel::instance() { static FavoriteFilterModel model; return &model; } QVariant FavoriteFilterModel::data(const QModelIndex &index, int role) const { return sourceModel()->data(mapToSource(index), role); } void FavoriteFilterModel::openMenu(const int &row) { m_sourceModel->openMenu(mapToSource(this->index(row, 0)).row()); } void FavoriteFilterModel::addAppToFavorites(const QString &id, int index) { if (index != -1) { index = mapToSource(this->index(index, 0)).row(); } m_sourceModel->addAppToFavorites(id, index); } void FavoriteFilterModel::removeAppFromFavorites(const QString &id) { m_sourceModel->removeAppFromFavorites(id); } void FavoriteFilterModel::exchangedAppsOrder(int indexFrom, int indexTo) { indexFrom = mapToSource(this->index(indexFrom, 0)).row(); indexTo = mapToSource(this->index(indexTo, 0)).row(); m_sourceModel->exchangedAppsOrder(indexFrom, indexTo); } void FavoriteFilterModel::addAppsToNewFolder(const QString &idFrom, const QString &idTo) { m_sourceModel->addAppsToNewFolder(idFrom, idTo); } void FavoriteFilterModel::addAppToFolder(const QString &appId, const QString &folderId) { m_sourceModel->addAppToFolder(appId, folderId); } void FavoriteFilterModel::clearFavorites() { m_sourceModel->clearFavorites(); } QString FavoriteFilterModel::getDesktopFile(const QString &dragSourceUrl) { return m_sourceModel->getDesktopFile(dragSourceUrl); } void FavoriteFilterModel::addAppFromFolder(const QString& appId, int order) { FavoriteFolderHelper::instance()->removeAppFromFolder(appId); order = qBound(0, order, m_sourceModel->rowCount() - 1); exchangedAppsOrder(0, order); } bool FavoriteFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { QModelIndex sourceIndex = sourceModel()->index(source_row, 0, source_parent); if (sourceIndex.data(DataEntity::Type) == DataType::Folder) { FavoritesFolder folder; FavoriteFolderHelper::instance()->getFolderFromId(sourceIndex.data(DataEntity::Id).toInt(), folder); return !folder.getDisplayApps().isEmpty(); } return SecurityFunctionControl::instance()->canAppDisplay(sourceIndex.data(DataEntity::Id).toString()); } void FavoriteFilterModel::onSecurityControlChanged() { invalidateFilter(); dataChanged(index(0,0), index(rowCount() - 1, 0), {DataEntity::Icon}); } FavoriteFilterModel::FavoriteFilterModel(QObject *parent) : QSortFilterProxyModel(parent) { setSourceModel(&AppFavoritesModel::instance()); m_sourceModel = &AppFavoritesModel::instance(); connect(SecurityFunctionControl::instance(), &SecurityFunctionControl::appWhiteListChanged, this, &FavoriteFilterModel::onSecurityControlChanged); connect(SecurityFunctionControl::instance(), &SecurityFunctionControl::appBlackListChanged, this, &FavoriteFilterModel::onSecurityControlChanged); } } // UkuiMenu ukui-menu/src/extension/favorite/favorite-widget.cpp0000664000175000017500000000362415160463353021660 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "favorite-widget.h" #include "favorite-filter-model.h" namespace UkuiMenu { FavoriteWidget::FavoriteWidget(QObject *parent) : WidgetExtension(parent) { m_metadata.insert(WidgetMetadata::Id, "favorite"); m_metadata.insert(WidgetMetadata::Icon, "non-starred-symbolic"); m_metadata.insert(WidgetMetadata::Name, tr("Favorite")); m_metadata.insert(WidgetMetadata::Tooltip, tr("favorite")); m_metadata.insert(WidgetMetadata::Version, "1.0.0"); m_metadata.insert(WidgetMetadata::Description, "favorite"); m_metadata.insert(WidgetMetadata::Main, "qrc:///qml/extensions/FavoriteExtension.qml"); m_metadata.insert(WidgetMetadata::Type, WidgetMetadata::Widget); m_metadata.insert(WidgetMetadata::Flag, WidgetMetadata::Normal); m_data.insert("favoriteAppsModel", QVariant::fromValue(FavoriteFilterModel::instance())); m_data.insert("folderModel", QVariant::fromValue(&FolderModel::instance())); } int FavoriteWidget::index() const { return 0; } MetadataMap FavoriteWidget::metadata() const { return m_metadata; } QVariantMap FavoriteWidget::data() { return m_data; } void FavoriteWidget::receive(const QVariantMap &data) { WidgetExtension::receive(data); } } // UkuiMenu ukui-menu/src/extension/favorite/favorite-extension-plugin.cpp0000664000175000017500000000215115160463353023677 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "favorite-extension-plugin.h" #include "favorite-widget.h" #include "favorite-context-menu.h" namespace UkuiMenu { QString FavoriteExtensionPlugin::id() { return "favorite"; } WidgetExtension *FavoriteExtensionPlugin::createWidgetExtension() { return new FavoriteWidget; } ContextMenuExtension *FavoriteExtensionPlugin::createContextMenuExtension() { return new FavoriteContextMenu; } } // UkuiMenu ukui-menu/src/extension/favorite/folder-model.cpp0000664000175000017500000000736615160463365021143 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "folder-model.h" #include "app-folder-helper.h" #include "app-data-manager.h" #include "favorite/favorite-folder-helper.h" #include "context-menu-manager.h" #include "libappdata/basic-app-model.h" #include "security-function-control.h" namespace UkuiMenu { FolderModel::FolderModel(QObject *parent) : QAbstractListModel(parent) { connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderAppsChanged, this, &FolderModel::loadFolderData); connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderAppChanged, this, &FolderModel::loadFolderData); connect(FavoriteFolderHelper::instance(), &FavoriteFolderHelper::folderToBeDeleted, this, [this] (int id) { if (id == m_folderId) { beginResetModel(); m_folderId = -1; m_apps.clear(); endResetModel(); } }); connect(SecurityFunctionControl::instance(), &SecurityFunctionControl::appWhiteListChanged, [this] { loadFolderData(m_folderId); }); connect(SecurityFunctionControl::instance(), &SecurityFunctionControl::appBlackListChanged, [this] { loadFolderData(m_folderId); }); } void FolderModel::setFolderId(const QString &folderId) { bool ok; int id = folderId.toInt(&ok); if (!ok) { return; } loadFolderData(id); } void FolderModel::renameFolder(const QString &folderName) { FavoriteFolderHelper::instance()->renameFolder(m_folderId, folderName); } void FolderModel::exchangedOrder(const int& indexFrom, const int& indexTo) { FavoriteFolderHelper::instance()->exchangedOrder(indexFrom, indexTo, m_folderId); } void FolderModel::loadFolderData(int id) { FavoritesFolder folder; if (!FavoriteFolderHelper::instance()->getFolderFromId(id, folder)) { return; } beginResetModel(); m_folderId = id; m_apps = folder.getDisplayApps(); endResetModel(); Q_EMIT countChanged(); } int FolderModel::rowCount(const QModelIndex &parent) const { return m_apps.count(); } QVariant FolderModel::data(const QModelIndex &index, int role) const { int i = index.row(); if (i < 0 || i >= m_apps.size()) { return {}; } DataEntity app; if (!BasicAppModel::instance()->getAppById(m_apps.at(i), app)) { return {}; } switch (role) { case DataEntity::Id: return app.id(); case DataEntity::Type: return app.type(); case DataEntity::Icon: return app.icon(); case DataEntity::Name: return app.name(); case DataEntity::Comment: return app.comment(); case DataEntity::ExtraData: return app.extraData(); case DataEntity::DesktopName: return app.desktopName(); default: break; } return {}; } QHash FolderModel::roleNames() const { return DataEntity::AppRoleNames(); } int FolderModel::count() { return m_apps.count(); } FolderModel &FolderModel::instance() { static FolderModel folderModel; return folderModel; } } // UkuiMenu ukui-menu/src/extension/favorite/favorites-config.cpp0000664000175000017500000001145615160463353022027 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include #include #include #include #include #include "favorites-config.h" #include "basic-app-model.h" #include "favorite-folder-helper.h" #define FOLDER_FILE_PATH ".config/ukui-menu/" #define FOLDER_FILE_NAME "favorite.json" namespace UkuiMenu { QString FavoritesConfig::s_favoritesConfigFile = QDir::homePath() + "/" + FOLDER_FILE_PATH + FOLDER_FILE_NAME; /** * 添加版本号,区分旧文件内容 */ static const QString FAVORITE_CONFIG_VERSION = QStringLiteral("1.0.0"); FavoritesConfig &FavoritesConfig::instance() { static FavoritesConfig favoritesConfig; return favoritesConfig; } FavoritesConfig::FavoritesConfig(QObject *parent) { initConfig(); } void FavoritesConfig::sync(const QStringList &list) { if (m_configList == list) { return; } m_configList = list; QDir dir; QString folderConfigDir(QDir::homePath() + "/" + FOLDER_FILE_PATH); if (!dir.exists(folderConfigDir)) { if (!dir.mkdir(folderConfigDir)) { qWarning() << "FavoritesConfig::Unable to create configuration file."; return; } } QFile file(s_favoritesConfigFile); file.open(QFile::WriteOnly); QJsonDocument jsonDocument; QJsonObject fileObject; fileObject.insert("version", FAVORITE_CONFIG_VERSION); fileObject.insert("favorites", QJsonArray::fromStringList(m_configList)); jsonDocument.setObject(fileObject); if (file.write(jsonDocument.toJson()) == -1) { qWarning() << "FavoritesConfig::Error saving configuration file."; } file.flush(); file.close(); } QStringList FavoritesConfig::getConfig() { return m_configList; } void FavoritesConfig::initConfig() { QFile file(s_favoritesConfigFile); if (!file.open(QFile::ReadOnly)) { qWarning() << "FavoritesConfig: configuration files open failed."; return; } QJsonDocument jsonDocument(QJsonDocument::fromJson(file.readAll())); file.close(); if (jsonDocument.isNull() || jsonDocument.isEmpty()) { qWarning() << "FavoritesConfig: configuration files format is empty."; return; } // 配置文件补充添加版本号1.0.0 QStringList list, invalidUrl; if (jsonDocument.isObject()) { QJsonObject fileObject = jsonDocument.object(); if (fileObject.value("version").toString() == FAVORITE_CONFIG_VERSION && fileObject.contains("favorites")) { for (const QVariant &variant : fileObject.value(QLatin1String("favorites")).toArray().toVariantList()) { list << variant.toString(); } invalidUrl = updateInvalidUrlList(list); if (invalidUrl.isEmpty()) { m_configList = list; } else { for (const QString &id : invalidUrl) { list.removeAll(id); } sync(list); } } } else if (jsonDocument.isArray()) { for (const QVariant &variant : jsonDocument.array().toVariantList()) { list << variant.toString(); } invalidUrl = updateInvalidUrlList(list); if (!invalidUrl.isEmpty()) { for (const QString&id : invalidUrl) { list.removeAll(id); } } sync(list); } else { qWarning() << "FavoritesConfig: configuration files format is incorrect."; } } QStringList FavoritesConfig::updateInvalidUrlList(QStringList &list) { QStringList invalidUrl; for (auto &url : list) { if (url.startsWith(APP_ID_SCHEME)) { QString appId = url.mid(6); if (FavoriteFolderHelper::instance()->containApp(appId) || appId.isEmpty() || ( BasicAppModel::instance()->indexOfApp(appId) < 0)) { invalidUrl.append(url); } } else if (url.startsWith(FOLDER_ID_SCHEME)) { bool ok; int folderId = url.mid(9).toInt(&ok); if (!ok || !FavoriteFolderHelper::instance()->containsFolder(folderId)) { invalidUrl.append(url); } } } return invalidUrl; } } // UkuiMenu ukui-menu/src/extension/favorite/folder-model.h0000664000175000017500000000317415160463365020601 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_FOLDER_MODEL_H #define UKUI_MENU_FOLDER_MODEL_H #include #include "commons.h" namespace UkuiMenu { class FolderModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(int count READ count NOTIFY countChanged) public: static FolderModel &instance(); Q_INVOKABLE void setFolderId(const QString &folderId); Q_INVOKABLE void renameFolder(const QString &folderName); Q_INVOKABLE void exchangedOrder(const int &indexFrom, const int &indexTo); int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; int count(); Q_SIGNALS: void countChanged(); private Q_SLOTS: void loadFolderData(int id); private: explicit FolderModel(QObject *parent = nullptr); int m_folderId; QStringList m_apps; }; } // UkuiMenu #endif //UKUI_MENU_FOLDER_MODEL_H ukui-menu/src/extension/favorite/app-favorite-model.h0000664000175000017500000000553715160463353021725 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_FAVORITE_MODEL_H #define UKUI_MENU_APP_FAVORITE_MODEL_H #include "data-entity.h" #include "libappdata/basic-app-model.h" #include #include namespace UkuiMenu { class AppFavoritesModel : public QAbstractListModel { Q_OBJECT public: static AppFavoritesModel &instance(); ~AppFavoritesModel() override; QHash roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; void changeFileState(const QString &url, const bool &favorite); //TODO int getOrderById(const QString&id); void openMenu(const int &row); /** * 通过id添加应用到收藏 * @param id * @param index 默认为-1,添加到收藏最后一位;可以添加到收藏指定位置 */ void addAppToFavorites(const QString &id, int index = -1); /** * 通过id从收藏中移除 * @param id */ void removeAppFromFavorites(const QString &id); /** * 拖拽交换位置。 * @param indexFrom * @param indexTo */ void exchangedAppsOrder(int indexFrom, int indexTo); /** * 两个应用推拽合并为应用组。 * @param idFrom * @param idTo 拖拽至某一应用,并在该位置创建应用组 */ void addAppsToNewFolder(const QString &idFrom, const QString &idTo); /** * 添加应用到应用组,包括新建应用组。 * @param appId * @param folderId id为""时,新建应用组;id为数值时,添加至对应应用组 */ void addAppToFolder(const QString &appId, const QString& folderId); /** * 清空所有已收藏应用 */ void clearFavorites(); /** * 通过url获取对应的应用名称 **/ QString getDesktopFile(const QString &dragSourceUrl); private: explicit AppFavoritesModel(QObject* parent = nullptr); QVariant folderDataFromId(const QString &id, int role) const; QVariant fileDataFromUrl(const QString &url, int role) const; class Private; Private *d = nullptr; }; } // UkuiMenu #endif //UKUI_MENU_APP_FAVORITE_MODEL_H ukui-menu/src/extension/favorite/favorite-folder-helper.cpp0000664000175000017500000002622115160463365023126 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include #include #include #include #include #include #include #include "favorite-folder-helper.h" #include "app-favorite-model.h" #include "event-track.h" #include "favorites-config.h" #include "libappdata/basic-app-model.h" #include "security-function-control.h" #define FOLDER_FILE_PATH ".config/ukui-menu/" #define FOLDER_FILE_NAME "folder.json" namespace UkuiMenu { QString FavoriteFolderHelper::s_folderConfigFile = QDir::homePath() + "/" + FOLDER_FILE_PATH + FOLDER_FILE_NAME; /** * changelog 1.0.0 添加版本号,区分旧文件内容 */ static const QString FOLDER_CONFIG_VERSION = QStringLiteral("1.0.0"); FavoriteFolderHelper *FavoriteFolderHelper::instance() { static FavoriteFolderHelper FavoriteFolderHelper; return &FavoriteFolderHelper; } FavoriteFolderHelper::FavoriteFolderHelper() { qRegisterMetaType("FavoritesFolder"); if (!QFile::exists(s_folderConfigFile)) { QDir dir; QString folderConfigDir(QDir::homePath() + "/" + FOLDER_FILE_PATH); if (!dir.exists(folderConfigDir)) { if (!dir.mkdir(folderConfigDir)) { qWarning() << "Unable to create profile folder."; return; } } QFile file(s_folderConfigFile); file.open(QFile::WriteOnly); file.close(); } readData(); } FavoriteFolderHelper::~FavoriteFolderHelper() { saveData(); } void FavoriteFolderHelper::insertFolder(const FavoritesFolder &folder) { QMutexLocker locker(&m_mutex); m_folders.insert(folder.id, folder); } void FavoriteFolderHelper::addAppToFolder(const QString &appId, const int &folderId) { if (appId.isEmpty()) { return; } { QMutexLocker locker(&m_mutex); if (!m_folders.contains(folderId)) { return; } FavoritesFolder &folder = m_folders[folderId]; if (folder.apps.contains(appId)) { return; } folder.apps.append(appId); } forceSync(); Q_EMIT folderAppChanged(folderId, appId, true); EventTrack::instance()->sendDefaultEvent("add_app_to_folder", "AppView"); } void FavoriteFolderHelper::addAppToNewFolder(const QString &appId, const QString &folderName) { if (appId.isEmpty()) { return; } // TODO: max越界处理 int max = m_folders.isEmpty() ? -1 : m_folders.lastKey(); QString name = folderName; if (name.isEmpty()) { name = tr("New Folder %1").arg(m_folders.size() + 1); } FavoritesFolder folder; folder.id = ++max; folder.name = name; folder.apps.append(appId); insertFolder(folder); forceSync(); Q_EMIT folderAdded(folder.id, AppFavoritesModel::instance().getOrderById(APP_ID_SCHEME + appId)); EventTrack::instance()->sendDefaultEvent("add_app_to_new_folder", "AppView"); } void FavoriteFolderHelper::addAppsToNewFolder(const QString &idFrom, const QString &idTo, const QString &folderName) { if (idFrom.isEmpty() || idTo.isEmpty()) { return; } // TODO: max越界处理 int max = m_folders.isEmpty() ? -1 : m_folders.lastKey(); QString name = folderName; if (name.isEmpty()) { name = tr("New Folder %1").arg(m_folders.size() + 1); } FavoritesFolder folder; folder.id = ++max; folder.name = name; folder.apps.append(idFrom); folder.apps.append(idTo); insertFolder(folder); // 确定folder位置 int orderTo = AppFavoritesModel::instance().getOrderById(APP_ID_SCHEME + idTo); int orderFrom = AppFavoritesModel::instance().getOrderById(APP_ID_SCHEME + idFrom); int folderOrder; if (orderFrom > orderTo) { folderOrder = orderTo; } else { folderOrder = orderTo - 1; } Q_EMIT folderAdded(folder.id, folderOrder); forceSync(); } void FavoriteFolderHelper::removeAppFromFolder(const QString& appId) { if (appId.isEmpty()) { return; } int folderId; { QMutexLocker locker(&m_mutex); for (const auto &folder: m_folders) { if (folder.apps.contains(appId)) { folderId = folder.getId(); } } if (!m_folders.contains(folderId)) { return; } FavoritesFolder &folder = m_folders[folderId]; if (!folder.apps.contains(appId)) { return; } folder.apps.removeOne(appId); } if (m_folders[folderId].getApps().isEmpty()) { deleteFolder(folderId); } forceSync(); Q_EMIT folderAppChanged(folderId, appId, false); } bool FavoriteFolderHelper::deleteFolder(const int& folderId) { Q_EMIT folderToBeDeleted(folderId, m_folders[folderId].getApps()); { QMutexLocker locker(&m_mutex); if (!m_folders.contains(folderId)) { return false; } if (!m_folders.remove(folderId)) { return false; } } forceSync(); EventTrack::instance()->sendDefaultEvent("delete_folder", "AppView"); return true; } void FavoriteFolderHelper::renameFolder(const int &folderId, const QString &folderName) { { QMutexLocker locker(&m_mutex); if (!m_folders.contains(folderId)) { return; } if (m_folders[folderId].name == folderName) { return; } m_folders[folderId].name = folderName; } Q_EMIT folderNameChanged(folderId); forceSync(); } void FavoriteFolderHelper::exchangedOrder(const int& indexFrom, const int& indexTo, const int& folderId) { if (indexFrom == indexTo) { return; } { QMutexLocker locker(&m_mutex); if (!m_folders.contains(folderId)) { return; } FavoritesFolder &folder = m_folders[folderId]; folder.apps.move(indexFrom, indexTo); } Q_EMIT folderAppsChanged(folderId); forceSync(); } QList FavoriteFolderHelper::folderData() { QMutexLocker locker(&m_mutex); return m_folders.values(); } bool FavoriteFolderHelper::getFolderFromId(const int &folderId, FavoritesFolder& folder) { QMutexLocker locker(&m_mutex); if (!m_folders.contains(folderId)) { return false; } const FavoritesFolder &tmp = m_folders[folderId]; folder = tmp; return true; } bool FavoriteFolderHelper::containsFolder(const int& folderId) { QMutexLocker locker(&m_mutex); return m_folders.contains(folderId); } bool FavoriteFolderHelper::containApp(const QString &appId) { QMutexLocker locker(&m_mutex); return std::any_of(m_folders.constBegin(), m_folders.constEnd(), [appId] (const FavoritesFolder &folder) { return folder.apps.contains(appId); }); } void FavoriteFolderHelper::forceSync() { saveData(); } void FavoriteFolderHelper::readData() { QFile file(s_folderConfigFile); if (!file.open(QFile::ReadOnly)) { return; } // 读取json数据 QTextStream stream(&file); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) stream.setCodec("UTF-8"); #else stream.setEncoding(QStringConverter::Utf8); #endif QString str = stream.readAll(); file.close(); QJsonParseError jsonError; QJsonDocument jsonDocument(QJsonDocument::fromJson(str.toUtf8(),&jsonError)); if (jsonDocument.isNull() || jsonDocument.isEmpty()) { qWarning() << "FavoriteFolderHelper: Incorrect configuration files are ignored." << jsonError.error; return; } { QMutexLocker locker(&m_mutex); m_folders.clear(); } QJsonObject fileObject = jsonDocument.object(); if (fileObject.value("version").toString() == FOLDER_CONFIG_VERSION) { if (fileObject.contains("folders")) { QJsonArray jsonArray = fileObject.value(QLatin1String("folders")).toArray(); for (const auto &value : jsonArray) { QJsonObject object = value.toObject(); if (object.contains("name") && object.contains("id") && object.contains("apps")) { FavoritesFolder folder; folder.name = object.value(QLatin1String("name")).toString(); folder.id = object.value(QLatin1String("id")).toInt(); QJsonArray apps = object.value(QLatin1String("apps")).toArray(); for (const auto &app : apps) { folder.apps.append(app.toString()); } if (!folder.apps.isEmpty()) { insertFolder(folder); } } } } } else { saveData(); return; } } void FavoriteFolderHelper::saveData() { QFile file(s_folderConfigFile); if (!file.open(QFile::WriteOnly)) { return; } QJsonDocument jsonDocument; QJsonObject fileObject; QJsonArray folderArray; { QMutexLocker locker(&m_mutex); for (const auto &folder : m_folders) { QJsonObject object; QJsonArray apps; for (const auto &app : folder.apps) { apps.append(app); } object.insert("name", folder.name); object.insert("id", folder.id); object.insert("apps", apps); folderArray.append(object); } } fileObject.insert("version", FOLDER_CONFIG_VERSION); fileObject.insert("folders", folderArray); jsonDocument.setObject(fileObject); if (file.write(jsonDocument.toJson()) == -1) { qWarning() << "Error saving configuration file."; } file.flush(); file.close(); } QVariantList FavoriteFolderHelper::folderIcon(const FavoritesFolder &folder) { // TODO: 使用绘图API生成图片 QVariantList icons; DataEntity app; int count = qMin(folder.getDisplayApps().count(), FOLDER_MAX_ICON_NUM); for (int i = 0; i < count; ++i) { if (BasicAppModel::instance()->getAppById(folder.getDisplayApps().at(i), app)) { QVariantMap icon; icon["icon"] = app.icon(); icon["desktopName"] = app.desktopName(); icons.append(icon); } } return icons; } QStringList FavoriteFolderHelper::appsInFolders() { QStringList apps; for (const auto &folder : m_folders) { apps.append(folder.apps); } return apps; } QStringList FavoritesFolder::getDisplayApps() const { QStringList list; for (const QString &app : apps) { if (SecurityFunctionControl::instance()->canAppDisplay(app)) { list.append(app); } } return list; } } // UkuiMenu ukui-menu/src/extension/menu-extension-loader.h0000664000175000017500000000320215160463353020620 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_MENU_EXTENSION_LOADER_H #define UKUI_MENU_MENU_EXTENSION_LOADER_H #include #include #include #include "widget-extension.h" #include "context-menu-extension.h" namespace UkuiMenu { class MenuExtensionPlugin; class MenuExtensionLoader { public: static MenuExtensionLoader *instance(); QList widgets() const; QList menus() const; private: MenuExtensionLoader(); void loadInternalExtension(); void loadExtensionFromDisk(); void loadExtensionFromDir(QDir dir); void setBlackList(const QStringList &blackList); void load(); void expand(); void registerExtension(MenuExtensionPlugin *plugin); private: QStringList m_blackList; QList m_widgets; QList m_menus; static QHash plugins; }; } // UkuiMenu #endif //UKUI_MENU_MENU_EXTENSION_LOADER_H ukui-menu/src/extension/widget-model.h0000664000175000017500000000325515160463353016767 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_WIDGET_MODEL_H #define UKUI_MENU_WIDGET_MODEL_H #include #include #include "widget-extension.h" namespace UkuiMenu { class WidgetModel : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(UkuiMenu::WidgetMetadata::Types types READ types WRITE setTypes) Q_PROPERTY(UkuiMenu::WidgetMetadata::Flags flags READ flags WRITE setFlags) public: explicit WidgetModel(QObject *parent = nullptr); Q_INVOKABLE void init(); Q_INVOKABLE void send(int index, const QVariantMap &data); WidgetMetadata::Types types() const; void setTypes(WidgetMetadata::Types types); WidgetMetadata::Flags flags() const; void setFlags(WidgetMetadata::Flags flags); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; private: WidgetMetadata::Types m_types = WidgetMetadata::Widget; WidgetMetadata::Flags m_flags = WidgetMetadata::Normal; }; } #endif //UKUI_MENU_WIDGET_MODEL_H ukui-menu/src/extension/extensions/0000775000175000017500000000000015160463353016427 5ustar fengfengukui-menu/src/extension/extensions/favorite-extension.h0000664000175000017500000000262015160463353022431 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_FAVORITE_EXTENSION_H #define UKUI_MENU_FAVORITE_EXTENSION_H #include "../menu-extension-iface.h" namespace UkuiMenu { class FavoriteAppsModel; class FavoriteExtension : public MenuExtensionIFace { Q_OBJECT public: explicit FavoriteExtension(QObject *parent = nullptr); ~FavoriteExtension(); void receive(QVariantMap data) override; int index() override; QString name() override; QUrl url() override; QVariantMap data() override; private: QVariantMap m_data; FavoriteAppsModel *m_favoriteAppsModel = nullptr; private: void updateFavoriteData(); void openFavoriteApp(const QString &path); }; } // UkuiMenu #endif //UKUI_MENU_FAVORITE_EXTENSION_H ukui-menu/src/extension/extensions/favorite-extension.cpp0000664000175000017500000001552015160463353022767 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "favorite-extension.h" #include "app-data-manager.h" #include "app-manager.h" #include "commons.h" #include "settings.h" #include "event-track.h" #include #include #include #include namespace UkuiMenu { class FavoriteMenuProvider : public MenuProvider { public: int index() override { return 512; } bool isSupport(const RequestType &type) override; QList generateActions(QObject *parent, const QVariant &data, const MenuInfo::Location &location, const QString &locationId) override; }; class FavoriteAppsModel : public QAbstractListModel { Q_OBJECT public: enum RoleMessage { Id = Qt::UserRole, Icon = Qt::UserRole + 1, Name = Qt::UserRole + 2 }; explicit FavoriteAppsModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; QHash roleNames() const override; QVariant data(const QModelIndex &index, int role) const override; void setFavoriteAppsData(QVector &apps); Q_INVOKABLE void openMenu(const int &index); public Q_SLOTS: void exchangedAppsOrder(int startIndex, int endIndex); void onStyleChanged(const GlobalSetting::Key &key); private: QVector m_favoriteAppsData; UkuiSearch::ApplicationInfo *m_appInfoTable = nullptr; }; FavoriteExtension::FavoriteExtension(QObject *parent) : MenuExtensionIFace(parent) { qRegisterMetaType("FavoriteAppsModel*"); MenuManager::instance()->registerMenuProvider(new FavoriteMenuProvider); m_favoriteAppsModel = new FavoriteAppsModel(this); m_data.insert("favoriteAppsModel", QVariant::fromValue(m_favoriteAppsModel)); updateFavoriteData(); connect(AppDataManager::instance(),&AppDataManager::favoriteAppChanged, this,&FavoriteExtension::updateFavoriteData); connect(GlobalSetting::instance(), &GlobalSetting::styleChanged, m_favoriteAppsModel, &FavoriteAppsModel::onStyleChanged); } FavoriteExtension::~FavoriteExtension() { } void FavoriteExtension::receive(QVariantMap data) { QString path = data.value("id").toString(); openFavoriteApp(path); } int FavoriteExtension::index() { return 0; } QString FavoriteExtension::name() { return tr("Favorite"); } QUrl FavoriteExtension::url() { return {"qrc:///qml/extensions/FavoriteExtension.qml"}; } QVariantMap FavoriteExtension::data() { return m_data; } void FavoriteExtension::openFavoriteApp(const QString &path) { AppManager::instance()->launchApp(path); } void FavoriteExtension::updateFavoriteData() { QVector favoriteApps = AppDataManager::instance()->favoriteApps(); m_favoriteAppsModel->setFavoriteAppsData(favoriteApps); } FavoriteAppsModel::FavoriteAppsModel(QObject *parent) : QAbstractListModel(parent) { m_appInfoTable = new UkuiSearch::ApplicationInfo(this); } int FavoriteAppsModel::rowCount(const QModelIndex &parent) const { return m_favoriteAppsData.count(); } QHash FavoriteAppsModel::roleNames() const { QHash names; names.insert(Id,"id"); names.insert(Icon,"icon"); names.insert(Name,"name"); return names; } QVariant FavoriteAppsModel::data(const QModelIndex &index, int role) const { int row = index.row(); if (row < 0 || row > m_favoriteAppsData.count()) { return {}; } switch (role) { case Id: return m_favoriteAppsData.at(row).id(); case Icon: return m_favoriteAppsData.at(row).icon(); case Name: return m_favoriteAppsData.at(row).name(); default: break; } return {}; } void FavoriteAppsModel::setFavoriteAppsData(QVector &apps) { beginResetModel(); m_favoriteAppsData.swap(apps); endResetModel(); } void FavoriteAppsModel::openMenu(const int &index) { if (index < 0 || index >= m_favoriteAppsData.size()) { return; } MenuManager::instance()->showMenu(m_favoriteAppsData.at(index), MenuInfo::Extension, "favorite"); } void FavoriteAppsModel::exchangedAppsOrder(int startIndex, int endIndex) { int endNum = m_favoriteAppsData.at(endIndex).favorite(); QString startId = m_favoriteAppsData.at(startIndex).id(); AppDataManager::instance()->changedFavoriteOrderSignal(startId, endNum); } void FavoriteAppsModel::onStyleChanged(const GlobalSetting::Key &key) { if (key == GlobalSetting::IconThemeName) { beginResetModel(); endResetModel(); } } bool FavoriteMenuProvider::isSupport(const MenuProvider::RequestType &type) { return type == DataType; } QList FavoriteMenuProvider::generateActions(QObject *parent, const QVariant &data, const MenuInfo::Location &location, const QString &locationId) { if (!parent) { return {}; } DataEntity app = data.value(); if (app.type() != DataType::Normal) { return {}; } QList list; switch (location) { case MenuInfo::AppList: case MenuInfo::FolderPage: case MenuInfo::Extension: { if (app.favorite() == 0) { list << new QAction(QObject::tr("Fix to favorite"), parent); QObject::connect(list.last(), &QAction::triggered, parent, [app] { Q_EMIT AppDataManager::instance()->fixToFavoriteSignal(app.id(), 1); EventTrack::instance()->sendDefaultEvent("fix_to_favorite", "Right-click Menu"); }); } else if (locationId == "favorite") { list << new QAction(QObject::tr("Remove from favorite"), parent); QObject::connect(list.last(), &QAction::triggered, parent, [app] { Q_EMIT AppDataManager::instance()->fixToFavoriteSignal(app.id(), 0); EventTrack::instance()->sendDefaultEvent("remove_from_favorite", "Right-click Menu"); }); } break; } case MenuInfo::FullScreen: case MenuInfo::FullScreenFolder: default: break; } return list; } } // UkuiMenu #include "favorite-extension.moc" ukui-menu/src/extension/widget-extension-model.h0000664000175000017500000000312515160463353020775 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_WIDGET_EXTENSION_MODEL_H #define UKUI_MENU_WIDGET_EXTENSION_MODEL_H #include namespace UkuiMenu { class WidgetExtension; class WidgetExtensionModel : public QAbstractListModel { Q_OBJECT public: static WidgetExtensionModel *instance(); explicit WidgetExtensionModel(QObject *parent = nullptr); QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; Q_INVOKABLE UkuiMenu::WidgetExtension *widgetAt(int index) const; Q_INVOKABLE void notify(int index, const QVariantMap &data) const; private: QList m_widgets; }; } // UkuiMenu #endif //UKUI_MENU_WIDGET_EXTENSION_MODEL_H ukui-menu/src/extension/context-menu-manager.cpp0000775000175000017500000000543415160463353021003 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "context-menu-manager.h" #include "menu-extension-loader.h" #include "basic-app-filter-model.h" #include #include #include #include using namespace UkuiMenu; ContextMenuManager *ContextMenuManager::instance() { static ContextMenuManager manager; return &manager; } class MenuManagerPrivate { public: QPointer contextMenu; QWindow *mainWindow {nullptr}; QList extensions; }; ContextMenuManager::ContextMenuManager() : d(new MenuManagerPrivate) { d->extensions = MenuExtensionLoader::instance()->menus(); } ContextMenuManager::~ContextMenuManager() { for (auto &provider : d->extensions) { delete provider; provider = nullptr; } } void ContextMenuManager::showMenu(const QString &appid, const MenuInfo::Location location, const QString &lid, const QPoint &point) { DataEntity app; if (BasicAppFilterModel::instance()->getAppById(appid, app)) { showMenu(app, location, lid, point); } } void ContextMenuManager::showMenu(const DataEntity &data, MenuInfo::Location location, const QString &lid, const QPoint &point) { if (closeMenu()) { return; } auto menu = new QMenu; QList actions; for (const auto &extension : d->extensions) { actions.append(extension->actions(data, menu, location, lid)); } if (actions.isEmpty() && menu->isEmpty()) { delete menu; return; } d->contextMenu = menu; menu->setAttribute(Qt::WA_DeleteOnClose); if (menu->winId()) { menu->windowHandle()->setTransientParent(d->mainWindow); } menu->addActions(actions); menu->popup(checkPoint(point)); } void ContextMenuManager::setMainWindow(QWindow *mainWindow) { d->mainWindow = mainWindow; } bool ContextMenuManager::closeMenu() { if (d->contextMenu) { d->contextMenu.data()->close(); return true; } return false; } inline QPoint ContextMenuManager::checkPoint(const QPoint &rawPoint) { if (rawPoint.isNull()) { return QCursor::pos(); } return rawPoint; } ukui-menu/src/extension/menu/0000775000175000017500000000000015160463365015177 5ustar fengfengukui-menu/src/extension/menu/app-menu-plugin.cpp0000664000175000017500000002005615160463365020724 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "app-menu-plugin.h" #include "settings.h" #include "app-manager.h" #include "../context-menu-extension.h" #include "basic-app-filter-model.h" #include "user-config.h" #include #include #include #include #include #include #include namespace UkuiMenu { class AppContentMenu : public ContextMenuExtension { public: int index() const override; QList actions(const DataEntity &data, QMenu *parent, const MenuInfo::Location &location, const QString &locationId) override; private: static void addToTop(QObject *parent, const QString &appId, const int &appTop, QList &list); static void addToPanelAction(QObject *parent, const QString &appId, QList &list); static void addToDesktopAction(QObject *parent, const QString &appId, QList &list); static void addUninstall(QObject *parent, const QString &appId, QList &list); static void addRemoveFromList(QObject *parent, const QString &appId, const int &appLaunched, const QString &appInsertTime, QList &list); }; int AppContentMenu::index() const { return ContextMenuExtension::index(); } QList AppContentMenu::actions(const DataEntity &data, QMenu *parent, const MenuInfo::Location &location, const QString &locationId) { if (!parent || (data.type() != DataType::Normal)) { return {}; } QList list; QAction* actionSeparator = nullptr; QString appId = data.id(); int appTop = data.top(); int appLaunched = data.launched(); QString appInsertTime = data.insertTime(); switch (location) { case MenuInfo::AppList: { //置顶 if (locationId == "all") { addToTop(parent, appId, appTop, list); } } case MenuInfo::Extension: case MenuInfo::FolderPage: case MenuInfo::FullScreen: case MenuInfo::Folder: // 添加到任务栏 addToPanelAction(parent, appId, list); //添加到桌面快捷方式 addToDesktopAction(parent, appId, list); //添加分割线 actionSeparator = new QAction; actionSeparator->setSeparator(true); list << actionSeparator; //添加从当前列表移除 addRemoveFromList(parent, appId, appLaunched, appInsertTime, list); // 卸载 if (data.removable()) { addUninstall(parent, appId, list); } break; default: break; } if (!actionSeparator) { actionSeparator->setVisible(list.last() != actionSeparator); } return list; } void AppContentMenu::addToTop(QObject *parent, const QString &appId, const int &appTop, QList &list) { QString actionName = (appTop == 0) ? QObject::tr("Fixed to all applications") : QObject::tr("Unfixed from all applications"); list << new QAction(actionName, parent); QObject::connect(list.last(), &QAction::triggered, parent, [appId, appTop] { BasicAppFilterModel::instance()->databaseInterface()->fixAppToTop(appId, appTop); }); } void AppContentMenu::addToPanelAction(QObject *parent, const QString &appId, QList &list) { QDBusInterface iface("org.ukui.taskManager", "/taskManager", "org.ukui.taskManager", QDBusConnection::sessionBus()); if (!iface.isValid()) { qWarning() << "Ukui taskManager dbusinterface error"; return; } iface.setTimeout(1000); QDBusReply isFixedOnTaskBar = iface.call("checkQuickLauncher", appId); if (!isFixedOnTaskBar.isValid()) { qWarning() << "Ukui taskManager dbusinterface call checkQuickLauncher timeout"; return; } QString actionName = isFixedOnTaskBar ? QObject::tr("Remove from taskbar") : QObject::tr("Add to taskbar"); QString functionName = isFixedOnTaskBar ? "removeQuickLauncher" : "addQuickLauncher"; QIcon icon = isFixedOnTaskBar ? QIcon::fromTheme("ukui-unfixed-symbolic") : QIcon::fromTheme("view-pin-symbolic"); list << new QAction(icon, actionName, parent); QObject::connect(list.last(), &QAction::triggered, parent, [appId, functionName] { QDBusInterface iface("org.ukui.taskManager", "/taskManager", "org.ukui.taskManager", QDBusConnection::sessionBus()); if (!iface.isValid()) { qWarning() << "Ukui taskManager dbusinterface error"; return; } iface.setTimeout(1); QDBusReply ret = iface.call(functionName, appId); if (!ret.isValid() || !ret) { qWarning() << "Add/Remove from taskbar error"; } }); } void AppContentMenu::addToDesktopAction(QObject *parent, const QString &appId, QList &list) { list << new QAction(QIcon::fromTheme("emblem-link2-symbolic"), QObject::tr("Send to desktop shortcuts"), parent); QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); QString path = QString(desktopPath + "/" + QFileInfo(appId).fileName()); if (QFile::exists(path)) { list.last()->setEnabled(false); return; } QObject::connect(list.last(), &QAction::triggered, parent, [appId, path] { if (QFile::copy(appId, path)) { // 设置权限755 // QFile::Permissions permissions(0x0755); // 这也是可以的 QFile::Permissions permissions = { QFile::ReadUser | QFile::WriteUser | QFile::ExeUser | QFile::ReadGroup | QFile::ExeGroup | QFile::ReadOther | QFile::ExeOther }; if (!QFile::setPermissions(path, permissions)) { qWarning() << "set file permissions failed, file:" << path; } } }); } void AppContentMenu::addUninstall(QObject *parent, const QString &appId, QList &list) { list << new QAction(QIcon::fromTheme("user-trash-symbolic"), QObject::tr("Uninstall"), parent); QObject::connect(list.last(), &QAction::triggered, parent, [appId] { AppManager::instance()->launchBinaryApp("kylin-uninstaller", appId); }); } void AppContentMenu::addRemoveFromList(QObject *parent, const QString &appId, const int &appLaunched, const QString &appInsertTime, QList &list) { if (appLaunched == 1) return; if (UserConfig::instance()->isPreInstalledApps(appId)) return; QDateTime installDate = QDateTime::fromString(appInsertTime, "yyyy-MM-dd hh:mm:ss"); if (!installDate.isValid()) return; QDateTime currentDateTime = QDateTime::currentDateTime(); qint64 xt = currentDateTime.toSecsSinceEpoch() - installDate.toSecsSinceEpoch(); if ((xt >= 0) && (xt <= 30 * 24 * 3600)) { list << new QAction(QIcon::fromTheme("edit-delete-symbolic"), QObject::tr("Remove from List"), parent); QObject::connect(list.last(), &QAction::triggered, parent, [appId] { BasicAppFilterModel::instance()->databaseInterface()->updateApLaunchedState(appId, true); }); } } // ====== AppMenuPlugin ====== // QString AppMenuPlugin::id() { return QStringLiteral("app-menu"); } WidgetExtension *AppMenuPlugin::createWidgetExtension() { return nullptr; } ContextMenuExtension *AppMenuPlugin::createContextMenuExtension() { return new AppContentMenu; } } // UkuiMenu ukui-menu/src/extension/menu/app-menu-plugin.h0000664000175000017500000000213115160463353020360 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_MENU_PLUGIN_H #define UKUI_MENU_APP_MENU_PLUGIN_H #include "../menu-extension-plugin.h" namespace UkuiMenu { class AppMenuPlugin : public MenuExtensionPlugin { Q_OBJECT public: QString id() override; WidgetExtension *createWidgetExtension() override; ContextMenuExtension *createContextMenuExtension() override; }; } // UkuiMenu #endif //UKUI_MENU_APP_MENU_PLUGIN_H ukui-menu/src/extension/widget-extension.cpp0000664000175000017500000000207315160463353020233 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "widget-extension.h" namespace UkuiMenu { WidgetExtension::WidgetExtension(QObject *parent) : QObject(parent) { qRegisterMetaType(); } int WidgetExtension::index() const { return -1; } QVariantMap WidgetExtension::data() { return {}; } void WidgetExtension::receive(const QVariantMap &data) { Q_UNUSED(data) } } // UkuiMenu ukui-menu/src/extension/menu-extension-loader.cpp0000664000175000017500000001050615160463353021160 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "menu-extension-loader.h" #include "menu-extension-plugin.h" #include "menu/app-menu-plugin.h" #include "favorite/favorite-extension-plugin.h" #include namespace UkuiMenu { QHash MenuExtensionLoader::plugins = QHash(); MenuExtensionLoader *MenuExtensionLoader::instance() { static MenuExtensionLoader loader; return &loader; } MenuExtensionLoader::MenuExtensionLoader() { // setBlackList({}); load(); } void MenuExtensionLoader::load() { loadInternalExtension(); loadExtensionFromDisk(); expand(); } void MenuExtensionLoader::loadInternalExtension() { registerExtension(new FavoriteExtensionPlugin); registerExtension(new AppMenuPlugin); } void MenuExtensionLoader::loadExtensionFromDisk() { QDir usrDir(UKUI_MENU_EXTENSION_DIR); loadExtensionFromDir(usrDir); QDir optDir("/opt/system/lib/ukui-menu/extensions"); loadExtensionFromDir(optDir); } void MenuExtensionLoader::loadExtensionFromDir(QDir dir) { for(const QString& fileName : dir.entryList({"*.so"},QDir::Files)) { QPluginLoader pluginLoader(dir.absoluteFilePath(fileName)); QJsonObject metaData = pluginLoader.metaData().value("MetaData").toObject(); QString type = metaData.value("Type").toString(); QString version = metaData.value("Version").toString(); if(type != UKUI_MENU_EXTENSION_I_FACE_TYPE) { continue; } if(version != UKUI_MENU_EXTENSION_I_FACE_VERSION) { qWarning() << "UKUI_MENU_EXTENSION version check failed:" << fileName << "version:" << version << "iface version : " << UKUI_MENU_EXTENSION_I_FACE_VERSION; continue; } QObject *obj = pluginLoader.instance(); if (!obj) { continue; } MenuExtensionPlugin *plugin = qobject_cast(obj); if (!plugin) { continue; } registerExtension(plugin); } } void MenuExtensionLoader::expand() { for (const auto &plugin : MenuExtensionLoader::plugins) { // qDebug() << "Expand Extension:" << plugin->id(); WidgetExtension *widget = plugin->createWidgetExtension(); if (widget) { // register widget. m_widgets.append(widget); } ContextMenuExtension *contextMenu = plugin->createContextMenuExtension(); if (contextMenu) { // 注册菜单 m_menus.append(contextMenu); } } std::sort(m_widgets.begin(), m_widgets.end(), [] (const WidgetExtension *a, const WidgetExtension *b) { if (a->index() < 0) { return false; } else if (b->index() < 0) { return true; } return a->index() <= b->index(); }); std::sort(m_menus.begin(), m_menus.end(), [] (const ContextMenuExtension *a, const ContextMenuExtension *b) { if (a->index() < 0) { return false; } else if (b->index() < 0) { return true; } return a->index() <= b->index(); }); } void MenuExtensionLoader::setBlackList(const QStringList &blackList) { m_blackList = blackList; } QList MenuExtensionLoader::widgets() const { return m_widgets; } QList MenuExtensionLoader::menus() const { return m_menus; } void MenuExtensionLoader::registerExtension(MenuExtensionPlugin *plugin) { QString id = plugin->id(); if (m_blackList.contains(id) || MenuExtensionLoader::plugins.contains(id)) { delete plugin; return; } MenuExtensionLoader::plugins.insert(id, plugin); } } // UkuiMenu ukui-menu/src/ukui-menu-application.cpp0000664000175000017500000002100515160463365017141 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "ukui-menu-application.h" #include "menu-dbus-service.h" #include "settings.h" #include "commons.h" #include "menu-main-window.h" #include "power-button.h" #include "app-manager.h" #include "context-menu-manager.h" #include "event-track.h" #include "sidebar-button-utils.h" #include "widget-model.h" #include "app-page-backend.h" #include "app-group-model.h" #include "favorite/folder-model.h" #include "favorite/favorite-filter-model.h" #include "security-function-control.h" #include #include #include #include #include using namespace UkuiMenu; UkuiMenuApplication::UkuiMenuApplication(MenuMessageProcessor *processor) : QObject(nullptr) { registerQmlTypes(); startUkuiMenu(); initDbusService(); connect(processor, &MenuMessageProcessor::request, this, &UkuiMenuApplication::execCommand); } void UkuiMenuApplication::startUkuiMenu() { initQmlEngine(); loadMenuUI(); } void UkuiMenuApplication::registerQmlTypes() { const char *uri = "org.ukui.menu.core"; int versionMajor = 1, versionMinor = 0; SettingModule::defineModule(uri, versionMajor, versionMinor); WindowModule::defineModule(uri, versionMajor, versionMinor); PowerButton::defineModule(uri, versionMajor, versionMinor); SidebarButtonUtils::defineModule(uri, versionMajor, versionMinor); qmlRegisterType(uri, versionMajor, versionMinor, "WidgetModel"); qmlRegisterType(uri, versionMajor, versionMinor, "AppGroupModel"); //qmlRegisterType(uri, versionMajor, versionMinor, "AppPageBackend"); qmlRegisterSingletonInstance(uri, versionMajor, versionMinor, "AppPageBackend", AppPageBackend::instance()); qmlRegisterUncreatableType(uri, versionMajor, versionMinor, "PluginGroup", "Use enums only."); // commons qRegisterMetaType("DataType::Type"); qRegisterMetaType("DataEntity"); qRegisterMetaType("MenuInfo::Location"); qmlRegisterUncreatableType(uri, versionMajor, versionMinor, "Display", "Use enums only."); qmlRegisterUncreatableType(uri, versionMajor, versionMinor, "DataType", "Use enums only"); qmlRegisterUncreatableType(uri, versionMajor, versionMinor, "MenuInfo", "Use enums only."); qmlRegisterUncreatableType(uri, versionMajor, versionMinor, "LabelItem", "Use enums only."); qmlRegisterUncreatableType(uri, versionMajor, versionMinor, "WidgetMetadata", "Use enums only."); // qmlRegisterUncreatableType(uri, versionMajor, versionMinor, "DataEntity", "unknown"); qmlRegisterUncreatableType(uri, versionMajor, versionMinor, "EventTrack", "Attached only."); } void UkuiMenuApplication::initQmlEngine() { m_engine = new QQmlEngine(this); m_engine->addImportPath("qrc:/qml"); QQmlContext *context = m_engine->rootContext(); context->setContextProperty("menuSetting", MenuSetting::instance()); context->setContextProperty("menuManager", ContextMenuManager::instance()); context->setContextProperty("appManager", AppManager::instance()); context->setContextProperty("favoriteModel", FavoriteFilterModel::instance()); context->setContextProperty("FolderModel", &FolderModel::instance()); context->setContextProperty("functionControl", SecurityFunctionControl::instance()); // MenuMainWindow // const QUrl url(QStringLiteral("qrc:/qml/MenuMainWindow.qml")); // QQmlApplicationEngine *m_applicationEngine{nullptr}; // m_applicationEngine = new QQmlApplicationEngine(this); // QObject::connect(m_applicationEngine, &QQmlApplicationEngine::objectCreated, // this, [url](QObject *obj, const QUrl &objUrl) { // if (!obj && url == objUrl) // QCoreApplication::exit(-1); // }, Qt::QueuedConnection); // // m_applicationEngine->load(url); connect(AppManager::instance(), &AppManager::request, this, &UkuiMenuApplication::execCommand); } void UkuiMenuApplication::loadMenuUI() { const QUrl url(QStringLiteral("qrc:/qml/main.qml")); m_mainWindow = new MenuWindow(m_engine, nullptr); m_mainWindow->setSource(url); connect(m_mainWindow, &QQuickView::activeFocusItemChanged, m_mainWindow, [this] { if (m_mainWindow->activeFocusItem()) { return; } execCommand(Hide); }); } void UkuiMenuApplication::initDbusService() { m_menuDbusService = new MenuDbusService(QGuiApplication::instance()->property("display").toString(), this); if (m_menuDbusService) { connect(m_menuDbusService, &MenuDbusService::menuActive, this, [this] { execCommand(Active); }); // connect(m_menuDbusService, &MenuDbusService::reloadConfigSignal, this, [this] { //// reload ... // }); } } void UkuiMenuApplication::execCommand(Command command) { switch (command) { case Active: { if (m_mainWindow) { m_mainWindow->activeMenuWindow(!m_mainWindow->isVisible()); } break; } case Show: { if (m_mainWindow) { m_mainWindow->activeMenuWindow(true); } break; } case Quit: { if (m_mainWindow) { m_mainWindow->activeMenuWindow(false); m_engine->quit(); } QCoreApplication::quit(); break; } case Hide: { if (m_mainWindow) { m_mainWindow->activeMenuWindow(false); } break; } default: break; } // TODO: 发送窗口显示隐藏信号到插件 } UkuiMenuApplication::~UkuiMenuApplication() { if (m_mainWindow) { m_mainWindow->deleteLater(); } } // == MenuMessageProcessor == // QCommandLineOption MenuMessageProcessor::showUkuiMenu({"s", "show"}, QObject::tr("Show ukui-menu.")); QCommandLineOption MenuMessageProcessor::quitUkuiMenu({"q", "quit"}, QObject::tr("Quit ukui-menu.")); MenuMessageProcessor::MenuMessageProcessor() : QObject(nullptr) {} void MenuMessageProcessor::processMessage(const QString &message) { QStringList options = QString(message).split(' '); QCommandLineParser parser; parser.addOption(MenuMessageProcessor::showUkuiMenu); parser.addOption(MenuMessageProcessor::quitUkuiMenu); parser.parse(options); if (parser.isSet(MenuMessageProcessor::showUkuiMenu)) { Q_EMIT request(UkuiMenuApplication::Show); return; } if (parser.isSet(MenuMessageProcessor::quitUkuiMenu)) { Q_EMIT request(UkuiMenuApplication::Quit); return; } } /** * 已知问题,使用命令quit时,会出现 QFileSystemWatcher::removePaths: list is empty * 可见该bug: https://bugreports.qt.io/browse/QTBUG-68607 * @param message * @return */ bool MenuMessageProcessor::preprocessMessage(const QStringList& message) { qDebug() << "Hey, there."; QCommandLineParser parser; QCommandLineOption helpOption = parser.addHelpOption(); QCommandLineOption versionOption = parser.addVersionOption(); parser.addOption(MenuMessageProcessor::showUkuiMenu); parser.addOption(MenuMessageProcessor::quitUkuiMenu); parser.parse(message); if (message.size() <= 1 || parser.isSet(helpOption) || !parser.unknownOptionNames().isEmpty()) { if (!parser.unknownOptionNames().isEmpty()) { qDebug() << "Unknown options:" << parser.unknownOptionNames(); } parser.showHelp(); return false; } if (parser.isSet(versionOption)) { parser.showVersion(); return false; } return parser.isSet(MenuMessageProcessor::showUkuiMenu) || parser.isSet(MenuMessageProcessor::quitUkuiMenu); } ukui-menu/src/data-entity.cpp0000664000175000017500000002162415160463353015150 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "data-entity.h" using namespace UkuiMenu; class DataEntityPrivate { public: DataEntityPrivate() = default; DataEntityPrivate(DataType::Type type, QString name, QString icon, QString comment, QString extraData) : type(type), name(std::move(name)), icon(std::move(icon)), comment(std::move(comment)), extraData(std::move(extraData)) {} bool lock{false}; // 应用锁定 bool recentInstall{false}; int launched{0}; // 是否启动过 int top{0}; // 置顶状态及序号 int favorite{0}; // 收藏状态及序号 int launchTimes{0}; // 启动次数 double priority{0}; DataType::Type type {DataType::Normal}; QString id; // 应用可执行文件路径 QString icon; QString name; QString category; QString firstLetter; QString comment; // 应用描述 QString extraData; // 额外的数据 QString insertTime; //安装的时间 QString group; // 分类 bool removable; // 可否卸载 QString desktopName; // 应用desktop文件名称 }; DataEntity::DataEntity() : d(new DataEntityPrivate) {} DataEntity::DataEntity(DataType::Type type, const QString& name, const QString& icon, const QString& comment, const QString& extraData) : d(new DataEntityPrivate(type, name, icon, comment, extraData)) { } int DataEntity::top() const { return d->top; } void DataEntity::setTop(int top) { d->top = top; } bool DataEntity::isLock() const { return d->lock; } void DataEntity::setLock(bool lock) { d->lock = lock; } bool DataEntity::isRecentInstall() const { return d->recentInstall; } void DataEntity::setRecentInstall(bool recentInsatll) { d->recentInstall = recentInsatll; } int DataEntity::launched() const { return d->launched; } void DataEntity::setLaunched(int launched) { d->launched = launched; } int DataEntity::favorite() const { return d->favorite; } void DataEntity::setFavorite(int favorite) { d->favorite = favorite; } int DataEntity::launchTimes() const { return d->launchTimes; } void DataEntity::setLaunchTimes(int launchTimes) { d->launchTimes = launchTimes; } double DataEntity::priority() const { return d->priority; } void DataEntity::setPriority(double priority) { d->priority = priority; } QString DataEntity::insertTime() const { return d->insertTime; } void DataEntity::setInsertTime(const QString &insertTime) { d->insertTime = insertTime; } QString DataEntity::id() const { return d->id; } void DataEntity::setId(const QString &id) { d->id = id; setDesktopName(QFileInfo(id).completeBaseName()); } void DataEntity::setCategory(const QString &category) { d->category = category; } QString DataEntity::category() const { return d->category; } void DataEntity::setFirstLetter(const QString &firstLetter) { d->firstLetter = firstLetter; } QString DataEntity::firstLetter() const { return d->firstLetter; } void DataEntity::setType(DataType::Type type) { d->type = type; } DataType::Type DataEntity::type() const { return d->type; } void DataEntity::setIcon(const QString &icon) { d->icon = icon; } QString DataEntity::icon() const { return d->icon; } void DataEntity::setName(const QString &name) { d->name = name; } QString DataEntity::name() const { return d->name; } void DataEntity::setComment(const QString &comment) { d->comment = comment; } QString DataEntity::comment() const { return d->comment; } void DataEntity::setExtraData(const QString &extraData) { d->extraData = extraData; } QString DataEntity::extraData() const { return d->extraData; } QHash DataEntity::AppRoleNames() { QHash names; names.insert(DataEntity::Id, "id"); names.insert(DataEntity::Type, "type"); names.insert(DataEntity::Icon, "icon"); names.insert(DataEntity::Name, "name"); names.insert(DataEntity::Comment, "comment"); names.insert(DataEntity::ExtraData, "extraData"); names.insert(DataEntity::Category, "category"); names.insert(DataEntity::Group, "group"); names.insert(DataEntity::FirstLetter, "firstLetter"); names.insert(DataEntity::InstallationTime, "installationTime"); names.insert(DataEntity::IsLaunched, "isLaunched"); names.insert(DataEntity::LaunchTimes, "launchTimes"); names.insert(DataEntity::IsLocked, "isLocked"); names.insert(DataEntity::Favorite, "favorite"); names.insert(DataEntity::Top, "top"); names.insert(DataEntity::RecentInstall, "recentInstall"); names.insert(DataEntity::Entity, "entity"); names.insert(DataEntity::DesktopName, "desktopName"); return names; } DataEntity::DataEntity(const DataEntity &other) : d(new DataEntityPrivate) { *d = *(other.d); } DataEntity& DataEntity::operator=(const DataEntity &other) { if (this != &other) { *d = *(other.d); } return *this; } DataEntity::DataEntity(DataEntity &&other) noexcept : d(new DataEntityPrivate) { *d = *(other.d); } DataEntity &DataEntity::operator=(DataEntity &&other) noexcept { *d = *(other.d); return *this; } DataEntity::~DataEntity() { if (d) { delete d; d = nullptr; } } QVariant DataEntity::getValue(DataEntity::PropertyName property) const { switch (property) { case Id: return d->id; case Type: return d->type; case Icon: return d->icon; case Name: return d->name; case Comment: return d->comment; case ExtraData: return d->extraData; case Category: return d->category; case Group: return d->group; case FirstLetter: return d->firstLetter; case InstallationTime: return d->insertTime; case IsLaunched: return d->launched; case LaunchTimes: return d->launchTimes; case IsLocked: return d->lock; case Favorite: return d->favorite; case Top: return d->top; case RecentInstall: return d->recentInstall; case Entity: return QVariant::fromValue(*this); case DesktopName: return d->desktopName; default: break; } return {}; } void DataEntity::setValue(DataEntity::PropertyName property, const QVariant &value) { switch (property) { case Id: d->id = value.toString(); break; case Type: d->type = value.value(); break; case Icon: d->icon = value.toString(); break; case Name: d->name = value.toString(); break; case Comment: d->comment = value.toString(); break; case ExtraData: d->extraData = value.toString(); break; case Category: d->category = value.toString(); break; case Group: d->group = value.toString(); break; case FirstLetter: d->firstLetter = value.toString(); break; case InstallationTime: d->insertTime = value.toString(); break; case IsLaunched: d->launched = value.toBool(); break; case LaunchTimes: d->launchTimes = value.toInt(); break; case IsLocked: d->lock = value.toBool(); break; case Favorite: d->favorite = value.toInt(); break; case Top: d->top = value.toInt(); break; case RecentInstall: d->recentInstall = value.toBool(); break; default: break; } } void DataEntity::setGroup(const QString &group) { d->group = group; } QString DataEntity::group() const { return d->group; } void DataEntity::setRemovable(bool removable) { d->removable = removable; } bool DataEntity::removable() const { return d->removable; } void DataEntity::setDesktopName(const QString &desktopName) { d->desktopName = desktopName; } QString DataEntity::desktopName() const { return d->desktopName; } ukui-menu/src/menu-dbus-service.h0000664000175000017500000000334115160463365015726 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef MENU_DBUS_SERVICE_H #define MENU_DBUS_SERVICE_H #include #include class MenuAdaptor; class QDBusServiceWatcher; namespace UkuiMenu { class MenuDbusService : public QObject, public QDBusContext { Q_OBJECT public: explicit MenuDbusService(const QString& display, QObject *parent = nullptr); public Q_SLOTS: void WinKeyResponse(); QString GetSecurityConfigPath(); void ReloadSecurityConfig(); void active(const QString &display); Q_SIGNALS: void menuActive(); void reloadConfigSignal(); private Q_SLOTS: void activeMenu(QString display); void onServiceOwnerChanged(const QString &service, const QString &oldOwner, const QString &newOwner); private: static QString displayFromPid(uint pid); bool registerService(); void initWatcher(); void connectToActiveRequest(); void disConnectActiveRequest(); private: QString m_display {}; MenuAdaptor *m_menuAdaptor {nullptr}; QDBusServiceWatcher *m_watcher {nullptr}; }; } #endif // MENU_DBUS_SERVICE_H ukui-menu/src/main.cpp0000664000175000017500000001536415160463365013660 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include #include #include #include #include #include #include #include #include #include "qtsingleapplication.h" #include "ukui-menu-application.h" #define LOG_FILE_COUNT 2 #define MAX_LOG_FILE_SIZE 4194304 #define MAX_LOG_CHECK_INTERVAL 43200000 static int logFileId = -1; static QString logFile = "ukui-menu.log"; static QString logFilePath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.log/ukui-menu/"; static QString logFileName = logFilePath + "ukui-menu-%1.log"; static quint64 startupTime; bool checkFileSize(const QString &fileName) { return QFile(fileName).size() < MAX_LOG_FILE_SIZE; } void clearFile(const QString &fileName) { QFile file(fileName); file.open(QIODevice::WriteOnly); file.write(""); file.flush(); file.close(); } void initLogFile() { QDir dir; if (!dir.exists(logFilePath)) { if (!dir.mkpath(logFilePath)) { qWarning() << "Unable to create" << logFilePath; return; } } for (int i = 0; i < LOG_FILE_COUNT; ++i) { logFile = logFileName.arg(i); if (QFile::exists(logFile)) { if (checkFileSize(logFile)) { logFileId = i; break; } } else { QFile file(logFile); file.open(QIODevice::WriteOnly); file.close(); } } if (logFileId < 0) { logFileId = 0; logFile = logFileName.arg(logFileId); clearFile(logFile); } qInfo() << "init log file:" << logFile; } void checkLogFile() { quint64 logTime = QDateTime::currentDateTime().toMSecsSinceEpoch(); quint64 spacing = std::max(logTime, startupTime) - std::min(logTime, startupTime); if (spacing <= MAX_LOG_CHECK_INTERVAL || checkFileSize(logFile)) { return; } logFileId = ((logFileId + 1) % LOG_FILE_COUNT); logFile = logFileName.arg(logFileId); if (!checkFileSize(logFile)) { clearFile(logFile); } } void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { checkLogFile(); QByteArray localMsg = msg.toLocal8Bit(); QByteArray currentTime = QTime::currentTime().toString().toLocal8Bit(); const char *file = context.file ? context.file : ""; const char *function = context.function ? context.function : ""; FILE *log_file = fopen(logFile.toLocal8Bit().constData(), "a+"); switch (type) { case QtDebugMsg: if (!log_file) { break; } fprintf(log_file, "Debug: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function); break; case QtInfoMsg: fprintf(log_file? log_file: stdout, "Info: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function); break; case QtWarningMsg: fprintf(log_file? log_file: stderr, "Warning: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function); break; case QtCriticalMsg: fprintf(log_file? log_file: stderr, "Critical: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function); break; case QtFatalMsg: fprintf(log_file? log_file: stderr, "Fatal: %s: %s (%s:%u, %s)\n", currentTime.constData(), localMsg.constData(), file, context.line, function); break; } if (log_file) { fclose(log_file); } } int main(int argc, char *argv[]) { startupTime = QDateTime::currentDateTime().toMSecsSinceEpoch(); #ifndef UKUI_MENU_LOG_FILE_DISABLE initLogFile(); qInstallMessageHandler(messageOutput); #endif #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); #else #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); #endif QCoreApplication::setApplicationName("ukui-menu"); QCoreApplication::setOrganizationName("ukui"); QCoreApplication::setOrganizationDomain("ukui.org"); QCoreApplication::setApplicationVersion("0.0.1-alpha"); QString display; if (UkuiQuick::WindowProxy::isWayland()) { display = QLatin1String(getenv("WAYLAND_DISPLAY")); } else { display = QLatin1String(getenv("DISPLAY")); } QString appid = QString("ukui-menu-%1").arg(display); qDebug() << "ukui-menu launch with:" << display << "appid:" << appid; QtSingleApplication app(appid, argc, argv); QGuiApplication::instance()->setProperty("display", display); QTranslator translator; QString translationFile{(QString(UKUI_MENU_TRANSLATION_DIR) + "/ukui-menu_" + QLocale::system().name() + ".qm")}; // translation files if (QFile::exists(translationFile) && translator.load(translationFile)) { QCoreApplication::installTranslator(&translator); } else { qWarning() << "Unable to load translation file:" << translationFile; } UkuiMenu::MenuMessageProcessor messageProcessor; if (app.isRunning()) { if (UkuiMenu::MenuMessageProcessor::preprocessMessage(QtSingleApplication::arguments())) { app.sendMessage(QtSingleApplication::arguments().join(" ").toUtf8()); } return 0; } UkuiMenu::UkuiMenuApplication menuApplication(&messageProcessor); messageProcessor.processMessage(QtSingleApplication::arguments().join(" ").toUtf8()); QObject::connect(&app, &QtSingleApplication::messageReceived, &messageProcessor, &UkuiMenu::MenuMessageProcessor::processMessage); qInfo() << "ukui-menu startup time:" << (QDateTime::currentDateTime().toMSecsSinceEpoch() - startupTime) << ",date:" << QDateTime::currentDateTime().toString(); return QtSingleApplication::exec(); } ukui-menu/src/utils/0000775000175000017500000000000015160463365013357 5ustar fengfengukui-menu/src/utils/user-info-item.cpp0000664000175000017500000000772515160463353016736 0ustar fengfeng#include "user-info-item.h" #include #include #include #include #define KYLIN_ACCOUNT_INFORMATION_NAME "org.freedesktop.Accounts" #define KYLIN_ACCOUNT_INFORMATION_PATH "/org/freedesktop/Accounts" #define KYLIN_ACCOUNT_INFORMATION_INTERFACE "org.freedesktop.Accounts" #define DEFAULT_USER_ICON_FILE ":/res/icon/default-community-image.png" using namespace UkuiMenu; UkuiMenu::UserInfoItem::UserInfoItem(QString objectPath, QObject *parent) : QObject(parent) { m_objPath = objectPath; initUserInfo(); createCircularIcon(); QDBusConnection::systemBus().connect(KYLIN_ACCOUNT_INFORMATION_NAME, objectPath, "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(userInfoUpdateSlot(QString, QMap, QStringList))); } void UserInfoItem::initUserInfo() { m_isCurrentUser = false; m_isLogged = false; m_autoLogin = false; QDBusInterface userInterface(KYLIN_ACCOUNT_INFORMATION_NAME, m_objPath, "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); QDBusReply > reply = userInterface.call("GetAll", "org.freedesktop.Accounts.User"); if (reply.isValid()) { QMap propertyMap; propertyMap = reply.value(); m_userName = propertyMap.find("UserName").value().toString(); m_realName = propertyMap.find("RealName").value().toString(); m_accountType = propertyMap.find("AccountType").value().toInt(); m_iconFile = propertyMap.find("IconFile").value().toString(); m_passwdType = propertyMap.find("PasswordMode").value().toInt(); m_uid = propertyMap.find("Uid").value().toInt(); m_autoLogin = propertyMap.find("AutomaticLogin").value().toBool(); if (m_userName == QString(qgetenv("USER"))) { m_isCurrentUser = true; m_isLogged = true; } } else { qWarning() << "UserInfoItem::initUserInfo: 'org.freedesktop.Accounts.User' dbus reply is invalid"; } } void UserInfoItem::createCircularIcon() { QPixmap sourcePixmap(m_iconFile); if (sourcePixmap.isNull()) { qWarning() << "UserInfoItem::createCircularIcon: Can't load icon file: " << m_iconFile; return; } int diameter = std::min(sourcePixmap.height(), sourcePixmap.width()); QPixmap circularPixmap(diameter, diameter); circularPixmap.fill(Qt::transparent); QPainter painter(&circularPixmap); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); QPainterPath path; path.addEllipse(0, 0, diameter, diameter); painter.setClipPath(path); QRect targetRect, sourceRect; targetRect = QRect(0, 0, diameter, diameter); if (sourcePixmap.width() > sourcePixmap.height()) { sourceRect = QRect((sourcePixmap.width() - diameter) / 2, 0, diameter, diameter); } else { sourceRect = QRect(0, (sourcePixmap.height() - diameter) / 2, diameter, diameter); } painter.drawPixmap(targetRect, sourcePixmap, sourceRect); m_circularIcon = QIcon(circularPixmap); } void UserInfoItem::userInfoUpdateSlot(const QString &property, const QMap &propertyMap, const QStringList &propertyList) { Q_UNUSED(property) Q_UNUSED(propertyList) QStringList keys = propertyMap.keys(); if (keys.contains("IconFile")) { m_iconFile = propertyMap.value("IconFile").toString(); createCircularIcon(); } if (keys.contains("UserName")) { m_userName = propertyMap.value("UserName").toString(); } if (keys.contains("RealName")) { m_realName = propertyMap.value("RealName").toString(); } if (keys.contains("AccountType")) { m_accountType = propertyMap.value("AccountType").toInt(); } Q_EMIT userInfoChanged(); } ukui-menu/src/utils/app-manager.cpp0000664000175000017500000001145015160463353016251 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "app-manager.h" #include "basic-app-filter-model.h" #include #include #include #include #include #include #define KYLIN_APP_MANAGER_NAME "com.kylin.ProcessManager" #define KYLIN_APP_MANAGER_PATH "/com/kylin/ProcessManager/AppLauncher" #define KYLIN_APP_MANAGER_INTERFACE "com.kylin.ProcessManager.AppLauncher" namespace UkuiMenu { AppManager *AppManager::instance() { static AppManager instance(nullptr); return &instance; } AppManager::AppManager(QObject *parent) : QObject(parent) { } void AppManager::launchApp(const QString &appid) { if (appid.isEmpty()) { return; } Q_EMIT request(UkuiMenuApplication::Hide); QDBusMessage message = QDBusMessage::createMethodCall(KYLIN_APP_MANAGER_NAME, KYLIN_APP_MANAGER_PATH, KYLIN_APP_MANAGER_INTERFACE, "LaunchApp"); message << appid; auto watcher = new QDBusPendingCallWatcher(QDBusPendingCall(QDBusConnection::sessionBus().asyncCall(message)), this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [appid] (QDBusPendingCallWatcher *self) { if (self->isError()) { QStringList list = parseDesktopFile(appid); if (!list.isEmpty()) { QString cmd = list.takeFirst(); QProcess::startDetached(cmd, list); qDebug() << "launch with QProcess:" << cmd << list; } } self->deleteLater(); }); BasicAppFilterModel::instance()->databaseInterface()->updateApLaunchedState(appid, true); } void AppManager::launchBinaryApp(const QString &app, const QString &args) { if (app.isEmpty()) { return; } QDBusMessage message = QDBusMessage::createMethodCall(KYLIN_APP_MANAGER_NAME, KYLIN_APP_MANAGER_PATH, KYLIN_APP_MANAGER_INTERFACE, "LaunchApp"); message << app << args; auto watcher = new QDBusPendingCallWatcher(QDBusPendingCall(QDBusConnection::sessionBus().asyncCall(message)), this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [app, args] (QDBusPendingCallWatcher *self) { if (self->isError()) { QProcess::startDetached(app, {args}, ""); } self->deleteLater(); }); } void AppManager::launchAppWithArguments(const QString &appid, const QStringList &args, const QString &applicationName) { if (appid.isEmpty()) { return; } Q_EMIT request(UkuiMenuApplication::Hide); QDBusMessage message = QDBusMessage::createMethodCall(KYLIN_APP_MANAGER_NAME, KYLIN_APP_MANAGER_PATH, KYLIN_APP_MANAGER_INTERFACE, "LaunchAppWithArguments"); message << appid << args; auto watcher = new QDBusPendingCallWatcher(QDBusConnection::sessionBus().asyncCall(message), this); connect(watcher, &QDBusPendingCallWatcher::finished, this, [applicationName, args] (QDBusPendingCallWatcher *self) { if (self) { QDBusPendingReply reply = *self; if (self->isError()) { qWarning() << reply.error().message(); QProcess::startDetached(applicationName, args); } self->deleteLater(); } }); } void AppManager::changeFavoriteState(const QString &appid, bool isFavorite) { BasicAppFilterModel::instance()->databaseInterface()->fixAppToFavorite(appid, isFavorite ? 1 : 0); } QStringList AppManager::parseDesktopFile(const QString &appid) { // TODO: try QSettings? QSettings file(appid, QSettings::IniFormat); file.beginGroup(QStringLiteral("Desktop Entry")); QStringList stringList = file.value(QStringLiteral("Exec")).toString().split(" "); file.endGroup(); if (stringList.isEmpty()) { return {}; } QStringList res; res << stringList.takeFirst(); // TODO: freedesktop group for (const auto &str : stringList) { if (!str.startsWith("%")) { res.append(str); } } return res; } } // UkuiMenu ukui-menu/src/utils/event-track.h0000664000175000017500000000274215160463353015755 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_EVENT_TRACK_H #define UKUI_MENU_EVENT_TRACK_H #include #include #include namespace UkuiMenu { class EventTrack : public QObject { Q_OBJECT public: static EventTrack *qmlAttachedProperties(QObject *object); static EventTrack *instance(); explicit EventTrack(QObject *parent = nullptr); Q_INVOKABLE void sendClickEvent(const QString& event, const QString& page, const QVariantMap &map = {}); Q_INVOKABLE void sendDefaultEvent(const QString& event, const QString& page, const QVariantMap &map = {}); Q_INVOKABLE void sendSearchEvent(const QString& event, const QString& page, const QString& content); }; } // Sidebar QML_DECLARE_TYPEINFO(UkuiMenu::EventTrack, QML_HAS_ATTACHED_PROPERTIES) #endif //UKUI_MENU_EVENT_TRACK_H ukui-menu/src/utils/user-info-item.h0000664000175000017500000000310415160463365016371 0ustar fengfeng#ifndef USERINFOITEM_H #define USERINFOITEM_H #include #include #include #include namespace UkuiMenu { class UserInfoItemPrivate; class UserInfoItem : public QObject { Q_OBJECT public: explicit UserInfoItem(QString objectPath, QObject *parent = nullptr); ~UserInfoItem() = default; void initUserInfo(); void createCircularIcon(); bool isCurrentUser() {return m_isCurrentUser;}; bool isLogged() {return m_isLogged;}; bool autoLogin() {return m_autoLogin;}; bool noPwdLogin() {return m_noPwdLogin;}; int accountType() {return m_accountType;}; int passwdType() {return m_passwdType;}; qint64 uid() {return m_uid;}; QString objPath() {return m_objPath;}; QString userName() {return m_userName;}; QString realName() {return m_realName;}; QString iconFile() {return m_iconFile;}; QString passwd() {return m_passwd;}; QIcon circularIcon() {return m_circularIcon;}; private Q_SLOTS: void userInfoUpdateSlot(const QString &property, const QMap &propertyMap, const QStringList &propertyList); Q_SIGNALS: void userInfoChanged(); private: QString m_objectPath; UserInfoItemPrivate *d = nullptr; bool m_isCurrentUser; bool m_isLogged; bool m_autoLogin; bool m_noPwdLogin; int m_accountType; int m_passwdType; qint64 m_uid; QString m_objPath; QString m_userName; QString m_realName; QString m_iconFile; QString m_passwd; QIcon m_circularIcon; }; } #endif // USERINFOITEM_H ukui-menu/src/utils/security-function-control.cpp0000664000175000017500000001504015160463365021233 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: youdiansaodongxi * */ #include "security-function-control.h" #include "function-control.h" namespace UkuiMenu { UkuiMenu::SecurityFunctionControl *UkuiMenu::SecurityFunctionControl::instance() { static SecurityFunctionControl instance(nullptr); return &instance; } bool UkuiMenu::SecurityFunctionControl::disable() const { return m_disable; } bool UkuiMenu::SecurityFunctionControl::disableMouseLeftButton() const { return m_disableMouseLeftButton; } bool UkuiMenu::SecurityFunctionControl::disableMouseRightButton() const { return m_disableMouseRightButton; } bool UkuiMenu::SecurityFunctionControl::disableMouseHover() const { return m_disableMouseHover; } QStringList UkuiMenu::SecurityFunctionControl::appWhiteList() const { return m_appWhiteList; } QStringList UkuiMenu::SecurityFunctionControl::appBlackList() const { return m_appBlackList; } bool SecurityFunctionControl::canAppDisplay(const QString &desktop) const { if (!m_appWhiteList.isEmpty()) { if (!std::any_of(m_appWhiteList.constBegin(), m_appWhiteList.constEnd(), [&](const QString& app) { return desktop.endsWith(app); })) { return false; } } if (!m_appBlackList.isEmpty()) { if (std::any_of(m_appBlackList.constBegin(), m_appBlackList.constEnd(), [&](const QString& app) { return desktop.endsWith(app); })) { return false; } } return true; } UkuiMenu::SecurityFunctionControl::SecurityFunctionControl(QObject *parent) : QObject(parent) { UkuiQuick::FunctionControl::instance()->addWatch(QStringLiteral("ukui-menu-v11")); onFunctionControlChanged("ukui-menu-v11", false); connect(UkuiQuick::FunctionControl::instance(), &UkuiQuick::FunctionControl::functionControlChanged, this, [&](const QString &name) { onFunctionControlChanged(name); }); connect(UkuiQuick::FunctionControl::instance(), &UkuiQuick::FunctionControl::functionControlExit, this, [&]() { onFunctionControlChanged(QStringLiteral("ukui-menu-v11"), true); }); } bool UkuiMenu::SecurityFunctionControl::compareListBySort(QStringList &list1, QStringList &list2) { if (list1.size() != list2.size()) return false; list1.sort(); list2.sort(); return list1 == list2; } void UkuiMenu::SecurityFunctionControl::onFunctionControlChanged(const QString &name, bool exit) { if (name != QStringLiteral("ukui-menu-v11")) { return; } //管控中有值时,设置为管控值,否则设置为配置文件中的值 auto config = exit ? QJsonObject(): UkuiQuick::FunctionControl::instance()->functionControlObject(name).value(name).toObject(); qDebug() << "ukui-menu function control config:" << config; //禁用开始菜单 if (config.contains(QStringLiteral("disable"))) { bool disable = config.value(QStringLiteral("disable")).toBool(); if (m_disable != disable) { m_disable = disable; Q_EMIT disableChanged(); } } else { if (m_disable != false) { m_disable = false; Q_EMIT disableChanged(); } } //禁用左键点击 if (config.contains(QStringLiteral("disableMouseLeftButton"))) { bool disableMouseLeftButton = config.value(QStringLiteral("disableMouseLeftButton")).toBool(); if (m_disableMouseLeftButton != disableMouseLeftButton) { m_disableMouseLeftButton = disableMouseLeftButton; Q_EMIT disableMouseLeftButtonChanged(); } } else { if (m_disableMouseLeftButton != false) { m_disableMouseLeftButton = false; Q_EMIT disableMouseLeftButtonChanged(); } } //禁用右键点击 if (config.contains(QStringLiteral("disableMouseRightButton"))) { bool disableMouseRightButton = config.value(QStringLiteral("disableMouseRightButton")).toBool(); if (m_disableMouseRightButton != disableMouseRightButton) { m_disableMouseRightButton = disableMouseRightButton; Q_EMIT disableMouseRightButtonChanged(); } } else { if (m_disableMouseRightButton != false) { m_disableMouseRightButton = false; Q_EMIT disableMouseRightButtonChanged(); } } //禁用鼠标悬浮 if (config.contains(QStringLiteral("disableMouseHover"))) { bool disableMouseHover = config.value(QStringLiteral("disableMouseHover")).toBool(); if (m_disableMouseHover != disableMouseHover) { m_disableMouseHover = disableMouseHover; Q_EMIT disableMouseHoverChanged(); } } else { if (m_disableMouseHover != false) { m_disableMouseHover = false; Q_EMIT disableMouseHoverChanged(); } } //应用白名单 if (config.contains(QStringLiteral("appWhiteList"))) { QStringList appWhiteList; for (QVariant &app : config.value(QStringLiteral("appWhiteList")).toArray().toVariantList()) { appWhiteList.append(app.toString()); } if (!compareListBySort(m_appWhiteList, appWhiteList)) { m_appWhiteList = appWhiteList; Q_EMIT appWhiteListChanged(); } } else if (!m_appWhiteList.isEmpty()) { m_appWhiteList.clear(); Q_EMIT appWhiteListChanged(); } //应用黑名单 if (config.contains(QStringLiteral("appBlackList"))) { QStringList appBlackList; for (QVariant &app : config.value(QStringLiteral("appBlackList")).toArray().toVariantList()) { appBlackList.append(app.toString()); } if (!compareListBySort(m_appBlackList, appBlackList)) { m_appBlackList= appBlackList; Q_EMIT appBlackListChanged(); } } else if (!m_appBlackList.isEmpty()) { m_appBlackList.clear(); Q_EMIT appBlackListChanged(); } } } // UkuiMenu ukui-menu/src/utils/app-manager.h0000664000175000017500000000321315160463353015714 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_MANAGER_H #define UKUI_MENU_APP_MANAGER_H #include #include "ukui-menu-application.h" class QDBusInterface; namespace UkuiMenu { class AppManager : public QObject { Q_OBJECT public: static AppManager *instance(); AppManager(const AppManager &obj) = delete; AppManager &operator = (const AppManager &obj) = delete; Q_INVOKABLE void launchApp(const QString &appid); Q_INVOKABLE void launchBinaryApp(const QString &app, const QString &args = QString()); Q_INVOKABLE void launchAppWithArguments(const QString &appid, const QStringList &args, const QString &applicationName); Q_INVOKABLE void changeFavoriteState(const QString &appid, bool isFavorite); private: explicit AppManager(QObject *parent = nullptr); static QStringList parseDesktopFile(const QString &desktopFilePath); Q_SIGNALS: void request(UkuiMenuApplication::Command command); }; } // UkuiMenu #endif //UKUI_MENU_APP_MANAGER_H ukui-menu/src/utils/power-button.cpp0000664000175000017500000002207715160463365016540 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include #include #include #include #include "power-button.h" #include "app-manager.h" #define KYLIN_POWER_NORMAL_PATH_MAJOR "qrc:///res/icon/pad_mainpower.svg" using namespace UkuiMenu; PowerButton::PowerButton(QObject *parent) : QObject(nullptr) { } void PowerButton::defineModule(const char *uri, int versionMajor, int versionMinor) { qmlRegisterType("org.ukui.menu.utils", versionMajor, versionMinor, "PowerButton"); } PowerButton::~PowerButton() = default; QString PowerButton::getIcon() { return "exit-symbolic"; } QString PowerButton::getToolTip() { return {tr("Power")}; } void PowerButton::clicked(bool leftButtonClicked, int mouseX, int mouseY, bool isFullScreen) { if (leftButtonClicked) { QProcess::startDetached("ukui-session-tools", {}); } else { openMenu(mouseX, mouseY, isFullScreen); } } void PowerButton::openMenu(int menuX, int menuY, bool isFullScreen) { if (m_contextMenu) { m_contextMenu.data()->close(); return; } auto powerMenu = new QMenu; QDBusReply reply; QDBusInterface qDBusInterface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus()); reply = qDBusInterface.call("canLogout"); if (reply.isValid() && reply.value()) { QAction *action = new QAction(QIcon::fromTheme("system-log-out-symbolic"), tr("Log Out"), powerMenu); action->setToolTip(tr("

The current user logs out of the system, ending " "their session and returning to the login screen

")); connect(action, &QAction::triggered, powerMenu, [] { AppManager::instance()->launchBinaryApp("ukui-session-tools", "--logout"); }); powerMenu->addAction(action); } QAction *lockAction = new QAction(QIcon::fromTheme("system-lock-screen-symbolic"), tr("Lock Screen"), powerMenu); connect(lockAction, &QAction::triggered, powerMenu, [] { AppManager::instance()->launchBinaryApp("ukui-screensaver-command", "-l"); }); powerMenu->addAction(lockAction); reply = qDBusInterface.call("canSuspend"); if (reply.isValid() && reply.value()) { QAction *action = new QAction(QIcon::fromTheme("ukui-sleep-symbolic"), tr("Suspend"), powerMenu); action->setToolTip(tr("

The computer remains on but consumes less power, " "and the applications will remain open. You can quickly wake " "up the computer and return to the state you left

")); connect(action, &QAction::triggered, powerMenu, [] { AppManager::instance()->launchBinaryApp("ukui-session-tools", "--suspend"); }); powerMenu->addAction(action); } reply = qDBusInterface.call("canHibernate"); if (reply.isValid() && reply.value()) { QAction *action = new QAction(QIcon::fromTheme("ukui-hibernate-symbolic"), tr("Hibernate"), powerMenu); action->setToolTip(tr("

Turn off the computer, but the applications " "will remain open. When you turn on the computer again, " "you can return to the state you were in before

")); connect(action, &QAction::triggered, powerMenu, [] { AppManager::instance()->launchBinaryApp("ukui-session-tools", "--hibernate"); }); powerMenu->addAction(action); } reply = qDBusInterface.call("canPowerOff"); if (reply.isValid() && reply.value()) { if (canUpgrade()) { QAction *upgradeshutdown = new QAction(QIcon::fromTheme("system-upd-reboot-symbolic"), tr("Upgrade and Shut Down"), powerMenu); upgradeshutdown->setToolTip(tr("Close all applications, upgrade the computer") + minutesToHour(m_estimatedLeftMinutes).toUtf8() + tr(",and then turn off it")); connect(upgradeshutdown, &QAction::triggered, this, [this] { execUpgradeAction("shutdown"); }); powerMenu->addAction(upgradeshutdown); } QAction *action = new QAction(QIcon::fromTheme("system-shutdown-symbolic"), tr("Shut Down"), powerMenu); action->setToolTip(tr("

Close all applications, and then turn off the computer

")); connect(action, &QAction::triggered, powerMenu, [] { AppManager::instance()->launchBinaryApp("ukui-session-tools", "--shutdown"); }); powerMenu->addAction(action); } reply = qDBusInterface.call("canReboot"); if (reply.isValid() && reply.value()) { if (canUpgrade()) { QAction *upgradeReboot = new QAction(QIcon::fromTheme("system-upd-reboot-symbolic"), tr("Upgrade and Restart"), powerMenu); upgradeReboot->setToolTip(tr("Close all applications, upgrade the computer") + minutesToHour(m_estimatedLeftMinutes).toUtf8() + tr(",and then restart the computer")); connect(upgradeReboot, &QAction::triggered, this, [this] { execUpgradeAction("reboot"); }); powerMenu->addAction(upgradeReboot); } QAction *action = new QAction(QIcon::fromTheme("system-reboot-symbolic"), tr("Restart"), powerMenu); action->setToolTip(tr("

Close all applications, turn off the computer, and then turn it back on

")); connect(action, &QAction::triggered, powerMenu, [] { AppManager::instance()->launchBinaryApp("ukui-session-tools", "--reboot"); }); powerMenu->addAction(action); } m_contextMenu = powerMenu; powerMenu->setToolTipsVisible(true); powerMenu->setAttribute(Qt::WA_DeleteOnClose); if (isFullScreen) { m_contextMenu.data()->popup(QPoint(menuX - powerMenu->sizeHint().width(), menuY - powerMenu->sizeHint().height())); } else { m_contextMenu.data()->popup(QPoint(menuX, menuY - powerMenu->sizeHint().height())); } } bool PowerButton::hasMultipleUsers() { QDBusInterface interface("org.freedesktop.Accounts", "/org/freedesktop/Accounts", "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); if (!interface.isValid()) { return false; } QDBusReply reply = interface.call("Get", "org.freedesktop.Accounts", "HasMultipleUsers"); return reply.value().toBool(); } bool PowerButton::canSwitch() { QDBusInterface interface("org.freedesktop.DisplayManager", "/org/freedesktop/DisplayManager/Seat0", "org.freedesktop.DBus.Properties", QDBusConnection::systemBus()); if (!interface.isValid()) { return false; } QDBusReply reply = interface.call("Get", "org.freedesktop.DisplayManager.Seat", "CanSwitch"); return reply.value().toBool(); } bool PowerButton::canUpgrade() { QDBusMessage msg = QDBusMessage::createMethodCall("com.kylin.systemupgrade", "/com/kylin/systemupgrade", "com.kylin.systemupgrade.interface", "CheckInstallRequired"); QDBusMessage res = QDBusConnection::systemBus().call(msg); if (res.type() == QDBusMessage::ReplyMessage && res.arguments().count() > 1) { m_estimatedLeftMinutes = res.arguments().at(1).toInt(); return res.arguments().at(0).toBool(); } if (res.type() == QDBusMessage::InvalidMessage || res.type() == QDBusMessage::ErrorMessage) { qCritical() << "Can't creat method call 'CheckInstallRequired'. Error: " << res.errorMessage(); return false; } return false; } void PowerButton::execUpgradeAction(const QString &action) { QDBusInterface interface("com.kylin.systemupgrade", "/com/kylin/systemupgrade", "com.kylin.systemupgrade.interface", QDBusConnection::systemBus()); if (interface.isValid()) { interface.asyncCall("TriggerInstallOnShutdown", action); } else { qCritical() << "'com.kylin.systemupgrade' interface is invalid!" << QDBusConnection::systemBus().lastError().message(); } } QString PowerButton::minutesToHour(int estmateTime) { if (estmateTime > 60) { int hour = estmateTime / 60; int min = estmateTime % 60; return tr("(Estimated %1 hour %2 minutes)").arg(hour).arg(min); } else if (estmateTime > 0) { return tr("(Estimated %1 minutes)").arg(estmateTime); } return {}; } ukui-menu/src/utils/security-function-control.h0000664000175000017500000000447515160463365020712 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: youdiansaodongxi * */ #ifndef SECURITYFUNCTIONCONTROL_H #define SECURITYFUNCTIONCONTROL_H #include #include #include namespace UkuiMenu { class SecurityFunctionControl : public QObject { Q_OBJECT public: static SecurityFunctionControl *instance(); Q_PROPERTY(bool disableMouseLeftButton READ disableMouseLeftButton NOTIFY disableMouseLeftButtonChanged) Q_PROPERTY(bool disableMouseRightButton READ disableMouseRightButton NOTIFY disableMouseRightButtonChanged) Q_PROPERTY(bool disableMouseHover READ disableMouseHover NOTIFY disableMouseHoverChanged) bool disable() const; bool disableMouseLeftButton() const; bool disableMouseRightButton() const; bool disableMouseHover() const; QStringList appWhiteList() const; QStringList appBlackList() const; bool canAppDisplay(const QString &desktop) const; Q_SIGNALS: void disableChanged(); void disableMouseLeftButtonChanged(); void disableMouseRightButtonChanged(); void disableMouseHoverChanged(); void appWhiteListChanged(); void appBlackListChanged(); private: explicit SecurityFunctionControl(QObject *parent = nullptr); bool compareListBySort(QStringList &list1, QStringList &list2); bool m_disable {false}; bool m_disableMouseLeftButton {false}; bool m_disableMouseRightButton {false}; bool m_disableMouseHover {false}; QStringList m_appWhiteList {}; QStringList m_appBlackList {}; private Q_SLOTS: void onFunctionControlChanged(const QString& name, bool exit = false); }; } // UkuiMenu #endif // SECURITYFUNCTIONCONTROL_H ukui-menu/src/utils/power-button.h0000664000175000017500000000352315160463365016200 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef POWERBUTTON_H #define POWERBUTTON_H #include #include #include #include namespace UkuiMenu { class PowerButton : public QObject { Q_OBJECT Q_PROPERTY(QString icon READ getIcon NOTIFY iconChanged) Q_PROPERTY(QString toolTip READ getToolTip NOTIFY toolTipChanged) public: explicit PowerButton(QObject *parent = nullptr); ~PowerButton() override; static void defineModule(const char *uri, int versionMajor, int versionMinor); QString getIcon(); QString getToolTip(); public Q_SLOTS: void clicked(bool leftButtonClicked, int mouseX, int mouseY, bool isFullScreen); private: void openMenu(int menuX, int menuY, bool isFullScreen); bool hasMultipleUsers(); bool canSwitch(); //检测更新 bool canUpgrade(); void execUpgradeAction(const QString &action); QString minutesToHour(int estmateTime); QPointer m_contextMenu; //检测更新并重启和更新并关机预计剩余时间(单位:分钟) int m_estimatedLeftMinutes = 0; Q_SIGNALS: void iconChanged(); void toolTipChanged(); }; } // UkuiMenu #endif // POWERBUTTON_H ukui-menu/src/utils/event-track.cpp0000664000175000017500000000622515160463365016313 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "event-track.h" #include namespace UkuiMenu { KCustomProperty* appendCustomProp(KTrackData *data, const QVariantMap &map) { if (!data || map.isEmpty()) { return nullptr; } int i = 0; auto properties = new KCustomProperty[map.size()]; QMapIterator it(map); while (it.hasNext()) { it.next(); std::string string = it.key().toStdString(); properties[i].key = strdup(string.c_str()); string = it.value().toString().toStdString(); properties[i].value = strdup(string.c_str()); ++i; } return properties; } void freeKCustomProperty(KCustomProperty *customProperty, int size) { for (int i = 0; i < size; ++i) { delete customProperty[i].key; delete customProperty[i].value; } delete [] customProperty; } EventTrack::EventTrack(QObject *parent) : QObject(parent) { } EventTrack *EventTrack::qmlAttachedProperties(QObject *object) { Q_UNUSED(object) return EventTrack::instance(); } EventTrack *EventTrack::instance() { static EventTrack eventTrack; return &eventTrack; } void EventTrack::sendClickEvent(const QString &event, const QString &page, const QVariantMap &map) { KTrackData* data = kdk_dia_data_init(KEVENTSOURCE_DESKTOP, KEVENT_CLICK); KCustomProperty* properties = appendCustomProp(data, map); if (properties) { kdk_dia_append_custom_property(data, properties, map.size()); } kdk_dia_upload_default(data, event.toUtf8().data(), page.toUtf8().data()); kdk_dia_data_free(data); freeKCustomProperty(properties, map.size()); } void EventTrack::sendDefaultEvent(const QString& event, const QString& page, const QVariantMap &map) { KTrackData* data = kdk_dia_data_init(KEVENTSOURCE_DESKTOP, KEVENT_CUSTOM); KCustomProperty* properties = appendCustomProp(data, map); if (properties) { kdk_dia_append_custom_property(data, properties, map.size()); } kdk_dia_upload_default(data, event.toUtf8().data(), page.toUtf8().data()); kdk_dia_data_free(data); freeKCustomProperty(properties, map.size()); } void EventTrack::sendSearchEvent(const QString &event, const QString &page, const QString &content) { KTrackData* data = kdk_dia_data_init(KEVENTSOURCE_DESKTOP, KEVENT_SEARCH); kdk_dia_upload_search_content(data, event.toUtf8().data(), page.toUtf8().data(), content.toUtf8().data()); kdk_dia_data_free(data); } } // Sidebar ukui-menu/src/utils/sidebar-button-utils.h0000664000175000017500000000441615160463365017615 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef SIDEBARBUTTONUTILS_H #define SIDEBARBUTTONUTILS_H #include "user-info-item.h" #include #include #include #include namespace UkuiMenu { class SidebarButtonUtils : public QObject { Q_OBJECT Q_PROPERTY(QString username READ getUsername NOTIFY userInfoChanged) Q_PROPERTY(QString realName READ getRealName NOTIFY userInfoChanged) Q_PROPERTY(QString iconFile READ getIconFile NOTIFY userInfoChanged) public: explicit SidebarButtonUtils(QObject *parent = nullptr); static void defineModule(const char *uri, int versionMajor, int versionMinor); Q_INVOKABLE QString getUsername(); Q_INVOKABLE QString getRealName(); Q_INVOKABLE QString getIconFile(); Q_INVOKABLE void openUserCenter(); Q_INVOKABLE void openPeonyComputer(); Q_INVOKABLE void openControlCenter(); Q_INVOKABLE void openUsersInfoMenu(int menuX, int menuY, bool isFullScreen); private: void registeredAccountsDbus(); void initUserInfo(); QStringList getUserObjectPath(); void switchToUser(QString userName); private Q_SLOTS: void onUserAdded(const QDBusObjectPath &op); void onUserRemoved(const QDBusObjectPath &op); Q_SIGNALS: void userInfoChanged(); private: QDBusInterface *m_systemUserIFace = nullptr; QDBusInterface *m_currentUserIFace = nullptr; std::shared_ptr m_currentUserInfo; QString m_administrator; QString m_standardUser; QList> m_userInfoItemList; QPointer m_usersInfoMenu; }; } // UkuiMenu #endif // SIDEBARBUTTONUTILS_H ukui-menu/src/utils/sidebar-button-utils.cpp0000664000175000017500000001554115160463365020151 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "sidebar-button-utils.h" #include "app-manager.h" #include #include #include #define KYLIN_ACCOUNT_INFORMATION_NAME "org.freedesktop.Accounts" #define KYLIN_ACCOUNT_INFORMATION_PATH "/org/freedesktop/Accounts" #define KYLIN_ACCOUNT_INFORMATION_INTERFACE "org.freedesktop.Accounts" #define DEFAULT_USER_ICON_FILE ":/res/icon/default-community-image.png" using namespace UkuiMenu; SidebarButtonUtils::SidebarButtonUtils(QObject *parent) : QObject(parent) { registeredAccountsDbus(); initUserInfo(); } void SidebarButtonUtils::defineModule(const char *uri, int versionMajor, int versionMinor) { qmlRegisterType("org.ukui.menu.utils", versionMajor, versionMinor, "SidebarButtonUtils"); } QString SidebarButtonUtils::getUsername() { return m_currentUserInfo->userName(); } QString SidebarButtonUtils::getRealName() { return m_currentUserInfo->realName(); } QString SidebarButtonUtils::getIconFile() { if (m_currentUserInfo->iconFile().isEmpty() || m_currentUserInfo->iconFile().endsWith("/.face")) { return {DEFAULT_USER_ICON_FILE}; } return m_currentUserInfo->iconFile(); } void SidebarButtonUtils::openUserCenter() { QStringList args; args.append("-m"); args.append("Userinfo"); UkuiMenu::AppManager::instance()->launchAppWithArguments("/usr/share/applications/ukui-control-center.desktop", args, "ukui-control-center"); } void SidebarButtonUtils::openPeonyComputer() { UkuiMenu::AppManager::instance()->launchAppWithArguments("/usr/share/applications/peony-computer.desktop", QStringList(), "/usr/bin/peony computer:///"); } void SidebarButtonUtils::openControlCenter() { UkuiMenu::AppManager::instance()->launchAppWithArguments("/usr/share/applications/ukui-control-center.desktop", QStringList(), "ukui-control-center"); } void SidebarButtonUtils::openUsersInfoMenu(int menuX, int menuY, bool isFullScreen) { if (m_usersInfoMenu) { m_usersInfoMenu.data()->close(); return; } auto userMenu = new QMenu(); userMenu->setAttribute(Qt::WA_DeleteOnClose); for (std::shared_ptr userItem : m_userInfoItemList) { QAction *act = new QAction(userItem->circularIcon(), userItem->realName(), userMenu); connect(act, &QAction::triggered, userMenu, [this, userItem](){ if (userItem->userName() != m_currentUserInfo->userName()) { switchToUser(userItem->userName()); } }); userMenu->addAction(act); if (userItem->isCurrentUser() && userItem->isLogged()) { act->setCheckable(true); act->setChecked(true); } } userMenu->addSeparator(); QAction *act = new QAction(QIcon::fromTheme("user-available-symbolic"), tr("Change account settings"), userMenu); connect(act, &QAction::triggered, userMenu, [this](){ openUserCenter(); }); userMenu->addAction(act); m_usersInfoMenu = userMenu; if (isFullScreen) { m_usersInfoMenu->popup(QPoint(menuX - m_usersInfoMenu->sizeHint().width(), menuY - m_usersInfoMenu->sizeHint().height())); } else { m_usersInfoMenu->popup(QPoint(menuX, menuY - m_usersInfoMenu->sizeHint().height())); } } void SidebarButtonUtils::registeredAccountsDbus() { m_systemUserIFace = new QDBusInterface(KYLIN_ACCOUNT_INFORMATION_NAME, KYLIN_ACCOUNT_INFORMATION_PATH, KYLIN_ACCOUNT_INFORMATION_INTERFACE, QDBusConnection::systemBus(), this); QDBusConnection::systemBus().connect(KYLIN_ACCOUNT_INFORMATION_NAME, KYLIN_ACCOUNT_INFORMATION_PATH, KYLIN_ACCOUNT_INFORMATION_INTERFACE, "UserAdded", this, SLOT(onUserAdded(QDBusObjectPath))); QDBusConnection::systemBus().connect(KYLIN_ACCOUNT_INFORMATION_NAME, KYLIN_ACCOUNT_INFORMATION_PATH, KYLIN_ACCOUNT_INFORMATION_INTERFACE, "UserDeleted", this, SLOT(onUserRemoved(QDBusObjectPath))); } void SidebarButtonUtils::initUserInfo() { QStringList objectPaths = getUserObjectPath(); for (const QString& objectPath : objectPaths){ std::shared_ptr userItem(new UserInfoItem(objectPath)); m_userInfoItemList.append(userItem); if (userItem->isCurrentUser() && userItem->isLogged()) { m_currentUserInfo = userItem; connect(userItem.get(), &UserInfoItem::userInfoChanged, this, &SidebarButtonUtils::userInfoChanged); } } } QStringList SidebarButtonUtils::getUserObjectPath() { QStringList users; QDBusReply > reply = m_systemUserIFace->call("ListCachedUsers"); if (reply.isValid()) { for (const QDBusObjectPath& op : reply.value()) { users << op.path(); } } return users; } void SidebarButtonUtils::switchToUser(QString userName) { QDBusInterface interface("org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", QDBusConnection::sessionBus()); if (interface.isValid()) { interface.asyncCall("switchToUser", userName); } else { qCritical() << "Call 'org.gnome.SessionManager' interface's method 'switchToUser' failed!" << QDBusConnection::sessionBus().lastError().message(); } } void SidebarButtonUtils::onUserAdded(const QDBusObjectPath &op) { std::shared_ptr userItem(new UserInfoItem(op.path())); connect(userItem.get(), &UserInfoItem::userInfoChanged, this, &SidebarButtonUtils::userInfoChanged); m_userInfoItemList.append(userItem); } void SidebarButtonUtils::onUserRemoved(const QDBusObjectPath &op) { m_userInfoItemList.erase( std::remove_if(m_userInfoItemList.begin(), m_userInfoItemList.end(), [&op](const std::shared_ptr& item) { return item->objPath() == op.path(); }), m_userInfoItemList.end() ); } ukui-menu/src/commons.cpp0000664000175000017500000000132515160463353014374 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "commons.h" ukui-menu/src/menu-dbus-service.cpp0000664000175000017500000001415115160463365016262 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "menu-dbus-service.h" #include "menuadaptor.h" #include #include #include #include #include #include #define MENU_CORE_DBUS_SERVICE "org.ukui.menu" #define MENU_CORE_DBUS_PATH "/org/ukui/menu" #define MENU_CORE_DBUS_INTERFACE "org.ukui.menu" using namespace UkuiMenu; QString MenuDbusService::displayFromPid(uint pid) { QFile environFile(QStringLiteral("/proc/%1/environ").arg(QString::number(pid))); if (environFile.open(QIODevice::ReadOnly | QIODevice::Text)) { const QByteArray DISPLAY = UkuiQuick::WindowProxy::isWayland() ? QByteArrayLiteral("WAYLAND_DISPLAY") : QByteArrayLiteral("DISPLAY"); const auto lines = environFile.readAll().split('\0'); for (const QByteArray &line : lines) { const int equalsIdx = line.indexOf('='); if (equalsIdx <= 0) { continue; } const QByteArray key = line.left(equalsIdx); if (key == DISPLAY) { const QByteArray value = line.mid(equalsIdx + 1); return value; } } } return {}; } MenuDbusService::MenuDbusService(const QString &display, QObject *parent) : QObject(parent), m_display(display) { bool isServiceRegistered = QDBusConnection::sessionBus().interface()->isServiceRegistered(MENU_CORE_DBUS_SERVICE); qDebug() << "menu service is registered:" << isServiceRegistered << ", display:" << display; if (isServiceRegistered) { initWatcher(); } else { bool success = registerService(); if (!success) { initWatcher(); } qDebug() << "menu service register:" << success; } } void MenuDbusService::WinKeyResponse() { uint callerPid = QDBusConnection::sessionBus().interface()->servicePid(message().service()).value(); QString display = MenuDbusService::displayFromPid(callerPid); active(display); } QString MenuDbusService::GetSecurityConfigPath() { QString path = QDir::homePath() + "/.config/ukui-menu-security-config.json"; return path; } void MenuDbusService::ReloadSecurityConfig() { Q_EMIT reloadConfigSignal(); } void MenuDbusService::active(const QString &display) { if (display.isEmpty() || (display == m_display)) { Q_EMIT menuActive(); return; } if (m_menuAdaptor) { Q_EMIT m_menuAdaptor->activeRequest(display); } } void MenuDbusService::activeMenu(QString display) { if (display == m_display) { Q_EMIT menuActive(); } } bool MenuDbusService::registerService() { m_menuAdaptor = new MenuAdaptor(this); QDBusReply reply = QDBusConnection::sessionBus().interface()->registerService(MENU_CORE_DBUS_SERVICE, QDBusConnectionInterface::ReplaceExistingService, QDBusConnectionInterface::DontAllowReplacement); if (reply.value() == QDBusConnectionInterface::ServiceNotRegistered) { return false; } bool res = QDBusConnection::sessionBus().registerObject(MENU_CORE_DBUS_PATH, this); if (!res) { QDBusConnection::sessionBus().interface()->unregisterService(MENU_CORE_DBUS_SERVICE); } return res; } void MenuDbusService::onServiceOwnerChanged(const QString &service, const QString &oldOwner, const QString &newOwner) { qDebug() << "serviceOwnerChanged:" << service << oldOwner << newOwner; if (newOwner.isEmpty()) { bool success = registerService(); if (success) { disConnectActiveRequest(); delete m_watcher; m_watcher = nullptr; } qDebug() << "try to register service:" << success; return; } uint newOwnerPid = QDBusConnection::sessionBus().interface()->servicePid(newOwner); qDebug() << "newOwnerPid:" << newOwnerPid << ", myPid:" << QCoreApplication::applicationPid() << ", display:" << m_display; // if (newOwnerPid == QCoreApplication::applicationPid()) { // qDebug() << "Becoming a new service"; // } } void MenuDbusService::connectToActiveRequest() { QDBusConnection::sessionBus().connect(MENU_CORE_DBUS_SERVICE, MENU_CORE_DBUS_PATH, MENU_CORE_DBUS_INTERFACE, "activeRequest", this, SLOT(activeMenu(QString))); } void MenuDbusService::disConnectActiveRequest() { QDBusConnection::sessionBus().disconnect(MENU_CORE_DBUS_SERVICE, MENU_CORE_DBUS_PATH, MENU_CORE_DBUS_INTERFACE, "activeRequest", this, SLOT(activeMenu(QString))); } void MenuDbusService::initWatcher() { m_watcher = new QDBusServiceWatcher(MENU_CORE_DBUS_SERVICE,QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange); connect(m_watcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &MenuDbusService::onServiceOwnerChanged); connectToActiveRequest(); } ukui-menu/src/model/0000775000175000017500000000000015160463353013314 5ustar fengfengukui-menu/src/model/app-page-header-utils.cpp0000664000175000017500000001362215160463353020102 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "app-page-header-utils.h" #include "data-provider-manager.h" #include #include #include #include #include #include namespace UkuiMenu { class ProviderModel : public QAbstractListModel { Q_OBJECT public: enum Roles {Id = 0, Name, Icon, Title, IsChecked}; explicit ProviderModel(QVector, QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; void updateCurrentPId(const QString &providerId); //在各个插件之间切换 Q_INVOKABLE void reactivateProvider(); Q_INVOKABLE void changeProvider(int i); Q_INVOKABLE QString getProviderIcon(int i); Q_INVOKABLE int currentProviderId(); Q_SIGNALS: void currentIndexChanged(); void menuClosed(); private: int indexOfProvider(const QString &providerId); private: int m_currentIndex = 0; QPointer m_menu; QString m_currentId; QVector m_providers; QHash m_roleNames; }; // ====== ProviderModel ====== ProviderModel::ProviderModel(QVector infos, QObject *parent) : QAbstractListModel(parent), m_providers(std::move(infos)) { std::sort(m_providers.begin(), m_providers.end(), [=](const ProviderInfo &a, const ProviderInfo &b) { return a.index < b.index; }); m_roleNames.insert(Id, "id"); m_roleNames.insert(Name, "name"); m_roleNames.insert(Icon, "icon"); m_roleNames.insert(Title, "title"); m_roleNames.insert(IsChecked, "isChecked"); } int ProviderModel::rowCount(const QModelIndex &parent) const { return m_providers.size(); } QVariant ProviderModel::data(const QModelIndex &index, int role) const { int row = index.row(); if (row < 0 || row > m_providers.size()) { return {}; } switch (role) { case Id: return m_providers.at(row).id; case Name: return m_providers.at(row).name; case Icon: return m_providers.at(row).icon; case Title: return m_providers.at(row).title; case IsChecked: return (m_currentId == m_providers.at(row).id); default: break; } return {}; } QHash ProviderModel::roleNames() const { return m_roleNames; } void ProviderModel::updateCurrentPId(const QString &providerId) { int index = indexOfProvider(providerId); if (index < 0) { return; } m_currentId = providerId; m_currentIndex = index; Q_EMIT beginResetModel(); Q_EMIT endResetModel(); Q_EMIT currentIndexChanged(); } int ProviderModel::indexOfProvider(const QString &providerId) { for (int i = 0; i < m_providers.size(); ++i) { if(providerId == m_providers.at(i).id) { return i; } } return -1; } void ProviderModel::changeProvider(int i) { DataProviderManager::instance()->activateProvider(m_providers.at(i).id); } QString ProviderModel::getProviderIcon(int i) { return data(createIndex(i, 0), Icon).toString(); } int ProviderModel::currentProviderId() { return m_currentIndex; } void ProviderModel::reactivateProvider() { DataProviderManager::instance()->activateProvider(m_providers.at(m_currentIndex).id); } // ====== AppPageHeaderUtils ====== AppPageHeaderUtils::AppPageHeaderUtils(QObject *parent) : QObject(parent) { qRegisterMetaType("ProviderModel*"); qRegisterMetaType("PluginGroup::Group"); qmlRegisterUncreatableType("org.ukui.menu.utils", 1, 0, "PluginGroup", "Use enums only."); for (int i = PluginGroup::Button; i <= PluginGroup::SortMenuItem; ++i) { //qDebug() << "==PluginGroup==" << PluginGroup::Group(i); auto *model = new ProviderModel(DataProviderManager::instance()->providers(PluginGroup::Group(i)), this); model->updateCurrentPId(DataProviderManager::instance()->activatedProvider()); m_models.insert(PluginGroup::Group(i), model); } connect(DataProviderManager::instance(), &DataProviderManager::pluginChanged, this, &AppPageHeaderUtils::onPluginChanged); } void AppPageHeaderUtils::onPluginChanged(const QString &id, PluginGroup::Group group) { ProviderModel *model = m_models.value(group); if (model) { model->updateCurrentPId(id); Q_EMIT pluginChanged(id); } } ProviderModel *AppPageHeaderUtils::model(PluginGroup::Group group) const { return m_models.value(group); } void AppPageHeaderUtils::activateProvider(const QString &name) const { DataProviderManager::instance()->activateProvider(name); } void AppPageHeaderUtils::startSearch(QString key) const { DataProviderManager::instance()->forceUpdate(key); } QString AppPageHeaderUtils::getPluginTitle(QString id) const { if (id.isEmpty()) { id = DataProviderManager::instance()->activatedProvider(); } return DataProviderManager::instance()->providerInfo(id).title; } QString AppPageHeaderUtils::currentPluginId() const { return DataProviderManager::instance()->activatedProvider(); } } // UkuiMenu #include "app-page-header-utils.moc" ukui-menu/src/model/app-model.cpp0000664000175000017500000001514115160463353015700 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "app-model.h" #include "app-manager.h" #include "context-menu-manager.h" #include "app-folder-helper.h" #include "settings.h" #include #include #include using namespace UkuiMenu; AppModel::AppModel(QObject *parent) : QAbstractListModel(parent) { reloadPluginData(); connect(DataProviderManager::instance(), &DataProviderManager::dataChanged, this, &AppModel::onPluginDataChanged); connect(DataProviderManager::instance(), &DataProviderManager::pluginChanged, this, &AppModel::reloadPluginData); connect(GlobalSetting::instance(), &GlobalSetting::styleChanged, this, &AppModel::onStyleChanged); } int AppModel::rowCount(const QModelIndex &parent) const { return m_apps.size(); } QVariant AppModel::data(const QModelIndex &index, int role) const { int i = index.row(); if (i < 0 || i >= m_apps.size()) { return {}; } switch (role) { case DataEntity::Id: return m_apps.at(i).id(); case DataEntity::Type: return m_apps.at(i).type(); case DataEntity::Icon: return m_apps.at(i).icon(); case DataEntity::Name: return m_apps.at(i).name(); case DataEntity::Comment: return m_apps.at(i).comment(); case DataEntity::ExtraData: return m_apps.at(i).extraData(); case DataEntity::Favorite: return m_apps.at(i).favorite() == 0 ? false : true; default: break; } return {}; } QHash AppModel::roleNames() const { return DataEntity::AppRoleNames(); } int AppModel::getLabelIndex(const QString &id) { for (int index = 0; index < m_apps.count(); ++index) { if (m_apps.at(index).type() == DataType::Label && m_apps.at(index).id() == id) { return index; } } return -1; } QVariantList AppModel::folderApps(const QString &folderName) { DataEntity item1{DataType::Normal, "name 1", "icon", "comment", "extra"}; DataEntity item2{DataType::Normal, "name 2", "icon", "comment", "extra"}; DataEntity item3{DataType::Label, "name 3", "icon", "comment", "extra"}; DataEntity item4{DataType::Normal, "name 4", "icon", "comment", "extra"}; QVariantList list; list.append(QVariant::fromValue(item1)); list.append(QVariant::fromValue(item2)); list.append(QVariant::fromValue(item3)); list.append(QVariant::fromValue(item4)); return list; } void AppModel::appClicked(const int &index) { if (index < 0 || index >= m_apps.size()) { return; } AppManager::instance()->launchApp(m_apps.at(index).id()); } void AppModel::reloadPluginData() { QVector data = DataProviderManager::instance()->data(); resetModel(data); } void AppModel::onStyleChanged(const GlobalSetting::Key &key) { if (key == GlobalSetting::IconThemeName) { Q_EMIT beginResetModel(); Q_EMIT endResetModel(); } } void AppModel::toRenameFolder(QString id) { Q_EMIT renameText(id); } void AppModel::onPluginDataChanged(QVector data, DataUpdateMode::Mode mode, quint32 index) { switch (mode) { default: case DataUpdateMode::Reset: { resetModel(data); break; } case DataUpdateMode::Append: { appendData(data); break; } case DataUpdateMode::Prepend: { prependData(data); break; } case DataUpdateMode::Insert: { insertData(data, static_cast(index)); break; } case DataUpdateMode::Update: { updateData(data); break; } } } void AppModel::resetModel(QVector &data) { Q_EMIT beginResetModel(); m_apps.swap(data); Q_EMIT endResetModel(); } void AppModel::appendData(QVector &data) { Q_EMIT beginInsertRows(QModelIndex(), m_apps.size(), (m_apps.size() + data.size() - 1)); m_apps.append(data); Q_EMIT endInsertRows(); } void AppModel::prependData(QVector &data) { Q_EMIT beginInsertRows(QModelIndex(), 0, data.size() - 1); data.append(m_apps); m_apps.swap(data); Q_EMIT endInsertRows(); } void AppModel::insertData(QVector &data, int index) { if (index < 0 || (!m_apps.isEmpty() && index >= m_apps.size())) { return; } Q_EMIT beginInsertRows(QModelIndex(), index, index + data.size() - 1); for (const auto &item : data) { m_apps.insert(index, item); ++index; } Q_EMIT endInsertRows(); } void AppModel::updateData(QVector &data) { for (const DataEntity &item : data) { for (int i = 0; i < m_apps.length(); i++) { if (item.id() == m_apps.at(i).id()) { m_apps.replace(i, item); Q_EMIT dataChanged(index(i, 0), index(i, 0)); break; } } } } void AppModel::openMenu(const int &index, MenuInfo::Location location) { if (index < 0 || index >= m_apps.size()) { return; } ContextMenuManager::instance()->showMenu(m_apps.at(index), location, DataProviderManager::instance()->activatedProvider()); } void AppModel::renameFolder(const QString &index, const QString &folderName) { AppFolderHelper::instance()->renameFolder(index.toInt(), folderName); } void AppModel::addAppsToFolder(const QString &appIdA, const QString &appIdB, const QString &folderName) { AppFolderHelper::instance()->addAppsToNewFolder(appIdA, appIdB, folderName); } void AppModel::addAppToFolder(const QString &appId, const QString &folderId) { AppFolderHelper::instance()->addAppToFolder(appId, folderId.toInt()); } QVariantList AppModel::getApps(int start, int end) { if (start < 0 || start >= m_apps.size() || end < 0 || end > m_apps.size()) { return {}; } QVariantList list; for (int i = start; i < end; ++i) { list.append(QVariant::fromValue(m_apps.at(i))); } return list; } ukui-menu/src/model/model-manager.cpp0000664000175000017500000000412415160463353016531 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "model-manager.h" #include "label-model.h" #include "app-model.h" #include "app-group-model.h" #include "favorite/folder-model.h" #include namespace UkuiMenu { ModelManager *ModelManager::instance() { static ModelManager modelManager; return &modelManager; } void ModelManager::registerMetaTypes() { qRegisterMetaType("AppModel*"); qRegisterMetaType("LabelModel*"); qRegisterMetaType("FolderModel*"); qRegisterMetaType("AppGroupModel*"); } ModelManager::ModelManager(QObject *parent) : QObject(parent) { ModelManager::registerMetaTypes(); appModel = new AppModel(this); labelModel = new LabelModel(this); folderModel = new FolderModel(this); labelGroupModel = new AppGroupModel(appModel, this); QQmlEngine::setObjectOwnership(appModel, QQmlEngine::CppOwnership); QQmlEngine::setObjectOwnership(labelModel, QQmlEngine::CppOwnership); QQmlEngine::setObjectOwnership(folderModel, QQmlEngine::CppOwnership); QQmlEngine::setObjectOwnership(labelGroupModel, QQmlEngine::CppOwnership); } AppModel *ModelManager::getAppModel() { return appModel; } LabelModel *ModelManager::getLabelModel() { return labelModel; } AppGroupModel *ModelManager::getLabelGroupModel() { return labelGroupModel; } FolderModel *ModelManager::getFolderModel() { return folderModel; } } // UkuiMenu ukui-menu/src/model/app-model.h0000664000175000017500000000470215160463353015346 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_MODEL_H #define UKUI_MENU_APP_MODEL_H #include #include #include "commons.h" #include "data-provider-manager.h" #include "settings.h" #include "context-menu-extension.h" namespace UkuiMenu { class AppModel : public QAbstractListModel { Q_OBJECT public: explicit AppModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; QVariantList getApps(int start = 0, int end = 0); Q_INVOKABLE int getLabelIndex(const QString &id); Q_INVOKABLE QVariantList folderApps(const QString &folderName); Q_INVOKABLE void appClicked(const int &index); Q_INVOKABLE void openMenu(const int &index, MenuInfo::Location location); Q_INVOKABLE void renameFolder(const QString &index, const QString &folderName); Q_INVOKABLE void addAppsToFolder(const QString &appIdA, const QString &appIdB, const QString &folderName); Q_INVOKABLE void addAppToFolder(const QString &appId, const QString &folderId); public Q_SLOTS: void toRenameFolder(QString id); private Q_SLOTS: void onPluginDataChanged(QVector data, DataUpdateMode::Mode mode, quint32 index); void reloadPluginData(); void onStyleChanged(const GlobalSetting::Key& key); private: void resetModel(QVector &data); void appendData(QVector &data); void prependData(QVector &data); void insertData(QVector &data, int index); void updateData(QVector &data); private: QVector m_apps; Q_SIGNALS: void renameText(QString id); }; } // UkuiMenu #endif //UKUI_MENU_APP_MODEL_H ukui-menu/src/model/app-group-model.h0000664000175000017500000000351215160463353016476 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_GROUP_MODEL_H #define UKUI_MENU_APP_GROUP_MODEL_H #include #include "commons.h" namespace UkuiMenu { class AppModel; class LabelGroupModelPrivate; class AppGroupModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(bool containLabel READ containLabel NOTIFY containLabelChanged) public: explicit AppGroupModel(AppModel *appModel, QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; bool containLabel(); Q_INVOKABLE void openMenu(int labelIndex, int appIndex); Q_INVOKABLE void openApp(int labelIndex, int appIndex); Q_INVOKABLE int getLabelIndex(const QString &labelId); Q_SIGNALS: void containLabelChanged(bool containLabel); private Q_SLOTS: void reloadLabels(); private: void connectSignals(); inline int getLabelIndex(int index) const; inline void resetModel(QVector &labels); private: LabelGroupModelPrivate *d{nullptr}; }; } // UkuiMenu #endif //UKUI_MENU_APP_GROUP_MODEL_H ukui-menu/src/model/model-manager.h0000664000175000017500000000277415160463353016207 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_MODEL_MANAGER_H #define UKUI_MENU_MODEL_MANAGER_H #include namespace UkuiMenu { class AppModel; class LabelModel; class AppGroupModel; class FolderModel; class ModelManager : public QObject { Q_OBJECT public: static ModelManager *instance(); static void registerMetaTypes(); ~ModelManager() override = default; Q_INVOKABLE AppModel *getAppModel(); Q_INVOKABLE LabelModel *getLabelModel(); Q_INVOKABLE FolderModel *getFolderModel(); Q_INVOKABLE AppGroupModel *getLabelGroupModel(); private: explicit ModelManager(QObject *parent = nullptr); private: AppModel *appModel{nullptr}; LabelModel *labelModel{nullptr}; FolderModel *folderModel{nullptr}; AppGroupModel *labelGroupModel{nullptr}; }; } // UkuiMenu #endif //UKUI_MENU_MODEL_MANAGER_H ukui-menu/src/model/label-model.cpp0000664000175000017500000000435015160463353016177 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "label-model.h" #include "data-provider-manager.h" namespace UkuiMenu { LabelModel::LabelModel(QObject *parent) : QAbstractListModel(parent) { reloadLabelData(); connect(DataProviderManager::instance(),&DataProviderManager::pluginChanged, this,&LabelModel::reloadLabelData); connect(DataProviderManager::instance(),&DataProviderManager::labelChanged, this,&LabelModel::reloadLabelData); } int LabelModel::rowCount(const QModelIndex &parent) const { return m_labels.size(); } QVariant LabelModel::data(const QModelIndex &index, int role) const { int i = index.row(); if (i < 0 || i >= m_labels.size()) { return {}; } switch (role) { case LabelItem::IsDisable: return m_labels.at(i).isDisable(); case LabelItem::Id: return m_labels.at(i).id(); case LabelItem::Index: return m_labels.at(i).index(); case LabelItem::DisplayName: return m_labels.at(i).displayName(); default: break; } return {}; } QHash LabelModel::roleNames() const { QHash names; names.insert(LabelItem::IsDisable, "isDisable"); names.insert(LabelItem::Id, "id"); names.insert(LabelItem::Index, "index"); names.insert(LabelItem::DisplayName, "displayName"); return names; } void LabelModel::reloadLabelData() { QVector labels = DataProviderManager::instance()->labels(); Q_EMIT beginResetModel(); m_labels.swap(labels); Q_EMIT endResetModel(); } } // UkuiMenu ukui-menu/src/model/app-page-header-utils.h0000664000175000017500000000317315160463353017547 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_PAGE_HEADER_UTILS_H #define UKUI_MENU_APP_PAGE_HEADER_UTILS_H #include #include #include "data-provider-plugin-iface.h" namespace UkuiMenu { class ProviderModel; class AppPageHeaderUtils : public QObject { Q_OBJECT public: explicit AppPageHeaderUtils(QObject *parent = nullptr); // 激活某插件 Q_INVOKABLE void activateProvider(const QString &name) const; // 获取不同的model Q_INVOKABLE ProviderModel *model(PluginGroup::Group group) const; Q_INVOKABLE void startSearch(QString key) const; Q_INVOKABLE QString getPluginTitle(QString id) const; Q_INVOKABLE QString currentPluginId() const; Q_SIGNALS: void pluginChanged(const QString &id); private Q_SLOTS: void onPluginChanged(const QString &id, PluginGroup::Group group); private: QHash m_models; }; } // UkuiMenu #endif //UKUI_MENU_APP_PAGE_HEADER_UTILS_H ukui-menu/src/model/label-model.h0000664000175000017500000000241315160463353015642 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_LABEL_MODEL_H #define UKUI_MENU_LABEL_MODEL_H #include #include "commons.h" namespace UkuiMenu { class LabelModel : public QAbstractListModel { Q_OBJECT public: explicit LabelModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; private Q_SLOTS: void reloadLabelData(); private: QVector m_labels; }; } // UkuiMenu #endif //UKUI_MENU_LABEL_MODEL_H ukui-menu/src/model/app-group-model.cpp0000664000175000017500000001223415160463353017032 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "app-group-model.h" #include "data-provider-manager.h" #include "app-model.h" namespace UkuiMenu { class LabelGroupModelPrivate { public: explicit LabelGroupModelPrivate(AppModel *appModel) : appModel(appModel) {} AppModel *appModel{nullptr}; bool containLabel{false}; QVector labels; QVector labelIndex; }; AppGroupModel::AppGroupModel(AppModel *appModel, QObject *parent) : QAbstractListModel(parent), d(new LabelGroupModelPrivate(appModel)) { reloadLabels(); connect(DataProviderManager::instance(), &DataProviderManager::pluginChanged, this, &AppGroupModel::reloadLabels); connect(DataProviderManager::instance(), &DataProviderManager::labelChanged, this, &AppGroupModel::reloadLabels); connectSignals(); } void AppGroupModel::reloadLabels() { Q_EMIT beginResetModel(); d->labels.clear(); for (const auto &label : DataProviderManager::instance()->labels()) { if (label.isDisable()) { continue; } d->labels.append(label); } d->containLabel = !d->labels.isEmpty(); d->labelIndex = QVector(d->labels.size(), -1); Q_EMIT endResetModel(); Q_EMIT containLabelChanged(d->containLabel); } int AppGroupModel::rowCount(const QModelIndex &parent) const { return d->labels.count(); } QVariant AppGroupModel::data(const QModelIndex &index, int role) const { int i = index.row(); if (i < 0 || i >= d->labels.size()) { return {}; } switch (role) { case DataEntity::Type: return DataType::Label; case DataEntity::Icon: return ""; case DataEntity::Name: return d->labels.at(i).displayName(); case DataEntity::Comment: return {}; case DataEntity::ExtraData: { int start = getLabelIndex(i) + 1; int end; if (i >= (d->labels.size() - 1)) { end = d->appModel->rowCount(QModelIndex()); } else { end = getLabelIndex(i + 1); } return d->appModel->getApps(start, end); } default: break; } return {}; } QHash AppGroupModel::roleNames() const { return d->appModel->roleNames(); } void AppGroupModel::connectSignals() { connect(d->appModel, &QAbstractItemModel::modelReset, this, [this] { if (!d->containLabel) { return; } beginResetModel(); for (auto &item : d->labelIndex) { item = -1; } endResetModel(); }); connect(d->appModel, &QAbstractItemModel::rowsInserted, this, [this] (const QModelIndex &parent, int first, int last) { if (d->containLabel) { beginResetModel(); endResetModel(); } }); // connect(d->appModel, &QAbstractItemModel::rowsRemoved, // this, &QAbstractItemModel::rowsRemoved); // // connect(d->appModel, &QAbstractItemModel::rowsAboutToBeRemoved, // this, &QAbstractItemModel::rowsAboutToBeRemoved); // // connect(d->appModel, &QAbstractItemModel::dataChanged, // this, &QAbstractItemModel::dataChanged); // // connect(d->appModel, &QAbstractItemModel::rowsMoved, // this, &QAbstractItemModel::rowsMoved); // // connect(d->appModel, &QAbstractItemModel::layoutChanged, // this, &QAbstractItemModel::layoutChanged); } bool AppGroupModel::containLabel() { return d->containLabel; } inline int AppGroupModel::getLabelIndex(int i) const { int index = d->labelIndex.at(i); if (index < 0) { index = d->appModel->getLabelIndex(d->labels.at(i).id()); d->labelIndex[i] = index; } return index; } void AppGroupModel::openApp(int labelIndex, int appIndex) { int index = d->labelIndex.at(labelIndex); d->appModel->appClicked(++index + appIndex); } int AppGroupModel::getLabelIndex(const QString &labelId) { for (int i = 0; i < d->labels.size(); ++i) { if (d->labels.at(i).id() == labelId) { return i; } } return -1; } inline void AppGroupModel::resetModel(QVector &labels) { d->labels.swap(labels); d->containLabel = !d->labels.isEmpty(); d->labelIndex = QVector(d->labels.size(), -1); } void AppGroupModel::openMenu(int labelIndex, int appIndex) { int index = d->labelIndex.at(labelIndex); d->appModel->openMenu(++index + appIndex, MenuInfo::FullScreen); } } // UkuiMenu ukui-menu/src/libappdata/0000775000175000017500000000000015160463365014320 5ustar fengfengukui-menu/src/libappdata/basic-app-model.cpp0000664000175000017500000001357515160463353017771 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include "basic-app-model.h" #include "user-config.h" #include namespace UkuiMenu { BasicAppModel *BasicAppModel::instance() { static BasicAppModel model; return &model; } BasicAppModel::BasicAppModel(QObject *parent) : QAbstractListModel(parent) , m_databaseInterface(new AppDatabaseInterface(this)) { connect(m_databaseInterface, &AppDatabaseInterface::appDatabaseOpenFailed, this, [this] { qWarning() << "BasicAppModel: app database open failed."; m_apps.clear(); // TODO: 显示错误信息到界面 }); m_apps = m_databaseInterface->apps(); connect(m_databaseInterface, &AppDatabaseInterface::appAdded, this, &BasicAppModel::onAppAdded); connect(m_databaseInterface, &AppDatabaseInterface::appUpdated, this, &BasicAppModel::onAppUpdated); connect(m_databaseInterface, &AppDatabaseInterface::appDeleted, this, &BasicAppModel::onAppDeleted); } int BasicAppModel::rowCount(const QModelIndex &parent) const { return m_apps.count(); } int BasicAppModel::columnCount(const QModelIndex &parent) const { return 1; } QVariant BasicAppModel::data(const QModelIndex &index, int role) const { if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { return {}; } const DataEntity &app = m_apps[index.row()]; return app.getValue(static_cast(role)); } bool BasicAppModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { return false; } DataEntity &app = m_apps[index.row()]; DataEntity::PropertyName propertyName; switch (propertyName) { case DataEntity::Group: app.setGroup(value.toString()); return true; case DataEntity::IsLaunched: { if (value.toBool() == app.launched()) { return false; } m_databaseInterface->updateApLaunchedState(app.id(), value.toBool()); return true; } case DataEntity::Favorite: if (value.toInt() == app.favorite()) { return false; } m_databaseInterface->fixAppToFavorite(app.id(), value.toInt()); return true; case DataEntity::Top: if (value.toInt() == app.top()) { return false; } m_databaseInterface->fixAppToTop(app.id(), value.toInt()); return true; case DataEntity::RecentInstall: app.setRecentInstall(value.toBool()); return true; default: break; } return QAbstractItemModel::setData(index, value, role); } QHash BasicAppModel::roleNames() const { return DataEntity::AppRoleNames(); } const AppDatabaseInterface *BasicAppModel::databaseInterface() const { return m_databaseInterface; } void BasicAppModel::onAppAdded(const DataEntityVector &apps) { DataEntityVector appItems; for (auto const & app : apps) { if (indexOfApp(app.id()) < 0) { appItems.append(app); } } if (appItems.isEmpty()) return; beginInsertRows(QModelIndex(), m_apps.size(), m_apps.size() + appItems.size() - 1); m_apps.append(appItems); endInsertRows(); } void BasicAppModel::onAppUpdated(const QVector > > &updates) { for (const auto &pair : updates) { int index = indexOfApp(pair.first.id()); if (index < 0) { continue; } DataEntity &app = m_apps[index]; QVector roles = pair.second; if (!roles.isEmpty()) { for (const auto &role : roles) { app.setValue(static_cast(role), pair.first.getValue(static_cast(role))); } } else { app = pair.first; } dataChanged(QAbstractListModel::index(index), QAbstractListModel::index(index), roles); } } void BasicAppModel::onAppDeleted(const QStringList &apps) { for (const auto &appid : apps) { UserConfig::instance()->removePreInstalledApp(appid); int index = indexOfApp(appid); if (index < 0) { continue; } beginRemoveRows(QModelIndex(), index, index); m_apps.takeAt(index); endRemoveRows(); } } int BasicAppModel::indexOfApp(const QString &appid) const { if (appid.isEmpty()) { return -1; } auto it = std::find_if(m_apps.constBegin(), m_apps.constEnd(), [&appid] (const DataEntity &app) { return app.id() == appid; }); if (it == m_apps.constEnd()) { return -1; } return std::distance(m_apps.constBegin(), it); } DataEntity BasicAppModel::appOfIndex(int row) const { QModelIndex idx = QAbstractListModel::index(row); if (!checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) { return {}; } return m_apps.at(row); } bool BasicAppModel::getAppById(const QString &appid, DataEntity &app) const { int idx = indexOfApp(appid); if (idx < 0) { return false; } app = m_apps.at(idx); return true; } } // UkuiMenu ukui-menu/src/libappdata/app-list-model.h0000664000175000017500000000572315160463353017324 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #ifndef UKUI_MENU_APP_LIST_MODEL_H #define UKUI_MENU_APP_LIST_MODEL_H #include "app-list-plugin.h" #include "context-menu-extension.h" #include #include namespace UkuiMenu { /** * @class AppListHeader * * 应用列表顶部功能区域 * 显示当前插件的操作选项,如果插件的action为空,那么不显示header. */ class AppListHeader : public QObject { Q_OBJECT Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) Q_PROPERTY(QList actions READ actions NOTIFY actionsChanged) friend class AppListModel; public: explicit AppListHeader(QObject *parent = nullptr); bool visible() const; void setVisible(bool visible); QString title() const; void setTitle(const QString &title); QList actions() const; void addAction(QAction *actions); void removeAction(QAction *actions); void removeAllAction(); Q_SIGNALS: void titleChanged(); void actionsChanged(); void visibleChanged(); private: bool m_visible {false}; QString m_title; QList m_actions; }; class AppListModel : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(UkuiMenu::AppListHeader *header READ getHeader NOTIFY headerChanged) Q_PROPERTY(UkuiMenu::LabelBottle *labelBottle READ labelBottle NOTIFY labelBottleChanged) public: explicit AppListModel(QObject *parent = nullptr); /** * 覆盖全部roles,子model必须返回相同的roles * @return */ QHash roleNames() const override; AppListHeader *getHeader() const; LabelBottle *labelBottle() const; void installPlugin(AppListPluginInterface *plugin); // reset void unInstallPlugin(); Q_INVOKABLE void openMenu(const int &index, MenuInfo::Location location) const; Q_INVOKABLE int findLabelIndex(const QString &label) const; Q_SIGNALS: void headerChanged(); void labelBottleChanged(); private: AppListHeader *m_header {nullptr}; AppListPluginInterface *m_plugin {nullptr}; }; } // UkuiMenu Q_DECLARE_METATYPE(UkuiMenu::AppListModel*) Q_DECLARE_METATYPE(UkuiMenu::AppListHeader*) #endif //UKUI_MENU_APP_LIST_MODEL_H ukui-menu/src/libappdata/app-group-model.h0000664000175000017500000000537215160463353017505 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #ifndef UKUI_MENU_APP_GROUP_MODEL_H #define UKUI_MENU_APP_GROUP_MODEL_H #include #include "data-entity.h" namespace UkuiMenu { /** * @class AppGroupModel * * 根据app的group属性进行分组,将同一组的应用挂在到某一个索引下 */ class AppGroupModel : public QAbstractProxyModel { Q_OBJECT // Q_PROPERTY(UkuiMenu::DataEntity::PropertyName sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged) public: explicit AppGroupModel(QObject *parent = nullptr); void setSourceModel(QAbstractItemModel *sourceModel) override; QModelIndex index(int row, int column, const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; bool hasChildren(const QModelIndex &parent) const override; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QModelIndex mapToSource(const QModelIndex &proxyIndex) const override; QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override; QHash roleNames() const override; QVariant data(const QModelIndex &proxyIndex, int role) const override; Q_INVOKABLE int findLabelIndex(const QString &label) const; private Q_SLOTS: void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); void onLayoutChanged(const QList &parents, QAbstractItemModel::LayoutChangeHint hint); void onRowsInserted(const QModelIndex &parent, int first, int last); void onRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last); private: void rebuildAppGroups(); void reLocationIndex(int base, int offset); int findGroupIndex(const QModelIndex &sourceIndex) const; void insertApp(int groupIndex, const QModelIndex &sourceIndex); private: // 存储分组信息 QVector*> m_groups; bool m_needRebuild {false}; }; } // UkuiMenu #endif //UKUI_MENU_APP_GROUP_MODEL_H ukui-menu/src/libappdata/app-database-interface.cpp0000664000175000017500000003446215160463365021315 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include "app-database-interface.h" #include "user-config.h" #include "settings.h" #include #include #define APP_ICON_PREFIX "image://theme/" namespace UkuiMenu { class AppDatabaseWorkerPrivate : public QObject { Q_OBJECT friend class AppDatabaseInterface; public: explicit AppDatabaseWorkerPrivate(AppDatabaseInterface *parent = nullptr); DataEntityVector getAllApps(); bool getApp(const QString &appid, DataEntity &app); // 数据库操作函数 void setAppProperty(const QString &appid, const UkuiSearch::ApplicationPropertyMap &propertyMap); void setAppProperty(const QString &appid, const UkuiSearch::ApplicationProperty::Property &property, const QVariant &value); private Q_SLOTS: /** * 应用数据库的添加信号处理函数 * @param infos 新增应用的id列表 */ void onAppDatabaseAdded(const QStringList &infos); void onAppDatabaseUpdate(const UkuiSearch::ApplicationInfoMap &infoMap); void onAppDatabaseUpdateAll(const QStringList &infos); void onAppDatabaseDeleted(const QStringList &infos); private: static void addInfoToApp(const QMap &info, DataEntity &app); static void setRemovable(DataEntity &app); bool isFilterAccepted(const UkuiSearch::ApplicationPropertyMap &appInfo) const; private: AppDatabaseInterface *q {nullptr}; UkuiSearch::ApplicationInfo *appDatabase {nullptr}; UkuiSearch::ApplicationProperties properties; // 设置我们需要的属性和值 UkuiSearch::ApplicationPropertyMap filter; }; AppDatabaseWorkerPrivate::AppDatabaseWorkerPrivate(AppDatabaseInterface *parent) : QObject(parent), q(parent) { // 注册需要在信号和槽函数中使用的数据结构 qRegisterMetaType("DataEntityVector"); // 初始化应用数据库链接 appDatabase = new UkuiSearch::ApplicationInfo(this); // 首次启动时,为某些应用设置标志位 if (UserConfig::instance()->isFirstStartUp()) { // 预装应用 QStringList appList; UkuiSearch::ApplicationProperties appProperty; UkuiSearch::ApplicationPropertyMap appFilter; appProperty.append(UkuiSearch::ApplicationProperty::Property::DesktopFilePath); appFilter.insert(UkuiSearch::ApplicationProperty::Property::DontDisplay, 0); appFilter.insert(UkuiSearch::ApplicationProperty::Property::AutoStart, 0); UkuiSearch::ApplicationInfoMap appInfos = appDatabase->getInfo(appProperty, appFilter); for (const auto &info : appInfos) { appList.append(info.value(UkuiSearch::ApplicationProperty::Property::DesktopFilePath).toString()); } UserConfig::instance()->initPreInstalledLists(appList); // 默认收藏应用 for (const auto &appid : GlobalSetting::instance()->defaultFavoriteApps()) { appDatabase->setAppToFavorites(appid); appDatabase->setAppLaunchedState(appid, true); } } // 设置从数据库查询哪些属性 properties << UkuiSearch::ApplicationProperty::Property::Top << UkuiSearch::ApplicationProperty::Property::Lock << UkuiSearch::ApplicationProperty::Property::Favorites << UkuiSearch::ApplicationProperty::Property::LaunchTimes << UkuiSearch::ApplicationProperty::Property::DesktopFilePath << UkuiSearch::ApplicationProperty::Property::Icon << UkuiSearch::ApplicationProperty::Property::LocalName << UkuiSearch::ApplicationProperty::Property::Category << UkuiSearch::ApplicationProperty::Property::FirstLetterAll << UkuiSearch::ApplicationProperty::Property::DontDisplay << UkuiSearch::ApplicationProperty::Property::AutoStart << UkuiSearch::ApplicationProperty::Property::InsertTime << UkuiSearch::ApplicationProperty::Property::Launched; // 需要从数据库过滤的属性,满足该条件的数据才会被查询出来 filter.insert(UkuiSearch::ApplicationProperty::Property::DontDisplay, 0); filter.insert(UkuiSearch::ApplicationProperty::Property::AutoStart, 0); // 链接数据库信号 connect(appDatabase, &UkuiSearch::ApplicationInfo::appDBItems2BAdd, this, &AppDatabaseWorkerPrivate::onAppDatabaseAdded); connect(appDatabase, &UkuiSearch::ApplicationInfo::appDBItems2BUpdate, this, &AppDatabaseWorkerPrivate::onAppDatabaseUpdate); connect(appDatabase, &UkuiSearch::ApplicationInfo::appDBItems2BUpdateAll, this, &AppDatabaseWorkerPrivate::onAppDatabaseUpdateAll); connect(appDatabase, &UkuiSearch::ApplicationInfo::appDBItems2BDelete, this, &AppDatabaseWorkerPrivate::onAppDatabaseDeleted); connect(appDatabase, &UkuiSearch::ApplicationInfo::DBOpenFailed, q, &AppDatabaseInterface::appDatabaseOpenFailed); } void AppDatabaseWorkerPrivate::addInfoToApp(const UkuiSearch::ApplicationPropertyMap &info, DataEntity &app) { app.setTop(info.value(UkuiSearch::ApplicationProperty::Property::Top).toInt()); app.setLock(info.value(UkuiSearch::ApplicationProperty::Property::Lock).toInt() == 1); app.setFavorite(info.value(UkuiSearch::ApplicationProperty::Property::Favorites).toInt()); app.setLaunchTimes(info.value(UkuiSearch::ApplicationProperty::Property::LaunchTimes).toInt()); app.setId(info.value(UkuiSearch::ApplicationProperty::Property::DesktopFilePath).toString()); app.setIcon(info.value(UkuiSearch::ApplicationProperty::Property::Icon).toString()); app.setName(info.value(UkuiSearch::ApplicationProperty::Property::LocalName).toString()); app.setCategory(info.value(UkuiSearch::ApplicationProperty::Property::Category).toString()); app.setFirstLetter(info.value(UkuiSearch::ApplicationProperty::Property::FirstLetterAll).toString()); app.setInsertTime(info.value(UkuiSearch::ApplicationProperty::Property::InsertTime).toString()); app.setLaunched(info.value(UkuiSearch::ApplicationProperty::Property::Launched).toInt()); } void AppDatabaseWorkerPrivate::setRemovable(DataEntity &app) { app.setRemovable(!GlobalSetting::instance()->isSystemApp(app.id())); } bool AppDatabaseWorkerPrivate::isFilterAccepted(const UkuiSearch::ApplicationPropertyMap &appInfo) const { QMapIterator iterator(filter); while (iterator.hasNext()) { iterator.next(); if (appInfo.value(iterator.key()) != iterator.value()) { return false; } // TODO: 根据数据的类型进行比较 // bool equals = false; // QVariant value = appInfo.value(iterator.key()); // value.userType(); } return true; } DataEntityVector AppDatabaseWorkerPrivate::getAllApps() { UkuiSearch::ApplicationInfoMap appInfos = appDatabase->getInfo(properties, filter); if (appInfos.isEmpty()) { return {}; } DataEntityVector apps; for (const auto &info : appInfos) { DataEntity app; addInfoToApp(info, app); setRemovable(app); apps.append(app); } return apps; } void AppDatabaseWorkerPrivate::onAppDatabaseAdded(const QStringList &infos) { if (infos.isEmpty()) { return; } DataEntityVector apps; for (const QString &appid : infos) { const UkuiSearch::ApplicationPropertyMap appInfo = appDatabase->getInfo(appid, properties); if (!isFilterAccepted(appInfo)) { continue; } DataEntity app; addInfoToApp(appInfo, app); setRemovable(app); apps.append(app); } if (apps.isEmpty()) { return; } Q_EMIT q->appAdded(apps); } void AppDatabaseWorkerPrivate::onAppDatabaseUpdate(const UkuiSearch::ApplicationInfoMap &infoMap) { if (infoMap.isEmpty()) { return; } QVector > > updates; QMapIterator iterator(infoMap); while (iterator.hasNext()) { iterator.next(); DataEntity app; QVector roles; QMapIterator it(iterator.value()); while (it.hasNext()) { it.next(); switch (it.key()) { case UkuiSearch::ApplicationProperty::LocalName: app.setName(it.value().toString()); roles.append(DataEntity::Name); break; case UkuiSearch::ApplicationProperty::FirstLetterAll: app.setFirstLetter(it.value().toString()); roles.append(DataEntity::FirstLetter); break; case UkuiSearch::ApplicationProperty::Icon: app.setIcon(it.value().toString()); roles.append(DataEntity::Icon); break; case UkuiSearch::ApplicationProperty::InsertTime: app.setInsertTime(it.value().toString()); roles.append(DataEntity::Icon); break; case UkuiSearch::ApplicationProperty::Category: app.setCategory(it.value().toString()); roles.append(DataEntity::Category); break; case UkuiSearch::ApplicationProperty::LaunchTimes: app.setLaunchTimes(it.value().toInt()); roles.append(DataEntity::LaunchTimes); break; case UkuiSearch::ApplicationProperty::Favorites: app.setFavorite(it.value().toInt()); roles.append(DataEntity::Favorite); break; case UkuiSearch::ApplicationProperty::Launched: app.setLaunched(it.value().toInt()); roles.append(DataEntity::IsLaunched); break; case UkuiSearch::ApplicationProperty::Top: app.setTop(it.value().toInt()); roles.append(DataEntity::Top); break; case UkuiSearch::ApplicationProperty::Lock: app.setLock(it.value().toBool()); roles.append(DataEntity::IsLocked); break; default: break; } } // 这个函数中,没有更新列不会发送信号 if (roles.isEmpty()) { continue; } app.setId(iterator.key()); updates.append({app, roles}); } if (!updates.isEmpty()) { Q_EMIT q->appUpdated(updates); } } void AppDatabaseWorkerPrivate::onAppDatabaseDeleted(const QStringList &infos) { if (infos.empty()) { return; } Q_EMIT q->appDeleted(infos); } void AppDatabaseWorkerPrivate::onAppDatabaseUpdateAll(const QStringList &infos) { if (infos.isEmpty()) { return; } QVector > > updates; DataEntityVector apps; for (const auto &appid : infos) { const UkuiSearch::ApplicationPropertyMap appInfo = appDatabase->getInfo(appid, properties); DataEntity app; addInfoToApp(appInfo, app); setRemovable(app); apps.append(app); updates.append({app, {}}); } Q_EMIT q->appUpdated(updates); } void AppDatabaseWorkerPrivate::setAppProperty(const QString &appid, const UkuiSearch::ApplicationPropertyMap &propertyMap) { QMapIterator iterator(propertyMap); while (iterator.hasNext()) { iterator.next(); setAppProperty(appid, iterator.key(), iterator.value()); } } void AppDatabaseWorkerPrivate::setAppProperty(const QString &appid, const UkuiSearch::ApplicationProperty::Property &property, const QVariant &value) { switch (property) { case UkuiSearch::ApplicationProperty::Favorites: appDatabase->setFavoritesOfApp(appid, value.toInt()); break; case UkuiSearch::ApplicationProperty::Launched: appDatabase->setAppLaunchedState(appid, value.toBool()); break; case UkuiSearch::ApplicationProperty::Top: appDatabase->setTopOfApp(appid, value.toInt()); break; default: break; } } bool AppDatabaseWorkerPrivate::getApp(const QString &appid, DataEntity &app) { if (appid.isEmpty()) { return false; } const UkuiSearch::ApplicationPropertyMap appInfo = appDatabase->getInfo(appid, properties); addInfoToApp(appInfo, app); setRemovable(app); return true; } // ====== AppDatabaseInterface ====== // // TODO: 多线程 AppDatabaseInterface::AppDatabaseInterface(QObject *parent) : QObject(parent), d(new AppDatabaseWorkerPrivate(this)) { } DataEntityVector AppDatabaseInterface::apps() const { return d->getAllApps(); } void AppDatabaseInterface::fixAppToTop(const QString &appid, int index) const { if (index < 0) { index = 0; } d->setAppProperty(appid, UkuiSearch::ApplicationProperty::Top, index); } void AppDatabaseInterface::fixAppToFavorite(const QString &appid, int index) const { if (index < 0) { index = 0; } d->setAppProperty(appid, UkuiSearch::ApplicationProperty::Favorites, index); } void AppDatabaseInterface::updateApLaunchedState(const QString &appid, bool state) const { d->setAppProperty(appid, UkuiSearch::ApplicationProperty::Launched, state); } bool AppDatabaseInterface::getApp(const QString &appid, DataEntity &app) const { return d->getApp(appid, app); } } // UkuiMenu #include "app-database-interface.moc" ukui-menu/src/libappdata/app-category-model.cpp0000664000175000017500000002111315160463353020510 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include "app-category-model.h" #include "basic-app-filter-model.h" #include namespace UkuiMenu { AppCategoryModel::AppCategoryModel(QObject *parent) : QSortFilterProxyModel(parent) { m_mainCategories.insert(QStringLiteral("AudioVideo"), {tr("AudioVideo"), 0}); m_mainCategories.insert(QStringLiteral("Audio"), {tr("Audio"), 1}); m_mainCategories.insert(QStringLiteral("Video"), {tr("Video"), 2}); m_mainCategories.insert(QStringLiteral("Development"), {tr("Development"), 3}); m_mainCategories.insert(QStringLiteral("Education"), {tr("Education"), 4}); m_mainCategories.insert(QStringLiteral("Game"), {tr("Game"), 5}); m_mainCategories.insert(QStringLiteral("Graphics"), {tr("Graphics"), 6}); m_mainCategories.insert(QStringLiteral("Network"), {tr("Network"), 7}); m_mainCategories.insert(QStringLiteral("Office"), {tr("Office"), 8}); m_mainCategories.insert(QStringLiteral("Science"), {tr("Science"), 9}); m_mainCategories.insert(QStringLiteral("Settings"), {tr("Settings"), 10}); m_mainCategories.insert(QStringLiteral("System"), {tr("System"), 11}); m_mainCategories.insert(QStringLiteral("Utility"), {tr("Utility"), 12}); m_mainCategories.insert(QStringLiteral("Other"), {tr("Other"), 13}); m_additionalCategories.insert(QStringLiteral("AudioVideo"), {"Midi", "Mixer", "Sequencer", "Tuner", "TV", "AudioVideoEditing", "Player", "Recorder", "DiscBurning", "Music"}); m_additionalCategories.insert(QStringLiteral("Development"), {"Building", "Debugger", "IDE", "GUIDesigner", "Profiling", "RevisionControl", "Translation"}); m_additionalCategories.insert(QStringLiteral("Education"), {"Art", "Construction", "Languages", "ArtificialIntelligence", "Astronomy", "Biology", "Chemistry", "ComputerScience", "DataVisualization", "Economy", "Electricity", "Geography", "Geology", "Geoscience", "History", "Humanities", "ImageProcessing", "Literature", "Maps", "Math", "NumericalAnalysis", "MedicalSoftware", "Physics", "Robotics", "Spirituality", "Sports", "ParallelComputing"}); m_additionalCategories.insert(QStringLiteral("Game"), {"ActionGame", "AdventureGame", "ArcadeGame", "BoardGame", "BlocksGame", "CardGame", "KidsGame", "LogicGame", "RolePlaying", "Shooter", "Simulation", "SportsGame", "StrategyGame", "LauncherStore", "GameTool"}); m_additionalCategories.insert(QStringLiteral("Graphics"), {"2DGraphics", "VectorGraphics", "RasterGraphics", "3DGraphics", "Scanning", "OCR", "Photography", "Publishing", "Viewer"}); m_additionalCategories.insert(QStringLiteral("Network"), {"Dialup", "InstantMessaging", "Chat", "IRCClient", "Feed", "FileTransfer", "HamRadio", "News", "P2P", "RemoteAccess", "Telephony", "VideoConference", "WebBrowser", "WebDevelopment"}); m_additionalCategories.insert(QStringLiteral("Office"), {"Calendar", "ContactManagement", "Database", "Dictionary", "Chart", "Email", "Finance", "FlowChart", "PDA", "ProjectManagement", "Presentation", "Spreadsheet", "WordProcessor"}); m_additionalCategories.insert(QStringLiteral("Settings"), {"DesktopSettings", "HardwareSettings", "Printing", "PackageManager", "Security", "Accessibility"}); m_additionalCategories.insert(QStringLiteral("System"), {"Emulator", "FileManager", "TerminalEmulator", "Filesystem", "Monitor"}); m_additionalCategories.insert(QStringLiteral("Utility"), {"TextTools", "TelephonyTools", "Archiving", "Compression", "FileTools", "Calculator", "Clock", "TextEditor"}); QSortFilterProxyModel::setSourceModel(BasicAppFilterModel::instance()); QSortFilterProxyModel::sort(0); } AppCategoryModel::Mode AppCategoryModel::mode() const { return m_mode; } void AppCategoryModel::setMode(AppCategoryModel::Mode mode) { if (m_mode == mode) { return; } m_mode = mode; // TODO: 刷新 invalidateFilter(); invalidate(); } QVariant AppCategoryModel::data(const QModelIndex &index, int role) const { if (!checkIndex(index, CheckIndexOption::IndexIsValid)) { return {}; } if (role == DataEntity::Group) { if (m_mode == All) { return ""; } else if (m_mode == FirstLatter) { return AppCategoryModel::getFirstLatterUpper(QSortFilterProxyModel::data(index, DataEntity::FirstLetter).toString()); } else { return getCategoryName(QSortFilterProxyModel::data(index, DataEntity::Category).toString()); } } return QSortFilterProxyModel::data(index, role); } bool AppCategoryModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (m_mode == All) { return false; } return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } bool AppCategoryModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { int result = 0; if (m_mode == Category) { // 按功能分类进行排序 int leftIndex = getCategoryIndex(source_left.data(DataEntity::Category).toString()); int rightIndex = getCategoryIndex(source_right.data(DataEntity::Category).toString()); result = (leftIndex < rightIndex) ? -1 : (leftIndex > rightIndex) ? 1 : 0; } // 功能分类相同时,也按字母排序 if ((result == 0) || (m_mode == FirstLatter)) { QString leftKey = source_left.data(DataEntity::FirstLetter).toString(); QString rightKey = source_right.data(DataEntity::FirstLetter).toString(); result = leftKey.compare(rightKey, Qt::CaseInsensitive); } return result < 0; } inline QString AppCategoryModel::getCategoryName(const QString &categories) const { return m_mainCategories[getCategoryKey(categories)].first; } inline int AppCategoryModel::getCategoryIndex(const QString &categories) const { return m_mainCategories[getCategoryKey(categories)].second; } QString AppCategoryModel::getCategoryKey(const QString &categories) const { QStringList list; // 某些应用使用空格(" ")进行分隔,此处做一下处理 if (categories.contains( QStringLiteral(";"))) { list = categories.split(QStringLiteral(";")); } else { list = categories.split(QStringLiteral(" ")); } for (const auto &key : list) { if (m_mainCategories.contains(key)) { return key; } } QMap::const_iterator it; for (const auto &key : list) { for (const auto & pair : m_additionalCategories) { for (it = m_additionalCategories.constBegin(); it != m_additionalCategories.constEnd(); ++it) { if (it.value().contains(key)) { return it.key(); } } } } return QStringLiteral("Other"); } QString AppCategoryModel::getFirstLatterUpper(const QString &pinyinName) { if (!pinyinName.isEmpty()) { QChar first = pinyinName.at(0).toUpper(); if (first >= 'A' && first <= 'Z') { return first; } else if (first >= '0' && first <= '9') { return QStringLiteral("#"); } } return QStringLiteral("&"); } } // UkuiMenu ukui-menu/src/libappdata/recently-installed-model.h0000664000175000017500000000345415160463365021377 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #ifndef UKUI_MENU_RECENTLY_INSTALLED_MODEL_H #define UKUI_MENU_RECENTLY_INSTALLED_MODEL_H #include "app-list-plugin.h" #include "app-category-model.h" #include "data-entity.h" #include class QTimer; namespace UkuiMenu { class RecentlyInstalledModel : public QSortFilterProxyModel { Q_OBJECT public: explicit RecentlyInstalledModel(QObject *parent = nullptr); bool event(QEvent *event) override; QVariant data(const QModelIndex &index, int role) const override; void setMode(AppCategoryModel::Mode mode); static bool isRecentInstall(const DataEntity &data); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; private: QTimer *m_timer {nullptr}; AppCategoryModel::Mode m_mode = AppCategoryModel::All; qreal getDegree(const QModelIndex &index) const; }; } // UkuiMenu //Q_DECLARE_METATYPE(UkuiMenu::RecentlyInstalledModel*) #endif //UKUI_MENU_RECENTLY_INSTALLED_MODEL_H ukui-menu/src/libappdata/combined-list-model.cpp0000664000175000017500000001530015160463365020652 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include "combined-list-model.h" #include namespace UkuiMenu { // CombinedListModel CombinedListModel::CombinedListModel(QObject *parent) : QAbstractItemModel(parent) { } QModelIndex CombinedListModel::index(int row, int column, const QModelIndex &parent) const { if (column != 0 || row < 0 || row >= rowCount(QModelIndex())) { return {}; } int start = 0; for (const auto &pair : m_subModels) { int rc = pair.second; if (row < (start + rc)) { return createIndex(row, 0, pair.first); } else { start += rc; } } return {}; } QModelIndex CombinedListModel::parent(const QModelIndex &child) const { return {}; } int CombinedListModel::rowCount(const QModelIndex &parent) const { int count = 0; for (const auto &pair : m_subModels) { count += pair.second; } return count; } int CombinedListModel::columnCount(const QModelIndex &parent) const { return 1; } QHash CombinedListModel::roleNames() const { return QAbstractItemModel::roleNames(); } QModelIndex CombinedListModel::mapToSource(const QModelIndex &proxyIndex) const { if (!proxyIndex.isValid()) { return {}; } auto sourceModel = static_cast(proxyIndex.internalPointer()); if (!sourceModel) { return {}; } int offset = offsetOfSubModel(sourceModel); if (offset < 0) { return {}; } return sourceModel->index(proxyIndex.row() - offset, 0); } QModelIndex CombinedListModel::mapFromSource(const QModelIndex &sourceIndex) const { if (!sourceIndex.isValid()) { return {}; } int offset = offsetOfSubModel(sourceIndex.model()); if (offset < 0) { return {}; } return createIndex(offset + sourceIndex.row(), 0); } int CombinedListModel::offsetOfSubModel(const QAbstractItemModel *subModel) const { int offset = 0; for (const auto &pair : m_subModels) { if (pair.first == subModel) { return offset; } else { offset += pair.second; } } return -1; } QVariant CombinedListModel::data(const QModelIndex &proxyIndex, int role) const { if (!checkIndex(proxyIndex, CheckIndexOption::IndexIsValid)) { return {}; } QModelIndex sourceIndex = mapToSource(proxyIndex); if (!sourceIndex.isValid()) { return {}; } return sourceIndex.data(role); } int CombinedListModel::subModelCount() const { return m_subModels.size(); } const QAbstractItemModel *CombinedListModel::subModelAt(int index) const { if (index < 0 || index >= m_subModels.size()) { return nullptr; } return m_subModels.at(index).first; } void CombinedListModel::insertSubModel(QAbstractItemModel *subModel, int index) { if (!subModel || offsetOfSubModel(subModel) >= 0) { return; } beginResetModel(); if (index < 0 || index > m_subModels.size()) { m_subModels.append({subModel, subModel->rowCount()}); } else { m_subModels.insert(index, {subModel, subModel->rowCount()}); } // 在删除前通知上层model进行处理 connect(subModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, [subModel, this] (const QModelIndex &parent, int first, int last) { int offset = offsetOfSubModel(subModel); if (offset >= 0) { beginRemoveRows(mapFromSource(parent), offset + first, offset + last); for (auto &pair : m_subModels) { if (pair.first == subModel) { // pair.second = subModel->rowCount(); pair.second = pair.second - (last - first + 1); } } endRemoveRows(); } }); connect(subModel, &QAbstractItemModel::rowsInserted, this, [subModel, this] (const QModelIndex &parent, int first, int last) { int offset = offsetOfSubModel(subModel); if (offset >= 0) { beginInsertRows(mapFromSource(parent), offset + first, offset + last); for (auto &pair : m_subModels) { if (pair.first == subModel) { // pair.second = subModel->rowCount(); pair.second = pair.second + (last - first + 1); } } endInsertRows(); } }); connect(subModel, &QAbstractItemModel::dataChanged, this, [subModel, this] (const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { Q_EMIT dataChanged( mapFromSource(topLeft), mapFromSource(bottomRight), roles); }); connect(subModel, &QAbstractItemModel::modelReset, this, [subModel, this] { beginResetModel(); for (auto &pair : m_subModels) { if (pair.first == subModel) { pair.second = pair.second; } } endResetModel(); }); connect(subModel, &QAbstractItemModel::layoutAboutToBeChanged, this, [this] { emit layoutAboutToBeChanged(); }); connect(subModel, &QAbstractItemModel::layoutChanged, this, [this] { emit layoutChanged(); }); endResetModel(); } void CombinedListModel::removeSubModel(int index) { if (index < 0 || index >= m_subModels.size()) { return; } QPair pair = m_subModels[index]; int offset = offsetOfSubModel(pair.first); beginRemoveRows(QModelIndex(), offset, offset + pair.second); pair = m_subModels.takeAt(index); disconnect(pair.first, nullptr, this, nullptr); endRemoveRows(); } void CombinedListModel::removeSubModel(QAbstractItemModel *subModel) { removeSubModel(indexOfSubModel(subModel)); } int CombinedListModel::indexOfSubModel(QAbstractItemModel *subModel) { for (int i = 0; i < m_subModels.size(); ++i) { if (m_subModels.at(i).first == subModel) { return i; } } return -1; } } // UkuiMenu ukui-menu/src/libappdata/app-database-interface.h0000664000175000017500000000535315160463353020754 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #ifndef UKUI_MENU_APP_DATABASE_INTERFACE_H #define UKUI_MENU_APP_DATABASE_INTERFACE_H #include #include #include "data-entity.h" namespace UkuiMenu { typedef QVector DataEntityVector; /** * 封装与应用数据库的链接 */ class AppDatabaseWorkerPrivate; class AppDatabaseInterface : public QObject { Q_OBJECT public: explicit AppDatabaseInterface(QObject *parent = nullptr); /** * 从数据库获取全部应用 * @return */ DataEntityVector apps() const; /** * 从数据库获取单个应用 * @param appid 应用id * @param app 获取应用 */ bool getApp(const QString &appid, DataEntity &app) const; /** * 置顶应用 * @param appid 应用id * @param index 置顶后的位置,小于或等于0将会取消置顶 */ void fixAppToTop(const QString &appid, int index) const; /** * 收藏应用 * @param appid 应用id * @param index 收藏后的位置,小于或等于0将会取消收藏 */ void fixAppToFavorite(const QString &appid, int index) const; /** * 设置应用是否已经被启动过 * @param state 状态值,true已被启动,false未被启动 */ void updateApLaunchedState(const QString &appid, bool state = true) const; Q_SIGNALS: /** * 新增应用信号 * @param apps 新增加的应用 */ void appAdded(const UkuiMenu::DataEntityVector &apps); /** * 更新的应用及其属性列表 * @param updates */ void appUpdated(const QVector > > &updates); /** * 应用被删除的信号 * @param apps 返回被删除应用的id列表 */ void appDeleted(const QStringList &apps); /** * 应用数据库打开失败信号 */ void appDatabaseOpenFailed(); private: AppDatabaseWorkerPrivate *d {nullptr}; }; } // UkuiMenu Q_DECLARE_METATYPE(UkuiMenu::DataEntityVector) #endif //UKUI_MENU_APP_DATABASE_INTERFACE_H ukui-menu/src/libappdata/combined-list-model.h0000664000175000017500000000456115160463353020323 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #ifndef UKUI_MENU_COMBINED_LIST_MODEL_H #define UKUI_MENU_COMBINED_LIST_MODEL_H #include #include #include namespace UkuiMenu { /** * 由多个子model组合成的model * * 通过index设置或获取子model * CombinedListModel */ class CombinedListModel : public QAbstractItemModel { Q_OBJECT public: explicit CombinedListModel(QObject *parent = nullptr); QModelIndex index(int row, int column, const QModelIndex &parent) const override; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QModelIndex mapToSource(const QModelIndex &proxyIndex) const; QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; QHash roleNames() const override; QVariant data(const QModelIndex &proxyIndex, int role) const override; // 获取subModel的数量 int subModelCount() const; // 获取index位置和对应的subModel const QAbstractItemModel *subModelAt(int index) const; int indexOfSubModel(QAbstractItemModel *subModel); /** * 插入一个subModel * @param subModel * @param index 插入的位置,默认放到最后 */ void insertSubModel(QAbstractItemModel *subModel, int index = -1); void removeSubModel(int index); void removeSubModel(QAbstractItemModel *subModel); private: int offsetOfSubModel(const QAbstractItemModel *subModel) const; private: QVector > m_subModels; }; } // UkuiMenu #endif //UKUI_MENU_COMBINED_LIST_MODEL_H ukui-menu/src/libappdata/app-category-model.h0000664000175000017500000000400415160463353020155 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #ifndef UKUI_MENU_APP_CATEGORY_MODEL_H #define UKUI_MENU_APP_CATEGORY_MODEL_H #include "app-list-plugin.h" #include #include namespace UkuiMenu { class AppCategoryModel : public QSortFilterProxyModel { Q_OBJECT public: enum Mode { All, /**> 全部应用排序 */ FirstLatter, /**> 按首字母排序 */ Category /**> 按类型排序 */ }; Q_ENUM(Mode) explicit AppCategoryModel(QObject *parent = nullptr); QVariant data(const QModelIndex &index, int role) const override; Mode mode() const; void setMode(Mode mode); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; private: static QString getFirstLatterUpper(const QString &pinyinName) ; inline QString getCategoryName(const QString &categories) const; inline int getCategoryIndex(const QString &categories) const; QString getCategoryKey(const QString &categories) const; private: Mode m_mode { All }; QMap > m_mainCategories; QMap m_additionalCategories; }; } // UkuiMenu #endif //UKUI_MENU_APP_CATEGORY_MODEL_H ukui-menu/src/libappdata/app-category-plugin.h0000664000175000017500000000330215160463353020353 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #ifndef UKUI_MENU_APP_CATEGORY_PLUGIN_H #define UKUI_MENU_APP_CATEGORY_PLUGIN_H #include "app-list-plugin.h" namespace UkuiMenu { class CombinedListModel; class AppCategoryModel; class RecentlyInstalledModel; class AppCategoryPlugin : public AppListPluginInterface { Q_OBJECT public: explicit AppCategoryPlugin(QObject *parent = nullptr); AppListPluginGroup::Group group() override; QString name() override; QString title() override; QList actions() override; QAbstractItemModel *dataModel() override; LabelBottle *labelBottle() override; private: void setTitle(const QString &title); void updateLabelBottle(); private: QString m_title; QList m_actions; LabelBottle *m_labelBottle {nullptr}; AppCategoryModel *m_categoryModel {nullptr}; RecentlyInstalledModel *m_recentlyModel {nullptr}; CombinedListModel *m_dataModel {nullptr}; }; } // UkuiMenu #endif //UKUI_MENU_APP_CATEGORY_PLUGIN_H ukui-menu/src/libappdata/app-page-backend.cpp0000664000175000017500000000424015160463353020100 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include "app-page-backend.h" #include "app-list-model.h" #include "app-category-plugin.h" #include "app-search-plugin.h" #include namespace UkuiMenu { AppPageBackend *AppPageBackend::instance() { static AppPageBackend backend; return &backend; } AppPageBackend::AppPageBackend(QObject *parent) : QObject(parent), m_appModel(new AppListModel(this)) { auto searchPlugin = new AppSearchPlugin(this); auto categoryPlugin = new AppCategoryPlugin(this); m_plugins.insert(searchPlugin->group(), searchPlugin); m_plugins.insert(categoryPlugin->group(), categoryPlugin); switchGroup(); } AppListModel *AppPageBackend::appModel() const { return m_appModel; } void AppPageBackend::startSearch(const QString &keyword) { if (m_group != AppListPluginGroup::Search) { return; } auto plugin = m_plugins.value(m_group, nullptr); if (plugin) { plugin->search(keyword); } } AppListPluginGroup::Group AppPageBackend::group() const { return AppListPluginGroup::Display; } void AppPageBackend::setGroup(AppListPluginGroup::Group group) { if (m_group == group) { return; } m_group = group; switchGroup(); Q_EMIT groupChanged(); } void AppPageBackend::switchGroup() { const auto plugin = m_plugins.value(m_group, nullptr); if (plugin) { m_appModel->installPlugin(plugin); } else { m_appModel->unInstallPlugin(); } } } // UkuiMenu ukui-menu/src/libappdata/app-list-plugin.h0000664000175000017500000000574315160463353017524 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #ifndef UKUI_MENU_APP_LIST_PLUGIN_H #define UKUI_MENU_APP_LIST_PLUGIN_H #include #include class QAction; class QAbstractItemModel; namespace UkuiMenu { class LabelItem { Q_GADGET Q_PROPERTY(UkuiMenu::LabelItem::Type type READ type) Q_PROPERTY(QString label READ labelName) Q_PROPERTY(QString display READ displayName) public: enum Type { Text, Icon }; Q_ENUM(Type) explicit LabelItem(QString labelName = "", QString displayName = "", Type type = Text); Type type() const; QString labelName() const; QString displayName() const; private: Type m_type; QString m_labelName; QString m_displayName; }; class LabelBottle : public QObject { Q_OBJECT Q_PROPERTY(int column READ column NOTIFY columnChanged) Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged) Q_PROPERTY(QList labels READ labels NOTIFY labelsChanged) public: explicit LabelBottle(QObject *parent = nullptr); int column() const; bool visible() const; QList labels() const; void setColumn(int column); void setVisible(bool visible); void setLabels(const QList &labels); Q_SIGNALS: void columnChanged(); void visibleChanged(); void labelsChanged(); private: int m_column {2}; bool m_visible {false}; QList m_labels; }; class AppListPluginGroup { Q_GADGET public: enum Group { Display, /**> 应用展示模式 */ Search /**> 应用搜索模式 */ }; Q_ENUM(Group) }; class AppListPluginInterface : public QObject { Q_OBJECT public: explicit AppListPluginInterface(QObject *parent = nullptr); virtual AppListPluginGroup::Group group() = 0; virtual QString name() = 0; virtual QString title() = 0; virtual QList actions() = 0; virtual QAbstractItemModel *dataModel() = 0; virtual void search(const QString &keyword); virtual LabelBottle *labelBottle(); Q_SIGNALS: void titleChanged(); void labelChanged(); }; } // UkuiMenu Q_DECLARE_METATYPE(UkuiMenu::AppListPluginGroup::Group) Q_DECLARE_METATYPE(UkuiMenu::LabelItem) Q_DECLARE_METATYPE(UkuiMenu::LabelBottle*) #endif //UKUI_MENU_APP_LIST_PLUGIN_H ukui-menu/src/libappdata/app-category-plugin.cpp0000664000175000017500000001363215160463365020720 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include "app-category-plugin.h" #include "combined-list-model.h" #include "app-category-model.h" #include "recently-installed-model.h" #include "data-entity.h" #include "event-track.h" #include #include namespace UkuiMenu { AppCategoryPlugin::AppCategoryPlugin(QObject *parent) : AppListPluginInterface(parent) , m_dataModel(new CombinedListModel(this)), m_labelBottle(new LabelBottle(this)) { m_categoryModel = new AppCategoryModel(this); m_recentlyModel = new RecentlyInstalledModel(this); m_dataModel->insertSubModel(m_recentlyModel); m_dataModel->insertSubModel(m_categoryModel); auto allAction = new QAction(QIcon::fromTheme("view-grid-symbolic"), tr("All Applications"), this); auto firstLatterAction = new QAction(QIcon::fromTheme("ukui-capslock-symbolic"), tr("Letter Sort"), this); auto categoryAction = new QAction(QIcon::fromTheme("applications-utilities-symbolic"), tr("Category"), this); allAction->setCheckable(true); firstLatterAction->setCheckable(true); categoryAction->setCheckable(true); connect(allAction, &QAction::triggered, this, [=] { m_categoryModel->setMode(AppCategoryModel::All); m_recentlyModel->setMode(AppCategoryModel::All); allAction->setChecked(true); firstLatterAction->setChecked(false); categoryAction->setChecked(false); setTitle(allAction->text()); updateLabelBottle(); m_labelBottle->setVisible(false); QMap map; map.insert(QStringLiteral("viewName"), QStringLiteral("all")); EventTrack::instance()->sendClickEvent("switch_app_view", "AppView", map); }); connect(firstLatterAction, &QAction::triggered, this, [=] { m_categoryModel->setMode(AppCategoryModel::FirstLatter); m_recentlyModel->setMode(AppCategoryModel::FirstLatter); allAction->setChecked(false); firstLatterAction->setChecked(true); categoryAction->setChecked(false); setTitle(firstLatterAction->text()); updateLabelBottle(); m_labelBottle->setVisible(true); QMap map; map.insert(QStringLiteral("viewName"), QStringLiteral("letter")); EventTrack::instance()->sendClickEvent("switch_app_view", "AppView", map); }); connect(categoryAction, &QAction::triggered, this, [=] { m_categoryModel->setMode(AppCategoryModel::Category); m_recentlyModel->setMode(AppCategoryModel::Category); allAction->setChecked(false); firstLatterAction->setChecked(false); categoryAction->setChecked(true); setTitle(categoryAction->text()); updateLabelBottle(); m_labelBottle->setVisible(true); QMap map; map.insert(QStringLiteral("viewName"), QStringLiteral("category")); EventTrack::instance()->sendClickEvent("switch_app_view", "AppView", map); }); connect(m_categoryModel, &AppCategoryModel::rowsInserted, this, [=] { Q_EMIT labelChanged(); }); connect(m_categoryModel, &AppCategoryModel::rowsRemoved, this, [=] { Q_EMIT labelChanged(); }); connect(m_recentlyModel, &RecentlyInstalledModel::rowsInserted, this, [=] { Q_EMIT labelChanged(); }); connect(m_recentlyModel, &RecentlyInstalledModel::rowsRemoved, this, [=] { Q_EMIT labelChanged(); }); m_actions.append(allAction); m_actions.append(firstLatterAction); m_actions.append(categoryAction); allAction->setChecked(true); setTitle(allAction->text()); m_labelBottle->setColumn(0); m_labelBottle->setVisible(false); } QString AppCategoryPlugin::name() { return QStringLiteral("AppCategoryPlugin"); } QList AppCategoryPlugin::actions() { return m_actions; } QAbstractItemModel *AppCategoryPlugin::dataModel() { return m_dataModel; } AppListPluginGroup::Group AppCategoryPlugin::group() { return AppListPluginGroup::Display; } QString AppCategoryPlugin::title() { return m_title; } void AppCategoryPlugin::setTitle(const QString &title) { if (title == m_title) { return; } m_title = title; Q_EMIT titleChanged(); } LabelBottle *AppCategoryPlugin::labelBottle() { updateLabelBottle(); return m_labelBottle; } void AppCategoryPlugin::updateLabelBottle() { QList labels; if (m_recentlyModel->rowCount() > 0) { if (m_categoryModel->mode() != AppCategoryModel::All) { labels << LabelItem(tr("Recently Installed"), "document-open-recent-symbolic", LabelItem::Icon); } } QHash groups; int rowCount = m_categoryModel->rowCount(); for (int row = 0; row < rowCount; ++row) { QString group = m_categoryModel->index(row, 0, QModelIndex()).data(DataEntity::Group).toString(); if (groups.contains(group)) { continue; } groups.insert(group, 0); labels.append(LabelItem(group, group)); } m_labelBottle->setLabels(labels); if (m_categoryModel->mode() == AppCategoryModel::All) { m_labelBottle->setColumn(0); } else { m_labelBottle->setColumn(m_categoryModel->mode() == AppCategoryModel::FirstLatter ? 5 : 2); } } } // UkuiMenu ukui-menu/src/libappdata/app-page-backend.h0000664000175000017500000000431415160463353017547 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #ifndef UKUI_MENU_APP_PAGE_BACKEND_H #define UKUI_MENU_APP_PAGE_BACKEND_H #include #include class QAbstractItemModel; class QSortFilterProxyModel; #include "app-list-plugin.h" namespace UkuiMenu { class AppListModel; /** * 此类管理开始菜单的主体功能和接口 * * 提供:应用搜索,应用分类模式的切换功能 * * * AppPage * | * AppListHeader * AppList * / \ * Search Display * | \ * (Model Title Actions) (Model Title Actions) * */ class AppPageBackend : public QObject { Q_OBJECT Q_PROPERTY(QAbstractItemModel *appModel READ appModel NOTIFY appModelChanged) Q_PROPERTY(UkuiMenu::AppListPluginGroup::Group group READ group WRITE setGroup NOTIFY groupChanged) public: static AppPageBackend *instance(); // 开始菜单主要功能,显示应用列表 AppListModel *appModel() const; Q_INVOKABLE void startSearch(const QString &keyword); AppListPluginGroup::Group group() const; void setGroup(AppListPluginGroup::Group group); Q_SIGNALS: void appModelChanged(); void groupChanged(); private: explicit AppPageBackend(QObject *parent = nullptr); void switchGroup(); private: AppListPluginGroup::Group m_group {AppListPluginGroup::Display}; AppListModel *m_appModel {nullptr}; QMap m_plugins; }; } // UkuiMenu #endif //UKUI_MENU_APP_PAGE_BACKEND_H ukui-menu/src/libappdata/basic-app-filter-model.cpp0000664000175000017500000000514715160463353021250 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: youdiansaodongxi * */ #include "basic-app-filter-model.h" #include "basic-app-model.h" #include "security-function-control.h" #include namespace UkuiMenu { BasicAppFilterModel *BasicAppFilterModel::instance() { static BasicAppFilterModel model; return &model; } const AppDatabaseInterface *BasicAppFilterModel::databaseInterface() const { return m_sourceModel->databaseInterface(); } DataEntity BasicAppFilterModel::appOfIndex(int row) const { return m_sourceModel->appOfIndex(mapToSource(index(row, 0)).row()); } int BasicAppFilterModel::indexOfApp(const QString &appid) const { return mapFromSource(m_sourceModel->index(m_sourceModel->indexOfApp(appid))).row(); } bool BasicAppFilterModel::getAppById(const QString &appid, DataEntity &app) const { return m_sourceModel->getAppById(appid, app); } QVariant BasicAppFilterModel::data(const QModelIndex &index, int role) const { return sourceModel()->data(mapToSource(index), role); } bool BasicAppFilterModel::setData(const QModelIndex &index, const QVariant &value, int role) { return m_sourceModel->setData(mapToSource(index), value, role); } bool BasicAppFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { QModelIndex sourceIndex = sourceModel()->index(source_row, 0, source_parent); return SecurityFunctionControl::instance()->canAppDisplay(sourceIndex.data(DataEntity::Id).toString()); } BasicAppFilterModel::BasicAppFilterModel(QObject *parent) : QSortFilterProxyModel(parent) { setSourceModel(BasicAppModel::instance()); m_sourceModel = BasicAppModel::instance(); connect(SecurityFunctionControl::instance(), &SecurityFunctionControl::appWhiteListChanged, this, &BasicAppFilterModel::invalidateFilter); connect(SecurityFunctionControl::instance(), &SecurityFunctionControl::appBlackListChanged, this, &BasicAppFilterModel::invalidateFilter); } } // UkuiMenu ukui-menu/src/libappdata/app-search-plugin.h0000664000175000017500000000310315160463353020002 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #ifndef UKUI_MENU_APP_SEARCH_PLUGIN_H #define UKUI_MENU_APP_SEARCH_PLUGIN_H #include "app-list-plugin.h" namespace UkuiMenu { class AppSearchPluginPrivate; class AppSearchModel; /** * @class AppSearchPlugin * 应用搜索插件,调用搜索接口进行应用搜索 */ class AppSearchPlugin : public AppListPluginInterface { Q_OBJECT public: explicit AppSearchPlugin(QObject *parent = nullptr); ~AppSearchPlugin() override; AppListPluginGroup::Group group() override; QString name() override; QString title() override; QList actions() override; QAbstractItemModel *dataModel() override; void search(const QString &keyword) override; private: AppSearchModel *m_model {nullptr}; AppSearchPluginPrivate * m_searchPluginPrivate {nullptr}; }; } // UkuiMenu #endif //UKUI_MENU_APP_SEARCH_PLUGIN_H ukui-menu/src/libappdata/app-list-plugin.cpp0000664000175000017500000000457515160463353020061 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include "app-list-plugin.h" #include #include namespace UkuiMenu { // ====== AppListPluginInterface ====== // AppListPluginInterface::AppListPluginInterface(QObject *parent) : QObject(parent) { qRegisterMetaType("LabelItem"); qRegisterMetaType("LabelBottle*"); } void AppListPluginInterface::search(const QString &keyword) { Q_UNUSED(keyword) } LabelBottle *AppListPluginInterface::labelBottle() { return nullptr; } // ====== LabelItem ====== // LabelItem::LabelItem(QString labelName, QString displayName, LabelItem::Type type) : m_type(type), m_labelName(std::move(labelName)), m_displayName(std::move(displayName)) { } QString LabelItem::labelName() const { return m_labelName; } LabelItem::Type LabelItem::type() const { return m_type; } QString LabelItem::displayName() const { return m_displayName; } // ====== LabelBottle ====== // LabelBottle::LabelBottle(QObject *parent) : QObject(parent) { } int LabelBottle::column() const { return m_column; } bool LabelBottle::visible() const { return m_visible; } QList LabelBottle::labels() const { return m_labels; } void LabelBottle::setColumn(int column) { if (m_column == column) { return; } m_column = column; Q_EMIT columnChanged(); } void LabelBottle::setVisible(bool visible) { if (m_visible == visible) { return; } m_visible = visible; Q_EMIT visibleChanged(); } void LabelBottle::setLabels(const QList &labels) { m_labels.clear(); m_labels.append(labels); Q_EMIT labelsChanged(); } } // UkuiMenu ukui-menu/src/libappdata/app-search-plugin.cpp0000664000175000017500000001750315160463353020346 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include "app-search-plugin.h" #include "data-entity.h" #include "basic-app-filter-model.h" #include #include #include #include #include #include "recently-installed-model.h" namespace UkuiMenu { // ====== AppSearchPluginPrivate ====== class AppSearchPluginPrivate : public QThread { Q_OBJECT public: explicit AppSearchPluginPrivate(QObject *parent = nullptr); Q_SIGNALS: void searchedOne(const QString &id); public Q_SLOTS: void startSearch(const QString &keyword); void stopSearch(); protected: void run() override; private: size_t m_searchId{0}; QTimer *m_timer{nullptr}; UkuiSearch::UkuiSearchTask *m_appSearchTask {nullptr}; UkuiSearch::DataQueue *m_dataQueue{nullptr}; }; AppSearchPluginPrivate::AppSearchPluginPrivate(QObject *parent) : QThread(parent), m_appSearchTask(new UkuiSearch::UkuiSearchTask(this)) { m_dataQueue = m_appSearchTask->init(); m_appSearchTask->initSearchPlugin(UkuiSearch::SearchProperty::SearchType::Application); m_appSearchTask->setSearchOnlineApps(false); UkuiSearch::SearchResultProperties searchResultProperties; searchResultProperties << UkuiSearch::SearchProperty::SearchResultProperty::ApplicationDesktopPath; m_appSearchTask->setResultProperties(UkuiSearch::SearchProperty::SearchType::Application, searchResultProperties); m_timer = new QTimer; m_timer->setInterval(3000); m_timer->moveToThread(this); } void AppSearchPluginPrivate::startSearch(const QString &keyword) { if (!this->isRunning()) { this->start(); } m_appSearchTask->clearKeyWords(); m_appSearchTask->addKeyword(keyword); m_searchId = m_appSearchTask->startSearch(UkuiSearch::SearchProperty::SearchType::Application); } void AppSearchPluginPrivate::stopSearch() { m_appSearchTask->stop(); this->requestInterruption(); } void AppSearchPluginPrivate::run() { while (!isInterruptionRequested()) { UkuiSearch::ResultItem result = m_dataQueue->tryDequeue(); if(result.getSearchId() == 0 && result.getItemKey().isEmpty() && result.getAllValue().isEmpty()) { if(!m_timer->isActive()) { // 超时退出 m_timer->start(); } msleep(100); } else { m_timer->stop(); if (result.getSearchId() == m_searchId) { QString id = result.getValue(UkuiSearch::SearchProperty::ApplicationDesktopPath).toString(); Q_EMIT this->searchedOne(id); } } if(m_timer->isActive() && m_timer->remainingTime() < 0.01 && m_dataQueue->isEmpty()) { this->requestInterruption(); } } } // ====== AppSearchModel ====== // class AppSearchModel : public QAbstractListModel { Q_OBJECT public: explicit AppSearchModel(QObject *parent = nullptr); int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; void appendApp(const QString &id); void clear(); private: QVector m_apps; private Q_SLOTS: void onAppRemoved(const QModelIndex &parent, int first, int last); void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles = QVector()); }; AppSearchModel::AppSearchModel(QObject *parent) : QAbstractListModel(parent) { connect(BasicAppFilterModel::instance(), &BasicAppFilterModel::rowsAboutToBeRemoved, this, &AppSearchModel::onAppRemoved); connect(BasicAppFilterModel::instance(), &BasicAppFilterModel::dataChanged, this, &AppSearchModel::onDataChanged); } int AppSearchModel::rowCount(const QModelIndex &parent) const { return m_apps.size(); } int AppSearchModel::columnCount(const QModelIndex &parent) const { return 1; } QVariant AppSearchModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_apps.size()) { return {}; } if (index.row() < m_apps.count()) { if (role == DataEntity::RecentInstall) { return RecentlyInstalledModel::isRecentInstall(m_apps.at(index.row()).data(DataEntity::Entity).value()); } return m_apps.at(index.row()).data(role); } return QVariant(); } void AppSearchModel::appendApp(const QString &id) { int index = BasicAppFilterModel::instance()->indexOfApp(id); if (index < 0) return; QPersistentModelIndex modelIndex(BasicAppFilterModel::instance()->index(index, 0, QModelIndex())); if (m_apps.contains(modelIndex) || !modelIndex.isValid()) { return; } beginInsertRows(QModelIndex(), m_apps.count(), m_apps.count()); m_apps.append(modelIndex); endInsertRows(); } void AppSearchModel::clear() { beginResetModel(); m_apps.clear(); endResetModel(); } void AppSearchModel::onAppRemoved(const QModelIndex &parent, int first, int last) { for(int row = first; row <= last; ++row) { QModelIndex sourceIndex = BasicAppFilterModel::instance()->index(row, 0, {}); QPersistentModelIndex index(sourceIndex); if (!m_apps.contains(index)) { return; } beginRemoveRows(QModelIndex(), m_apps.indexOf(index), m_apps.indexOf(index)); m_apps.removeOne(index); endRemoveRows(); } } void AppSearchModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { QModelIndex sourceIndex; QPersistentModelIndex persistentModelIndex; int indexRow; for (int row = topLeft.row(); row <= bottomRight.row(); row ++) { sourceIndex = BasicAppFilterModel::instance()->index(row, 0, {}); persistentModelIndex = QPersistentModelIndex(sourceIndex); indexRow = m_apps.indexOf(persistentModelIndex); if (indexRow == -1) continue; Q_EMIT dataChanged(index(indexRow, 0, QModelIndex()), index(indexRow, 0, QModelIndex())); } } // ====== AppSearchPlugin ====== // AppSearchPlugin::AppSearchPlugin(QObject *parent) : AppListPluginInterface(parent) , m_searchPluginPrivate(new AppSearchPluginPrivate(this)), m_model(new AppSearchModel(this)) { connect(m_searchPluginPrivate, &AppSearchPluginPrivate::searchedOne, m_model, &AppSearchModel::appendApp); } AppListPluginGroup::Group AppSearchPlugin::group() { return AppListPluginGroup::Search; } QString AppSearchPlugin::name() { return "Search"; } QString AppSearchPlugin::title() { return "Search"; } QList AppSearchPlugin::actions() { // TODO: 搜索结果排序选项 return {}; } QAbstractItemModel *AppSearchPlugin::dataModel() { return m_model; } void AppSearchPlugin::search(const QString &keyword) { m_model->clear(); if (keyword.isEmpty()) { return; } m_searchPluginPrivate->startSearch(keyword); } AppSearchPlugin::~AppSearchPlugin() { m_searchPluginPrivate->stopSearch(); m_searchPluginPrivate->quit(); m_searchPluginPrivate->wait(); m_searchPluginPrivate->deleteLater(); } } // UkuiMenu #include "app-search-plugin.moc" ukui-menu/src/libappdata/basic-app-model.h0000664000175000017500000000411615160463353017425 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #ifndef UKUI_MENU_BASIC_APP_MODEL_H #define UKUI_MENU_BASIC_APP_MODEL_H #include #include #include "data-entity.h" #include "app-database-interface.h" namespace UkuiMenu { class BasicAppModel : public QAbstractListModel { Q_OBJECT public: static BasicAppModel *instance(); /** * 数据库访问接口 * @return @AppDatabaseInterface * */ const AppDatabaseInterface *databaseInterface() const; DataEntity appOfIndex(int row) const; int indexOfApp(const QString &appid) const; bool getAppById(const QString &appid, DataEntity &app) const; int rowCount(const QModelIndex &parent) const override; int columnCount(const QModelIndex &parent) const override; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; QHash roleNames() const override; private Q_SLOTS: void onAppAdded(const UkuiMenu::DataEntityVector &apps); void onAppUpdated(const QVector > > &updates); void onAppDeleted(const QStringList &apps); private: explicit BasicAppModel(QObject *parent = nullptr); AppDatabaseInterface *m_databaseInterface {nullptr}; QVector m_apps; }; } // UkuiMenu #endif //UKUI_MENU_BASIC_APP_MODEL_H ukui-menu/src/libappdata/basic-app-filter-model.h0000664000175000017500000000323415160463353020710 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: youdiansaodongxi * */ #ifndef BASICAPPFILTERMODEL_H #define BASICAPPFILTERMODEL_H #include #include "app-database-interface.h" namespace UkuiMenu { class BasicAppModel; class BasicAppFilterModel : public QSortFilterProxyModel { Q_OBJECT public: static BasicAppFilterModel *instance(); const AppDatabaseInterface *databaseInterface() const; DataEntity appOfIndex(int row) const; int indexOfApp(const QString &appid) const; bool getAppById(const QString &appid, DataEntity &app) const; QVariant data(const QModelIndex &index, int role) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; private: BasicAppFilterModel(QObject *parent = nullptr); BasicAppModel *m_sourceModel = nullptr; }; } // UkuiMenu #endif // BASICAPPFILTERMODEL_H ukui-menu/src/libappdata/recently-installed-model.cpp0000664000175000017500000001416715160463365021735 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include "recently-installed-model.h" #include "basic-app-filter-model.h" #include "user-config.h" #include #include #include #include #include #define KYSDK_TIMERSERVER "com.kylin.kysdk.TimeServer" #define KYSDK_TIMERPATH "/com/kylin/kysdk/Timer" #define KYSDK_TIMERINTERFACE "com.kylin.kysdk.TimeInterface" namespace UkuiMenu { // ====== RecentlyInstalledModel ====== // RecentlyInstalledModel::RecentlyInstalledModel(QObject *parent) : QSortFilterProxyModel(parent), m_timer(new QTimer(this)) { QSortFilterProxyModel::setSourceModel(BasicAppFilterModel::instance()); // 触发排序动作 // QSortFilterProxyModel::sort(0, Qt::DescendingOrder); QSortFilterProxyModel::sort(0); // 每24小时主动刷新 m_timer->setInterval(24 * 3600000); m_timer->start(); QDBusConnection::systemBus().connect(KYSDK_TIMERSERVER, KYSDK_TIMERPATH, KYSDK_TIMERINTERFACE, "TimeChangeSignal", this, SLOT(invalidate())); } bool RecentlyInstalledModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { if (m_mode == AppCategoryModel::All) { return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); } else { QModelIndex sourceIndex = sourceModel()->index(source_row, 0, source_parent); DataEntity data = sourceIndex.data(DataEntity::Entity).value(); return isRecentInstall(data); } } bool RecentlyInstalledModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { DataEntity left_app = source_left.data(DataEntity::Entity).value(), right_app = source_right.data(DataEntity::Entity).value(); bool isLeftRecent = isRecentInstall(left_app), isRightRecent = isRecentInstall(right_app); if (isLeftRecent && isRightRecent) { QDateTime leftInstallDate = QDateTime::fromString(source_left.data(DataEntity::InstallationTime).value(), "yyyy-MM-dd hh:mm:ss"); QDateTime rightInstallDate = QDateTime::fromString(source_right.data(DataEntity::InstallationTime).value(), "yyyy-MM-dd hh:mm:ss"); qint64 xt = leftInstallDate.toSecsSinceEpoch() - rightInstallDate.toSecsSinceEpoch(); if (xt == 0) { return source_left.data(DataEntity::FirstLetter).value() < source_right.data(DataEntity::FirstLetter).value(); } return xt >= 0; } else if (!isLeftRecent && !isRightRecent) { qreal leftDegree = getDegree(source_left); qreal rightDegree = getDegree(source_right); return leftDegree > rightDegree; } else if (isLeftRecent) { return true; } else { return false; } } bool RecentlyInstalledModel::isRecentInstall(const DataEntity &data) { // 是否为预装应用 if (UserConfig::instance()->isPreInstalledApps(data.getValue(DataEntity::Id).toString())) { return false; } // 是否打开过 if (data.getValue(DataEntity::IsLaunched).toInt() != 0) { return false; } // 是否收藏 if (data.getValue(DataEntity::Favorite).toInt() > 0) { return false; } QDateTime installDate = QDateTime::fromString(data.getValue(DataEntity::InstallationTime).value(), "yyyy-MM-dd hh:mm:ss"); if (!installDate.isValid()) { return false; } QDateTime currentDateTime = QDateTime::currentDateTime(); // 安装时间在30天内 qint64 xt = currentDateTime.toSecsSinceEpoch() - installDate.toSecsSinceEpoch(); return (xt >= 0) && (xt <= 30 * 24 * 3600); } qreal RecentlyInstalledModel::getDegree(const QModelIndex &index) const { qreal degree; QDateTime installTime = QDateTime::fromString(index.data(DataEntity::InstallationTime).toString(), "yyyy-MM-dd hh:mm:ss"); if (!installTime.isValid()) { return -1; } qreal installDays = installTime.daysTo(QDateTime::currentDateTime()); qreal LaunchTimes = index.data(DataEntity::LaunchTimes).toInt(); if (installDays > 10) { degree = LaunchTimes * (240 / (installDays - 6)); } else { degree = LaunchTimes * (-0.4 * installDays * installDays + 100); } return degree; } bool RecentlyInstalledModel::event(QEvent *event) { if (event->type() == QEvent::Timer) { auto timerEvent = static_cast(event); if (timerEvent->timerId() == m_timer->timerId()) { invalidate(); return true; } } return QObject::event(event); } QVariant RecentlyInstalledModel::data(const QModelIndex &index, int role) const { if (role == DataEntity::Group) { if (m_mode == AppCategoryModel::All) { return ""; } return tr("Recently Installed"); } if (role == DataEntity::RecentInstall) { if (m_mode == AppCategoryModel::All) { return isRecentInstall(index.data(DataEntity::Entity).value()); } else { return true; } } return QSortFilterProxyModel::data(index, role); } void RecentlyInstalledModel::setMode(AppCategoryModel::Mode mode) { if (m_mode != mode) { m_mode = mode; invalidateFilter(); invalidate(); } } } // UkuiMenu ukui-menu/src/libappdata/app-group-model.cpp0000664000175000017500000002400415160463365020034 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include "app-group-model.h" #include #include namespace UkuiMenu { AppGroupModel::AppGroupModel(QObject *parent) : QAbstractProxyModel(parent) { } QModelIndex AppGroupModel::index(int row, int column, const QModelIndex &parent) const { if (column != 0 || row < 0) { return {}; } // 通过parent找到所属的组,将组的信息放入index附带数据中,作为判断依据 if (parent.isValid()) { const auto subItems = m_groups.at(parent.row()); if (row >= subItems->size()) { return {}; } return createIndex(row, column, subItems); } return createIndex(row, column, nullptr); } QModelIndex AppGroupModel::parent(const QModelIndex &child) const { if (!child.isValid()) { return {}; } auto subItems = static_cast*>(child.internalPointer()); if (subItems) { return createIndex(m_groups.indexOf(subItems), 0); } return {}; } bool AppGroupModel::hasChildren(const QModelIndex &parent) const { if (!sourceModel()) { return false; } // root if (!parent.isValid()) { return !m_groups.isEmpty(); } // child, 两层 if (parent.parent().isValid()) { return false; } return true; } int AppGroupModel::rowCount(const QModelIndex &parent) const { if (!sourceModel()) { return 0; } // root if (!parent.isValid()) { return m_groups.size(); } if (parent.parent().isValid()) { return 0; } int i = parent.row(); if (i < 0 || i >= m_groups.size()) { return 0; } return m_groups.at(i)->size(); } int AppGroupModel::columnCount(const QModelIndex &parent) const { return 1; } QHash AppGroupModel::roleNames() const { if (sourceModel()) return sourceModel()->roleNames(); return QAbstractItemModel::roleNames(); } void AppGroupModel::setSourceModel(QAbstractItemModel *sourceModel) { if (AppGroupModel::sourceModel() == sourceModel) { return; } beginResetModel(); if (AppGroupModel::sourceModel()) { AppGroupModel::sourceModel()->disconnect(this); } QAbstractProxyModel::setSourceModel(sourceModel); if (sourceModel) { rebuildAppGroups(); connect(sourceModel, &QAbstractItemModel::dataChanged, this, &AppGroupModel::onDataChanged); connect(sourceModel, &QAbstractItemModel::layoutChanged, this, &AppGroupModel::onLayoutChanged); connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &AppGroupModel::onRowsInserted); connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &AppGroupModel::onRowsAboutToBeRemoved); connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, [=] (const QModelIndex &parent, int first, int last) { if (m_needRebuild) { beginResetModel(); rebuildAppGroups(); endResetModel(); m_needRebuild = false; } else if (!parent.isValid()) { reLocationIndex(first, -(last - first + 1)); } }); connect(sourceModel, &QAbstractItemModel::modelReset, this, [=] { beginResetModel(); rebuildAppGroups(); endResetModel(); }); } endResetModel(); } QModelIndex AppGroupModel::mapToSource(const QModelIndex &proxyIndex) const { if (!sourceModel() || !proxyIndex.isValid()) { return {}; } auto subItems = static_cast*>(proxyIndex.internalPointer()); if (subItems) { int idx = subItems->at(proxyIndex.row()); return sourceModel()->index(idx, 0); } return {}; } QModelIndex AppGroupModel::mapFromSource(const QModelIndex &sourceIndex) const { if (!sourceModel() || !sourceIndex.isValid()) { return {}; } int groupIndex = findGroupIndex(sourceIndex); if (groupIndex < 0) { return {}; } int tempIndex = m_groups.at(groupIndex)->indexOf(sourceIndex.row()); return index(tempIndex, 0, index(groupIndex, 0, QModelIndex())); } QVariant AppGroupModel::data(const QModelIndex &proxyIndex, int role) const { if (!checkIndex(proxyIndex, CheckIndexOption::IndexIsValid)) { return {}; } if (proxyIndex.parent().isValid()) { QModelIndex sourceIdex = mapToSource(proxyIndex); if (sourceIdex.isValid()) { return sourceModel()->data(sourceIdex, role); } } if (role == DataEntity::Name || role == DataEntity::Group) { return sourceModel()->index(m_groups.at(proxyIndex.row())->first(), 0).data(DataEntity::Group); } return {}; } void AppGroupModel::rebuildAppGroups() { qDeleteAll(m_groups); m_groups.clear(); for (int i = 0; i < sourceModel()->rowCount(); ++i) { int groupIndex = findGroupIndex(sourceModel()->index(i, 0)); QVector *subItems {nullptr}; if (groupIndex < 0) { subItems = new QVector(); m_groups.append(subItems); } else { subItems = m_groups[groupIndex]; } subItems->append(i); } } int AppGroupModel::findGroupIndex(const QModelIndex &sourceIndex) const { for (int i = 0; i < m_groups.size(); ++i) { const QVector *subItems = m_groups.at(i); if (subItems->isEmpty()) { continue; } // 使用group属性进行分组 QString groupA = sourceIndex.data(DataEntity::Group).toString(); QString groupB = sourceModel()->index(subItems->at(0), 0).data(DataEntity::Group).toString(); if (groupA == groupB) { return i; } } return -1; } void AppGroupModel::insertApp(int groupIndex, const QModelIndex &sourceIndex) { int newIndex = sourceIndex.row(); if (groupIndex < 0 || groupIndex >= m_groups.size()) { if (newIndex > 0) { // 查找前一个item的index newIndex = findGroupIndex(sourceModel()->index(--newIndex, 0)); if (newIndex < 0) { newIndex = m_groups.size(); } else { ++newIndex; } } beginInsertRows(QModelIndex(), newIndex, newIndex); m_groups.insert(newIndex, new QVector(1, sourceIndex.row())); endInsertRows(); return; } int index = 0; QVector *subItems = m_groups[groupIndex]; for (; index < subItems->size(); ++index) { if (newIndex < subItems->at(index)) { break; } } beginInsertRows(AppGroupModel::index(groupIndex, 0, QModelIndex()), index, index); subItems->insert(index, newIndex); endInsertRows(); } // slots void AppGroupModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles) { for (int i = topLeft.row(); i <= bottomRight.row(); ++i) { QModelIndex proxyIndex = mapFromSource(sourceModel()->index(i, 0)); Q_EMIT dataChanged(proxyIndex, proxyIndex, roles); } } void AppGroupModel::onLayoutChanged(const QList &parents, QAbstractItemModel::LayoutChangeHint hint) { Q_UNUSED(parents) Q_UNUSED(hint) beginResetModel(); rebuildAppGroups(); endResetModel(); } void AppGroupModel::onRowsInserted(const QModelIndex &parent, int first, int last) { if (parent.isValid()) { return; } if (first < (sourceModel()->rowCount() - 1)) { reLocationIndex(first, (last - first + 1)); } for (int i = first; i <= last; ++i) { QModelIndex sourceIndex = sourceModel()->index(i, 0, parent); insertApp(findGroupIndex(sourceIndex), sourceIndex); } } void AppGroupModel::reLocationIndex(int base, int offset) { for (QVector *group : m_groups) { QMutableVectorIterator it(*group); while (it.hasNext()) { const int &value = it.next(); if (value >= base) { it.setValue(value + offset); } } } } void AppGroupModel::onRowsAboutToBeRemoved(const QModelIndex &parent, int first, int last) { if (parent.isValid()) { return; } int groupIndex = 0, itemIndex = 0; for (int i = first; i <= last; ++i) { groupIndex = findGroupIndex(sourceModel()->index(i, 0)); if (groupIndex < 0) { continue; } auto subItems = m_groups[groupIndex]; itemIndex = subItems->indexOf(i); if (itemIndex < 0) { // 如果出现错误,那么重新构建映射关系 m_needRebuild = true; break; } if (subItems->size() > 1) { // 删除组里的元素 beginRemoveRows(index(groupIndex, 0, QModelIndex()), itemIndex, itemIndex); subItems->removeAt(itemIndex); endRemoveRows(); } else { // 删除组 beginRemoveRows(QModelIndex(), groupIndex, groupIndex); delete m_groups.takeAt(groupIndex); endRemoveRows(); } } } int AppGroupModel::findLabelIndex(const QString &label) const { int rowCount = AppGroupModel::rowCount(QModelIndex()); for (int i = 0; i < rowCount; ++i) { if (index(i, 0, QModelIndex()).data(DataEntity::Group).toString() == label) { return i; } } return -1; } } // UkuiMenu ukui-menu/src/libappdata/app-list-model.cpp0000664000175000017500000001005515160463353017651 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ #include "app-list-model.h" #include "data-entity.h" #include "context-menu-manager.h" #include namespace UkuiMenu { AppListHeader::AppListHeader(QObject *parent) : QObject(parent) { } QString AppListHeader::title() const { return m_title; } void AppListHeader::setTitle(const QString &title) { if (title == m_title) { return; } m_title = title; Q_EMIT titleChanged(); } QList AppListHeader::actions() const { return m_actions; } void AppListHeader::addAction(QAction *action) { if (m_actions.indexOf(action) >= 0) { return; } m_actions.append(action); Q_EMIT actionsChanged(); setVisible(true); } bool AppListHeader::visible() const { return m_visible; } void AppListHeader::setVisible(bool visible) { if (m_visible == visible) { return; } m_visible = visible; Q_EMIT visibleChanged(); } void AppListHeader::removeAction(QAction *action) { if (m_actions.removeOne(action)) { Q_EMIT actionsChanged(); setVisible(!m_actions.isEmpty()); } } void AppListHeader::removeAllAction() { m_actions.clear(); Q_EMIT actionsChanged(); setVisible(false); } // ====== // AppListModel::AppListModel(QObject *parent) : QSortFilterProxyModel(parent), m_header(new AppListHeader(this)) { qRegisterMetaType(); qRegisterMetaType(); } QHash AppListModel::roleNames() const { return DataEntity::AppRoleNames(); } AppListHeader *AppListModel::getHeader() const { return m_header; } void AppListModel::installPlugin(AppListPluginInterface *plugin) { if (!plugin || m_plugin == plugin) { return; } unInstallPlugin(); m_plugin = plugin; QSortFilterProxyModel::setSourceModel(plugin->dataModel()); for (const auto &action : plugin->actions()) { m_header->addAction(action); } m_header->setTitle(plugin->title()); connect(m_plugin, &AppListPluginInterface::titleChanged, this, [this, plugin] { m_header->setTitle(plugin->title()); }); connect(m_plugin, &AppListPluginInterface::labelChanged, this, [this, plugin] { Q_EMIT labelBottleChanged(); }); Q_EMIT labelBottleChanged(); } void AppListModel::unInstallPlugin() { if (!m_plugin) { return; } m_header->setTitle(""); m_header->removeAllAction(); QSortFilterProxyModel::setSourceModel(nullptr); disconnect(m_plugin, nullptr, this, nullptr); m_plugin = nullptr; Q_EMIT labelBottleChanged(); } void AppListModel::openMenu(const int &index, MenuInfo::Location location) const { QModelIndex idx = AppListModel::index(index, 0); if (checkIndex(idx, CheckIndexOption::IndexIsValid)) { ContextMenuManager::instance()->showMenu(idx.data(DataEntity::Entity).value(), location); } } LabelBottle *AppListModel::labelBottle() const { if (m_plugin) { return m_plugin->labelBottle(); } return nullptr; } int AppListModel::findLabelIndex(const QString &label) const { // TODO: 潜在的优化空间 int count = AppListModel::rowCount(); for (int i = 0; i < count; ++i) { if (AppListModel::sourceModel()->index(i, 0).data(DataEntity::Group).toString() == label) { return i; } } return -1; } } // UkuiMenu ukui-menu/src/appdata/0000775000175000017500000000000015160463353013626 5ustar fengfengukui-menu/src/appdata/app-folder-helper.h0000664000175000017500000000600415160463353017305 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_FOLDER_HELPER_H #define UKUI_MENU_APP_FOLDER_HELPER_H #include #include #include #include #include #include namespace UkuiMenu { #define FOLDER_MAX_ICON_NUM 16 class AppFolderHelper; class Folder { Q_GADGET Q_PROPERTY(int id READ getId) Q_PROPERTY(QString name READ getName) Q_PROPERTY(QStringList apps READ getApps) friend class AppFolderHelper; public: int getId() const { return id; } QString getName() const { return name; } QStringList getApps() const { return apps; } private: int id; // 文件夹唯一Id,文件夹排序值 QString name; // 名称 QStringList apps; // 应用列表 }; class AppFolderHelper : public QObject { Q_OBJECT public: static AppFolderHelper *instance(); static QStringList folderIcon(const Folder &folder); ~AppFolderHelper() override; AppFolderHelper(const AppFolderHelper& obj) = delete; AppFolderHelper &operator=(const AppFolderHelper& obj) = delete; AppFolderHelper(AppFolderHelper&& obj) = delete; AppFolderHelper &operator=(AppFolderHelper&& obj) = delete; // TODO 修改文件夹信息 // xxxx bool searchFolder(const int& folderId, Folder &folder); bool searchFolderByAppName(const QString& appId, Folder &folder); bool containFolder(int folderId); bool containApp(const QString& appId); QList folderData(); void addAppToFolder(const QString& appId, const int& folderId); void addAppToNewFolder(const QString& appId, const QString& folderName); void addAppsToNewFolder(const QString& appIdA, const QString& appIdB, const QString& folderName); void removeAppFromFolder(const QString& appId, const int& folderId); bool deleteFolder(const int& folderId); void renameFolder(const int& folderId, const QString& folderName); void forceSync(); Q_SIGNALS: void folderAdded(int folderId); void folderDeleted(int folderId); void folderDataChanged(int folderId); private: AppFolderHelper(); void readData(); void saveData(); void insertFolder(const Folder& folder); // TODO 配置文件监听 private: QMutex m_mutex; QMap m_folders; static QString s_folderConfigFile; }; } // UkuiMenu #endif //UKUI_MENU_APP_FOLDER_HELPER_H ukui-menu/src/appdata/app-data-manager.cpp0000664000175000017500000003673615160463353017450 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "app-data-manager.h" #include "user-config.h" #include "settings.h" #include #include #define APP_ICON_PREFIX "image://appicon/" namespace UkuiMenu { class AppDataWorker : public QObject { Q_OBJECT public: explicit AppDataWorker(AppDataManager *appManager = nullptr); Q_SIGNALS: void appAdded(QList apps); void appUpdated(QList apps, bool totalUpdate); void appDeleted(QStringList idList); void favoriteAppChanged(); void appDataBaseOpenFailed(); private Q_SLOTS: void initAppData(); void onAppAdded(const QStringList &infos); void onAppUpdated(const UkuiSearch::ApplicationInfoMap &infos); void onAppUpdatedAll(const QStringList &infos); void onAppDeleted(QStringList infos); public Q_SLOTS: void fixToFavoriteSlot(const QString &path, const int &num); void changedFavoriteOrderSlot(const QString &path, const int &num); void fixToTopSlot(const QString &path, const int &num); void setAppLaunched(const QString &path); private: void updateFavoriteApps(); void removeApps(QStringList& appIdList, QStringList &removedIdList); bool updateApps(const UkuiSearch::ApplicationInfoMap &infos, QList &apps); void updateAppsAll(const QStringList &infos, QList &apps); void appendApps(const QStringList &infos, QList &apps); void addInfoToApp(const UkuiSearch::ApplicationPropertyMap &info, DataEntity &app); private: AppDataManager *m_appManager{nullptr}; UkuiSearch::ApplicationInfo *m_applicationInfo{nullptr}; UkuiSearch::ApplicationProperties m_appProperties; UkuiSearch::ApplicationPropertyMap m_appPropertyMap; }; AppDataWorker::AppDataWorker(AppDataManager *appManager) : QObject(nullptr), m_appManager(appManager) { qRegisterMetaType >("QList"); qRegisterMetaType >("QVector"); m_applicationInfo = new UkuiSearch::ApplicationInfo(this); if (!m_applicationInfo || !m_appManager) { return; } initAppData(); connect(m_applicationInfo, &UkuiSearch::ApplicationInfo::appDBItems2BAdd, this, &AppDataWorker::onAppAdded); connect(m_applicationInfo, &UkuiSearch::ApplicationInfo::appDBItems2BUpdate, this, &AppDataWorker::onAppUpdated); connect(m_applicationInfo, &UkuiSearch::ApplicationInfo::appDBItems2BUpdateAll, this, &AppDataWorker::onAppUpdatedAll); connect(m_applicationInfo, &UkuiSearch::ApplicationInfo::appDBItems2BDelete, this, &AppDataWorker::onAppDeleted); connect(m_applicationInfo, &UkuiSearch::ApplicationInfo::DBOpenFailed, this, &AppDataWorker::appDataBaseOpenFailed); } void AppDataWorker::initAppData() { if (UserConfig::instance()->isFirstStartUp()) { // 默认收藏应用 for (const auto &appid : GlobalSetting::instance()->defaultFavoriteApps()) { m_applicationInfo->setAppToFavorites(appid); } } m_appProperties << UkuiSearch::ApplicationProperty::Property::Top << UkuiSearch::ApplicationProperty::Property::Lock << UkuiSearch::ApplicationProperty::Property::Favorites << UkuiSearch::ApplicationProperty::Property::LaunchTimes << UkuiSearch::ApplicationProperty::Property::DesktopFilePath << UkuiSearch::ApplicationProperty::Property::Icon << UkuiSearch::ApplicationProperty::Property::LocalName << UkuiSearch::ApplicationProperty::Property::Category << UkuiSearch::ApplicationProperty::Property::FirstLetterAll << UkuiSearch::ApplicationProperty::Property::DontDisplay << UkuiSearch::ApplicationProperty::Property::AutoStart << UkuiSearch::ApplicationProperty::Property::InsertTime << UkuiSearch::ApplicationProperty::Property::Launched; m_appPropertyMap.insert(UkuiSearch::ApplicationProperty::Property::DontDisplay, 0); m_appPropertyMap.insert(UkuiSearch::ApplicationProperty::Property::AutoStart, 0); UkuiSearch::ApplicationInfoMap appInfos = m_applicationInfo->getInfo(m_appProperties, m_appPropertyMap); if (appInfos.isEmpty()) { return; } for (const auto &info : appInfos) { DataEntity app; addInfoToApp(info, app); m_appManager->m_normalApps.insert(app.id(), app); } updateFavoriteApps(); } void AppDataWorker::updateFavoriteApps() { QVector favoriteApps; for (const auto &app : m_appManager->m_normalApps) { if (app.favorite() > 0) { favoriteApps.append(app); } } if (!favoriteApps.isEmpty()) { // 排序搜藏夹数据 std::sort(favoriteApps.begin(), favoriteApps.end(), [](const DataEntity& a, const DataEntity &b) { return a.favorite() < b.favorite(); }); } { QMutexLocker locker(&m_appManager->m_mutex); m_appManager->m_favoriteApps.swap(favoriteApps); } Q_EMIT favoriteAppChanged(); } void AppDataWorker::onAppAdded(const QStringList &infos) { if (infos.isEmpty()) { return; } QList apps; appendApps(infos, apps); if (apps.isEmpty()) { return; } Q_EMIT appAdded(apps); updateFavoriteApps(); } void AppDataWorker::appendApps(const QStringList &infos, QList &apps) { QMutexLocker locker(&m_appManager->m_mutex); for (const QString &info : infos) { const UkuiSearch::ApplicationPropertyMap appInfo = m_applicationInfo->getInfo(info, m_appProperties); if (appInfo.value(UkuiSearch::ApplicationProperty::Property::DontDisplay).toInt() != 0) { continue; } if (appInfo.value(UkuiSearch::ApplicationProperty::Property::AutoStart).toInt() != 0) { continue; } if (m_appManager->m_normalApps.contains(info)) { continue; } DataEntity app; addInfoToApp(appInfo, app); m_appManager->m_normalApps.insert(app.id(), app); apps.append(app); } } void AppDataWorker::addInfoToApp(const UkuiSearch::ApplicationPropertyMap &info, DataEntity &app) { app.setTop(info.value(UkuiSearch::ApplicationProperty::Property::Top).toInt()); app.setLock(info.value(UkuiSearch::ApplicationProperty::Property::Lock).toInt() == 1); app.setFavorite(info.value(UkuiSearch::ApplicationProperty::Property::Favorites).toInt()); app.setLaunchTimes(info.value(UkuiSearch::ApplicationProperty::Property::LaunchTimes).toInt()); app.setId(info.value(UkuiSearch::ApplicationProperty::Property::DesktopFilePath).toString()); app.setIcon(APP_ICON_PREFIX + info.value(UkuiSearch::ApplicationProperty::Property::Icon).toString()); app.setName(info.value(UkuiSearch::ApplicationProperty::Property::LocalName).toString()); app.setCategory(info.value(UkuiSearch::ApplicationProperty::Property::Category).toString()); app.setFirstLetter(info.value(UkuiSearch::ApplicationProperty::Property::FirstLetterAll).toString()); app.setInsertTime(info.value(UkuiSearch::ApplicationProperty::Property::InsertTime).toString()); app.setLaunched(info.value(UkuiSearch::ApplicationProperty::Property::Launched).toInt()); } void AppDataWorker::onAppUpdated(const UkuiSearch::ApplicationInfoMap &infos) { if (infos.isEmpty()) { return; } QList apps; bool totalUpdate = updateApps(infos, apps); if (apps.isEmpty()) { return; } Q_EMIT appUpdated(apps, totalUpdate); updateFavoriteApps(); } void AppDataWorker::onAppUpdatedAll(const QStringList &infos) { if (infos.isEmpty()) { return; } QList apps; updateAppsAll(infos, apps); if (apps.isEmpty()) { return; } Q_EMIT appUpdated(apps, true); updateFavoriteApps(); } bool AppDataWorker::updateApps(const UkuiSearch::ApplicationInfoMap &infos, QList &apps) { QMutexLocker locker(&m_appManager->m_mutex); if (infos.isEmpty()) { return false; } bool totalUpdate = false; for (const QString &info : infos.keys()) { if (m_appManager->m_normalApps.contains(info)) { DataEntity &app = m_appManager->m_normalApps[info]; for (auto &appProperty : infos.value(info).keys()) { switch (appProperty) { case UkuiSearch::ApplicationProperty::Property::Top: app.setTop(infos.value(info).value(UkuiSearch::ApplicationProperty::Property::Top).toInt()); break; case UkuiSearch::ApplicationProperty::Property::Lock: app.setLock(infos.value(info).value(UkuiSearch::ApplicationProperty::Property::Lock).toInt() == 1); break; case UkuiSearch::ApplicationProperty::Property::Favorites: app.setFavorite(infos.value(info).value(UkuiSearch::ApplicationProperty::Property::Favorites).toInt()); break; case UkuiSearch::ApplicationProperty::Property::LaunchTimes: app.setLaunchTimes(infos.value(info).value(UkuiSearch::ApplicationProperty::Property::LaunchTimes).toInt()); break; case UkuiSearch::ApplicationProperty::Property::DesktopFilePath: app.setId(infos.value(info).value(UkuiSearch::ApplicationProperty::Property::DesktopFilePath).toString()); break; case UkuiSearch::ApplicationProperty::Property::Icon: app.setIcon(APP_ICON_PREFIX + infos.value(info).value(UkuiSearch::ApplicationProperty::Property::Icon).toString()); break; case UkuiSearch::ApplicationProperty::Property::LocalName: app.setName(infos.value(info).value(UkuiSearch::ApplicationProperty::Property::LocalName).toString()); break; case UkuiSearch::ApplicationProperty::Property::Category: app.setCategory(infos.value(info).value(UkuiSearch::ApplicationProperty::Property::Category).toString()); totalUpdate = true; break; case UkuiSearch::ApplicationProperty::Property::FirstLetterAll: app.setFirstLetter(infos.value(info).value(UkuiSearch::ApplicationProperty::Property::FirstLetterAll).toString()); totalUpdate = true; break; case UkuiSearch::ApplicationProperty::Property::InsertTime: app.setInsertTime(infos.value(info).value(UkuiSearch::ApplicationProperty::Property::InsertTime).toString()); break; case UkuiSearch::ApplicationProperty::Property::Launched: app.setLaunched(infos.value(info).value(UkuiSearch::ApplicationProperty::Property::Launched).toInt()); break; default: break; } } apps.append(app); } } return totalUpdate; } void AppDataWorker::updateAppsAll(const QStringList &infos, QList &apps) { QMutexLocker locker(&m_appManager->m_mutex); for (const auto &info : infos) { if (m_appManager->m_normalApps.contains(info)) { UkuiSearch::ApplicationPropertyMap appinfo = m_applicationInfo->getInfo(info, m_appProperties); DataEntity &app = m_appManager->m_normalApps[info]; addInfoToApp(appinfo, app); apps.append(app); } } } void AppDataWorker::onAppDeleted(QStringList infos) { if (infos.isEmpty()) { return; } QStringList removedIdList; removeApps(infos, removedIdList); if (removedIdList.isEmpty()) { return; } Q_EMIT appDeleted(removedIdList); updateFavoriteApps(); for (const auto &appid : removedIdList) { UserConfig::instance()->removePreInstalledApp(appid); } UserConfig::instance()->sync(); } void AppDataWorker::fixToFavoriteSlot(const QString &path, const int &num) { if (num == 0) { m_applicationInfo->setFavoritesOfApp(path, 0); } else { m_applicationInfo->setFavoritesOfApp(path, m_appManager->m_favoriteApps.length() + 1); } } void AppDataWorker::changedFavoriteOrderSlot(const QString &path, const int &num) { m_applicationInfo->setFavoritesOfApp(path, num); } void AppDataWorker::fixToTopSlot(const QString &path, const int &num) { if (num == 0) { m_applicationInfo->setAppToTop(path); } else { m_applicationInfo->setTopOfApp(path, 0); } } void AppDataWorker::setAppLaunched(const QString &path) { if (m_appManager->m_normalApps.value(path).launched() == 0) { m_applicationInfo->setAppLaunchedState(path); } } void AppDataWorker::removeApps(QStringList &appIdList, QStringList &removedIdList) { QMutexLocker locker(&m_appManager->m_mutex); for (const QString &id : appIdList) { if (m_appManager->m_normalApps.remove(id)) { removedIdList.append(id); } } } // ===== AppDataManager ===== // AppDataManager *AppDataManager::instance() { static AppDataManager appDataManager; return &appDataManager; } AppDataManager::AppDataManager() { AppDataWorker *appDataWorker = new AppDataWorker(this); appDataWorker->moveToThread(&m_workerThread); connect(&m_workerThread, &QThread::finished, appDataWorker, &QObject::deleteLater); connect(appDataWorker, &AppDataWorker::appAdded, this, &AppDataManager::appAdded); connect(appDataWorker, &AppDataWorker::appDeleted, this, &AppDataManager::appDeleted); connect(appDataWorker, &AppDataWorker::appUpdated, this, &AppDataManager::appUpdated); connect(appDataWorker, &AppDataWorker::favoriteAppChanged, this, &AppDataManager::favoriteAppChanged); connect(this, &AppDataManager::fixToFavoriteSignal, appDataWorker, &AppDataWorker::fixToFavoriteSlot); connect(this, &AppDataManager::changedFavoriteOrderSignal, appDataWorker, &AppDataWorker::changedFavoriteOrderSlot); connect(this, &AppDataManager::fixToTop, appDataWorker, &AppDataWorker::fixToTopSlot); connect(this, &AppDataManager::appLaunch, appDataWorker, &AppDataWorker::setAppLaunched); m_workerThread.start(); } AppDataManager::~AppDataManager() { m_workerThread.quit(); m_workerThread.wait(); } QList AppDataManager::normalApps() { QMutexLocker locker(&m_mutex); return m_normalApps.values(); } QVector AppDataManager::favoriteApps() { QMutexLocker locker(&m_mutex); return m_favoriteApps; } bool AppDataManager::getApp(const QString &appId, DataEntity &app) { QMutexLocker locker(&m_mutex); if (!m_normalApps.contains(appId)) { return false; } app = m_normalApps[appId]; return true; } } // UkuiMenu #include "app-data-manager.moc" ukui-menu/src/appdata/app-icon-provider.h0000664000175000017500000000323415160463353017337 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_ICON_PROVIDER_H #define UKUI_MENU_APP_ICON_PROVIDER_H #include #include #include namespace UkuiMenu { // see: https://doc.qt.io/archives/qt-5.12/qquickimageprovider.html#details class AppIconProvider : public QQuickImageProvider { public: AppIconProvider(); QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override; static QPixmap getPixmap(const QString &id, QSize *size, const QSize &requestedSize); private: static void loadIcon(const QString &id, QIcon &icon); static void loadDefault(QIcon &icon); static bool loadIconFromUrl(const QUrl &url, QIcon &icon); static bool loadIconFromPath(const QString &path, QIcon &icon); static bool loadIconFromTheme(const QString &name, QIcon &icon); static bool loadIconFromXdg(const QString &name, QIcon &icon); private: static QSize s_defaultSize; }; } // UkuiMenu #endif //UKUI_MENU_APP_ICON_PROVIDER_H ukui-menu/src/appdata/data-provider-manager.cpp0000664000175000017500000001175015160463353020507 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "data-provider-manager.h" #include "event-track.h" #include "plugin/all-app-data-provider.h" #include "plugin/app-search-plugin.h" #include "plugin/app-category-plugin.h" #include "plugin/app-letter-sort-plugin.h" namespace UkuiMenu { /** * 1.统一不同model显示相同的数据。 * 2.对model隐藏插件。 */ DataProviderManager *DataProviderManager::instance() { static DataProviderManager dataProviderManager; return &dataProviderManager; } DataProviderManager::DataProviderManager() { qRegisterMetaType("DataUpdateMode::Mode"); m_worker.start(); initProviders(); } void DataProviderManager::initProviders() { auto *search = new AppSearchPlugin; connect(this, &DataProviderManager::toUpdate, search, &AppSearchPlugin::update); registerProvider(search); auto *category = new AppCategoryPlugin; connect(this, &DataProviderManager::toUpdate, category, &AppCategoryPlugin::update); registerProvider(category); auto *sort = new AppLetterSortPlugin; connect(this, &DataProviderManager::toUpdate, sort, &AppLetterSortPlugin::update); registerProvider(sort); activateProvider(sort->id(), false); search->moveToThread(&m_worker); category->moveToThread(&m_worker); sort->moveToThread(&m_worker); } void DataProviderManager::registerProvider(DataProviderPluginIFace *provider) { // if nullptr if (!provider) { return; } if (m_providers.contains(provider->id())) { return; } m_providers.insert(provider->id(), provider); connect(provider, &AllAppDataProvider::dataChanged, this, [this, provider](const QVector &data, DataUpdateMode::Mode mode, quint32 index) { //qDebug() << "==DataProviderManager dataChanged:" << provider->name() << QThread::currentThreadId(); if (m_activatedPlugin != provider->id()) { return; } Q_EMIT dataChanged(data, mode, index); }); connect(provider, &AllAppDataProvider::labelChanged, this, [this, provider]() { if (m_activatedPlugin != provider->id()) { return; } Q_EMIT labelChanged(); }); } QVector DataProviderManager::data() const { return m_providers.value(m_activatedPlugin)->data(); } QVector DataProviderManager::labels() const { return m_providers.value(m_activatedPlugin)->labels(); } QString DataProviderManager::activatedProvider() const { return m_activatedPlugin; } QStringList DataProviderManager::providers() const { return m_providers.keys(); } QVector DataProviderManager::providers(PluginGroup::Group group) const { QVector infos; for (const auto &provider : m_providers) { if (group == provider->group()) { infos.append({ provider->index(), provider->id(), provider->name(), provider->icon(), provider->title(), provider->group() }); } } return infos; } ProviderInfo DataProviderManager::providerInfo(const QString &id) const { ProviderInfo info; DataProviderPluginIFace* provider = m_providers.value(id, nullptr); if (provider) { info = { provider->index(), provider->id(), provider->name(), provider->icon(), provider->title(), provider->group() }; } return info; } void DataProviderManager::activateProvider(const QString &id, bool record) { if (!m_providers.contains(id) || m_activatedPlugin == id) { return; } m_activatedPlugin = id; Q_EMIT pluginChanged(m_activatedPlugin, m_providers.value(m_activatedPlugin)->group()); if (record) { QMap map; map.insert("viewName", id); EventTrack::instance()->sendClickEvent("switch_app_view", "AppView", map); } } DataProviderManager::~DataProviderManager() { m_worker.quit(); m_worker.wait(); for (auto &provider : m_providers) { provider->deleteLater(); } } void DataProviderManager::forceUpdate() const { m_providers.value(m_activatedPlugin)->forceUpdate(); } void DataProviderManager::forceUpdate(QString &key) const { m_providers.value(m_activatedPlugin)->forceUpdate(key); } } // UkuiMenu ukui-menu/src/appdata/plugin/0000775000175000017500000000000015160463353015124 5ustar fengfengukui-menu/src/appdata/plugin/app-letter-sort-plugin.h0000664000175000017500000000344715160463353021643 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_LETTER_SORT_PLUGIN_H #define UKUI_MENU_APP_LETTER_SORT_PLUGIN_H #include "data-provider-plugin-iface.h" namespace UkuiMenu { class AppLetterSortPlugin : public DataProviderPluginIFace { Q_OBJECT public: explicit AppLetterSortPlugin(); int index() override; QString id() override; QString name() override; QString icon() override; QString title() override; PluginGroup::Group group() override; QVector data() override; void forceUpdate() override; QVector labels() override; private Q_SLOTS: void onAppAdded(const QList& apps); void onAppDeleted(QStringList idList); void onAppUpdated(const QList& apps, bool totalUpdate); private: void updateAppData(); void sendChangeSignal(); void loadAppData(); void sortAppData(QList &apps); void addAppToLabel(const QString &labelName, QVector &apps); private: QMutex m_mutex; QVector m_appData; QVector m_labels; }; } // UkuiMenu #endif // UKUI_MENU_APP_LETTER_SORT_PLUGIN_H ukui-menu/src/appdata/plugin/all-app-data-provider.cpp0000664000175000017500000002324215160463353021720 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "all-app-data-provider.h" #include "app-data-manager.h" #include "app-folder-helper.h" #include "user-config.h" #include #include namespace UkuiMenu { AllAppDataProvider::AllAppDataProvider() : DataProviderPluginIFace() { reloadFolderData(); reloadAppData(); connect(AppDataManager::instance(), &AppDataManager::appAdded, this, &AllAppDataProvider::onAppAdded); connect(AppDataManager::instance(), &AppDataManager::appDeleted, this, &AllAppDataProvider::onAppDeleted); connect(AppDataManager::instance(), &AppDataManager::appUpdated, this, &AllAppDataProvider::onAppUpdated); connect(AppFolderHelper::instance(), &AppFolderHelper::folderAdded, this, &AllAppDataProvider::onAppFolderChanged); connect(AppFolderHelper::instance(), &AppFolderHelper::folderDeleted, this, &AllAppDataProvider::onAppFolderChanged); connect(AppFolderHelper::instance(), &AppFolderHelper::folderDataChanged, this, &AllAppDataProvider::onAppFolderChanged); } int AllAppDataProvider::index() { return 2; } QString AllAppDataProvider::id() { return "all"; } QString AllAppDataProvider::name() { return tr("All"); } QString AllAppDataProvider::icon() { return "image://appicon/view-grid-symbolic"; } QString AllAppDataProvider::title() { return tr("All applications"); } PluginGroup::Group AllAppDataProvider::group() { return PluginGroup::SortMenuItem; } QVector AllAppDataProvider::data() { QVector data; mergeData(data); return data; } void AllAppDataProvider::forceUpdate() { reloadFolderData(); reloadAppData(); sendData(); } void AllAppDataProvider::update(bool isShowed) { m_windowStatus = isShowed; if (isShowed) { m_timer->blockSignals(true); bool isRecentDataChanged = false; { QMutexLocker locker(&m_mutex); for (DataEntity &appdata : m_appData) { bool info = appdata.isRecentInstall(); setRecentState(appdata); if (appdata.isRecentInstall() != info) { isRecentDataChanged = true; break; } } } if (isRecentDataChanged) { std::sort(m_appData.begin(), m_appData.end(), appDataSort); sendData(); } } else { m_timer->blockSignals(false); if (m_updateStatus) { reloadAppData(); m_updateStatus = false; } } } void AllAppDataProvider::reloadAppData() { QMutexLocker locker(&m_mutex); QVector appData; QList apps = AppDataManager::instance()->normalApps(); if (apps.isEmpty()) { m_appData.swap(appData); return; } for (auto &app : apps) { if (AppFolderHelper::instance()->containApp(app.id())) { continue; } setRecentState(app); setSortPriority(app); appData.append(app); } std::sort(appData.begin(), appData.end(), appDataSort); m_appData.swap(appData); updateTimer(); } void AllAppDataProvider::reloadFolderData() { QMutexLocker locker(&m_mutex); QVector folderData; QList folders = AppFolderHelper::instance()->folderData(); if (folders.isEmpty()) { m_folderData.swap(folderData); return; } DataEntity folderItem; for (const auto &folder : folders) { folderItem.setId(QString::number(folder.getId())); folderItem.setType(DataType::Folder); folderItem.setIcon(AppFolderHelper::folderIcon(folder).join(" ")); folderItem.setName(folder.getName()); folderData.append(folderItem); } m_folderData.swap(folderData); } void AllAppDataProvider::mergeData(QVector &data) { QMutexLocker locker(&m_mutex); data.append(m_folderData); data.append(m_appData); } void AllAppDataProvider::updateData(const QList &apps) { QMutexLocker locker(&m_mutex); for (const DataEntity & app : apps) { for (DataEntity & appdata : m_appData) { if (appdata.id() == app.id()) { appdata = app; setRecentState(appdata); setSortPriority(appdata); break; } } } std::sort(m_appData.begin(), m_appData.end(), appDataSort); } void AllAppDataProvider::updateFolderData(QStringList &idList) { QList folders = AppFolderHelper::instance()->folderData(); if (folders.isEmpty()) { return; } for (const auto &folder : folders) { for (const auto &app : folder.getApps()) { if (idList.contains(app)) { AppFolderHelper::instance()->removeAppFromFolder(app, folder.getId()); } } } AppFolderHelper::instance()->forceSync(); } void AllAppDataProvider::updateTimer() { if (m_timer == nullptr) { m_timer = new QTimer(this); m_timer->setInterval(3600000*48); connect(m_timer, &QTimer::timeout, this, [this]{ if (m_windowStatus) { m_updateStatus = true; } else { reloadAppData(); } }); } m_timer->start(); } bool AllAppDataProvider::appDataSort(const DataEntity &a, const DataEntity &b) { if ((a.top() != 0) && (b.top() != 0)) { return a.top() < b.top(); } else if ((a.top() == 0) && (b.top() == 0)) { if (a.isRecentInstall()) { if (b.isRecentInstall()) { if (QDateTime::fromString(a.insertTime(), "yyyy-MM-dd hh:mm:ss") != QDateTime::fromString(b.insertTime(), "yyyy-MM-dd hh:mm:ss")) { return QDateTime::fromString(a.insertTime(), "yyyy-MM-dd hh:mm:ss") > QDateTime::fromString(b.insertTime(), "yyyy-MM-dd hh:mm:ss"); } else { return letterSort(a.firstLetter(), b.firstLetter()); } } else { return true; } } else if (b.isRecentInstall()) { return false; } else { if (a.priority() == b.priority()) { return letterSort(a.firstLetter(), b.firstLetter()); } else { return a.priority() > b.priority(); } } } else { return a.top() > b.top(); } } void AllAppDataProvider::setSortPriority(DataEntity &app) { QDateTime installTime = QDateTime::fromString(app.insertTime(), "yyyy-MM-dd hh:mm:ss"); if (installTime.isValid()) { qint64 appTime = installTime.secsTo(QDateTime::currentDateTime()); if (appTime <= 3600*240) { appTime = appTime / (3600*24); double priority = app.launchTimes() * (-0.4 * (appTime^2) + 100); app.setPriority(priority); return; } else { appTime = appTime / (3600*24); double priority = app.launchTimes() * (240 / (appTime - 6)); app.setPriority(priority); return; } } } void AllAppDataProvider::setRecentState(DataEntity &app) { if (!UserConfig::instance()->isPreInstalledApps(app.id())) { if (app.launched() == 0) { QDateTime installTime = QDateTime::fromString(app.insertTime(), "yyyy-MM-dd hh:mm:ss"); if (installTime.isValid()) { qint64 appTime = installTime.secsTo(QDateTime::currentDateTime()); if ((appTime >= 0 ) && (appTime <= 3600*48)) { app.setRecentInstall(true); return; } } } } app.setRecentInstall(false); } bool AllAppDataProvider::letterSort(const QString &a, const QString &b) { if (QString::compare(a, b, Qt::CaseInsensitive) == 0) { return false; } return QString::compare(a, b, Qt::CaseInsensitive) > 0 ? false : true; } void AllAppDataProvider::sendData() { QVector data; mergeData(data); Q_EMIT dataChanged(data); } void AllAppDataProvider::onAppFolderChanged() { forceUpdate(); } void AllAppDataProvider::onAppAdded(const QList& apps) { { QMutexLocker locker(&m_mutex); for (auto app : apps) { setRecentState(app); setSortPriority(app); m_appData.append(app); } std::sort(m_appData.begin(), m_appData.end(), appDataSort); } sendData(); } void AllAppDataProvider::onAppDeleted(QStringList idList) { removeApps(idList); reloadFolderData(); updateFolderData(idList); sendData(); } void AllAppDataProvider::removeApps(QStringList &idList) { QMutexLocker locker(&m_mutex); QVector::iterator iterator = m_appData.begin(); while (iterator != m_appData.end() && !idList.isEmpty()) { if (idList.removeOne((*iterator).id())) { iterator = m_appData.erase(iterator); continue; } ++iterator; } } void AllAppDataProvider::onAppUpdated(const QList& apps) { updateData(apps); sendData(); } } // UkuiMenu ukui-menu/src/appdata/plugin/app-letter-sort-plugin.cpp0000664000175000017500000001075115160463353022172 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "app-letter-sort-plugin.h" #include "app-data-manager.h" #include namespace UkuiMenu { AppLetterSortPlugin::AppLetterSortPlugin() { loadAppData(); connect(AppDataManager::instance(), &AppDataManager::appAdded, this, &AppLetterSortPlugin::onAppAdded); connect(AppDataManager::instance(), &AppDataManager::appDeleted, this, &AppLetterSortPlugin::onAppDeleted); connect(AppDataManager::instance(), &AppDataManager::appUpdated, this, &AppLetterSortPlugin::onAppUpdated); } int AppLetterSortPlugin::index() { return 0; } QString AppLetterSortPlugin::id() { return "letterSort"; } QString AppLetterSortPlugin::name() { return tr("Letter Sort"); } QString AppLetterSortPlugin::icon() { return "image://appicon/ukui-capslock-symbolic"; } QString AppLetterSortPlugin::title() { return tr("Letter Sort"); } PluginGroup::Group AppLetterSortPlugin::group() { return PluginGroup::SortMenuItem; } QVector AppLetterSortPlugin::data() { return m_appData; } void AppLetterSortPlugin::forceUpdate() { updateAppData(); } QVector AppLetterSortPlugin::labels() { return m_labels; } void AppLetterSortPlugin::onAppAdded(const QList &apps) { Q_UNUSED(apps); updateAppData(); } void AppLetterSortPlugin::onAppDeleted(QStringList idList) { Q_UNUSED(idList); updateAppData(); } void AppLetterSortPlugin::onAppUpdated(const QList &apps, bool totalUpdate) { if (totalUpdate) { updateAppData(); } else { Q_EMIT dataChanged(apps.toVector(), DataUpdateMode::Update); } } void AppLetterSortPlugin::updateAppData() { loadAppData(); sendChangeSignal(); } void AppLetterSortPlugin::sendChangeSignal() { Q_EMIT labelChanged(); Q_EMIT dataChanged(m_appData); } void AppLetterSortPlugin::loadAppData() { QMutexLocker locker(&m_mutex); m_appData.clear(); m_labels.clear(); QList appData; appData = AppDataManager::instance()->normalApps(); if (appData.isEmpty()) { return; } sortAppData(appData); } void AppLetterSortPlugin::sortAppData(QList &apps) { QVector appVector[28]; for (int i = 0; i < apps.length(); i++) { QString letter = apps.at(i).firstLetter(); char firstLetter; if (!letter.isEmpty()) { firstLetter = letter.at(0).toUpper().toLatin1(); } else { firstLetter = '&'; } if (firstLetter >= 'A' && firstLetter <= 'Z') { appVector[(static_cast(firstLetter) - 65)].append(apps.at(i)); } else if (firstLetter >= '1' && firstLetter <= '9') { appVector[26].append(apps.at(i)); } else { appVector[27].append(apps.at(i)); } } for (int i = 0; i < 26; i++) { addAppToLabel(QString(QChar(static_cast(i + 65))), appVector[i]); } addAppToLabel(QString("#"), appVector[26]); addAppToLabel(QString("&"), appVector[27]); } void AppLetterSortPlugin::addAppToLabel(const QString &labelName, QVector &apps) { LabelItem labelItem; labelItem.setDisable(apps.isEmpty()); labelItem.setIndex(m_labels.size()); labelItem.setId(labelName); labelItem.setDisplayName(labelName); m_labels.append(labelItem); if (!apps.isEmpty()) { DataEntity dataEntity; dataEntity.setComment(tr("Open Letter Sort Menu")); dataEntity.setName(labelName); dataEntity.setId(labelName); dataEntity.setType(DataType::Label); m_appData.append(dataEntity); std::sort(apps.begin(), apps.end(), [](const DataEntity &a, const DataEntity &b) { return !(a.firstLetter().compare(b.firstLetter(), Qt::CaseInsensitive) > 0); }); m_appData.append(apps); } } } // UkuiMenu ukui-menu/src/appdata/plugin/app-category-plugin.h0000664000175000017500000000372215160463353021170 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_CATEGORY_PLUGIN_H #define UKUI_MENU_APP_CATEGORY_PLUGIN_H #include "data-provider-plugin-iface.h" namespace UkuiMenu { class AppCategoryPluginPrivate; class AppCategoryPlugin : public DataProviderPluginIFace { Q_OBJECT public: AppCategoryPlugin(); ~AppCategoryPlugin() override; int index() override; QString id() override; QString name() override; QString icon() override; QString title() override; PluginGroup::Group group() override; QVector data() override; void forceUpdate() override; QVector labels() override; private Q_SLOTS: void onAppAdded(const QList& apps); void onAppDeleted(const QStringList& idList); void onAppUpdated(const QList& apps, bool totalUpdate); private: void reLoadApps(); void initCategories(); // 解析Category,获取正确的index int parseAppCategory(const DataEntity &app); void addAppToCategoryList(const DataEntity &app); // 从私有数据类型生成数据 void generateData(QVector &data); inline void updateData(); private: QMutex m_mutex; QString m_otherLabelId; AppCategoryPluginPrivate *d{nullptr}; }; } // UkuiMenu #endif //UKUI_MENU_APP_CATEGORY_PLUGIN_H ukui-menu/src/appdata/plugin/app-category-plugin.cpp0000664000175000017500000001542615160463353021527 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "app-category-plugin.h" #include "app-data-manager.h" namespace UkuiMenu { // 临时使用,可能会删除 class CategoryPair { public: CategoryPair() = default; CategoryPair(QString id_t, QString localName_t) : id(std::move(id_t)), localName(std::move(localName_t)) {} QString id; QString localName; }; class CategoryItem { public: LabelItem label; QList apps; }; class AppCategoryPluginPrivate { public: QHash categoryIndex; QList categoryData; }; AppCategoryPlugin::AppCategoryPlugin() : d(new AppCategoryPluginPrivate) { initCategories(); reLoadApps(); connect(AppDataManager::instance(), &AppDataManager::appAdded, this, &AppCategoryPlugin::onAppAdded); connect(AppDataManager::instance(), &AppDataManager::appDeleted, this, &AppCategoryPlugin::onAppDeleted); connect(AppDataManager::instance(), &AppDataManager::appUpdated, this, &AppCategoryPlugin::onAppUpdated); } AppCategoryPlugin::~AppCategoryPlugin() { if (d) { delete d; d = nullptr; } } int AppCategoryPlugin::index() { return 1; } QString AppCategoryPlugin::id() { return "category"; } QString AppCategoryPlugin::name() { return tr("Category"); } QString AppCategoryPlugin::icon() { return "image://appicon/applications-utilities-symbolic"; } QString AppCategoryPlugin::title() { return tr("Category"); } PluginGroup::Group AppCategoryPlugin::group() { return PluginGroup::SortMenuItem; } QVector AppCategoryPlugin::data() { QVector data; generateData(data); return data; } void AppCategoryPlugin::forceUpdate() { } QVector AppCategoryPlugin::labels() { QMutexLocker locker(&m_mutex); QVector labels; for (const auto &item : d->categoryData) { // qDebug() << "==label==" << item.label.id() << item.label.displayName(); labels.append(item.label); } return labels; } // see: https://specifications.freedesktop.org/menu-spec/latest/apa.html // see: https://specifications.freedesktop.org/menu-spec/latest/apas02.html // TODO: 暂时只列出freedesktop规范中的常用分类,需要继续支持显示应用自定义的分类进行显示 void AppCategoryPlugin::initCategories() { QList primaryCategories; primaryCategories.append({"Audio", tr("Audio")}); primaryCategories.append({"AudioVideo", tr("AudioVideo")}); primaryCategories.append({"Development", tr("Development")}); primaryCategories.append({"Education", tr("Education")}); primaryCategories.append({"Game", tr("Game")}); primaryCategories.append({"Graphics", tr("Graphics")}); primaryCategories.append({"Network", tr("Network")}); primaryCategories.append({"Office", tr("Office")}); primaryCategories.append({"Science", tr("Science")}); primaryCategories.append({"Settings", tr("Settings")}); primaryCategories.append({"System", tr("System")}); primaryCategories.append({"Utility", tr("Utility")}); primaryCategories.append({"Video", tr("Video")}); // 未定义Category的应用类别 m_otherLabelId = "Other"; primaryCategories.append({m_otherLabelId, tr("Other")}); for (const auto &category : primaryCategories) { // 默认拷贝 CategoryItem categoryItem; categoryItem.label.setId(category.id); categoryItem.label.setDisplayName(category.localName); // categoryItem.label.setDisable(true); categoryItem.label.setIndex(d->categoryData.size()); d->categoryData.append(categoryItem); d->categoryIndex.insert(category.id, categoryItem.label.index()); } } int AppCategoryPlugin::parseAppCategory(const DataEntity &app) { // qDebug() << "=parseAppCategory=" << app.name(); // TODO: 某些应用的Category字段不是使用;进行分割的,是否需要支持? QStringList categories = app.category().split(";"); for (const auto &category : categories) { int index = d->categoryIndex.value(category, -1); if (index != -1) { return index; } } // qDebug() << "Other index:" << d->categoryIndex.value(m_otherLabelId) << "==" << m_otherLabelId; return d->categoryIndex.value(m_otherLabelId); } void AppCategoryPlugin::addAppToCategoryList(const DataEntity &app) { CategoryItem &categoryItem = d->categoryData[parseAppCategory(app)]; categoryItem.label.setDisable(false); categoryItem.apps.append(app); } void AppCategoryPlugin::reLoadApps() { QMutexLocker locker(&m_mutex); for (auto &item : d->categoryData) { item.label.setDisable(true); item.apps.clear(); } QList apps = AppDataManager::instance()->normalApps(); for (const auto &app : apps) { addAppToCategoryList(app); } } void AppCategoryPlugin::generateData(QVector &data) { QMutexLocker locker(&m_mutex); for (const auto &item : d->categoryData) { // TODO: 显示不包含应用的分类? if (item.apps.isEmpty()) { continue; } DataEntity label; label.setComment(tr("Open Function Sort Menu")); label.setType(DataType::Label); label.setId(item.label.id()); label.setName(item.label.displayName()); data.append(label); // TODO: 性能优化 // data.append(item.apps.toVector()); for (const auto &app : item.apps) { data.append(app); } } } void AppCategoryPlugin::updateData() { Q_EMIT dataChanged(data()); } void AppCategoryPlugin::onAppAdded(const QList& apps) { { QMutexLocker locker(&m_mutex); for (const auto &app : apps) { addAppToCategoryList(app); } } updateData(); } void AppCategoryPlugin::onAppDeleted(const QStringList& idList) { reLoadApps(); updateData(); Q_EMIT labelChanged(); } void AppCategoryPlugin::onAppUpdated(const QList &apps, bool totalUpdate) { if (totalUpdate) { reLoadApps(); updateData(); } else { Q_EMIT dataChanged(apps.toVector(), DataUpdateMode::Update); } } } // UkuiMenu ukui-menu/src/appdata/plugin/all-app-data-provider.h0000664000175000017500000000452415160463353021367 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_ALL_APP_DATA_PROVIDER_H #define UKUI_MENU_ALL_APP_DATA_PROVIDER_H #include "data-provider-plugin-iface.h" #include #include #include namespace UkuiMenu { class AllAppDataProvider : public DataProviderPluginIFace { Q_OBJECT public: explicit AllAppDataProvider(); int index() override; QString id() override; QString name() override; QString icon() override; QString title() override; PluginGroup::Group group() override; QVector data() override; void forceUpdate() override; public Q_SLOTS: void update(bool isShowed) override; private Q_SLOTS: void onAppAdded(const QList& apps); void onAppDeleted(QStringList idList); void onAppUpdated(const QList& apps); // TODO 文件夹数据新增,删除信号处理 void onAppFolderChanged(); private: inline void sendData(); void removeApps(QStringList& idList); void reloadAppData(); void reloadFolderData(); void mergeData(QVector &data); void updateData(const QList& apps); void updateFolderData(QStringList& idList); void updateTimer(); static bool appDataSort(const DataEntity &a, const DataEntity &b); static void setSortPriority(DataEntity &app); static void setRecentState(DataEntity &app); static bool letterSort(const QString &a, const QString &b); private: QTimer *m_timer = nullptr; QMutex m_mutex; QVector m_appData; QVector m_folderData; bool m_updateStatus = false; bool m_windowStatus = false; }; } // UkuiMenu #endif //UKUI_MENU_ALL_APP_DATA_PROVIDER_H ukui-menu/src/appdata/plugin/app-search-plugin.h0000664000175000017500000000274015160463353020617 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_SEARCH_PLUGIN_H #define UKUI_MENU_APP_SEARCH_PLUGIN_H #include "data-provider-plugin-iface.h" namespace UkuiMenu { class AppSearchPluginPrivate; class AppSearchPlugin : public DataProviderPluginIFace { Q_OBJECT public: AppSearchPlugin(); ~AppSearchPlugin() override; int index() override; QString id() override; QString name() override; QString icon() override; QString title() override; PluginGroup::Group group() override; QVector data() override; void forceUpdate() override; void forceUpdate(QString &key) override; private Q_SLOTS: void onSearchedOne(const DataEntity &app); private: AppSearchPluginPrivate *m_searchPluginPrivate{nullptr}; }; } // UkuiMenu #endif //UKUI_MENU_APP_SEARCH_PLUGIN_H ukui-menu/src/appdata/plugin/app-search-plugin.cpp0000664000175000017500000001171515160463353021154 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "app-search-plugin.h" #include #include #include namespace UkuiMenu { class AppSearchPluginPrivate : public QThread { Q_OBJECT public: AppSearchPluginPrivate(); Q_SIGNALS: void searchedOne(DataEntity app); public Q_SLOTS: void startSearch(QString &keyword); void stopSearch(); protected: void run() override; private: size_t m_searchId{0}; QTimer *m_timer{nullptr}; UkuiSearch::UkuiSearchTask *m_appSearchTask{nullptr}; UkuiSearch::DataQueue *m_dataQueue{nullptr}; }; AppSearchPluginPrivate::AppSearchPluginPrivate() : QThread(nullptr) { m_appSearchTask = new UkuiSearch::UkuiSearchTask(this); m_dataQueue = m_appSearchTask->init(); m_appSearchTask->initSearchPlugin(UkuiSearch::SearchProperty::SearchType::Application); m_appSearchTask->setSearchOnlineApps(false); UkuiSearch::SearchResultProperties searchResultProperties; searchResultProperties << UkuiSearch::SearchProperty::SearchResultProperty::ApplicationDesktopPath << UkuiSearch::SearchProperty::SearchResultProperty::ApplicationLocalName << UkuiSearch::SearchProperty::SearchResultProperty::ApplicationIconName; m_appSearchTask->setResultProperties(UkuiSearch::SearchProperty::SearchType::Application, searchResultProperties); m_timer = new QTimer; m_timer->setInterval(3000); m_timer->moveToThread(this); } void AppSearchPluginPrivate::startSearch(QString &keyword) { if (!this->isRunning()) { this->start(); } m_appSearchTask->clearKeyWords(); m_appSearchTask->addKeyword(keyword); m_searchId = m_appSearchTask->startSearch(UkuiSearch::SearchProperty::SearchType::Application); } void AppSearchPluginPrivate::stopSearch() { m_appSearchTask->stop(); this->requestInterruption(); } void AppSearchPluginPrivate::run() { while (!isInterruptionRequested()) { UkuiSearch::ResultItem result = m_dataQueue->tryDequeue(); if(result.getSearchId() == 0 && result.getItemKey().isEmpty() && result.getAllValue().isEmpty()) { if(!m_timer->isActive()) { // 超时退出 m_timer->start(); } msleep(100); } else { m_timer->stop(); if (result.getSearchId() == m_searchId) { DataEntity app; app.setType(DataType::Normal); app.setId(result.getValue(UkuiSearch::SearchProperty::ApplicationDesktopPath).toString()); app.setName(result.getValue(UkuiSearch::SearchProperty::ApplicationLocalName).toString()); app.setIcon("image://appicon/" + result.getValue(UkuiSearch::SearchProperty::ApplicationIconName).toString()); Q_EMIT this->searchedOne(app); } } if(m_timer->isActive() && m_timer->remainingTime() < 0.01 && m_dataQueue->isEmpty()) { this->requestInterruption(); } } } // ========AppSearchPlugin======== // AppSearchPlugin::AppSearchPlugin() { m_searchPluginPrivate = new AppSearchPluginPrivate; connect(m_searchPluginPrivate, &AppSearchPluginPrivate::searchedOne, this, &AppSearchPlugin::onSearchedOne); } int AppSearchPlugin::index() { return 0; } QString AppSearchPlugin::id() { return "search"; } QString AppSearchPlugin::name() { return tr("Search"); } QString AppSearchPlugin::icon() { return "image://appicon/search-symbolic"; } QString AppSearchPlugin::title() { return ""; } PluginGroup::Group AppSearchPlugin::group() { return PluginGroup::Button; } QVector AppSearchPlugin::data() { return {}; } void AppSearchPlugin::forceUpdate() { } void AppSearchPlugin::forceUpdate(QString &key) { // qDebug() << "==AppSearchPlugin key:" << key; Q_EMIT dataChanged({}); if (key.isEmpty()) { return; } m_searchPluginPrivate->startSearch(key); } void AppSearchPlugin::onSearchedOne(const DataEntity &app) { Q_EMIT dataChanged({1, app}, DataUpdateMode::Append); } AppSearchPlugin::~AppSearchPlugin() { m_searchPluginPrivate->stopSearch(); m_searchPluginPrivate->quit(); m_searchPluginPrivate->wait(); m_searchPluginPrivate->deleteLater(); } } // UkuiMenu #include "app-search-plugin.moc" ukui-menu/src/appdata/app-data-manager.h0000664000175000017500000000422215160463353017076 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_APP_DATA_MANAGER_H #define UKUI_MENU_APP_DATA_MANAGER_H #include #include #include #include #include #include #include #include #include "commons.h" namespace UkuiMenu { class AppDataManager : public QObject { friend class AppDataWorker; Q_OBJECT public: static AppDataManager *instance(); ~AppDataManager() override; AppDataManager(const AppDataManager& obj) = delete; AppDataManager &operator=(const AppDataManager& obj) = delete; AppDataManager(AppDataManager&& obj) = delete; AppDataManager &operator=(AppDataManager&& obj) = delete; bool getApp(const QString &appId, DataEntity &app); QList normalApps(); QVector favoriteApps(); Q_SIGNALS: void appAdded(QList apps); void appUpdated(QList apps, bool totalUpdate); void appDeleted(QStringList idList); void appDataChanged(); void favoriteAppChanged(); void fixToFavoriteSignal(const QString &path, const int &num); void changedFavoriteOrderSignal(const QString &path, const int &num); void fixToTop(const QString &path, const int &num); void appLaunch(const QString &path); private: AppDataManager(); private: QMutex m_mutex; QThread m_workerThread; QMap m_normalApps; QVector m_favoriteApps; }; } // UkuiMenu #endif //UKUI_MENU_APP_DATA_MANAGER_H ukui-menu/src/appdata/data-provider-plugin-iface.h0000664000175000017500000000440515160463353021104 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_DATA_PROVIDER_PLUGIN_IFACE_H #define UKUI_MENU_DATA_PROVIDER_PLUGIN_IFACE_H #include #include #include #include #include #include "commons.h" namespace UkuiMenu { class PluginGroup { Q_GADGET public: enum Group { Button = 0, SortMenuItem, Search, }; Q_ENUM(Group) }; class DataUpdateMode { Q_GADGET public: enum Mode { Reset = 0, Append, Prepend, Insert, Update }; Q_ENUM(Mode) }; class DataProviderPluginIFace : public QObject { Q_OBJECT public: explicit DataProviderPluginIFace() : QObject(nullptr) {} virtual int index() = 0; virtual QString id() = 0; virtual QString name() = 0; virtual QString icon() = 0; virtual QString title() = 0; virtual PluginGroup::Group group() = 0; /** * 获取插件存放的数据 * @return 数据容器 */ virtual QVector data() = 0; virtual QVector labels() { return {}; }; /** * 强制刷新数据,刷新完成后发送dataChange信号 */ virtual void forceUpdate() = 0; virtual void forceUpdate(QString &key) {}; public Q_SLOTS: virtual void update(bool isShowed) {}; Q_SIGNALS: /** * 数据变化 */ void dataChanged(QVector data, DataUpdateMode::Mode mode = DataUpdateMode::Reset, quint32 index = 0); void labelChanged(); }; } // UkuiMenu //Q_DECLARE_METATYPE(UkuiMenu::DataUpdateMode::Mode) #endif //UKUI_MENU_DATA_PROVIDER_PLUGIN_IFACE_H ukui-menu/src/appdata/data-provider-manager.h0000664000175000017500000000423615160463353020155 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_DATA_PROVIDER_MANAGER_H #define UKUI_MENU_DATA_PROVIDER_MANAGER_H #include #include #include #include "data-provider-plugin-iface.h" namespace UkuiMenu { class ProviderInfo { public: int index; QString id; QString name; QString icon; QString title; PluginGroup::Group group; }; class DataProviderManager : public QObject { Q_OBJECT public: static DataProviderManager *instance(); ~DataProviderManager() override; QStringList providers() const; QVector providers(PluginGroup::Group group) const; ProviderInfo providerInfo(const QString &id) const; QString activatedProvider() const; void activateProvider(const QString &id, bool record = true); QVector data() const; QVector labels() const; void forceUpdate() const; void forceUpdate(QString &key) const; Q_SIGNALS: void pluginChanged(const QString &id, PluginGroup::Group group); void dataChanged(QVector data, DataUpdateMode::Mode mode, quint32 index); void labelChanged(); void toUpdate(bool isShowed); private: DataProviderManager(); Q_DISABLE_COPY(DataProviderManager); void initProviders(); void registerProvider(DataProviderPluginIFace *provider); private: QThread m_worker; QString m_activatedPlugin{""}; QHash m_providers; }; } // UkuiMenu #endif //UKUI_MENU_DATA_PROVIDER_MANAGER_H ukui-menu/src/appdata/app-folder-helper.cpp0000664000175000017500000003205215160463353017642 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "app-folder-helper.h" #include "context-menu-manager.h" #include "app-data-manager.h" #include "model-manager.h" #include "app-model.h" #include "event-track.h" #include #include #include #include #include #include #include #define FOLDER_FILE_PATH ".config/ukui-menu/" #define FOLDER_FILE_NAME "folder.json" namespace UkuiMenu { //class FolderMenuProvider : public MenuProvider //{ //public: // FolderMenuProvider(); // int index() override { return 256; } // bool isSupport(const RequestType &type) override; // QList generateActions(QObject *parent, const QVariant &data, const MenuInfo::Location &location, const QString &locationId) override; //private: // static void folderMenuForNormal(QObject *parent, const QString &appId, QList &list); // static void dissolveFolder(QObject *parent, const QString &folderId, QList &list); // static void renameFolder(QObject *parent, const QString &folderId, QList &list); //}; //FolderMenuProvider::FolderMenuProvider() //{ // //} // //bool FolderMenuProvider::isSupport(const MenuProvider::RequestType &type) //{ // return type == DataType; //} // //QList //FolderMenuProvider::generateActions(QObject *parent, const QVariant &data, const MenuInfo::Location &location, // const QString &locationId) //{ // if (!parent) { // return {}; // } // // DataEntity app = data.value(); // QList list; // // switch (location) { // case MenuInfo::FullScreen: { // if (app.type() == DataType::Folder) { // dissolveFolder(parent, app.id(), list); // break; // } // } // case MenuInfo::AppList: { // if (app.type() == DataType::Folder) { // renameFolder(parent, app.id(), list); // dissolveFolder(parent, app.id(), list); // break; // } // // if (app.type() != DataType::Normal || locationId != "all") { // break; // } // } // case MenuInfo::FullScreenFolder: // case MenuInfo::FolderPage: { // if (app.type() == DataType::Normal) { // folderMenuForNormal(parent, app.id(), list); // } // break; // } // case MenuInfo::Extension: // default: // break; // } // // return list; //} // //void FolderMenuProvider::folderMenuForNormal(QObject *parent, const QString &appId, QList &list) //{ // Folder folder; // if (AppFolderHelper::instance()->searchFolderByAppName(appId, folder)) { // //从当前文件夹移除 // list << new QAction(QObject::tr("Remove from folder"), parent); // QObject::connect(list.last(), &QAction::triggered, parent, [appId, folder] { // AppFolderHelper::instance()->removeAppFromFolder(appId, folder.getId()); // AppFolderHelper::instance()->forceSync(); // }); // return; // } // // QMenu *menu = static_cast(parent); // //添加到应用组 // QMenu *subMenu = new QMenu(QObject::tr("Add to folder"), menu); // // QAction* newAct = new QAction(QObject::tr("Add to new folder"), subMenu); // QObject::connect(newAct, &QAction::triggered, parent, [appId] { // AppFolderHelper::instance()->addAppToNewFolder(appId, ""); // AppFolderHelper::instance()->forceSync(); // }); // subMenu->addAction(newAct); // // QList folders = AppFolderHelper::instance()->folderData(); // for (const Folder &f : folders) { // QString name = QObject::tr("Add to \"%1\"").arg(f.getName()); // QAction* act = new QAction(name, subMenu); // QObject::connect(act, &QAction::triggered, parent, [appId, f] { // AppFolderHelper::instance()->addAppToFolder(appId, f.getId()); // }); // subMenu->addAction(act); // } // menu->addMenu(subMenu); //} // //void FolderMenuProvider::dissolveFolder(QObject *parent, const QString &folderId, QList &list) //{ // list << new QAction(QObject::tr("Dissolve folder"), parent); // QObject::connect(list.last(), &QAction::triggered, parent, [folderId] { // AppFolderHelper::instance()->deleteFolder(folderId.toInt()); // AppFolderHelper::instance()->forceSync(); // }); //} // //void FolderMenuProvider::renameFolder(QObject *parent, const QString &folderId, QList &list) //{ // list << new QAction(QObject::tr("Rename"), parent); // QObject::connect(list.last(), &QAction::triggered, parent, [folderId] { // ModelManager::instance()->getAppModel()->toRenameFolder(folderId); // }); //} QString AppFolderHelper::s_folderConfigFile = QDir::homePath() + "/" + FOLDER_FILE_PATH + FOLDER_FILE_NAME; AppFolderHelper *AppFolderHelper::instance() { static AppFolderHelper appFolderHelper; return &appFolderHelper; } AppFolderHelper::AppFolderHelper() { qRegisterMetaType("Folder"); if (!QFile::exists(s_folderConfigFile)) { QDir dir; QString folderConfigDir(QDir::homePath() + "/" + FOLDER_FILE_PATH); if (!dir.exists(folderConfigDir)) { if (!dir.mkdir(folderConfigDir)) { qWarning() << "Unable to create profile folder."; return; } } QFile file(s_folderConfigFile); file.open(QFile::WriteOnly); file.close(); } readData(); } AppFolderHelper::~AppFolderHelper() { saveData(); } void AppFolderHelper::insertFolder(const Folder &folder) { QMutexLocker locker(&m_mutex); m_folders.insert(folder.id, folder); } void AppFolderHelper::addAppToFolder(const QString &appId, const int &folderId) { if (appId.isEmpty()) { return; } { QMutexLocker locker(&m_mutex); if (!m_folders.contains(folderId)) { return; } Folder &folder = m_folders[folderId]; if (folder.apps.contains(appId)) { return; } folder.apps.append(appId); } forceSync(); Q_EMIT folderDataChanged(folderId); EventTrack::instance()->sendDefaultEvent("add_app_to_folder", "AppView"); } void AppFolderHelper::addAppToNewFolder(const QString &appId, const QString &folderName) { if (appId.isEmpty()) { return; } // TODO: max越界处理 int max = m_folders.isEmpty() ? -1 : m_folders.lastKey(); QString name = folderName; if (name.isEmpty()) { name = tr("New Folder %1").arg(m_folders.size() + 1); } Folder folder; folder.id = ++max; folder.name = name; folder.apps.append(appId); insertFolder(folder); Q_EMIT folderAdded(folder.id); EventTrack::instance()->sendDefaultEvent("add_app_to_new_folder", "AppView"); } void AppFolderHelper::addAppsToNewFolder(const QString &appIdA, const QString &appIdB, const QString &folderName) { if (appIdA.isEmpty() || appIdB.isEmpty()) { return; } // TODO: max越界处理 int max = m_folders.isEmpty() ? -1 : m_folders.lastKey(); QString name = folderName; if (name.isEmpty()) { name = tr("New Folder %1").arg(m_folders.size() + 1); } Folder folder; folder.id = ++max; folder.name = name; folder.apps.append(appIdA); folder.apps.append(appIdB); insertFolder(folder); Q_EMIT folderAdded(folder.id); forceSync(); } void AppFolderHelper::removeAppFromFolder(const QString &appId, const int &folderId) { if (appId.isEmpty()) { return; } { QMutexLocker locker(&m_mutex); if (!m_folders.contains(folderId)) { return; } Folder &folder = m_folders[folderId]; if (!folder.apps.contains(appId)) { return; } folder.apps.removeOne(appId); } if (m_folders[folderId].getApps().isEmpty()) { deleteFolder(folderId); } Q_EMIT folderDataChanged(folderId); } bool AppFolderHelper::deleteFolder(const int& folderId) { { QMutexLocker locker(&m_mutex); if (!m_folders.contains(folderId)) { return false; } if (!m_folders.remove(folderId)) { return false; } } Q_EMIT folderDeleted(folderId); EventTrack::instance()->sendDefaultEvent("delete_folder", "AppView"); return true; } void AppFolderHelper::renameFolder(const int &folderId, const QString &folderName) { { QMutexLocker locker(&m_mutex); if (!m_folders.contains(folderId)) { return; } if (m_folders[folderId].name == folderName) { return; } m_folders[folderId].name = folderName; } Q_EMIT folderDataChanged(folderId); forceSync(); } QList AppFolderHelper::folderData() { QMutexLocker locker(&m_mutex); return m_folders.values(); } bool AppFolderHelper::searchFolder(const int &folderId, Folder &folder) { QMutexLocker locker(&m_mutex); if (!m_folders.contains(folderId)) { return false; } const Folder &tmp = m_folders[folderId]; folder = tmp; return true; } bool AppFolderHelper::searchFolderByAppName(const QString &appId, Folder &folder) { if (appId.isEmpty()) { return false; } QMutexLocker locker(&m_mutex); for (const auto &tmp: m_folders) { if (tmp.apps.contains(appId)) { folder = tmp; return true; } } return false; } bool AppFolderHelper::containFolder(int folderId) { QMutexLocker locker(&m_mutex); return m_folders.contains(folderId); } bool AppFolderHelper::containApp(const QString &appId) { QMutexLocker locker(&m_mutex); return std::any_of(m_folders.constBegin(), m_folders.constEnd(), [appId] (const Folder &folder) { return folder.apps.contains(appId); }); } void AppFolderHelper::forceSync() { saveData(); //readData(); } void AppFolderHelper::readData() { QFile file(s_folderConfigFile); if (!file.open(QFile::ReadOnly)) { return; } // 读取json数据 QByteArray byteArray = file.readAll(); file.close(); QJsonDocument jsonDocument(QJsonDocument::fromJson(byteArray)); if (jsonDocument.isNull() || jsonDocument.isEmpty() || !jsonDocument.isArray()) { qWarning() << "AppFolderHelper: Incorrect configuration files are ignored."; return; } { QMutexLocker locker(&m_mutex); m_folders.clear(); } // 遍历json数据节点 QJsonArray jsonArray = jsonDocument.array(); for (const auto &value : jsonArray) { QJsonObject object = value.toObject(); if (object.contains("name") && object.contains("id") && object.contains("apps")) { Folder folder; folder.name = object.value(QLatin1String("name")).toString(); folder.id = object.value(QLatin1String("id")).toInt(); QJsonArray apps = object.value(QLatin1String("apps")).toArray(); for (const auto &app : apps) { folder.apps.append(app.toString()); } if (!folder.apps.isEmpty()) { insertFolder(folder); } } } } void AppFolderHelper::saveData() { QFile file(s_folderConfigFile); if (!file.open(QFile::WriteOnly)) { return; } QJsonDocument jsonDocument; QJsonArray folderArray; { QMutexLocker locker(&m_mutex); for (const auto &folder : m_folders) { QJsonObject object; QJsonArray apps; for (const auto &app : folder.apps) { apps.append(app); } object.insert("name", folder.name); object.insert("id", folder.id); object.insert("apps", apps); folderArray.append(object); } } jsonDocument.setArray(folderArray); if (file.write(jsonDocument.toJson()) == -1) { qWarning() << "Error saving configuration file."; } file.flush(); file.close(); } QStringList AppFolderHelper::folderIcon(const Folder &folder) { // TODO: 使用绘图API生成图片 QStringList icons; DataEntity app; int count = qMin(folder.apps.count(), FOLDER_MAX_ICON_NUM); for (int i = 0; i < count; ++i) { if (AppDataManager::instance()->getApp(folder.apps.at(i), app)) { icons.append(app.icon()); } } return icons; } } // UkuiMenu ukui-menu/src/appdata/app-icon-provider.cpp0000664000175000017500000000735715160463353017704 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "app-icon-provider.h" #include #include #include #include namespace UkuiMenu { QSize AppIconProvider::s_defaultSize = QSize(128, 128); AppIconProvider::AppIconProvider() : QQuickImageProvider(QQmlImageProviderBase::Pixmap) { } QPixmap AppIconProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) { return AppIconProvider::getPixmap(id, size, requestedSize); } QPixmap AppIconProvider::getPixmap(const QString &id, QSize *size, const QSize &requestedSize) { QIcon icon; loadIcon(id, icon); QPixmap pixmap = icon.pixmap(requestedSize.isEmpty() ? s_defaultSize : requestedSize); if (size) { QSize pixmapSize = pixmap.size(); size->setWidth(pixmapSize.width()); size->setHeight(pixmapSize.height()); } return pixmap; } void AppIconProvider::loadIcon(const QString &id, QIcon &icon) { if (id.isEmpty()) { loadDefault(icon); return; } bool isOk; QUrl url(id); if (!url.scheme().isEmpty()) { isOk = loadIconFromUrl(url, icon); } else if (id.startsWith(QLatin1String("/")) || id.startsWith(QLatin1String(":/"))) { //qrc path: :/xxx/xxx.png isOk = loadIconFromPath(id, icon); } else { isOk = loadIconFromTheme(id, icon); if (!isOk) { isOk = loadIconFromXdg(id, icon); } } if (!isOk) { loadDefault(icon); } } // see: https://doc.qt.io/archives/qt-5.12/qurl.html#details bool AppIconProvider::loadIconFromUrl(const QUrl &url, QIcon &icon) { QString path = url.path(); if (path.isEmpty()) { qWarning() << "Error: loadIconFromUrl, path is empty."; return false; } // qrc example: the Path ":/images/cut.png" or the URL "qrc:///images/cut.png" // see: https://doc.qt.io/archives/qt-5.12/resources.html if (url.scheme() == QLatin1String("qrc")) { path.prepend(QLatin1String(":")); return loadIconFromPath(path, icon); } // net? return false; } bool AppIconProvider::loadIconFromPath(const QString &path, QIcon &icon) { if (!QFile::exists(path)) { qWarning() << "Error: loadIconFromPath, File dose not exists." << path; return false; } QPixmap filePixmap; bool isOk = filePixmap.load(path); if (!isOk) { return false; } icon.addPixmap(filePixmap); return true; } bool AppIconProvider::loadIconFromTheme(const QString &name, QIcon &icon) { if (!QIcon::hasThemeIcon(name)) { return false; } icon = QIcon::fromTheme(name); return true; } void AppIconProvider::loadDefault(QIcon &icon) { if (!loadIconFromTheme("application-x-desktop", icon)) { loadIconFromPath(":/res/icon/application-x-desktop.png", icon); } } bool AppIconProvider::loadIconFromXdg(const QString &name, QIcon &icon) { icon = XdgIcon::fromTheme(name); if (icon.isNull()) { qWarning() << "Error: loadIconFromXdg, icon dose not exists. name:" << name; return false; } return true; } } // UkuiMenu ukui-menu/src/settings/0000775000175000017500000000000015160463365014057 5ustar fengfengukui-menu/src/settings/settings.cpp0000664000175000017500000002140215160463365016422 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "settings.h" #include "libkypackages.h" #include #include #include #include #include #define UKUI_MENU_SCHEMA "org.ukui.menu.settings" #define CONTROL_CENTER_SETTING "org.ukui.control-center.personalise" #define CONTROL_CENTER_TRANSPARENCY_KEY "transparency" #define CONTROL_CENTER_EFFECT_KEY "effect" #define UKUI_STYLE_SCHEMA "org.ukui.style" #define UKUI_STYLE_NAME_KEY "styleName" #define UKUI_STYLE_THEME_COLOR_KEY "themeColor" #define UKUI_STYLE_SYSTEM_FONT_SIZE "systemFontSize" #define UKUI_STYLE_ICON_THEME_NAME_KEY "iconThemeName" namespace UkuiMenu { void SettingModule::defineModule(const char *uri, int versionMajor, int versionMinor) { qRegisterMetaType("GlobalSetting::Key"); qmlRegisterUncreatableType(uri, versionMajor, versionMinor, "GlobalSetting", "Use enums only."); } GlobalSetting *GlobalSetting::instance() { static GlobalSetting setting(nullptr); return &setting; } GlobalSetting::GlobalSetting(QObject *parent) : QObject(parent) { initStyleSetting(); initGlobalSettings(); initUSDSetting(); } void GlobalSetting::initStyleSetting() { m_cache.insert(StyleName, UKUI_STYLE_VALUE_LIGHT); m_cache.insert(ThemeColor, UKUI_STYLE_THEME_COLOR_KEY); m_cache.insert(SystemFontSize, UKUI_STYLE_SYSTEM_FONT_SIZE); m_cache.insert(Transparency, 1); m_cache.insert(EffectEnabled, false); if (QGSettings::isSchemaInstalled(UKUI_STYLE_SCHEMA)) { QGSettings *settings = new QGSettings(UKUI_STYLE_SCHEMA, {}, this); QStringList keys = settings->keys(); if (keys.contains(UKUI_STYLE_NAME_KEY)) { m_cache.insert(StyleName, settings->get(UKUI_STYLE_NAME_KEY)); } if (keys.contains(UKUI_STYLE_THEME_COLOR_KEY)) { m_cache.insert(ThemeColor,settings->get(UKUI_STYLE_THEME_COLOR_KEY)); } if (keys.contains(UKUI_STYLE_SYSTEM_FONT_SIZE)) { m_cache.insert(SystemFontSize,settings->get(UKUI_STYLE_SYSTEM_FONT_SIZE)); } connect(settings, &QGSettings::changed, this, [this, settings] (const QString &key) { if (key == UKUI_STYLE_NAME_KEY) { updateData(StyleName, settings->get(key)); Q_EMIT styleChanged(StyleName); } else if (key == UKUI_STYLE_THEME_COLOR_KEY) { updateData(ThemeColor, settings->get(key)); Q_EMIT styleChanged(ThemeColor); } else if (key == UKUI_STYLE_SYSTEM_FONT_SIZE) { updateData(SystemFontSize, settings->get(key)); Q_EMIT styleChanged(SystemFontSize); } else if (key == UKUI_STYLE_ICON_THEME_NAME_KEY) { updateData(IconThemeName, settings->get(key)); Q_EMIT styleChanged(IconThemeName); } }); } if (QGSettings::isSchemaInstalled(CONTROL_CENTER_SETTING)) { QGSettings *settings = new QGSettings(CONTROL_CENTER_SETTING,{},this); QStringList keys = settings->keys(); if (keys.contains(CONTROL_CENTER_TRANSPARENCY_KEY)) { m_cache.insert(Transparency, settings->get(CONTROL_CENTER_TRANSPARENCY_KEY).toReal()); } if (keys.contains(CONTROL_CENTER_EFFECT_KEY)) { m_cache.insert(EffectEnabled, settings->get(CONTROL_CENTER_EFFECT_KEY).toBool()); } connect(settings, &QGSettings::changed, this, [this, settings] (const QString &key) { if (key == CONTROL_CENTER_TRANSPARENCY_KEY) { updateData(Transparency, settings->get(key).toReal()); Q_EMIT styleChanged(Transparency); } if (key == CONTROL_CENTER_EFFECT_KEY) { updateData(EffectEnabled, settings->get(key).toReal()); Q_EMIT styleChanged(EffectEnabled); } }); } } QVariant GlobalSetting::get(const GlobalSetting::Key& key) { QMutexLocker locker(&mutex); return m_cache.value(key); } void GlobalSetting::updateData(const GlobalSetting::Key &key, const QVariant &value) { QMutexLocker locker(&mutex); m_cache.insert(key, value); } bool GlobalSetting::isSystemApp(const QString &appid) const { // return std::any_of(m_systemApps.constBegin(), m_systemApps.constEnd(), [&appid](const QString &appName) { // return appid.contains(appName); // }); bool removable = kdk_package_is_removable_by_desktop(appid.toUtf8().constData()); qDebug() << appid << "removable from kdk_package is" << removable; return !removable; } QStringList GlobalSetting::systemApps() const { return m_systemApps; } bool GlobalSetting::isDefaultFavoriteApp(const QString &appid) const { return std::any_of(m_defaultFavoriteApps.constBegin(), m_defaultFavoriteApps.constEnd(), [&appid](const QString &id) { return appid == id; }); } QStringList GlobalSetting::defaultFavoriteApps() const { return m_defaultFavoriteApps; } void GlobalSetting::initGlobalSettings() { if (QFile::exists(UKUI_MENU_GLOBAL_CONFIG_FILE)) { QSettings settings(UKUI_MENU_GLOBAL_CONFIG_FILE, QSettings::IniFormat); settings.beginGroup("System Apps"); m_systemApps.append(settings.allKeys()); settings.endGroup(); //= settings.beginGroup("Default Favorite Apps"); QMap apps; for (const auto &key : settings.allKeys()) { apps.insert(key.toInt(), settings.value(key).toString()); } for (const auto &app : apps) { m_defaultFavoriteApps.append(app); } settings.endGroup(); } } void GlobalSetting::initUSDSetting() { m_cache.insert(IsLiteMode, false); const QString service = QStringLiteral("org.ukui.SettingsDaemon"); const QString path = QStringLiteral("/GlobalSignal"); const QString interface = QStringLiteral("org.ukui.SettingsDaemon.GlobalSignal"); QDBusInterface dBusInterface(service, path, interface); if (dBusInterface.isValid()) { QDBusReply reply = dBusInterface.call(QStringLiteral("getUKUILiteAnimation")); if (reply.isValid()) { QMap m; m.insert("animation", reply.value()); onSysModeChanged(m); } } QDBusConnection::sessionBus().connect(service, path, interface, "UKUILiteChanged", this, SLOT(onSysModeChanged)); } void GlobalSetting::onSysModeChanged(QMap map) { if (map.contains("animation")) { m_cache.insert(IsLiteMode, (map.value("animation").toString() == QStringLiteral("lite"))); Q_EMIT styleChanged(IsLiteMode); } } MenuSetting *MenuSetting::instance() { static MenuSetting setting(nullptr); return &setting; } MenuSetting::MenuSetting(QObject *parent) : QObject(parent) { initSettings(); } void MenuSetting::initSettings() { m_cache.insert(UKUI_MENU_ANIMATION_DURATION, {300}); m_cache.insert(FOLLOW_UKUI_PANEL, {true}); m_cache.insert(MENU_WIDTH, {652}); m_cache.insert(MENU_HEIGHT, {540}); m_cache.insert(MENU_MARGIN, {8}); QByteArray id{UKUI_MENU_SCHEMA}; if (QGSettings::isSchemaInstalled(id)) { m_gSettings = new QGSettings(id, "/", this); if (!m_gSettings) { qWarning() << "Unable to initialize settings."; return; } for (const auto &key: m_gSettings->keys()) { m_cache.insert(key, m_gSettings->get(key)); } connect(m_gSettings, &QGSettings::changed, this, &MenuSetting::updateValue); } } QVariant MenuSetting::get(const QString &key) { QMutexLocker locker(&mutex); return m_cache.value(key, {}); } bool MenuSetting::set(const QString &key, const QVariant &value) { QMutexLocker locker(&mutex); if (m_gSettings) { m_gSettings->set(key, value); // m_cache.insert(key,value); // Q_EMIT changed(key); return true; } return false; } void MenuSetting::updateValue(const QString &key) { mutex.lock(); m_cache.insert(key, m_gSettings->get(key)); mutex.unlock(); Q_EMIT changed(key); } } // UkuiMenu ukui-menu/src/settings/user-config.h0000664000175000017500000000313215160463353016445 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_USER_CONFIG_H #define UKUI_MENU_USER_CONFIG_H #include #include #include namespace UkuiMenu { class UserConfig : public QObject { Q_OBJECT public: static const QString configFilePath; static const QString configFileName; static UserConfig *instance(); bool isFirstStartUp() const; QSet preInstalledApps() const; void addPreInstalledApp(const QString &appid); void removePreInstalledApp(const QString &appid); bool isPreInstalledApps(const QString &appid) const; void initPreInstalledLists(QStringList apps); void sync(); private: explicit UserConfig(QObject *parent=nullptr); void init(); void initConfigFile(); void readConfigFile(); void writeConfigFile(); private: bool m_isFirstStartUp {false}; QSet m_preInstalledApps; }; } // UkuiMenu #endif //UKUI_MENU_USER_CONFIG_H ukui-menu/src/settings/settings.h0000664000175000017500000000636615160463353016100 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_SETTINGS_H #define UKUI_MENU_SETTINGS_H #include #include #include #include #include #define UKUI_STYLE_VALUE_DARK "ukui-dark" #define UKUI_STYLE_VALUE_LIGHT "ukui-light" #define UKUI_STYLE_VALUE_DEFAULT "ukui-default" // ukui-menu settings #define UKUI_MENU_ANIMATION_DURATION "animationDuration" #define FOLLOW_UKUI_PANEL "followUkuiPanel" #define MENU_WIDTH "width" #define MENU_HEIGHT "height" #define MENU_MARGIN "margin" namespace UkuiMenu { class SettingModule { public: static void defineModule(const char *uri, int versionMajor, int versionMinor); }; class GlobalSetting : public QObject { Q_OBJECT public: enum Key { UnKnowKey = 0, StyleName, ThemeColor, IconThemeName, Transparency, EffectEnabled, SystemFontSize, IsLiteMode }; Q_ENUM(Key) static GlobalSetting *instance(); GlobalSetting() = delete; GlobalSetting(const GlobalSetting& obj) = delete; GlobalSetting(const GlobalSetting&& obj) = delete; QVariant get(const GlobalSetting::Key& key); bool isSystemApp(const QString &appid) const; QStringList systemApps() const; bool isDefaultFavoriteApp(const QString &appid) const; QStringList defaultFavoriteApps() const; Q_SIGNALS: void styleChanged(const GlobalSetting::Key& key); private Q_SLOTS: void onSysModeChanged(QMap map); private: explicit GlobalSetting(QObject *parent = nullptr); void initStyleSetting(); void initGlobalSettings(); void initUSDSetting(); void updateData(const GlobalSetting::Key& key, const QVariant &value); private: QMutex mutex; QMap m_cache; QStringList m_systemApps; QStringList m_defaultFavoriteApps; }; class MenuSetting : public QObject { Q_OBJECT public: static MenuSetting *instance(); MenuSetting() = delete; MenuSetting(const MenuSetting& obj) = delete; MenuSetting(const MenuSetting&& obj) = delete; Q_INVOKABLE QVariant get(const QString& key); Q_INVOKABLE bool set(const QString& key, const QVariant& value); Q_SIGNALS: void changed(const QString& key); private Q_SLOTS: void updateValue(const QString& key); private: explicit MenuSetting(QObject *parent = nullptr); void initSettings(); private: QMutex mutex; QMap m_cache; QGSettings *m_gSettings{nullptr}; }; } // UkuiMenu #endif //UKUI_MENU_SETTINGS_H ukui-menu/src/settings/user-config.cpp0000664000175000017500000001006515160463353017003 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #include "user-config.h" #include #include #include #include #include #include #define PRE_INSTALLED_APPS_KEY "PreInstalledApps" namespace UkuiMenu { const QString UserConfig::configFilePath = QDir::homePath() + "/.config/ukui-menu/"; const QString UserConfig::configFileName = "config.json"; UserConfig *UserConfig::instance() { static UserConfig userConfig; return &userConfig; } UserConfig::UserConfig(QObject *parent) : QObject(parent) { init(); } bool UserConfig::isFirstStartUp() const { return m_isFirstStartUp; } void UserConfig::init() { QFile configFile(configFilePath + configFileName); if ((m_isFirstStartUp = !configFile.exists())) { if (!QDir(configFilePath).exists() && !QDir().mkdir(configFilePath)) { qWarning() << "Could not create the user profile directory"; return; } initConfigFile(); return; } // read readConfigFile(); } void UserConfig::initConfigFile() { QFile configFile(configFilePath + configFileName); configFile.open(QFile::WriteOnly); configFile.close(); } void UserConfig::sync() { writeConfigFile(); } QSet UserConfig::preInstalledApps() const { return m_preInstalledApps; } void UserConfig::addPreInstalledApp(const QString &appid) { m_preInstalledApps.insert(appid); } bool UserConfig::isPreInstalledApps(const QString &appid) const { return m_preInstalledApps.contains(appid); } void UserConfig::initPreInstalledLists(QStringList apps) { for (const auto &app : apps) { m_preInstalledApps.insert(app); } sync(); } void UserConfig::removePreInstalledApp(const QString &appid) { m_preInstalledApps.remove(appid); sync(); } void UserConfig::readConfigFile() { m_preInstalledApps.clear(); QFile file(configFilePath + configFileName); if (!file.open(QFile::ReadOnly)) { return; } QByteArray byteArray = file.readAll(); file.close(); QJsonDocument jsonDocument(QJsonDocument::fromJson(byteArray)); if (jsonDocument.isNull() || jsonDocument.isEmpty() || !jsonDocument.isArray()) { qWarning() << "UserConfig: Incorrect configuration files are ignored."; return; } QJsonArray jsonArray = jsonDocument.array(); for (const auto &value : jsonArray) { QJsonObject object = value.toObject(); // 预装app if (object.contains(PRE_INSTALLED_APPS_KEY)) { QJsonArray apps = object.value(QLatin1String(PRE_INSTALLED_APPS_KEY)).toArray(); for (const auto &app : apps) { m_preInstalledApps.insert(app.toString()); } } } } void UserConfig::writeConfigFile() { QFile file(configFilePath + configFileName); if (!file.open(QFile::WriteOnly)) { return; } QJsonDocument jsonDocument; QJsonArray configArray; // preInstalledApps QJsonObject preInstalledAppsObject; QJsonArray apps; for (const auto &appid : m_preInstalledApps) { apps.append(appid); } preInstalledAppsObject.insert(PRE_INSTALLED_APPS_KEY, apps); configArray.append(preInstalledAppsObject); jsonDocument.setArray(configArray); if (file.write(jsonDocument.toJson()) == -1) { qWarning() << "Error saving configuration file."; } file.flush(); file.close(); } } // UkuiMenu ukui-menu/src/data-entity.h0000664000175000017500000001061015160463353014606 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_DATA_ENTITY_H #define UKUI_MENU_DATA_ENTITY_H #include #include #include #include class DataEntityPrivate; namespace UkuiMenu { class DataType { Q_GADGET public: enum Type { Normal, // 普通Item Label, // 标签数据 Folder, // 应用组 Files // 文件夹 }; Q_ENUM(Type); }; // 应用数据 class DataEntity { Q_GADGET Q_PROPERTY(DataType::Type type READ type WRITE setType) Q_PROPERTY(QString id READ id) Q_PROPERTY(QString icon READ icon WRITE setIcon) Q_PROPERTY(QString name READ name WRITE setName) Q_PROPERTY(QString comment READ comment WRITE setComment) Q_PROPERTY(QString extraData READ extraData WRITE setExtraData) public: static QHash AppRoleNames(); enum PropertyName { Id = 0, Type, Icon, Name, Comment, ExtraData, Category, /**> 应用类别 */ Group, /**> 应用分组 */ FirstLetter, /**> 应用名称首字母 */ InstallationTime, /**> 安装时间 format: yyyy-MM-dd hh:mm:ss */ IsLaunched, /**> 是否启动过该程序 */ LaunchTimes, /**> 应用被启动的次数 */ IsLocked, /**> 应用是否锁定 */ Favorite, /**> 是否被收藏及序号, 小于或等于0表示未被收藏 */ Top, /**> 是否被置顶及序号, 小于或等于0表示未被置顶 */ RecentInstall, Entity, /**> 返回自己的拷贝 */ Removable, /**> 应用是否可卸载 */ DesktopName /**> 应用desktop文件名称 */ }; DataEntity(); DataEntity(DataType::Type type, const QString& name, const QString& icon, const QString& comment, const QString& extraData); virtual ~DataEntity(); DataEntity(const DataEntity& other); DataEntity &operator=(const DataEntity& other); DataEntity(DataEntity&& other) noexcept; DataEntity &operator=(DataEntity&& other) noexcept; QVariant getValue(DataEntity::PropertyName property) const; void setValue(DataEntity::PropertyName property, const QVariant &value); int top() const; void setTop(int top); bool isLock() const; void setLock(bool lock); bool isRecentInstall() const; void setRecentInstall(bool recentInsatll); int launched() const; void setLaunched(int launched); int favorite() const; void setFavorite(int favorite); int launchTimes() const; void setLaunchTimes(int launchTimes); double priority() const; void setPriority(double priority); QString insertTime() const; void setInsertTime(const QString& insertTime); QString id() const; void setId(const QString& id); void setCategory(const QString& category); QString category() const; void setFirstLetter(const QString& firstLetter); QString firstLetter() const; void setType(DataType::Type type); DataType::Type type() const; void setIcon(const QString& icon); QString icon() const; void setName(const QString& name); QString name() const; void setComment(const QString& comment); QString comment() const; void setExtraData(const QString& extraData); QString extraData() const; void setGroup(const QString& group); QString group() const; void setRemovable(bool removable); bool removable() const; void setDesktopName(const QString& desktopName); QString desktopName() const; private: DataEntityPrivate *d {nullptr}; }; } // UkuiMenu Q_DECLARE_METATYPE(UkuiMenu::DataType) Q_DECLARE_METATYPE(UkuiMenu::DataEntity) #endif //UKUI_MENU_DATA_ENTITY_H ukui-menu/src/ukui-menu-application.h0000664000175000017500000000421015160463353016602 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_UKUI_MENU_APPLICATION_H #define UKUI_MENU_UKUI_MENU_APPLICATION_H #include class QQmlEngine; class QCommandLineOption; namespace UkuiMenu { class MenuMessageProcessor; class MenuWindow; class MenuDbusService; class UkuiMenuApplication : public QObject { Q_OBJECT public: enum Command { Active = 0, Show, Quit, Hide }; explicit UkuiMenuApplication(MenuMessageProcessor *processor); ~UkuiMenuApplication() override; UkuiMenuApplication() = delete; UkuiMenuApplication(const UkuiMenuApplication& obj) = delete; UkuiMenuApplication(const UkuiMenuApplication&& obj) = delete; private Q_SLOTS: void execCommand(UkuiMenuApplication::Command command); private: static void registerQmlTypes(); void startUkuiMenu(); void initQmlEngine(); void loadMenuUI(); //注册dubs void initDbusService(); private: QQmlEngine *m_engine{nullptr}; MenuWindow *m_mainWindow{nullptr}; MenuDbusService *m_menuDbusService{nullptr}; }; class MenuMessageProcessor : public QObject { Q_OBJECT public: MenuMessageProcessor(); static bool preprocessMessage(const QStringList& message); static QCommandLineOption showUkuiMenu; static QCommandLineOption quitUkuiMenu; public Q_SLOTS: void processMessage(const QString &message); Q_SIGNALS: void request(UkuiMenuApplication::Command command); }; } #endif //UKUI_MENU_UKUI_MENU_APPLICATION_H ukui-menu/src/commons.h0000664000175000017500000000177115160463353014046 0ustar fengfeng/* * Copyright (C) 2022, KylinSoft Co., Ltd. * * 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 3 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, see . * */ #ifndef UKUI_MENU_COMMONS_H #define UKUI_MENU_COMMONS_H #include "data-entity.h" namespace UkuiMenu { class Display { Q_GADGET public: enum Type { IconOnly, TextOnly, TextBesideIcon, TextUnderIcon }; Q_ENUM(Type) }; } // UkuiMenu #endif //UKUI_MENU_COMMONS_H ukui-menu/translations/0000775000175000017500000000000015160463365014151 5ustar fengfengukui-menu/translations/ukui-menu_fr_FR.ts0000664000175000017500000005554315160463365017532 0ustar fengfeng EditText Folder FavoriteExtension Enable editing mode Activer le mode d’édition par lots Remove all favorite apps Ignorer toutes les applications favorites Editing mode FolderGridView Folder FullScreenAppList Enable editing mode Activer le mode d’édition par lots Remove all favorite apps Ignorer toutes les applications favorites All Applications Editing mode Favorite collection FullScreenHeader Contract rétrécir PluginSelectButton Letter Sort Ordre alphabétique Category Type de caractéristiques QObject Add to favorite Remove from folder Supprimé du groupe d’applications Move to folder Add to folder Ajoutez-le à un groupe d’applications Add to "%1" Ajouter à « %1 » Dissolve folder Dissoudre le groupe d’applications Fix to favorite Épingler aux favoris Remove from favorite Détacher l’épinglage des favoris Fixed to all applications Épingler à toutes les applications Unfixed from all applications Détacher l’épinglage de toutes les applications Remove from taskbar Détacher de la barre des tâches Add to taskbar Épingler à la barre des tâches Send to desktop shortcuts Add to desktop shortcuts Ajouter aux raccourcis du bureau Uninstall décharger Show ukui-menu. Le menu Démarrer s’affiche. Quit ukui-menu. Quittez le menu Démarrer. Create a new folder Créer un groupe d’applications Remove from List Retirer de la liste SearchInputBar Search App Rechercher l’application Sidebar Expand déplier Contract rétrécir Computer Ordinateur Settings Installer UkuiMenu::AllAppDataProvider All Toutes les applications All applications Toutes les applications UkuiMenu::AppCategoryModel Audio audio AudioVideo Audio et vidéo Development exploitation Education éduquer Game Jeu Graphics graphisme Network Internet Office Bureau Science science Settings Installer System système Utility Utilitaires Video Vidéo Other autre UkuiMenu::AppCategoryPlugin Category Type de caractéristiques Audio audio AudioVideo Audio et vidéo Development exploitation Education éduquer Game Jeu Graphics graphisme Network Internet Office Bureau Science science Settings Installer System système Utility Utilitaires Video Vidéo Other autre Open Function Sort Menu Ouvrez le menu Tri des fonctions All Applications Letter Sort Ordre alphabétique Recently Installed Dernière installation UkuiMenu::AppFolderHelper New Folder %1 Nouveau dossier %1 UkuiMenu::AppLetterSortPlugin Letter Sort Ordre alphabétique Open Letter Sort Menu Ouvrir le menu de tri alphabétique UkuiMenu::AppSearchPlugin Search Rechercher UkuiMenu::FavoriteExtension Favorite collection UkuiMenu::FavoriteFolderHelper New Folder %1 Appliquer le groupe %1 UkuiMenu::FavoriteWidget Favorite collection favorite collection UkuiMenu::PowerButton Power alimentation Switch user Changer d’utilisateur Hibernate dormance Suspend dormir Lock Screen Verrouiller votre écran Log Out déconnexion Restart Redémarrer Shut Down Arrêt <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>Éteignez votre ordinateur, mais l’application reste ouverte. Lorsque vous allumez votre PC, vous pouvez revenir à l’état que vous avez laissé</p> <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>L’ordinateur reste allumé, mais consomme moins d’énergie. L’application reste ouverte, ce qui vous permet de réveiller rapidement votre PC et de revenir à l’état que vous avez laissé</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>L’utilisateur actuel se déconnecte du système, met fin à sa session et revient à l’écran de connexion</p> Upgrade and Shut Down Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart ,and then restart the computer <p>Close all applications, turn off the computer, and then turn it back on</p> <p>Fermez toutes les applications, éteignez votre PC, puis rallumez-le</p> (Estimated %1 hour %2 minutes) (Estimated %1 minutes) <p>Close all applications, and then turn off the computer</p> <p>Fermez toutes les apps, puis éteignez votre PC</p> UkuiMenu::RecentlyInstalledModel Recently Installed Dernière installation UkuiMenu::SidebarButtonUtils Change account settings ukui-menu/translations/ukui-menu_zh_HK.ts0000664000175000017500000005250515160463365017532 0ustar fengfeng EditText Folder FavoriteExtension Editing mode 批量編輯 FolderGridView Folder FullScreenAppList All Applications 所有應用 Editing mode 批量編輯 Favorite 收藏 FullScreenHeader Contract 合同 PluginSelectButton Letter Sort 字母排序 Category 類別 QObject Send to desktop shortcuts 發送到桌面快捷方式 Uninstall 卸載 Remove from folder 從應用組中刪除 Move to folder 移動到應用組 Dissolve folder 解散應用組 Add to "%1" 添加到“%1” Show ukui-menu. 顯示 ukui-menu。 Quit ukui-menu. 退出 ukui-menu。 Add to favorite 添加到收藏 Fix to favorite Remove from favorite 取消收藏 Remove from taskbar 從任務列取消固定 Add to taskbar 固定到任務列 Fixed to all applications 固定到所有應用 Unfixed from all applications 從所有應用取消固定 Create a new folder 創建新應用組 Remove from List 從清單中删除 SearchInputBar Search App 搜索應用 Sidebar Expand 擴大 Contract 合同 Computer 計算機 Settings 設置 UkuiMenu::AllAppDataProvider All 所有應用 All applications 所有应用 所有應用 UkuiMenu::AppCategoryModel Audio 音訊 AudioVideo 音訊視頻 Development 發展 Education 教育 Game 遊戲 Graphics 圖形 Network 網路 Office 辦公室 Science 科學 Settings 設置 System 系統 Utility 效用 Video 視頻 Other 其他 UkuiMenu::AppCategoryPlugin Category 功能排序 類別 Audio 音訊 AudioVideo 音訊視頻 Development 發展 Education 教育 Game 遊戲 Graphics 圖形 Network 網路 Office 辦公室 Science 科學 Settings 設置 System 系統 Utility 效用 Video 視頻 Other 其他 Open Function Sort Menu 打開功能排序功能表 Letter Sort 字母排序 Recently Installed 最近安装 All Applications 所有應用 UkuiMenu::AppFolderHelper New Folder %1 新建應用組 %1 UkuiMenu::AppLetterSortPlugin Letter Sort 字母排序 Open Letter Sort Menu 打開字母排序功能表 UkuiMenu::AppSearchPlugin Search 搜索 UkuiMenu::FavoriteExtension Favorite 收藏 UkuiMenu::FavoriteFolderHelper New Folder %1 新建應用組 %1 UkuiMenu::FavoriteWidget Favorite 收藏 favorite 收藏 UkuiMenu::PowerButton Power 電源 Hibernate 休眠 Suspend 睡眠 Lock Screen 鎖定荧幕 Log Out 註銷 Restart 重啟 Shut Down 關機 <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>關閉電腦,但是應用會一直保持打開狀態,當打開電腦時,可以恢復到你離開的狀態</p> <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>計算機保持打開狀態,但功耗較低,應用程序將保持打開狀態。您可以快速喚醒計算機並返回到您離開的狀態</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>當前使用者從系統中註銷,結束其會話並返回登錄介面</p> Upgrade and Shut Down Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart ,and then restart the computer <p>Close all applications, turn off the computer, and then turn it back on</p> <p>關閉所有應用,關閉電腦,然後重新打開電腦</p> (Estimated %1 hour %2 minutes) (Estimated %1 minutes) <p>Close all applications, and then turn off the computer</p> <p>關閉所有應用,然後關閉電腦</p> UkuiMenu::RecentlyInstalledModel Recently Installed 最近安裝 UkuiMenu::SidebarButtonUtils Change account settings ukui-menu/translations/ukui-menu_mn.ts0000664000175000017500000007156615160463365017151 0ustar fengfeng EditText Folder ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠤ᠋ ᠪᠦᠯᠦᠭ FavoriteExtension Enable editing mode ᠪᠦᠬᠡᠮ ᠵᠢᠡᠷ ᠨᠠᠢᠷᠠᠭᠤᠯᠬᠤ ᠮᠤᠳ᠋ᠸᠯ ᠢ᠋ ᠡᠬᠢᠯᠡᠬᠦᠯᠬᠦ Remove all favorite apps ᠨᠢᠭᠡᠨᠳᠡ ᠬᠠᠳᠠᠭᠠᠯᠠᠭᠰᠠᠨ ᠪᠦᠬᠦ ᠬᠡᠷᠡᠭᠯᠡᠭᠡ ᠵᠢ ᠳᠠᠢᠯᠠᠬᠤ Editing mode FolderGridView Folder ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠤ᠋ ᠪᠦᠯᠦᠭ FullScreenAppList Editing mode Favorite ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ Enable editing mode ᠪᠦᠬᠡᠮ ᠵᠢᠡᠷ ᠨᠠᠢᠷᠠᠭᠤᠯᠬᠤ ᠮᠤᠳ᠋ᠸᠯ ᠢ᠋ ᠡᠬᠢᠯᠡᠬᠦᠯᠬᠦ Remove all favorite apps ᠨᠢᠭᠡᠨᠳᠡ ᠬᠠᠳᠠᠭᠠᠯᠠᠭᠰᠠᠨ ᠪᠦᠬᠦ ᠬᠡᠷᠡᠭᠯᠡᠭᠡ ᠵᠢ ᠳᠠᠢᠯᠠᠬᠤ All Applications ᠪᠦᠬᠦ ᠬᠡᠷᠡᠭᠯᠡᠭᠡ FullScreenHeader Contract ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ PluginSelectButton Letter Sort ᠠᠪᠢᠶᠠᠨ ᠤ᠋ ᠳᠠᠷᠠᠭᠠᠯᠠᠯ Category ᠴᠢᠳᠠᠪᠬᠢ ᠵᠢᠨ ᠳᠠᠷᠠᠭᠠᠯᠠᠯ QObject Add to desktop shortcuts ᠰᠢᠷᠡᠭᠡᠨ ᠨᠢᠭᠤᠷ ᠤ᠋ᠨ ᠳᠦᠳᠡ ᠠᠷᠭ᠎ᠠ ᠳ᠋ᠤ᠌ ᠨᠡᠮᠡᠬᠦ Uninstall ᠰᠣᠹᠲ᠋ᠧᠠᠢᠢᠷ ᠢ᠋ ᠪᠠᠭᠤᠯᠭᠠᠬᠤ Remove from folder ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠤ᠋ ᠪᠦᠯᠦᠭ ᠡᠴᠡ ᠰᠢᠯᠵᠢᠬᠦᠯᠦᠨ ᠬᠠᠰᠤᠬᠤ Add to folder ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠤ᠋ ᠪᠦᠯᠦᠭ ᠲᠤ᠌ ᠨᠡᠮᠡᠬᠦ Dissolve folder ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠤ᠋ ᠪᠦᠯᠦᠭ ᠢ᠋ ᠳᠠᠷᠬᠠᠭᠠᠬᠤ Add to favorite Move to folder Add to "%1" "%1" ᠲᠤ᠌\ ᠳ᠋ᠤ᠌ ᠨᠡᠮᠡᠬᠦ Show ukui-menu. ᠡᠬᠢᠯᠡᠬᠦ ᠲᠣᠪᠶᠣᠭ ᠢ᠋ ᠢᠯᠡᠷᠡᠬᠦᠯᠦᠨ᠎ᠡ᠃ Quit ukui-menu. ᠡᠬᠢᠯᠡᠬᠦ ᠲᠣᠪᠶᠣᠭ ᠡᠴᠡ ᠪᠤᠴᠠᠵᠤ ᠭᠠᠷᠤᠨ᠎ᠠ᠃ Fix to favorite ᠬᠠᠳᠠᠭᠠᠯᠠᠭᠤᠷ ᠲᠤ᠌ ᠳᠤᠭᠳᠠᠭᠠᠬᠤ Remove from favorite ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ ᠬᠠᠪᠢᠰᠤᠨ ᠳ᠋ᠤ᠌ ᠳᠤᠭᠳᠠᠭᠠᠭᠰᠠᠨ ᠢ᠋ ᠦᠬᠡᠢᠰᠬᠡᠬᠦ Remove from taskbar ᠡᠬᠦᠷᠬᠡ ᠵᠢᠨ ᠪᠠᠭᠠᠷ ᠲᠤ᠌ ᠳᠤᠭᠳᠠᠭᠠᠭᠰᠠᠨ ᠢ᠋ ᠦᠬᠡᠢᠰᠬᠡᠬᠦ Add to taskbar ᠡᠬᠦᠷᠬᠡ ᠵᠢᠨ ᠪᠠᠭᠠᠷ ᠲᠤ᠌ ᠳᠤᠭᠳᠠᠭᠠᠬᠤ Send to desktop shortcuts Fixed to all applications ᠪᠦᠬᠦ ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠳ᠋ᠤ᠌ ᠳᠤᠭᠳᠠᠭᠠᠬᠤ Unfixed from all applications ᠪᠦᠬᠦ ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠳ᠋ᠤ᠌ ᠳᠤᠭᠳᠠᠭᠠᠭᠰᠠᠨ ᠢ᠋ ᠦᠬᠡᠢᠰᠬᠡᠬᠦ Create a new folder ᠬᠡᠷᠡᠭᠯᠡᠬᠡᠨ ᠤ᠋ ᠪᠦᠯᠦᠭ ᠢ᠋ ᠰᠢᠨ᠎ᠡ ᠪᠡᠷ ᠪᠠᠢᠭᠤᠯᠬᠤ Remove from List ᠵᠢᠭᠰᠠᠭᠠᠯᠳᠠ ᠡᠴᠡ ᠤᠰᠠᠳᠬᠠᠠᠬᠤ SearchInputBar Search App ᠬᠠᠢᠯᠳᠠ ᠵᠢᠨ ᠬᠡᠷᠡᠭᠯᠡᠭᠡ Sidebar Expand ᠳᠡᠯᠬᠡᠬᠦ Contract ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ Computer ᠺᠣᠮᠫᠢᠦᠢᠲ᠋ᠧᠷ ᠃ Settings ᠳᠤᠬᠢᠷᠠᠭᠤᠯᠭ᠎ᠠ UkuiMenu::AllAppDataProvider All ᠪᠦᠬᠦᠢᠯᠡ All applications 所有应用 ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡᠨ UkuiMenu::AppCategoryModel Audio ᠠᠦ᠋ᠳᠢᠤ᠋ AudioVideo ᠠᠦ᠋ᠳᠢᠤ᠋ ᠸᠢᠳᠢᠤ᠋ Development ᠨᠡᠬᠡᠬᠡᠨ ᠬᠦᠭᠵᠢᠬᠦᠯᠬᠦ Education ᠰᠤᠷᠭᠠᠨ ᠬᠥᠮᠦᠵᠢᠯ Game ᠲᠣᠭᠯᠠᠭᠠᠮ Graphics ᠭᠷᠡᠹᠢᠺ ᠵᠢᠷᠤᠭ Network ᠨᠧᠲ Office ᠠᠯᠪᠠᠯᠠᠬᠤ Science ᠰᠢᠨᠵᠢᠯᠡᠬᠦ ᠤᠬᠠᠭᠠᠨ Settings ᠲᠤᠬᠢᠷᠠᠭᠤᠯᠬᠤ System ᠰᠢᠰᠲ᠋ᠧᠮ Utility ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠤ᠋ ᠫᠷᠣᠭ᠌ᠷᠠᠮ Video ᠸᠢᠳᠢᠤ᠋ Other ᠪᠤᠰᠤᠳ All Applications ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡᠨ UkuiMenu::AppCategoryPlugin Category 功能排序 ᠴᠢᠳᠠᠪᠬᠢ ᠵᠢᠨ ᠳᠠᠷᠠᠭᠠᠯᠠᠯ Audio ᠠᠦ᠋ᠳᠢᠤ᠋ AudioVideo ᠠᠦ᠋ᠳᠢᠤ᠋ ᠸᠢᠳᠢᠤ᠋ Development ᠨᠡᠬᠡᠬᠡᠨ ᠬᠦᠭᠵᠢᠬᠦᠯᠬᠦ Education ᠰᠤᠷᠭᠠᠨ ᠬᠥᠮᠦᠵᠢᠯ Game ᠲᠣᠭᠯᠠᠭᠠᠮ Graphics ᠭᠷᠡᠹᠢᠺ ᠵᠢᠷᠤᠭ Network ᠨᠧᠲ Office ᠠᠯᠪᠠᠯᠠᠬᠤ Science ᠰᠢᠨᠵᠢᠯᠡᠬᠦ ᠤᠬᠠᠭᠠᠨ Settings ᠲᠤᠬᠢᠷᠠᠭᠤᠯᠬᠤ System ᠰᠢᠰᠲ᠋ᠧᠮ Utility ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠤ᠋ ᠫᠷᠣᠭ᠌ᠷᠠᠮ Video ᠸᠢᠳᠢᠤ᠋ Other ᠪᠤᠰᠤᠳ Open Function Sort Menu ᠴᠢᠳᠠᠪᠬᠢ ᠵᠢᠨ ᠳᠠᠷᠠᠭᠠᠯᠠᠯ ᠤ᠋ᠨ ᠲᠣᠪᠶᠣᠭ ᠢ᠋ ᠨᠡᠬᠡᠬᠡᠬᠦ Letter Sort ᠠᠪᠢᠶᠠᠨ ᠤ᠋ ᠳᠠᠷᠠᠭᠠᠯᠠᠯ Recently Installed ᠬᠠᠮᠤᠭ ᠤ᠋ᠨ ᠰᠢᠨ᠎ᠡ ᠤᠭᠰᠠᠷᠠᠯ All Applications ᠪᠦᠬᠦ ᠬᠡᠷᠡᠭᠯᠡᠭᠡ UkuiMenu::AppFolderHelper New Folder %1 ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠤ᠋ ᠪᠦᠯᠦᠭ %1 UkuiMenu::AppLetterSortPlugin Letter Sort ᠠᠪᠢᠶᠠᠨ ᠤ᠋ ᠳᠠᠷᠠᠭᠠᠯᠠᠯ Open Letter Sort Menu ᠠᠪᠢᠶᠠᠨ ᠤ᠋ ᠳᠠᠷᠠᠭᠠᠯᠠᠯ ᠤ᠋ᠨ ᠲᠣᠪᠶᠣᠭ ᠢ᠋ ᠨᠡᠬᠡᠬᠡᠬᠦ UkuiMenu::AppSearchPlugin Search ᠪᠦᠬᠦ ᠳᠠᠯ᠎ᠠ ᠪᠡᠷ ᠬᠠᠢᠬᠤ UkuiMenu::FavoriteExtension Favorite ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ UkuiMenu::FavoriteFolderHelper New Folder %1 ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠤ᠋ ᠪᠦᠯᠦᠭ %1 UkuiMenu::FavoriteWidget Favorite ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ favorite ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ UkuiMenu::PowerButton Power ᠴᠠᠬᠢᠯᠭᠠᠨ ᠡᠭᠦᠰᠭᠡᠭᠴᠢ Switch user ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ ᠵᠢ ᠰᠣᠯᠢᠬᠤ Hibernate ᠢᠴᠡᠬᠡᠯᠡᠬᠦ᠌ Suspend ᠵᠤᠭ᠍ᠰᠤᠭᠠᠬᠤ Lock Screen ᠳᠡᠯᠭᠡᠴᠡ ᠣᠨᠢᠰᠤᠯᠠᠬᠤ Log Out ᠬᠦᠴᠦᠨ ᠦᠭᠡᠶ ᠪᠣᠯᠭᠠᠬᠤ ,and then restart the computer Restart ᠳᠠᠬᠢᠨ ᠡᠬᠢᠯᠡᠬᠦᠯᠬᠦ Shut Down ᠮᠠᠰᠢᠨ ᠤᠨᠳᠠᠷᠠᠭᠠᠬᠤ <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p> ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠨᠡᠬᠡᠬᠡᠭᠰᠡᠨ ᠬᠡᠪ ᠵᠢᠡᠷ᠂ ᠬᠡᠪᠡᠴᠤ ᠶᠡᠬᠡ ᠴᠠᠬᠢᠯᠭᠠᠨ ᠪᠠᠷᠠᠬᠤ ᠥᠬᠡᠢ᠃ ᠬᠡᠷᠡᠭᠯᠡᠭᠡ ᠵᠢ ᠨᠡᠬᠡᠬᠡᠭᠰᠡᠨ ᠬᠡᠪ ᠵᠢᠡᠷ ᠪᠠᠢᠯᠭᠠᠵᠤ᠂ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ᠋ ᠬᠤᠷᠳᠤᠨ ᠳᠠᠭᠤᠳᠠᠨ ᠰᠡᠷᠡᠬᠡᠬᠦ ᠪᠤᠶᠤ ᠲᠠᠨ ᠢ᠋ ᠰᠠᠯᠬᠤ ᠦᠶ᠎ᠡ ᠵᠢᠨ ᠪᠠᠢᠳᠠᠯ ᠳ᠋ᠤ᠌ ᠬᠤᠷᠳᠤᠨ ᠪᠤᠴᠠᠬᠤ ᠳ᠋ᠤ᠌ ᠳᠤᠰᠠᠳᠠᠢ</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p> ᠣᠳᠣᠬᠠᠨ ᠤ᠋ ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ ᠰᠢᠰᠲ᠋ᠧᠮ ᠡᠴᠡ ᠬᠠᠰᠤᠭᠳᠠᠭᠰᠠᠨ ᠪᠠᠢᠨ᠎ᠠ᠂ ᠶᠠᠷᠢᠯᠴᠠᠭ᠎ᠠ ᠵᠢ ᠳᠠᠭᠤᠰᠬᠠᠵᠤ ᠨᠡᠪᠳᠡᠷᠡᠬᠦ ᠵᠠᠭᠠᠭ ᠭᠠᠳᠠᠷᠢᠬᠤ ᠳ᠋ᠤ᠌ ᠪᠤᠴᠠᠭᠠᠷᠠᠢ</p> <p>Close all applications, turn off the computer, and then turn it back on</p> <p> ᠪᠦᠬᠦ ᠬᠡᠷᠡᠭᠯᠡᠭᠡ ᠵᠢ ᠬᠠᠭᠠᠠᠵᠤ᠂ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ᠋ ᠬᠠᠭᠠᠠᠵᠤ᠂ ᠳᠠᠷᠠᠭ᠎ᠠ ᠨᠢ ᠳᠠᠬᠢᠨ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ᠋ ᠨᠡᠬᠡᠬᠡᠷᠡᠢ</p> <p>Close all applications, and then turn off the computer</p> <p> ᠪᠦᠬᠦ ᠬᠡᠷᠡᠭᠯᠡᠭᠡ ᠵᠢ ᠬᠠᠭᠠᠠᠵᠤ᠂ ᠳᠠᠷᠠᠭ᠎ᠠ ᠨᠢ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ᠋ ᠬᠠᠭᠠᠭᠠᠷᠠᠢ</p> <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p> ᠺᠣᠮᠫᠢᠶᠦ᠋ᠲ᠋ᠧᠷ ᠢ᠋ ᠬᠠᠭᠠᠭᠰᠠᠨ ᠴᠤ᠌ ᠬᠡᠷᠡᠭᠯᠡᠭᠡ ᠨᠡᠬᠡᠬᠡᠭᠰᠡᠨ ᠬᠡᠪ ᠵᠢᠡᠷ ᠪᠠᠢᠬᠤ ᠪᠤᠯᠤᠨ᠎ᠠ᠃ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ᠋ ᠨᠡᠬᠡᠬᠡᠬᠦ ᠦᠶ᠎ᠡ ᠳ᠋ᠤ᠌ ᠲᠠᠨ ᠤ᠋ ᠰᠠᠯᠬᠤ ᠦᠶ᠎ᠡ ᠵᠢᠨ ᠪᠠᠢᠳᠠᠯ ᠳ᠋ᠤ᠌ ᠪᠤᠴᠠᠵᠤ ᠪᠤᠯᠤᠨ᠎ᠠ</p> Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart ᠰᠢᠨᠡᠳᠬᠡᠬᠦ ᠶᠢ ᠵᠡᠷᠭᠡᠪᠡᠷ ᠳᠠᠬᠢᠨ ᠡᠭᠢᠯᠡᠭᠦᠯᠦᠨ᠎ᠡ ᠃ (Estimated %1 hour %2 minutes) (Estimated %1 minutes) Upgrade and Shut Down ᠰᠢᠨᠡᠳᠬᠡᠬᠦ ᠶᠢᠨ ᠬᠠᠮᠲᠤ ᠪᠠᠶᠢᠭᠤᠯᠤᠯᠭ᠎ᠠ ᠶᠢ ᠰᠢᠨᠡᠳᠬᠡᠨ᠎ᠡ ᠃ Estimated %1 hour %2 minutes ᠵᠢᠱᠢᠯᠲᠡ ᠪᠡᠷ ᠨᠢᠭᠡ ᠴᠠᠭ ᠢᠶᠠᠷ 2 ᠮᠢᠨᠦᠢᠲ᠋ ᠃ Estimated %1 minutes ᠵᠢᠱᠢᠯᠲᠡ ᠪᠡᠷ ᠨᠢᠭᠡ ᠮᠢᠨᠦᠢᠲ᠋ ᠃ Close all applications, upgrade the computer( ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡ ᠶᠢ ᠬᠠᠭᠠᠵᠤ ᠂ ᠺᠣᠮᠫᠢᠦᠢᠲ᠋ᠧᠷ ᠢ ᠰᠢᠨᠡᠳᠬᠡᠨ᠎ᠡ ᠃ and then restart the computer ᠳᠠᠷᠠᠭ᠎ᠠ ᠨᠢ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ ᠳᠠᠬᠢᠨ ᠰᠡᠩᠭᠡᠷᠡᠭᠦᠯᠵᠡᠢ ᠃ and then turn off it ᠳᠠᠷᠠᠭ᠎ᠠ ᠨᠢ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ ᠬᠠᠭᠠᠪᠠ ᠃ UkuiMenu::RecentlyInstalledModel Recently Installed ᠬᠠᠮᠤᠭ ᠤ᠋ᠨ ᠰᠢᠨ᠎ᠡ ᠤᠭᠰᠠᠷᠠᠯ UkuiMenu::SidebarButtonUtils Change account settings ᠳᠠᠩᠰᠠᠨ ᠡᠷᠦᠬᠡ ᠶᠢᠨ ᠪᠠᠶᠢᠷᠢᠯᠠᠭᠤᠯᠤᠯᠲᠠ ᠶᠢ ᠥᠭᠡᠷᠡᠴᠢᠯᠡᠨ᠎ᠡ ᠃ ukui-menu/translations/ukui-menu_zh_CN.ts0000664000175000017500000005245715160463365017536 0ustar fengfeng EditText Folder 应用组 FavoriteExtension Editing mode 批量编辑 FolderGridView Folder 应用组 FullScreenAppList Favorite 收藏 Editing mode 批量编辑 All Applications 全部应用 FullScreenHeader Contract 收缩 PluginSelectButton Letter Sort 字母排序 Category 功能排序 QObject Send to desktop shortcuts 发送到桌面快捷方式 Uninstall 卸载 Remove from folder 从应用组中移除 Move to folder 移动到应用组 Dissolve folder 解散应用组 Add to "%1" 添加到 "%1" Show ukui-menu. 显示开始菜单。 Quit ukui-menu. 退出开始菜单。 Add to favorite 添加到收藏 Fix to favorite Remove from favorite 取消收藏 Remove from taskbar 从任务栏取消固定 Add to taskbar 固定到任务栏 Fixed to all applications 固定到所有应用 Unfixed from all applications 从所有应用取消固定 Create a new folder 新建应用组 Remove from List 从列表中删除 SearchInputBar Search App 搜索应用 Sidebar Expand 展开 Contract 收缩 Computer 计算机 Settings 设置 UkuiMenu::AllAppDataProvider All 所有应用 All applications 所有应用 所有应用 UkuiMenu::AppCategoryModel Audio 音频 AudioVideo 音视频 Development 开发 Education 教育 Game 游戏 Graphics 图形 Network 网络 Office 办公 Science 科学 Settings 设置 System 系统 Utility 实用程序 Video 视频 Other 其他 UkuiMenu::AppCategoryPlugin Category 功能排序 功能排序 Audio 音频 AudioVideo 音视频 Development 开发 Education 教育 Game 游戏 Graphics 图形 Network 网络 Office 办公 Science 科学 Settings 设置 System 系统 Utility 实用程序 Video 视频 Other 其他 Open Function Sort Menu 打开功能排序菜单 Letter Sort 字母排序 Recently Installed 最新安装 All Applications 全部应用 UkuiMenu::AppFolderHelper New Folder %1 新文件夹 %1 UkuiMenu::AppLetterSortPlugin Letter Sort 字母排序 Open Letter Sort Menu 打开字母排序菜单 UkuiMenu::AppSearchPlugin Search 搜索 UkuiMenu::FavoriteExtension Favorite 收藏 UkuiMenu::FavoriteFolderHelper New Folder %1 应用组 %1 UkuiMenu::FavoriteWidget Favorite 收藏 favorite 收藏 UkuiMenu::PowerButton Power 电源 Hibernate 休眠 Suspend 睡眠 Lock Screen 锁定屏幕 Log Out 注销 Restart 重启 Shut Down 关机 <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>电脑保持开机状态,但耗电较少。应用会一直保持打开状态,可快速唤醒电脑并恢复到你离开的状态</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>当前用户从系统中注销,结束其会话并返回登录界面</p> <p>Close all applications, turn off the computer, and then turn it back on</p> <p>关闭所有应用,关闭电脑,然后重新打开电脑</p> <p>Close all applications, and then turn off the computer</p> <p>关闭所有应用,然后关闭电脑</p> <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>关闭电脑,但是应用会一直保持打开状态。当打开电脑时,可以恢复到你离开的状态</p> Upgrade and Restart 更新并重启 Upgrade and Shut Down 更新并关机 Close all applications, upgrade the computer 关闭所有应用,更新电脑 ,and then restart the computer ,然后重启电脑 ,and then turn off it , 然后关闭电脑 (Estimated %1 hour %2 minutes) (预计 %1 小时 %2 分钟) (Estimated %1 minutes) (预计 %1 分钟) UkuiMenu::RecentlyInstalledModel Recently Installed 最新安装 UkuiMenu::SidebarButtonUtils Change account settings 更改账户设置 ukui-menu/translations/ukui-menu_zh_Hant.ts0000664000175000017500000005341215160463365020120 0ustar fengfeng EditText Folder 應用組 FavoriteExtension Editing mode 批量編輯 FolderGridView Folder 應用組 FullScreenAppList Favorite 收藏 Editing mode 批量編輯 All Applications 全部應用 FullScreenHeader Contract 收縮 PluginSelectButton Letter Sort 字母排序 Category 功能排序 QObject Send to desktop shortcuts 發送到桌面快捷方式 Uninstall 卸載 Remove from folder 從應用組中移除 Move to folder 移動到應用組 Dissolve folder 解散應用組 Add to "%1" 添加到%1 Show ukui-menu. 顯示開始功能表。 Quit ukui-menu. 退出開始功能表。 Add to favorite 添加到收藏 Fix to favorite Remove from favorite 取消收藏 Remove from taskbar 從任務列取消固定 Add to taskbar 固定到任務列 Fixed to all applications 固定到所有應用 Unfixed from all applications 從所有應用取消固定 Create a new folder 新建應用組 Remove from List 從清單中刪除 SearchInputBar Search App 搜索應用 Sidebar Expand 展開 Contract 收縮 Computer 計算機 Settings 設置 UkuiMenu::AllAppDataProvider All 所有應用 All applications 所有应用 所有應用 UkuiMenu::AppCategoryModel Audio 音訊 AudioVideo 音視頻 Development 開發 Education 教育 Game 遊戲 Graphics 圖形 Network 網路 Office 辦公 Science 科學 Settings 設置 System 系統 Utility 實用程式 Video 視頻 Other 其他 UkuiMenu::AppCategoryPlugin Category 功能排序 功能排序 Audio 音訊 AudioVideo 音視頻 Development 開發 Education 教育 Game 遊戲 Graphics 圖形 Network 網路 Office 辦公 Science 科學 Settings 設置 System 系統 Utility 實用程式 Video 視頻 Other 其他 Open Function Sort Menu 打開功能排序功能表 Letter Sort 字母排序 Recently Installed 最新安裝 All Applications 全部應用 UkuiMenu::AppFolderHelper New Folder %1 新應用組 %1 UkuiMenu::AppLetterSortPlugin Letter Sort 字母排序 Open Letter Sort Menu 打開字母排序功能表 UkuiMenu::AppSearchPlugin Search 搜索 UkuiMenu::FavoriteExtension Favorite 收藏 UkuiMenu::FavoriteFolderHelper New Folder %1 應用群組 %1 UkuiMenu::FavoriteWidget Favorite 收藏 favorite 收藏 UkuiMenu::PowerButton Power 電源 Hibernate 休眠 Suspend 睡眠 Lock Screen 鎖定螢幕 Log Out 註銷 Restart 重啟 Shut Down 關機 <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>電腦保持開機狀態,但耗電較少。 應用會一直保持打開狀態,可快速喚醒電腦並恢復到你離開的狀態</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>當前使用者從系統中註銷,結束其會話並返回登錄介面</p> <p>Close all applications, turn off the computer, and then turn it back on</p> <p>關閉所有應用,關閉電腦,然後重新打開電腦</p> <p>Close all applications, and then turn off the computer</p> <p>關閉所有應用,然後關閉電腦</p> <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>關閉電腦,但是應用會一直保持打開狀態。 當打開電腦時,可以恢復到你離開的狀態</p> Close all applications, upgrade the computer Upgrade and Restart 更新並重啟 (Estimated %1 hour %2 minutes) (Estimated %1 minutes) Upgrade and Shut Down 更新並關機 Estimated %1 hour %2 minutes 預計 %1 小時 %2 分鐘 Estimated %1 minutes 預計 %1 分鐘 Close all applications, upgrade the computer( 關閉所有應用,更新電腦( ,and then restart the computer , 然後重啟電腦 ,and then turn off it , 然後關閉電腦 UkuiMenu::RecentlyInstalledModel Recently Installed 最新安裝 UkuiMenu::SidebarButtonUtils Change account settings 更改帳戶設置 ukui-menu/translations/ukui-menu_es_ES.ts0000664000175000017500000005557715160463365017541 0ustar fengfeng EditText Folder FavoriteExtension Enable editing mode Habilitar el modo de edición por lotes Remove all favorite apps Descartar todas las aplicaciones favoritas Editing mode FolderGridView Folder FullScreenAppList Enable editing mode Habilitar el modo de edición por lotes Remove all favorite apps Descartar todas las aplicaciones favoritas All Applications Editing mode Favorite colección FullScreenHeader Contract encogerse PluginSelectButton Letter Sort Orden alfabético Category Tipo de características QObject Add to desktop shortcuts Agregar a accesos directos de escritorio Uninstall descargar Remove from folder Eliminado del grupo de aplicaciones Add to folder Agregarlo a un grupo de aplicaciones Dissolve folder Disolver el grupo de aplicaciones Add to favorite Move to folder Add to "%1" Añadir a "%1" Show ukui-menu. Se muestra el menú Inicio. Quit ukui-menu. Salga del menú Inicio. Fix to favorite Anclar a favoritos Remove from favorite Desanclar de favoritos Remove from taskbar Desanclar desde la barra de tareas Add to taskbar Anclar a la barra de tareas Send to desktop shortcuts Fixed to all applications Anclar a todas las aplicaciones Unfixed from all applications Desanclar de todas las aplicaciones Create a new folder Creación de un grupo de aplicaciones Remove from List Quitar de la lista SearchInputBar Search App Buscar la aplicación Sidebar Expand desplegar Contract encogerse Computer Ordenador Settings Construir UkuiMenu::AllAppDataProvider All Todas las aplicaciones All applications 所有应用 Todas las aplicaciones UkuiMenu::AppCategoryModel Audio audio AudioVideo Audio y video Development explotación Education educar Game Juego Graphics grafismo Network Internet Office Oficina Science ciencia Settings Construir System sistema Utility Utilidades Video Vídeo Other Otro UkuiMenu::AppCategoryPlugin Category 功能排序 Tipo de características Audio audio AudioVideo Audio y video Development explotación Education educar Game Juego Graphics grafismo Network Internet Office Oficina Science ciencia Settings Construir System sistema Utility Utilidades Video Vídeo Other Otro Open Function Sort Menu Abra el menú Clasificación de funciones All Applications Letter Sort Orden alfabético Recently Installed Última instalación UkuiMenu::AppFolderHelper New Folder %1 Nueva carpeta %1 UkuiMenu::AppLetterSortPlugin Letter Sort Orden alfabético Open Letter Sort Menu Abra el menú de clasificación alfabética UkuiMenu::AppSearchPlugin Search Buscar UkuiMenu::FavoriteExtension Favorite colección UkuiMenu::FavoriteFolderHelper New Folder %1 Aplicar grupo %1 UkuiMenu::FavoriteWidget Favorite colección favorite colección UkuiMenu::PowerButton Power fuente de alimentación Switch user Cambiar de usuario Hibernate dormancia Suspend dormir Lock Screen Bloquea tu pantalla Log Out Cerrar sesión Restart Reiniciar Shut Down Apagado <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>Apague su computadora, pero la aplicación permanece abierta. Cuando enciendes tu PC, puedes volver al estado en el que lo dejaste</p> <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>El equipo permanece encendido, pero consume menos energía. La aplicación permanece abierta, por lo que puede activar rápidamente su PC y volver al estado en el que lo dejó</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>El usuario actual cierra la sesión del sistema, finaliza su sesión y vuelve a la pantalla de inicio de sesión</p> Upgrade and Shut Down Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart ,and then restart the computer <p>Close all applications, turn off the computer, and then turn it back on</p> <p>Cierre todas las aplicaciones, apague su PC y vuelva a encenderlo</p> (Estimated %1 hour %2 minutes) (Estimated %1 minutes) <p>Close all applications, and then turn off the computer</p> <p>Cierra todas las aplicaciones y, a continuación, apaga el equipo</p> UkuiMenu::RecentlyInstalledModel Recently Installed Última instalación UkuiMenu::SidebarButtonUtils Change account settings ukui-menu/translations/ukui-menu_kk.ts0000664000175000017500000006341115160463365017132 0ustar fengfeng EditText Folder حۇجات قىسقىش FavoriteExtension Enable editing mode تۇركىمدەپ تالداۋجاساۋ ۇلگىنى ٸسكە كىرستىرۋ Remove all favorite apps بارلٸق جيىپ ساقتاۋ قولدانبالار بوس ەتۋ Editing mode FolderGridView Folder حۇجات قىسقىش FullScreenAppList Editing mode Favorite تەز ۇسىنس ەتۋ Enable editing mode تۇركىمدەپ تالداۋجاساۋ ۇلگىنى ٸسكە كىرستىرۋ Remove all favorite apps بارلٸق جيىپ ساقتاۋ قولدانبالار بوس ەتۋ All Applications بارلٸق پٸروگٸراممالار FullScreenHeader Contract قىسقارۋ PluginSelectButton Letter Sort ٴارىپ تارتٸپكە تىزۋ Category تۇرى QObject Add to desktop shortcuts ۇستەل بەتىنە قوسۋ تەز ٴتاسىل Uninstall ٴوشىرۋ Remove from folder قولدانعىش گرۋپپادان شىعارىپ جىبەرمەك Add to folder قولدانۋ گرۋپپاسىنا قوسىلماق Dissolve folder تارقاتىپ ەتۋ قولدانۋ گرۋپپاسى Add to favorite Move to folder Add to "%1" «1٪» قوسىلدى Show ukui-menu. باستالۋ تاماق تٸزٸمدٸكدٸ كورسەتۋ Quit ukui-menu. باستالۋ تٸزٸمدٸگٸنەن شەگنىپ شىقپاق Fix to favorite جيىپ كۇتكەنشە تۇراقتاندىرۋ Remove from favorite جيىپ ساقتاۋ قىسقشتان تۇراقتىلىعىن كۇشىنەن قالدىرۋ Remove from taskbar مىندەتتى ستونىنان تۇراقتىلىعىن كۇشىنەن قالدىرۋ Add to taskbar مىندەتتى كاتەكشەسىنە تۇراقتاندىرۋ Send to desktop shortcuts Fixed to all applications بارلٸق قولدانىس تۇراقتاندىرۋ Unfixed from all applications بارلٸق قولدانۋدان تۇراقتىلىعىن كۇشىنەن قالدىرۋ Create a new folder جاڭادان قولدانۋ گرۋپپاسى گۋرما Remove from List تٸزٸمدەٸكتەن شىعارىپ جىبەرمەك SearchInputBar Search App ٸزدەۋ قولدانۋ Sidebar Expand قانات جارييالاندى Contract قىسقارۋ Computer ەسەپتەۋ اسبابى Settings تور تەڭگەرگٸش UkuiMenu::AllAppDataProvider All جالپىسى All applications 所有应用 بارلٸق قولدانعىش پٸروگٸراممالار UkuiMenu::AppCategoryModel Audio ۇن AudioVideo اۋا ۋا كورىنبە جيٸلٸك Development ٸشٸۋ Education وقۋ-اعارتۋ Game ويٸن Graphics گرافيىك Network تور Office ئشىخانا Science عىلىم -عىلىم Settings ورنالاسترعان ەتۋ System سەستيما Utility قولدانعىش پٸروگٸرامما Video سىن Other باسقا UkuiMenu::AppCategoryPlugin Category 功能排序 تۇرى Audio ۇن AudioVideo اۋا ۋا كورىنبە جيٸلٸك Development ٸشٸۋ Education وقۋ-اعارتۋ Game ويٸن Graphics گرافيىك Network تور Office ئشىخانا Science عىلىم -عىلىم Settings ورنالاسترعان ەتۋ System سەستيما Utility قولدانعىش پٸروگٸرامما Video سىن Other باسقا Open Function Sort Menu رولٸ تارتٸپكە تىزۋ تٸزٸمدٸكدٸ ٸشٸۋ Letter Sort ٴارىپ تارتٸپكە تىزۋ Recently Installed ەڭ جاڭا قۇراستىرۋ All Applications بارلٸق پٸروگٸراممالار UkuiMenu::AppFolderHelper New Folder %1 جاڭا حۇجات قىسقىش ٪1 UkuiMenu::AppLetterSortPlugin Letter Sort ٴارىپ تارتٸپكە تىزۋ Open Letter Sort Menu ٴارىپ جاعٸ تٸزٸمدٸكدٸ ٸشٸۋ UkuiMenu::AppSearchPlugin Search ٸزدەۋ UkuiMenu::FavoriteExtension Favorite تەز ۇسىنس ەتۋ UkuiMenu::FavoriteFolderHelper New Folder %1 جاڭا حۇجات قىسقىش ٪1 UkuiMenu::FavoriteWidget Favorite تەز ۇسىنس ەتۋ favorite جاقسى كورەتىن UkuiMenu::PowerButton Power قۋات Switch user تۇتٸنۋشٸنٸ سايكەستىرۋ Hibernate ۇيقى Suspend توقتاتۋ Lock Screen ەكٸران قۇلپىلاۋ Log Out بوس قىلىۋەتمەك ,and then restart the computer Restart قاتە قوزعالتۋ Shut Down وشىرۋ <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>كومپيۋتەر اشىلۋ كۇيىن ساقتايدى،لىكى توك سارىپ قىلعانى از قولدانۋ باستان اقٸر اشىلۋ كۇيىن ساقتاپ كەلدى، كومپيۋتەرنى تەز جىلدامدىقتا ئويغىتىدۇ ونىڭ ۇستىنە ايٸرٸلۋ ھالىتىڭىزگە قايتادى</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>كەزەكتە ابونتتار سەستامادان بوس ورىندالىپ، سويلەسۋدى اقىرلاستىردى ونىڭ ۇستىنە تٸزٸمدەۇ كورىنسكە قايتادى</p> <p>Close all applications, turn off the computer, and then turn it back on</p> <p>بارلٸق قولدانعىش نۇكتەلەردى ٶلترۋ، كومپيۋتەردى قۇلىپتاپ ەت، سونان كومپيۋتەردى قايتادان ٴٸشڭٸز</p> <p>Close all applications, and then turn off the computer</p> <p>بارلٸق قولدانعىش نۇكتەنى تاقاۋ، سونان كومپيۋتەردى جاپ</p> <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>كومپيۋتەردى قۇلىپتاپ ەت، بىراق قولدانعىش قالىپىن باستان اقٸر ساقتاپ قالدى كومپيوتوردى اشقاندا ،ٴسىز ايىرىلعان قالىپقا قايتقالى بولادٸ</p> Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart جانارتو جانا قايه جوكتيو (Estimated %1 hour %2 minutes) (Estimated %1 minutes) Upgrade and Shut Down جانارتو جانا قوعات توريب Estimated %1 hour %2 minutes بُلْجَم بويينشا %1 ساغات %2 مِينُت Estimated %1 minutes بُلْجَم بويينشا %1 مِينُت Close all applications, upgrade the computer( بارلغ بايْدَارْمَلَارْدِي جَابينِز, كومبيوتردي جانارتينِز and then restart the computer سودان كئِين كومبيوتردي قايطَا جُكْتِيڭِز and then turn off it سودان كئِين كومبيوتردي جَابينِز UkuiMenu::RecentlyInstalledModel Recently Installed ەڭ جاڭا قۇراستىرۋ UkuiMenu::SidebarButtonUtils Change account settings يَسِيبْتِك جَازْبَانِڭ پارَامِتْرَلِرِن أوزغَرتِينِز ukui-menu/translations/ukui-menu_mn_MN.ts0000664000175000017500000007036215160463365017534 0ustar fengfeng EditText Folder FavoriteExtension Enable editing mode ᠪᠥᠭᠡᠮ ᠢᠶᠡᠷ ᠨᠠᠶᠢᠷᠠᠭᠤᠯᠬᠤ ᠵᠠᠭᠪᠤᠷ ᠢ ᠬᠡᠷᠡᠭᠯᠡᠨ᠎ᠡ Remove all favorite apps ᠪᠤᠶ ᠪᠦᠬᠦᠢ ᠬᠤᠷᠢᠶᠠᠨ ᠬᠠᠳᠠᠭᠠᠯᠠᠵᠤ ᠬᠡᠷᠡᠭᠯᠡᠬᠦ ᠶᠢ ᠬᠦᠴᠦᠨ ᠦᠭᠡᠶ ᠪᠣᠯᠭᠠᠪᠠ Editing mode FolderGridView Folder FullScreenAppList Enable editing mode ᠪᠥᠭᠡᠮ ᠢᠶᠡᠷ ᠨᠠᠶᠢᠷᠠᠭᠤᠯᠬᠤ ᠵᠠᠭᠪᠤᠷ ᠢ ᠬᠡᠷᠡᠭᠯᠡᠨ᠎ᠡ Remove all favorite apps ᠪᠤᠶ ᠪᠦᠬᠦᠢ ᠬᠤᠷᠢᠶᠠᠨ ᠬᠠᠳᠠᠭᠠᠯᠠᠵᠤ ᠬᠡᠷᠡᠭᠯᠡᠬᠦ ᠶᠢ ᠬᠦᠴᠦᠨ ᠦᠭᠡᠶ ᠪᠣᠯᠭᠠᠪᠠ All Applications ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡᠨ Editing mode Favorite ᠬᠤᠷᠢᠶᠠᠨ ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ FullScreenHeader Contract ᠠᠭᠰᠢᠭᠠᠬᠤ PluginSelectButton Letter Sort ᠴᠠᠭᠠᠨ ᠲᠣᠯᠣᠭᠠᠢ ᠵᠢᠭ᠌ᠰᠠᠭᠠᠬᠤ Category ᠴᠢᠳᠠᠮᠵᠢ ᠶᠢᠨ ᠵᠢᠭᠰᠠᠭᠠᠯᠲᠠ QObject Add to favorite Remove from folder ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠦ ᠳᠤᠭᠤᠶᠢᠯᠠᠩ ᠠᠴᠠ ᠰᠢᠯᠵᠢᠭᠦᠯᠦᠨ ᠠᠷᠢᠯᠭᠠᠨ᠎ᠠ Move to folder Add to folder ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠦ ᠳᠤᠭᠤᠶᠢᠯᠠᠩ ᠳᠤ ᠨᠡᠮᠡᠵᠦ ᠥᠭ᠍ᠬᠦ Add to "%1" 《 1》 ᠪᠣᠯᠲᠠᠯ᠎ᠠ ᠨᠡᠮᠡᠪᠡ Dissolve folder ᠲᠠᠷᠬᠠᠭᠠᠬᠤ ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠦ ᠳᠤᠭᠤᠶᠢᠯᠠᠩ ᠢ ᠲᠠᠷᠬᠠᠭᠠᠨ᠎ᠠ Fix to favorite ᠬᠤᠷᠢᠶᠠᠨ ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ ᠪᠠᠷ ᠲᠣᠭᠲᠠᠪᠠ Remove from favorite ᠬᠤᠷᠢᠶᠠᠨ ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ ᠠᠴᠠ ᠲᠣᠭᠲᠠᠮᠠᠯ ᠢ ᠦᠭᠡᠶᠢᠰᠬᠡᠨ᠎ᠡ Fixed to all applications ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡᠨ ᠳᠦ ᠲᠣᠭᠲᠠᠪᠤᠷᠢᠵᠢᠭᠤᠯᠤᠨ᠎ᠠ Unfixed from all applications ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡᠨ ᠡᠴᠡ ᠲᠣᠭᠲᠠᠮᠠᠯ ᠢ ᠬᠦᠴᠦᠨ ᠦᠭᠡᠢ ᠪᠣᠯᠭᠠᠨ᠎ᠠ Remove from taskbar ᠡᠭᠦᠷᠭᠡ ᠶᠢᠨ ᠪᠤᠯᠤᠩ ᠠᠴᠠ ᠲᠣᠭᠲᠠᠮᠠᠯ ᠢ ᠦᠭᠡᠶᠢᠰᠬᠡᠨ᠎ᠡ Add to taskbar ᠡᠭᠦᠷᠭᠡ ᠶᠢᠨ ᠪᠤᠯᠤᠩ ᠳᠤ ᠲᠣᠭᠲᠠᠪᠠ Send to desktop shortcuts Add to desktop shortcuts ᠰᠢᠷᠡᠭᠡᠨ ᠭᠠᠳᠠᠷᠭᠤ ᠳᠤ ᠨᠡᠮᠡᠬᠦ ᠲᠦᠷᠭᠡᠨ ᠠᠷᠭ᠎ᠠ ᠮᠠᠶᠢᠭ ᠨᠡᠮᠡᠵᠡᠢ Uninstall ᠠᠴᠢᠶ᠎ᠠ ᠪᠠᠭᠤᠯᠭᠠᠨ᠎ᠠ Show ukui-menu. ᠵᠠᠭᠤᠰᠢ ᠶᠢᠨ ᠨᠡᠷᠡᠰ ᠦᠨ ᠬᠠᠭᠤᠳᠠᠰᠤ ᠶᠢ ᠢᠯᠡᠷᠡᠭᠦᠯᠵᠡᠢ Quit ukui-menu. ᠵᠠᠭᠤᠰᠢᠨ ᠤ ᠨᠡᠷᠡᠰ ᠦᠨ ᠬᠠᠭᠤᠳᠠᠰᠤ ᠠᠴᠠ ᠭᠠᠷᠴᠤ ᠡᠬᠢᠯᠡᠵᠡᠢ Create a new folder ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠦ ᠳᠤᠭᠤᠶᠢᠯᠠᠩ ᠢ ᠰᠢᠨ᠎ᠡ ᠪᠡᠷ ᠪᠠᠶᠢᠭᠤᠯᠤᠨ᠎ᠠ Remove from List ᠵᠢᠭᠰᠠᠭᠠᠯᠲᠠ ᠶᠢᠨ ᠬᠦᠰᠦᠨᠦᠭᠲᠦ ᠡᠴᠡ ᠬᠠᠰᠤᠨ᠎ᠠ ᠃ SearchInputBar Search App ᠡᠷᠢᠵᠦ ᠬᠡᠷᠡᠭᠯᠡᠨ᠎ᠡ Sidebar Expand ᠡᠬᠢᠯᠡᠪᠡ Contract ᠠᠭᠰᠢᠭᠠᠬᠤ Computer ᠺᠣᠮᠫᠢᠦᠢᠲ᠋ᠧᠷ Settings ᠪᠠᠶᠢᠷᠢᠯᠠᠭᠤᠯᠬᠤ UkuiMenu::AllAppDataProvider All ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡᠨ All applications ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡᠨ UkuiMenu::AppCategoryModel Audio ᠳᠠᠭᠤᠨ ᠤ ᠳᠠᠪᠲᠠᠮᠵᠢ AudioVideo ᠳᠠᠭᠤ ᠳᠦᠷᠰᠦ ᠰᠢᠩᠭᠡᠭᠡᠯᠲᠡ Development ᠨᠡᠭᠡᠭᠡᠨ ᠬᠥᠭᠵᠢᠭᠦᠯᠦᠨ᠎ᠡ Education ᠰᠤᠷᠭᠠᠨ ᠬᠦᠮᠦᠵᠢᠯ Game ᠲᠣᠭᠯᠠᠭᠠᠮ Graphics ᠵᠢᠷᠤᠭ ᠳᠦᠷᠰᠦ Network ᠲᠣᠣᠷ ᠰᠦᠯᠵᠢᠶ᠎ᠡ Office ᠠᠯᠪᠠᠨ ᠠᠵᠢᠯᠯᠠᠬᠤ Science ᠰᠢᠨᠵᠢᠯᠡᠬᠦ ᠤᠬᠠᠭᠠᠨᠴᠢ Settings ᠪᠠᠶᠢᠷᠢᠯᠠᠭᠤᠯᠬᠤ System ᠰᠢᠰᠲ᠋ᠧᠮ Utility ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠦ ᠳᠡᠰ ᠳᠠᠷᠠᠭᠠᠯᠠᠯ Video ᠬᠠᠷᠠᠭᠠᠨ ᠳᠠᠪᠲᠠᠮᠵᠢ Other ᠪᠤᠰᠤᠳ All Applications ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡᠨ UkuiMenu::AppCategoryPlugin Category ᠴᠢᠳᠠᠮᠵᠢ ᠶᠢᠨ ᠵᠢᠭᠰᠠᠭᠠᠯᠲᠠ Audio ᠳᠠᠭᠤᠨ ᠤ ᠳᠠᠪᠲᠠᠮᠵᠢ AudioVideo ᠳᠠᠭᠤ ᠳᠦᠷᠰᠦ ᠰᠢᠩᠭᠡᠭᠡᠯᠲᠡ Development ᠨᠡᠭᠡᠭᠡᠨ ᠬᠥᠭᠵᠢᠭᠦᠯᠦᠨ᠎ᠡ Education ᠰᠤᠷᠭᠠᠨ ᠬᠦᠮᠦᠵᠢᠯ Game ᠲᠣᠭᠯᠠᠭᠠᠮ Graphics ᠵᠢᠷᠤᠭ ᠳᠦᠷᠰᠦ Network ᠲᠣᠣᠷ ᠰᠦᠯᠵᠢᠶ᠎ᠡ Office ᠠᠯᠪᠠᠨ ᠠᠵᠢᠯᠯᠠᠬᠤ Science ᠰᠢᠨᠵᠢᠯᠡᠬᠦ ᠤᠬᠠᠭᠠᠨᠴᠢ Settings ᠪᠠᠶᠢᠷᠢᠯᠠᠭᠤᠯᠬᠤ System ᠰᠢᠰᠲ᠋ᠧᠮ Utility ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠦ ᠳᠡᠰ ᠳᠠᠷᠠᠭᠠᠯᠠᠯ Video ᠬᠠᠷᠠᠭᠠᠨ ᠳᠠᠪᠲᠠᠮᠵᠢ Other ᠪᠤᠰᠤᠳ Open Function Sort Menu ᠴᠢᠳᠠᠮᠵᠢ ᠶᠢᠨ ᠵᠢᠭᠰᠠᠭᠠᠯᠲᠠ ᠶᠢᠨ ᠨᠡᠷᠡᠰ ᠦᠨ ᠬᠠᠭᠤᠳᠠᠰᠤ ᠶᠢ ᠨᠡᠭᠡᠭᠡᠨ᠎ᠡ All Applications ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡᠨ Letter Sort ᠴᠠᠭᠠᠨ ᠲᠣᠯᠣᠭᠠᠢ ᠵᠢᠭ᠌ᠰᠠᠭᠠᠬᠤ Recently Installed ᠬᠠᠮᠤᠭ ᠰᠢᠨ᠎ᠡ ᠤᠭᠰᠠᠷᠠᠬᠤ UkuiMenu::AppFolderHelper New Folder %1 ᠰᠢᠨ᠎ᠡ ᠪᠢᠴᠢᠭ᠌ ᠮᠠᠲ᠋ᠧᠷᠢᠶᠠᠯ ᠤᠨ ᠬᠠᠪᠤᠳᠠᠷ ᠨᠢ 1 UkuiMenu::AppLetterSortPlugin Letter Sort ᠴᠠᠭᠠᠨ ᠲᠣᠯᠣᠭᠠᠢ ᠵᠢᠭ᠌ᠰᠠᠭᠠᠬᠤ Open Letter Sort Menu ᠴᠠᠭᠠᠨ ᠲᠣᠯᠣᠭᠠᠢ ᠶᠢᠨ ᠵᠢᠭᠰᠠᠭᠠᠯᠲᠠ ᠶᠢᠨ ᠨᠡᠷᠡᠰ ᠦᠨ ᠬᠠᠭᠤᠳᠠᠰᠤ ᠶᠢ ᠨᠡᠭᠡᠭᠡᠨ᠎ᠡ UkuiMenu::AppSearchPlugin Search ᠡᠷᠢᠬᠦ UkuiMenu::FavoriteExtension Favorite ᠬᠤᠷᠢᠶᠠᠨ ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ UkuiMenu::FavoriteFolderHelper New Folder %1 ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠦ ᠳᠤᠭᠤᠶᠢᠯᠠᠩ 1 UkuiMenu::FavoriteWidget Favorite ᠬᠤᠷᠢᠶᠠᠨ ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ favorite ᠬᠤᠷᠢᠶᠠᠨ ᠬᠠᠳᠠᠭᠠᠯᠠᠬᠤ UkuiMenu::PowerButton Power ᠴᠠᠬᠢᠯᠭᠠᠨ ᠡᠭᠦᠰᠭᠡᠭᠴᠢ Switch user ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ ᠶᠢ ᠰᠣᠯᠢᠨ᠎ᠠ Hibernate ᠤᠨᠲᠠᠵᠤ ᠠᠮᠠᠷᠠᠨ᠎ᠠ Suspend ᠤᠨᠲᠠ Lock Screen ᠳᠡᠯᠬᠡᠴᠡ ᠶᠢ ᠲᠣᠭᠲᠠᠭᠠᠨ᠎ᠠ Log Out ᠬᠦᠴᠦᠨ ᠦᠭᠡᠶ ᠪᠣᠯᠭᠠᠬᠤ Restart ᠳᠠᠬᠢᠨ ᠰᠡᠩᠭᠡᠷᠡᠭᠦᠯᠬᠦ Shut Down ᠪᠠᠶᠢᠭᠤᠯᠤᠯᠭ᠎ᠠ <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ ᠬᠠᠭᠠᠭᠰᠠᠨ ᠪᠣᠯᠪᠠᠴᠤ ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠦ ᠬᠤᠷᠠᠯ ᠨᠡᠭᠡᠭᠡᠬᠦ ᠪᠠᠶᠢᠳᠠᠯ ᠢᠶᠠᠨ ᠪᠠᠷᠢᠮᠲᠠᠯᠠᠳᠠᠭ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ ᠨᠡᠭᠡᠭᠡᠬᠦ ᠦᠶ᠎ᠡ ᠳᠦ ᠂ ᠴᠢᠨᠦ ᠰᠠᠯᠤᠭᠰᠠᠨ ᠪᠠᠶᠢᠳᠠᠯ ᠢᠶᠠᠨ ᠰᠡᠷᠭᠦᠭᠡᠵᠦ ᠪᠣᠯᠣᠨ</p>᠎ᠠ <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠮᠠᠰᠢᠨ ᠢᠶᠠᠨ ᠨᠡᠭᠡᠭᠡᠬᠦ ᠪᠠᠶᠢᠳᠠᠯ ᠢᠶᠠᠨ ᠪᠠᠷᠢᠮᠲᠠᠯᠠᠳᠠᠭ ᠂ ᠭᠡᠪᠡᠴᠦ ᠴᠠᠬᠢᠯᠭᠠᠨ ᠬᠣᠷᠣᠭᠳᠠᠭᠤᠯᠬᠤ ᠨᠢ ᠨᠡᠯᠢᠶᠡᠳ ᠪᠠᠭ᠎ᠠ ᠬᠡᠷᠡᠭᠯᠡᠭᠡᠨ ᠦ ᠬᠤᠷᠠᠯ ᠨᠡᠭᠡᠭᠡᠬᠦ ᠪᠠᠶᠢᠳᠠᠯ ᠢᠶᠠᠨ ᠦᠷᠭᠦᠯᠵᠢᠯᠡᠭᠦᠯᠦᠭᠰᠡᠭᠡᠷ ᠂ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ ᠲᠦᠷᠭᠡᠨ ᠰᠡᠷᠢᠭᠡᠬᠦ ᠶᠢᠨ ᠬᠠᠮᠲᠤ ᠴᠢᠨᠦ ᠰᠠᠯᠤᠭᠰᠠᠨ ᠪᠠᠶᠢᠳᠠᠯ ᠢᠶᠠᠨ ᠰᠡᠷᠭᠦᠭᠡᠵᠦ ᠳᠡᠶᠢᠯᠦᠨ</p>᠎ᠡ <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>ᠣᠳᠣᠬᠠᠨ ᠳᠤ ᠬᠡᠷᠡᠭᠯᠡᠭᠴᠢ ᠰᠢᠰᠲ᠋ᠧᠮ ᠡᠴᠡ ᠬᠦᠴᠦᠨ ᠦᠭᠡᠶ ᠪᠣᠯᠭᠠᠵᠤ ᠂ ᠲᠡᠭᠦᠨ ᠦ ᠬᠤᠷᠠᠯ ᠤᠨ ᠦᠭᠡ ᠪᠡᠨ ᠳᠠᠭᠤᠰᠬᠠᠬᠤ ᠶᠢᠨ ᠬᠠᠮᠲᠤ ᠲᠡᠮᠳᠡᠭᠯᠡᠭᠰᠡᠨ ᠨᠢᠭᠤᠷ ᠲᠤ ᠪᠤᠴᠠᠵᠠᠶ</p> Upgrade and Shut Down Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart ,and then restart the computer <p>Close all applications, turn off the computer, and then turn it back on</p> <p>ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡ ᠶᠢ ᠬᠠᠭᠠᠵᠤ ᠂ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ ᠬᠠᠭᠠᠵᠤ ᠂ ᠳᠠᠷᠠᠭ᠎ᠠ ᠨᠢ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ ᠳᠠᠬᠢᠨ ᠨᠡᠭᠡᠭᠡᠨ᠎ᠡ</p> (Estimated %1 hour %2 minutes) (Estimated %1 minutes) <p>Close all applications, and then turn off the computer</p> <p>ᠪᠤᠢ ᠪᠥᠬᠥᠢ ᠬᠡᠷᠡᠭ᠍ᠯᠡᠭᠡ ᠶᠢ ᠬᠠᠭᠠᠵᠤ ᠂ ᠳᠠᠷᠠᠭ᠎ᠠ ᠨᠢ ᠺᠣᠮᠫᠢᠦ᠋ᠲ᠋ᠧᠷ ᠢ ᠬᠠᠭᠠᠬᠤ</p> UkuiMenu::RecentlyInstalledModel Recently Installed ᠬᠠᠮᠤᠭ ᠰᠢᠨ᠎ᠡ ᠤᠭᠰᠠᠷᠠᠬᠤ UkuiMenu::SidebarButtonUtils Change account settings ukui-menu/translations/ukui-menu_de_DE.ts0000664000175000017500000005541515160463365017472 0ustar fengfeng EditText Folder FavoriteExtension Enable editing mode Batch-Bearbeitungsmodus aktivieren Remove all favorite apps Alle Lieblings-Apps verwerfen Editing mode FolderGridView Folder FullScreenAppList Enable editing mode Batch-Bearbeitungsmodus aktivieren Remove all favorite apps Alle Lieblings-Apps verwerfen All Applications Editing mode Favorite Sammlung FullScreenHeader Contract schrumpfen PluginSelectButton Letter Sort Alphabetische Reihenfolge Category Art der Funktionen QObject Add to favorite Remove from folder Aus der App-Gruppe entfernt Move to folder Add to folder Hinzufügen zu einer App-Gruppe Add to "%1" Zu "%1" hinzufügen Dissolve folder Auflösen der App-Gruppe Fix to favorite An Favoriten anheften Remove from favorite Von Favoriten lösen Fixed to all applications An alle Apps anheften Unfixed from all applications Von allen Apps lösen Remove from taskbar Lösen von der Taskleiste Add to taskbar An die Taskleiste anheften Send to desktop shortcuts Add to desktop shortcuts Zu Desktop-Verknüpfungen hinzufügen Uninstall entladen Show ukui-menu. Das Startmenü wird angezeigt. Quit ukui-menu. Beenden Sie das Startmenü. Create a new folder Erstellen einer Anwendungsgruppe Remove from List Aus Liste entfernen SearchInputBar Search App Suchen Sie nach der App Sidebar Expand entfalten Contract schrumpfen Computer Settings Aufstellen UkuiMenu::AllAppDataProvider All Alle Apps All applications Alle Apps UkuiMenu::AppCategoryModel Audio Audio AudioVideo Audio und Video Development Ausbeutung Education erziehen Game Spiel Graphics Graphik Network Internet Office Büro Science Wissenschaft Settings Aufstellen System System Utility Versorgungswirtschaft Video Video Other andere UkuiMenu::AppCategoryPlugin Category Art der Funktionen Audio Audio AudioVideo Audio und Video Development Ausbeutung Education erziehen Game Spiel Graphics Graphik Network Internet Office Büro Science Wissenschaft Settings Aufstellen System System Utility Versorgungswirtschaft Video Video Other andere Open Function Sort Menu Öffnen Sie das Menü "Funktionssortierung" All Applications Letter Sort Alphabetische Reihenfolge Recently Installed Letzte Installation UkuiMenu::AppFolderHelper New Folder %1 Neuer Ordner %1 UkuiMenu::AppLetterSortPlugin Letter Sort Alphabetische Reihenfolge Open Letter Sort Menu Öffnen Sie das alphabetische Sortiermenü UkuiMenu::AppSearchPlugin Search Suchen UkuiMenu::FavoriteExtension Favorite Sammlung UkuiMenu::FavoriteFolderHelper New Folder %1 Gruppe %1 anwenden UkuiMenu::FavoriteWidget Favorite Sammlung favorite Sammlung UkuiMenu::PowerButton Power Stromversorgung Switch user Benutzer wechseln Hibernate Schlafzustand Suspend schlafen Lock Screen Sperren Sie Ihren Bildschirm Log Out Abmeldung Restart Neustart Shut Down Herunterfahren <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>Schalten Sie Ihren Computer aus, aber die App bleibt geöffnet. Wenn Sie Ihren PC einschalten, können Sie zu dem Zustand zurückkehren, den Sie unterbrochen haben</p> <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>Der Computer bleibt eingeschaltet, verbraucht aber weniger Strom. Die App bleibt geöffnet, sodass Sie Ihren PC schnell aufwecken und in den Zustand zurückkehren können, den Sie aufgehört haben</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>Der aktuelle Benutzer meldet sich vom System ab, beendet seine Sitzung und kehrt zum Anmeldebildschirm zurück</p> Upgrade and Shut Down Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart ,and then restart the computer <p>Close all applications, turn off the computer, and then turn it back on</p> <p>Schließen Sie alle Apps, fahren Sie Ihren PC herunter und schalten Sie ihn wieder ein</p> (Estimated %1 hour %2 minutes) (Estimated %1 minutes) <p>Close all applications, and then turn off the computer</p> <p>Schließen Sie alle Apps und fahren Sie dann Ihren PC herunter</p> UkuiMenu::RecentlyInstalledModel Recently Installed Letzte Installation UkuiMenu::SidebarButtonUtils Change account settings ukui-menu/translations/ukui-menu_ug_CN.ts0000664000175000017500000006155115160463365017523 0ustar fengfeng EditText Folder FavoriteExtension Enable editing mode تۈركۈملەپ تەھرىرلەش ئەندىزىسىنى ئىشقا كىرىشتۈرۈش Remove all favorite apps بارلىق يىغىپ ساقلاش قوللىنىلىشىنى بىكار قىلىش Editing mode FolderGridView Folder FullScreenAppList Enable editing mode تۈركۈملەپ تەھرىرلەش ئەندىزىسىنى ئىشقا كىرىشتۈرۈش Remove all favorite apps بارلىق يىغىپ ساقلاش قوللىنىلىشىنى بىكار قىلىش All Applications Editing mode Favorite يىغىپ ساقلاش FullScreenHeader Contract قىسقىراش PluginSelectButton Letter Sort ھەرپ رەتكە تىزىش Category ئىقتىدار تەرتىپى QObject Add to desktop shortcuts ئۈستەل يۈزىگە قوشۇش تېز ئۇسۇل Uninstall يۈكنى چۈشۈرۈش Remove from folder قوللىنىشچان گۇرۇپپىدىن چىقىرىۋەتمەك Add to folder قوللىنىش گۇرۇپپىسىغا قوشۇلماق Dissolve folder تارقىتىۋېتىش قوللىنىش گۇرۇپپىسى Add to favorite Move to folder Add to "%1" «1٪» قوشۇلدى Show ukui-menu. باشلىنىش تاماق تىزىملىكىنى كۆرسىتىش Quit ukui-menu. باشلىنىش تىزىملىكىدىن چېكىنىپ چىقماق Fix to favorite يىغىپ ساقلىغىچە مۇقىملاشتۇرۇش Remove from favorite يىغىپ ساقلاش قىسقۇچتىن مۇقىملىقنى ئەمەلدىن قالدۇرۇش Remove from taskbar ۋەزىپە ئىستونىدىن مۇقىملىقنى ئەمەلدىن قالدۇرۇش Add to taskbar ۋەزىپە كاتەكچىسىگە مۇقىملاشتۇرۇش Send to desktop shortcuts Fixed to all applications بارلىق قوللىنىشچانلىققا مۇقىملاشتۇرۇش Unfixed from all applications بارلىق قوللىنىشتىن مۇقىملىقنى ئەمەلدىن قالدۇرۇش Create a new folder يېڭىدىن قوللىنىش گۇرۇپپىسى قۇرما Remove from List تىزىملىكتىن چىقىرىۋەتمەك SearchInputBar Search App ئىزدەش قوللىنىش Sidebar Expand قانات يايدۇرۇلدى Contract قىسقىراش Computer كومپيۇتېر Settings تەسىس قىلىش UkuiMenu::AllAppDataProvider All ھەممە ئادەم All applications 所有应用 بارلىق قوللىنىشچانلىق UkuiMenu::AppCategoryModel Audio ئاۋازلىق چاستوتا AudioVideo ئاۋاز ۋە كۆرۈنمە چاستوتا Development ئېچىش Education مائارىپ Game ئويۇن Graphics گىرافىك Network تور Office ئىشخانا Science ئىلىم -پەن Settings تەسىس قىلىش System سىستىما Utility قوللىنىشچان پروگرامما Video كۆرۈنمە چاستوتا Other باشقىسى UkuiMenu::AppCategoryPlugin Category 功能排序 ئىقتىدار تەرتىپى Audio ئاۋازلىق چاستوتا AudioVideo ئاۋاز ۋە كۆرۈنمە چاستوتا Development ئېچىش Education مائارىپ Game ئويۇن Graphics گىرافىك Network تور Office ئىشخانا Science ئىلىم -پەن Settings تەسىس قىلىش System سىستىما Utility قوللىنىشچان پروگرامما Video كۆرۈنمە چاستوتا Other باشقىسى Open Function Sort Menu ئىقتىدار رەتكە تىزىش تىزىملىكىنى ئېچىش All Applications Letter Sort ھەرپ رەتكە تىزىش Recently Installed ئەڭ يېڭى قۇراشتۇرۇش UkuiMenu::AppFolderHelper New Folder %1 يىڭى ھۆججەت قىسقۇچ ٪1 UkuiMenu::AppLetterSortPlugin Letter Sort ھەرپ رەتكە تىزىش Open Letter Sort Menu ھەرپ تەرتىپى تىزىملىكىنى ئېچىش UkuiMenu::AppSearchPlugin Search ئىزدەش UkuiMenu::FavoriteExtension Favorite يىغىپ ساقلاش UkuiMenu::FavoriteFolderHelper New Folder %1 قوللىنىش گۇرۇپپىسى ٪1 UkuiMenu::FavoriteWidget Favorite يىغىپ ساقلاش favorite يىغىپ ساقلاش UkuiMenu::PowerButton Power توك مەنبەسى Switch user خېرىدار ئالماشتۇرماق Hibernate ئۇيقۇ Suspend ئۇيقۇ Lock Screen ئېكراننى قۇلۇپلاش Log Out بىكار قىلىۋەتمەك Restart قايتىدىن قوزغىتىش Shut Down تېلېفوننى ئېتىۋېتىش <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>كومپيۇتېرنى ئېتىۋېتىڭ، لېكىن قوللىنىشچان ھالەتنى ئىزچىل ساقلاپ قالىدۇ كومپىيوتىرنى ئاچقاندا ،سىز ئايرىلغان ھالەتكە قايتقىلى بولىدۇ</p> <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>كومپيۇتېر ئېچىلىش ھالىتىنى ساقلايدۇ،لىكىن توك سەرپىياتى ئاز قوللىنىش ئىزچىل ئېچىلىش ھالىتىنى ساقلاپ كەلدى، كومپيۇتېرنى تېز سۈرئەتتە ئويغىتىدۇ ھەمدە ئايرىلىش ھالىتىڭىزگە قايتىدۇ</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>نۆۋەتتە ئابونتلار سىستېمىدىن بىكار قىلىنىپ، سۆزلىشىشنى ئاخىرلاشتۇردى ھەمدە تىزىملىتىش كۆرۈنۈشىگە قايتىدۇ</p> Upgrade and Shut Down Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart ,and then restart the computer <p>Close all applications, turn off the computer, and then turn it back on</p> <p>بارلىق قوللىنىشچان نۇقتىلارنى ئېتىپ، كومپيۇتېرنى ئېتىۋېتىڭ، ئاندىن كومپيۇتېرنى قايتىدىن ئېچىڭ</p> (Estimated %1 hour %2 minutes) (Estimated %1 minutes) <p>Close all applications, and then turn off the computer</p> <p>بارلىق قوللىنىشچان نۇقتىنى تاقاش، ئاندىن كومپيۇتېرنى ئېتىڭ</p> UkuiMenu::RecentlyInstalledModel Recently Installed ئەڭ يېڭى قۇراشتۇرۇش UkuiMenu::SidebarButtonUtils Change account settings ukui-menu/translations/ukui-menu_ar.ts0000664000175000017500000005662315160463365017136 0ustar fengfeng EditText Folder FavoriteExtension Enable editing mode تمكين وضع التحرير Remove all favorite apps إزالة جميع التطبيقات المفضلة Editing mode FolderGridView Folder FullScreenAppList All Applications Editing mode Favorite المفضله Enable editing mode تمكين وضع التحرير Remove all favorite apps إزالة جميع التطبيقات المفضلة FullScreenHeader Contract عقد PluginSelectButton Letter Sort فرز الحروف Category باب QObject Add to desktop shortcuts إضافة إلى اختصارات سطح المكتب Uninstall إلغاء التثبيت Remove from folder إزالة من المجلد Add to folder أضف إلى المجلد Dissolve folder حل المجلد Add to favorite Move to folder Add to "%1" أضف إلى "٪1" Show ukui-menu. إظهار قائمة ukui. Quit ukui-menu. قم بإنهاء قائمة ukui. Fix to favorite الإصلاح إلى المفضلة Remove from favorite إزالة من المفضلة Remove from taskbar إزالة من شريط المهام Add to taskbar إضافة إلى شريط المهام Send to desktop shortcuts Fixed to all applications ثابت لجميع التطبيقات Unfixed from all applications غير ثابت من جميع التطبيقات Create a new folder إنشاء مجلد جديد Remove from List إزالة من القائمة SearchInputBar Search App البحث في التطبيق Sidebar Expand ستوسع Contract عقد Computer حاسوب Settings اعدادات UkuiMenu::AllAppDataProvider All كل All applications 所有应用 جميع التطبيقات UkuiMenu::AppCategoryModel Audio صوتي AudioVideo سمعي فيديو Development تطور Education تعليم Game لعب Graphics الرسومات Network شبكة Office مكتب Science علم Settings اعدادات System نظام Utility الاداه المساعده Video فيديو Other آخر UkuiMenu::AppCategoryPlugin Category 功能排序 باب Audio صوتي AudioVideo سمعي فيديو Development تطور Education تعليم Game لعب Graphics الرسومات Network شبكة Office مكتب Science علم Settings اعدادات System نظام Utility الاداه المساعده Video فيديو Other آخر Open Function Sort Menu افتح قائمة فرز الوظائف All Applications Letter Sort فرز الحروف Recently Installed تم التثبيت مؤخرا UkuiMenu::AppFolderHelper New Folder %1 مجلد جديد ٪1 UkuiMenu::AppLetterSortPlugin Letter Sort فرز الحروف Open Letter Sort Menu افتح قائمة فرز الحروف UkuiMenu::AppSearchPlugin Search بحث UkuiMenu::FavoriteExtension Favorite المفضله UkuiMenu::FavoriteFolderHelper New Folder %1 مجلد جديد ٪1 UkuiMenu::FavoriteWidget Favorite المفضله favorite المفضله UkuiMenu::PowerButton Power قوة Switch user تبديل المستخدم Hibernate السبات Suspend تعليق Lock Screen قفل الشاشة Log Out تسجيل الخروج Upgrade and Shut Down Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart ,and then restart the computer Restart اعاده تمهيد (Estimated %1 hour %2 minutes) (Estimated %1 minutes) Shut Down إيقاف التشغيل <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>يظل الكمبيوتر قيد التشغيل ولكنه يستهلك طاقة أقل ، وستظل التطبيقات مفتوحة. يمكنك تنشيط الكمبيوتر بسرعة والعودة إلى الحالة التي تركتها </p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>يقوم المستخدم الحالي بتسجيل الخروج من النظام وإنهاء جلسته والعودة إلى شاشة تسجيل الدخول </p> <p>Close all applications, turn off the computer, and then turn it back on</p> <p>أغلق جميع التطبيقات، وقم بإيقاف تشغيل الكمبيوتر، ثم أعد تشغيله </p> <p>Close all applications, and then turn off the computer</p> <p>أغلق جميع التطبيقات، ثم قم بإيقاف تشغيل الكمبيوتر</p> <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>قم بإيقاف تشغيل الكمبيوتر ، لكن التطبيقات ستظل مفتوحة. عند تشغيل الكمبيوتر مرة أخرى، يمكنك العودة إلى الحالة التي كنت فيها من قبل </p> UkuiMenu::RecentlyInstalledModel Recently Installed تم التثبيت مؤخرا UkuiMenu::SidebarButtonUtils Change account settings ukui-menu/translations/ukui-menu_vi.ts0000664000175000017500000005602715160463365017150 0ustar fengfeng EditText Folder FavoriteExtension Enable editing mode Bật chế độ chỉnh sửa Remove all favorite apps Xóa tất cả các ứng dụng yêu thích Editing mode FolderGridView Folder FullScreenAppList All Applications Editing mode Favorite Yêu thích Enable editing mode Bật chế độ chỉnh sửa Remove all favorite apps Xóa tất cả các ứng dụng yêu thích FullScreenHeader Contract Hợp đồng PluginSelectButton Letter Sort Sắp xếp chữ cái Category Loại QObject Add to desktop shortcuts Thêm vào phím tắt màn hình nền Uninstall Gỡ cài đặt Remove from folder Xóa khỏi thư mục Add to folder Thêm vào thư mục Dissolve folder Thư mục hòa tan Add to favorite Move to folder Add to "%1" Thêm vào "%1" Show ukui-menu. Hiển thị ukui-menu. Quit ukui-menu. Bỏ ukui-menu. Fix to favorite Sửa thành mục yêu thích Remove from favorite Xóa khỏi mục yêu thích Remove from taskbar Xóa khỏi thanh tác vụ Add to taskbar Thêm vào thanh tác vụ Send to desktop shortcuts Fixed to all applications Cố định cho tất cả các ứng dụng Unfixed from all applications Không cố định từ tất cả các ứng dụng Create a new folder Tạo thư mục mới Remove from List Xóa khỏi danh sách SearchInputBar Search App Tìm kiếm ứng dụng Sidebar Expand Mở rộng Contract Hợp đồng Computer Máy tính Settings Cài đặt UkuiMenu::AllAppDataProvider All Tất cả All applications 所有应用 Tất cả các ứng dụng UkuiMenu::AppCategoryModel Audio Âm thanh AudioVideo Âm thanhVideo Development Phát triển Education Giáo dục Game Trò chơi Graphics Đồ họa Network Mạng lưới Office Chức Science Khoa học Settings Cài đặt System Hệ thống Utility Tiện ích Video Video Other Khác UkuiMenu::AppCategoryPlugin Category 功能排序 Loại Audio Âm thanh AudioVideo Âm thanhVideo Development Phát triển Education Giáo dục Game Trò chơi Graphics Đồ họa Network Mạng lưới Office Chức Science Khoa học Settings Cài đặt System Hệ thống Utility Tiện ích Video Video Other Khác Open Function Sort Menu Mở menu sắp xếp chức năng All Applications Letter Sort Sắp xếp chữ cái Recently Installed Đã cài đặt gần đây UkuiMenu::AppFolderHelper New Folder %1 Thư mục mới %1 UkuiMenu::AppLetterSortPlugin Letter Sort Sắp xếp chữ cái Open Letter Sort Menu Menu sắp xếp chữ cái mở UkuiMenu::AppSearchPlugin Search Tìm kiếm UkuiMenu::FavoriteExtension Favorite Yêu thích UkuiMenu::FavoriteFolderHelper New Folder %1 Thư mục mới %1 UkuiMenu::FavoriteWidget Favorite Yêu thích favorite Yêu thích UkuiMenu::PowerButton Power sức mạnh Switch user Chuyển đổi người dùng Hibernate Ngủ đông Suspend Đình chỉ Lock Screen Màn hình khóa Log Out Đăng xuất Upgrade and Shut Down Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart ,and then restart the computer Restart Khởi động lại (Estimated %1 hour %2 minutes) (Estimated %1 minutes) Shut Down Tắt nguồn <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>Máy tính vẫn bật nhưng tiêu thụ ít năng lượng hơn và các ứng dụng sẽ vẫn mở. Bạn có thể nhanh chóng đánh thức máy tính và trở về trạng thái bạn đã rời đi</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>Người dùng hiện tại đăng xuất khỏi hệ thống, kết thúc phiên của họ và quay lại màn hình đăng nhập</p> <p>Close all applications, turn off the computer, and then turn it back on</p> <p>Đóng tất cả các ứng dụng, tắt máy tính, sau đó bật lại </p> <p>Close all applications, and then turn off the computer</p> <p>Đóng tất cả các ứng dụng, sau đó tắt máy tính </p> <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>Tắt máy tính, nhưng các ứng dụng sẽ vẫn mở. Khi bạn bật lại máy tính, bạn có thể trở lại trạng thái trước đây</p> UkuiMenu::RecentlyInstalledModel Recently Installed Đã cài đặt gần đây UkuiMenu::SidebarButtonUtils Change account settings ukui-menu/translations/ukui-menu_bo_CN.ts0000664000175000017500000006764515160463365017522 0ustar fengfeng EditText Folder ཡིག་སྣོད་ FavoriteExtension Enable editing mode སྡེབ་འབོར་རྩོམ་སྒྲིག་གི་རྣམ་པ་སྤྱོད་འགོ་རྩོམ་པ Remove all favorite apps སྡུད་ཉེར་བྱས་ཟིན་པའི་ཉེར་སྤྱོད་ཆ་ཚང་འདོར་བ Editing mode FolderGridView Folder ཡིག་སྣོད་ FullScreenAppList Editing mode Favorite སྡུད་ཉེར Enable editing mode སྡེབ་འབོར་རྩོམ་སྒྲིག་གི་རྣམ་པ་སྤྱོད་འགོ་རྩོམ་པ Remove all favorite apps སྡུད་ཉེར་བྱས་ཟིན་པའི་ཉེར་སྤྱོད་ཆ་ཚང་འདོར་བ All Applications ཉེར་སྤྱོད་ཆ་ཚང་། FullScreenHeader Contract སྐུམ་པ PluginSelectButton Letter Sort གསལ་བྱེད་སྒྲིག་རིམ། Category བྱེད་ནུས་རིམ་སྒྲིག QObject Add to desktop shortcuts ཅོག་ངོས་སུ་མྱུར་འཐེབ་སྣོན་པ། Uninstall སྒྲིག་སྦྱོར་བྱས་མེད་པ། Remove from folder ཉེར་སྤྱོད་ཚོ་ལས་འབུད་པ། Add to folder ཉེར་སྤྱོད་ཚོ་ནང་ལ་གནོན་པ། Dissolve folder ཉེར་སྤྱོད་ཚོ་གཏོར་བ། Add to favorite Move to folder Add to "%1" "%1"ནང་ལ་གནོན་པ། Show ukui-menu. མགོ་འཛུགས་འདེམས་བང་འཆར་བ།。 Quit ukui-menu. མགོ་འཛུགས་འདེམས་བང་ལས་འབུད་པ。 Fix to favorite སྡུད་ཉེར་ལ་ཕྱེད་གཏན Remove from favorite སྡུད་ཁུག་ལས་གཏན་དོར་བ། Remove from taskbar འགན་སྡེ་ལས་གཏན་དོར་བ། Add to taskbar འགན་སྡེར་གཏན་ཁེལ། Send to desktop shortcuts Fixed to all applications ཉེར་སྤྱོད་ཆ་ཚང་ལ་གཏན་ཁེལ། Unfixed from all applications ཉེར་སྤྱོད་ཆ་ཚང་ལས་གཏན་ཁེལ་དོར་བ Create a new folder ཉེར་སྤྱོད་ཚོ་གསར་བཟོ Remove from List རེའུ་མིག་ལས་སུབ་པ། SearchInputBar Search App ཉེར་སྤྱོད་འཚོལ་བ Sidebar Expand འགྲེམ་པ། Contract སྐུམ་པ Computer རྩིས་འཁོར། Settings སྒྲིག་འགོད། UkuiMenu::AllAppDataProvider All ཚང་མ། All applications 所有应用 སྤྱོད་བྱ་ཚང་མ། UkuiMenu::AppCategoryModel Audio སྒྲ་ཕབ། AudioVideo སྒྲ་བརྙན Development གོང་འཕེལ་ Education སློབ་གསོ། Game རོལ་རྩེད། Graphics པར་རིས Network དྲ་རྒྱ། Office གཞུང་ལས།དྲ་རྒྱ། Science ཚན་རིག Settings བཀོད་སྒྲིག་བཅས་བྱ་དགོས། System ལམ་ལུགས། Utility ཉེར་སྤྱོད་བ་རིམ Video བརྙན་ཕབ། Other དེ་མིན། UkuiMenu::AppCategoryPlugin Category 功能排序 བྱེད་ནུས་རིམ་སྒྲིག Audio སྒྲ་ཕབ། AudioVideo སྒྲ་བརྙན Development གོང་འཕེལ་ Education སློབ་གསོ། Game རོལ་རྩེད། Graphics པར་རིས Network དྲ་རྒྱ། Office གཞུང་ལས།དྲ་རྒྱ། Science ཚན་རིག Settings བཀོད་སྒྲིག་བཅས་བྱ་དགོས། System ལམ་ལུགས། Utility ཉེར་སྤྱོད་བ་རིམ Video བརྙན་ཕབ། Other དེ་མིན། Open Function Sort Menu སྒོ་མོ་འབྱེད་པའི་ནུས་པའི་དཀར་ཆག། Letter Sort གསལ་བྱེད་སྒྲིག་རིམ། Recently Installed གསར་ཤོས་སྒྲིག་འཇུག All Applications ཉེར་སྤྱོད་ཆ་ཚང་། UkuiMenu::AppFolderHelper New Folder %1 ཉེར་སྤྱོད་ཚོ%1 UkuiMenu::AppLetterSortPlugin Letter Sort གསལ་བྱེད་སྒྲིག་རིམ། Open Letter Sort Menu སྒོ་མོ་འབྱེད་པའི་ཡི་གེ། UkuiMenu::AppSearchPlugin Search སྤྱིའི་་བཤེར་འཚོལ། UkuiMenu::FavoriteExtension Favorite སྡུད་ཉེར UkuiMenu::FavoriteFolderHelper New Folder %1 ཉེར་སྤྱོད་ཚོ%1 UkuiMenu::FavoriteWidget Favorite སྡུད་ཉེར favorite སྡུད་ཉེར UkuiMenu::PowerButton Power སྟོབས་ཤུགས། Switch user སྤྱོད་མཁན་བརྗེ་བ། Hibernate མངལ་གནས་སུ་སྦས་པ། Suspend ལས་མཚམས་བཞག་པ། Lock Screen བརྙན་ཡོལ་དམིགས་འཛིན Log Out དོར་བ ,and then restart the computer Restart ཡང་བསྐྱར་འགོ་འཛུགས་ Shut Down ལས་མཚམས་འཇོག <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>གློག་ཀླད་ཁ་ཕྱེ་བའི་རྣམ་པ་རྒྱུན་སྲུང་རུང་གློག་ཟད་ཚད་ཅུང་ཉུང་།ཉེར་སྤྱོད་ཀྱིས་ནམ་ཡང་ཁ་ཕྱེ་བའི་རྣམ་པ་རྒྱུན་བསྲངས་ནས་མྱུར་དུ་གློག་ཀླད་ནི་ཁྱེད་རང་ཁ་བྲེལ་བའི་རྣམ་པར་བསྐྱར་གསོ་བྱས་ཆོག</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>མིག་སྔའི་སྤྱོད་མཁན་རྒྱུད་ཁོངས་ལས་དོར་ནས།དེའི་གཏམ་གླེང་འཇུག་སྒྲིལ་ཏེ་ཐོ་འགོད་མཚམས་ངོས</p>་སུ་ལོག <p>Close all applications, turn off the computer, and then turn it back on</p> <p>ཉེར་སྤྱོད་ཆ་ཚང་ཁ་རྒྱོབ།གློག་ཀླད་ཁ་རྒྱོབ།དེ་ནས་ཡང་བསྐྱར་གློག་ཀླད་</p>ཁ་ཕྱེས། <p>Close all applications, and then turn off the computer</p> <p>བེད་སྤྱོད་ཡོད་ཚད་སྒོ་རྒྱག་པ་དང་།དེ་ནས་གློག་ཀླད་སྒོ་རྒྱག་དགོས་།</p> <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>གློག་ཀླད་ཁ་ཕྱེ་བའི་རྣམ་པ་རྒྱུན་སྲུང་རུང་གློག་ཟད་ཚད་ཅུང་ཉུང་།ཉེར་སྤྱོད་ཀྱིས་ནམ་ཡང་ཁ་ཕྱེ་བའི་རྣམ་པ་རྒྱུན་བསྲངས་ནས་མྱུར་དུ་གློག་ཀླད་ནི་ཁྱེད་རང་ཁ་བྲེལ་བའི་རྣམ་པར་བསྐྱར་གསོ་བྱས་ཆོག</p> Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart གསར་སྒྱུར་གལ་ཆེ་མཉམ་དུ་འཛིན་པ། (Estimated %1 hour %2 minutes) (Estimated %1 minutes) Upgrade and Shut Down གསར་སྒྱུར་བྱས་པར་མ་ཟད་།ཁ་བརྒྱབ་། Estimated %1 hour %2 minutes སྔོན་དཔག་ལྟར་ན་ཆུ་ཚོད་1དང་སྐར་མ་2ཟིན་གྱི་ཡོད། Estimated %1 minutes སྔོན་དཔག་བྱས་ན་སྐར་མ་1ཡིན། Close all applications, upgrade the computer( ཉེར་སྤྱོད་ཚང་མ་སྒོ་བརྒྱབ་ནས་གློག་ཀླད་གསར་སྒྱུར་བྱེད་དགོས། and then restart the computer དེ་ནས་གློག་ཀླད་ཡང་བསྐྱར་ཕྱེ་བ་རེད། and then turn off it དེ་ནས་གློག་ཀླད་སྒོ་རྒྱག་དགོས། UkuiMenu::RecentlyInstalledModel Recently Installed གསར་ཤོས་སྒྲིག་འཇུག UkuiMenu::SidebarButtonUtils Change account settings ཐོ་ཁོངས་བཟོ་བཅོས་རྒྱག་རྒྱུ། ukui-menu/translations/ukui-menu_kk_KZ.ts0000664000175000017500000006117215160463365017540 0ustar fengfeng EditText Folder FavoriteExtension Enable editing mode تۇركىمدەپ تالداۋجاساۋ ۇلگىنى ٸسكە كىرستىرۋ Remove all favorite apps بارلٸق جيىپ ساقتاۋ قوللىنىلىشىنى بوس ەتۋ Editing mode FolderGridView Folder FullScreenAppList Enable editing mode تۇركىمدەپ تالداۋجاساۋ ۇلگىنى ٸسكە كىرستىرۋ Remove all favorite apps بارلٸق جيىپ ساقتاۋ قوللىنىلىشىنى بوس ەتۋ All Applications Editing mode Favorite جيىپ ساقتاۋ FullScreenHeader Contract قىسقارۋ PluginSelectButton Letter Sort ٴارىپ تارتٸپكە تىزۋ Category رولٸ جاعٸ QObject Add to favorite Remove from folder قولدانعىش گرۋپپادان شىعارىپ جىبەرمەك Move to folder Add to folder قولدانۋ گرۋپپاسىنا قوسىلماق Add to "%1" "%1" قوسىلدى Dissolve folder تارقاتىپ ەتۋ قولدانۋ گرۋپپاسى Fix to favorite جيىپ ساقلىغۇچە تۇراقتاندىرۋ Remove from favorite جيىپ ساقتاۋ قىسقشتان تۇراقتىلىعىن كۇشىنەن قالدىرۋ Fixed to all applications بارلٸق قوللىنىشچانلىققا تۇراقتاندىرۋ Unfixed from all applications بارلٸق قولدانۋدان تۇراقتىلىعىن كۇشىنەن قالدىرۋ Remove from taskbar مىندەتتى ستونىنان تۇراقتىلىعىن كۇشىنەن قالدىرۋ Add to taskbar مىندەتتى كاتەكشەسىنە تۇراقتاندىرۋ Send to desktop shortcuts Add to desktop shortcuts ۇستەل بەتىنە قوسۋ تەز ٴتاسىل Uninstall جۇكتى دومالاتۋ Show ukui-menu. باستالۋ تاماق تٸزٸمدٸكدٸ كورسەتۋ Quit ukui-menu. باستالۋ تٸزٸمدٸگٸنەن شەگنىپ شىقپاق Create a new folder جاڭادان قولدانۋ گرۋپپاسى قۇرما Remove from List تٸزٸمدەٸكتەن شىعارىپ جىبەرمەك SearchInputBar Search App ٸزدەۋ قولدانۋ Sidebar Expand قانات يايدۇرۇلدى Contract قىسقارۋ Computer كومپيۋتەر Settings ورنالاسترعان ەتۋ UkuiMenu::AllAppDataProvider All بارلٸق قوللىنىشچانلىق All applications بارلٸق قوللىنىشچانلىق UkuiMenu::AppCategoryModel Audio دىبىستى جيٸلٸك AudioVideo اۋا ۋا كورىنبە جيٸلٸك Development ٸشٸۋ Education وقۋ-اعارتۋ Game ويٸن Graphics گرافيىك Network تور Office كەڭسە Science عىلىم -عىلىم Settings ورنالاسترعان ەتۋ System سەستيما Utility قولدانعىش پروگرامما Video كورىنبە جيٸلٸك Other باسقاسى UkuiMenu::AppCategoryPlugin Category رولٸ جاعٸ Audio دىبىستى جيٸلٸك AudioVideo اۋا ۋا كورىنبە جيٸلٸك Development ٸشٸۋ Education وقۋ-اعارتۋ Game ويٸن Graphics گرافيىك Network تور Office كەڭسە Science عىلىم -عىلىم Settings ورنالاسترعان ەتۋ System سەستيما Utility قولدانعىش پروگرامما Video كورىنبە جيٸلٸك Other باسقاسى Open Function Sort Menu رولٸ تارتٸپكە تىزۋ تٸزٸمدٸكدٸ ٸشٸۋ All Applications Letter Sort ٴارىپ تارتٸپكە تىزۋ Recently Installed ەڭ جاڭا قۇراستىرۋ UkuiMenu::AppFolderHelper New Folder %1 جاڭا حۇجات قىسقىش %1 UkuiMenu::AppLetterSortPlugin Letter Sort ٴارىپ تارتٸپكە تىزۋ Open Letter Sort Menu ٴارىپ جاعٸ تٸزٸمدٸكدٸ ٸشٸۋ UkuiMenu::AppSearchPlugin Search ٸزدەۋ UkuiMenu::FavoriteExtension Favorite جيىپ ساقتاۋ UkuiMenu::FavoriteFolderHelper New Folder %1 جاڭا حۇجات قىسقىش %1 UkuiMenu::FavoriteWidget Favorite جيىپ ساقتاۋ favorite جيىپ ساقتاۋ UkuiMenu::PowerButton Power توك قاينارى Switch user قاريدار ايلاندىرماق Hibernate ۇيقى Suspend ۇيقى Lock Screen ەكٸرانٸ قۇلپىلاۋ Log Out بوس قىلىۋەتمەك Restart قايتادان قوزعالتۋ Shut Down قولفوندى جابىپ الۋ <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>كومپيۋتەردى قۇلىپتاپ ەت، بىراق قولدانعىش قالىپىن باستان اقٸر ساقتاپ قالدى كومپيۋتەردى اشقاندا ،ٴسىز ايىرىلعان قالىپقا قايتقالى بولادٸ</p> <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>كومپيۋتەر اشىلۋ كۇيىن ساقتايدى،لىكى توك سارىپ قىلعانى از قولدانۋ باستان اقٸر اشىلۋ كۇيىن ساقتاپ كەلدى، كومپيۋتەرنى تەز جىلدامدىقتا ئويغىتىدۇ ونىڭ ۇستىنە ايٸرٸلۋ ھالىتىڭىزگە قايتادى</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>كەزەكتە ابونتتار سەستامادان بوس ورىندالىپ، سويلەسۋدى اقىرلاستىردى ونىڭ ۇستىنە تٸزٸمدەۇ كورىنسكە قايتادى</p> Upgrade and Shut Down Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart ,and then restart the computer <p>Close all applications, turn off the computer, and then turn it back on</p> <p>بارلٸق قولدانعىش نۇكتەلەردى ٶلترۋ، كومپيۋتەردى قۇلىپتاپ ەت، سونان كومپيۋتەردى قايتادان ٴٸشڭٸز</p> (Estimated %1 hour %2 minutes) (Estimated %1 minutes) <p>Close all applications, and then turn off the computer</p> <p>بارلٸق قولدانعىش نۇكتەنى تاقاۋ، سونان كومپيۋتەردى جاپ</p> UkuiMenu::RecentlyInstalledModel Recently Installed ەڭ جاڭا قۇراستىرۋ UkuiMenu::SidebarButtonUtils Change account settings ukui-menu/translations/ukui-menu_ky.ts0000664000175000017500000006370115160463365017152 0ustar fengfeng EditText Folder تىزىمدىك FavoriteExtension Enable editing mode ماسسالىق تۅپتۅش قالپىن ىشكە كىرىشتىرۉۉ Remove all favorite apps باردىق جىيىپ ساقتوو تىركەمەلەر بەكەر جاسوو ،اتقارۇۇ Editing mode FolderGridView Folder تىزىمدىك FullScreenAppList Editing mode Favorite تەز زىيارات جاسوو ،اتقارۇۇ Enable editing mode ماسسالىق تۅپتۅش قالپىن ىشكە كىرىشتىرۉۉ Remove all favorite apps باردىق جىيىپ ساقتوو تىركەمەلەر بەكەر جاسوو ،اتقارۇۇ All Applications باردىق پراگراممالار FullScreenHeader Contract قىسقارۇۇ PluginSelectButton Letter Sort تامعا ىرەتكە تىزۉۉ Category تۉرۉ QObject Add to desktop shortcuts شىرە بەتىنە قوشۇۇ تەز ىڭعاي Uninstall ۅچۉرۉۉ Remove from folder قولدونۇشچان گۇرۇپپادان چىقىرىۋەتمەك Add to folder قولدونۇش گۇرۇپپاسىنا ماقۇلدۇعۇ Dissolve folder تارقاتىپ جىبەرۉۉ قولدونۇش گۇرۇپپاسى Add to favorite Move to folder Add to "%1" «1٪» قوشۇلدۇ Show ukui-menu. باشتالۇۇ تاماق ، اش تىزىمدىگىن كۅرسۅتۉۉ Quit ukui-menu. باشتالۇۇ تىزىمدىكدەن جانىپ چىعىش Fix to favorite جىيىپ ساقلىغىچە تۇرۇقتاندىرۇۇ Remove from favorite جىيىپ ساقتوو قىسقىچتان تۇرۇقتۇۇلۇق ارعادان قالتىرىش Remove from taskbar مىلدەت ىستوندون تۇرۇقتۇۇلۇق ارعادان قالتىرىش Add to taskbar مىلدەت قاشااسىنا تۇرۇقتاندىرۇۇ Send to desktop shortcuts Fixed to all applications باردىق قولدونۇلۇۇ. تۇرۇقتاندىرۇۇ Unfixed from all applications باردىق قولدونۇۇدان تۇرۇقتۇۇلۇق ارعادان قالتىرىش Create a new folder جاڭىدان قولدونۇش گۇرۇپپاسى گۇرما Remove from List تىزىمدىكتەن چىقىرىۋەتمەك SearchInputBar Search App ىزدۅۅ قولدونۇش Sidebar Expand قانات جارىيالانعان Contract قىسقارۇۇ Computer كومپىيۇتەر Settings قۇرۇۇ ، اچۇۇ ، باشتوو جاسوو ،اتقارۇۇ UkuiMenu::AllAppDataProvider All باردىعى All applications 所有应用 باردىق قولدونۇشچان پراگراممالار UkuiMenu::AppCategoryModel Audio دووش AudioVideo دووش جانا كۅرۉنمۅ چاستوتا Development اچۇۇ Education اعارتۇۇ Game ويۇن Graphics گىرافىك Network تور Office كەڭسە Science بئلىم -پەن Settings تور تەڭشەگى System ساامالىق Utility قولدونۇشچان پراگرامما Video ايىپ Other باشقا UkuiMenu::AppCategoryPlugin Category 功能排序 تۉرۉ Audio دووش AudioVideo دووش جانا كۅرۉنمۅ چاستوتا Development اچۇۇ Education اعارتۇۇ Game ويۇن Graphics گىرافىك Network تور Office كەڭسە Science بئلىم -پەن Settings تور تەڭشەگى System ساامالىق Utility قولدونۇشچان پراگرامما Video ايىپ Other باشقا Open Function Sort Menu قۇربات ، جۅندۅم ىرەتكە تىزۉۉ تىزىمدىگىن اچۇۇ Letter Sort تامعا ىرەتكە تىزۉۉ Recently Installed ەڭ جاڭى قۇراشتىرۇۇ All Applications باردىق پراگراممالار UkuiMenu::AppFolderHelper New Folder %1 جاڭى ۅجۅت قىپچىعىچ ٪1 UkuiMenu::AppLetterSortPlugin Letter Sort تامعا ىرەتكە تىزۉۉ Open Letter Sort Menu تامعا تارتىبى تىزىمدىگىن اچۇۇ UkuiMenu::AppSearchPlugin Search ىزدۅۅ UkuiMenu::FavoriteExtension Favorite تەز زىيارات جاسوو ،اتقارۇۇ UkuiMenu::FavoriteFolderHelper New Folder %1 جاڭى ۅجۅت قىپچىعىچ ٪1 UkuiMenu::FavoriteWidget Favorite تەز زىيارات جاسوو ،اتقارۇۇ favorite جاقشى كۅرۅتۇرعان UkuiMenu::PowerButton Power بىيلىك Switch user ىشتەتۉۉچۉنۉ الماشتىرۇۇ Hibernate ۇيقۇ Suspend توقتوتۇۇ Lock Screen ەكىران قۇلۇپتوو Log Out بەكەر قىلىۋەتمەك ,and then restart the computer Restart قايرا قوزعوتۇۇ Shut Down جااپ جىبەرۉۉ <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p> كومپىيۇتەر اچىلىش ابالىن ساقتايت ،بىروق تۅك كەرەكتۅۅ از قولدونۇش ۉزگۉلتۉكسۉز اچىلىش ابالىن ساقتاپ كەلدى، كومپىيۇتەرنى تەز تەزدىكتە ويعونوت. داعى ايرىلۇۇ جاعدايىڭىزعا قايتات</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>گەزەكتە ابونتتار سەستىمادان بەكەر قىلىنىپ، سۉيلۅشۉۉنۉ بۉتتۉ. داعى تىزىمدەتىش كۅرۉنۉشۉنۅ قايتات</p> <p>Close all applications, turn off the computer, and then turn it back on</p> <p>باردىق قولدونۇشچان تۉيۉندۅردۉ ايتىپ، كومپىيۇتەردى ەتىپ جىبەرىڭ، اندان كومپىيۇتەردى قايتادان اچىڭ</p> <p>Close all applications, and then turn off the computer</p> <p>باردىق قولدونۇشچان تۉيۉندۉ بەكىتىش ، اندان كومپىيۇتەردى بەكىتىڭ</p> <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p> كومپىيۇتەردى ەتىپ جىبەرىڭ، بىروق قولدونۇشچان ابالىن ۉزگۉلتۉكسۉز ساقتاپ قالات كومپىيوتەردى اچقاندا ،سىز ايرىلعان ابالعا قايتقالى بولوت </p> Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart جانيلوچو جانا جوكتو (Estimated %1 hour %2 minutes) (Estimated %1 minutes) Upgrade and Shut Down جانيلوچو جانا اوشورور Estimated %1 hour %2 minutes كُوتُو بويُنجَ %1 سَات %2 مْنُت Estimated %1 minutes كُوتُو بويُنجَ %1 مْنُت Close all applications, upgrade the computer( بارديك برُوقرَملَاردي جَابُوا, كومبيوتردي جانيرتوغا and then restart the computer أندان كئِين كومبيوترا قايِرَا جُكْتُو and then turn off it أندان كئِين كومبيوتردي جابُوا UkuiMenu::RecentlyInstalledModel Recently Installed ەڭ جاڭى قۇراشتىرۇۇ UkuiMenu::SidebarButtonUtils Change account settings إسِيبْتِيك جَازْبَانِ أوزغُورْتُو ukui-menu/translations/ukui-menu_ug.ts0000664000175000017500000006355615160463365017152 0ustar fengfeng EditText Folder ھۆججەت قىسقۇچ FavoriteExtension Enable editing mode تۈركۈملەپ تەھرىرلەش ئەندىزىسىنى ئىشقا كىرىشتۈرۈش Remove all favorite apps بارلىق يىغىپ ساقلاش قوللىنىلىشىنى بىكار قىلىش Editing mode FolderGridView Folder ھۆججەت قىسقۇچ FullScreenAppList Editing mode Favorite ياخشى كۆرىدىغان Enable editing mode تۈركۈملەپ تەھرىرلەش ئەندىزىسىنى ئىشقا كىرىشتۈرۈش Remove all favorite apps بارلىق يىغىپ ساقلاش قوللىنىلىشىنى بىكار قىلىش All Applications بارلىق پروگراممىلار FullScreenHeader Contract قىسقىراش PluginSelectButton Letter Sort ھەرپ رەتكە تىزىش Category تۈرى QObject Add to desktop shortcuts ئۈستەل يۈزىگە قوشۇش تېز ئۇسۇل Uninstall ئۆچۈرۈش Remove from folder قوللىنىشچان گۇرۇپپىدىن چىقىرىۋەتمەك Add to folder قوللىنىش گۇرۇپپىسىغا قوشۇلماق Dissolve folder تارقىتىۋېتىش قوللىنىش گۇرۇپپىسى Add to favorite Move to folder Add to "%1" «1%» قوشۇلدى Show ukui-menu. باشلىنىش تاماق تىزىملىكىنى كۆرسىتىش Quit ukui-menu. باشلىنىش تىزىملىكىدىن چېكىنىپ چىقماق Fix to favorite يىغىپ ساقلىغىچە مۇقىملاشتۇرۇش Remove from favorite يىغىپ ساقلاش قىسقۇچتىن مۇقىملىقنى ئەمەلدىن قالدۇرۇش Remove from taskbar ۋەزىپە ئىستونىدىن مۇقىملىقنى ئەمەلدىن قالدۇرۇش Add to taskbar ۋەزىپە كاتەكچىسىگە مۇقىملاشتۇرۇش Send to desktop shortcuts Fixed to all applications بارلىق قوللىنىشچانلىققا مۇقىملاشتۇرۇش Unfixed from all applications بارلىق قوللىنىشتىن مۇقىملىقنى ئەمەلدىن قالدۇرۇش Create a new folder يېڭىدىن قوللىنىش گۇرۇپپىسى قۇرما Remove from List تىزىملىكتىن چىقىرىۋەتمەك SearchInputBar Search App ئىزدەش قوللىنىش Sidebar Expand قانات يايدۇرۇلدى Contract قىسقىراش Computer ھېسابلىغۇچ Settings تەڭشەك UkuiMenu::AllAppDataProvider All بارلىق All applications 所有应用 بارلىق قوللىنىشچان پروگراممىلار UkuiMenu::AppCategoryModel Audio ئۈن AudioVideo ئاۋاز ۋە كۆرۈنمە چاستوتا Development ئېچىش Education مائارىپ Game ئويۇن Graphics گىرافىك Network تور Office ئشىخانا Science ئىلىم -پەن Settings تەڭشەك System سىستېما Utility قوللىنىشچان پروگرامما Video سىن Other باشقا UkuiMenu::AppCategoryPlugin Category 功能排序 تۈرى Audio ئۈن AudioVideo ئاۋاز ۋە كۆرۈنمە چاستوتا Development ئېچىش Education مائارىپ Game ئويۇن Graphics گىرافىك Network تور Office ئشىخانا Science ئىلىم -پەن Settings تەڭشەك System سىستېما Utility قوللىنىشچان پروگرامما Video سىن Other باشقا Open Function Sort Menu ئىقتىدار رەتكە تىزىش تىزىملىكىنى ئېچىش Letter Sort ھەرپ رەتكە تىزىش Recently Installed ئەڭ يېڭى قۇراشتۇرۇش All Applications بارلىق پروگراممىلار UkuiMenu::AppFolderHelper New Folder %1 يېڭى ھۆججەت قىسقۇچ %1 UkuiMenu::AppLetterSortPlugin Letter Sort ھەرپ رەتكە تىزىش Open Letter Sort Menu ھەرپ تەرتىپى تىزىملىكىنى ئېچىش UkuiMenu::AppSearchPlugin Search ئىزدەش UkuiMenu::FavoriteExtension Favorite ياخشى كۆرىدىغان UkuiMenu::FavoriteFolderHelper New Folder %1 يېڭى ھۆججەت قىسقۇچ %1 UkuiMenu::FavoriteWidget Favorite ياخشى كۆرىدىغان favorite ياخشى كۆرىدىغان UkuiMenu::PowerButton Power توك مەنبەسىنى باشقۇرۇش Switch user ئىشلەتكۈچىنى ئالماشتۇرۇش Hibernate ئۇيقۇدا Suspend ئېسىش Lock Screen ئېكران قۇلۇپلاش Log Out بىكار قىلىۋەتمەك ,and then restart the computer Restart قايتا قوزغىتىش Shut Down تاقىۋېتىش <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p>كومپيۇتېر ئېچىلىش ھالىتىنى ساقلايدۇ،لىكىن توك سەرپىياتى ئاز قوللىنىش ئىزچىل ئېچىلىش ھالىتىنى ساقلاپ كەلدى، كومپيۇتېرنى تېز سۈرئەتتە ئويغىتىدۇ ھەمدە ئايرىلىش ھالىتىڭىزگە قايتىدۇ</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>نۆۋەتتە ئابونتلار سىستېمىدىن بىكار قىلىنىپ، سۆزلىشىشنى ئاخىرلاشتۇردى ھەمدە تىزىملىتىش كۆرۈنۈشىگە قايتىدۇ</p> <p>Close all applications, turn off the computer, and then turn it back on</p> <p>بارلىق قوللىنىشچان نۇقتىلارنى ئېتىپ، كومپيۇتېرنى ئېتىۋېتىڭ، ئاندىن كومپيۇتېرنى قايتىدىن ئېچىڭ</p> <p>Close all applications, and then turn off the computer</p> <p>بارلىق قوللىنىشچان نۇقتىنى تاقاش، ئاندىن كومپيۇتېرنى ئېتىڭ</p> <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p>كومپيۇتېرنى ئېتىۋېتىڭ، لېكىن قوللىنىشچان ھالەتنى ئىزچىل ساقلاپ قالىدۇ كومپىيوتىرنى ئاچقاندا ،سىز ئايرىلغان ھالەتكە قايتقىلى بولىدۇ</p> Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart يېڭىلاش ھەمدە قايتىدىن قوزغىتىش (Estimated %1 hour %2 minutes) (Estimated %1 minutes) Upgrade and Shut Down يېڭىلاش ھەمدە يانفونى ئېتىك Estimated %1 hour %2 minutes مۆلچەرلىنىشچە ٪1 سائەت ئىككى مىنۇت Estimated %1 minutes مۆلچەرلىنىشىچە ٪1 مىنۇت Close all applications, upgrade the computer( بارلىق قوللىنىشچان نۇقتىنى تاقاش ، كومپيۇتېرنى يېڭىلاش ( and then restart the computer ئاندىن كومپيۇتېرنى قايتىدىن قوزغىتىمىز and then turn off it ئاندىن كومپيۇتېرنى ئېتىۋېتىش UkuiMenu::RecentlyInstalledModel Recently Installed ئەڭ يېڭى قۇراشتۇرۇش UkuiMenu::SidebarButtonUtils Change account settings ھېسابات تەسىس قىلىشنى ئۆزگەرتىش ukui-menu/translations/ukui-menu_ky_KG.ts0000664000175000017500000006215315160463365017533 0ustar fengfeng EditText Folder FavoriteExtension Enable editing mode تۈركۈملەپ تۅپتۅش قالپىن ىشكە كىرىشتىرۉۉ Remove all favorite apps باردىق جىيىپ ساقتوو قوللىنىلىشىنى بەكەر جاسوو ،اتقارۇۇ Editing mode FolderGridView Folder FullScreenAppList Enable editing mode تۈركۈملەپ تۅپتۅش قالپىن ىشكە كىرىشتىرۉۉ Remove all favorite apps باردىق جىيىپ ساقتوو قوللىنىلىشىنى بەكەر جاسوو ،اتقارۇۇ All Applications Editing mode Favorite جىيىپ ساقتوو FullScreenHeader Contract قىسقارۇۇ PluginSelectButton Letter Sort تامعا ىرەتكە تىزۉۉ Category قۇربات ، جۅندۅم تارتىبى QObject Add to desktop shortcuts شىرە بەتىنە قوشۇۇ تەز ىڭعاي Uninstall يۈكنى تۉشۉرۉ Remove from folder قولدونۇشچان گۇرۇپپادان چىقىرىۋەتمەك Add to folder قولدونۇش گۇرۇپپاسىنا قوشۇلماق Dissolve folder تارقاتىپ جىبەرۉۉ قولدونۇش گۇرۇپپاسى Add to favorite Move to folder Add to "%1" "%1" قوشۇلدۇ Show ukui-menu. باشتالۇۇ تاماق ، اش تىزىمدىگىن كۅرسۅتۉۉ Quit ukui-menu. باشتالۇۇ تىزىمدىكدەن جانىپ چىعىش Fix to favorite جىيىپ ساقلىغۇچە تۇرۇقتاندىرۇۇ Remove from favorite جىيىپ ساقتوو قىسقىچتان مۇقىملىقنى ارعادان قالتىرىش Remove from taskbar مىلدەت ىستوندون مۇقىملىقنى ارعادان قالتىرىش Add to taskbar مىلدەت قاشااسىنا تۇرۇقتاندىرۇۇ Send to desktop shortcuts Fixed to all applications باردىق قوللىنىشچانلىققا تۇرۇقتاندىرۇۇ Unfixed from all applications باردىق قولدونۇۇدان مۇقىملىقنى ارعادان قالتىرىش Create a new folder جاڭىدان قولدونۇش گۇرۇپپاسى قۇرما Remove from List تىزىمدىكتەن چىقىرىۋەتمەك SearchInputBar Search App ىزدۅۅ قولدونۇش Sidebar Expand قانات يايدۇرۇلدى Contract قىسقارۇۇ Computer كومپىيۇتەر Settings قۇرۇۇ ، اچۇۇ ، باشتوو جاسوو ،اتقارۇۇ UkuiMenu::AllAppDataProvider All باردىق قوللىنىشچانلىق All applications 所有应用 باردىق قوللىنىشچانلىق UkuiMenu::AppCategoryModel Audio دووشتۇ چاستوتا AudioVideo دووش جانا كۅرۉنمۅ چاستوتا Development اچۇۇ Education اعارتۇۇ Game ويۇن Graphics گىرافىك Network تور Office ئشقانا Science ئىلىم -پەن Settings قۇرۇۇ ، اچۇۇ ، باشتوو جاسوو ،اتقارۇۇ System ساامالىق Utility قولدونۇشچان پراگرامما Video كۅرۉنمۅ چاستوتا Other باشقاسى UkuiMenu::AppCategoryPlugin Category 功能排序 قۇربات ، جۅندۅم تارتىبى Audio دووشتۇ چاستوتا AudioVideo دووش جانا كۅرۉنمۅ چاستوتا Development اچۇۇ Education اعارتۇۇ Game ويۇن Graphics گىرافىك Network تور Office ئشقانا Science ئىلىم -پەن Settings قۇرۇۇ ، اچۇۇ ، باشتوو جاسوو ،اتقارۇۇ System ساامالىق Utility قولدونۇشچان پراگرامما Video كۅرۉنمۅ چاستوتا Other باشقاسى Open Function Sort Menu قۇربات ، جۅندۅم ىرەتكە تىزۉۉ تىزىمدىگىن اچۇۇ All Applications Letter Sort تامعا ىرەتكە تىزۉۉ Recently Installed ەڭ جاڭى قۇراشتىرۇۇ UkuiMenu::AppFolderHelper New Folder %1 جاڭى ۅجۅت قىپچىعىچ %1 UkuiMenu::AppLetterSortPlugin Letter Sort تامعا ىرەتكە تىزۉۉ Open Letter Sort Menu تامعا تارتىبى تىزىمدىگىن اچۇۇ UkuiMenu::AppSearchPlugin Search ىزدۅۅ UkuiMenu::FavoriteExtension Favorite جىيىپ ساقتوو UkuiMenu::FavoriteFolderHelper New Folder %1 جاڭى ۅجۅت قىپچىعىچ %1 UkuiMenu::FavoriteWidget Favorite جىيىپ ساقتوو favorite جىيىپ ساقتوو UkuiMenu::PowerButton Power تۅك قاينارى Switch user سووداگەر ، جولووچۇ ئالماشتۇرماق Hibernate ۇيقۇ Suspend ۇيقۇ Lock Screen ەكراندى قۇلۇپتوو Log Out بەكەر قىلىۋەتمەك Restart قايتادان قوزعوتۇۇ Shut Down تەلفوندۇ ەتىپ جىبەرۉۉ <p>Turn off the computer, but the applications will remain open. When you turn on the computer again, you can return to the state you were in before</p> <p> كومپىيۇتەردى ەتىپ جىبەرىڭ، بىروق قولدونۇشچان ابالىن ۉزگۉلتۉكسۉز ساقتاپ قالات كومپىيۇتەردى اچقاندا ،سىز ايرىلعان ابالعا قايتقالى بولوت </p> <p>The computer remains on but consumes less power, and the applications will remain open. You can quickly wake up the computer and return to the state you left</p> <p> كومپىيۇتەر اچىلىش ابالىن ساقتايت ،بىروق تۅك سەرپىياتى از قولدونۇش ۉزگۉلتۉكسۉز اچىلىش ابالىن ساقتاپ كەلدى، كومپىيۇتەرنى تەز تەزدىكتە ئويغىتىدۇ داعى ايرىلۇۇ جاعدايىڭىزعا قايتات</p> <p>The current user logs out of the system, ending their session and returning to the login screen</p> <p>گەزەكتە ابونتتار سەستىمادان بەكەر قىلىنىپ، سۉيلۅشۉۉنۉ ئاخىرلاشتۇردى داعى تىزىمدەتىش كۅرۉنۉشۉنۅ قايتات</p> Upgrade and Shut Down Close all applications, upgrade the computer ,and then turn off it Upgrade and Restart ,and then restart the computer <p>Close all applications, turn off the computer, and then turn it back on</p> <p>باردىق قولدونۇشچان تۉيۉندۅردۉ ايتىپ، كومپىيۇتەردى ەتىپ جىبەرىڭ، اندان كومپىيۇتەردى قايتادان اچىڭ</p> (Estimated %1 hour %2 minutes) (Estimated %1 minutes) <p>Close all applications, and then turn off the computer</p> <p>باردىق قولدونۇشچان تۉيۉندۉ بەكىتىش ، اندان كومپىيۇتەردى بەكىتىڭ</p> UkuiMenu::RecentlyInstalledModel Recently Installed ەڭ جاڭى قۇراشتىرۇۇ UkuiMenu::SidebarButtonUtils Change account settings ukui-menu/README.md0000664000175000017500000001021115160463353012677 0ustar fengfeng### 简介 开始菜单(ukui-menu)是UKUI桌面环境的一部分,是一个简易的应用程序启动器,通常与UKUI一起发布。 > Application launcher for ukui desktop environment ukui-menu is a convenient application launcher which includes normal and full-screen modes, it also has a plugin page which can be used to load extra plugins like favourite apps page, recent files page, etc. * 基本功能 1. 显示应用程序图标列表 2. 左键点击应用图标打开应用程序 3. 右键点击应用图标显示右键菜单 * 额外功能 (由开始菜单扩展插件实现) 1. 应用收藏夹 2. 最近文件列表 3. AI助手 ### 扩展插件 (基于版本:upstream/4.10.1.0) 可扩展性是开始菜单的设计目标之一,目的是让用户可以更方便快捷的使用一些额外功能。 当然用户也可以自行编写自己的扩展插件,以下是关于扩展插件的简单介绍。 * 插件入口 扩展插件在开始菜单中被抽象为MenuExtensionPlugin类,该接口是开始菜单插件的主入口。基于该接口可以开发多种插件。 ```c++ // 头文件路径为:src/extension/menu-extension-plugin.h class Q_DECL_EXPORT MenuExtensionPlugin : public QObject { Q_OBJECT public: explicit MenuExtensionPlugin(QObject *parent = nullptr); ~MenuExtensionPlugin() override; /** * 插件的唯一id,会被用于区分插件 * @return 唯一id */ virtual QString id() = 0; /** * 创建一个Widget扩展 * @return 返回nullptr代表不生产此插件 */ virtual WidgetExtension *createWidgetExtension() = 0; /** * 创建上下文菜单扩展 * @return 返回nullptr代表不生产此插件 */ virtual ContextMenuExtension *createContextMenuExtension() = 0; }; ``` 目前4.10.1.0版本的开始菜单提供了以下扩展点用于支持扩展开发: 1. 组件扩展(WidgetExtension) 该类型的插件需要提供一个ui界面显示在开始菜单的插件页。 ```c++ // 头文件路径:src/extension/widget-extension.h class WidgetExtension : public QObject { Q_OBJECT public: explicit WidgetExtension(QObject *parent = nullptr); virtual int index() const; virtual MetadataMap metadata() const = 0; // 兼容老版本 virtual QVariantMap data(); virtual void receive(const QVariantMap &data); Q_SIGNALS: void dataUpdated(); }; ``` 2. 右键菜单扩展(ContextMenuExtension) 该类型的插件会向开始菜单的右键菜单中添加一些选择项,用于执行特定的操作。 ```c++ // 头文件路径:src/extension/context-menu-extension.h class ContextMenuExtension { public: virtual ~ContextMenuExtension() = default; /** * 控制菜单项显示在哪个位置 * 对于第三方项目们应该选择-1,或者大于1000的值 * @return -1:表示随机放在最后 */ virtual int index() const; /** * 根据data生成action,或者子菜单 * * @param data app信息 * @param parent action最终显示的QMenu * @param location 请求菜单的位置 * @param locationId 位置的描述信息,可选的值有:all,category,letterSort和favorite等插件的id * @return */ virtual QList actions(const DataEntity &data, QMenu *parent, const MenuInfo::Location &location, const QString &locationId) = 0; }; ``` * 插件开发实践 推荐使用cmake作为构建工具 > 建议参考此项目:https://gitee.com/openkylin/ukui-menu-extensions.git 开始菜单提供cmake文件和pc(pkgconfig)文件用于导入开发文件。 ```cmake # 下面是基于cmake的大致步骤 find_package(ukui-menu REQUIRED) target_link_libraries(${PROJECT_NAME} PRIVATE ukui-menu ) # 下面是基于pkgconfig的大致步骤 find_package(PkgConfig REQUIRED) pkg_check_modules(ukui-menu REQUIRED ukui-menu) include_directories(ukui-menu_INCLUDE_DIRS) link_directories(ukui-menu_LIBRARY_DIRS) link_directories(ukui-menu_LIBRARIES) target_link_libraries(${PROJECT_NAME} PRIVATE ukui-menu_LIBRARIES) ``` * 插件安装路径 ```cmake install(TARGETS ${EXTENSION_NAME} LIBRARY DESTINATION "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/ukui-menu/extensions") ``` ukui-menu/man/0000775000175000017500000000000015160463365012203 5ustar fengfengukui-menu/man/ukui-menu.10000664000175000017500000000074015160463365014205 0ustar fengfeng.\" Hey, EMACS: -*- nroff -*- .TH UKUI-MENU 1 "20 SEP 2023" .\" Please adjust this date whenever revising the manpage. .SH NAME ukui-menu: \- launch ukui-menu program .SH SYNOPSIS .B ukui-menu .SH DESCRIPTION .B ukui-menu It is used to launch program of launch ukui-menu. .PP .SH SEE ALSO .BR launch ukui-menu (1), .br .SH AUTHOR launch ukui-menu was written by zhangpengfei . .PP This manual page was written by zhangpengfei . ukui-menu/CMakeLists.txt0000664000175000017500000002775515160463365014210 0ustar fengfengcmake_minimum_required(VERSION 3.16) project(ukui-menu LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) # see https://cmake.org/cmake/help/v3.16/manual/cmake-qt.7.html set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) # 查找qt组件 find_package(QT NAMES Qt6 Qt5 COMPONENTS Core Gui Quick Widgets LinguistTools DBus REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Quick Widgets LinguistTools DBus REQUIRED) if (QT_VERSION_MAJOR EQUAL 5) find_package(Qt5 COMPONENTS X11Extras REQUIRED) find_package(Qt5Xdg REQUIRED) elseif(QT_VERSION_MAJOR EQUAL 6) find_package(Qt6 COMPONENTS GuiPrivate REQUIRED) find_package(Qt6Xdg REQUIRED) endif() find_package(ukui-quick COMPONENTS platform REQUIRED) # 查找其他组件 # see: https://cmake.org/cmake/help/v3.16/module/FindPkgConfig.html # see: https://cmake.org/cmake/help/v3.16/command/list.html find_package(PkgConfig REQUIRED) set(UKUI_MENU_EXTERNAL_LIBS "") # glib-2.0 gio-unix-2.0 gsettings-qt x11 kysdk-waylandhelper if (QT_VERSION_MAJOR EQUAL "5") set(UKUI_MENU_PC_PKGS gsettings-qt ukui-search kysdk-datacollect kysdk-package gio-2.0) elseif (QT_VERSION_MAJOR EQUAL "6") set(UKUI_MENU_PC_PKGS gsettings-qt6 ukui-search kysdk-datacollect kysdk-package gio-2.0) endif() foreach(external_lib IN ITEMS ${UKUI_MENU_PC_PKGS}) pkg_check_modules(${external_lib} REQUIRED ${external_lib}) if(${${external_lib}_FOUND}) include_directories(${${external_lib}_INCLUDE_DIRS}) link_directories(${${external_lib}_LIBRARY_DIRS}) list(APPEND UKUI_MENU_EXTERNAL_LIBS ${${external_lib}_LIBRARIES}) endif() endforeach() message(STATUS "External libraries found: ${UKUI_MENU_EXTERNAL_LIBS}") # include single-application add_subdirectory(3rd-parties/qtsingleapplication) # static lib of single-application. set(SingleApplication "qtsingleapplication") # include文件夹 include_directories(src) #include_directories(src/model) include_directories(src/appdata) include_directories(src/libappdata) include_directories(src/settings) include_directories(src/uiconfig) include_directories(src/windows) include_directories(src/extension) include_directories(src/utils) include_directories(3rd-parties/qtsingleapplication/src) # 用于Qt Creator识别自定义qml模块的导入路径 list(APPEND QML_MODULE_DIRS "${PROJECT_SOURCE_DIR}/qml") set(QML_IMPORT_PATH "${QML_MODULE_DIRS}" CACHE STRING "Qt Creator extra qml import paths." FORCE) #message(STATUS "QML_IMPORT_PATH: ${QML_IMPORT_PATH}") # 基础设置 set(UKUI_MENU_DATA_DIR "/usr/share/ukui-menu") set(UKUI_MENU_SO_DIR "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/ukui-menu") set(UKUI_MENU_TRANSLATION_DIR "${UKUI_MENU_DATA_DIR}/translations") set(UKUI_MENU_GLOBAL_CONFIG_FILE "${UKUI_MENU_DATA_DIR}/ukui-menu-global-config.conf") set(UKUI_MENU_EXTENSION_DIR "${UKUI_MENU_SO_DIR}/extensions") set(UKUI_MENU_CONTEXT_MENU_DIR "${UKUI_MENU_SO_DIR}/context-menu") set(UKUI_MENU_LIBRARY_VERSION 1.0.0) set(UKUI_MENU_LIBRARY_API_VERSION 1) #set(UKUI_MENU_LIBRARY_NAME "ukui-menu${UKUI_MENU_LIBRARY_API_VERSION}") set(UKUI_MENU_LIBRARY_NAME "ukui-menu") set(UKUI_MENU_LIBRARY_TARGET "libukui-menu") set(PC_INSTALL_DIR "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/pkgconfig") set(CMAKE_CONFIG_INSTALL_DIR "/usr/share/cmake/${UKUI_MENU_LIBRARY_NAME}") # 宏定义 add_compile_definitions(UKUI_MENU_TRANSLATION_DIR="${UKUI_MENU_TRANSLATION_DIR}" UKUI_MENU_DATA_DIR="${UKUI_MENU_DATA_DIR}" UKUI_MENU_CONTEXT_MENU_DIR="${UKUI_MENU_CONTEXT_MENU_DIR}" UKUI_MENU_EXTENSION_DIR="${UKUI_MENU_EXTENSION_DIR}" UKUI_MENU_GLOBAL_CONFIG_FILE="${UKUI_MENU_GLOBAL_CONFIG_FILE}" ) # ukui-menu的源码 set(SOURCE_FILES src/main.cpp src/commons.h src/commons.cpp src/menu-dbus-service.cpp src/menu-dbus-service.h src/ukui-menu-application.cpp src/ukui-menu-application.h src/windows/menu-main-window.cpp src/windows/menu-main-window.h src/settings/settings.cpp src/settings/settings.h src/settings/user-config.cpp src/settings/user-config.h src/utils/power-button.cpp src/utils/power-button.h src/utils/user-info-item.cpp src/utils/user-info-item.h src/utils/app-manager.cpp src/utils/app-manager.h src/utils/event-track.cpp src/utils/event-track.h src/utils/sidebar-button-utils.cpp src/utils/sidebar-button-utils.h src/utils/security-function-control.cpp src/utils/security-function-control.h src/extension/menu-extension-plugin.cpp src/extension/menu-extension-plugin.h src/extension/menu-extension-loader.cpp src/extension/menu-extension-loader.h src/extension/widget-extension.cpp src/extension/widget-extension.h src/extension/context-menu-extension.cpp src/extension/context-menu-extension.h src/extension/context-menu-manager.cpp src/extension/context-menu-manager.h src/extension/widget-extension-model.cpp src/extension/widget-extension-model.h src/extension/widget-model.cpp src/extension/widget-model.h src/extension/menu/app-menu-plugin.cpp src/extension/menu/app-menu-plugin.h src/extension/favorite/folder-model.cpp src/extension/favorite/folder-model.h src/extension/favorite/favorite-widget.cpp src/extension/favorite/favorite-widget.h src/extension/favorite/favorites-config.cpp src/extension/favorite/favorites-config.h src/extension/favorite/app-favorite-model.cpp src/extension/favorite/app-favorite-model.h src/extension/favorite/favorite-context-menu.cpp src/extension/favorite/favorite-context-menu.h src/extension/favorite/favorite-folder-helper.cpp src/extension/favorite/favorite-folder-helper.h src/extension/favorite/favorite-extension-plugin.cpp src/extension/favorite/favorite-extension-plugin.h src/extension/favorite/favorite-filter-model.cpp src/extension/favorite/favorite-filter-model.h src/libappdata/basic-app-model.cpp src/libappdata/basic-app-model.h src/libappdata/app-database-interface.cpp src/libappdata/app-database-interface.h src/libappdata/app-category-model.cpp src/libappdata/app-category-model.h src/libappdata/combined-list-model.cpp src/libappdata/combined-list-model.h src/libappdata/recently-installed-model.cpp src/libappdata/recently-installed-model.h src/libappdata/app-page-backend.cpp src/libappdata/app-page-backend.h src/libappdata/app-list-model.cpp src/libappdata/app-list-model.h src/libappdata/app-list-plugin.cpp src/libappdata/app-list-plugin.h src/libappdata/app-search-plugin.cpp src/libappdata/app-search-plugin.h src/libappdata/app-category-plugin.cpp src/libappdata/app-category-plugin.h src/libappdata/app-group-model.cpp src/libappdata/app-group-model.h src/libappdata/basic-app-filter-model.cpp src/libappdata/basic-app-filter-model.h ) if(COMMAND qt_add_dbus_adaptor) qt_add_dbus_adaptor(SOURCE_FILES data/org.ukui.menu.xml menu-dbus-service.h UkuiMenu::MenuDbusService) else() qt5_add_dbus_adaptor(SOURCE_FILES data/org.ukui.menu.xml menu-dbus-service.h UkuiMenu::MenuDbusService) endif() # library sources set(LIBRARY_SOURCES src/data-entity.cpp src/extension/menu-extension-plugin.cpp src/extension/widget-extension.cpp src/extension/context-menu-extension.cpp ) set(LIBRARY_HEADERS_DIR "/usr/include/${UKUI_MENU_LIBRARY_NAME}") set(LIBRARY_HEADERS src/data-entity.h src/extension/menu-extension-plugin.h src/extension/widget-extension.h src/extension/context-menu-extension.h ) # qrc文件 set(QRC_FILES qml/qml.qrc res/res.qrc) # desktop file set(DESKTOP_FILE data/ukui-menu.desktop) set(GSETTING_FILE data/org.ukui.menu.settings.gschema.xml) set(GLOBAL_CONFIG_FILE data/ukui-menu-global-config.conf) set(DBUS_SERVICE_FILE data/org.ukui.menu.service) # data files #set(DATA_FILES data/xxx) # 翻译文件 file(GLOB TS_FILES "${PROJECT_SOURCE_DIR}/translations/*.ts") # 更新翻译并创建.qm文件 if(COMMAND qt_create_translation) qt_create_translation(QM_FILES ${PROJECT_SOURCE_DIR} ${TS_FILES}) else() qt5_create_translation(QM_FILES ${PROJECT_SOURCE_DIR} ${TS_FILES}) endif() # see https://cmake.org/cmake/help/v3.16/command/add_custom_command.html # add_custom_target(GEN_TS ALL DEPENDS ${TS_FILES}) # add_custom_target(generate_qm ALL DEPENDS ${QM_FILES}) add_library(${UKUI_MENU_LIBRARY_TARGET} SHARED ${LIBRARY_SOURCES}) set_target_properties(${UKUI_MENU_LIBRARY_TARGET} PROPERTIES VERSION ${UKUI_MENU_LIBRARY_VERSION} SOVERSION ${UKUI_MENU_LIBRARY_API_VERSION} OUTPUT_NAME "ukui-menu" ) target_link_libraries(${UKUI_MENU_LIBRARY_TARGET} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets) add_executable( ${PROJECT_NAME} ${QRC_FILES} ${QM_FILES} ${SOURCE_FILES} ) target_compile_definitions(${PROJECT_NAME} PRIVATE $<$:UKUI_MENU_LOG_FILE_DISABLE>) target_compile_definitions(${PROJECT_NAME} PRIVATE $<$:QT_QML_DEBUG>) target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Quick Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::DBus Qt${QT_VERSION_MAJOR}Xdg ${SingleApplication} ${UKUI_MENU_EXTERNAL_LIBS} ${UKUI_MENU_LIBRARY_TARGET} ukui-quick::platform ) if (QT_VERSION_MAJOR EQUAL 5) target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::X11Extras ) elseif(QT_VERSION_MAJOR EQUAL 6) target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::GuiPrivate ) endif() # 安装ukui-menu install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION "/usr/bin") install(TARGETS ${UKUI_MENU_LIBRARY_TARGET} EXPORT ${UKUI_MENU_LIBRARY_NAME} PUBLIC_HEADER DESTINATION ${LIBRARY_HEADERS_DIR} LIBRARY DESTINATION "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}" ) # 安装翻译文件 install(FILES ${QM_FILES} DESTINATION "${UKUI_MENU_TRANSLATION_DIR}") # 安装desktop文件 install(FILES ${DESKTOP_FILE} DESTINATION "/etc/xdg/autostart") install(FILES ${GSETTING_FILE} DESTINATION "/usr/share/glib-2.0/schemas") install(FILES ${GLOBAL_CONFIG_FILE} DESTINATION "${UKUI_MENU_DATA_DIR}") install(FILES ${DBUS_SERVICE_FILE} DESTINATION "/usr/share/dbus-1/services/") install(FILES ${LIBRARY_HEADERS} DESTINATION "${LIBRARY_HEADERS_DIR}") install(DIRECTORY "qml/org" DESTINATION "/usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}/qt${QT_VERSION_MAJOR}/qml") ## 生成开发配置文件 include(CMakePackageConfigHelpers) target_include_directories(${UKUI_MENU_LIBRARY_NAME} PUBLIC $) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/${UKUI_MENU_LIBRARY_NAME}-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake/${UKUI_MENU_LIBRARY_NAME}-config.cmake" INSTALL_DESTINATION ${CMAKE_CONFIG_INSTALL_DIR} ) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/cmake/${UKUI_MENU_LIBRARY_NAME}-config-version.cmake VERSION ${UKUI_MENU_LIBRARY_VERSION} COMPATIBILITY SameMajorVersion ) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/${UKUI_MENU_LIBRARY_NAME}.pc.in" "${CMAKE_CURRENT_BINARY_DIR}/cmake/${UKUI_MENU_LIBRARY_NAME}.pc" INSTALL_DESTINATION ${PC_INSTALL_DIR} ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/${UKUI_MENU_LIBRARY_NAME}.pc DESTINATION ${PC_INSTALL_DIR}) install(EXPORT ${UKUI_MENU_LIBRARY_NAME} FILE ${UKUI_MENU_LIBRARY_NAME}-targets.cmake DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/${UKUI_MENU_LIBRARY_NAME}-config.cmake ${CMAKE_CURRENT_BINARY_DIR}/cmake/${UKUI_MENU_LIBRARY_NAME}-config-version.cmake DESTINATION ${CMAKE_CONFIG_INSTALL_DIR}) ukui-menu/res/0000775000175000017500000000000015160463353012216 5ustar fengfengukui-menu/res/res.qrc0000664000175000017500000000032215160463353013513 0ustar fengfeng icon/application-x-desktop.png icon/pad_mainpower.svg icon/default-community-image.png ukui-menu/res/icon/0000775000175000017500000000000015160463353013146 5ustar fengfengukui-menu/res/icon/default-community-image.png0000664000175000017500000022517415160463353020415 0ustar fengfengPNG  IHDR\rfIDATx^weU?>gF1PP:0:(J,9hnB+眫[7]]{sV댨u׳ϽuߵL^X0}exr{fu]{U}y՛{@E 8ߖs;sXWtv90:=N2m|ϳD/uyZo>=H9RE`ol1vv>ok:SQ:5;~k e#|d(vBsf\f@ş*~e&"tʕvg 7t1yM^[4F_a_ߕ=,!z!|AX6?88N@zU8T߯e 8汪?Gjgjy4?TE8J)A>Į@݊ g7SmlŸ5y_-Q:=Ksz B.n['m<7XuY; *x-GK y Kbqˉ z*coR'޿k8`X3$F2(tއ @@8#??8yM^6WCْ3:E_bigC_ˊ2!wU,~n@G^iyh8mfBro! n@01:݁[%W qd@0@R4 r(tD0 ȎS~`l5yQ,}sXZ4V_V`Yh' ﱸC.ϚzvmHX.` wZ$(t0Z\tA{@@\@#P{:3T;&o4 @@A@lqQp5y/"߻tbUv@MۈJ0o[c@ 1"Hj`@`^D`¾_ßy<lQ  /#G0F~CA{   Xc XZ‚ڧR@`/v=@ΗvtOĿa:yM^mO;R㴞uގ5U!m ?uN`@0 gu ȋI5ӥ My'@b a{ )}σ#` @xOP׈ @ uFzz7Cn`@!p>t͗O^W}6Js!|~_X0T@ , V'f}4{gIޯ/*|8W;s<mЉ]/#G\@{9a ` 邦V0P4L lqnpva+j*K7TQnohs"oJ2!q'izn!!P .V |M>R.;4eI0O˺rg8 )c =4< \`saP} Q N *AC-3ߏ/3Խ7S*?ӞQM"|a@AP&06 @U3ECH>cCE]̢o2($:'d]*z twO O&.Kwx ~W X6NA Z* _{ u&1Vu$rX.`+ ‡3H{ 'F7Ny-00@{   = sך&pZPN5hѤ| :ٍ7D*_Zi~g~xMcg˳? ؝ 7CC+Z#jsྙliBB>tpx %7bNJb-|6,_N4!|T f 8@R*:HN`0F 8X/ ?Lwz|f!Wm |q58~k=? JsNԥYwߊ8«ij|& ގ3x7@§XJT`d7 кD ͗&?kO^ _0G&rpS@9[ܬ]=ꭾ A!bBoK~]. @S-: 3^2H1-1b!p/~=_ V@ !p@PD*xW@j A]X О<6Y ((5^M^o譜ANcJ@)f2@߇@g@!:,<QQXs,a72K_nT!p9-~rD>^k@ 8HA!,v w-,~sy7*`pFt JHh@;ƌKWrH4yM^okwYI"] HX X8V_ޏ1( Nz{a>|#%8N^q}EX} ]w,v *V=/Y h``{@!P8" ;D;F&xGay}-"Ğp%/hjM^okW͖_ו܀ r 4 }$~9ey6FH 8E(,G ә$M4^ k=E\+ [_+f~2hG T8޷{2zk @)MR q#@ 7 @8@9}N PTR~ 焿wןM6\B=ppK-#ij)h_ůFҁ$D "\bsX>dcS΋7s\q]X@6}K*XϷg[!p|yx ܑ!pBGHr = pZw! p[ .@¯hm>-ɡph71j6 @t;[p˄FdpX#\ƒs<.kNBʗ<FΉǬh4·?@`.8x\КuJ0ч \'{ Qh }mCƴkgAЊ +p W~q]hAP@3mM }{fys[?taWcTD, Po 1`  x'4qZGq#Eɝ7k0әwQI 9 u ',-N:#[{^ {wc,N Q_{x F+P`C U@jaZ Dt@@ 7Pǟ|T a#gt1"-uWa Au8'v )D/)SK> Xș4I.1z,ȕ??]z}ͼi;JS>@T (х, r22 ` (0J $piB :Hd S.9%=سv ?*܇. (*EGA`Х X/"@,*E\zb57@`p8i@  LBDc#BA ` HQA Dv qjF,)@sSRe(HK+x!zQ";@%T:@ \!B?ľGqZ`!k@0-^R B t694":92TH'`]Ƅ0A Et0P( 2Wh G`jAO t@ P60va0C!\ LN^kO>Fv/|+~`_@%G x|0oC!H%D@D@Bs&-"%ď@~#v']6k{8F@ H܈'#$|A^[ >'XzuX! )/e _93yb{U:r@F.FAƤ*>2%'~=BBrzΚ= 3Ƃz@0"^d Q%y~6tT'@-|m:9yLb (45%Eu ݅|/""|JADC)n 'pY)&9[ ش t+Тu G)?Fk#3?!ſߣbv uXH ._i/CuZ`TlcH/ S||AA`a3  R,L8M#¦ -b@ 6]@ >!j*|=} 86ڶQ qcB >t V~|Mb\6\.:_r Cs9:cMٚ{@V6 Y#qAAp7Rk"*K|!cG @0Ӂ AYz 5vi@'\3Q.Y$n .u&@= Og q\۲t& @+}:{_&Ps.A'|cEq9^A8e?ďSoϹ{7̍9:sKrX\D;Rrv yUXgmDi>k Le9Rx;G_! n7.]w( HPS@q }t>:ssl!pH׎pϷ#6bI.-SyŸ((( PӁL#Pt PG`!7@"-0EBuB )uL:%~I'zۿ̨)A hda`S ?x}*Wu>.*Nv\ 2B +M4}0Z'v}x8gW]je#P X8N `?Ȟ { A n@r_OTX1PDˇlEB Z2t E.!+"! _ LpmY9_#n JoTؗPN:ldE:"u.9huC0z= .Mbc(C~8p.Hsh+H*jW7\9'݂`_5 . AX@"r!ni,#zH@jH |m@W d N,F(0BՁbgt,\@( lM  (@AB:q|_ ߅n+C8EB.\sN_M.3׍( bQ1l%F6 ` A_ MT7nhK~@\wR.+vG! N5͹~\gO,"̢ t!_V0aAP @!@!6qwpyt3BvEA@<}sNH #p>:n`Y-'@\"PY^?5.5Z!BįB)A 0 (:J t6`EcHdRJB Ԧ!Y&S|(ƞMD晿qS _G8gpdZh>t5haP D@x (Pٿۉ\Ͽ/by`o 382yZ';:k0 DKis.`% PXGf4]LtSыsLųR:PWra:`e.[~P! P\B.`!){ JOWw,VBCn5X5&S"G3k,Oq6H0^.j-4Z Q }D!:stn:S,s6]E+]qH)Jt`:F&;TYG5M"7{27{|OxDSPD.%U,j@0* ixX&|F~"ZEP X @0BN@]u  {  :X&~S =ecO޻k( Z (u1Wod'7Igrzp,=.o7;Nv]@p<>H߂@߳NDj@#(; ДKa+AuĦ8C@w CG:7>5 5  Yd /|A@6` S` L l];`Xiw^Kz R C ȌwV]7-a]~Sba3/g"@*PwU7u?O,1tA .߇.@@ 7 r@( T `uA@ &,/ Rlh#_ł·aA"-He ӂT  G+L#D-g+EZGk{{yvUxpIri[Pc:G Ѓ-`Ӡ{z:l-V :%8J3w=ՙZ$Ku As״]Pspzp@sP#@FQ] o\q P`CTJ*'s0tD5?}ћ./ڒϾ*Q `lt.uvL9 t1֤e#tګ#tœ!s<; {ѳ/_cs?|c0h0x::bQN[>JSVhƦ,WO!`A $ `[14Jm Z2}۰"s"C @ ҧ׳#h^;D_), D u$@. q DB|? hjhc@L@@5c+q(z|bM6z3o po L"g7`Fu YdUk4u0l}^#п>K|>>S; Oe|9@``~ `0DxyX xm~`~0%q%i1c˯g;Ѓξ{@{G h]pt3/<ω,b Į$\I`ȯ#A,؜$b70vuuV!"(r6`ACGtn(J-Cbi濸#Ǚl(pǨh@TӁ@>}yNzqP}n}]+W=7@Rx8;JHO=NX|i^Ym~7PKk{lL@.vn@O’S @`.g4/ńm:H |\ TCLuzH%!P Du(%qzP|q8E93oL47ПŶ u@0NWY~;`7}NpO}."Ǘx ߁4 rCǿ4~( 2 XY? ,硻 @>.G]U!h2m>d!Ӟ[ܟTb$TaJ4$gH^RMqj U8 uVA!ҀQ:AM 􌁲t8Bށցl7]87џ)CGltC.ӭM4@_gav;$DwD`P|;@1 ~u`Ҁ?6. \ N[=J4·3:BXx=tMQ g#SUAzbP=LK[Ds:~e C }t~*oS`>pww1 @s҂( `P@(N WeP@@(LA]H=}8E Л* D־?g zQjZbEE^QJ` ` sta:zmɛ۝9 |>q ::@\T . u /IG+F|bևՏf}?;枧[99담k_   ]u c "Uc8Y8os@,P7P)]A3"ڑvi@|t n0 a ([&sMC:˄0 DIwGL&?,9V!.`v/nk8<:V:679|q;:w(Xww%b @@`:: wt5 QH1L@ \ei/Wf=Ѭn>?B*~,ih`3x7 |H 0֥95Ez!G7cY0/ xJ;x5(@ػ;OTP !@)vbP@>kN - *#)zC O `.QO,gPmr9, :{Hy񏖃@G|]t. tUutt5p7K v|.|Np<@X4!is.u,ub:įi/;p r.ߞNv!z,!d[3Ӊ.H%zmnkG\*eEz"Dn?~=4]l'ztRP 'R\e蒠z*6 ĺH4*,ǔQQ7M}#3 u^{t ,jWWTWopGpytutĵ|qm-G GG5y]5}q}5}75w4n/F_{@_ +\@Y?\@;A(NZ\bڦ,:>doC@O0 K{XH g(%z ş_Οm4,d'q!"j :@nd7p;: :B؉~Y*P[X!*-&V c. `yPCEqd[> 4 u[֦_W؞_t'}|ne{%<pe5Á{9= =tǡW8t5{=( R#[k_nP@h©AsO9` eurh  afNdQ̳wc8˭9s㴬ol) /9}i}.ؓs7 ӔU4eyM5T ts\S4׹hAPmá@9s/$VySК@D(EA@6,(H>T {8qL>A-@[t }Z|*C%e]?}o-x6:wKv"vk_d'_;'@Jî`!p@@P pW}z= [ߞW*:@ 4@ؕu'tiVek贵ctGf}+~\w`kKsXAUXs.-WP G#Yh., b"/[Rts@`SrJp/;Qg!BWM"G"~J@9r ztAVcIDF!|<҅iT,dzkGYMt- 6." 7l?6uC@Bx2vA@]@ .s:ps :==Ȏ|@T:. @8\dջrd#Ǣp?זEݮqBĦ,3?U:\jHڈ=+_7 u~?n`~V    haP]"@ / ud5)O߭>tRoXLfp+{ CmNF p]s,x+>x?h'Fu8@;v̉:D ы^9&`!`@ xAGZbveCO#os$R?鵞Y3i\}t̝Gw9ŭtzr0ܞeg0Π(ŵ_ W2a[P{h C` l~[fctQhyN?@?|~o 8Jp':;-`l F Bs 1pچ,,RYZ~?f@yq_d8?Z gklݹvLOotX 8%] J (K4~L|D@οVh:=܃@QDM@5E`i(󡻶>Z ՘ӱ Аt" t}m{ʚ4<->u8=c` tHѥK{gT/ijH"OmHit@iBWi wer2ۜf!~ Fo9x} 9'H`VDn@Dú*(\b`G.YC'Ix q ?@ToT╁$(Tے' iȵ N.iw@pt9$R!F<ڜ,]NJ_VeI* U _ {}/@ݮ)Hw~DW "mqW`,h5 P:iL78fclqJ8oo="G׻zjX2D҂[b3D5+>W`UD+(s}Gۊs!Pie@?eYp4!Z@XqTH6*/vtB+O h zA:6 aW#')@YTl/J_~ }C:=%y; ˁJ/Հ!] +utstƦvsfÍqA@(X>܄ )|;{ފ r {`{].xMC'JuD:?:2(T>4%Hr'C@RA ӈnUPhJ_F)+'&~A`~-j#js'u\<Fy{8ko{q@AjVFl瘟Cx.: , C=&?}S-}FZe_)A6%wn r┠9)`Ơ:@z@po958k(So2_-<]>ߚ Eu㒷[1Y]gX ߏA6^V2N Pڦa:F H8A@G{Aw 6sȲ j4`0pMB8O0h2Ԏ 0sA stZ9e_8GR@BXQ|O(Jž5=Ŀ||H~þ_qqF`sBP`/@ $O O4 B@O4L>q ]'E̟3̟_/{:#0PeއB7~Lt{VL7ў$R HT T@@\\QPj\* hUц ʄouxL֋HK`"M'w h!UUY_ALQ*TXy*В}y7>r^.8<>ycu[;}xw CrH N ;s-Af9PD?@/@[ 0u_hӀ>K?O}Yߋ!|7Y]9zeV.s( H>kNA,O3p2 Z`@Y*G@@@"sXz@A1m rD.PEPmז uI>ʞ䓃8X]pjgz<fZIzZ+ehђv[9yr l }::&?!m8%(:'ۃ+4+Y~`_P-.~wm99OgnalŘ ZgFk> f~q'XX隄|@< PfUJ@?ƨICw:l } -K ᇎ O! .=k`m$+68`]=-ùC\DW$FQԘ8H~DzD˻ȦcY=ՌPT-Abh6ѐkh!==# >'Ч8$? D\?@]@t>n+)Aj9@wq!p݁*BO0l󘣟2@?C2Q9hb2`Ny"[O72aO^BP)!rIc\(( (SM8UXQ^)2R56CѯU{ O%! UC4zNy \ X6M{>}6:*VOE7܈mSt=IBiur@Ha|݁$] h l?t|!3?\.~xt|gd 8߉׺0RQb i<V!g&~+fՐ'[ Tt @ ⷢ'TH|o UX(_8?^*FpPkEvB+ u\K^;笢r&@A!𱋶C!!; ٜ}y9A~ܝ,@\@0W wj辀x90,@Y u  (n/~8N~}7Cgpv)}e`*`JjrJP0@pӂ?{[;?6B-o-Хlua@pgxr>309ȴ: 1aiJY@`W1F?x!0N|_ Y?)|Dbg[!~?"j"smas#Q'f_8@Bxx ]BH~E .=_k{s*DA(C= {c\@` _1\ѿ^>p X`wJW*~||spPhO rl-nʊk901:ffo u   T(c~@  vcRXr`Y-.Pı/x4ݑ}/K?%OkRA-ὐ#ч6}}Fw&"]A xoAP8@>Y* +ROk T3^' z'?P e,B'bDG҇N{ "| @`gN ? D psZ0J  "]l8z^N iJؓ S$` 8.U\4iІ@p`Ie(>CvZ4H7Veݏs~fևojDV^!` 0.P'b_z["3f2mq ]@zmqкJ˄:KW,sk=@VC4z @RnG} tO;Yy ش@gE}y:*:_,S. YM9:8=4UoZ;BT5K5ct v*/.9ڂ=)Asu54m+ u:q.s9;L|7ʹtk&~"~wfY@Ӆlog4`zmi'XZ G _݀" T@\, rPV!pR}靡4M[,޸[XƳm:s[BZJ>vf9+qXv;(# \^>/_g<0$zj{`Hذ6fE{c#簧Hc*,ڂ# (_ A@Ӏ-إ ,[. #p S90`09~rn$&~&U 8(HB* qB@DY;Xǎ?ǩ>@Tr D*_} unLrU . x' +R ~Α?,)#|ukLѷŋ'D|K/q|x r"`pDžGus\>0D \ u] Ѯ/I׭ֽ{и-XӀS:DžGm n us>@`Ж`i@`/G8<cg^vd߉Hn<pFoGKM'~7~9X|=[Q-~tXA@@@`3Bjur |} {\&bYou芅 )/{~>'('8A-*? ?9@`kp\8qӀ(AxlافQCOg>fзA( l!P@[@h/>B yLD A] u@Qpa8)Ke= ^:i0] _ 9QDٔY=shnn6m'fz#>&x1(63ԀFYxRq!Pi !Nb*\@\ PsUOAv3!|}񙡞SZ-IQVw'~_\\'3n5?w@ch`L?@@ltrAuӥKh;ԝ <*Iu0 \@1aZp}-: `V:@j{ nq@ 'K6ej'|oRYb Gau`8b6Uދ ~f_ߏ $Z. BPNAާ8ezǏwgUv}Y+ic@xH]ˁ!pm==`ӀѾ{OҫMY>PnþPJۃˁTW 4v%`@}ڂ8T9$/ ?/n:y&ڃu9zÝ'C`p`LCPr(}g\*c`: @%- ZX.@! T t.@,,fơA"#4b%pLgO;~4 7gr5=G8q6N]H9c))OYJp "/Mt 88 t`B||8\@Xx69І ,+gf<½zN;+0LTa~lCB4 `B@;x5@1~WOtq}N][`<1.5愿!~5>p=9z-/PŲm<^LLJ@?o_C8Sp@2 /DNN@W@Z\p j @  ؕ.}7лB}b?cPAr__>q=$2}Ӝ bϒx?2ÿY,؟SSWp=: `#.@ b\MA:@h_@yY N rۃ9_{ޔb$2  K1aѾ͎ wq[& .4M. ,)A6 A @P@&.MГջ]\g yz(@J-s;/HVMot%CH1}q$~ X@Xk0@9 dvM#4zPS b ;$dDTPox_0,>Gw?8L:mP8_)(c<8w=}i O0>8\ o2id5#TJ\.@_eFpQ!v4c~4o @{LXP) P@-HytE3]!)NN_OM&vxC88p(! b`}?@]D1a_ם8'nY fu04tcOj@/ m |10;PX H Py95 |CP(]8@8b+ʝI9Ux%xòBӅx9\zMcr0D-y lzӀE> [.t L * D0(t"~_ @ȑaV.@\@"K-P^lO]08N [zh! KuLz=9{I=p\ r:pE"m\ezN:epUM*@Phi@|>k Vh33[M<^ g*HO mi~c?/ >-Xm@-T\]WG(N0ǕW0;膽cp}ZЌkiK !k$X 8B->&D H8į.x8(P } hůC] xm[_!lM* T@^C|1.@Hw`;AHPe[uη|N8p 9Og(yR @{>[!6ȔU񾀠-X6! P-x h vˁ^]GӀzv kS6ѧcJU|/~x%@ 7ЃB5 ۃuc# `uwNC b]"i!t蹁~{p!!HӀJˁ,OZ,أgu1a?xgD8p~ ;F t ]!#y丐?Ӥ8~:ynzfp@vN 8US F(l0`߿ܟ$MAD̔( `¦+`A% P7`f}D]DGXr@]*^C@`3{zױ-߾m8}^g;b@>O\g6n x%9,d{݁f_@| `? t{\HApC uSpRjoa!no@/]>E ,Y* y\  Q[XYаpں r?L~3]rP(/SfInCt]qƵXYG7ew-e? N g P`oC@wb0;8*G@t{p;Н+vc%uy]CI\G]h0SAuvū)(QzXhxL[F= g4<`8dž4MA)s\x$ +lJ@6*jEJNS]')h4]*M|\cMډ_C 0b 1d ~;D@p nnܓr  e]/pA>6;r[Dn W!B7O=*\1uu(c]Bq0wtzw-ߺq3ۏgʺ?ݯX=x9q hRpH :){G{Uew\,-[]_w{~]eelCΠA̠(Ar9'199s"0;Z{?f9>X+NӁt %1 Y |%_58<`@p`B| CY?p o*l@sq[p<@'0ӁQ O AZ!AhTI5*0IRA_"k ٽ-|5* 601dW&sP:Ձ01O)@a* BN5ଆǘt@'hst|5ZUO~Ӽ.cROSS\q \2P'`QLCBN' C @m &aU*mـkpB\y) x+_Zdr`0 @U@6ݻRVhOa޿: L!HU@ ö``:.T;kU߹H-= .,nk+db0=; J P.pIAa> `A*f2tE ýlKa?|.E";*mDMA>r P !@^G@h4~d280(ۂ JK*Vr.6-Ͼ(gƵ\~[Po_ 4H U'`S<!! @?eWq@0`Am BeNWO5ok*k&ȵ \r`tkP*Hۂo<"@h5-~ۂAs^+~Ǽ"c>vpBKdZ!P`q )x6 }.QC_HNu쌚;Q!@o{Y^?,NȻmr0Z&/d $T)^hӁI:1>T Z· v+"US. `@ ÀXշEA;QvmΩ ;r*B~ٞz|_뇨, <4@t0ڿv6 L B|35a֠}n.  ~M^M(Nɵr"H4A@!`* 6o ~o&o|s$ǃ׆YCPDk ^<]"-T#|+] ]V`C`.Ȣ L0 sqR ssuu2z7^~E6ޚ<S&%#鼀ǎ')yZ4 !Bd( a9@ 4heT1X [^]7\7 T f@3CBT U /w``60m^0 A&7kG`: { +-A VRa@x_ǎ@7y kS%p4@`:8{B!#6.*6Dn Oo L Rp & B鱶RS9D!PR 9_!>K H AB`8ɏY|Op,*PncΟVn'C QIU"%АP,f( 0?ː=,g41UBCFZcͲJ13}W{N&8b|vP#il* 4j |@cS+C\* *$V\:?ބ8惿/u$P$8UWoSdҟ00@U | s]x-@d_,-A`*vv A04)JrB*wzp]Wх!@ÌѺpkÂrc[k䞥Cȷ3@Qsߍ2o!T.P*la@7 3!SHU߰{ɀ2糟TC=Yn 4$8,x 8 ]yy=TA(y =2@6D8A!+ \ 4:ڠF Pd &i[" Fr!@0HpU y(@dV,$;@7u``@yD`pmo @PX wkܖh:B+]0 6 0%_npy6!P 70T/ aG mPjƉ >܇L>Pr}7N˓ɗgSJ80N8pȠm52>lzH|<(Xx`4a_@!@@TFjHРJߖB鷽HA<aķg,/b؃-f@jMBrXp''bK!ߙw"Hwa gM0@ˁmq8tAYN;Vo.~~r٭ *r@ @ 0@(@;`>Dsǃ- @9P\e)Z?. k˜0 s d| uow|3 t '@_ H\_Al'^xF]~.U_?oB~ ~yr#mZA]Kgʡ$)=?o_á/a_þ7?kS6~8N7o׹6_ * [. DU d(U_>ȀwKKoX 4I @ 2!4!yeaI+Jq>V5'?4p Aץ0D g 4:~ Է%v>@Um>z"@>\K`(m z{ `gx?q€9ȇY;*ow}Z-vI yci]|FA ZI٥~~˫a+ a v0@4x1h"ʁm>!(a@-A&X B\[,N-›FL8-7S 7q[/͸/ @8^ !,=׸FJy`m‚;0qm@_օ'ڂp[p'B#[F;':/ ֊Qyl)lSu3,Q U,a=<F $`YyOK FE!P{,1pP%6\+L3ɕDm8NszBd* )@+g,8d KL0p)4t|5,ͯe3%À_a@m 0xlvq釮 cȍׄqK?Ih~h< @W Hz{70r`!(&t?g+ 7Oe"7BD꓁vg@0@o TM 1>/ V@ufNўfu~p6,y}xƞ@N̳zv}Y{^9PR* K0 b85-+u9ˎIEG{si+'pEʾڳR Cx.-+/an5 :->sT@f{NC@w B@Tan x䍋/WE!|a)]ׄ!0*KBׇm.@hCPׄ%r.@t:JY͔әs`. u YE-2R7'0-8 H '0qo05ȅķ% En]8ci^!@ʠM^G=d`c2@ iW l8 0dw-;.Vt_{J*g7*n)?BiɡsR|Ns0Bm.ˁi$' 8~BhN)|A`90<3 *,:0/TjZw\MKozE~~SM~ᚂ|@!\stH@['PU. D` 3D@K5a~ |"P! UdB- @D@ 25_n%fB*@`]_IDATF0 vMѭA~:0˚ĵa~<8 `MXtm[KCFrY5R='7k" *iD pH A}JץGӼCm%"q%lp .9/ ,'Mq9C 0@w4'B{P @5  (! :|s~HE˿W$xMoI (]4`[Jҏ-83 `ċClyD9U@w{pT̼/qF4X-PfmsKpbwkP:o^W#;؁y;pn7cA?@-8^ =n0* ~Q7nM~Q(@Bŗhp<֠5o~5nI HB @| D ZlF8tsP_@ y2hw*[0!1\ʰ`7:w"fh|ChN(A W LqU hKS%@`ǫ7g72*uF Ax,),#mGF* ~0((j@C 0.dӷ%<^m,* p Ț<73CVY8O0b࿜'0[`5((axxZHnW etD`ׇ?V&BnƿB:VB[`&K{ ǃ}SCpKWa?@? 1<#nV kN/apUN>fqrkJcÁ4ay Ь Ѳeg%eAy] l-҆?#U6F̻,^QwgXJ L?Φ?!D3&5<<LD`TT+^m_^󿬉KSy8ha@FA>m4,?1'l AY98 j&k-ARYW@@UBx@\ۂ=[t.67yiAFYA:0 ^w{B->`{ Z' vBwm`Z0Ȅ2=,toSAB0!*}I״HCY} X|XO:/>*/Ⱥ6WGXP|'B8@ s}p>BOpwgg_zKbyDV^wa@\[ݬ u+ը4!Oö  UePjO\-x pP2 +a [90'0yo` uvufrl@`nܱL2mnKP $\08`3$>ހxL>,/18f˙^P}67pZ5@mb$@ePU~ľy9v$2Bhl8P%݋_mgɺKi9_!@%]KlyԜ\!j) )By XR*vT. yW `RN_E/QP 'A *a4cKU+uC@vopP+ĝr`XWv“{IpxQhP xT@a5ȗ, B$plB O *Er-8,Fy $XP&™^?xq9=muy65W"mYe*B7l?i1p|oJ6V% *lCLdB " ? !xuya`E( W @}=w^!_e-2a)BTh@uV^g.sJ3 o54g|8?*ͯ V cx*j=Vt~O]73U/E< 4*`G]yׅ[370.r5aQ9Pk J.<\f 9 t`Pto`PmYʁiׄ@aS yNܵDYV!3  K x-A9Ga&kywxs~!ZEml)xK/{&}Q7pW?0H:6Y@<$ 7 2,'%99dזM (32U&'he-p:@`g%wg3mYAXdCg-8@j!Jowu xYr7TVꅡ\-T Y W kÂD`DX5 Q90 Հ䂐(a@}š~M6+qYrBXY\Pa \ׄiE^Dk\%+=p)~9IoNοJ_SYs>^l u/ ["w1bA2 ~v7>O$9'mX(L4tQ@Sf@8DCށJLl-e.{*p6v5ҩR HBH)yi.lC Bǯp%׿NjWv AT܍@|~-T yt?8 o HBk¢j#  wg@pkoyR@XwΆwE%~i<B Df qßkk:Z| }T/}Z/>#h9pEǚ /wNKO3!II!@[xlU^v66pxp 0 ,h p <|(\@ ztd'm[& O`,5qY Xo0W4gH  +B  㼀&ٽHg€#i8D`{5GB ;=wĄ '\`9Wʁ=@a<@<@  ,T@ b؞T_vpM r'‚;g][]\*.+ARMw`/;*9w55v*6V6X2p 2 >]/v~oCYH0Pś~1sp:wt#4ss&D?Ԥ` ) 8CuY<Rry ><'G=0j 23 . $X yXB &*~v ՗\7.VQWA25E;l6 TKKr`)ȫ(J5aaӁ-8sMXf[pxmL#0\\@U@{eў`<ZX9s <`~!@P"--Vԅl.xXQ%}WJ_j ß%&hTA oA087UV)0`+~g VZY-wjkeAvcoLfrQ8g11y|U@R<[`fo@.3"*WEG5 `&y(B!>TmJJ8] H)Bx}v3 8N0!w Oĵ'Wݢ]uB_*ÿ[#-*S"S}. i@BdXHA8~@-:&Sy^3hZU4 x€<4 r+;uANNӛr`fG`r&h:05( ,ƕ@? @&&W|\-*] k~k?2{a9+0;*.L3,ZtS(9!"nc]P(JTEx^bt_b'?x w_RYmNڢ"yh~:~t|y xyZ.D<"Auu>PrpGӒ3CytU3vi1nipr"9?!CLJx(~ O뙻PX^ e[ zF|P'/ drȜw`oArTU 64Uog@XDp0P Y@%0TҦO|SA8C l&C(,!} H0@Ct g k^hTЬ+W 4g . be:ĝ6D@k|W U@rO׆e}E@5a~W@:hj d`zO`Ӂ~`s!&)x#}V@@y&~fa Bbjsf]pXXB{S . "X :蹜V,VJbV'k0V绯`,Oz*Ljodڑ|9p@@ I&"=^r鿫X @yplmeg,xjX 8O4o~9M8N20@`oX%10䝟VڂkV_B1H \v;pL B)*KAPăA`PrMXh ؂86ƃö <%Ki%t` ̼6/ t0x J'\q2=/.s{|x3᤼\|kz0,`8BKJT\ﹼx~%Uepb<샓*߆J+&ZTɠ5jx}BU9K2z)~ |X+6x {@]~P|ix=2  €}8PP L?*;h oSxK+D(86fUI@B.f(iv\3kZL.%HG)rC~t`yt90l n 0 }{px}x: J[y֠D &&=a)8y As+MP-Bcq4&{zCY@hѓ e% *Z%fJ[GX#7"`U93P#ޤ;jA!ZVUލ+61opZ}6H2y3S7TSuo",x8HT\($K`fIϪj8~k('wB ˁ0>GB[`qsB~^ j 3[G0hU&/P w)#po~BV&Ð hщ \0p׍ÿiPX.@- ^8"[#Csr(H[ g:?}e @SWPPooU I]"P@<Vt>8 @(5"W'`rn~#h z&9MAK5`xpF~*|RYǃa ۂ/=8ˁ>8(mY€Fϝǟ75p Xh)^TK@Wf/Xu 6Ժḿ E u?}h<'`=߃*x`~MP-k5I,% VHP2zy[ 'A`j` H;0jr*;dh`ć*P6*@A`wU~P xϦNO@۟9 gA[X<wpa*@ҙ p<8/yBwVlǃ _І[=<0hADp槡 zi@ypJ@T|<Ut|J||/{=)< 1!6QCC;v )opPD DR{>&J+5_kmt_S gUk7(5[8_A>8?@AWb5/m0 ^ ,*lӖaB. HA&?ہ՜:~k!/@ˁ)U+TnB px%  3@0 ˁ;XRحA~KÀ!hQ"vsfgWD֓ p- K Z"SAO( "0Xf0pіP YHDC 0W 5cϾTwICം| F@X@LM8(c1(p0#KExŘV\8˃/k t>b8 YՀB<"pgh]|@J[ {{@txhk[tma"W~8"[BN`P %€Qn4`*+0 7UCq(U}&uA@?ҡsz,ozτ@w=pQC@ "<܃rÁx`J 8@8/270\ }:zۆ9*<:Pl-×JՅҍXB̗~[J%uoo ^d믆i@;P fW?U!Y%iAjê:p~i5P{wP!A`ICTt~0;?-u.zl1.<KBl>@DÀ@<ysPtS r ˁ/ ׅg+|1xO`j@0pL:?rF9CH 8*@]J@M-6#/`8`J Q.:?RuQ>{ 6 DO7HHi`r`U* dćU742x}r( LfNh@>Y+j C&t|Z@0P^gEmTzs~8((^v5U< q:oUٺ#@7À`Pb. ts(0\/ #@ pPYe3 @ D* P^x0Нf!X7&s;+U@2@w& ]8C \.ozj0TA> J&H $r |8^C5b~{{g?<@s,4`SQߍҷ2O{ s A&`Z hŀ'Ֆ1'p7 oB]e@D`6 =N4{Wz_FE~v=UmDت,  %o ~pA 0a:x9wRm>4, pH,  Pk¢0 !ۂtY(2~jezI٪=>/ `9#'cA)L07cy\!! I C|\*pAa.@u3IEXr ,?-? PtYCҹtU {(>?G 5,*Ty_C9`CHwy)xݹ.zT, a!@``؞*ˣdD 2t`2`g [-XG-`kk؞@ R]<@tyhzOr €!(%(Z* ` !XPЦ}tT`7TwW ,)T#Т3p;ۥ%׌P]3Z.Q0MA euh.m68O=@|YK` v}vD$Nda@0 cMXu]o 2. 4N?ǓxN H0'E U@r5vvu^p~=ˌ%R5BBZ V`\{bC"%@2* xV`: H,uns$ lKǚ"t]|Rc5|u֐ ߟa> `Yp8>32Fya‚9m?PAs*at~8Xcw A@z̿^tsNS 72ք~3`3f+Th.`q c׍xpH' ׅ''(\6*@ao u޶SQtS@Pd) 0Ѓ p`N O2L{nlP%ʓsAt_F (,sDDC(uT a@JC{•!P ] Áp`L3{KϷm\I3 gp  }!IA N~_5Je ,[? t%GpA?.D Bv8R'Wq`k\. vXOaP!| U N S++@a@bO`0 5d^+5c*RFx %Ȯʃ` dy|  q:FJ*龜e"ybln1P@.+iHUj "l|H@sɪ@mVa0`U@lFl+ n( :Eg{esmo)? 0x4džc%$]U'CD#>_1.}x#6[wNǀ'A:98>#F: |!A{zpQű xT^py@k`Nt}9hB @~T:6C[w!}?}}x6/ x ۂ 7x3DrnP Vy0iv pn0&;-ՆA+Ɔ^"ytV<%@<5 ʬ:a!v @2TAy0&%V BQ:XLZqw^ ~ڠCwg݅M%"O=ˋ6* \P**A( 0l_~u 9"! ުUl8(!AsLt8P7 Z$MRίB!`yk fBJp WV!LjSC\%!`*9. p>Z2;'5aA k?@</ $d &,9\5'@(sb%D8`2Pj;oT\78~WfT]:|2鸸XY(U$A <9 ,,?R6 U=\ DJ`} mࠐM Z8|U \*VQ I2mv 1]#/ 55+s5c @: € ) 8 鍟>c ]uV֖ɎJ6 k-4HVsOАRE(wW\s^:l5$ŝN pO@o]ꚅj6Ө `*~oAqa @߫#%kə3 3?CFy j`@ }f p' pW CzA|m6^qV4A$U{wFg.AAm~/U紣m0걋!Dx^~Ʀ m *OZ5A @: .;ߒ_!N  Ϫ Xa }Z / yd"0\Ώ#$xpGyfFt}˙TRe.ppeA:~`8i%!@O+Wp/υ~Yt^ZϗA 0( q@6 uY'y+ͭCS! A8W\B>a8} 9$I4 A<3}zÐ;Ȋsn~d*`fsno@.AUT^ =T@< <3 ߃/,\Lp ۟kɼ 8TT|"НڳEf jܞUdƄY⮩8+5)"Q7o'rp,%-ϽwٚPtyׇG M,2fI O O``B`Е3j ]#_Z,`ɇA 0(R%؜Byڅ%rgd^ 豲BÂ^0 %B|GUՀB@0`K`  lл8 ePqTP`)mC R*Ls@HP*q?q 2• HVa:}W|gljԼ!Y]&KL?q59.WTͦxz78.ӈfTtLv+pi"l>@;8@`pM00ۖ '0Jfi @>SY\&9O,.("@P $@$eA D |.w]>gkocaۿ  oz-paL><0T@Y? ߁ w@!eG*= G ԪZ=@ЏU0V1r@*1a Z@PE+S TEC @oÈ9[bLGŤ i>Iǧ8,w?Y Ԟ|\Β`0 Àa|Ix@hL*4;6o  { B\ ^d?'BZ TaBpqi0L%@슷 HgJ/A,CZ\,O.i U 099TE@<9D^P h~趼Vn @8@빺2:/Ic ß`dgxo s*视ת }D() |‚P |\A`J 'e'uA|Ȑoj=rs#9HO_Q!0Dl/mg 2}7Ҝy!Ώ:U:@N97uU-< p .aj pW$s] A 0* Pa\jSq2pEr[pt?;P -81a-n}P:^f A  x|( `* nʖC3|ŸW.#3K@`X^i8Sp'm.@6Ӑ@X^1('D8qRA8t_U oPF0O{0`[gy}Ҫz6|pq}?``Ake 1Ϻ3C]TV_?A,9PX*P'_K;U:PZTzyB#ͲU!@mӛ8 po' \94cùiowU## p``B@n pЭ)<~q* TB Ppspar }*> H 3\Ztz @?T1p  K7@bj0BrqvwGlh辒 T:O,ćx|aڃSx*@C٬sbUJ2(ee (  ] 53\ϳڞiAX/ga=7bϴ-UfBϭP (@$ j/`Ї@  N ʼnB&MC Ϙd\,=oe @{ X!TR 48|o<1k>b0ee^ ErI1 `o3 %"G N;8^( c\ 28W *1( U#bwzh9By?tXے pyd"06,Sn]UIW3gQ DH$# `!;, FÆzh*@[@5P "iyw,|xV*AE<( P3)<FIPnk++ <z ]ۙg(t݈ 98Aݷ( ը |BxWJ>Á"鷵D66L s/ ݠ—n="x4tTOh?paIZN\u=`R TK* qOQ@U Ǘ,)佁OPo r* BRk|90~BJ A:** TR0!( ; <|Sr =7Ѻϭ&)   6@~Z3| YLZ~ Ts 0߀ XuY@BXB$]V{ۚrΫi: [Wn֛u2 D(TI7 @辝AAP@N AT   r b} ~{™,*T@:!;[ޓ߁ &?nPkNqo@(ߝ ) N7~l( >$>8&O,w.vI 0!òs?P8? 5<8ˊu^O}? 0"<!A"0 $pL]ׅ3 U@x}8~ch.ۃ NJ:;XiЇ SQy0'C.\a0|}<l M@}kM: ?6ϩ   `^^  4$[l!|B,3*ttO#:4:~G@9 7+ÛuxkYϵ| SArPO 0`w F=Q];>kNUU :?ht8R< T Fzo^{gm1a8(s!?o* M?ѯK~E&ixb* d5]ͯjD =l wO``_5{ Aan?T~ϙMQ?@&,'kr/^,/RQRp|CTB0L ǓA Ɔ#8»xm-\ZӀ*LkELq1 sz:EY$xtNOl2jbɅtXd谄1%! BJ€ϑ2Lpj]6$+ 0G`Gz0@ 9 85'84 '`<0 FPUYW`G3x)#| l:/ʴ'~emUU.yـf%w)ÃhO`*<IPlJ। U8|V cVY+L2tM٠!mV @2`*(N5xPx)KbDFl(AkƐ,Tq1Î2`tٽw։o}8~']UH o*exJJ}J[)9$k[V *TLnqB&yJCW&v"p*E^wf ;`KclR H^w\gXpEafk t|;M .=W {iA*[Y xI@ D> q5@yu:mH\*X"{T5aaMcOHpEf^p@QU)@To@$ Bt~Ka/@@u- mʷttv;Pm0~[j#l2jyyZ]L[/3~h0W/Yf7l؜v9X9YpA9#ˎǶY8ℝV86odG5k@U6*驡+.` 1h A p*_8hTOS~V _p*`,#-2X~˗"j#:|. |0V T%t2uHp?\$ MO}o|]gGn.59R"UGpX8cgd)nd:,+O64[ mm`kyjFY}֤z)u'm-'>3$|W'v+2 Ф`,B@wp|XrqHR$CU1x󿄷ģ2sU֖XƻU;K?5 sޘ dSOQ妴g'( 2TAu\*n RC5` <ԅ'ÀI|(P!|o@ %|@4s)}Ġ@@gd2o`yfS Pk`r[S{b09HjyjNUV&Zd3ު+6t|:V_׺fY_* Zd=-ߙ{P 3Oohx经3?یڹ ?Æ&3g@ppn`u ji*쩋; I{PJIF^㿈7k-gl;*c:Ǐ(u3N Pgg0`}Z0 7j Tۂ _e*Wwa|o0>)0[bnL~0 yX珩p 2 J?%nJNdt ws9AKe>~KMf4x/=ݪpNڢ6xCK)?:?A@T7Q;s~}/W[k0ClNp_(n!: ސ__T.ù2;؃SX. Iۃ!c X%F*@Aa(@D*Qo@jb0nUX (¾h^L\/ 6 X}Wma-Ů\Ϸ>o~& \;+db6ɯn6F' OmrN[fkegaJJ\3 8bl^׳7o_ئvxn_MŒTݘ(rI*J9!DhPR?TJgs@ݵ:%@@}os.b[%VsX+}.+T|_M<+QR8H),!Rms]9@7 &u%7 Ty$1a&' oi7CY$Czʯ$?:?{?ڃ`7JO_Ӫ9,x.Nq^|y`APΓ4 |:+t4H~  '&L ~"v`( 7Sv3 `V M[P3!_0S) "箺@8?aI5F5At+%xVTrt@Q@{&a[\{<>5ןO?x߭wը_8j-(I 6Q%8X}:g=6•P XF'/Z?DlH@w <|U/ CׇW'}Db!S 0P/ƿΞ`Wv`Oa ڂGrA^l/&6WLO& `'8A=\4@& 3~B[n;^#]>}-'<yC~i wk:P\d{:(f=V_7g~/~m¯˹gëւ U R|r>~> Cu >C@`*}T~& n&2ZC*JfDPB%&` #)W^ PkD !E'or&Hxl"{>Gϙ_ 6tƠM'nc8T@`P5;GBQ@:% jw׮q.'-@"y6t>7t Z~C x)muׯpw鱱hOɿ+*HɹG2}Q|g6`-u֒tz"J8??QA# ~~n0D0 j/@8C0fF,@& tsMreR Gv0׷¦x캦x]mTBF .Oױ_i8$0 T@F>a L^ 09{?@kOjxɝ=v{"0 HH L C)d7L* @M"?-@Iv}. pt< п=A, d1r>7퉒{=$! 5zk)EN9+5ncYz_ l:q' pƒF; ` -a.A= k7H!%HOW hK?]΀$ %  $ ߪU >쀿[poV )EO5YCA  +!! @7Lxӽ p ϞN8O|at@Ht\u@ϸvx@d$"ځL[L/M]ˉf @0'G,pWkgCL0I+PAD0qI[|:ݫnI2_N`[W.a(\z/+x>Փr$X'6vyIlwZ";_ƜH; A|z7Ms+鿵|m~{Z x[J{/"x PO&;>96`<m; PmtViN0EC1a 9yoZ o&z`" ;g (+@e@W@|Sw&(:#H.m/gu:ݣox[rpJ ?R'0=(ص~<)ok="NWd JxN?ë:tҌBu8J̟)0ow+ݚ]C rnsP2BAV yMU+uIS>yW t@RvU''24r'mفEu< ۃ! @ |ʝ?_˫oRB` )AOSU<* ]IK(0FtqRocgHHmf{X ܝ8v -[z|(}nk|NUPB)O~Ϟ#2"@Hv]9ٞl_ Vuu7?o?~MkC3\ Rը ^c@!PDs_r)#_6 t S 9י odCA?'_?}9@AwE6cc[ xؗݙߊU"":Y\9 :Ut'?[dpbX@]'a 0 \>Y{]}Kޤ')%oDߟLHA|&̪**%KP!YSPtU=+Ql$]fQNO ?̈́;0]q=U`'^R"P*U z{C5ǽ/O %KPx诫S<1_b @ 0l|׈[8;rq va3PT9 wj!] l sz%ۂF֠؁)Tv?ZY%@B 0z_1%3= E Q~pVgwc$n $w'z-g?& H @SEz"*9$;@6b-Fn8gxшW~LlÓmj %kB$H-L pl@Pft x6vvyi2}NoNl u{0H @ TS ;?~!<HWv*`4$Pk>@o2$dI @֡Ǔ^&| &hrc``.}!/" o22I\nY6na>7[ g sj"$`#0k ۑAPANۆa*@< v d>&n,"uI_Mj豽r$k,TQ.+`{P q~}lux  EDg9 P|gDr|V࿌| Pa`T@~< ƀ:O~BT ۍ!0j~>s*6*VUUj(ݽ?u_݊vO% iYv (T5jeV XЧ%vaٓH?Dt;8|uqSAW`4@?OuD8U?V{PBog" ` Ы R2 ߍIqo_M ـ<N@P y!DXvx V"gp6`v`( 9HN Jl0QwzQ P|@f2%U1΀+6`G̓xx/%~zu}+U@4,xcD 6Rx8F\H iAzN`Lحq?_$`<* Ä  E^(zլNZRbӁ.r]9,6~!!bNzIk!УE$1!-k?JhЮ@D$U+%@`CB @G`ڒJ'yxGFAu?߼-?=[d6H D!Ui/U@ufJ(7׽/Ob y(Hہ Of Jہ8 }mX@d,|ʝ1v'pv&H X@iP+k j (%*`>TȀkT&DȇL pÈ\>;SJl;p,OWtE{^~Ty/=f[ ߉M?G}0T D$`(D=JFEZ*> 0`e TmlF"7~c٦^MOwd* !Z@{R Sm @lf/VS+)<Hsx,X @IoMs=};aLR!U>wƌ ?3U~! `hk0RJAds)DV S44ZM6b,aLop˶p3dH+B& /;-} I GVN_C"-݁60A` Ht?1aFy C.׆v_;n/!a Dv$`@b"; nb@PT ` O J Dt# 9TvL)|9mtxrn:}Xɫ\ v g sD>ځH߇U0LE !$=ukډFvu Dnnmӊ(j@8~O5SQw[s=+DSᘰ v)GDŽ6`8,T 4DUGN-@f@lQG`0]V| J k Uef?,+dWp? Y~}~-{M/ɽ"sXwVOv "l6D {3 ~Y;pIAxYPXn#=͛z-rzx{FkD|k ,)"_x+ xP+^5eʔ?8/n@lہ  P <տ>׆)/ #~z?%lk0Rf6 2HO`0(*^vwb*x @ @0rm> @xiAd-,_u-| @yN?RJY&j DD )/U{g+ݐ=–v7n:Vrkz-Z'Sjl9ZI lYxy{=>WyP;09 B@Hw3=׿>_zpt5t"w;Vj@ D@UkT}Fiٓ%7dN;{rR$sƑxn~-!7o?j $j X@#3 *9 ^^7({~/deXpdA9@;PnWsHo ځJTh (J@Uځxi n* 1(6 mPfvkZ$W 2`sU@/$ _7@unFJ;Pxl(mco1<~uo}5/T< ` R52H@"` B&`A/+ UJq/Uy 3?f}A\Z[N)փZִR`? ߪPH,rד9l@FAl@r0=-Xr 3gKC /A~,|2DAX; yU-#  T@a%&5Z&R>¿OiO +p4) xx6ź{r[[udc n)&OJ0$roUU_\!q]h;wA _A?{CH\z[KgVyȃ >T5QE*@om^@U_sJq[>zf{JPc83lc PT߾%"t=-H(hmQ`s4 3ۆ Aܰ D`"` ?aaۍPjt#F @sO}b Я֗S$q{ܲ]y5-?| *-jH ULX XEߪ@(K(Z@Ao n,8r~Ʈr.$B\?_$O_AO~lU%!] 0V֣BS<֯6 I<X/ * `VD@< ?.YN Q PPX |@ CBLdVBKok zQ*)0L&*?J+hz pl"Y~.`wtIl?ur+^ja}u!%_xs[*%rYmU9[ <@KH -B'(^X2@W׭r_45-}n8:?wƀU\)&""HA P;`U@ ؁^w>ؿ|} 6Ⱦ`ry_@)s3@h/.(ߙ쀵bـ\P+:xLXc ~&%x¯ LR3$+TY ts8ps|KO+<`#d䅗ïΚ)"8!J&,e@Ikdlc3"x[kJ(HAהlՍXn$_0%zꁿß)h ۂ߬9+ v2dv}N9ؿZkw`Q 6DŽy5H@=,Կ/ ߺ%w\ApIW m ca> Q~@zv ] RzA(D" A%@ UrRl`I X"73q_k;` pA׸3f:<  Xe@ l hA-+PKDvTPBP+"@mܵS ɿn |=\Dh}P=*eMI eH@@kwzh??&(a}o 4 φ.sm7`U@0 Hk0<@+ j@ H@`]rwzQɋ$ @'06h!s:p9mb &B[Zq@qar<1r`m@JL "++ v|dP@ h?/_PnSQzHYj^ "sj  <ߝ:'!&ځƠ I}|5H_ }Gi$)@d]5U@c֠tRP@g=H\ajT+f<,⇁@MN> +?= iRfk>>zB! + ;@>쀬E*ZD`I J@Z2ᒻ{S}@rJG`5OL.PDl=/x>zsLsh,Lj^N*' 6T7/Q@o)sA2*]?ll@+`; UrSV*DI|@' =%9s:?ˇÉ-dxj SZH# UxBP@0%@%&϶]`$*yD`^b?݄,rz+ГUc ~] Xk`@V)Q/ϧcr9@tN`<@(=!APz7芭ԫlG@@T@Ak0  sUIG@AaHj2od";Lj@ ` ݀F"]na=!G<= ^&{H_Y06 GX"K W%BW$(ߴCaB0%TX56 zb5/PT !{Ń>߫_GM<H@@=8B >NrCC{ `vT8a(%L*V%K@eQBzB|7i'Xo'i6*tkD`"c>c@/ ,%,ϸS z dV SQ V@rlG M&yjOn*+IDAT" H`<Gǔ}]m|Ͻۇ-<DpP@@!KPIUɬ@a6PH*UJP2HCds'"PHfMU@de}t_Jq{} T<@jt*P9@_@ U&VPv@Ag4Tj)s\&l>*`>Ȁ<@dEĀ"C7J@@P#9gN?6LsQw*'B` E'* @0>Lw<&@ `e%` _/09T@r@qdvci[x+@-̈́ޕw/:,{-*%DO@Pك?"8S%?@d/V * 3j u۞w)Wq߸+Dm݀hۃu=x $e9+`T@DHLGYtc e)@ @wXtT`$H^bA `IcXE'-dZZR5DBTI&_DQ8T"5*jbRL| |$:T{WRHdU{%HE@v@@-k]Y*y p0pHwݾM$nǂ X014UD\ 0P3C逐N z cPcL :B PZEp&me *K5H|k Ţ@":R<@~nʢ.7~Z7m?ֿ- T*y%+@T)wB X1U]:/PK -B%^H ( X%A \O^%>q$"C e@t4-=ijW/x̝H`V#Tv FBG?A&IӮ @0V锠LA92 @a|0 PMA`ˍk YHO|Tvi'sVK,Ka x@H[HUJ Tb)),!h. "RQ.P17E* g s.е"RE+m ᗇ*E=`B@*t #+`+;Q6 nmT@LW@;ID5@ÿ |@%5!hXn'=BO'+g-Du`y9V%Ч%h7Y\"Er>KY֊Q $oUJ ]܅  QTyeR>IV@]PDd#D<"X~@Pn(ŝ@BD ϩ-P}ռvKP- W%P@J zKnVIq>͟~(pJ݁◇"9ݠۄ2@@!DY !8 @ByO0) $_g 0nnɍ%*nVo:͡'sVp'@d~"or79[DB^ iDT-%rAjЄyڹP\͟;?>ܜlrO_?L*B(] &œ( '1Y~5, s~7@2o  V%^&A D | 50#&5ui(5/wkqXy~skP%+e =œ*@%*@Uj؁J,j@6EH1{bﶒ\u~Ӷ<8͝0d&_9o+D Dv@ȩ\kPdPnA.+vT\rK+$+WQ~/{@wK7O.(0WE%m}-Dp*! ) X؋@Qw ,GW9"е[k BPVUJGe*ٓ?Uo{TEˏua:A'Yk?N P$@O3$`H @< oʔg` = Ha X 8 #q ds\@%Plۀ H^O| 1 Dv\`P XB(0@* ,f(L*z_8P_IDp ? ̩ /Ae `2%B , j ($䮀0 HI * @W[fɯJ8M~D5@5@= F Ԗn7=K$`; `@נʱJ%D> @PֈdM "jBje 0%?Tf?po_?x,8 T@ Hۂ;V)3V Asb%.1й d@l+~WH 0;"u‚.w!j+ݲ{ݪD`[kjn"(g @?GDϲj UzoSA $`@T R6 y I.s}?}nehk, =S`䔀 l@֠vGA5hDp7.lX>ೂPs #P%0@=ـ /wKH ,'9 DЁYHG G(!l*hARH %(k9.*K j T$+ӟ'l$ * $* ,"@N $`CAol+@WGݸ^f3% x%pp=ǒGle7l@#ـP `+ $c ~>gj?"Jl@_DiY p4J HIuʏ#v_K_X.* GGE* <` `+YW+A dv  \t}+[&YBxY lsu 턪e/w  d A `6@@* g5A)o r*RL95 Ev ULW>YGQD "9\On|ǭ|-z-n=c V DjnSEvjـ*U˱HAJEZtx(ʀw]$"h;g, ("P'eV`{;)]H24@txH X@\+0A uqD Oz7~(H@(m @ Livw?RӁy{ `ۘ^xPT0cv@(Ȕ@јp<@d 0cEBl<-*l]=900t_-`2(!-G&; n&ǵ @G AB=Ri(XHl1+0OՀ2 `݇vWo:ܭ :oۼ+ Bl@Q`EV *;v@ !z3'ZlL@U Kn=Κ$;i b<YZBHm@#"0OIH Ƞ)Q/ eU@ 0}/ryǜ|CjH oR"bG 8H Gkg c0!00AHm =nC| aFf;^%~D|A fAtsGஇnѦTխw- sТG@P@N J@@ YSЧ g L(gj @9V!c>4'>^v) ȬU F@2YDl?#XlT8QP0R5v?XŊT~V~ @\~e~Rr3OVR? %*-_ GrjdMI v"UJU@UnA?r `'C(h[JBPH;-Z [ET@ Ѥ3lԴY tPuuq K^H3+=TXAcgW"#D0q^P6'n 3(@Z%Xl> A-%VY"(RQ d*k =7~P~^l .6ꔋ{ Aq ӽ!ųj#J#A^.!7ztX@  BYa5@r.,iSX5V gt@81`,?UED$@ $*%kYH@֣@N225 ACw~T6U P+{Q Vkl6r<7*l&P ,@;Dـ' fJ0@U5@ `&4v 2ѷb!y0#a:AXb5 J@m@r^K^?b,AR)eݶw}Я3.S.I@@ 6 ՚6 ,܀ mùEo@j򛅠~A(pOOt3 hP ꀃ@OXꭀ M^~IA0jH J L dT' ('$R(TJ>%lg@?[z?נ;F[]0 DڂY $!&D?LQ Xx+u&00 Ȼɷ?T5TCR!!+:vW @2!8uaP!b2LaZ!6  $`_?ԀGD lJ1]""XGdjz].%hJP Y@ +B֠ r2&5 ` \D*`,"?YJ9Ԁ cA Pgx$+0qjŮCn1=0CnOKbJy" yAjB@>3 ZZJ@i]R4p)S2t^UiPml!Xu<@@ qg@w 2L?V{ X8,J<5|p˲gu{pCխApBt0@~bdU[H4ȩ]>R(/"%ֹ?J5 s3v @0T@+X&> J1 xnE𶀟3;=BVP^!\~Isy\[\,0@)P[2H RxUJ" X}>>`ש8@ߚ, < +Y( |G@T@ J@|@_@I`]Yh +! %ŀ2*` ?_яvB70d;yi ЊTO@ rL rJ])V T\Uw^s2p}38Kw\?mB[ o`H`4!A!}ht`++ @vbpmn]X 5 `Փ@E0߫%9?]Tv$MnտH*  /(IwGPyA );@~ǝ;.>$w4< RB+!0 U6* #l@( PA P0HO BT0)ty"\@k'N&(@V;xPq+{+ y`% ف(4 %"@IՔ h^Wط?p5ߢ!Ȕ(XO;!k D]V3PxxHFdb0t٭mۂz[+`g 9Q3`򼒻z=<6RDM `%Pg j(RR+ /%{vU}+?p \ Q BH=nOTu*x{(ɏ''$}Հf^eIKC(kfd JF T ȪD`KUځT 1ށm)SzَөHUv⮀ D]( AUq U^V}Qb (J^ [tl`^Im*[JH^[JȆ(Č@ɴ K-H@ 4J "!yoY]u

S9洫v;A [I hGsN r%Gg耐Co*`dW]Bxb `EA \rnX([ Yl,XL G xUUwBg~kC9ԫ;sT@XH[0j <\ xO 2-$##aɍɡurq@&5v8KB2V@ wz]0b'\% ۹Hx) ؽ M@PV`n=x0* 270 mBc%-̭B",L}g`9? VfXHIYVq,8kz?\ֵ}v9m#Wnʺ>7 ikЃ߶s&}nJI@*GVĠ@ZJ@ L CL7zۭlwzX G#._[uM\2f߉|b"&Jm8V!<!(t.n/U^R i ~ =`xJ*ˇ]vLn&$߱VqK H\ǝw5e8sΟb,\U`P0 =n]KA^ ""}x0QY<[G~!&Nw=nJsB/Rqxڹ55p \=ǟ>fΨyuom$Z`Fl+p. #sHn0{ D@+$+ xHJZ8yewʁ{6l\.%w|c>׹S1g7gykـY"Jl:# hi~?F XE0J2%ؙ"𪀀Gl݌ FSik^5p \#άg7rvkϪ5o=v{b2 #T?BE{9flj`(j@y}{,=F5HڱϩGj \{~})9|{ϧ{;{ϙқ֏őwtN9wwu{a?L w=]j%; /R'7Qu^7:z?wGM+כ渏uUθzqIENDB`ukui-menu/res/icon/pad_mainpower.svg0000664000175000017500000000132315160463353016513 0ustar fengfeng ukui-menu/res/icon/application-x-desktop.png0000664000175000017500000001172015160463353020074 0ustar fengfengPNG  IHDR9Q] pHYs  ~IDATx]kp>" *p$D* x#B ,^yX[y+*OA Q\@D /Q$*$0;;ݙݝݙkݙ3O>} eeedED4 ݙ_ @oVQVAiii7 `ܽ{7+p3ڲ=S>T"{]@\NB;vTvmc>)RpmzŎg~[3m ~'ڼy3{2ʕ+رQ0{əFݺu_~Q ] AAu?^X.fR_l T}PMnwf;+(2<,As?ChOcƌUҺuk׮@:uOJܬF__x衇Gc32tЯw'=TRR"T0nXz]QAѝ=~ӬeŊԪU+v͛/ L',wPJJ" (*TGB A%5u8=X#SRsκɃq7|ϳVZT~}JL!F7OJK Wܹ:vԭXcBA<8Νj0a7kKlq5jD|vܸq }9h+(aÆivÜ:q5mڔ=`ĉL6Ԑpc4Nqj9TXXH{Yݜ ѣG+a P:pɒ%цb܈' N5!E%i!6JBx<&(--Mð" M)aF湦)PWxwwj# pG 0iD:v8 jftE3G=0Āq"Cq u "RTTC͛7y>TSL7nܠ|PGr&|ܹsGw]>*@~S۶m3o>f={AdvB>}t)MDMP*[iٲ%36KbO%۷gmڴaEsH$eE#S߾}uWNqrOL09 p?ݺu WA3= FEYYYOSRR·={RϞ=ڵk-[jS7q$&5kdw}̻.M{1իWUWP(((DoD{Vy%* /0%%xu"xib@˿u_c0r4B~v2vo ]JeJJH$͢KIAѺGab`Ԫ]9_tIg 9𓲳ҥKt9م;Iӆ;Yp! O0Cϧ}-''$c@zЩӋԫW/r.b '1}TøqJ*͘1݇'o&0aDMT`cG[40VS$G@WXaA af,_^5s&[NRWP{a>p,;|tIڴi) nX vHݻwg 3?SO=^5zV 0 kFIY>: $֭[KòV*ըQ}.%`CX^^ ,@ıСu(Af5H+W*^IY!W1I˝r2V Xw6%)D .\Pd( ~QZZZ$>U K.M%I.THCR^^^h(  Q(FPyH8Rx(\vgT@Q ss۝..c/yc<[,'2nX x3 ᗝHCH8:VBϝ;O:lF\ p0e6ɻ{„eW%ѣGѐ!oJOy١CCdWb,߿Ka#fIdȔ"EȁM 6`)H 0.)"&#@+udr@LA"Dkj{ycP֭o f2Φ߼yyHy4?$])}b xV_~B "d 6-4Đ gAoN=z`#(R j`[rxMK/5iD_/:<5D }~Pnݕ-GݺuXAIF(L$ KH2C(L8(:-\#ax2_oQt@$``$& 5AkXtR6Z1y .25o l{ehƍ,eęq[+L--M|Dc}&dI^p .3JMoiut m8]@G^DH D`lZeٲ_HVvӔ hKCP\Cj,كe*.ZΝ;P89==]Nd=۰k:xY1Dވ4Uj9e7`/ma۶,4h>U`5>1²81? *5D 85ћYJ/`ؒߴiSV Г΃aƮuԞzADfl&hLhF 9jڌTFM|mzС,/-DfQb5`8/ʨnݺ?h5E\pvȶ1h(q*ԑV J p~ lcXCdz-b0Ê8vfMw 'jؤIu'G+r [Ҹ  ?_IJi`Xݻw' 0Wf@c/8ICCQ֭ߐSJ|a+$3 Cr|H }|߾}e˖Le0JeLÇ BRbSKdF^APgϒ;eѮ]5EA#Ft֍e#oC!c>8QJ^Npr er"=I!h`4]*`$>`˖-yfC| ӧOWNz ==<sj֬8vkx-@{<>\@S ѥ3f G9 6Hz~?p7crJ5\|B̬^#;l՞W' B _-`Hz<ћi]g26իu/$aƙ!C<%338ʼ''!SIGΝSsŊ4gaU 6d)KpLP->@b v) YN2ha' U3g46 >|x+20 ? ;uĮWw Da{Dڳ{X,Dx@ݻ/=x$0k,c0lX#557~LrCS6m"xРa咝Q 7رv:#i*rs o4 #c "۷~\ ˶ҩzGYc!@ \dRh&fU|;P\\Z>$Gl .[鉯z uWnuQQ=+VdvRЦ <0*^L0#BX.&W^LVz|1.۷{TYVNU >4>_UgB-~JcNX-̙aF\ @Clpڜ={ΟuرK-&fL: ?;;ʬw5@=X]@|Pԡhٲe ZD>3r4q1JQ^^arPG, aX˅P:CD؁␓ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 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 3 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, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ukui-menu/qml/0000775000175000017500000000000015160463365012221 5ustar fengfengukui-menu/qml/MenuMainWindow.qml0000664000175000017500000000226215160463353015634 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import org.ukui.menu.core 1.0 import AppUI 1.0 as AppUI MenuMainWindow { id: mainWindow visible: true Component.onCompleted: { console.log("MenuMainWindow Completed."); } onIsFullScreenChanged: { console.log("full screen", isFullScreen) //loader.source = isFullScreen ? "qrc:/qml/FullScreenUI.qml" : "qrc:/qml/NormalUI.qml"; } AppUI.NormalUI { parent: mainWindow.contentItem anchors.fill: parent } } ukui-menu/qml/AppUI/0000775000175000017500000000000015160463365013177 5ustar fengfengukui-menu/qml/AppUI/FullScreenAppList.qml0000664000175000017500000004757215160463365017270 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ import QtQuick 2.15 import QtQml.Models 2.12 import QtQuick.Layouts 1.12 import org.ukui.menu.core 1.0 import org.ukui.menu.extension 1.0 import "../extensions" as Extension import AppControls2 1.0 as AppControls2 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform ListView { id: root signal openFolderSignal(string folderId, string folderName, int x, int y) signal contentShowFinished() property bool isContentShow property bool isSearching property int itemHeight: 40 property alias sourceModel: appGroupModel.sourceModel property int cellWidth: 180 property int cellHeight: 180 property int column: Math.floor(width / cellWidth) property var currentGridView: null property int currentNum: -2 property string currentId: "" spacing: 5 clip: true boundsBehavior: Flickable.StopAtBounds currentIndex: -1 highlightMoveDuration: 0 //除header外其他鼠标区域,用于关闭编辑模式或隐藏全屏开始菜单,当鼠标进入appItem和编辑按钮时失效 MouseArea { id: appArea anchors.fill: headerItem.parent onClicked: { if (mainWindow.editMode) { mainWindow.editMode = false; } else { mainWindow.hide(); } } } onFocusChanged: { if (focus) { if (root.isSearching) { currentIndex = 0; itemAtIndex(0).forceActiveFocus(); } else { headerItem.forceActiveFocus(); } } else { currentIndex = -1; currentGridView.focus = false currentGridView = null; currentNum = -2; } } Keys.onPressed: { if (!currentGridView || currentGridView.itemCurrentIndex === -1) return; var newIndex = currentGridView.itemCurrentIndex; var row = Math.floor(newIndex / root.column); var col = newIndex % root.column; switch (event.key) { case Qt.Key_Up: row = row - 1; break; case Qt.Key_Down: row = row + 1; break; case Qt.Key_Left: col = Math.max(0, col - 1); break; case Qt.Key_Right: col = Math.min(root.column - 1, col + 1); break; case Qt.Key_Enter: appManager.launchApp(currentId); event.accepted = true; return; case Qt.Key_Return: appManager.launchApp(currentId); event.accepted = true; return; default: return; } newIndex = row * root.column + col; if (row < 0 || newIndex >= currentGridView.itemCount) { if (currentNum === 0 && newIndex < 0) { if (!root.isSearching) headerItem.forceActiveFocus(); } else if (currentNum === -1 && newIndex >= currentGridView.itemCount) { currentIndex = 0; itemAtIndex(0).forceActiveFocus(); } event.accepted = false; } else { currentGridView.itemCurrentIndex = newIndex; event.accepted = true; } } model: AppGroupModel { id: appGroupModel } delegate: Item { id: groupItem property int itemCount: appGridView.count property int itemCurrentIndex: -1 width: ListView.view.width height: childrenRect.height onFocusChanged: { if (focus) { root.currentGridView = this; itemCurrentIndex = 0; currentNum = index; } else { itemCurrentIndex = -1; } } Column { width: parent.width height: childrenRect.height spacing: 0 AppControls2.LabelItem { width: parent.width height: root.itemHeight displayName: model.name === "" ? qsTr("All Applications") : model.name visible: displayName !== "" onEntered: appArea.enabled = false onExited: appArea.enabled = true } GridView { id: appGridView property bool isFavorite: false anchors.horizontalCenter: parent.horizontalCenter width: root.cellWidth * root.column height: contentHeight cellWidth: root.cellWidth cellHeight: root.cellHeight interactive: false currentIndex: groupItem.itemCurrentIndex onCurrentItemChanged: { if (!currentItem) return; var itemYInListView = currentItem.y + root.y + groupItem.y; var visibleTop = root.contentY; var visibleBottom = visibleTop + root.height; if (itemYInListView < visibleTop) { var targetY = itemYInListView; if (currentItem.y === 0) targetY -= root.itemHeight; root.contentY = targetY; } else if (itemYInListView + currentItem.height > visibleBottom) { targetY = itemYInListView + currentItem.height - root.height; root.contentY = targetY; } } model: DelegateModel { model: appGroupModel Component.onCompleted: { // Warning: rootIndex只需要设置一次,多次设置会导致对应关系错误, // delegateModel使用persistedIndex保存rootIndex,会自动进行修正 rootIndex = modelIndex(index); } delegate: Item { id: appBaseItem width: GridView.view.cellWidth height: GridView.view.cellHeight Accessible.role: Accessible.Button Accessible.description: "This is an app button on ukui-menu" Accessible.name: model.name focus: GridView.isCurrentItem onFocusChanged: { if (focus) { root.currentId = id; } } FullScreenAppItem { id: appItem anchors.fill: parent anchors.margins: 12 acceptedButtons: Qt.LeftButton | Qt.RightButton text.text: model.name icon.source: model.icon icon.proxy.appId: model.desktopName === undefined ? "" : model.desktopName isRecentInstalled: model.recentInstall === undefined ? false : model.recentInstall hasFocus: (appBaseItem.GridView.isCurrentItem && root.currentIndex !== -1) ? true : false onClicked: (event) => { console.log("ICON_CLICK!", "DESKTOPFILE:", id); if (event.button === Qt.LeftButton) { // openApplication appManager.launchApp(id); } else if (event.button === Qt.RightButton) { menuManager.showMenu(model.id, MenuInfo.FullScreen); } } onEntered: appArea.enabled = false onExited: appArea.enabled = true drag.target: appItem onPressed: { fullScreenUI.focus = false; grabToImage(function(result) { Drag.imageSource = result.url }) } Drag.supportedActions: Qt.CopyAction Drag.dragType: Drag.Automatic Drag.active: appItem.drag.active Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 Drag.mimeData: { "source": "ukui-menu", "url": "app://" + model.id, "favorite": model.favorite > 0 } } // 收藏按钮 Extension.EditModeFlag { isFavorited: model.favorite > 0 anchors.top: parent.top anchors.topMargin: 10 anchors.right: parent.right anchors.rightMargin: 28 onClicked: { appManager.changeFavoriteState(id, !isFavorited); } onIsEnteredChanged: { appArea.enabled = !isEntered; } } } } } } } header: MouseArea { id: favoriteItem property bool show: (visualModel.count > 0) && (!root.isSearching) property int itemCount: favoriteView.count property int itemCurrentIndex: -1 width: show ? root.width : 0 height: show ? childrenRect.height : 0 acceptedButtons: Qt.LeftButton | Qt.RightButton visible: show property var widgets: [] property var widgetInfos: [] property int widgetCount: 1 Component.onCompleted: { widgetInfos.push({label: "favorite", display: "non-starred-symbolic", type: WidgetMetadata.Widget}); widgets.push("favorite"); } onFocusChanged: { if (focus) { root.currentGridView = this; itemCurrentIndex = 0; currentNum = -1; } else { itemCurrentIndex = -1; } } onClicked: { if (mouse.button === Qt.RightButton) { menu.open(); } else if (mouse.button === Qt.LeftButton) { if (mainWindow.editMode) { mainWindow.editMode = false; } else { mainWindow.hide(); } } } UkuiItems.Menu { id: menu content: UkuiItems.MenuItem { text: qsTr("Editing mode") icon: "edit-symbolic" onClicked: mainWindow.editMode = true; } } Column { width: parent.width height: childrenRect.height + spacing spacing: root.spacing AppControls2.LabelItem { width: parent.width height: root.itemHeight displayName: qsTr("Favorite") } Item { width: parent.width height: favoriteView.height DropArea { z: -10 anchors.fill: parent property int addedIndex: -1 property string addedId: "" function reset() { addedId = ""; var nullIndex = favoriteView.indexAtNullItem(); if (nullIndex > -1 && nullIndex < favoriteView.count) { favoriteView.viewModel.items.remove(nullIndex); } } onEntered: { if (drag.getDataAsString("folder") === "true" || drag.getDataAsString("favorite") === "false") { // 从应用组内部拖拽 || 从左侧列表添加到收藏 // 已经触发从左侧拖动到收藏区域的事件,不需要再次添加空白item let id = ""; if (drag.getDataAsString("url").startsWith("app://")) { id = drag.getDataAsString("url").slice(6); } if (addedId === id || id === "") { return; } addedId = id; addedIndex = favoriteView.count; visualModel.items.insert(addedIndex, {"id":"", "icon":"", "name": ""}); } else if (drag.getDataAsString("favorite") === "true") { drag.accepted = false; } else { // 收藏插件内拖拽 favoriteView.dragTypeIsMerge = false; } } onPositionChanged: { if ((drag.getDataAsString("folder") === "true" || drag.getDataAsString("favorite") === "false") && addedIndex > -1) { var dragIndex = favoriteView.indexAt(drag.x, drag.y); if (dragIndex < 0) { return; } visualModel.items.move(addedIndex, dragIndex); addedIndex = dragIndex; } } onDropped: { // folder需要在favorite之前判断,因为应用组内的应用favorite都为true if (drop.getDataAsString("folder") === "true") { reset(); favoriteModel.addAppFromFolder(drop.getDataAsString("url").slice(6), addedIndex); folderLoader.isFolderOpened = false; } else if (drop.getDataAsString("favorite") === "false") { reset(); favoriteModel.addAppToFavorites(drop.getDataAsString("url").slice(6), addedIndex); } } onExited: reset() } GridView { id: favoriteView anchors.horizontalCenter: parent.horizontalCenter width: root.cellWidth * root.column height: contentHeight property string mergeToAppId: "" property bool isMergeToFolder: false property bool dragTypeIsMerge: false property int exchangedStartIndex: 0 property alias viewModel: visualModel property bool isFavorite: true cellWidth: root.cellWidth cellHeight: root.cellHeight interactive: false currentIndex: favoriteItem.itemCurrentIndex function indexAtNullItem() { var nullItemIndex; for (var i = 0; i < favoriteView.count; i ++) { if (favoriteView.itemAtIndex(i).id === "") { nullItemIndex = i; return nullItemIndex; } } return -1; } onCurrentItemChanged: { if (!currentItem) return; var itemYInListView = currentItem.y + root.y + favoriteItem.y; var visibleTop = root.contentY; var visibleBottom = visibleTop + root.height; if (itemYInListView < visibleTop) { var targetY = itemYInListView; if (currentItem.y === 0) targetY -= root.itemHeight; root.contentY = targetY; } else if (itemYInListView + currentItem.height > visibleBottom) { targetY = itemYInListView + currentItem.height - root.height; root.contentY = targetY; } } model: DelegateModel { id: visualModel model: favoriteModel delegate: Item { id: container width: favoriteView.cellWidth height: favoriteView.cellHeight property var id: model.id focus: GridView.isCurrentItem onFocusChanged: { if (focus) { root.currentId = id; } } Extension.FavoriteDelegate { anchors.fill: parent anchors.margins: 12 isFullScreen: true borderColor: Platform.GlobalTheme.highlightActive border.width: (container.GridView.isCurrentItem && root.currentNum === -1) ? 2 : 0 visualIndex: container.DelegateModel.itemsIndex delegateLayout.anchors.topMargin: 16 delegateLayout.anchors.bottomMargin: 16 delegateLayout.spacing: 8 mergePrompt.anchors.topMargin: 10 mergePrompt.width: width*0.675 // mergePrompt.width: 108 mergePrompt.radius: 32 Component.onCompleted: contentShowFinished.connect(resetOpacity) Component.onDestruction: contentShowFinished.disconnect(resetOpacity) } //编辑模式标志 Extension.EditModeFlag { id: tag isFavorited: true anchors.top: parent.top anchors.topMargin: 10 anchors.right: parent.right anchors.rightMargin: 28 onClicked: { visualModel.model.removeAppFromFavorites(id); } } } } displaced: Transition { NumberAnimation { properties: "x,y"; easing.type: Easing.InOutQuad; duration: 200 } } remove: Transition { NumberAnimation { properties: "opacity"; to: 0; easing.type: Easing.InOutQuad; duration: 200 } } } } } } } ukui-menu/qml/AppUI/SearchInputBar.qml0000664000175000017500000001177215160463365016574 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.12 import org.ukui.menu.core 1.0 import AppControls2 1.0 as AppControls2 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems UkuiItems.DtThemeBackground { property Item changeFocusTarget property alias text: textInput.text; useStyleTransparency: false backgroundColor: Platform.GlobalTheme.kGrayAlpha12 alpha: textInput.activeFocus ? 0 : 1 border.width: 1 borderColor: textInput.activeFocus ? Platform.GlobalTheme.kBrandNormal : Platform.GlobalTheme.kGrayAlpha1 Component.onCompleted: mainWindow.visibleChanged.connect(clear) Component.onDestruction: mainWindow.visibleChanged.disconnect(clear) function textInputFocus() { textInput.forceActiveFocus(); } function clear() { textInput.clear(); } Item { id: defaultSearch width: searchIcon.width + defaultText.contentWidth; height: parent.height anchors.verticalCenter: parent.verticalCenter x: 0 Behavior on x { NumberAnimation { duration: 100; easing.type: Easing.InOutQuad } } Item { id: searchIcon width: 32; height: 32 anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left anchors.leftMargin: (parent.height - height) / 2 UkuiItems.Icon { anchors.centerIn: parent width: parent.height / 2; height: width source: "search-symbolic" mode: UkuiItems.Icon.AutoHighlight } } UkuiItems.DtThemeText { id: defaultText anchors.verticalCenter: parent.verticalCenter anchors.left: searchIcon.right text: qsTr("Search App") visible: !textInput.activeFocus && textInput.text.length === 0 textColor: Platform.GlobalTheme.kGrayAlpha8 verticalAlignment: TextInput.AlignVCenter } } TextInput { id: textInput clip: true anchors.right: clearButton.left width: parent.width - searchIcon.width - clearButton.width - searchIcon.anchors.leftMargin - clearButton.anchors.rightMargin height: parent.height selectByMouse: true verticalAlignment: TextInput.AlignVCenter font.pointSize: defaultText.font.pointSize font.family: defaultText.font.family focus: true activeFocusOnTab: false color : Platform.GlobalTheme.kFontPrimary.pureColor; selectionColor : Platform.GlobalTheme.highlightActive.pureColor; function changeFocusToListView() { if (!mainWindow.isFullScreen) { normalUI.focus = true; changeFocusTarget.focus = true; appPage.content.resetFocus(); } } Keys.onDownPressed: changeFocusToListView() Keys.onReturnPressed: changeFocusToListView() onActiveFocusChanged: { if (activeFocus) { defaultSearch.x = 0; } else { defaultSearch.x = text.length === 0 ? (parent.width - defaultSearch.width - searchIcon.width/2) / 2 : 0; } } } UkuiItems.DtThemeButton { id: clearButton anchors.right: parent.right anchors.rightMargin: (parent.height - height) / 2 anchors.verticalCenter: parent.verticalCenter width: 32; height: width background.radius: width / 2 visible: textInput.length !== 0 activeFocusOnTab: false icon.source: "edit-clear-symbolic" icon.mode: UkuiItems.Icon.AutoHighlight onClicked: { textInput.clear(); //失去焦点后,若输入框不为空,按下清除按钮后defaultSearch应该移动位置 if (textInput.text.length === 0 && !textInput.activeFocus) { defaultSearch.x = (parent.width - defaultSearch.width - searchIcon.width/2) / 2; } } } MouseArea { anchors.left: textInput.left anchors.verticalCenter: parent.verticalCenter width: clearButton.visible ? textInput.width : textInput.width + clearButton.width height: parent.height cursorShape: Qt.IBeamCursor enabled: false } } ukui-menu/qml/AppUI/AppListView.qml0000664000175000017500000000467515160463365016135 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 import AppControls2 1.0 as AppControls2 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.menu.core 1.0 MouseArea { id: root readonly property alias view: listView property alias model: listView.model property alias delegate: listView.delegate property alias listFocus: listView.focus property int itemHeight: 40 hoverEnabled: true clip: true UkuiItems.DtThemeBackground { anchors.top: parent.top width: parent.width height: 1 useStyleTransparency: false backgroundColor: Platform.GlobalTheme.kLineNormal visible: listView.contentY > 0 } RowLayout { anchors.fill: parent spacing: 0 anchors.leftMargin: 4 ListView { id: listView cacheBuffer: count * root.itemHeight spacing: 4 Layout.fillHeight: true Layout.fillWidth: true highlightMoveDuration: 0 boundsBehavior: Flickable.StopAtBounds ScrollBar.vertical: listViewScrollBar keyNavigationWraps: true // 焦点切换后,listView按键导航重新开始 onCountChanged: resetCurrentIndex() function resetCurrentIndex() { currentIndex = 0 } Component.onCompleted: mainWindow.visibleChanged.connect(resetCurrentIndex) Component.onDestruction: mainWindow.visibleChanged.disconnect(resetCurrentIndex) } AppControls2.ScrollBar { id: listViewScrollBar Layout.fillHeight: true Layout.preferredWidth: 14 visual: root.containsMouse } } } ukui-menu/qml/AppUI/FullScreenFooter.qml0000664000175000017500000000513515160463353017134 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.menu.core 1.0 import org.ukui.menu.utils 1.0 Row { Layout.fillWidth: true Layout.preferredHeight: 48 layoutDirection: Qt.RightToLeft UkuiItems.DtThemeBackground { width: 48; height: width useStyleTransparency: false backgroundColor: Platform.GlobalTheme.lightActive alpha: powerButtonArea.containsPress ? 0.25 : powerButtonArea.containsMouse ? 0.12 : 0 radius: height / 2 borderColor: Platform.GlobalTheme.highlightActive border.width: powerButtonArea.activeFocus ? 2 : 0 PowerButton { id: powerButtonBase } UkuiItems.Icon { anchors.centerIn: parent width: Math.floor(parent.width / 2) height: width mode: UkuiItems.Icon.Highlight source: powerButtonBase.icon } MouseArea { id: powerButtonArea anchors.fill: parent hoverEnabled: true ToolTip.delay: 500 ToolTip.visible: containsMouse ToolTip.text: powerButtonBase.toolTip acceptedButtons: Qt.LeftButton | Qt.RightButton property int spacingFromMenu: 8 activeFocusOnTab: true onClicked: { var buttonPosition = powerButtonArea.mapToGlobal(width, height); powerButtonBase.clicked(mouse.button === Qt.LeftButton, buttonPosition.x - width - spacingFromMenu, buttonPosition.y + spacingFromMenu, mainWindow.isFullScreen); } Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { powerButtonBase.clicked(true, 0, 0, mainWindow.isFullScreen); } } } } } ukui-menu/qml/AppUI/Sidebar.qml0000664000175000017500000002706215160463365015272 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQml 2.12 import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5 import org.ukui.menu.core 1.0 import org.ukui.menu.utils 1.0 import org.ukui.menu.extension 1.0 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems Item { property alias foldButton: fullScreenButton property Item focusToAppPage ColumnLayout { anchors.fill: parent anchors.topMargin: 12 anchors.bottomMargin: 12 spacing: 4 UkuiItems.DtThemeButton { id: fullScreenButton visible: !isLiteMode background.backgroundColor: containsPress ? Platform.GlobalTheme.kContainAlphaClick : containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.kContainGeneralAlphaNormal Layout.preferredWidth: 36 Layout.preferredHeight: 36 Layout.alignment: Qt.AlignHCenter KeyNavigation.tab: userInfoButton Accessible.role: Accessible.Button Accessible.description: "This is a state swtich button on ukui-menu" Accessible.name: mainWindow.isFullScreen ? "contract--button" : "expand--button" UkuiItems.Tooltip { anchors.fill: parent mainText: mainWindow.isFullScreen ? qsTr("Contract") : qsTr("Expand") posFollowCursor: true margin: 6 } onClicked: { if (mainWindow.isFullScreen) { root.currentSearchText = searchInputBar.text; mainWindow.exitFullScreen(); } else { root.currentSearchText = appPage.search.searchInputBar.text; mainWindow.isFullScreen = true; } } Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return || event.key === Qt.Key_Space) { root.focusOnFoldButton = true; if (mainWindow.isFullScreen) { root.currentSearchText = searchInputBar.text; mainWindow.exitFullScreen(); } else { root.currentSearchText = appPage.search.searchInputBar.text; mainWindow.isFullScreen = true; } } } background.radius: Platform.GlobalTheme.kRadiusMin icon.mode: UkuiItems.Icon.AutoHighlight icon.source: mainWindow.isFullScreen ? "view-restore-symbolic" : "view-fullscreen-symbolic" Component.onCompleted: { if (root.focusOnFoldButton) { root.focusOnFoldButton = false; forceActiveFocus(); } } } Item { id: blankSpace Layout.fillHeight: true Layout.fillWidth: true SidebarButtonUtils { id: totalUtils } } MouseArea { id: userInfoButton property int spacingFromMenu: 16 Layout.preferredWidth: 36 Layout.preferredHeight: 36 Layout.alignment: Qt.AlignHCenter acceptedButtons: Qt.LeftButton | Qt.RightButton hoverEnabled: true Accessible.role: Accessible.Button Accessible.description: "This is a userInfo button on ukui-menu" Accessible.name: "userInfo-button" KeyNavigation.down: computerButton KeyNavigation.tab: focusToAppPage KeyNavigation.backtab: fullScreenButton UkuiItems.DtThemeBackground { anchors.fill: parent border.width: parent.activeFocus ? 2 : 0 backgroundColor: parent.containsPress ? Platform.GlobalTheme.kContainAlphaClick : parent.containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.kContainGeneralAlphaNormal borderColor: Platform.GlobalTheme.highlightActive useStyleTransparency: false radius: Platform.GlobalTheme.kRadiusMin UkuiItems.Icon { anchors.centerIn: parent width: 24 height: 24 mode: UkuiItems.Icon.AutoHighlight forceRound: true source: totalUtils.iconFile } } UkuiItems.Tooltip { anchors.fill: parent mainText: totalUtils.realName posFollowCursor: true margin: 6 } onClicked: { if (mouse.button === Qt.LeftButton) { totalUtils.openUserCenter() } else { var buttonPosition = mapToGlobal(width, height); totalUtils.openUsersInfoMenu(buttonPosition.x + spacingFromMenu, buttonPosition.y + spacingFromMenu, mainWindow.isFullScreen); } } Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { totalUtils.openUserCenter(); } else if (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab) { event.accepted = false; } } } UkuiItems.DtThemeButton { id: computerButton background.backgroundColor: containsPress ? Platform.GlobalTheme.kContainAlphaClick : containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.windowTextActive background.alpha: containsPress ? 1 : containsMouse ? 1 : 0.0 Layout.preferredWidth: 36 Layout.preferredHeight: 36 Layout.alignment: Qt.AlignHCenter KeyNavigation.down: setButton KeyNavigation.tab: focusToAppPage KeyNavigation.backtab: fullScreenButton Accessible.role: Accessible.Button Accessible.description: "This is a computer button on ukui-menu" Accessible.name: "computer-button" UkuiItems.Tooltip { anchors.fill: parent mainText: qsTr("Computer") posFollowCursor: true margin: 6 } onClicked: { totalUtils.openPeonyComputer(); } Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { totalUtils.openPeonyComputer(); } else if (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab) { event.accepted = false; } } background.radius: Platform.GlobalTheme.kRadiusMin icon.mode: UkuiItems.Icon.AutoHighlight icon.source: "computer-symbolic" } UkuiItems.DtThemeButton { id: setButton background.backgroundColor: containsPress ? Platform.GlobalTheme.kContainAlphaClick : containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.windowTextActive background.alpha: containsPress ? 1 : containsMouse ? 1 : 0.0 Layout.preferredWidth: 36 Layout.preferredHeight: 36 Layout.alignment: Qt.AlignHCenter KeyNavigation.down: powerButton KeyNavigation.tab: focusToAppPage KeyNavigation.backtab: fullScreenButton Accessible.role: Accessible.Button Accessible.description: "This is a control center button on ukui-menu" Accessible.name: "controlCenter-button" UkuiItems.Tooltip { anchors.fill: parent mainText: qsTr("Settings") posFollowCursor: true margin: 6 } onClicked: { totalUtils.openControlCenter(); } Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { totalUtils.openControlCenter(); } else if (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab) { event.accepted = false; } } background.radius: Platform.GlobalTheme.kRadiusMin icon.mode: UkuiItems.Icon.AutoHighlight icon.source: "applications-system-symbolic" } UkuiItems.DtThemeButton { id: powerButton background.backgroundColor: containsPress ? Platform.GlobalTheme.kContainAlphaClick : containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.windowTextActive background.alpha: containsPress ? 1 : containsMouse ? 1 : 0.0 Layout.preferredWidth: 36 Layout.preferredHeight: 36 Layout.alignment: Qt.AlignHCenter KeyNavigation.tab: focusToAppPage KeyNavigation.backtab: fullScreenButton Accessible.role: Accessible.Button Accessible.description: "This is a power button on ukui-menu" Accessible.name: "power-button" UkuiItems.Tooltip { anchors.fill: parent mainText: powerButtonBase.toolTip posFollowCursor: true margin: 6 } PowerButton { id: powerButtonBase } acceptedButtons: Qt.LeftButton | Qt.RightButton property int spacingFromMenu: 16 Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { powerButtonBase.clicked(true, 0, 0, mainWindow.isFullScreen); } else if (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab) { event.accepted = false; } } onClicked: { var buttonPosition = mapToGlobal(width, height); powerButtonBase.clicked(mouse.button === Qt.LeftButton, buttonPosition.x + spacingFromMenu, buttonPosition.y + spacingFromMenu, mainWindow.isFullScreen); } background.radius: Platform.GlobalTheme.kRadiusMin icon.mode: UkuiItems.Icon.AutoHighlight icon.source: powerButtonBase.icon } } } ukui-menu/qml/AppUI/FullScreenUI.qml0000664000175000017500000003060015160463365016211 0ustar fengfengimport QtQuick 2.15 import QtQuick.Layouts 1.12 import QtQml.Models 2.12 import org.ukui.menu.core 1.0 import "../extensions" as Extension import AppControls2 1.0 as AppControls2 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform FocusScope { id: fullScreenUI Component.onCompleted: { forceFocus(); mainWindow.visibleChanged.connect(forceFocus); } Component.onDestruction: { mainWindow.visibleChanged.disconnect(forceFocus); } function forceFocus() { if (mainWindow.visible) { searchInputBase.forceActiveFocus(); } } function keyPressed(event) { if ((0x2f < event.key && event.key < 0x3a)||(0x40 < event.key && event.key < 0x5b)) { focus = true; searchInputBar.text = event.text; searchInputBase.forceActiveFocus(); } } Item { anchors.fill: parent MouseArea { anchors.fill: parent onClicked: { if (mainWindow.editMode) { mainWindow.editMode = false; } else { if (mainContainer.visible) { //执行全屏退出操作 mainWindow.hide(); } else { folderLoader.isFolderOpened = false; } } } } Extension.FolderGridView { id: folderLoader anchors.fill: parent isFullScreen: true folderModel: FolderModel Component.onCompleted: fullScreenAppList.openFolderSignal.connect(initFolder) Component.onDestruction: fullScreenAppList.openFolderSignal.disconnect(initFolder) } Item { id: mainContainer anchors.fill: parent z: 10 state: !fullScreenAppList.isContentShow ? "contentShow" : "contentHidden" states: [ State { name: "contentHidden" PropertyChanges { target: mainContainer; opacity: 0; scale: 0.95 } }, State { name: "contentShow" PropertyChanges { target: mainContainer; opacity: 1; scale: 1 } } ] transitions: [ Transition { to:"contentHidden" SequentialAnimation { PropertyAnimation { properties: "opacity, scale"; duration: 300; easing.type: Easing.InOutCubic } ScriptAction { script: mainContainer.visible = false } } }, Transition { to: "contentShow" SequentialAnimation { ScriptAction { script: mainContainer.visible = true } PropertyAnimation { properties: "opacity, scale"; duration: 300; easing.type: Easing.InOutCubic } } } ] // 两行三列 GridLayout { anchors.fill: parent // anchors.margins: 36 anchors.leftMargin: 36 anchors.topMargin: 36 anchors.rightMargin: 36 anchors.bottomMargin: 5 rowSpacing: 5 columnSpacing: 5 rows: 2 columns: 3 // 应用列表模式选择按钮: [row: 0, column: 0] [rowspan: 2, columnSpan: 1] Item { id: actionsItemBase Layout.row: 0 Layout.column: 0 Layout.preferredWidth: Math.max(actionsItem.width, labelsItem.width) Layout.preferredHeight: 36 activeFocusOnTab: true KeyNavigation.tab: appLabelPage.count > 0 ? appLabelBase : fullScreenAppList onFocusChanged: { if (focus) { actionsItem.view.forceActiveFocus(); } } // TODO: 设计最小保持宽度 AppListActions { id: actionsItem anchors.centerIn: parent height: parent.height actions: AppPageBackend.appModel.header.actions visible: count > 0 } } // 搜索框: [row: 0, column: 1] Item { id: searchInputBase property bool showSearchState: searchInputBar.text !== "" Layout.row: 0 Layout.column: 1 Layout.fillWidth: true Layout.preferredHeight: 40 activeFocusOnTab: true KeyNavigation.tab: showSearchState ? fullScreenAppList : actionsItemBase onFocusChanged: { if (focus) searchInputBar.textInputFocus(); } Accessible.role: Accessible.Button Accessible.description: "This is a search bar on ukui-menu" Accessible.name: "searchBar-button" SearchInputBar { id: searchInputBar width: 372; height: 36 anchors.centerIn: parent radius: Platform.GlobalTheme.kRadiusNormal visible: opacity onTextChanged: { if (text === "") { AppPageBackend.group = PluginGroup.Display; } else { AppPageBackend.group = PluginGroup.Search; AppPageBackend.startSearch(text); } } Component.onCompleted: { text = root.currentSearchText; } } } // 右侧按钮区域: [row: 0, column: 2] [rowspan: 2, columnSpan: 1] Sidebar { id: sidebar Layout.row: 0 Layout.column: 2 Layout.rowSpan: 2 Layout.columnSpan: 1 Layout.preferredWidth: 60 Layout.fillHeight: true activeFocusOnTab: true focusToAppPage: searchInputBase onFocusChanged: { if (focus) sidebarAreaButton.forceActiveFocus(); } } // 左侧标签列表: [row: 1, column: 0] Item { id: appLabelBase Layout.row: 1 Layout.column: 0 Layout.preferredWidth: Math.max(actionsItem.width, labelsItem.width) Layout.fillHeight: true activeFocusOnTab: true KeyNavigation.tab: fullScreenAppList onFocusChanged: { if (focus && appLabelPage.count > 0) appLabelPage.forceActiveFocus(); } Item { id: labelsItem anchors.centerIn: parent width: 120 height: parent.height clip: true AppLabelPage { id: appLabelPage anchors.centerIn: parent height: (contentHeight > parent.height) ? parent.height : contentHeight width: parent.width labelBottle: AppPageBackend.appModel.labelBottle labelColum: 1 cellHeight: 34 // TODO: 潜在的优化点,尝试组合widget的model和应用model model: { if ((labelBottle !== null) && (labelBottle.visible)) { let labelItems = [], i = 0, item = null; let widgetInfos = fullScreenAppList.headerItem.widgetInfos; for (i = 0; i < widgetInfos.length; ++i) { item = widgetInfos[i]; labelItems.push({label: item.label, type: item.type, display: item.display}); } let labels = labelBottle.labels; for (i = 0; i < labels.length; ++i) { item = labels[i]; labelItems.push({label: item.label, type: item.type, display: item.display}); } return labelItems; } return []; } boundsBehavior: Flickable.StopAtBounds interactive: height >= parent.height highlightMoveDuration: 300 highlight: UkuiItems.DtThemeBackground { width: appLabelPage.cellWidth height: appLabelPage.cellHeight useStyleTransparency: false radius: Platform.GlobalTheme.kRadiusMin backgroundColor: Platform.GlobalTheme.kContainAlphaClick } onLabelSelected: (label) => { fullScreenAppList.positionLabel(label); } } } } // 应用列表: [row: 1, column: 1] FullScreenAppList { id: fullScreenAppList Layout.row: 1 Layout.column: 1 Layout.fillWidth: true Layout.fillHeight: true activeFocusOnTab: true KeyNavigation.tab: sidebar.foldButton KeyNavigation.backtab: searchInputBase.showSearchState ? searchInputBase : appLabelPage.count > 0 ? appLabelBase : actionsItemBase sourceModel: AppPageBackend.appModel isContentShow: folderLoader.isFolderOpened isSearching: searchInputBar.text !== "" onContentHeightChanged: { cacheBuffer = contentHeight >= 0 ? contentHeight : 0; } function positionLabel(label) { // 如果是Widget那么直接滚动到最顶上 // console.log("=positionLabel=", label) if (headerItem.widgets.includes(label)) { positionViewAtBeginning(); } else { let index = model.findLabelIndex(label); if (index >= 0) { positionViewAtIndex(index, ListView.Beginning) } } } onContentYChanged: { // 向下偏移200, 计算偏移后在那个item里 let index = indexAt(10, contentY + 200); if (contentY < 0) { appLabelPage.currentIndex = 0; } else if (index >= 0) { appLabelPage.currentIndex = index + headerItem.widgetCount; } } Component.onCompleted: { Qt.callLater(()=>positionViewAtBeginning()); folderLoader.turnPageFinished.connect(contentShowFinished) } Component.onDestruction: folderLoader.turnPageFinished.disconnect(contentShowFinished) } } } } } ukui-menu/qml/AppUI/qmldir0000664000175000017500000000120115160463365014404 0ustar fengfengmodule AppUI AppPage 1.0 AppPage.qml AppList 1.0 AppList.qml Sidebar 1.0 Sidebar.qml WidgetPage 1.0 WidgetPage.qml NormalUI 1.0 NormalUI.qml FullScreenUI 1.0 FullScreenUI.qml AppListHeader 1.0 AppListHeader.qml AppListView 1.0 AppListView.qml AppListActions 1.0 AppListActions.qml SearchInputBar 1.0 SearchInputBar.qml PluginSelectButton 1.0 PluginSelectButton.qml FullScreenHeader 1.0 FullScreenHeader.qml FullScreenContent 1.0 FullScreenContent.qml FullScreenFooter 1.0 FullScreenFooter.qml FullScreenAppList 1.0 FullScreenAppList.qml FullScreenAppItem 1.0 FullScreenAppItem.qml AppLabelPage 1.0 AppLabelPage.qml EditText 1.0 EditText.qml ukui-menu/qml/AppUI/AppListActions.qml0000664000175000017500000001016715160463365016614 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQuick.Controls 2.12 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform UkuiItems.DtThemeBackground { id: root property alias count: actionsRepeater.count property alias actions: actionsRepeater.model property alias spacing: mainLayout.spacing property alias view: actionsRepeater property int maring: 3 width: count > 0 ? (mainLayout.width + maring*2) : 0 radius: Platform.GlobalTheme.kRadiusNormal alpha: 0.03 useStyleTransparency: false backgroundColor: Platform.GlobalTheme.windowTextActive border.width: 1 borderAlpha: 0.06 borderColor: Platform.GlobalTheme.windowTextActive Row { id: mainLayout width: childrenRect.width height: parent.height - maring*2 x: root.maring; y: root.maring spacing: 2 Repeater { id: actionsRepeater property int selectIndex: -1 onFocusChanged: { selectIndex = focus ? 0 : -1 } Keys.onPressed: { switch (event.key) { case Qt.Key_Left: if (selectIndex > 0) { selectIndex--; event.accepted = true; } break; case Qt.Key_Right: if (selectIndex < actionsRepeater.count - 1) { selectIndex++; event.accepted = true; } break; case Qt.Key_Enter: case Qt.Key_Return: actionsRepeater.model[selectIndex].trigger(); event.accepted = true; break; default: break; } } delegate: Component { UkuiItems.DtThemeButton { width: height height: parent.height background.border.width: index === actionsRepeater.selectIndex ? 2 : 0 Accessible.role: Accessible.Button Accessible.description: "This is a categoriy switch button on ukui-menu" Accessible.name: modelData.toolTip UkuiItems.Tooltip { anchors.fill: parent mainText: modelData.toolTip posFollowCursor: true margin: 6 visible: modelData.toolTip !== "" } background.radius: Platform.GlobalTheme.kRadiusNormal background.backgroundColor: modelData.checked ? Platform.GlobalTheme.highlightActive : containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.kContainGeneralAlphaNormal icon.source: modelData.icon icon.mode: modelData.checked ? UkuiItems.Icon.Highlight : UkuiItems.Icon.AutoHighlight icon.width: background.width / 2 + 1 icon.height: background.height / 2 + 1 onClicked: { // 执行action modelData.trigger(); } } } } } } ukui-menu/qml/AppUI/AppList.qml0000664000175000017500000001442715160463365015276 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.15 import QtQml 2.12 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.12 import AppControls2 1.0 as AppControls2 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.menu.core 1.0 AppListView { id: appListView signal labelItemClicked(string displayName) property bool isTouch: false function resetListFocus() { listFocus = true; } // 在listview区域,鼠标进出和移动事件都会清空原有的按键焦点 // 鼠标不移动,原有的鼠标悬浮三态会保留 onContainsMouseChanged: { listFocus = false } onPositionChanged: { listFocus = false } Item {id: dragItem} delegate: Component { Loader { focus: true width: ListView.view ? ListView.view.width : 0 height: appListView.itemHeight property int index: model.index property int type: model.type property string id: model.id property string name: model.name property string icon: model.icon property string comment: model.comment // label tooltip property string desktopName: model.desktopName property bool favorite: model.favorite sourceComponent: Component { Item { id: appItemBase Accessible.role: Accessible.Button Accessible.description: "This is an app button on ukui-menu" Accessible.name: name AppControls2.AppItem { id: appItem focus: true width: appListView.view ? appListView.view.width : 0 height: appListView.itemHeight anchors.centerIn: parent acceptedButtons: Qt.LeftButton | Qt.RightButton isRecentInstalled: model.recentInstall Drag.hotSpot.x: 48 / 2 Drag.hotSpot.y: 48 / 2 Drag.supportedActions: Qt.CopyAction Drag.dragType: Drag.Automatic Drag.mimeData: { "source": "ukui-menu", "url": "app://" + id, "favorite": favorite } drag.target: appListView.isTouch ? null : dragItem drag.onActiveChanged: { Drag.active = drag.active; } Drag.onDragFinished: function (dropAction) { appItem.Drag.active = false; } onClicked: { console.log("ICON_CLICK!", "DESKTOPFILE:", id); if (mouse.button === Qt.RightButton) { appListView.model.openMenu(index, MenuInfo.AppList); return; } if (mouse.button === Qt.LeftButton) { appManager.launchApp(id); return; } } onPressed: { normalUI.focus = false; // 在启动拖拽前进行截图,避免开始拖拽后没有图标 grabImage(); if (mouse.source === Qt.MouseEventSynthesizedBySystem || mouse.source === Qt.MouseEventSynthesizedByQt) { appListView.isTouch = true; } else { appListView.isTouch = false; } } onPressAndHold: { // 触摸长按时启动拖拽 if (isTouch) { Drag.active = true; } } } function grabImage() { var icon = mouseGrabImage.createObject(appItem, {width: 48, height: 48}) if (icon !== null) { icon.proxy.appId = model.desktopName; icon.source = model.icon; icon.grabToImage(function(result) { appItem.Drag.imageSource = result.url icon.destroy(); }); icon.visible = false; } } Component { id: mouseGrabImage UkuiItems.Icon { } } Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { appManager.launchApp(id); } } } } } } view.section.property: "group" view.section.delegate: Component { AppControls2.LabelItem { width: ListView.view.width height: appListView.itemHeight focus: true displayName: section onClicked: labelItemClicked(displayName); Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { labelItemClicked(displayName); } } } } } ukui-menu/qml/AppUI/FullScreenAppItem.qml0000664000175000017500000000671015160463365017240 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: hxf * */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform MouseArea { id: root property alias icon: appIcon property alias text: appName property alias background: styleBackground property bool isRecentInstalled: false property bool hasFocus: false hoverEnabled: true activeFocusOnTab: true UkuiItems.Tooltip { anchors.fill: parent mainText: text.text posFollowCursor: true active: text.truncated } UkuiItems.DtThemeBackground { id: styleBackground anchors.fill: parent radius: Platform.GlobalTheme.kRadiusNormal useStyleTransparency: false backgroundColor: root.containsPress ? Platform.GlobalTheme.kContainAlphaClick : root.containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.kContainGeneralAlphaNormal border.width: root.hasFocus ? 2 : 0 borderColor: Platform.GlobalTheme.highlightActive ColumnLayout { anchors.fill: parent anchors.topMargin: 16 anchors.bottomMargin: 16 spacing: 8 UkuiItems.Icon { id: appIcon Layout.preferredWidth: styleBackground.width * 0.6 Layout.preferredHeight: width Layout.alignment: Qt.AlignHCenter } RowLayout { Layout.fillWidth: true Layout.preferredHeight: appName.contentHeight Layout.maximumHeight: appName.contentHeight Layout.alignment: Qt.AlignHCenter UkuiItems.DtThemeBackground { id: tagPoint property int tagSize: isRecentInstalled ? 8 : 0 Layout.alignment: Qt.AlignVCenter visible: isRecentInstalled Layout.preferredHeight: tagSize Layout.preferredWidth: tagSize radius: width / 2 useStyleTransparency: false backgroundColor: Platform.GlobalTheme.highlightActive } UkuiItems.DtThemeText { id: appName Layout.maximumWidth: styleBackground.width - tagPoint.width Layout.preferredHeight: contentHeight Layout.maximumHeight: contentHeight elide: Text.ElideRight textColor: Platform.GlobalTheme.textActive horizontalAlignment: Text.AlignHCenter } } } } } ukui-menu/qml/AppUI/AppPageContent.qml0000664000175000017500000001503315160463365016564 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5 import AppControls2 1.0 as AppControls2 import org.ukui.menu.core 1.0 Item { property bool isAppListShow: appList.visible property bool isHeaderShow property Item focusToWidgetPage property Item focusToSearchInput function resetFocus() { if (appList.visible) { appList.resetListFocus(); } else { selectionPage.focus = true; labelPage.focus = true; } } onFocusChanged: { if (focus) { if (isHeaderShow) { appListHeader.forceActiveFocus(); } else { appList.forceActiveFocus(); } } } ColumnLayout { anchors.fill: parent spacing: 5 AppListHeader { id: appListHeader Layout.fillWidth: true Layout.preferredHeight: 48 header: AppPageBackend.appModel.header KeyNavigation.tab: appList } Item { Layout.fillWidth: true Layout.fillHeight: true AppList { id: appList anchors.fill: parent visible: !selectionPage.visible model: AppPageBackend.appModel KeyNavigation.tab: focusToWidgetPage KeyNavigation.backtab: isHeaderShow ? appListHeader : focusToSearchInput onFocusChanged: { if (focus) resetFocus(); } view.onContentYChanged: { if (view.contentY <= 0) { appListHeader.title = ""; } else { appListHeader.title = view.currentSection; } } function positionLabel(label) { let index = model.findLabelIndex(label); if (index >= 0) { view.positionViewAtIndex(index, ListView.Beginning) } } onLabelItemClicked: { labelPage.labelBottle = AppPageBackend.appModel.labelBottle; let labelIndex = labelPage.labelBottle.labels.findIndex(item => item.label === displayName); if (labelIndex >= 0) { labelPage.currentIndex = labelIndex; } selectionPage.state = "viewShowed"; } } MouseArea { property int stateDuration: 300 id: selectionPage anchors.fill: parent visible: false onClicked: state = "viewHid"; state: "viewHid" states: [ State { name: "viewShowed" changes: [ PropertyChanges {target: selectionPage; scale: 1; opacity: 1; focus: true } ] }, State { name: "viewHid" changes: [ PropertyChanges {target: selectionPage; scale: 1.5; opacity: 0; focus: false } ] } ] transitions: [ Transition { from: "*"; to: "viewShowed" SequentialAnimation { ScriptAction { script: selectionPage.visible = true; } NumberAnimation { properties: "scale,opacity"; easing.type: Easing.InOutCubic; duration: selectionPage.stateDuration} ScriptAction { script: labelPage.focus = true; } } }, Transition { from: "*"; to: "viewHid" SequentialAnimation { NumberAnimation { properties: "scale,opacity"; easing.type: Easing.InOutCubic; duration: selectionPage.stateDuration} ScriptAction { script: { selectionPage.visible = false; labelPage.labelBottle = null; labelPage.focus = false; appList.focus = false; } } } } ] AppLabelPage { id: labelPage anchors.centerIn: parent interactive: height > parent.height onLabelSelected: (label) => { appList.positionLabel(label); selectionPage.state = "viewHid"; } onModelChanged: { currentIndex = -1; } onShowLabelPageChanged: { if (showLabelPage) { selectionPage.stateDuration = 300; return; } else { Qt.callLater(() => { selectionPage.stateDuration = 0; selectionPage.state = "viewHid"; }); } } } function hidePage() { state = "viewHid"; visible = false; } Component.onCompleted: { mainWindow.visibleChanged.connect(hidePage) } Component.onDestruction: { mainWindow.visibleChanged.disconnect(hidePage) } } } } } ukui-menu/qml/AppUI/PluginSelectButton.qml0000664000175000017500000000746115160463353017511 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 import org.ukui.menu.core 1.0 import org.ukui.menu.utils 1.0 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems UkuiItems.DtThemeBackground { useStyleTransparency: false backgroundColor: Platform.GlobalTheme.textActive radius: childrenRect.width / 2 alpha: 0.06 RowLayout { id: pluginSortButtonRoot property int currentId: 0 anchors.fill: parent anchors.topMargin: 2 anchors.bottomMargin: 2 anchors.leftMargin: 2 anchors.rightMargin: 2 // property var model: appPageHeaderUtils.model(PluginGroup.SortMenuItem) spacing: 4 UkuiItems.DtThemeButton { id: pluginLetterSortButton Layout.fillHeight: true Layout.fillWidth: true Layout.maximumWidth: height activeFocusOnTab: true ToolTip.delay: 500 ToolTip.text: qsTr("Letter Sort") ToolTip.visible: containsMouse background.radius: width / 2 icon.mode: parent.currentId === 0 ? UkuiItems.Icon.AutoHighlight : UkuiItems.Icon.Disabled icon.source: pluginSortButtonRoot.model.getProviderIcon(0); background.backgroundColor: Platform.GlobalTheme.lightActive background.alpha: parent.currentId === 0 ? 0.7 : 0 onClicked: { pluginSortButtonRoot.model.changeProvider(0); } Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { pluginSortButtonRoot.model.changeProvider(0); } } } UkuiItems.DtThemeButton { id: pluginCateGoryButton Layout.fillHeight: true Layout.fillWidth: true Layout.maximumWidth: height activeFocusOnTab: true ToolTip.delay: 500 ToolTip.text: qsTr("Category") ToolTip.visible: containsMouse background.radius: width / 2 icon.mode: parent.currentId === 1 ? UkuiItems.Icon.AutoHighlight : UkuiItems.Icon.Disabled icon.source: pluginSortButtonRoot.model.getProviderIcon(1); background.backgroundColor: Platform.GlobalTheme.lightActive background.alpha: parent.currentId === 1 ? 0.7 : 0 onClicked: { pluginSortButtonRoot.model.changeProvider(1); } Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { pluginSortButtonRoot.model.changeProvider(1); } } } Component.onCompleted: { updateProviderId(); pluginSortButtonRoot.model.currentIndexChanged.connect(updateProviderId); } Component.onDestruction: { pluginSortButtonRoot.model.currentIndexChanged.disconnect(updateProviderId); } function updateProviderId() { currentId = pluginSortButtonRoot.model.currentProviderId(); } } } ukui-menu/qml/AppUI/SelectionPage.qml0000664000175000017500000001636715160463365016451 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQuick.Layouts 1.2 import org.ukui.menu.core 1.0 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems Item { id: root signal viewHideFinished() signal labelSelected(string labelId) function viewShowStart() { viewShow.start(); } function viewFocusEnable() { selectionArea.focus = true; } Component.onCompleted: mainWindow.visibleChanged.connect(viewHideFinished) ParallelAnimation { id: viewShow NumberAnimation { target: selectionArea; property: "scale"; easing.type: Easing.InOutCubic; from: 1.5; to: 1.0; duration: 300} NumberAnimation { target: selectionArea; property: "opacity"; easing.type: Easing.InOutCubic; from: 0; to: 1.0; duration: 300} onFinished: { viewFocusEnable(); } } ParallelAnimation { id: viewHide NumberAnimation { target: selectionArea; property: "scale"; easing.type: Easing.InOutCubic; from: 1.0; to: 1.5 ;duration: 300} NumberAnimation { target: selectionArea; property: "opacity"; easing.type: Easing.InOutCubic; from: 1.0; to: 0 ;duration: 300} onFinished: { selectionArea.focus = false; viewHideFinished(); } } MouseArea { anchors.fill: parent onClicked: viewHide.start(); } GridView { id: selectionArea anchors.centerIn: parent interactive: false property int itemWidth: 0 property int itemHeight: 40 property int column: width / itemWidth cellWidth: itemWidth; cellHeight: itemHeight onCountChanged: { if (count === 0) { viewHide.start(); } } function itemAtIndex(index) { if (index < 0 || index > count - 1) { return; } else { for (var i = 0; i <= count; i++) { var item; item = selectionArea.children[0].children[i]; if (item.index === index) { return item; } } } } function skipDisableItem() { while (itemAtIndex(currentIndex).isDisable) { if (currentIndex === count - 1) { currentIndex = 0; } else { currentIndex++; } } } onActiveFocusChanged: { if (activeFocus) { currentIndex = 0; skipDisableItem(); } } Keys.onRightPressed: { if(currentIndex === count - 1) { currentIndex = 0; } else { currentIndex++; } skipDisableItem(); } Keys.onLeftPressed: { if(currentIndex === 0) { currentIndex = count - 1; } else { currentIndex--; } while (itemAtIndex(currentIndex).isDisable) { if (currentIndex === 0) { currentIndex = count - 1; } else { currentIndex--; } } } Keys.onDownPressed: { if(currentIndex > count - 1 - column) { return; } else { currentIndex = currentIndex + column; } while (itemAtIndex(currentIndex).isDisable) { if (currentIndex > count - 1 - column) { currentIndex = currentIndex - column; } else { currentIndex = currentIndex + column; } } } Keys.onUpPressed: { if(currentIndex < column) { return; } else { currentIndex = currentIndex - column; } while (itemAtIndex(currentIndex).isDisable) { if (currentIndex < column) { currentIndex = currentIndex + column; } else { currentIndex = currentIndex - column; } } } state: count < 20 ? "functionArea" : "AlphabetArea" states: [ State { name: "functionArea" PropertyChanges { target: selectionArea; itemWidth: 80; width: itemWidth * 2; height: itemHeight * 7 } }, State { name: "AlphabetArea" PropertyChanges { target: selectionArea; itemWidth: 40; width: itemWidth * 5; height: itemHeight * 6 } } ] model: modelManager.getLabelModel() delegate: UkuiItems.DtThemeBackground { id: labelItem height: selectionArea.itemHeight; width: selectionArea.itemWidth property int index: model.index property bool isDisable: model.isDisable property string displayName: model.displayName property string id: model.id alpha: (itemMouseArea.containsPress && !model.isDisable) ? 0.82 : (itemMouseArea.containsMouse && !model.isDisable) ? 0.55 : 0.00 useStyleTransparency: false radius: 8 focus: true Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { viewHide.start(); root.labelSelected(selectionArea.itemAtIndex(selectionArea.currentIndex).id) } } states: State { when: labelItem.activeFocus PropertyChanges { target: labelItem borderColor: Platform.GlobalTheme.highlightActive border.width: 2 } } UkuiItems.DtThemeText { anchors.fill: parent text: model.displayName alpha: model.isDisable ? 0.2 : 0.9 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight } MouseArea { id: itemMouseArea anchors.fill: parent hoverEnabled: !model.isDisable onClicked: { if (!model.isDisable) { viewHide.start(); root.labelSelected(model.id); } } onEntered: { selectionArea.focus = false; } } } } } ukui-menu/qml/AppUI/AppLabelPage.qml0000664000175000017500000001371415160463365016175 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import org.ukui.menu.core 1.0 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform GridView { property var labelBottle: null property int labelColum: labelBottle === null ? 0 : labelBottle.column property int labelRow: Math.ceil(count / labelColum) property bool showLabelPage: labelBottle !== null && labelBottle.visible property int selectIndex: -1 signal labelSelected(string label) onFocusChanged: { selectIndex = focus ? 0 : -1; } // 默认为小屏幕下尺寸 width: 200 height: childrenRect.height cellWidth: width / labelColum cellHeight: 40 model: labelBottle === null ? [] : labelBottle.labels delegate: MouseArea { width: GridView.view.cellWidth height: GridView.view.cellHeight focus: true hoverEnabled: true Accessible.role: Accessible.Button Accessible.description: "This is a label button on ukui-menu" Accessible.name: modelData.label UkuiItems.Tooltip { anchors.fill: parent mainText: modelData.label posFollowCursor: true active: labelArea.showTooltip } onClicked: { GridView.view.labelSelected(modelData.label); GridView.view.currentIndex = model.index; } Keys.onPressed: (event) => { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { GridView.view.labelSelected(modelData.label); } } UkuiItems.DtThemeBackground { id: labelArea property bool showTooltip: labelText.visible ? labelText.truncated : false property bool isSelect: { if (mainWindow.isFullScreen) { return selectIndex === index; } else { return parent.GridView.isCurrentItem; } } anchors.fill: parent radius: Platform.GlobalTheme.kRadiusNormal useStyleTransparency: false backgroundColor: parent.containsPress ? Platform.GlobalTheme.kContainAlphaClick : parent.containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.kContainGeneralAlphaNormal border.width: isSelect ? 2 : 0 borderColor: Platform.GlobalTheme.highlightActive UkuiItems.DtThemeText { id: labelText anchors.fill: parent visible: modelData.type === LabelItem.Text text: modelData.display elide: Text.ElideRight verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter } UkuiItems.Icon { anchors.centerIn: parent width: parent.height/2 height: parent.height/2 visible: modelData.type === LabelItem.Icon source: modelData.type === LabelItem.Icon ? modelData.display : "" mode: UkuiItems.Icon.AutoHighlight } } } Keys.onRightPressed: { if (mainWindow.isFullScreen) return; if (currentIndex < 0) { currentIndex = 0; } else { if (currentIndex === count - 1) { currentIndex = 0; } else { currentIndex++; } } } Keys.onLeftPressed: { if (mainWindow.isFullScreen) return; if (currentIndex === 0) { currentIndex = count - 1; } else { if (currentIndex < 0) { currentIndex = 0; } else { currentIndex--; } } } Keys.onDownPressed: { if (mainWindow.isFullScreen) { if (selectIndex < count - 1) { selectIndex++; } } else { if (currentIndex < 0) { currentIndex = 0; } else { if (currentIndex + labelColum < count) { currentIndex = currentIndex + labelColum; } else { currentIndex = currentIndex - Math.floor(currentIndex / labelColum) * labelColum; } } } } Keys.onUpPressed: { if (mainWindow.isFullScreen) { if (selectIndex > 0) { selectIndex--; } } else { if (currentIndex < 0) { currentIndex = 0; } else { if (currentIndex - labelColum >= 0) { currentIndex = currentIndex - labelColum; } else { currentIndex = currentIndex + (labelRow - 1) * labelColum < count ? currentIndex + (labelRow - 1) * labelColum : currentIndex + (labelRow - 2) * labelColum; } } } } Keys.onPressed: (event) => { if (!mainWindow.isFullScreen) return; if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { labelSelected(model[selectIndex].label); } } } ukui-menu/qml/AppUI/NormalUI.qml0000664000175000017500000000400615160463365015400 0ustar fengfengimport QtQuick 2.12 import QtQuick.Layouts 1.12 import org.ukui.menu.core 1.0 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems FocusScope { id: normalUI Component.onCompleted: { forceFocus(); mainWindow.visibleChanged.connect(forceFocus); } Component.onDestruction: { mainWindow.visibleChanged.disconnect(forceFocus); } function forceFocus() { normalUI.focus = true; if (mainWindow.visible) { appPage.search.changeToSearch(""); appPage.search.forceActiveFocus(); } } function keyPressed(event) { // 任意字符键焦点切换到搜索(0-9 a-z) if ((0x2f < event.key && event.key < 0x3a)||(0x40 < event.key && event.key < 0x5b)) { focus = true; appPage.search.changeToSearch(event.text); } } RowLayout { anchors.fill: parent spacing: 0 z: 10 AppPage { id: appPage Layout.preferredWidth: 312 Layout.fillHeight: true content.focusToWidgetPage: widgetPage onFocusChanged: { if (focus) appPage.search.forceActiveFocus(); } } UkuiItems.DtThemeBackground { Layout.preferredWidth: 1 Layout.fillHeight: true backgroundColor: Platform.GlobalTheme.kDivider useStyleTransparency: false } WidgetPage { id: widgetPage Layout.fillWidth: true Layout.fillHeight: true focusToSidebar: sidebar.foldButton } UkuiItems.DtThemeBackground { Layout.preferredWidth: 1 Layout.fillHeight: true backgroundColor: Platform.GlobalTheme.kDivider useStyleTransparency: false } Sidebar { id: sidebar Layout.preferredWidth: 60 Layout.fillHeight: true focusToAppPage: appPage } } } ukui-menu/qml/AppUI/AppPageSearch.qml0000664000175000017500000000236315160463365016361 0ustar fengfengimport QtQuick 2.12 import QtQuick.Controls 2.5 import org.ukui.menu.core 1.0 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.menu.utils 1.0 Item { property Item focusToPageContent property bool inputStatus: false property alias searchInputBar: searchInputBar SearchInputBar { id: searchInputBar property string providerId: "search" anchors.fill: parent anchors.leftMargin: 16 anchors.rightMargin: 16 radius: Platform.GlobalTheme.kRadiusNormal changeFocusTarget: focusToPageContent visible: true onTextChanged: { if (text === "") { AppPageBackend.group = PluginGroup.Display; inputStatus = false; } else { AppPageBackend.group = PluginGroup.Search; AppPageBackend.startSearch(text); inputStatus = true; } } Component.onCompleted: { text = root.currentSearchText; } } function changeToSearch(keyEvent) { // if (header.content === null) { searchInputBar.text = keyEvent; searchInputBar.textInputFocus(); // } } } ukui-menu/qml/AppUI/WidgetPage.qml0000664000175000017500000001725515160463365015744 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQml 2.12 import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5 import AppControls2 1.0 as AppControls2 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.menu.core 1.0 import org.ukui.menu.utils 1.0 import org.ukui.menu.extension 1.0 Item { property Item focusToSidebar onFocusChanged: { if (focus) widgetList.forceActiveFocus(); } ColumnLayout { anchors.fill: parent anchors.topMargin: 12 anchors.bottomMargin: 5 spacing: 5 Item { // header Layout.fillWidth: true Layout.preferredHeight: 32 RowLayout { anchors.fill: parent anchors.leftMargin: 24 anchors.rightMargin: 28 spacing: 0 ListView { id: widgetList property int selectIndex: -1 Layout.fillWidth: true Layout.fillHeight: true KeyNavigation.tab: widgetLoader clip: true spacing: 24 interactive: false orientation: ListView.Horizontal function send(data) { if (currentItem !== null) { model.send(currentIndex, data); } } onCurrentIndexChanged: { if (currentItem !== null) { currentItem.select(); } } onFocusChanged: { selectIndex = focus ? 0 : -1 } Keys.onPressed: { switch (event.key) { case Qt.Key_Left: if (selectIndex > 0) { selectIndex--; event.accepted = true; } break; case Qt.Key_Right: if (selectIndex < widgetList.count - 1) { selectIndex++; event.accepted = true; } break; case Qt.Key_Enter: case Qt.Key_Return: currentIndex = selectIndex; event.accepted = true; break; default: break; } } model: WidgetModel {} delegate: Component { // 插件信息 UkuiItems.DtThemeBackground { useStyleTransparency: false backgroundColor: Platform.GlobalTheme.highlightActive alpha: 0 radius: Platform.GlobalTheme.kRadiusMin borderColor: Platform.GlobalTheme.highlightActive border.width: index === widgetList.selectIndex ? 2 : 0 property var extensionData: model.data property var extensionOptions: model.options width: styleText.width height: ListView.view ? ListView.view.height : 0 KeyNavigation.down: widgetLoader Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { widgetList.currentIndex = model.index; EventTrack.sendClickEvent("switch_plugin", "Sidebar", {"plugin": model.name}); } } onExtensionDataChanged: { if (widgetLoader.source === model.main) { widgetLoader.item.extensionData = extensionData; } } Accessible.role: Accessible.Button Accessible.description: "This is a page switch button on ukui-menu" Accessible.name: model.name function select() { if (widgetLoader.source !== model.main) { widgetLoader.setSource(model.main, {extensionData: extensionData}); } } UkuiItems.DtThemeText { height: parent.height id: styleText verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter font.bold: parent.ListView.isCurrentItem textColor: parent.ListView.isCurrentItem ? Platform.GlobalTheme.highlightActive: Platform.GlobalTheme.textActive text: model.name } MouseArea { anchors.fill: parent onClicked: { widgetList.currentIndex = model.index; EventTrack.sendClickEvent("switch_plugin", "Sidebar", {"plugin": model.name}); } } } } } Loader { id: widgetMenuLoader Layout.preferredWidth: 34 Layout.preferredHeight: 34 Layout.alignment: Qt.AlignVCenter } } } Loader { id: widgetLoader // 加载插件区域 Layout.fillWidth: true Layout.fillHeight: true clip: true KeyNavigation.tab: focusToSidebar onLoaded: { item.send.connect(widgetList.send); // sidebarLayout.updateSidebarLayout(widgetList.currentItem.extensionOptions); updateMenu(); item.extensionMenuChanged.connect(updateMenu); } function updateMenu() { if (item === null) { return; } if (item.extensionMenu !== null) { widgetMenuLoader.sourceComponent = item.extensionMenu; } else { widgetMenuLoader.sourceComponent = undefined; } } } } Component.onCompleted: { if (widgetList.count > 0) { widgetList.currentIndex = 0; } } } ukui-menu/qml/AppUI/FullScreenHeader.qml0000664000175000017500000000465215160463353017071 0ustar fengfengimport QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.menu.core 1.0 import org.ukui.menu.utils 1.0 Item { clip: true property alias searchOpacity: searchInputBar.opacity property alias searchText: searchInputBar.text PluginSelectButton { id: pluginSelectButton anchors.left: parent.left anchors.leftMargin: 72 anchors.verticalCenter: parent.verticalCenter height: 32; width: 64 } SearchInputBar { id: searchInputBar width: 372; height: 36 anchors.centerIn: parent radius: Platform.GlobalTheme.kRadiusMin visible: opacity onTextChanged: { if (text === "") { // appPageHeaderUtils.model(PluginGroup.SortMenuItem).reactivateProvider(); } else { // appPageHeaderUtils.model(PluginGroup.Button).reactivateProvider(); // appPageHeaderUtils.startSearch(text); } } Behavior on opacity { NumberAnimation { duration: 300; easing.type: Easing.InOutCubic } } } UkuiItems.DtThemeBackground { width: 48; height: width radius: Platform.GlobalTheme.kRadiusMin useStyleTransparency: false backgroundColor: Platform.GlobalTheme.lightActive alpha: buttonMouseArea.containsPress ? 0.30 : buttonMouseArea.containsMouse ? 0.20 : 0.00 anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter borderColor: Platform.GlobalTheme.highlightActive border.width: buttonMouseArea.activeFocus ? 2 : 0 UkuiItems.Icon { anchors.centerIn: parent width: parent.width / 2; height: width source: "view-restore-symbolic" mode: UkuiItems.Icon.Highlight } MouseArea { id: buttonMouseArea hoverEnabled: true anchors.fill: parent ToolTip.delay: 500 ToolTip.text: qsTr("Contract") ToolTip.visible: containsMouse onClicked: mainWindow.exitFullScreen() activeFocusOnTab: true Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { mainWindow.exitFullScreen(); } } } } } ukui-menu/qml/AppUI/AppListHeader.qml0000664000175000017500000000403315160463365016377 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.12 import org.ukui.menu.core 1.0 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform Item { id: root property var header: null property string title: "" clip: true visible: header !== null && header.visible onFocusChanged: { if (focus) actionsItem.view.forceActiveFocus();; } RowLayout { anchors.fill: parent anchors.leftMargin: 16 anchors.rightMargin: 16 spacing: 5 UkuiItems.DtThemeText { Layout.fillWidth: true Layout.fillHeight: true verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft elide: Text.ElideRight text: root.title === "" ? header.title : root.title UkuiItems.Tooltip { anchors.fill: parent mainText: parent.text posFollowCursor: true visible: parent.truncated } } AppListActions { id: actionsItem Layout.preferredWidth: width Layout.preferredHeight: 36 Layout.topMargin: 5 Layout.bottomMargin: 5 Layout.alignment: Qt.AlignVCenter actions: header.actions } } } ukui-menu/qml/AppUI/AppPage.qml0000664000175000017500000000341515160463353015227 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import org.ukui.menu.core 1.0 Item { property alias search: appPageSearch property alias content: appPageContent ColumnLayout { anchors.fill: parent anchors.topMargin: 12 anchors.bottomMargin: 5 spacing: 5 AppPageSearch { id: appPageSearch Layout.fillWidth: true Layout.preferredHeight: 40 focusToPageContent: appPageContent Accessible.role: Accessible.Button Accessible.description: "This is a search bar on ukui-menu" Accessible.name: "searchBar-button" KeyNavigation.tab: appPageContent onFocusChanged: { if (focus) searchInputBar.textInputFocus(); } } AppPageContent { id: appPageContent isHeaderShow: appPageSearch.searchInputBar.text === "" focusToSearchInput: appPageSearch Layout.fillWidth: true Layout.fillHeight: true activeFocusOnTab: false } } } ukui-menu/qml/AppUI/EditText.qml0000664000175000017500000001133015160463365015442 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5 import org.ukui.menu.core 1.0 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems Item { id: contain property bool textCenterIn: false property bool editStatus: false property string textEdited: title property string text: editLoader.item.text property int textSizeOffset: 0 property int textBorderWidth: 0 signal textEditingFinished(string text) Component { id: unEditText UkuiItems.DtThemeText { id: textShow verticalAlignment: Text.AlignVCenter horizontalAlignment: contain.textCenterIn ? Text.AlignHCenter : Text.AlignLeft elide: Text.ElideRight text: contain.textEdited textColor: Platform.GlobalTheme.textActive font.bold: true pointSizeOffset: textSizeOffset MouseArea { id: textArea anchors.fill: parent onClicked: contain.editStatus = true; } } } Component { id: editText UkuiItems.DtThemeBackground { radius: Platform.GlobalTheme.kRadiusNormal useStyleTransparency: false backgroundColor: textEdit.activeFocus ? Platform.GlobalTheme.kGrayAlpha1 : Platform.GlobalTheme.kGrayAlpha0 border.width: textBorderWidth borderColor: textEdit.activeFocus ? Platform.GlobalTheme.kBrandNormal : Platform.GlobalTheme.kGrayAlpha0 property string text: textEdit.text TextInput { id: textEdit clip: true anchors.left: parent.left anchors.leftMargin: 8 anchors.right: buttonMouseArea.left anchors.rightMargin: 8 anchors.verticalCenter: parent.verticalCenter verticalAlignment: Text.AlignVCenter horizontalAlignment: contain.textCenterIn ? Text.AlignHCenter : Text.AlignLeft text: contain.textEdited selectByMouse: true maximumLength: 14 font.pointSize: Platform.GlobalTheme.fontSize + textSizeOffset font.family: Platform.GlobalTheme.fontFamily; font.bold: true color : Platform.GlobalTheme.kFontPrimary.pureColor; selectionColor : Platform.GlobalTheme.highlightActive.pureColor; onEditingFinished: { // modelManager.getFolderModel().renameFolder(text); var tmpText = text; if (tmpText.trim() === "") { text = qsTr("Folder"); } contain.textEdited = text; contain.editStatus = false; textEditingFinished(text); } Component.onCompleted: { forceActiveFocus(); } MouseArea { anchors.fill: parent cursorShape: Qt.IBeamCursor enabled: false } } MouseArea { id: buttonMouseArea hoverEnabled: true width: 16; height: width anchors.right: parent.right anchors.rightMargin: 10 anchors.verticalCenter: parent.verticalCenter visible: textEdit.activeFocus UkuiItems.Icon { anchors.centerIn: parent width: 16; height: width source: "image://theme/edit-clear-symbolic" mode: UkuiItems.Icon.AutoHighlight } onClicked: { textEdit.text = textEdited; contain.editStatus = false; } } } } Loader { id: editLoader anchors.fill: parent sourceComponent: contain.editStatus ? editText : unEditText } } ukui-menu/qml/AppUI/FullScreenContent.qml0000664000175000017500000007121115160463365017311 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQml.Models 2.12 import QtQuick.Controls 2.5 import org.ukui.menu.core 1.0 import AppControls2 1.0 as AppControls2 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems RowLayout { id: root clip: true signal openFolderSignal(string folderId, string folderName, int x, int y) signal contentShowFinished() property bool isContentShow: true property int animationDuration: 300 function viewFocusEnable() { contentViewLoader.focus = true; } function clearViewFocus() { contentViewLoader.focus = false; if (contentViewLoader.item.currentIndex) { contentViewLoader.item.currentIndex = 0; } } Component.onCompleted: mainWindow.visibleChanged.connect(clearViewFocus) Component.onDestruction: mainWindow.visibleChanged.disconnect(clearViewFocus) state: isContentShow ? "contentShow" : "contentHidden" states: [ State { name: "contentHidden" PropertyChanges { target: root; opacity: 0; scale: 0.95 } }, State { name: "contentShow" PropertyChanges { target: root; opacity: 1; scale: 1 } } ] transitions: [ Transition { to:"contentHidden" SequentialAnimation { PropertyAnimation { properties: "opacity, scale"; duration: animationDuration; easing.type: Easing.InOutCubic } ScriptAction { script: root.visible = false } } }, Transition { to: "contentShow" PropertyAnimation { properties: "opacity, scale"; duration: animationDuration; easing.type: Easing.InOutCubic } } ] Item { Layout.preferredWidth: 145 Layout.fillHeight: true Layout.leftMargin: 15 ListView { id: labelListView signal labelClicked(string labelId) property int maxLabelWidth: count < 20 ? width : 30 anchors.centerIn: parent width: parent.width height: contentHeight > parent.height ? parent.height : contentHeight interactive: contentHeight > parent.height highlightMoveDuration: animationDuration highlight: UkuiItems.DtThemeBackground { width: labelListView.maxLabelWidth; height: 30 radius: Platform.GlobalTheme.kRadiusMin useStyleTransparency: false backgroundColor: Platform.GlobalTheme.kContainAlphaClick border.width: 1 borderColor: Platform.GlobalTheme.highlightActive } onCountChanged: currentIndex = 0 model: DelegateModel { groups: DelegateModelGroup { name: "disabledItem" includeByDefault: false } items.onChanged: { for (let i = 0; i < items.count;) { let item = items.get(i); if (item.model.isDisable) { items.setGroups(i, 1, "disabledItem"); continue; } ++i; } } model: modelManager.getLabelModel() delegate: MouseArea { width: labelListView.maxLabelWidth height: 30 hoverEnabled: true UkuiItems.DtThemeText { anchors.fill: parent textColor: Platform.GlobalTheme.highlightedTextActive elide: Text.ElideRight horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter text: model.displayName } onClicked: { labelListView.currentIndex = DelegateModel.itemsIndex; labelListView.labelClicked(model.id) } } } } } Item { id: contentViewBase Layout.fillWidth: true Layout.fillHeight: true property int maxColumns: 8 property int columns: { let c = Math.floor(width / cellWidth); return c > maxColumns ? maxColumns : c; } property int cellWidth: 220 // property int cellWidth: Math.min(Math.floor(width / columns), 220) property int cellHeight: cellWidth property int headerHeight: 30 Component.onCompleted: { changeView(modelManager.getLabelGroupModel().containLabel); modelManager.getLabelGroupModel().containLabelChanged.connect(changeView); } Component.onDestruction: { modelManager.getLabelGroupModel().containLabelChanged.disconnect(changeView); contentViewLoader.sourceComponent = undefined; } function changeView(toLabelView) { contentViewLoader.sourceComponent = toLabelView ? labelViewComponent : appViewComponent; } Loader { id: contentViewLoader height: parent.height width: contentViewBase.cellWidth * contentViewBase.columns + 2 anchors.horizontalCenter: parent.horizontalCenter } // view id: 1 Component { id: appViewComponent GridView { id: appGridView property string selectId: "" ScrollBar.vertical: fullScreenScrollBar cellWidth: contentViewBase.cellWidth cellHeight: contentViewBase.cellHeight boundsBehavior: Flickable.StopAtBounds model: modelManager.getAppModel() cacheBuffer: cellHeight * appGridView.count / 6 focus: true keyNavigationEnabled: true delegate: Loader { width: GridView.view.cellWidth height: width focus: true property int index: model.index property int type: model.type property string id: model.id property string name: model.name property string icon: model.icon sourceComponent: { if (type === DataType.Normal) { return appComponent; } if (type === DataType.Folder) { return normalFolderComponent; } } } Component { id: appComponent DropArea { id: dropArea property int originalX property int originalY onEntered: { if (appGridView.selectId !== id) { imageBase.visible = true; } } onExited: { imageBase.visible = false; } onDropped: { if (appGridView.selectId !== id) { appGridView.model.addAppsToFolder(appGridView.selectId, id, ""); } } // ********按键导航******** focus: true states: State { when: dropArea.activeFocus PropertyChanges { target: controlBase borderColor: Platform.GlobalTheme.highlightActive border.width: 2 } } Keys.onPressed: { if (event.key === Qt.Key_Enter || event.key === Qt.Key_Return) { appGridView.model.appClicked(index); } } Item { id: appItem property bool isSelect: false property bool isEnterd: false width: contentViewBase.cellWidth height: contentViewBase.cellHeight Drag.active: appItemMouseArea.drag.active Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 MouseArea { id: appItemMouseArea anchors.centerIn: parent hoverEnabled: true width: 170; height: width acceptedButtons: Qt.LeftButton | Qt.RightButton ToolTip.delay: 500 ToolTip.text: name ToolTip.visible: iconText.truncated && containsMouse UkuiItems.DtThemeBackground { id: controlBase anchors.fill: parent useStyleTransparency: false backgroundColor: appItem.isSelect ? Platform.GlobalTheme.lightActive : parent.containsPress ? Platform.GlobalTheme.kContainAlphaClick : parent.containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.kBrandNormal radius: 16 alpha: appItem.isSelect ? 0 : parent.containsPress ? 1 : parent.containsMouse ? 1 : 0 Item { anchors.top: parent.top anchors.topMargin: 14 anchors.horizontalCenter: parent.horizontalCenter height: 108 width: 108 UkuiItems.DtThemeBackground { id: imageBase anchors.fill: parent backgroundColor: Platform.GlobalTheme.textActive useStyleTransparency: false alpha: 0.25 radius: 24 visible: false } Image { id: iconImage anchors.centerIn: parent height: 96 width: 96 source: icon cache: false } } UkuiItems.DtThemeText { id: iconText visible: !appItem.isSelect width: parent.width horizontalAlignment: Text.AlignHCenter anchors.bottom: parent.bottom anchors.bottomMargin: 20 anchors.horizontalCenter: parent.horizontalCenter text: name elide: Text.ElideRight textColor: Platform.GlobalTheme.highlightedTextActive } } onClicked: { if (mouse.button === Qt.RightButton) { appGridView.model.openMenu(index, MenuInfo.FullScreen); return; } if (mouse.button === Qt.LeftButton) { appGridView.model.appClicked(index); return; } } onPressAndHold: { if (mouse.button === Qt.LeftButton) { originalX = appItem.x; originalY = appItem.y; appItem.x = appItem.mapToItem(contentViewBase,0,0).x; appItem.y = appItem.mapToItem(contentViewBase,0,0).y; drag.target = appItem; appItem.parent = contentViewBase; appItem.isSelect = true; appGridView.selectId = id; } } onReleased: { parent.Drag.drop(); appItem.isSelect = false; drag.target = null; appItem.parent = dropArea; appItem.x = originalX; appItem.y = originalY; } } } } } Component { id: normalFolderComponent DropArea { onEntered: { folderItem.isSelect = true; } onExited: { folderItem.isSelect = false; } onDropped: { appGridView.model.addAppToFolder(appGridView.selectId, id); } MouseArea { anchors.centerIn: parent hoverEnabled: true width: 170; height: width acceptedButtons: Qt.LeftButton | Qt.RightButton ToolTip.delay: 500 ToolTip.text: name ToolTip.visible: folderText.truncated && containsMouse UkuiItems.DtThemeBackground { anchors.fill: parent useStyleTransparency: false backgroundColor: parent.containsPress ? Platform.GlobalTheme.kContainAlphaClick : parent.containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.kBrandNormal radius: 16 alpha: parent.containsPress ? 1 : parent.containsMouse ? 1 : 0 Item { id: folderItem property bool isSelect: false anchors.horizontalCenter: parent.horizontalCenter height: 108; width: 108 anchors.top: parent.top anchors.topMargin: 14 UkuiItems.DtThemeBackground { anchors.fill: parent backgroundColor: Platform.GlobalTheme.textActive useStyleTransparency: false alpha: 0.25 radius: 24 visible: folderItem.isSelect } AppControls2.FolderIcon { id: folderIcon width: 86 height: 86 rows: 4; columns: 4 spacing: 2; padding: 8 icons: icon radius: 16; alpha: folderItem.isSelect ? 0 : 0.25 anchors.centerIn: parent } } UkuiItems.DtThemeText { id: folderText anchors.bottom: parent.bottom anchors.bottomMargin: 20 width: parent.width horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter elide: Text.ElideRight text: name textColor: Platform.GlobalTheme.highlightedTextActive } } onClicked: { if (mouse.button === Qt.RightButton) { modelManager.getAppModel().openMenu(index, MenuInfo.FullScreen); return; } if (mouse.button === Qt.LeftButton) { var x = folderIcon.mapToGlobal(0,0).x; var y = folderIcon.mapToGlobal(0,0).y openFolderSignal(id, name, x, y); // 执行隐藏动画,并且当前图标消失且鼠标区域不可用 root.isContentShow = false; folderIcon.opacity = 0; enabled = false; hoverEnabled = false; return; } } function resetOpacity() { folderIcon.opacity = 1; enabled = true; hoverEnabled = true; } Component.onCompleted: root.contentShowFinished.connect(resetOpacity) Component.onDestruction: root.contentShowFinished.disconnect(resetOpacity) } } } } } // view id: 2 Component { id: labelViewComponent Flickable { id: labelViewFlickable ScrollBar.vertical: fullScreenScrollBar contentHeight: labelColumn.height flickableDirection: Flickable.VerticalFlick boundsBehavior: Flickable.StopAtBounds interactive: !contentYAnimation.running Column { id: labelColumn width: parent.width height: childrenRect.height Repeater { id: labelRepeater model: modelManager.getLabelGroupModel() delegate: GridView { id: labelAppsGridView width: parent.width height: contentHeight property int labelIndex: index interactive: false cacheBuffer: 50 cellWidth: contentViewBase.cellWidth cellHeight: contentViewBase.cellHeight header: Item { width: labelAppsGridView.width height: contentViewBase.headerHeight Row { anchors.fill: parent anchors.leftMargin: 67 spacing: 15 UkuiItems.DtThemeText { id: labelName anchors.verticalCenter: parent.verticalCenter verticalAlignment: Text.AlignVCenter width: contentWidth text: name textColor: Platform.GlobalTheme.highlightedTextActive } UkuiItems.DtThemeBackground { anchors.verticalCenter: parent.verticalCenter useStyleTransparency: false alpha: 0.14 backgroundColor: Platform.GlobalTheme.lightActive height: 1 width: parent.width - labelName.width - parent.spacing } } } model: extraData delegate: Item { width: GridView.view.cellWidth height: GridView.view.cellHeight MouseArea { id: labelAppsMouseArea anchors.centerIn: parent hoverEnabled: true width: 170; height: width acceptedButtons: Qt.LeftButton | Qt.RightButton UkuiItems.DtThemeBackground { anchors.fill: parent useStyleTransparency: false backgroundColor: Platform.GlobalTheme.lightActive radius: Platform.GlobalTheme.kRadiusMax alpha: parent.containsPress ? 0.25 : parent.containsMouse ? 0.15 : 0.00 AppControls2.IconLabel { anchors.fill: parent iconWidth: 96; iconHeight: 96 appName: modelData.name appIcon: modelData.icon spacing: 8 textHighLight: true ToolTip.delay: 500 ToolTip.text: modelData.name ToolTip.visible: textTruncated && labelAppsMouseArea.containsMouse } } onClicked: function (mouse) { if (mouse.button === Qt.RightButton) { labelRepeater.model.openMenu(labelAppsGridView.labelIndex, model.index); return; } if (mouse.button === Qt.LeftButton) { labelRepeater.model.openApp(labelAppsGridView.labelIndex, model.index); return; } } } } } } } onContentYChanged: { if (contentYAnimation.running) { return } if ((contentY + height) === contentHeight) { labelListView.currentIndex = labelRepeater.count - 1 return } if (labelColumn.childAt(contentX,contentY) !== null) { labelListView.currentIndex = labelColumn.childAt(contentX,contentY).labelIndex } } NumberAnimation { id: contentYAnimation target: labelViewFlickable property: "contentY" duration: animationDuration onFinished: { labelViewFlickable.returnToBounds(); } } function scrollerView(labelId) { let labelindex = labelRepeater.model.getLabelIndex(labelId); if (labelindex < 0 || labelindex >= labelRepeater.count) { return; } let nextY = labelRepeater.itemAt(labelindex).y; let sh = labelColumn.height - nextY; if (sh < height) { nextY -= (height - sh); } contentYAnimation.running = false; if (nextY === contentY) { return; } contentYAnimation.from = contentY; contentYAnimation.to = nextY; contentYAnimation.running = true; } Component.onCompleted: { labelListView.labelClicked.connect(scrollerView); } Component.onDestruction: { labelListView.labelClicked.disconnect(scrollerView); } } } } Item { Layout.preferredWidth: 160 Layout.fillHeight: true Rectangle { anchors.fill: parent color: "red" } ScrollBar { id: fullScreenScrollBar anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.right anchors.horizontalCenterOffset: -24 height: 200 width: (hovered || pressed) ? 8 : 4 padding: 0 opacity: fullScreenScrollBar.size < 1.0 ? 1.0 : 0.0 Behavior on width { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } } background: UkuiItems.DtThemeBackground { useStyleTransparency: false backgroundColor: Platform.GlobalTheme.darkActive alpha: 0.25 radius: width / 2 } contentItem: UkuiItems.DtThemeBackground { radius: width / 2 useStyleTransparency: false backgroundColor: Platform.GlobalTheme.lightActive alpha: fullScreenScrollBar.pressed ? 0.90 : fullScreenScrollBar.hovered ? 0.78 : 0.60 } } } } ukui-menu/qml/AppControls2/0000775000175000017500000000000015160463365014547 5ustar fengfengukui-menu/qml/AppControls2/FolderIcon.qml0000664000175000017500000000335515160463365017314 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import org.ukui.menu.core 1.0 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform UkuiItems.DtThemeBackground { property alias icons: iconGrid.icons property alias rows: iconGrid.rows property alias columns: iconGrid.columns property alias padding: iconGrid.padding property alias spacing: iconGrid.spacing backgroundColor: Platform.GlobalTheme.textActive useStyleTransparency: false Grid { id: iconGrid anchors.fill: parent property var icons property int cellWidth: Math.floor((width - padding*2 - spacing*(columns - 1)) / columns) property int cellHeight: Math.floor((height - padding*2 - spacing*(rows - 1)) / rows) Repeater { model: icons.slice(0, rows*columns) delegate: UkuiItems.Icon { width: iconGrid.cellWidth height: iconGrid.cellHeight source: modelData.icon proxy.appId: modelData.desktopName } } } } ukui-menu/qml/AppControls2/qmldir0000664000175000017500000000033415160463365015762 0ustar fengfengmodule AppControls2 ScrollBar 1.0 ScrollBar.qml AppItem 1.0 AppItem.qml FolderItem 1.0 FolderItem.qml LabelItem 1.0 LabelItem.qml IconLabel 1.0 IconLabel.qml RoundButton 1.0 RoundButton.qml FolderIcon 1.0 FolderIcon.qml ukui-menu/qml/AppControls2/App.qml0000664000175000017500000000014215160463353015774 0ustar fengfengimport QtQuick 2.0 Rectangle { color: "red"; Text { text: "AppControls2" } } ukui-menu/qml/AppControls2/ScrollBar.qml0000664000175000017500000000137515160463365017153 0ustar fengfengimport QtQuick 2.0 import QtQuick.Controls 2.5 import org.ukui.menu.core 1.0 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform ScrollBar { id: control padding: (hovered || pressed) ? 3 : 5 property bool visual: true Behavior on padding { NumberAnimation { duration: 200 easing.type: Easing.InOutQuad } } contentItem: UkuiItems.DtThemeBackground { radius: width / 2 useStyleTransparency: false backgroundColor: Platform.GlobalTheme.textActive alpha: control.pressed ? 0.28 : control.hovered ? 0.18 : 0.10 opacity: ((control.policy === ScrollBar.AlwaysOn || control.size < 1.0 ) && control.visual) ? 1.0 : 0.0 } } ukui-menu/qml/AppControls2/IconLabel.qml0000664000175000017500000000455315160463365017121 0ustar fengfengimport QtQuick 2.0 import org.ukui.menu.core 1.0 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform Item { id: root property int display: Display.TextUnderIcon // 上下布局icon大小为48*48 左右布局icon大小为32*32 property int iconHeight: 48 property int iconWidth: 48 property string appName: "" property string appIcon: "" property string appDesktopName: "" // 上下布局spacing:0 左右布局spacing:12 property int spacing: 2 property bool textTruncated: iconText.truncated property bool textHighLight: false state: root.display states: [ State { name: Display.TextBesideIcon PropertyChanges { target: iconImage anchors.verticalCenter: root.verticalCenter anchors.left: root.left anchors.leftMargin: root.spacing } PropertyChanges { target: iconText width: root.width - iconImage.width - root.spacing * 2 horizontalAlignment: Text.AlignLeft anchors.left: iconImage.right anchors.leftMargin: root.spacing anchors.verticalCenter: root.verticalCenter } }, State { name: Display.TextUnderIcon PropertyChanges { target: iconImage anchors.top: root.top anchors.topMargin: (root.height - root.spacing - iconImage.height - iconText.height) / 2 anchors.horizontalCenter: root.horizontalCenter } PropertyChanges { target: iconText width: root.width horizontalAlignment: Text.AlignHCenter anchors.top: iconImage.bottom anchors.topMargin: root.spacing anchors.horizontalCenter: root.horizontalCenter } } ] UkuiItems.Icon { id: iconImage height: root.iconHeight width: root.iconWidth source: root.appIcon proxy.appId: root.appDesktopName } UkuiItems.DtThemeText { id: iconText text: root.appName elide: Text.ElideRight textColor: root.textHighLight ? Platform.GlobalTheme.highlightedTextActive : Platform.GlobalTheme.textActive } } ukui-menu/qml/AppControls2/RoundButton.qml0000664000175000017500000000205615160463353017545 0ustar fengfengimport QtQuick 2.15 import org.ukui.menu.core 1.0 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform MouseArea { id: buttonMouseArea hoverEnabled: true property string buttonIcon: "" property alias highlight: themeIcon.highLight property alias autoHighLight: themeIcon.autoHighLight UkuiItems.DtThemeBackground { id: buttonBase useStyleTransparency: false backgroundColor: Platform.GlobalTheme.textActive anchors.fill: parent radius: height / 2 alpha: buttonMouseArea.containsPress ? 0.20 : buttonMouseArea.containsMouse ? 0.16 : 0.10 } UkuiItems.Icon { id: themeIcon anchors.centerIn: parent width: 16; height: width source: buttonIcon mode: UkuiItems.Icon.AutoHighlight } states: State { when: buttonMouseArea.activeFocus PropertyChanges { target: buttonBase borderColor: Platform.GlobalTheme.highlightActive border.width: 2 } } } ukui-menu/qml/AppControls2/FolderItem.qml0000664000175000017500000001243115160463353017312 0ustar fengfengimport QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5 import org.ukui.menu.core 1.0 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform MouseArea { id: control property bool editStatus: false property bool truncate: false property bool isSelect: false hoverEnabled: true UkuiItems.Tooltip { anchors.fill: parent mainText: name posFollowCursor: true margin: 6 visible: !editStatus && truncate } onPositionChanged: { if (tip.isVisible) { if (tip.visible) { tip.hide(); } else { tip.show(name); } } } states: State { when: control.activeFocus PropertyChanges { target: controlBase borderColor: Platform.GlobalTheme.highlightActive border.width: 2 } } UkuiItems.DtThemeBackground { id: controlBase anchors.fill: parent radius: Platform.GlobalTheme.kRadiusMin useStyleTransparency: false alpha: isSelect ? 0.55 : control.containsPress ? 0.82 : control.containsMouse ? 0.55 : 0.00 RowLayout { anchors.fill: parent anchors.leftMargin: 12 spacing: 12 FolderIcon { rows: 2; columns: 2 spacing: 2; padding: 2 icons: icon alpha: 0.12; radius: Platform.GlobalTheme.kRadiusMin Layout.alignment: Qt.AlignVCenter Layout.preferredWidth: 32 Layout.preferredHeight: 32 } Item { id: renameArea Layout.fillWidth: true Layout.fillHeight: true Component { id: unEditText UkuiItems.DtThemeText { id: textShow verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft elide: Text.ElideRight text: name onWidthChanged: { control.truncate = textShow.truncated } } } Component { id: editText UkuiItems.DtThemeBackground { radius: Platform.GlobalTheme.kRadiusNormal useStyleTransparency: false backgroundColor: textChange.activeFocus ? Platform.GlobalTheme.baseActive : Platform.GlobalTheme.kContainGeneralAlphaNormal border.width: 2 borderColor: textChange.activeFocus ? Platform.GlobalTheme.highlightActive : Platform.GlobalTheme.kGrayAlpha0 TextInput { id: textChange text: name clip: true anchors.left: parent.left anchors.leftMargin: 8 anchors.right: buttonMouseArea.left anchors.rightMargin: 8 anchors.verticalCenter: parent.verticalCenter verticalAlignment: Text.AlignVCenter selectByMouse: true maximumLength: 14 color : Platform.GlobalTheme.textActive.pureColor selectionColor : Platform.GlobalTheme.highlightActive.pureColor function editStatusEnd() { control.editStatus = false; control.focus = true; } onEditingFinished: { modelManager.getAppModel().renameFolder(id, text); textChange.editStatusEnd(); } Component.onCompleted: { forceActiveFocus(); } } MouseArea { id: buttonMouseArea hoverEnabled: true width: 16; height: width anchors.right: parent.right anchors.rightMargin: 10 anchors.verticalCenter: parent.verticalCenter visible: textChange.activeFocus UkuiItems.Icon { anchors.centerIn: parent width: 16; height: width source: "edit-clear-symbolic" } onClicked: { textChange.text = name; textChange.editStatusEnd(); } } } } Loader { id: editLoader anchors.fill: parent sourceComponent: editStatus ? editText : unEditText } } } } } ukui-menu/qml/AppControls2/LabelItem.qml0000664000175000017500000000554415160463365017130 0ustar fengfengimport QtQuick 2.0 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5 import org.ukui.menu.core 1.0 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems MouseArea { id: control hoverEnabled: true property string displayName // ToolTip.text: comment // ToolTip.visible: control.containsMouse // ToolTip.delay: 500 Loader { anchors.fill: parent sourceComponent: mainWindow.isFullScreen ? fullComponent : normalComponent } Component { id: normalComponent UkuiItems.DtThemeBackground { anchors.fill: parent radius: Platform.GlobalTheme.kRadiusMin useStyleTransparency: false backgroundColor: control.containsPress ? Platform.GlobalTheme.kContainAlphaClick : control.containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.kContainGeneralAlphaNormal RowLayout { anchors.fill: parent UkuiItems.DtThemeText { id: labelText Layout.fillWidth: true Layout.fillHeight: true Layout.leftMargin: 12 horizontalAlignment: Qt.AlignLeft verticalAlignment: Qt.AlignVCenter font.bold: true elide: Text.ElideRight text: control.displayName } UkuiItems.Icon { visible: control.containsMouse Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.preferredWidth: 24 Layout.preferredHeight: 24 Layout.rightMargin: 16 mode: UkuiItems.Icon.AutoHighlight source: "open-menu-symbolic" } } } } Component { id: fullComponent RowLayout { anchors.fill: parent spacing: 14 UkuiItems.DtThemeText { id: labelText Layout.preferredWidth: contentWidth Layout.fillHeight: true Layout.leftMargin: 12 horizontalAlignment: Qt.AlignLeft verticalAlignment: Qt.AlignVCenter font.bold: true elide: Text.ElideRight text: control.displayName } UkuiItems.DtThemeBackground { Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true Layout.preferredHeight: 1 Layout.rightMargin: 16 useStyleTransparency: false backgroundColor: Platform.GlobalTheme.kDivider } } } } ukui-menu/qml/AppControls2/AppItem.qml0000664000175000017500000000572015160463365016625 0ustar fengfengimport QtQuick 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5 import org.ukui.menu.core 1.0 import "../extensions" as Extension import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems MouseArea { id: control property bool isSelect: false property bool isRecentInstalled: false hoverEnabled: true pressAndHoldInterval: 300 states: State { when: control.activeFocus PropertyChanges { target: controlBase borderColor: Platform.GlobalTheme.highlightActive border.width: 2 } } UkuiItems.Tooltip { anchors.fill: parent mainText: name posFollowCursor: true margin: 6 visible: content.textTruncated } UkuiItems.DtThemeBackground { id: controlBase anchors.fill: parent radius: Platform.GlobalTheme.kRadiusMin useStyleTransparency: false backgroundColor: isSelect ? Platform.GlobalTheme.kContainAlphaClick : control.containsPress ? Platform.GlobalTheme.kContainAlphaClick : control.containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.kContainGeneralAlphaNormal RowLayout { anchors.fill: parent spacing: 2 IconLabel { id: content Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Layout.fillWidth: true Layout.fillHeight: true iconHeight: 32; iconWidth: iconHeight appName: name; appIcon: icon appDesktopName: desktopName display: Display.TextBesideIcon spacing: 12 } Item { Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.fillWidth: true Layout.fillHeight: true Layout.maximumWidth: tagLabel.visible ? 28 : isRecentInstalled ? 8 : 0 Layout.maximumHeight: width Layout.rightMargin: tagLabel.visible ? 4 : 0 Extension.EditModeFlag { id: tagLabel anchors.fill: parent isFavorited: favorite onClicked: { appManager.changeFavoriteState(id, !isFavorited); } } UkuiItems.DtThemeBackground { id: tagPoint visible: tagLabel.sourceComponent != null ? false : isRecentInstalled anchors.centerIn: parent width: 8; height: width radius: width / 2 useStyleTransparency: false backgroundColor: Platform.GlobalTheme.highlightActive } } } } } ukui-menu/qml/extensions/0000775000175000017500000000000015160463365014420 5ustar fengfengukui-menu/qml/extensions/FavoriteDelegate.qml0000664000175000017500000003215015160463365020346 0ustar fengfengimport QtQuick 2.15 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.15 import org.ukui.menu.core 1.0 import org.ukui.menu.extension 1.0 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems import AppControls2 1.0 as AppControls2 UkuiItems.DtThemeBackground { id: iconItem property bool hold: false property alias delegateLayout: itemLayout property alias mergePrompt: mergePrompt property bool isFullScreen: false radius: 8 useStyleTransparency: false backgroundColor: hold ? Platform.GlobalTheme.kContainGeneralAlphaNormal : control.containsPress ? Platform.GlobalTheme.kContainAlphaClick : control.containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.kContainGeneralAlphaNormal property int visualIndex property int aniDuration: 250 Binding { target: itemLoader; property: "visualIndex"; value: visualIndex } // 合并提示框 52*52 ,backgroundColor的改变在状态切换的时候变 UkuiItems.DtThemeBackground { id: mergePrompt height: width anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter useStyleTransparency: false alpha: 0 z: -1 } DropArea { id: delegateDropArea anchors.fill: parent // drag.source [itemLoader] onEntered: { // 拖拽对象为folder 或 左侧列表应用时 if (!drag.source.sourceId) { drag.accepted = false; return; } if (!drag.source.isFolder && drag.source.sourceId !== model.id) { delegateDropTimer.running = true; } } onExited: { if (!drag.source.sourceId) { return; } delegateDropTimer.running = false; itemLoader.item.state = "normal"; } DropArea { id: moveDropArea anchors.fill: parent anchors.margins: delegateDropArea.width * 0.2 onEntered: { // 拖拽对象为folder 或 左侧列表应用时 if (!drag.source.sourceId) { drag.accepted = false; return; } delegateDropTimer.running = false; itemLoader.item.state = "normal"; if (delegateDropTimer.timeOutCount < 1) { favoriteView.dragTypeIsMerge = false; favoriteView.viewModel.items.move(drag.source.visualIndex, iconItem.visualIndex); } } } } Timer { id: delegateDropTimer property int timeOutCount: 0 interval: 300 repeat: true onTriggered: { ++timeOutCount; if (timeOutCount == 1) { favoriteView.mergeToAppId = model.id; favoriteView.isMergeToFolder = (model.type === DataType.Folder); favoriteView.dragTypeIsMerge = true; // 添加应用合并动画 itemLoader.item.state = "prepareToMerge"; } } onRunningChanged: timeOutCount = 0 } MouseArea { id: control property bool isTouch: false anchors.fill: parent hoverEnabled: true pressAndHoldInterval: 300 acceptedButtons: Qt.LeftButton | Qt.RightButton drag.target: itemLoader drag.onActiveChanged: { if (control.isTouch) return; if (drag.active) { beginDrag(); } } onClicked: { if (isFullScreen) { fullScreenUI.focus = false; } else { normalUI.focus = false; } itemLoader.item.itemClicked(mouse.button); } onPressAndHold: { if (!control.isTouch) return; if (mouse.button === Qt.LeftButton) { beginDrag(); } } onPressed: { itemLoader.grabToImage(function(result) { itemLoader.Drag.imageSource = result.url; }) if (mouse.source === Qt.MouseEventSynthesizedBySystem || mouse.source === Qt.MouseEventSynthesizedByQt) { control.isTouch = true; } else { control.isTouch = false; } } UkuiItems.Tooltip { anchors.fill: parent posFollowCursor: true visible: iconText.truncated && control.containsMouse && !iconItem.hold mainText: model.name } function beginDrag() { if (isFullScreen) { fullScreenUI.focus =false; } else { normalUI.focus = false; } iconItem.hold = true; favoriteView.exchangedStartIndex = itemLoader.visualIndex; itemLoader.sourceId = model.id; // 开始拖拽 itemLoader.Drag.active = true; itemLoader.Drag.startDrag(Qt.CopyAction); } } ColumnLayout { id: itemLayout anchors.fill: parent anchors.topMargin: 8 anchors.bottomMargin: 14 spacing: 4 Item { id: loaderBase Layout.preferredWidth: iconItem.width * 0.6 Layout.preferredHeight: width Layout.alignment: Qt.AlignHCenter Accessible.role: Accessible.Button Accessible.description: "This is an app button on favorite of ukui-menu" Accessible.name: model.id Loader { id: itemLoader anchors.fill: parent Drag.dragType: Drag.None Drag.proposedAction: Qt.CopyAction Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 Drag.source: itemLoader Drag.mimeData: { "source": "ukui-menu", "url": "app://" + model.id } Drag.onActiveChanged: { if (Drag.active) { itemLoader.opacity = 0; } else { itemLoader.item.state = "normal"; iconItem.hold = false; itemLoader.opacity = 1; } } property int visualIndex: 0 property string sourceId: "" property bool isFolder: model.type === DataType.Folder property var icon: model.icon ? model.icon : "" property bool isFavorite: true sourceComponent: { if (type === DataType.Normal) { return appIconComponent; } if (type === DataType.Folder) { return folderIconComponent; } } } } UkuiItems.DtThemeText { id: iconText Layout.fillWidth: true Layout.preferredHeight: contentHeight Layout.maximumHeight: contentHeight text: name elide: Text.ElideRight textColor: Platform.GlobalTheme.textActive horizontalAlignment: Text.AlignHCenter opacity: !iconItem.hold Behavior on opacity { NumberAnimation { duration: 150 } } } } Component { id: appIconComponent Item { state: "normal" states: [ State { name: "prepareToMerge" PropertyChanges { target: iconImage x: 7; y: 7 width: loaderBase.width / 3 height: loaderBase.height / 3 } PropertyChanges { target: mergePrompt backgroundColor: Platform.GlobalTheme.kContainAlphaClick alpha: 1 } }, State { name: "normal" PropertyChanges { target: iconImage x: 0; y: 0 width: loaderBase.width height: loaderBase.height } PropertyChanges { target: mergePrompt backgroundColor: Platform.GlobalTheme.kContainGeneralAlphaNormal alpha: 0 } } ] transitions: [ Transition { NumberAnimation { properties: "width, height, x, y, alpha"; duration: aniDuration; easing.type: Easing.InOutCubic } } ] UkuiItems.Icon { id: iconImage source: icon proxy.appId: desktopName } function itemClicked(mouseButton) { console.log("ICON_CLICK!", "DESKTOPFILE:", id); if (mouseButton === Qt.RightButton) { // 需要用注册的favoriteModel对象,全屏状态无法通过extensionData.favoriteAppsModel取得 favoriteModel.openMenu(index) } else { // var data = {"id": model.id}; // send(data); appManager.launchApp(id); } } } } Component { id: folderIconComponent Item { states: [ State { name: "iconHide" PropertyChanges { target: folderIcon; opacity: 0 } }, State { name: "iconShow" PropertyChanges { target: folderIcon; opacity: 1 } }, State { name: "prepareToMerge" PropertyChanges { target: mergePrompt backgroundColor: Platform.GlobalTheme.kContainAlphaClick alpha: 1 } }, State { name: "normal" PropertyChanges { target: mergePrompt backgroundColor: Platform.GlobalTheme.kContainGeneralAlphaNormal alpha: 0 } } ] transitions: [ Transition { to: "iconShow" NumberAnimation { property: "opacity"; duration: aniDuration; easing.type: Easing.InOutCubic } } ] AppControls2.FolderIcon { id: folderIcon height: width anchors.centerIn: parent alpha: 0.10 icons: icon columns: rows width: isFullScreen ? 84: 40 rows: isFullScreen ? 4 : 2 spacing: isFullScreen ? 4 : 2 padding: isFullScreen ? 8 : 2 radius: isFullScreen ? Platform.GlobalTheme.kRadiusMax : Platform.GlobalTheme.kRadiusNormal } function itemClicked(mouseButton) { if (mouseButton === Qt.RightButton) { favoriteModel.openMenu(index); } else { var x = mapToGlobal(folderIcon.x, folderIcon.y).x; var y = mapToGlobal(folderIcon.x, folderIcon.y).y openFolderSignal(id, name, x, y); // 执行隐藏动画,并且当前图标消失且鼠标区域不可用 state = "iconHide"; control.enabled = false; control.hoverEnabled = false; } } } } // folderFunction function resetOpacity() { // 将state为"iconHide"的应用组,恢复到初始状态 if (type === DataType.Folder) { itemLoader.item.state = "iconShow"; } control.enabled = true; control.hoverEnabled = true; } onHoldChanged: { if (hold) { favoriteView.interactive = false; } else { favoriteView.interactive = contentHeight > favoriteView.parent.height; if (favoriteView.dragTypeIsMerge && (model.id !== favoriteView.mergeToAppId) && (model.type === DataType.Normal)) { opacity = 0; if (favoriteView.isMergeToFolder) { favoriteModel.addAppToFolder(model.id, favoriteView.mergeToAppId); } else { favoriteModel.addAppsToNewFolder(model.id, favoriteView.mergeToAppId); } } else if (favoriteView.exchangedStartIndex !== itemLoader.visualIndex) { favoriteModel.exchangedAppsOrder(favoriteView.exchangedStartIndex, itemLoader.visualIndex); } } } } ukui-menu/qml/extensions/FavoriteExtension.qml0000664000175000017500000001453515160463365020617 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import QtQml.Models 2.1 import QtQuick.Controls 2.5 import org.ukui.menu.core 1.0 import org.ukui.menu.extension 1.0 import AppControls2 1.0 as AppControls2 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems import AppUI 1.0 as AppUI UkuiMenuExtension { MouseArea { id: viewMouseArea anchors.fill: parent hoverEnabled: true acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: { if (mouse.button === Qt.RightButton) { normalUI.focus = false; menu.open(); } else { if (mainWindow.editMode) { mainWindow.editMode = false; } else { folderLoader.isFolderOpened = false; } } } UkuiItems.Menu { id: menu content: UkuiItems.MenuItem { text: qsTr("Editing mode") icon: "edit-symbolic" onClicked: mainWindow.editMode = true; } } UkuiItems.DtThemeBackground { anchors.top: parent.top width: parent.width; height: 1 useStyleTransparency: false backgroundColor: Platform.GlobalTheme.kContainAlphaClick visible: favoriteView.contentY > 0 z: 1 } FolderGridView { id: folderLoader anchors.fill: parent z: 10 isFullScreen: false folderModel: extensionData.folderModel Component.onCompleted: favoriteView.openFolderSignal.connect(initFolder) Component.onDestruction: favoriteView.openFolderSignal.disconnect(initFolder) } Item { anchors.fill: parent anchors.bottomMargin: 8 anchors.leftMargin: 16 anchors.rightMargin: 16 anchors.topMargin: 8 // 拖动到文件到空白区域 添加到收藏 DropArea { z: -10 anchors.fill: parent property int addedIndex: -1 property string addedId: "" function reset() { addedId = ""; var nullIndex = favoriteView.indexAtNullItem(); if (nullIndex > -1 && nullIndex < favoriteView.count) { favoriteView.viewModel.items.remove(nullIndex); } } onEntered: { if (drag.getDataAsString("folder") === "true" || drag.getDataAsString("favorite") === "false") { // 从应用组内部拖拽 || 从左侧列表添加到收藏 // 已经触发从左侧拖动到收藏区域的事件,不需要再次添加空白item let id = ""; if (drag.getDataAsString("url").startsWith("app://")) { id = extensionData.favoriteAppsModel.getDesktopFile(drag.getDataAsString("url")); } if (addedId === id || id === "") { return; } addedId = id addedIndex = favoriteView.count; favoriteView.viewModel.items.insert(addedIndex, {"id":"", "icon":"", "name": ""}); } else if (drag.getDataAsString("favorite") === "true") { drag.accepted = false; } else { // 收藏插件内拖拽 favoriteView.dragTypeIsMerge = false; } } onPositionChanged: { if ((drag.getDataAsString("folder") === "true" || drag.getDataAsString("favorite") === "false") && addedIndex > -1) { var dragIndex = favoriteView.indexAt(drag.x + favoriteView.contentX, drag.y + favoriteView.contentY); if (dragIndex < 0) { return; } favoriteView.viewModel.items.move(addedIndex, dragIndex); addedIndex = dragIndex; } } onDropped: { // folder需要在favorite之前判断,因为应用组内的应用favorite都为true if (drop.getDataAsString("folder") === "true") { reset(); extensionData.favoriteAppsModel.addAppFromFolder(drop.getDataAsString("url").slice(6), addedIndex); folderLoader.isFolderOpened = false; } else if (drop.getDataAsString("favorite") === "false") { reset(); extensionData.favoriteAppsModel.addAppToFavorites(drop.getDataAsString("url").slice(6), addedIndex); } } onExited: reset() } FavoriteGridView { id: favoriteView width: parent.width height: (contentHeight > parent.height) ? parent.height : contentHeight interactive: contentHeight > parent.height isContentShow: !folderLoader.isFolderOpened Component.onCompleted: { favoriteView.viewModel.model = extensionData.favoriteAppsModel folderLoader.turnPageFinished.connect(contentShowFinished) } Component.onDestruction: { folderLoader.turnPageFinished.disconnect(contentShowFinished); } } } } } ukui-menu/qml/extensions/EditModeFlag.qml0000664000175000017500000000164515160463365017425 0ustar fengfengimport QtQuick 2.15 import org.ukui.menu.core 1.0 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform Loader { id: tag property bool isFavorited property bool isEntered: false signal clicked() Component { id: editImage UkuiItems.DtThemeButton { width: 28 height: 28 icon.width: 16 icon.height: 16 background.backgroundColor: Platform.GlobalTheme.lightActive background.alpha: 1 activeFocusOnTab: false onClicked: tag.clicked() onEntered: tag.isEntered = true onExited: tag.isEntered = false background.radius: width / 2 icon.source: isFavorited ? "ukui-cancel-star-symbolic" : "non-starred-symbolic" } } sourceComponent: mainWindow.editMode && (type === DataType.Normal) ? editImage : null } ukui-menu/qml/extensions/FolderParam.qml0000664000175000017500000000455315160463365017336 0ustar fengfeng/* * Copyright (C) 2025, KylinSoft Co., Ltd. * * 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 3 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, see . * * Authors: qiqi49 * */ import QtQml 2.15 import QtQuick 2.15 QtObject { property bool isFullScreen: false property int viewRow: 1 // 应用组底色 property int backgroundWidth: isFullScreen ? 728 : 388 // 全屏模式下, 应用组高度已经包含拖拽提示条的大小 property int backgroundHeight: isFullScreen ? folderIconBase.height : folderIconBase.height + dragHintBarHeight * 2 property int dragHintBarHeight: isFullScreen ? 32 : 20 property int dragHintBarIconSize: isFullScreen ? 32 : 16 property int itemIconSize: isFullScreen ? 69 : 48 // 展开后的应用组相关参数 // isFullScreen ? (itemHeight: 160 + spacing: 16 + margins: 24) : (itemHeight:96 + spacing:4) property rect folderIconBase: isFullScreen ? Qt.rect(0, 0, 720, viewRow*176 + folderContentMargins*2) : Qt.rect(0, 82, 348, viewRow * 100) property int folderContentMargins: isFullScreen ? 12 : 0 property int folderLabelSpacing: isFullScreen ? 8 : 2 property int folderLabelMagrins: isFullScreen ? 8 : 4 // 收起后的应用组相关参数 property rect normalIconBase: isFullScreen ? Qt.rect(0, 0, 84, 84) : Qt.rect(0, 0, 40, 40) property int iconContentMargins: isFullScreen ? 8 : 3 property int iconLabelSpacing: 0 property int iconLabelMagrins: 0 // 页码相关参数 property int pageIndicatorSize: isFullScreen ? 16 : 12 property int pageIndicatorSizeContainMouse: isFullScreen ? 12 : 8 property int pageIndicatorMargin: isFullScreen ? 14 : 6 // 应用组标题相关参数 property int folderNameMargin: isFullScreen ? 12 : 30 property int folderNameHeight: isFullScreen ? 72 : 47 } ukui-menu/qml/extensions/FavoriteGridView.qml0000664000175000017500000001315115160463365020354 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.15 import QtQml.Models 2.1 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.15 import org.ukui.menu.core 1.0 import org.ukui.menu.extension 1.0 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems import AppControls2 1.0 as AppControls2 GridView { id: favoriteView cellWidth: width / column cellHeight: cellWidth + 12 signal openFolderSignal(string folderId, string folderName, int x, int y) signal contentShowFinished() property bool isContentShow property int spacing: 4 property int column: 5 property int row: Math.ceil(count/column) property alias viewModel: visualModel property string mergeToAppId: "" property bool isMergeToFolder: false property bool dragTypeIsMerge: false property int exchangedStartIndex: 0 state: isContentShow ? "contentShow" : "contentHidden" states: [ State { name: "contentHidden" PropertyChanges { target: favoriteView; opacity: 0; scale: 0.95 } }, State { name: "contentShow" PropertyChanges { target: favoriteView; opacity: 1; scale: 1 } } ] transitions: [ Transition { to:"contentHidden" SequentialAnimation { PropertyAnimation { properties: "opacity, scale"; duration: 300; easing.type: Easing.InOutCubic } ScriptAction { script: favoriteView.visible = false } } }, Transition { to: "contentShow" SequentialAnimation { ScriptAction { script: favoriteView.visible = true } PropertyAnimation { properties: "opacity, scale"; duration: 300; easing.type: Easing.InOutCubic } } } ] // 按键导航处理(左右键可以循环) focus: true onActiveFocusChanged: currentIndex = 0 onCountChanged: currentIndex = 0 Keys.onRightPressed: { if(currentIndex === count - 1) { currentIndex = 0; return; } currentIndex++; } Keys.onLeftPressed: { if(currentIndex === 0) { currentIndex = count - 1; return; } currentIndex--; } Keys.onDownPressed: { currentIndex = currentIndex + column < count ? currentIndex + column : currentIndex % column; } Keys.onUpPressed: { currentIndex = currentIndex - column >= 0 ? currentIndex - column : currentIndex + (row - 1) * column <= count - 1 ? currentIndex + (row - 1) * column : currentIndex + (row - 2) * column } displaced: Transition { NumberAnimation { properties: "x,y"; duration: 200; easing.type: Easing.Bezier; easing.bezierCurve: [0.25, 0.1, 0.25, 1.0]} } remove: Transition { NumberAnimation { properties: "opacity"; to: 0; easing.type: Easing.InOutQuad; duration: 200 } } function indexAtNullItem() { var nullItemIndex; for (var i = 0; i < favoriteView.count; i ++) { if (favoriteView.itemAtIndex(i).id === "") { nullItemIndex = i; return nullItemIndex; } } return -1; } model: DelegateModel { id: visualModel delegate: Item { id: container focus: true width: favoriteView.cellWidth height: favoriteView.cellHeight property var id: model.id states: State { when: activeFocus PropertyChanges { target: iconItem borderColor: Platform.GlobalTheme.highlightActive border.width: 2 } } Keys.onReturnPressed: { appManager.launchApp(model.id); } FavoriteDelegate { id: iconItem anchors.fill: parent anchors.margins: 2 isFullScreen: false visualIndex: container.DelegateModel.itemsIndex mergePrompt.anchors.topMargin: 6 mergePrompt.width: 52 mergePrompt.radius: 14 delegateLayout.anchors.leftMargin: 2 delegateLayout.anchors.rightMargin: 2 Component.onCompleted: favoriteView.contentShowFinished.connect(resetOpacity) Component.onDestruction: favoriteView.contentShowFinished.disconnect(resetOpacity) } //编辑模式标志 EditModeFlag { isFavorited: true anchors.top: parent.top anchors.right: parent.right anchors.rightMargin: 10 onClicked: { visualModel.model.removeAppFromFavorites(id); } } } } } ukui-menu/qml/extensions/FolderGridView.qml0000664000175000017500000005773515160463365020030 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.0 import QtQml.Models 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5 import org.ukui.menu.core 1.0 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems import AppUI 1.0 as AppUI Loader { id: root active: false property var folderModel property string folderName: "" property int folderX: 0 property int folderY: 0 property int viewMaxRow: 0 property bool isFolderOpened: false property bool isFullScreen: true property int margins: isFullScreen? 20 : 0 property int animationDuration: 150 signal turnPageFinished() DropArea { id: folderDropBase anchors.fill: parent enabled: isFolderOpened onDropped: { // 在应用组打开时,快速拖拽到外部 - 将应用从组内移除,并放到最后一位 if (drop.getDataAsString("folder") === "true" && isFolderOpened) { favoriteModel.addAppFromFolder(drop.getDataAsString("url").slice(6), favoriteModel.rowCount()); isFolderOpened = false; } } } FolderParam { id: folderParam isFullScreen: root.isFullScreen viewRow: viewMaxRow } function initFolder(id, name, x, y) { folderModel.setFolderId(id); folderName = name; folderX = x; folderY = y; viewMaxRow = Math.ceil(folderModel.count / 4) > 4 ? 4 : Math.ceil(folderModel.count / 4); active = true; isFolderOpened = true; item.viewModel = folderModel; // 内部Gridview的model,在每次显示时需要重新赋值 } sourceComponent: folderComponent Component { id: folderComponent Item { id: folderBase property var viewModel // 内部Gridview的model DropArea { id: dropArea width: folderBackground.width height: folderBackground.height + folderParam.itemIconSize // 图标完全脱离边框 才执行exit操作 anchors.centerIn: folderIconBase enabled: isFolderOpened property int edgeThreshold: 20 + folderParam.itemIconSize // 边缘阈值, 拖拽提示条高度:20 + 图标边缘距离hotSpot距离:48 property int edgeStatus: 0 // 当前边缘状态 0:无 1:左边缘 2:右边缘 property bool isExit: false //完全脱离 property int movedTarget: -1 property int draggedIndex: -1 // 边缘检测定时器 Timer { id: edgeTimer interval: dropArea.isExit ? 1000 : 300 onTriggered: { if (content.isDragging) { if (dropArea.isExit) { dropArea.handleExitAction(); } else { dropArea.handleEdgeAction(); } } } } function handleEdgeAction() { switch (edgeStatus) { case 1: // 左边缘,向前翻页 content.setCurrentPageIndex(content.currentPageIndex - 1); break case 2: // 右边缘,向后翻页 content.setCurrentPageIndex(content.currentPageIndex + 1); break } // 重置状态 edgeStatus = 0; } function handleExitAction() { isFolderOpened = false; } // point 是否在item的中点前 function beforeMidpoint(item, point) { // 获取一个Item的中点坐标 let midPoint = Qt.point(item.x + item.width/2, item.y + item.height/2); return point.x < midPoint.x; } onEntered: { dropArea.isExit = false; edgeTimer.stop(); const obj = JSON.parse(drag.text); if (obj !== null) { draggedIndex = obj.dragStartIndex; } } onPositionChanged: (dragEvent) => { // 多页情况下,处于边缘,左右跨页移动 if (content.pageCount > 1) { const newStatus = (dragEvent.y < edgeThreshold) ? 1 : (dragEvent.y > height - edgeThreshold) ? 2 : 0; if (newStatus !== edgeStatus) { edgeTimer.stop(); edgeStatus = newStatus; if (newStatus !== 0) { edgeTimer.restart(); } } } // 正常拖拽换位 let sourceIndex = dragEvent.source.itemsIndex; let currentPoint = content.getContentPoint(dragEvent.x, dragEvent.y); let targetItem = content.itemAt(currentPoint.x, currentPoint.y); if (targetItem !== null) { let targetIndex = targetItem.DelegateModel.itemsIndex; movedTarget = targetIndex; content.model.items.move(sourceIndex, targetIndex); } } onExited: { edgeTimer.stop(); edgeStatus = 0; dropArea.isExit = true; edgeTimer.restart(); // 拖出应用组范围,之前的移动全都回位 if (movedTarget > -1 && draggedIndex > -1) { content.model.items.move(movedTarget, draggedIndex); movedTarget = -1; draggedIndex = -1; } } onDropped: (dragEvent) => { content.isDragging = false; edgeTimer.stop(); edgeStatus = 0; let currentPoint = content.getContentPoint(dragEvent.x, dragEvent.y); let targetItem = content.itemAt(currentPoint.x, currentPoint.y); const obj = JSON.parse(dragEvent.text); if (targetItem !== null && obj !== null) { folderModel.exchangedOrder(obj.dragStartIndex, targetItem.DelegateModel.itemsIndex); } else { folderModel.exchangedOrder(obj.dragStartIndex, folderModel.count - 1); } } } UkuiItems.DtThemeBackground { id: folderBackground width: folderParam.backgroundWidth height: folderParam.backgroundHeight anchors.centerIn: folderIconBase backgroundColor: isFullScreen ? Platform.GlobalTheme.kContainAlphaClick : Platform.GlobalTheme.kGrayAlpha0 useStyleTransparency: false radius: Platform.GlobalTheme.kRadiusMenu borderColor: Platform.GlobalTheme.kLineTable border.width: content.isDragging && isFolderOpened && !isFullScreen ? 1 : 0 UkuiItems.DtThemeBackground { id: topDragHintBar width: folderBackground.width height: folderParam.dragHintBarHeight anchors.top: folderBackground.top visible: content.isDragging && (content.currentPageIndex > 0) && isFolderOpened backgroundColor: Platform.GlobalTheme.kFontWhiteSecondaryDisable useStyleTransparency: false corners.topLeftRadius: folderBackground.radius corners.topRightRadius: folderBackground.radius corners.bottomLeftRadius: 0 corners.bottomRightRadius: 0 UkuiItems.Icon { width: folderParam.dragHintBarIconSize; height: width anchors.centerIn: parent source: "pan-up-symbolic" } } UkuiItems.DtThemeBackground { id: bottomDragHintBar width: folderBackground.width height: folderParam.dragHintBarHeight anchors.bottom: folderBackground.bottom visible: content.isDragging && content.currentPageIndex < (content.pageCount - 1) && isFolderOpened backgroundColor: Platform.GlobalTheme.kFontWhiteSecondaryDisable useStyleTransparency: false corners.topLeftRadius: 0 corners.topRightRadius: 0 corners.bottomLeftRadius: folderBackground.radius corners.bottomRightRadius: folderBackground.radius UkuiItems.Icon { width: folderParam.dragHintBarIconSize; height: width anchors.centerIn: parent source: "pan-down-symbolic" } } } Item { id: folderIconBase clip: true state: isFolderOpened ? "folderOpened" : "folderHidden" states: [ State { name: "folderOpened" PropertyChanges { target: folderIconBase width: folderParam.folderIconBase.width height: folderParam.folderIconBase.height x: (parent.width - width) / 2 y: isFullScreen ? (parent.height - height) / 2 : folderParam.folderIconBase.y opacity: 1 } PropertyChanges { target: folderBackground width: folderParam.backgroundWidth height: folderParam.backgroundHeight } PropertyChanges { target: content labelSpacing: folderParam.folderLabelSpacing labelMagrins: folderParam.folderLabelMagrins } PropertyChanges { target: contentBase contentMargins: folderParam.folderContentMargins } PropertyChanges { target: folderNameText; opacity: 1 } }, State { name: "folderHidden" PropertyChanges { target: folderIconBase width: folderParam.normalIconBase.width height: folderParam.normalIconBase.height x: root.mapFromGlobal(folderX, 0).x y: root.mapFromGlobal(0, folderY).y opacity: 0 } PropertyChanges { target: folderBackground width: folderParam.normalIconBase.width height: folderParam.normalIconBase.height } PropertyChanges { target: content labelSpacing: folderParam.iconLabelSpacing labelMagrins: folderParam.iconLabelMagrins } PropertyChanges { target: contentBase contentMargins: folderParam.iconContentMargins } PropertyChanges { target: folderNameText; opacity: 0 } } ] transitions: [ Transition { to: "folderHidden" SequentialAnimation { ScriptAction { script: content.mouseEnable = false } ParallelAnimation { PropertyAnimation { target: folderIconBase duration: animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.3, 0.2, 0.1, 1.0] properties: "x, y, width, height, opacity" } PropertyAnimation { target: folderBackground duration: animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.3, 0.2, 0.1, 1.0] properties: "width, height" } PropertyAnimation { target: folderNameText duration: animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.3, 0.2, 0.1, 1.0] properties: "opacity" } PropertyAnimation { target: content duration: animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.3, 0.2, 0.1, 1.0] properties: "labelMagrins, labelSpacing" } PropertyAnimation { target: contentBase duration: animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.3, 0.2, 0.1, 1.0] properties: "contentMargins" } ScriptAction { script: content.normalScreenIconSignal() } } ScriptAction { script: { // 点击空白区域实现重命名并收起应用组 var tmpText = folderNameText.text; if (tmpText.trim() === "") { folderNameText.text = qsTr("Folder"); } if (folderNameText.text !== folderNameText.textEdited) { folderModel.renameFolder(folderNameText.text); } content.hideFolder() } } } }, Transition { to: "folderOpened" SequentialAnimation { ParallelAnimation { PropertyAnimation { target: folderIconBase duration: animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.3, 0.2, 0.1, 1.0] properties: "x, y, width, height, radius, alpha, opacity" } PropertyAnimation { target: folderBackground duration: animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.3, 0.2, 0.1, 1.0] properties: "width, height" } PropertyAnimation { target: folderNameText duration: animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.3, 0.2, 0.1, 1.0] properties: "opacity" } PropertyAnimation { target: content duration: animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.3, 0.2, 0.1, 1.0] properties: "labelMagrins, labelSpacing" } PropertyAnimation { target: contentBase duration: animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.3, 0.2, 0.1, 1.0] properties: "contentMargins" } } ScriptAction { script: { content.mouseEnable = true } } } } ] Item { id: contentBase anchors.fill: parent property real contentMargins anchors.margins: contentMargins FolderContent { id: content width: parent.width height: contentItem.childrenRect.height x: 0; y: 0 isFullScreen: root.isFullScreen // 在整体拖拽结束后,Loader的组件才可以销毁 onIsDraggingChanged: { if (!isDragging && !isFolderOpened) { root.active = false; // TODO: 应用组变为一个Loader,一直显示 } } } } } MouseArea { id: baseMouseArea anchors.fill: dropArea enabled: isFolderOpened z: -10 onWheel: (wheel) => { if (wheel.angleDelta.y < 0) { content.setCurrentPageIndex(content.currentPageIndex + 1); // 滚轮向下,向下翻页 } else if (wheel.angleDelta.y > 0) { content.setCurrentPageIndex(content.currentPageIndex - 1); // 滚轮向上,向上翻页 } } property bool isDragging: false property real startY: 0 property real startContentY: 0 property real yOffset: 0 onPressed: { startY = mouseY; startContentY = content.y; baseMouseArea.isDragging = true; } onPositionChanged: { if (pressed && content.pageCount > 1) { // 计算Y轴位移 yOffset = mouseY - startY; var newY = startContentY + yOffset; // 限制滚动范围、弹性效果 var minY = -(content.cellHeight * content.maxRows * (content.pageCount - 1)); var maxY = 0; if (newY > maxY) { newY = maxY + (yOffset) * 0.3; } else if (newY < minY) { newY = minY + (yOffset) * 0.3; } content.y = newY; } } onReleased: { baseMouseArea.isDragging = false; // 判断翻页方向 var pageHeight = content.cellHeight * content.maxRows; var targetPageIndex = content.currentPageIndex; var threshold = pageHeight * 0.3; // 计算翻页阈值: 页面高度的30% if (yOffset > threshold) { // 向下拖动超过阈值 - 上一页 targetPageIndex = Math.max(0, content.currentPageIndex - 1); } else if (yOffset < -threshold) { // 向上拖动超过阈值 - 下一页 targetPageIndex = Math.min(content.pageCount - 1, content.currentPageIndex + 1); } else { // 根据当前位置决定目标页面 var currentPageOffset = -content.y % pageHeight; if (currentPageOffset > pageHeight / 2) { targetPageIndex = Math.floor(-content.y / pageHeight) + 1; } else { targetPageIndex = Math.floor(-content.y / pageHeight); } targetPageIndex = Math.max(0, Math.min(content.pageCount - 1, targetPageIndex)); } content.setCurrentPageIndex(targetPageIndex); yOffset = 0; } } // 多页时右侧导航栏 ListView { id: pageIndicator width: contentItem.childrenRect.width height: contentItem.childrenRect.height interactive: false model: content.pageCount visible: isFolderOpened && (count > 1) anchors.right: folderBackground.right anchors.rightMargin: folderParam.pageIndicatorMargin anchors.verticalCenter: folderBackground.verticalCenter delegate: MouseArea { width: folderParam.pageIndicatorSize; height: width hoverEnabled: true onClicked: { content.setCurrentPageIndex(index); } UkuiItems.DtThemeBackground { width: parent.containsMouse ? folderParam.pageIndicatorSizeContainMouse : parent.width / 2; height: width; radius: width / 2 anchors.centerIn: parent backgroundColor: (index === content.currentPageIndex) ? Platform.GlobalTheme.kFontStrong : Platform.GlobalTheme.kGrayAlpha7 useStyleTransparency: false } } } AppUI.EditText { id: folderNameText anchors.bottom: folderIconBase.top anchors.bottomMargin: folderParam.folderNameMargin anchors.horizontalCenter: folderIconBase.horizontalCenter height: folderParam.folderNameHeight; width: folderIconBase.width textEdited: folderName textCenterIn: true textSizeOffset: isFullScreen ? 13 : 1 textBorderWidth: isFullScreen ? 2 : 1 onTextEditingFinished: text=> { folderModel.renameFolder(text); } } } } } ukui-menu/qml/extensions/FolderContent.qml0000664000175000017500000002353515160463365017711 0ustar fengfeng/* * Copyright (C) 2024, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.15 import QtQml.Models 2.12 import QtQuick.Layouts 1.12 import QtQuick.Controls 2.5 import org.ukui.menu.core 1.0 import org.ukui.quick.platform 1.0 as Platform import org.ukui.quick.items 1.0 as UkuiItems GridView { id: folderGridView property bool needHide: false // 应用组展开收起 相关 property bool mouseEnable: false property real labelMagrins property real labelSpacing property bool isFullScreen: false property int maxItemNumPerPage: 16 property int maxItemNumPerRow: 4 property int maxRows: 4 // 导航栏 相关 property int pageCount: Math.ceil(count / maxItemNumPerPage) property int currentPageIndex: 0 property bool isDragging: false Behavior on y { enabled: !baseMouseArea.isDragging NumberAnimation { duration: 220 easing.type: Easing.Bezier easing.bezierCurve: [0.25, 0.1, 0.25, 1.0] onRunningChanged: { if (running && needHide) { reset(); } } } } onPageCountChanged: setCurrentPageIndex(0) function setCurrentPageIndex(index) { if (index < 0 || index > pageCount -1) { return; } currentPageIndex = index; y = -(cellHeight * maxRows * currentPageIndex); // 直接翻页效果 } function normalScreenIconSignal() { turnPageFinished(); } // 获取GridView的坐标对应的ContentItem内部坐标 // GridView与dropArea的大小需要重合 function getContentPoint(localX, localY) { return Qt.point(localX - x, localY - y); } function hideFolder() { needHide = true; // TODO:在非第一页时,先执行翻页动画,再隐藏 if (y !== 0) { setCurrentPageIndex(0); } else { reset(); } } function reset() { turnPageFinished(); if (!isDragging) { root.active = false; } needHide = false; } cellHeight: isFullScreen ? cellWidth : (cellWidth + 12) cellWidth: width / maxItemNumPerRow interactive: false onCountChanged: { if (count === 0) { reset(); folderLoader.isFolderOpened = false; } } model: DelegateModel { model: folderBase.viewModel delegate: Item { id: delegateBase width: folderGridView.cellWidth height: folderGridView.cellHeight Accessible.role: Accessible.Button Accessible.description: "This is an app button on favorite of ukui-menu" Accessible.name: id MouseArea { id: control hoverEnabled: true enabled: mouseEnable anchors.fill: parent anchors.margins: labelMagrins acceptedButtons: Qt.LeftButton | Qt.RightButton UkuiItems.Tooltip { anchors.fill: parent posFollowCursor: true visible: nameText.truncated mainText: nameText.text } UkuiItems.DtThemeBackground { anchors.fill: parent useStyleTransparency: false backgroundColor: isDragging ? Platform.GlobalTheme.kContainGeneralAlphaNormal : parent.containsPress ? Platform.GlobalTheme.kContainAlphaClick : parent.containsMouse ? Platform.GlobalTheme.kContainAlphaHover : Platform.GlobalTheme.kContainGeneralAlphaNormal radius: Platform.GlobalTheme.kRadiusMax clip: false ColumnLayout { anchors.fill: parent anchors.margins: labelMagrins spacing: labelSpacing Item { id: iconBase Layout.minimumHeight: 16 Layout.minimumWidth: 16 Layout.maximumWidth: 96 Layout.maximumHeight: 96 Layout.preferredWidth: parent.width - (isFullScreen ? 48 : 23) Layout.preferredHeight: width Layout.alignment: Qt.AlignHCenter UkuiItems.Icon { id: iconImage width: iconBase.width height: iconBase.width source: icon proxy.appId: desktopName anchors.centerIn: parent // Drag property int itemsIndex: delegateBase.DelegateModel.itemsIndex Drag.dragType: Drag.None Drag.proposedAction: Qt.CopyAction Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 // mimeData在脱拖拽开始时确定,并不再随属性变化更新 Drag.mimeData: { "source": "ukui-menu", "folder": "true", "url": "app://" + model.id, "text/plain": '{"dragStartIndex":' + delegateBase.DelegateModel.itemsIndex + '}' } Drag.onActiveChanged: { if (Drag.active) { iconImage.opacity = 0; } else { isDragging = false; iconImage.opacity = 1; } } } } UkuiItems.DtThemeText { id: nameText text: name elide: Text.ElideRight textColor: Platform.GlobalTheme.textActive Layout.preferredHeight: contentHeight Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter opacity: !iconImage.Drag.active Behavior on opacity { NumberAnimation { duration: 150 } } } } } onClicked: { console.log("ICON_CLICK!", "DESKTOPFILE:", id); if (mouse.button === Qt.RightButton) { menuManager.showMenu(id, MenuInfo.Folder); return; } if (mouse.button === Qt.LeftButton) { appManager.launchApp(id); return; } } // Drag pressAndHoldInterval: 300 property bool isTouch: false drag.target: iconImage drag.onActiveChanged: { if (control.isTouch) return; if (drag.active) { beginDrag(); } else { isDragging = false; } } onPressAndHold: { if (!control.isTouch) return; if (mouse.button === Qt.LeftButton) { beginDrag(); } } onPressed: { iconImage.grabToImage(function (result) { iconImage.Drag.imageSource = result.url; }) if (mouse.source === Qt.MouseEventSynthesizedBySystem || mouse.source === Qt.MouseEventSynthesizedByQt) { control.isTouch = true; } else { control.isTouch = false; } } function beginDrag() { if (isFullScreen) { fullScreenUI.focus = false; } else { normalUI.focus = false; } // 开始拖拽 isDragging = true; iconImage.Drag.active = true; iconImage.Drag.startDrag(Qt.CopyAction); } } EditModeFlag { isFavorited: true anchors.top: parent.top anchors.right: parent.right anchors.rightMargin: 10 onClicked: { extensionData.favoriteAppsModel.removeAppFromFavorites(id); } } } } displaced: Transition { NumberAnimation { properties: "x,y"; duration: 300 } } } ukui-menu/qml/main.qml0000664000175000017500000002132215160463365013660 0ustar fengfeng/* * Copyright (C) 2023, KylinSoft Co., Ltd. * * 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 3 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, see . * */ import QtQuick 2.12 import AppUI 1.0 as AppUI import AppControls2 1.0 as AppControls2 import org.ukui.menu.core 1.0 import org.ukui.quick.items 1.0 as UkuiItems import org.ukui.quick.platform 1.0 as Platform Item { id: root clip: true property int animationDuration: menuSetting.get("animationDuration") property var normalGeometry: mainWindow.normalRect property string currentSearchText: "" property bool focusOnFoldButton: false property bool beforeStateChange: false LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft LayoutMirroring.childrenInherit: true; MouseArea { z: 1 anchors.fill: parent acceptedButtons: { let button = Qt.NoButton; if (functionControl.disableMouseLeftButton) { button |= Qt.LeftButton } if (functionControl.disableMouseRightButton) { button |= Qt.RightButton } return button; } enabled: functionControl.disableMouseLeftButton | functionControl.disableMouseRightButton } Connections { target: mainWindow function onBeforeFullScreenChanged(){ beforeStateChange = true; } } Component.onCompleted: { mainWindow.fullScreenChanged.connect(enterFullScreen); mainWindow.beforeFullScreenExited.connect(exitFullScreen); menuSetting.changed.connect(updateAnimationDuration); } function enterFullScreen() { if (mainWindow.isFullScreen) { normalHide.start(); fullShow.start(); enterFullScreenAnimation.start(); } else { beforeStateChange = false; } } function exitFullScreen() { normalShow.start(); fullHide.start(); exitFullScreenAnimation.start(); } function updateAnimationDuration(key) { if (key === "animationDuration") { root.animationDuration = menuSetting.get("animationDuration"); } } UkuiItems.DtThemeBackground { id: backgroundMask // 初始状态默认为normalScreen x: mainWindow.isFullScreen ? 0 : normalGeometry.x y: mainWindow.isFullScreen ? 0 : normalGeometry.y height: mainWindow.isFullScreen ? root.height : normalGeometry.height width: mainWindow.isFullScreen ? root.width : normalGeometry.width radius: mainWindow.isFullScreen ? 0 : Platform.GlobalTheme.kRadiusWindow border.width: mainWindow.isFullScreen ? 0 : 1 //TODO borderColor: Platform.GlobalTheme.textActive borderAlpha: 0.15 Component.onCompleted: setWindowBlurRegion(x, y, width, height, radius) onRadiusChanged: setWindowBlurRegion(x, y, width, height, radius) onWidthChanged: setWindowBlurRegion(x, y, width, height, radius) onHeightChanged: setWindowBlurRegion(x, y, width, height, radius) ParallelAnimation { id: enterFullScreenAnimation PropertyAnimation { target: backgroundMask; property: "x" from: normalGeometry.x; to: 0 duration: root.animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.25, 0.1, 0.25, 1.0] } PropertyAnimation { target: backgroundMask; property: "y" from: normalGeometry.y; to: 0 duration: root.animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.25, 0.1, 0.25, 1.0] } PropertyAnimation { target: backgroundMask; property: "height" from: normalGeometry.height; to: root.height duration: root.animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.25, 0.1, 0.25, 1.0] } PropertyAnimation { target: backgroundMask; property: "width" from: normalGeometry.width; to: root.width duration: root.animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.25, 0.1, 0.25, 1.0] } onStarted: { beforeStateChange = false; fullScreenLoader.active = true; } onFinished: { normalScreenLoader.active = false; } } ParallelAnimation { id: exitFullScreenAnimation PropertyAnimation { target: backgroundMask; property: "x" from: 0; to: normalGeometry.x duration: root.animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.25, 0.1, 0.25, 1.0] } PropertyAnimation { target: backgroundMask; property: "y" from: 0; to: normalGeometry.y duration: root.animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.25, 0.1, 0.25, 1.0] } PropertyAnimation { target: backgroundMask; property: "height" from: root.height; to: normalGeometry.height duration: root.animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.25, 0.1, 0.25, 1.0] } PropertyAnimation { target: backgroundMask; property: "width" from: root.width; to: normalGeometry.width duration: root.animationDuration; easing.type: Easing.Bezier; easing.bezierCurve: [0.25, 0.1, 0.25, 1.0] } onStarted: { normalScreenLoader.active = true; } onFinished: { fullScreenLoader.active = false; mainWindow.isFullScreen = false; } } function setWindowBlurRegion (x, y, width, height, radius) { if (beforeStateChange) return; mainWindow.changeWindowBlurRegion(x, y, width, height, radius) } } UkuiItems.DtDropShadow { visible: !mainWindow.isFullScreen anchors.fill: backgroundMask shadowRadius: backgroundMask.radius shadowData: Platform.GlobalTheme.kShadowMenu } Loader { id: normalScreenLoader focus: !mainWindow.isFullScreen active: false x: normalGeometry.x y: normalGeometry.y height: normalGeometry.height width: normalGeometry.width sourceComponent: normalComponent NumberAnimation { id: normalShow; target: normalScreenLoader; properties: "opacity"; from: 0; to: 1; duration: root.animationDuration; easing.type: Easing.InQuint } NumberAnimation { id: normalHide; target: normalScreenLoader; properties: "opacity"; from: 1; to: 0; duration: root.animationDuration; easing.type: Easing.OutQuint } Component.onCompleted: { active = mainWindow.isFullScreen ? false : true; opacity = mainWindow.isFullScreen ? 0 : 1; } Keys.onPressed: { item.keyPressed(event); } } Loader { id: fullScreenLoader x: 0; y: 0 width: parent.width; height: parent.height focus: mainWindow.isFullScreen sourceComponent: fullSceenComponent ParallelAnimation { id: fullShow NumberAnimation { target: fullScreenLoader; properties: "opacity"; from: 0; to: 1; duration: root.animationDuration; easing.type: Easing.InQuint } NumberAnimation { target: fullScreenLoader; properties: "y"; from: -100; to: 0; duration: root.animationDuration; easing.type: Easing.InOutQuad } } NumberAnimation { id: fullHide; target: fullScreenLoader; properties: "opacity"; from: 1; to: 0; duration: root.animationDuration; easing.type: Easing.OutQuint } Component.onCompleted: { active = mainWindow.isFullScreen ? true : false; opacity = mainWindow.isFullScreen ? 1 : 0; } Keys.onPressed: { item.keyPressed(event); } } Component { id: fullSceenComponent AppUI.FullScreenUI {} } Component { id: normalComponent AppUI.NormalUI { } } } ukui-menu/qml/qml.qrc0000664000175000017500000000352715160463365013530 0ustar fengfeng main.qml MenuMainWindow.qml AppUI/qmldir AppUI/NormalUI.qml AppUI/FullScreenUI.qml AppUI/AppPage.qml AppUI/Sidebar.qml AppUI/WidgetPage.qml AppUI/AppList.qml AppUI/FullScreenHeader.qml AppUI/FullScreenContent.qml AppUI/FullScreenFooter.qml AppUI/AppListHeader.qml AppUI/AppListActions.qml AppUI/SearchInputBar.qml AppUI/AppListView.qml AppControls2/qmldir AppControls2/App.qml AppControls2/ScrollBar.qml AppControls2/IconLabel.qml AppControls2/AppItem.qml AppControls2/LabelItem.qml AppControls2/FolderItem.qml extensions/FavoriteExtension.qml extensions/FolderContent.qml extensions/FolderParam.qml extensions/FavoriteDelegate.qml extensions/FavoriteGridView.qml extensions/FolderGridView.qml AppControls2/RoundButton.qml AppControls2/FolderIcon.qml AppUI/SelectionPage.qml AppUI/AppPageContent.qml AppUI/PluginSelectButton.qml AppUI/EditText.qml AppUI/AppPageSearch.qml AppUI/FullScreenAppList.qml AppUI/FullScreenAppItem.qml AppUI/AppLabelPage.qml extensions/EditModeFlag.qml ukui-menu/qml/org/0000775000175000017500000000000015160463353013005 5ustar fengfengukui-menu/qml/org/ukui/0000775000175000017500000000000015160463353013762 5ustar fengfengukui-menu/qml/org/ukui/menu/0000775000175000017500000000000015160463353014726 5ustar fengfengukui-menu/qml/org/ukui/menu/extension/0000775000175000017500000000000015160463353016742 5ustar fengfengukui-menu/qml/org/ukui/menu/extension/qmldir0000664000175000017500000000011315160463353020150 0ustar fengfengmodule org.ukui.menu.extension UkuiMenuExtension 1.0 UkuiMenuExtension.qml ukui-menu/qml/org/ukui/menu/extension/UkuiMenuExtension.qml0000664000175000017500000000020415160463353023110 0ustar fengfengimport QtQuick 2.0 Item { property var extensionData; property Component extensionMenu: null; signal send(var data); } ukui-menu/.gitignore0000664000175000017500000000121015160463353013407 0ustar fengfeng# C++ objects and libs *.slo *.lo *.o *.a *.la *.lai *.so *.so.* *.dll *.dylib # Qt-es object_script.*.Release object_script.*.Debug *_plugin_import.cpp /.qmake.cache /.qmake.stash *.pro.user *.pro.user.* *.qbs.user *.qbs.user.* *.moc moc_*.cpp moc_*.h qrc_*.cpp ui_*.h *.qmlc *.jsc Makefile* *build-* *.qm *.prl # Qt unit tests target_wrapper.* # QtCreator *.autosave # QtCreator Qml *.qmlproject.user *.qmlproject.user.* # QtCreator CMake CMakeLists.txt.user* # QtCreator 4.8< compilation database compile_commands.json # QtCreator local machine specific files for imported projects *creator.user* *_qmlcache.qrc *.idea build *-build-* ukui-menu/cmake/0000775000175000017500000000000015160463365012510 5ustar fengfengukui-menu/cmake/ukui-menu.pc.in0000664000175000017500000000054615160463353015362 0ustar fengfengprefix=/usr exec_prefix=${prefix} libdir=${prefix}/lib/@CMAKE_LIBRARY_ARCHITECTURE@ includedir=${prefix}/include/@UKUI_MENU_LIBRARY_NAME@ Name: @UKUI_MENU_LIBRARY_NAME@ Description: @UKUI_MENU_LIBRARY_NAME@ lib header files URL: https://www.ukui.org/ Version: @UKUI_MENU_LIBRARY_VERSION@ Cflags: -I${includedir} Libs: -L${libdir} -l@UKUI_MENU_LIBRARY_NAME@ ukui-menu/cmake/ukui-menu-config.cmake.in0000664000175000017500000000013315160463365017276 0ustar fengfeng@PACKAGE_INIT@ include("${CMAKE_CURRENT_LIST_DIR}/@UKUI_MENU_LIBRARY_NAME@-targets.cmake") ukui-menu/cmake/UkuiPluginTranslationTs.cmake0000664000175000017500000000605215160463353020334 0ustar fengfengmacro(ukui_plugin_translate_ts PLUGIN) set(TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/translation/${PLUGIN}_zh_CN.ts) set(BO_TS_FILES ${CMAKE_CURRENT_SOURCE_DIR}/translation/${PLUGIN}_bo_CN.ts) set(B_QM_FILES ${CMAKE_CURRENT_BINARY_DIR}/translation/) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/translation/) else() execute_process( COMMAND mkdir ${CMAKE_CURRENT_SOURCE_DIR}/translation/ ) endif() if(EXISTS ${TS_FILES}) message(STATUS "${TS_FILES} is EXISTS") execute_process( COMMAND lupdate -recursive ${CMAKE_CURRENT_SOURCE_DIR} -target-language zh_CN -ts ${TS_FILES} ) execute_process( COMMAND lrelease ${TS_FILES} ) else() execute_process( COMMAND lupdate -recursive ${CMAKE_CURRENT_SOURCE_DIR} -target-language zh_CN -ts ${TS_FILES} ) execute_process( COMMAND lrelease ${TS_FILES} ) endif() if(EXISTS ${BO_TS_FILES}) message(STATUS "${BO_TS_FILES} is EXISTS") execute_process( COMMAND lupdate -recursive ${CMAKE_CURRENT_SOURCE_DIR} -target-language bo_CN -ts ${BO_TS_FILES} ) execute_process( COMMAND lrelease ${BO_TS_FILES} ) else() execute_process( COMMAND lupdate -recursive ${CMAKE_CURRENT_SOURCE_DIR} -target-language bo_CN -ts ${BO_TS_FILES} ) execute_process( COMMAND lrelease ${BO_TS_FILES} ) endif() if(EXISTS ${B_QM_FILES}) message(STATUS "${PLUGIN} buildQM dir is EXISTS") else() message(STATUS "${PLUGIN} buildQM dir is not EXISTS") execute_process( COMMAND mkdir ${B_QM_FILES} ) message(STATUS "${PLUGIN} buildQM dir is created") endif() set(P_QM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/translation/${PLUGIN}_zh_CN.qm) set(BO_QM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/translation/${PLUGIN}_bo_CN.qm) if(EXISTS ${P_QM_FILES}) message(STATUS "${PLUGIN} proQM file is EXISTS") execute_process( COMMAND cp -f ${P_QM_FILES} ${B_QM_FILES} ) execute_process( COMMAND rm -f ${P_QM_FILES} ) message(STATUS "${PLUGIN} buildQM file is created") else() message(STATUS "${PLUGIN} buildQM file is not EXISTS") endif() if(EXISTS ${BO_QM_FILES}) message(STATUS "${PLUGIN} proQM file is EXISTS") execute_process( COMMAND cp -f ${BO_QM_FILES} ${B_QM_FILES} ) execute_process( COMMAND rm -f ${BO_QM_FILES} ) message(STATUS "${PLUGIN} buildQM file is created") else() message(STATUS "${PLUGIN} buildQM file is not EXISTS") endif() if(${PLUGIN} STREQUAL "panel") set(P_QM_INSTALL ${PACKAGE_DATA_DIR}/${PLUGIN}/translation) message(STATUS " panel translation install : ${P_QM_INSTALL}") else() set(P_QM_INSTALL ${PACKAGE_DATA_DIR}/plugin-${PLUGIN}/translation) message(STATUS " plugin ${PLUGIN} translation install : ${P_QM_INSTALL}") endif() install(DIRECTORY ${B_QM_FILES} DESTINATION ${P_QM_INSTALL}) ADD_DEFINITIONS(-DQM_INSTALL=\"${P_QM_INSTALL}/${PLUGIN}_zh_CN.qm\") ADD_DEFINITIONS(-DBO_QM_INSTALL=\"${P_QM_INSTALL}/${PLUGIN}_bo_CN.qm\") ADD_DEFINITIONS(-DPLUGINNAME=\"${PLUGIN}\") endmacro() ukui-menu/NEWS0000664000175000017500000000014515160463353012124 0ustar fengfeng### ukui-menu 2.0.0 * Rewrite the project using Qt. ### ukui-menu 1.0.0 * Fork from mate-menu ukui-menu/3rd-parties/0000775000175000017500000000000015160463353013562 5ustar fengfengukui-menu/3rd-parties/qtsingleapplication/0000775000175000017500000000000015160463353017634 5ustar fengfengukui-menu/3rd-parties/qtsingleapplication/src/0000775000175000017500000000000015160463353020423 5ustar fengfengukui-menu/3rd-parties/qtsingleapplication/src/qtlocalpeer.h0000664000175000017500000000144515160463353023113 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #ifndef QTLOCALPEER_H #define QTLOCALPEER_H #include #include #include #include "qtlockedfile.h" class QtLocalPeer : public QObject { Q_OBJECT public: QtLocalPeer(QObject *parent = 0, const QString &appId = QString()); bool isClient(); bool sendMessage(const QString &message, int timeout); QString applicationId() const { return id; } Q_SIGNALS: void messageReceived(const QString &message); protected Q_SLOTS: void receiveConnection(); protected: QString id; QString socketName; QLocalServer* server; QtLP_Private::QtLockedFile lockFile; private: static const char* ack; }; #endif // QTLOCALPEER_H ukui-menu/3rd-parties/qtsingleapplication/src/qtsingleapplication.pri0000664000175000017500000000111015160463353025202 0ustar fengfenginclude(../common.pri) INCLUDEPATH += $$PWD DEPENDPATH += $$PWD QT *= network greaterThan(QT_MAJOR_VERSION, 4): QT *= widgets qtsingleapplication-uselib:!qtsingleapplication-buildlib { LIBS += -L$$QTSINGLEAPPLICATION_LIBDIR -l$$QTSINGLEAPPLICATION_LIBNAME } else { SOURCES += $$PWD/qtsingleapplication.cpp $$PWD/qtlocalpeer.cpp HEADERS += $$PWD/qtsingleapplication.h $$PWD/qtlocalpeer.h } win32 { contains(TEMPLATE, lib):contains(CONFIG, shared):DEFINES += QT_QTSINGLEAPPLICATION_EXPORT else:qtsingleapplication-uselib:DEFINES += QT_QTSINGLEAPPLICATION_IMPORT } ukui-menu/3rd-parties/qtsingleapplication/src/qtlockedfile.cpp0000664000175000017500000001020215160463353023570 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include "qtlockedfile.h" /*! \class QtLockedFile \brief The QtLockedFile class extends QFile with advisory locking functions. A file may be locked in read or write mode. Multiple instances of \e QtLockedFile, created in multiple processes running on the same machine, may have a file locked in read mode. Exactly one instance may have it locked in write mode. A read and a write lock cannot exist simultaneously on the same file. The file locks are advisory. This means that nothing prevents another process from manipulating a locked file using QFile or file system functions offered by the OS. Serialization is only guaranteed if all processes that access the file use QLockedFile. Also, while holding a lock on a file, a process must not open the same file again (through any API), or locks can be unexpectedly lost. The lock provided by an instance of \e QtLockedFile is released whenever the program terminates. This is true even when the program crashes and no destructors are called. */ /*! \enum QtLockedFile::LockMode This enum describes the available lock modes. \value ReadLock A read lock. \value WriteLock A write lock. \value NoLock Neither a read lock nor a write lock. */ /*! Constructs an unlocked \e QtLockedFile object. This constructor behaves in the same way as \e QFile::QFile(). \sa QFile::QFile() */ QtLockedFile::QtLockedFile() : QFile() { #ifdef Q_OS_WIN wmutex = 0; rmutex = 0; #endif m_lock_mode = NoLock; } /*! Constructs an unlocked QtLockedFile object with file \a name. This constructor behaves in the same way as \e QFile::QFile(const QString&). \sa QFile::QFile() */ QtLockedFile::QtLockedFile(const QString &name) : QFile(name) { #ifdef Q_OS_WIN wmutex = 0; rmutex = 0; #endif m_lock_mode = NoLock; } /*! Opens the file in OpenMode \a mode. This is identical to QFile::open(), with the one exception that the Truncate mode flag is disallowed. Truncation would conflict with the advisory file locking, since the file would be modified before the write lock is obtained. If truncation is required, use resize(0) after obtaining the write lock. Returns true if successful; otherwise false. \sa QFile::open(), QFile::resize() */ bool QtLockedFile::open(OpenMode mode) { if (mode & QIODevice::Truncate) { qWarning("QtLockedFile::open(): Truncate mode not allowed."); return false; } return QFile::open(mode); } /*! Returns \e true if this object has a in read or write lock; otherwise returns \e false. \sa lockMode() */ bool QtLockedFile::isLocked() const { return m_lock_mode != NoLock; } /*! Returns the type of lock currently held by this object, or \e QtLockedFile::NoLock. \sa isLocked() */ QtLockedFile::LockMode QtLockedFile::lockMode() const { return m_lock_mode; } /*! \fn bool QtLockedFile::lock(LockMode mode, bool block = true) Obtains a lock of type \a mode. The file must be opened before it can be locked. If \a block is true, this function will block until the lock is aquired. If \a block is false, this function returns \e false immediately if the lock cannot be aquired. If this object already has a lock of type \a mode, this function returns \e true immediately. If this object has a lock of a different type than \a mode, the lock is first released and then a new lock is obtained. This function returns \e true if, after it executes, the file is locked by this object, and \e false otherwise. \sa unlock(), isLocked(), lockMode() */ /*! \fn bool QtLockedFile::unlock() Releases a lock. If the object has no lock, this function returns immediately. This function returns \e true if, after it executes, the file is not locked by this object, and \e false otherwise. \sa lock(), isLocked(), lockMode() */ /*! \fn QtLockedFile::~QtLockedFile() Destroys the \e QtLockedFile object. If any locks were held, they are released. */ ukui-menu/3rd-parties/qtsingleapplication/src/qtlockedfile.h0000664000175000017500000000254715160463353023252 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #ifndef QTLOCKEDFILE_H #define QTLOCKEDFILE_H #include #ifdef Q_OS_WIN #include #endif #if defined(Q_OS_WIN) # if !defined(QT_QTLOCKEDFILE_EXPORT) && !defined(QT_QTLOCKEDFILE_IMPORT) # define QT_QTLOCKEDFILE_EXPORT # elif defined(QT_QTLOCKEDFILE_IMPORT) # if defined(QT_QTLOCKEDFILE_EXPORT) # undef QT_QTLOCKEDFILE_EXPORT # endif # define QT_QTLOCKEDFILE_EXPORT __declspec(dllimport) # elif defined(QT_QTLOCKEDFILE_EXPORT) # undef QT_QTLOCKEDFILE_EXPORT # define QT_QTLOCKEDFILE_EXPORT __declspec(dllexport) # endif #else # define QT_QTLOCKEDFILE_EXPORT #endif namespace QtLP_Private { class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile { public: enum LockMode { NoLock = 0, ReadLock, WriteLock }; QtLockedFile(); QtLockedFile(const QString &name); ~QtLockedFile(); bool open(OpenMode mode); bool lock(LockMode mode, bool block = true); bool unlock(); bool isLocked() const; LockMode lockMode() const; private: #ifdef Q_OS_WIN Qt::HANDLE wmutex; Qt::HANDLE rmutex; QVector rmutexes; QString mutexname; Qt::HANDLE getMutexHandle(int idx, bool doCreate); bool waitMutex(Qt::HANDLE mutex, bool doBlock); #endif LockMode m_lock_mode; }; } #endif ukui-menu/3rd-parties/qtsingleapplication/src/qtsinglecoreapplication.h0000664000175000017500000000126515160463353025523 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #ifndef QTSINGLECOREAPPLICATION_H #define QTSINGLECOREAPPLICATION_H #include class QtLocalPeer; class QtSingleCoreApplication : public QCoreApplication { Q_OBJECT public: QtSingleCoreApplication(int &argc, char **argv); QtSingleCoreApplication(const QString &id, int &argc, char **argv); bool isRunning(); QString id() const; public Q_SLOTS: bool sendMessage(const QString &message, int timeout = 5000); Q_SIGNALS: void messageReceived(const QString &message); private: QtLocalPeer* peer; }; #endif // QTSINGLECOREAPPLICATION_H ukui-menu/3rd-parties/qtsingleapplication/src/QtLockedFile0000664000175000017500000000003215160463353022647 0ustar fengfeng#include "qtlockedfile.h" ukui-menu/3rd-parties/qtsingleapplication/src/qtlockedfile_unix.cpp0000664000175000017500000000305415160463353024642 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include #include #include #include #include "qtlockedfile.h" bool QtLockedFile::lock(LockMode mode, bool block) { if (!isOpen()) { qWarning("QtLockedFile::lock(): file is not opened"); return false; } if (mode == NoLock) return unlock(); if (mode == m_lock_mode) return true; if (m_lock_mode != NoLock) unlock(); struct flock fl; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_type = (mode == ReadLock) ? F_RDLCK : F_WRLCK; int cmd = block ? F_SETLKW : F_SETLK; int ret = fcntl(handle(), cmd, &fl); if (ret == -1) { if (errno != EINTR && errno != EAGAIN) qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); return false; } m_lock_mode = mode; return true; } bool QtLockedFile::unlock() { if (!isOpen()) { qWarning("QtLockedFile::unlock(): file is not opened"); return false; } if (!isLocked()) return true; struct flock fl; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fl.l_type = F_UNLCK; int ret = fcntl(handle(), F_SETLKW, &fl); if (ret == -1) { qWarning("QtLockedFile::lock(): fcntl: %s", strerror(errno)); return false; } m_lock_mode = NoLock; return true; } QtLockedFile::~QtLockedFile() { if (isOpen()) unlock(); } ukui-menu/3rd-parties/qtsingleapplication/src/qtsingleapplication.cpp0000664000175000017500000002325015160463353025203 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include "qtsingleapplication.h" #include "qtlocalpeer.h" #include /*! \class QtSingleApplication qtsingleapplication.h \brief The QtSingleApplication class provides an API to detect and communicate with running instances of an application. This class allows you to create applications where only one instance should be running at a time. I.e., if the user tries to launch another instance, the already running instance will be activated instead. Another usecase is a client-server system, where the first started instance will assume the role of server, and the later instances will act as clients of that server. By default, the full path of the executable file is used to determine whether two processes are instances of the same application. You can also provide an explicit identifier string that will be compared instead. The application should create the QtSingleApplication object early in the startup phase, and call isRunning() to find out if another instance of this application is already running. If isRunning() returns false, it means that no other instance is running, and this instance has assumed the role as the running instance. In this case, the application should continue with the initialization of the application user interface before entering the event loop with exec(), as normal. The messageReceived() signal will be emitted when the running application receives messages from another instance of the same application. When a message is received it might be helpful to the user to raise the application so that it becomes visible. To facilitate this, QtSingleApplication provides the setActivationWindow() function and the activateWindow() slot. If isRunning() returns true, another instance is already running. It may be alerted to the fact that another instance has started by using the sendMessage() function. Also data such as startup parameters (e.g. the name of the file the user wanted this new instance to open) can be passed to the running instance with this function. Then, the application should terminate (or enter client mode). If isRunning() returns true, but sendMessage() fails, that is an indication that the running instance is frozen. Here's an example that shows how to convert an existing application to use QtSingleApplication. It is very simple and does not make use of all QtSingleApplication's functionality (see the examples for that). \code // Original int main(int argc, char **argv) { QApplication app(argc, argv); MyMainWidget mmw; mmw.show(); return app.exec(); } // Single instance int main(int argc, char **argv) { QtSingleApplication app(argc, argv); if (app.isRunning()) return !app.sendMessage(someDataString); MyMainWidget mmw; app.setActivationWindow(&mmw); mmw.show(); return app.exec(); } \endcode Once this QtSingleApplication instance is destroyed (normally when the process exits or crashes), when the user next attempts to run the application this instance will not, of course, be encountered. The next instance to call isRunning() or sendMessage() will assume the role as the new running instance. For console (non-GUI) applications, QtSingleCoreApplication may be used instead of this class, to avoid the dependency on the QtGui library. \sa QtSingleCoreApplication */ void QtSingleApplication::sysInit(const QString &appId) { actWin = 0; peer = new QtLocalPeer(this, appId); connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); } /*! Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). \a argc, \a argv, and \a GUIenabled are passed on to the QAppliation constructor. If you are creating a console application (i.e. setting \a GUIenabled to false), you may consider using QtSingleCoreApplication instead. */ QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled) : QApplication(argc, argv, GUIenabled) { sysInit(); } /*! Creates a QtSingleApplication object with the application identifier \a appId. \a argc and \a argv are passed on to the QAppliation constructor. */ QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv) : QApplication(argc, argv) { sysInit(appId); } #if QT_VERSION < 0x050000 /*! Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). \a argc, \a argv, and \a type are passed on to the QAppliation constructor. */ QtSingleApplication::QtSingleApplication(int &argc, char **argv, Type type) : QApplication(argc, argv, type) { sysInit(); } # if defined(Q_WS_X11) /*! Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). \a dpy, \a visual, and \a cmap are passed on to the QApplication constructor. */ QtSingleApplication::QtSingleApplication(Display* dpy, Qt::HANDLE visual, Qt::HANDLE cmap) : QApplication(dpy, visual, cmap) { sysInit(); } /*! Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). \a dpy, \a argc, \a argv, \a visual, and \a cmap are passed on to the QApplication constructor. */ QtSingleApplication::QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) : QApplication(dpy, argc, argv, visual, cmap) { sysInit(); } /*! Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be \a appId. \a dpy, \a argc, \a argv, \a visual, and \a cmap are passed on to the QApplication constructor. */ QtSingleApplication::QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual, Qt::HANDLE cmap) : QApplication(dpy, argc, argv, visual, cmap) { sysInit(appId); } # endif // Q_WS_X11 #endif // QT_VERSION < 0x050000 /*! Returns true if another instance of this application is running; otherwise false. This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session). \sa sendMessage() */ bool QtSingleApplication::isRunning() { return peer->isClient(); } /*! Tries to send the text \a message to the currently running instance. The QtSingleApplication object in the running instance will emit the messageReceived() signal when it receives the message. This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within \a timeout milliseconds, this function return false. \sa isRunning(), messageReceived() */ bool QtSingleApplication::sendMessage(const QString &message, int timeout) { return peer->sendMessage(message, timeout); } /*! Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application. */ QString QtSingleApplication::id() const { return peer->applicationId(); } /*! Sets the activation window of this application to \a aw. The activation window is the widget that will be activated by activateWindow(). This is typically the application's main window. If \a activateOnMessage is true (the default), the window will be activated automatically every time a message is received, just prior to the messageReceived() signal being emitted. \sa activateWindow(), messageReceived() */ void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage) { actWin = aw; if (activateOnMessage) connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); else disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow())); } /*! Returns the applications activation window if one has been set by calling setActivationWindow(), otherwise returns 0. \sa setActivationWindow() */ QWidget* QtSingleApplication::activationWindow() const { return actWin; } /*! De-minimizes, raises, and activates this application's activation window. This function does nothing if no activation window has been set. This is a convenience function to show the user that this application instance has been activated when he has tried to start another instance. This function should typically be called in response to the messageReceived() signal. By default, that will happen automatically, if an activation window has been set. \sa setActivationWindow(), messageReceived(), initialize() */ void QtSingleApplication::activateWindow() { if (actWin) { actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); actWin->raise(); actWin->activateWindow(); } } /*! \fn void QtSingleApplication::messageReceived(const QString& message) This signal is emitted when the current instance receives a \a message from another instance of this application. \sa sendMessage(), setActivationWindow(), activateWindow() */ /*! \fn void QtSingleApplication::initialize(bool dummy = true) \obsolete */ ukui-menu/3rd-parties/qtsingleapplication/src/qtlocalpeer.cpp0000664000175000017500000001174515160463353023452 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include "qtlocalpeer.h" #include #include #include #include #if defined(Q_OS_WIN) #include #include typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); static PProcessIdToSessionId pProcessIdToSessionId = 0; #endif #if defined(Q_OS_UNIX) #include #include #include #endif namespace QtLP_Private { #include "qtlockedfile.cpp" #if defined(Q_OS_WIN) #include "qtlockedfile_win.cpp" #else #include "qtlockedfile_unix.cpp" #endif } const char* QtLocalPeer::ack = "ack"; QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) : QObject(parent), id(appId) { QString prefix = id; if (id.isEmpty()) { id = QCoreApplication::applicationFilePath(); #if defined(Q_OS_WIN) id = id.toLower(); #endif prefix = id.section(QLatin1Char('/'), -1); } prefix.remove(QRegularExpression("[^a-zA-Z]")); prefix.truncate(6); QByteArray idc = id.toUtf8(); quint16 idNum = qChecksum(idc.constData(), idc.size()); socketName = QLatin1String("qtsingleapp-") + prefix + QLatin1Char('-') + QString::number(idNum, 16); #if defined(Q_OS_WIN) if (!pProcessIdToSessionId) { QLibrary lib("kernel32"); pProcessIdToSessionId = (PProcessIdToSessionId)lib.resolve("ProcessIdToSessionId"); } if (pProcessIdToSessionId) { DWORD sessionId = 0; pProcessIdToSessionId(GetCurrentProcessId(), &sessionId); socketName += QLatin1Char('-') + QString::number(sessionId, 16); } #else socketName += QLatin1Char('-') + QString::number(::getuid(), 16); #endif server = new QLocalServer(this); QString lockName = QDir(QDir::tempPath()).absolutePath() + QLatin1Char('/') + socketName + QLatin1String("-lockfile"); lockFile.setFileName(lockName); lockFile.open(QIODevice::ReadWrite); } bool QtLocalPeer::isClient() { if (lockFile.isLocked()) return false; if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) return true; bool res = server->listen(socketName); #if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) // ### Workaround if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); res = server->listen(socketName); } #endif if (!res) qWarning("QtSingleCoreApplication: listen on local socket failed, %s", qPrintable(server->errorString())); QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection())); return false; } bool QtLocalPeer::sendMessage(const QString &message, int timeout) { if (!isClient()) return false; QLocalSocket socket; bool connOk = false; for(int i = 0; i < 2; i++) { // Try twice, in case the other instance is just starting up socket.connectToServer(socketName); connOk = socket.waitForConnected(timeout/2); if (connOk || i) break; int ms = 250; #if defined(Q_OS_WIN) Sleep(DWORD(ms)); #else struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 }; nanosleep(&ts, NULL); #endif } if (!connOk) return false; QByteArray uMsg(message.toUtf8()); QDataStream ds(&socket); ds.writeBytes(uMsg.constData(), uMsg.size()); bool res = socket.waitForBytesWritten(timeout); if (res) { res &= socket.waitForReadyRead(timeout); // wait for ack if (res) res &= (socket.read(qstrlen(ack)) == ack); } return res; } void QtLocalPeer::receiveConnection() { QLocalSocket* socket = server->nextPendingConnection(); if (!socket) return; while (true) { if (socket->state() == QLocalSocket::UnconnectedState) { qWarning("QtLocalPeer: Peer disconnected"); delete socket; return; } if (socket->bytesAvailable() >= qint64(sizeof(quint32))) break; socket->waitForReadyRead(); } QDataStream ds(socket); QByteArray uMsg; quint32 remaining; ds >> remaining; uMsg.resize(remaining); int got = 0; char* uMsgBuf = uMsg.data(); do { got = ds.readRawData(uMsgBuf, remaining); remaining -= got; uMsgBuf += got; } while (remaining && got >= 0 && socket->waitForReadyRead(2000)); if (got < 0) { qWarning("QtLocalPeer: Message reception failed %s", socket->errorString().toLatin1().constData()); delete socket; return; } QString message(QString::fromUtf8(uMsg)); socket->write(ack, qstrlen(ack)); socket->waitForBytesWritten(1000); socket->waitForDisconnected(1000); // make sure client reads ack delete socket; emit messageReceived(message); //### (might take a long time to return) } ukui-menu/3rd-parties/qtsingleapplication/src/qtsingleapplication.h0000664000175000017500000000405715160463353024654 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #ifndef QTSINGLEAPPLICATION_H #define QTSINGLEAPPLICATION_H #include class QtLocalPeer; #if defined(Q_OS_WIN) # if !defined(QT_QTSINGLEAPPLICATION_EXPORT) && !defined(QT_QTSINGLEAPPLICATION_IMPORT) # define QT_QTSINGLEAPPLICATION_EXPORT # elif defined(QT_QTSINGLEAPPLICATION_IMPORT) # if defined(QT_QTSINGLEAPPLICATION_EXPORT) # undef QT_QTSINGLEAPPLICATION_EXPORT # endif # define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllimport) # elif defined(QT_QTSINGLEAPPLICATION_EXPORT) # undef QT_QTSINGLEAPPLICATION_EXPORT # define QT_QTSINGLEAPPLICATION_EXPORT __declspec(dllexport) # endif #else # define QT_QTSINGLEAPPLICATION_EXPORT #endif class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication { Q_OBJECT public: QtSingleApplication(int &argc, char **argv, bool GUIenabled = true); QtSingleApplication(const QString &id, int &argc, char **argv); #if QT_VERSION < 0x050000 QtSingleApplication(int &argc, char **argv, Type type); # if defined(Q_WS_X11) QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0); QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0); # endif // Q_WS_X11 #endif // QT_VERSION < 0x050000 bool isRunning(); QString id() const; void setActivationWindow(QWidget* aw, bool activateOnMessage = true); QWidget* activationWindow() const; // Obsolete: void initialize(bool dummy = true) { isRunning(); Q_UNUSED(dummy) } public Q_SLOTS: bool sendMessage(const QString &message, int timeout = 5000); void activateWindow(); Q_SIGNALS: void messageReceived(const QString &message); private: void sysInit(const QString &appId = QString()); QtLocalPeer *peer; QWidget *actWin; }; #endif // QTSINGLEAPPLICATION_H ukui-menu/3rd-parties/qtsingleapplication/src/qtlockedfile_win.cpp0000664000175000017500000001112115160463353024446 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include "qtlockedfile.h" #include #include #define MUTEX_PREFIX "QtLockedFile mutex " // Maximum number of concurrent read locks. Must not be greater than MAXIMUM_WAIT_OBJECTS #define MAX_READERS MAXIMUM_WAIT_OBJECTS #if QT_VERSION >= 0x050000 #define QT_WA(unicode, ansi) unicode #endif Qt::HANDLE QtLockedFile::getMutexHandle(int idx, bool doCreate) { if (mutexname.isEmpty()) { QFileInfo fi(*this); mutexname = QString::fromLatin1(MUTEX_PREFIX) + fi.absoluteFilePath().toLower(); } QString mname(mutexname); if (idx >= 0) mname += QString::number(idx); Qt::HANDLE mutex; if (doCreate) { QT_WA( { mutex = CreateMutexW(NULL, FALSE, (TCHAR*)mname.utf16()); }, { mutex = CreateMutexA(NULL, FALSE, mname.toLocal8Bit().constData()); } ); if (!mutex) { qErrnoWarning("QtLockedFile::lock(): CreateMutex failed"); return 0; } } else { QT_WA( { mutex = OpenMutexW(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, (TCHAR*)mname.utf16()); }, { mutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, mname.toLocal8Bit().constData()); } ); if (!mutex) { if (GetLastError() != ERROR_FILE_NOT_FOUND) qErrnoWarning("QtLockedFile::lock(): OpenMutex failed"); return 0; } } return mutex; } bool QtLockedFile::waitMutex(Qt::HANDLE mutex, bool doBlock) { Q_ASSERT(mutex); DWORD res = WaitForSingleObject(mutex, doBlock ? INFINITE : 0); switch (res) { case WAIT_OBJECT_0: case WAIT_ABANDONED: return true; break; case WAIT_TIMEOUT: break; default: qErrnoWarning("QtLockedFile::lock(): WaitForSingleObject failed"); } return false; } bool QtLockedFile::lock(LockMode mode, bool block) { if (!isOpen()) { qWarning("QtLockedFile::lock(): file is not opened"); return false; } if (mode == NoLock) return unlock(); if (mode == m_lock_mode) return true; if (m_lock_mode != NoLock) unlock(); if (!wmutex && !(wmutex = getMutexHandle(-1, true))) return false; if (!waitMutex(wmutex, block)) return false; if (mode == ReadLock) { int idx = 0; for (; idx < MAX_READERS; idx++) { rmutex = getMutexHandle(idx, false); if (!rmutex || waitMutex(rmutex, false)) break; CloseHandle(rmutex); } bool ok = true; if (idx >= MAX_READERS) { qWarning("QtLockedFile::lock(): too many readers"); rmutex = 0; ok = false; } else if (!rmutex) { rmutex = getMutexHandle(idx, true); if (!rmutex || !waitMutex(rmutex, false)) ok = false; } if (!ok && rmutex) { CloseHandle(rmutex); rmutex = 0; } ReleaseMutex(wmutex); if (!ok) return false; } else { Q_ASSERT(rmutexes.isEmpty()); for (int i = 0; i < MAX_READERS; i++) { Qt::HANDLE mutex = getMutexHandle(i, false); if (mutex) rmutexes.append(mutex); } if (rmutexes.size()) { DWORD res = WaitForMultipleObjects(rmutexes.size(), rmutexes.constData(), TRUE, block ? INFINITE : 0); if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) { if (res != WAIT_TIMEOUT) qErrnoWarning("QtLockedFile::lock(): WaitForMultipleObjects failed"); m_lock_mode = WriteLock; // trick unlock() to clean up - semiyucky unlock(); return false; } } } m_lock_mode = mode; return true; } bool QtLockedFile::unlock() { if (!isOpen()) { qWarning("QtLockedFile::unlock(): file is not opened"); return false; } if (!isLocked()) return true; if (m_lock_mode == ReadLock) { ReleaseMutex(rmutex); CloseHandle(rmutex); rmutex = 0; } else { foreach(Qt::HANDLE mutex, rmutexes) { ReleaseMutex(mutex); CloseHandle(mutex); } rmutexes.clear(); ReleaseMutex(wmutex); } m_lock_mode = QtLockedFile::NoLock; return true; } QtLockedFile::~QtLockedFile() { if (isOpen()) unlock(); if (wmutex) CloseHandle(wmutex); } ukui-menu/3rd-parties/qtsingleapplication/src/qtsinglecoreapplication.pri0000664000175000017500000000050415160463353026061 0ustar fengfengINCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += $$PWD/qtsinglecoreapplication.h $$PWD/qtlocalpeer.h SOURCES += $$PWD/qtsinglecoreapplication.cpp $$PWD/qtlocalpeer.cpp QT *= network win32:contains(TEMPLATE, lib):contains(CONFIG, shared) { DEFINES += QT_QTSINGLECOREAPPLICATION_EXPORT=__declspec(dllexport) } ukui-menu/3rd-parties/qtsingleapplication/src/qtsinglecoreapplication.cpp0000664000175000017500000000661515160463353026062 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include "qtsinglecoreapplication.h" #include "qtlocalpeer.h" /*! \class QtSingleCoreApplication qtsinglecoreapplication.h \brief A variant of the QtSingleApplication class for non-GUI applications. This class is a variant of QtSingleApplication suited for use in console (non-GUI) applications. It is an extension of QCoreApplication (instead of QApplication). It does not require the QtGui library. The API and usage is identical to QtSingleApplication, except that functions relating to the "activation window" are not present, for obvious reasons. Please refer to the QtSingleApplication documentation for explanation of the usage. A QtSingleCoreApplication instance can communicate to a QtSingleApplication instance if they share the same application id. Hence, this class can be used to create a light-weight command-line tool that sends commands to a GUI application. \sa QtSingleApplication */ /*! Creates a QtSingleCoreApplication object. The application identifier will be QCoreApplication::applicationFilePath(). \a argc and \a argv are passed on to the QCoreAppliation constructor. */ QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv) : QCoreApplication(argc, argv) { peer = new QtLocalPeer(this); connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); } /*! Creates a QtSingleCoreApplication object with the application identifier \a appId. \a argc and \a argv are passed on to the QCoreAppliation constructor. */ QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv) : QCoreApplication(argc, argv) { peer = new QtLocalPeer(this, appId); connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&))); } /*! Returns true if another instance of this application is running; otherwise false. This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session). \sa sendMessage() */ bool QtSingleCoreApplication::isRunning() { return peer->isClient(); } /*! Tries to send the text \a message to the currently running instance. The QtSingleCoreApplication object in the running instance will emit the messageReceived() signal when it receives the message. This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within \a timeout milliseconds, this function return false. \sa isRunning(), messageReceived() */ bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout) { return peer->sendMessage(message, timeout); } /*! Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application. */ QString QtSingleCoreApplication::id() const { return peer->applicationId(); } /*! \fn void QtSingleCoreApplication::messageReceived(const QString& message) This signal is emitted when the current instance receives a \a message from another instance of this application. \sa sendMessage() */ ukui-menu/3rd-parties/qtsingleapplication/src/QtSingleApplication0000664000175000017500000000004115160463353024253 0ustar fengfeng#include "qtsingleapplication.h" ukui-menu/3rd-parties/qtsingleapplication/doc/0000775000175000017500000000000015160463353020401 5ustar fengfengukui-menu/3rd-parties/qtsingleapplication/doc/images/0000775000175000017500000000000015160463353021646 5ustar fengfengukui-menu/3rd-parties/qtsingleapplication/doc/images/qt-logo.png0000664000175000017500000000775315160463353023752 0ustar fengfengPNG  IHDR9C:uIDATxb?bhJnJnJnJnJnJnԡ Є`r'Q( !!Nb_p??F&F312G.yG&ޏ?}eWSSgb( ~™t齸g0sVv`fcdY8E$xdUUex$xd9DDr=#gdd|Uףw9D- 2b8Ͽebddceᔐ╗SWScaB#cfd]-I\ _20110`xzw/).#'˯"ǯ*˫,+')@lY@}fۯͼ 2@d$1H')&#'ç,ϯ*˧,#/%́XgfdOX8Y9s2GC3g@ fFff.3O|v ,l%?fR_|bb`y엉3+ԇ,̌,4 "ͭ32>{E4އ ˯NԦ+Qefdϛ?6:&Ǜ?|"=\n$FFfF /0"`ddf cfbb`dx՛o yAP ,,eebWaá,QܬLL>5Lll10 m00wTVxOÚ bGB8GtUմXplII ׶R/@2 Y{ ]d9#ule+}N6XfJ"խJ13002³ @K"ŭzo?1000@{ lϿ`?ï2"ecgd H.fFfɆ}Ͽߌ"٭윌 y02000Ac<\po/."?;#ߛ!hgddd\<|\300<|?j"-U L9D8E i[ZP?z5fF6? o"b')~?3311g/ ֯cz߿>pvfL~g'""٭Qd'i `Ϳ'?HۅǞy?200 %)h1.7?."ͭ 32yVfFfGyO/:?LL [,?/?^}|:`luX-tN X"_CkǶ>w:7WlSofFcOv{OD7톚)*$@d"7$1CRgɉi[!.f&v_#ɧ{='JXXc`agպ]R\̅?ӧ_ᄏvѦ_2"g|fە(m )\!&&&F{ ba~>qci.O?l-"s܅Ǐ<Ϟn%hm)tN6P߿0H~̌,|ןhmF>1:19_/رcc7k䱞]ۯ咒SaB[?|}kf~?ɍkF/>9*qpQ( @##ïe1| QXn_ #ơp7B]X?F[a'K+ q/̌,,L L 7tDʡz /#300VnC")"/?110qp￿l_GVWNOU_UIPSCؐ?\e``=_R(2r-d?ÿ_?+78$$ +:xBVFn^?cbdd@yĂf```aba㔆L)J sI2Ҥ!#3 > ^y^~o (?+33d:.cfL(H*sJ`Mg222ڥž15,l l"< QtSingleApplication Class Reference
  Home

QtSingleApplication Class Reference

The QtSingleApplication class provides an API to detect and communicate with running instances of an application. More...

 #include <QtSingleApplication>

Inherits QApplication.


Public Functions

QtSingleApplication ( int & argc, char ** argv, bool GUIenabled = true )
QtSingleApplication ( const QString & appId, int & argc, char ** argv )
QtSingleApplication ( int & argc, char ** argv, Type type )
QtSingleApplication ( Display * dpy, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )
QtSingleApplication ( Display * dpy, int & argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )
QtSingleApplication ( Display * dpy, const QString & appId, int argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )
QWidget * activationWindow () const
QString id () const
bool isRunning ()
void setActivationWindow ( QWidget * aw, bool activateOnMessage = true )

Public Slots

void activateWindow ()
bool sendMessage ( const QString & message, int timeout = 5000 )

Signals

void messageReceived ( const QString & message )

Additional Inherited Members


Detailed Description

The QtSingleApplication class provides an API to detect and communicate with running instances of an application.

This class allows you to create applications where only one instance should be running at a time. I.e., if the user tries to launch another instance, the already running instance will be activated instead. Another usecase is a client-server system, where the first started instance will assume the role of server, and the later instances will act as clients of that server.

By default, the full path of the executable file is used to determine whether two processes are instances of the same application. You can also provide an explicit identifier string that will be compared instead.

The application should create the QtSingleApplication object early in the startup phase, and call isRunning() to find out if another instance of this application is already running. If isRunning() returns false, it means that no other instance is running, and this instance has assumed the role as the running instance. In this case, the application should continue with the initialization of the application user interface before entering the event loop with exec(), as normal.

The messageReceived() signal will be emitted when the running application receives messages from another instance of the same application. When a message is received it might be helpful to the user to raise the application so that it becomes visible. To facilitate this, QtSingleApplication provides the setActivationWindow() function and the activateWindow() slot.

If isRunning() returns true, another instance is already running. It may be alerted to the fact that another instance has started by using the sendMessage() function. Also data such as startup parameters (e.g. the name of the file the user wanted this new instance to open) can be passed to the running instance with this function. Then, the application should terminate (or enter client mode).

If isRunning() returns true, but sendMessage() fails, that is an indication that the running instance is frozen.

Here's an example that shows how to convert an existing application to use QtSingleApplication. It is very simple and does not make use of all QtSingleApplication's functionality (see the examples for that).

 // Original
 int main(int argc, char **argv)
 {
     QApplication app(argc, argv);

     MyMainWidget mmw;
     mmw.show();
     return app.exec();
 }

 // Single instance
 int main(int argc, char **argv)
 {
     QtSingleApplication app(argc, argv);

     if (app.isRunning())
         return !app.sendMessage(someDataString);

     MyMainWidget mmw;
     app.setActivationWindow(&mmw);
     mmw.show();
     return app.exec();
 }

Once this QtSingleApplication instance is destroyed (normally when the process exits or crashes), when the user next attempts to run the application this instance will not, of course, be encountered. The next instance to call isRunning() or sendMessage() will assume the role as the new running instance.

For console (non-GUI) applications, QtSingleCoreApplication may be used instead of this class, to avoid the dependency on the QtGui library.

See also QtSingleCoreApplication.


Member Function Documentation

QtSingleApplication::QtSingleApplication ( int & argc, char ** argv, bool GUIenabled = true )

Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). argc, argv, and GUIenabled are passed on to the QAppliation constructor.

If you are creating a console application (i.e. setting GUIenabled to false), you may consider using QtSingleCoreApplication instead.

QtSingleApplication::QtSingleApplication ( const QString & appId, int & argc, char ** argv )

Creates a QtSingleApplication object with the application identifier appId. argc and argv are passed on to the QAppliation constructor.

QtSingleApplication::QtSingleApplication ( int & argc, char ** argv, Type type )

Creates a QtSingleApplication object. The application identifier will be QCoreApplication::applicationFilePath(). argc, argv, and type are passed on to the QAppliation constructor.

QtSingleApplication::QtSingleApplication ( Display * dpy, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )

Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). dpy, visual, and cmap are passed on to the QApplication constructor.

QtSingleApplication::QtSingleApplication ( Display * dpy, int & argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )

Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be QCoreApplication::applicationFilePath(). dpy, argc, argv, visual, and cmap are passed on to the QApplication constructor.

QtSingleApplication::QtSingleApplication ( Display * dpy, const QString & appId, int argc, char ** argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap = 0 )

Special constructor for X11, ref. the documentation of QApplication's corresponding constructor. The application identifier will be appId. dpy, argc, argv, visual, and cmap are passed on to the QApplication constructor.

void QtSingleApplication::activateWindow ()   [slot]

De-minimizes, raises, and activates this application's activation window. This function does nothing if no activation window has been set.

This is a convenience function to show the user that this application instance has been activated when he has tried to start another instance.

This function should typically be called in response to the messageReceived() signal. By default, that will happen automatically, if an activation window has been set.

See also setActivationWindow(), messageReceived(), and initialize().

QWidget * QtSingleApplication::activationWindow () const

Returns the applications activation window if one has been set by calling setActivationWindow(), otherwise returns 0.

See also setActivationWindow().

QString QtSingleApplication::id () const

Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application.

bool QtSingleApplication::isRunning ()

Returns true if another instance of this application is running; otherwise false.

This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session).

See also sendMessage().

void QtSingleApplication::messageReceived ( const QString & message )   [signal]

This signal is emitted when the current instance receives a message from another instance of this application.

See also sendMessage(), setActivationWindow(), and activateWindow().

bool QtSingleApplication::sendMessage ( const QString & message, int timeout = 5000 )   [slot]

Tries to send the text message to the currently running instance. The QtSingleApplication object in the running instance will emit the messageReceived() signal when it receives the message.

This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within timeout milliseconds, this function return false.

See also isRunning() and messageReceived().

void QtSingleApplication::setActivationWindow ( QWidget * aw, bool activateOnMessage = true )

Sets the activation window of this application to aw. The activation window is the widget that will be activated by activateWindow(). This is typically the application's main window.

If activateOnMessage is true (the default), the window will be activated automatically every time a message is received, just prior to the messageReceived() signal being emitted.

See also activationWindow(), activateWindow(), and messageReceived().


Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Solutions
ukui-menu/3rd-parties/qtsingleapplication/doc/html/qtsingleapplication-members.html0000664000175000017500000007370215160463353027746 0ustar fengfeng List of All Members for QtSingleApplication
  Home

List of All Members for QtSingleApplication

This is the complete list of members for QtSingleApplication, including inherited members.


Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Solutions
ukui-menu/3rd-parties/qtsingleapplication/doc/html/qtsingleapplication.dcf0000664000175000017500000000507115160463353026100 0ustar fengfeng
QtSingleApplication activateWindow activationWindow id isRunning messageReceived sendMessage setActivationWindow
QtSingleCoreApplication id isRunning messageReceived sendMessage
A non-GUI example
A Trivial Example
Loading Documents
Single Application
ukui-menu/3rd-parties/qtsingleapplication/doc/html/qtsingleapplication-example-trivial.html0000664000175000017500000001243415160463353031412 0ustar fengfeng A Trivial Example
  Home

A Trivial Example

The application in this example has a log-view that displays messages sent by further instances of the same application.

The example demonstrates the use of the QtSingleApplication class to detect and communicate with a running instance of the application using the sendMessage() API. The messageReceived() signal is used to display received messages in a QTextEdit log.

 /****************************************************************************
 **
 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the Qt Solutions component.
 **
 ** You may use this file under the terms of the BSD license as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
 **     the names of its contributors may be used to endorse or promote
 **     products derived from this software without specific prior written
 **     permission.
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 **
 ****************************************************************************/

 #include <qtsingleapplication.h>
 #include <QtGui/QTextEdit>

 class TextEdit : public QTextEdit
 {
     Q_OBJECT
 public:
     TextEdit(QWidget *parent = 0)
         : QTextEdit(parent)
     {}
 public slots:
     void append(const QString &str)
     {
         QTextEdit::append(str);
     }
 };

 #include "main.moc"

 int main(int argc, char **argv)
 {
     QtSingleApplication instance(argc, argv);

The example has only the main entry point function. A QtSingleApplication object is created immediately.

     if (instance.sendMessage("Wake up!"))
         return 0;

If another instance of this application is already running, sendMessage() will succeed, and this instance just exits immediately.

     TextEdit logview;
     logview.setReadOnly(true);
     logview.show();

Otherwise the instance continues as normal and creates the user interface.

     instance.setActivationWindow(&logview);

     QObject::connect(&instance, SIGNAL(messageReceived(const QString&)),
                      &logview, SLOT(append(const QString&)));

     return instance.exec();

The logview object is also set as the application's activation window. Every time a message is received, the window will be raised and activated automatically.

The messageReceived() signal is also connected to the QTextEdit's append() slot. Every message received from further instances of this application will be displayed in the log.

Finally the event loop is entered.


Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Solutions
ukui-menu/3rd-parties/qtsingleapplication/doc/html/images/0000775000175000017500000000000015160463353022612 5ustar fengfengukui-menu/3rd-parties/qtsingleapplication/doc/html/images/qt-logo.png0000664000175000017500000000775315160463353024716 0ustar fengfengPNG  IHDR9C:uIDATxb?bhJnJnJnJnJnJnԡ Є`r'Q( !!Nb_p??F&F312G.yG&ޏ?}eWSSgb( ~™t齸g0sVv`fcdY8E$xdUUex$xd9DDr=#gdd|Uףw9D- 2b8Ͽebddceᔐ╗SWScaB#cfd]-I\ _20110`xzw/).#'˯"ǯ*˫,+')@lY@}fۯͼ 2@d$1H')&#'ç,ϯ*˧,#/%́XgfdOX8Y9s2GC3g@ fFff.3O|v ,l%?fR_|bb`y엉3+ԇ,̌,4 "ͭ32>{E4އ ˯NԦ+Qefdϛ?6:&Ǜ?|"=\n$FFfF /0"`ddf cfbb`dx՛o yAP ,,eebWaá,QܬLL>5Lll10 m00wTVxOÚ bGB8GtUմXplII ׶R/@2 Y{ ]d9#ule+}N6XfJ"խJ13002³ @K"ŭzo?1000@{ lϿ`?ï2"ecgd H.fFfɆ}Ͽߌ"٭윌 y02000Ac<\po/."?;#ߛ!hgddd\<|\300<|?j"-U L9D8E i[ZP?z5fF6? o"b')~?3311g/ ֯cz߿>pvfL~g'""٭Qd'i `Ϳ'?HۅǞy?200 %)h1.7?."ͭ 32yVfFfGyO/:?LL [,?/?^}|:`luX-tN X"_CkǶ>w:7WlSofFcOv{OD7톚)*$@d"7$1CRgɉi[!.f&v_#ɧ{='JXXc`agպ]R\̅?ӧ_ᄏvѦ_2"g|fە(m )\!&&&F{ ba~>qci.O?l-"s܅Ǐ<Ϟn%hm)tN6P߿0H~̌,|ןhmF>1:19_/رcc7k䱞]ۯ咒SaB[?|}kf~?ɍkF/>9*qpQ( @##ïe1| QXn_ #ơp7B]X?F[a'K+ q/̌,,L L 7tDʡz /#300VnC")"/?110qp￿l_GVWNOU_UIPSCؐ?\e``=_R(2r-d?ÿ_?+78$$ +:xBVFn^?cbdd@yĂf```aba㔆L)J sI2Ҥ!#3 > ^y^~o (?+33d:.cfL(H*sJ`Mg222ڥž15,l l"< A non-GUI example
  Home

A non-GUI example

This example shows how to use the single-application functionality in a console application. It does not require the QtGui library at all.

The only differences from the GUI application usage demonstrated in the other examples are:

1) The .pro file should include qtsinglecoreapplication.pri instead of qtsingleapplication.pri

2) The class name is QtSingleCoreApplication instead of QtSingleApplication.

3) No calls are made regarding window activation, for obvious reasons.

console.pro:

 TEMPLATE   = app
 CONFIG    += console
 SOURCES   += main.cpp
 include(../../src/qtsinglecoreapplication.pri)
 QT -= gui

main.cpp:

 /****************************************************************************
 **
 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the Qt Solutions component.
 **
 ** You may use this file under the terms of the BSD license as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
 **     the names of its contributors may be used to endorse or promote
 **     products derived from this software without specific prior written
 **     permission.
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 **
 ****************************************************************************/

 #include "qtsinglecoreapplication.h"
 #include <QtCore/QDebug>

 void report(const QString& msg)
 {
     qDebug("[%i] %s", (int)QCoreApplication::applicationPid(), qPrintable(msg));
 }

 class MainClass : public QObject
 {
     Q_OBJECT
 public:
     MainClass()
         : QObject()
         {}

 public slots:
     void handleMessage(const QString& message)
         {
             report( "Message received: \"" + message + "\"");
         }
 };

 int main(int argc, char **argv)
 {
     report("Starting up");

     QtSingleCoreApplication app(argc, argv);

     if (app.isRunning()) {
         QString msg(QString("Hi master, I am %1.").arg(QCoreApplication::applicationPid()));
         bool sentok = app.sendMessage(msg, 2000);
         QString rep("Another instance is running, so I will exit.");
         rep += sentok ? " Message sent ok." : " Message sending failed; the other instance may be frozen.";
         report(rep);
         return 0;
     } else {
         report("No other instance is running; so I will.");
         MainClass mainObj;
         QObject::connect(&app, SIGNAL(messageReceived(const QString&)),
                          &mainObj, SLOT(handleMessage(const QString&)));
         return app.exec();
     }
 }

 #include "main.moc"


Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Solutions
ukui-menu/3rd-parties/qtsingleapplication/doc/html/qtsingleapplication-example-loader.html0000664000175000017500000001670015160463353031206 0ustar fengfeng Loading Documents
  Home

Loading Documents

The application in this example loads or prints the documents passed as commandline parameters to further instances of this application.

 /****************************************************************************
 **
 ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
 ** Contact: http://www.qt-project.org/legal
 **
 ** This file is part of the Qt Solutions component.
 **
 ** You may use this file under the terms of the BSD license as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor
 **     the names of its contributors may be used to endorse or promote
 **     products derived from this software without specific prior written
 **     permission.
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 **
 ****************************************************************************/

 #include <qtsingleapplication.h>
 #include <QtCore/QFile>
 #include <QtGui/QMainWindow>
 #include <QtGui/QPrinter>
 #include <QtGui/QPainter>
 #include <QtGui/QTextEdit>
 #include <QtGui/QMdiArea>
 #include <QtCore/QTextStream>

 class MainWindow : public QMainWindow
 {
     Q_OBJECT
 public:
     MainWindow();

 public slots:
     void handleMessage(const QString& message);

 signals:
     void needToShow();

 private:
     QMdiArea *workspace;
 };

The user interface in this application is a QMainWindow subclass with a QMdiArea as the central widget. It implements a slot handleMessage() that will be connected to the messageReceived() signal of the QtSingleApplication class.

 MainWindow::MainWindow()
 {
     workspace = new QMdiArea(this);

     setCentralWidget(workspace);
 }

The MainWindow constructor creates a minimal user interface.

 void MainWindow::handleMessage(const QString& message)
 {
     enum Action {
         Nothing,
         Open,
         Print
     } action;

     action = Nothing;
     QString filename = message;
     if (message.toLower().startsWith("/print ")) {
         filename = filename.mid(7);
         action = Print;
     } else if (!message.isEmpty()) {
         action = Open;
     }
     if (action == Nothing) {
         emit needToShow();
         return;
     }

     QFile file(filename);
     QString contents;
     if (file.open(QIODevice::ReadOnly))
         contents = file.readAll();
     else
         contents = "[[Error: Could not load file " + filename + "]]";

     QTextEdit *view = new QTextEdit;
     view->setPlainText(contents);

     switch(action) {

The handleMessage() slot interprets the message passed in as a filename that can be prepended with /print to indicate that the file should just be printed rather than loaded.

     case Print:
         {
             QPrinter printer;
             view->print(&printer);
             delete view;
         }
         break;

     case Open:
         {
             workspace->addSubWindow(view);
             view->setWindowTitle(message);
             view->show();
             emit needToShow();
         }
         break;
     default:
         break;
     };
 }

Loading the file will also activate the window.

 #include "main.moc"

 int main(int argc, char **argv)
 {
     QtSingleApplication instance("File loader QtSingleApplication example", argc, argv);
     QString message;
     for (int a = 1; a < argc; ++a) {
         message += argv[a];
         if (a < argc-1)
             message += " ";
     }

     if (instance.sendMessage(message))
         return 0;

The main entry point function creates a QtSingleApplication object, and creates a message to send to a running instance of the application. If the message was sent successfully the process exits immediately.

     MainWindow mw;
     mw.handleMessage(message);
     mw.show();

     QObject::connect(&instance, SIGNAL(messageReceived(const QString&)),
                      &mw, SLOT(handleMessage(const QString&)));

     instance.setActivationWindow(&mw, false);
     QObject::connect(&mw, SIGNAL(needToShow()), &instance, SLOT(activateWindow()));

     return instance.exec();
 }

If the message could not be sent the application starts up. Note that false is passed to the call to setActivationWindow() to prevent automatic activation for every message received, e.g. when the application should just print a file. Instead, the message handling function determines whether activation is requested, and signals that by emitting the needToShow() signal. This is then simply connected directly to QtSingleApplication's activateWindow() slot.


Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Solutions
ukui-menu/3rd-parties/qtsingleapplication/doc/html/qtsinglecoreapplication.html0000664000175000017500000002356615160463353027172 0ustar fengfeng QtSingleCoreApplication Class Reference
  Home

QtSingleCoreApplication Class Reference

A variant of the QtSingleApplication class for non-GUI applications. More...

 #include <QtSingleCoreApplication>

Inherits QCoreApplication.


Public Functions

QtSingleCoreApplication ( int & argc, char ** argv )
QtSingleCoreApplication ( const QString & appId, int & argc, char ** argv )
QString id () const
bool isRunning ()

Public Slots

bool sendMessage ( const QString & message, int timeout = 5000 )

Signals

void messageReceived ( const QString & message )

Additional Inherited Members


Detailed Description

A variant of the QtSingleApplication class for non-GUI applications.

This class is a variant of QtSingleApplication suited for use in console (non-GUI) applications. It is an extension of QCoreApplication (instead of QApplication). It does not require the QtGui library.

The API and usage is identical to QtSingleApplication, except that functions relating to the "activation window" are not present, for obvious reasons. Please refer to the QtSingleApplication documentation for explanation of the usage.

A QtSingleCoreApplication instance can communicate to a QtSingleApplication instance if they share the same application id. Hence, this class can be used to create a light-weight command-line tool that sends commands to a GUI application.

See also QtSingleApplication.


Member Function Documentation

QtSingleCoreApplication::QtSingleCoreApplication ( int & argc, char ** argv )

Creates a QtSingleCoreApplication object. The application identifier will be QCoreApplication::applicationFilePath(). argc and argv are passed on to the QCoreAppliation constructor.

QtSingleCoreApplication::QtSingleCoreApplication ( const QString & appId, int & argc, char ** argv )

Creates a QtSingleCoreApplication object with the application identifier appId. argc and argv are passed on to the QCoreAppliation constructor.

QString QtSingleCoreApplication::id () const

Returns the application identifier. Two processes with the same identifier will be regarded as instances of the same application.

bool QtSingleCoreApplication::isRunning ()

Returns true if another instance of this application is running; otherwise false.

This function does not find instances of this application that are being run by a different user (on Windows: that are running in another session).

See also sendMessage().

void QtSingleCoreApplication::messageReceived ( const QString & message )   [signal]

This signal is emitted when the current instance receives a message from another instance of this application.

See also sendMessage().

bool QtSingleCoreApplication::sendMessage ( const QString & message, int timeout = 5000 )   [slot]

Tries to send the text message to the currently running instance. The QtSingleCoreApplication object in the running instance will emit the messageReceived() signal when it receives the message.

This function returns true if the message has been sent to, and processed by, the current instance. If there is no instance currently running, or if the running instance fails to process the message within timeout milliseconds, this function return false.

See also isRunning() and messageReceived().


Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Solutions
ukui-menu/3rd-parties/qtsingleapplication/doc/html/qtsinglecoreapplication-members.html0000664000175000017500000003577715160463353030631 0ustar fengfeng List of All Members for QtSingleCoreApplication
  Home

List of All Members for QtSingleCoreApplication

This is the complete list of members for QtSingleCoreApplication, including inherited members.


Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Solutions
ukui-menu/3rd-parties/qtsingleapplication/doc/html/qtsingleapplication.index0000664000175000017500000003171415160463353026456 0ustar fengfeng ukui-menu/3rd-parties/qtsingleapplication/doc/html/qtsingleapplication-obsolete.html0000664000175000017500000000372115160463353030122 0ustar fengfeng Obsolete Members for QtSingleApplication
  Home

Obsolete Members for QtSingleApplication

The following class members are obsolete. They are provided to keep old source code working. We strongly advise against using them in new code.

Public Functions

void initialize ( bool dummy = true )   (obsolete)

Member Function Documentation

void QtSingleApplication::initialize ( bool dummy = true )


Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Solutions
ukui-menu/3rd-parties/qtsingleapplication/doc/html/qtsingleapplication.qhp0000664000175000017500000000741115160463353026134 0ustar fengfeng com.nokia.qtsolutions.qtsingleapplication_head qdoc qt solutions qtsingleapplication qt solutions qtsingleapplication
qtsingleapplication.html index.html qtsingleapplication-example-trivial.html qtsinglecoreapplication.html qtsingleapplication-example-loader.html qtsinglecoreapplication-example-console.html classic.css images/qt-logo.png ukui-menu/3rd-parties/qtsingleapplication/doc/html/index.html0000664000175000017500000000527515160463353023353 0ustar fengfeng Single Application
  Home

Single Application

Description

The QtSingleApplication component provides support for applications that can be only started once per user.

For some applications it is useful or even critical that they are started only once by any user. Future attempts to start the application should activate any already running instance, and possibly perform requested actions, e.g. loading a file, in that instance.

The QtSingleApplication class provides an interface to detect a running instance, and to send command strings to that instance. For console (non-GUI) applications, the QtSingleCoreApplication variant is provided, which avoids dependency on QtGui.

Classes

Examples

Tested platforms

  • Qt 4.4, 4.5 / Windows XP / MSVC.NET 2005
  • Qt 4.4, 4.5 / Linux / gcc
  • Qt 4.4, 4.5 / MacOS X 10.5 / gcc


Copyright © 2010 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Solutions
ukui-menu/3rd-parties/qtsingleapplication/configure.bat0000664000175000017500000000176215160463353022313 0ustar fengfeng:: Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). :: SPDX-License-Identifier: BSD-3-Clause @echo off rem rem "Main" rem if not "%1"=="" ( if not "%1"=="-library" ( call :PrintUsage goto EOF ) ) if exist config.pri. del config.pri if "%1"=="-library" ( echo Configuring to build this component as a dynamic library. echo SOLUTIONS_LIBRARY = yes > config.pri ) echo . echo This component is now configured. echo . echo To build the component library (if requested) and example(s), echo run qmake and your make or nmake command. echo . echo To remove or reconfigure, run make (nmake) distclean. echo . goto EOF :PrintUsage echo Usage: configure.bat [-library] echo . echo -library: Build the component as a dynamic library (DLL). Default is to echo include the component source directly in the application. echo A DLL may be preferable for technical or licensing (LGPL) reasons. echo . goto EOF :EOF ukui-menu/3rd-parties/qtsingleapplication/common.pri0000664000175000017500000000124715160463353021644 0ustar fengfengexists(config.pri):infile(config.pri, SOLUTIONS_LIBRARY, yes): CONFIG += qtsingleapplication-uselib TEMPLATE += fakelib greaterThan(QT_MAJOR_VERSION, 5)|\ if(equals(QT_MAJOR_VERSION, 5):greaterThan(QT_MINOR_VERSION, 4))|\ if(equals(QT_MAJOR_VERSION, 5):equals(QT_MINOR_VERSION, 4):greaterThan(QT_PATCH_VERSION, 1)) { QTSINGLEAPPLICATION_LIBNAME = $$qt5LibraryTarget(QtSolutions_SingleApplication-head) } else { QTSINGLEAPPLICATION_LIBNAME = $$qtLibraryTarget(QtSolutions_SingleApplication-head) } TEMPLATE -= fakelib QTSINGLEAPPLICATION_LIBDIR = $$PWD/lib unix:qtsingleapplication-uselib:!qtsingleapplication-buildlib:QMAKE_RPATHDIR += $$QTSINGLEAPPLICATION_LIBDIR ukui-menu/3rd-parties/qtsingleapplication/qtsingleapplication.pro0000664000175000017500000000016515160463353024432 0ustar fengfengTEMPLATE=subdirs CONFIG += ordered include(common.pri) qtsingleapplication-uselib:SUBDIRS=buildlib SUBDIRS+=examples ukui-menu/3rd-parties/qtsingleapplication/CMakeLists.txt0000664000175000017500000000105515160463353022375 0ustar fengfengcmake_minimum_required(VERSION 3.16) project(qtsingleapplication) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Core Gui Widgets Network REQUIRED) include_directories(src) set(SRCS src/qtsingleapplication.h src/qtsingleapplication.cpp src/qtlocalpeer.h src/qtlocalpeer.cpp) add_library(${PROJECT_NAME} STATIC ${SRCS}) target_include_directories(${PROJECT_NAME} PRIVATE src) target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network) ukui-menu/3rd-parties/qtsingleapplication/README.TXT0000664000175000017500000000164115160463353021174 0ustar fengfengQt Solutions Component: Single Application The QtSingleApplication component provides support for applications that can be only started once per user. Version history: 2.0: - Version 1.3 ported to Qt 4. 2.1: - Fix compilation problem on Mac. 2.2: - Really fix the Mac compilation problem. - Mac: fix crash due to wrong object releasing. - Mac: Fix memory leak. 2.3: - Windows: Force creation of internal widget to make it work with Qt 4.2. 2.4: - Fix the system for automatic window raising on message reception. NOTE: minor API change. 2.5: - Mac: Fix isRunning() to work and report correctly. 2.6: - - initialize() is now obsolete, no longer necessary to call it - - Fixed race condition where multiple instances migth be started - - QtSingleCoreApplication variant provided for non-GUI (console) usage - Complete reimplementation. Visible changes: - LGPL release. ukui-menu/3rd-parties/qtsingleapplication/INSTALL.TXT0000664000175000017500000002237415160463353021353 0ustar fengfengINSTALLATION INSTRUCTIONS These instructions refer to the package you are installing as some-package.tar.gz or some-package.zip. The .zip file is intended for use on Windows. The directory you choose for the installation will be referred to as your-install-dir. Note to Qt Visual Studio Integration users: In the instructions below, instead of building from command line with nmake, you can use the menu command 'Qt->Open Solution from .pro file' on the .pro files in the example and plugin directories, and then build from within Visual Studio. Unpacking and installation -------------------------- 1. Unpacking the archive (if you have not done so already). On Unix and Mac OS X (in a terminal window): cd your-install-dir gunzip some-package.tar.gz tar xvf some-package.tar This creates the subdirectory some-package containing the files. On Windows: Unpack the .zip archive by right-clicking it in explorer and choosing "Extract All...". If your version of Windows does not have zip support, you can use the infozip tools available from www.info-zip.org. If you are using the infozip tools (in a command prompt window): cd your-install-dir unzip some-package.zip 2. Configuring the package. The configure script is called "configure" on unix/mac and "configure.bat" on Windows. It should be run from a command line after cd'ing to the package directory. You can choose whether you want to use the component by including its source code directly into your project, or build the component as a dynamic shared library (DLL) that is loaded into the application at run-time. The latter may be preferable for technical or licensing (LGPL) reasons. If you want to build a DLL, run the configure script with the argument "-library". Also see the note about usage below. (Components that are Qt plugins, e.g. styles and image formats, are by default built as a plugin DLL.) The configure script will prompt you in some cases for further information. Answer these questions and carefully read the license text before accepting the license conditions. The package cannot be used if you do not accept the license conditions. 3. Building the component and examples (when required). If a DLL is to be built, or if you would like to build the examples, next give the commands qmake make [or nmake if your are using Microsoft Visual C++] The example program(s) can be found in the directory called "examples" or "example". Components that are Qt plugins, e.g. styles and image formats, are ready to be used as soon as they are built, so the rest of this installation instruction can be skipped. 4. Building the Qt Designer plugin (optional). Some of the widget components are provided with plugins for Qt Designer. To build and install the plugin, cd into the some-package/plugin directory and give the commands qmake make [or nmake if your are using Microsoft Visual C++] Restart Qt Designer to make it load the new widget plugin. Note: If you are using the built-in Qt Designer from the Qt Visual Studio Integration, you will need to manually copy the plugin DLL file, i.e. copy %QTDIR%\plugins\designer\some-component.dll to the Qt Visual Studio Integration plugin path, typically: C:\Program Files\Trolltech\Qt VS Integration\plugins Note: If you for some reason are using a Qt Designer that is built in debug mode, you will need to build the plugin in debug mode also. Edit the file plugin.pro in the plugin directory, changing 'release' to 'debug' in the CONFIG line, before running qmake. Solutions components are intended to be used directly from the package directory during development, so there is no 'make install' procedure. Using a component in your project --------------------------------- To use this component in your project, add the following line to the project's .pro file (or do the equivalent in your IDE): include(your-install-dir/some-package/src/some-package.pri) This adds the package's sources and headers to the SOURCES and HEADERS project variables respectively (or, if the component has been configured as a DLL, it adds that library to the LIBS variable), and updates INCLUDEPATH to contain the package's src directory. Additionally, the .pri file may include some dependencies needed by the package. To include a header file from the package in your sources, you can now simply use: #include or alternatively, in pre-Qt 4 style: #include Refer to the documentation to see the classes and headers this components provides. Install documentation (optional) -------------------------------- The HTML documentation for the package's classes is located in the your-install-dir/some-package/doc/html/index.html. You can open this file and read the documentation with any web browser. To install the documentation into Qt Assistant (for Qt version 4.4 and later): 1. In Assistant, open the Edit->Preferences dialog and choose the Documentation tab. Click the Add... button and select the file your-install-dir/some-package/doc/html/some-package.qch For Qt versions prior to 4.4, do instead the following: 1. The directory your-install-dir/some-package/doc/html contains a file called some-package.dcf. Execute the following commands in a shell, command prompt or terminal window: cd your-install-dir/some-package/doc/html/ assistant -addContentFile some-package.dcf The next time you start Qt Assistant, you can access the package's documentation. Removing the documentation from assistant ----------------------------------------- If you have installed the documentation into Qt Assistant, and want to uninstall it, do as follows, for Qt version 4.4 and later: 1. In Assistant, open the Edit->Preferences dialog and choose the Documentation tab. In the list of Registered Documentation, select the item com.nokia.qtsolutions.some-package_version, and click the Remove button. For Qt versions prior to 4.4, do instead the following: 1. The directory your-install-dir/some-package/doc/html contains a file called some-package.dcf. Execute the following commands in a shell, command prompt or terminal window: cd your-install-dir/some-package/doc/html/ assistant -removeContentFile some-package.dcf Using the component as a DLL ---------------------------- 1. Normal components The shared library (DLL) is built and placed in the some-package/lib directory. It is intended to be used directly from there during development. When appropriate, both debug and release versions are built, since the run-time linker will in some cases refuse to load a debug-built DLL into a release-built application or vice versa. The following steps are taken by default to help the dynamic linker to locate the DLL at run-time (during development): Unix: The some-package.pri file will add linker instructions to add the some-package/lib directory to the rpath of the executable. (When distributing, or if your system does not support rpath, you can copy the shared library to another place that is searched by the dynamic linker, e.g. the "lib" directory of your Qt installation.) Mac: The full path to the library is hardcoded into the library itself, from where it is copied into the executable at link time, and ready by the dynamic linker at run-time. (When distributing, you will want to edit these hardcoded paths in the same way as for the Qt DLLs. Refer to the document "Deploying an Application on Mac OS X" in the Qt Reference Documentation.) Windows: the .dll file(s) are copied into the "bin" directory of your Qt installation. The Qt installation will already have set up that directory to be searched by the dynamic linker. 2. Plugins For Qt Solutions plugins (e.g. image formats), both debug and release versions of the plugin are built by default when appropriate, since in some cases the release Qt library will not load a debug plugin, and vice versa. The plugins are automatically copied into the plugins directory of your Qt installation when built, so no further setup is required. Plugins may also be built statically, i.e. as a library that will be linked into your application executable, and so will not need to be redistributed as a separate plugin DLL to end users. Static building is required if Qt itself is built statically. To do it, just add "static" to the CONFIG variable in the plugin/plugin.pro file before building. Refer to the "Static Plugins" section in the chapter "How to Create Qt Plugins" for explanation of how to use a static plugin in your application. The source code of the example program(s) will also typically contain the relevant instructions as comments. Uninstalling ------------ The following command will remove any fils that have been automatically placed outside the package directory itself during installation and building make distclean [or nmake if your are using Microsoft Visual C++] If Qt Assistant documentation or Qt Designer plugins have been installed, they can be uninstalled manually, ref. above. Enjoy! :) - The Qt Solutions Team. ukui-menu/3rd-parties/qtsingleapplication/examples/0000775000175000017500000000000015160463353021452 5ustar fengfengukui-menu/3rd-parties/qtsingleapplication/examples/examples.pro0000664000175000017500000000011115160463353024003 0ustar fengfengTEMPLATE = subdirs SUBDIRS = trivial \ loader \ console ukui-menu/3rd-parties/qtsingleapplication/examples/trivial/0000775000175000017500000000000015160463353023124 5ustar fengfengukui-menu/3rd-parties/qtsingleapplication/examples/trivial/trivial.qdoc0000664000175000017500000000271115160463353025447 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause /*! \page qtsingleapplication-example-trivial.html \title A Trivial Example The application in this example has a log-view that displays messages sent by further instances of the same application. The example demonstrates the use of the QtSingleApplication class to detect and communicate with a running instance of the application using the sendMessage() API. The messageReceived() signal is used to display received messages in a QTextEdit log. \quotefromfile trivial/main.cpp \printuntil instance The example has only the \c main entry point function. A QtSingleApplication object is created immediately. \printuntil return If another instance of this application is already running, sendMessage() will succeed, and this instance just exits immediately. \printuntil show() Otherwise the instance continues as normal and creates the user interface. \printuntil return instance.exec(); The \c logview object is also set as the application's activation window. Every time a message is received, the window will be raised and activated automatically. The messageReceived() signal is also connected to the QTextEdit's append() slot. Every message received from further instances of this application will be displayed in the log. Finally the event loop is entered. */ ukui-menu/3rd-parties/qtsingleapplication/examples/trivial/trivial.pro0000664000175000017500000000012115160463353025312 0ustar fengfengTEMPLATE = app include(../../src/qtsingleapplication.pri) SOURCES += main.cpp ukui-menu/3rd-parties/qtsingleapplication/examples/trivial/main.cpp0000664000175000017500000000145215160463353024556 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include #include class TextEdit : public QTextEdit { Q_OBJECT public: TextEdit(QWidget *parent = 0) : QTextEdit(parent) {} public slots: void append(const QString &str) { QTextEdit::append(str); } }; #include "main.moc" int main(int argc, char **argv) { QtSingleApplication instance(argc, argv); if (instance.sendMessage("Wake up!")) return 0; TextEdit logview; logview.setReadOnly(true); logview.show(); instance.setActivationWindow(&logview); QObject::connect(&instance, SIGNAL(messageReceived(const QString&)), &logview, SLOT(append(const QString&))); return instance.exec(); } ukui-menu/3rd-parties/qtsingleapplication/examples/loader/0000775000175000017500000000000015160463353022720 5ustar fengfengukui-menu/3rd-parties/qtsingleapplication/examples/loader/loader.pro0000664000175000017500000000020615160463353024706 0ustar fengfenggreaterThan(QT_MAJOR_VERSION, 4): QT += printsupport TEMPLATE = app include(../../src/qtsingleapplication.pri) SOURCES += main.cpp ukui-menu/3rd-parties/qtsingleapplication/examples/loader/file1.qsl0000664000175000017500000000000715160463353024436 0ustar fengfengFile 1 ukui-menu/3rd-parties/qtsingleapplication/examples/loader/main.cpp0000664000175000017500000000432415160463353024353 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include #include #include #include #include #include #include #include class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); public slots: void handleMessage(const QString& message); signals: void needToShow(); private: QMdiArea *workspace; }; MainWindow::MainWindow() { workspace = new QMdiArea(this); setCentralWidget(workspace); } void MainWindow::handleMessage(const QString& message) { enum Action { Nothing, Open, Print } action; action = Nothing; QString filename = message; if (message.toLower().startsWith("/print ")) { filename = filename.mid(7); action = Print; } else if (!message.isEmpty()) { action = Open; } if (action == Nothing) { emit needToShow(); return; } QFile file(filename); QString contents; if (file.open(QIODevice::ReadOnly)) contents = file.readAll(); else contents = "[[Error: Could not load file " + filename + "]]"; QTextEdit *view = new QTextEdit; view->setPlainText(contents); switch(action) { case Print: { QPrinter printer; view->print(&printer); delete view; } break; case Open: { workspace->addSubWindow(view); view->setWindowTitle(message); view->show(); emit needToShow(); } break; default: break; }; } #include "main.moc" int main(int argc, char **argv) { QtSingleApplication instance("File loader QtSingleApplication example", argc, argv); QString message; for (int a = 1; a < argc; ++a) { message += argv[a]; if (a < argc-1) message += " "; } if (instance.sendMessage(message)) return 0; MainWindow mw; mw.handleMessage(message); mw.show(); QObject::connect(&instance, SIGNAL(messageReceived(const QString&)), &mw, SLOT(handleMessage(const QString&))); instance.setActivationWindow(&mw, false); QObject::connect(&mw, SIGNAL(needToShow()), &instance, SLOT(activateWindow())); return instance.exec(); } ukui-menu/3rd-parties/qtsingleapplication/examples/loader/file2.qsl0000664000175000017500000000000715160463353024437 0ustar fengfengFile 2 ukui-menu/3rd-parties/qtsingleapplication/examples/loader/loader.qdoc0000664000175000017500000000335515160463353025044 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause /*! \page qtsingleapplication-example-loader.html \title Loading Documents The application in this example loads or prints the documents passed as commandline parameters to further instances of this application. \quotefromfile loader/main.cpp \printuntil }; The user interface in this application is a QMainWindow subclass with a QMdiArea as the central widget. It implements a slot \c handleMessage() that will be connected to the messageReceived() signal of the QtSingleApplication class. \printuntil } The MainWindow constructor creates a minimal user interface. \printto case Print: The handleMessage() slot interprets the message passed in as a filename that can be prepended with \e /print to indicate that the file should just be printed rather than loaded. \printto #include Loading the file will also activate the window. \printto mw The \c main entry point function creates a QtSingleApplication object, and creates a message to send to a running instance of the application. If the message was sent successfully the process exits immediately. \printuntil } If the message could not be sent the application starts up. Note that \c false is passed to the call to setActivationWindow() to prevent automatic activation for every message received, e.g. when the application should just print a file. Instead, the message handling function determines whether activation is requested, and signals that by emitting the needToShow() signal. This is then simply connected directly to QtSingleApplication's activateWindow() slot. */ ukui-menu/3rd-parties/qtsingleapplication/examples/console/0000775000175000017500000000000015160463353023114 5ustar fengfengukui-menu/3rd-parties/qtsingleapplication/examples/console/console.pro0000664000175000017500000000016515160463353025302 0ustar fengfengTEMPLATE = app CONFIG += console SOURCES += main.cpp include(../../src/qtsinglecoreapplication.pri) QT -= gui ukui-menu/3rd-parties/qtsingleapplication/examples/console/console.qdoc0000664000175000017500000000150115160463353025423 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause /*! \page qtsinglecoreapplication-example-console.html \title A non-GUI example This example shows how to use the single-application functionality in a console application. It does not require the \c QtGui library at all. The only differences from the GUI application usage demonstrated in the other examples are: 1) The \c.pro file should include \c qtsinglecoreapplication.pri instead of \c qtsingleapplication.pri 2) The class name is \c QtSingleCoreApplication instead of \c QtSingleApplication. 3) No calls are made regarding window activation, for obvious reasons. console.pro: \quotefile console/console.pro main.cpp: \quotefile console/main.cpp */ ukui-menu/3rd-parties/qtsingleapplication/examples/console/main.cpp0000664000175000017500000000245615160463353024553 0ustar fengfeng// Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). // SPDX-License-Identifier: BSD-3-Clause #include "qtsinglecoreapplication.h" #include void report(const QString& msg) { qDebug("[%i] %s", (int)QCoreApplication::applicationPid(), qPrintable(msg)); } class MainClass : public QObject { Q_OBJECT public: MainClass() : QObject() {} public slots: void handleMessage(const QString& message) { report( "Message received: \"" + message + "\""); } }; int main(int argc, char **argv) { report("Starting up"); QtSingleCoreApplication app(argc, argv); if (app.isRunning()) { QString msg(QString("Hi master, I am %1.").arg(QCoreApplication::applicationPid())); bool sentok = app.sendMessage(msg, 2000); QString rep("Another instance is running, so I will exit."); rep += sentok ? " Message sent ok." : " Message sending failed; the other instance may be frozen."; report(rep); return 0; } else { report("No other instance is running; so I will."); MainClass mainObj; QObject::connect(&app, SIGNAL(messageReceived(const QString&)), &mainObj, SLOT(handleMessage(const QString&))); return app.exec(); } } #include "main.moc" ukui-menu/3rd-parties/qtsingleapplication/buildlib/0000775000175000017500000000000015160463353021422 5ustar fengfengukui-menu/3rd-parties/qtsingleapplication/buildlib/buildlib.pro0000664000175000017500000000072115160463353023732 0ustar fengfengTEMPLATE=lib CONFIG += qt dll qtsingleapplication-buildlib mac:CONFIG += absolute_library_soname win32|mac:!wince*:!win32-msvc:!macx-xcode:CONFIG += debug_and_release build_all include(../src/qtsingleapplication.pri) TARGET = $$QTSINGLEAPPLICATION_LIBNAME DESTDIR = $$QTSINGLEAPPLICATION_LIBDIR win32 { DLLDESTDIR = $$[QT_INSTALL_BINS] QMAKE_DISTCLEAN += $$[QT_INSTALL_BINS]\\$${QTSINGLEAPPLICATION_LIBNAME}.dll } target.path = $$DESTDIR INSTALLS += target ukui-menu/3rd-parties/qtsingleapplication/configure0000775000175000017500000000126115160463353021543 0ustar fengfeng#!/bin/sh if [ "x$1" != "x" -a "x$1" != "x-library" ]; then echo "Usage: $0 [-library]" echo echo "-library: Build the component as a dynamic library (DLL). Default is to" echo " include the component source code directly in the application." echo exit 0 fi rm -f config.pri if [ "x$1" = "x-library" ]; then echo "Configuring to build this component as a dynamic library." echo "SOLUTIONS_LIBRARY = yes" > config.pri fi echo echo "This component is now configured." echo echo "To build the component library (if requested) and example(s)," echo "run qmake and your make command." echo echo "To remove or reconfigure, run make distclean." echo ukui-menu/data/0000775000175000017500000000000015160463365012341 5ustar fengfengukui-menu/data/ukui-menu-global-config.conf0000664000175000017500000000213515160463353017626 0ustar fengfeng# can not uninstall "System Apps" [System Apps] biometric-manager.desktop=0 box-manager.desktop=0 calendar.desktop=0 engrampa.desktop=0 kylin-screenshot.desktop=0 kylin-calculator.desktop=0 kylin-recorder.desktop=0 kylin-software-center.desktop=0 kylin-installer.desktop=0 kylin-camera.desktop=0 kylin-service-support.desktop=0 kylin-music.desktop=0 kylin-video.desktop kylin-user-guide.desktop=0 kylin-printer.desktop=0 kylin-photo-viewer.desktop=0 kylin-os-manager.desktop=0 kylin-os-manager-service-support.desktop=0 ksc-defender.desktop=0 logview.desktop=0 onboard.desktop=0 time-shutdown.desktop=0 ukui-notebook.desktop=0 ukui-new-function-introduction.desktop=0 ukui-clock.desktop=0 ukui-clipboard.desktop=0 ukui-system-monitor.desktop=0 ukui-search.desktop=0 ukui-control-center.desktop=0 peony.desktop=0 yhkylin-backup-tools.desktop=0 [Default Favorite Apps] 0=/usr/share/applications/peony.desktop 1=/usr/share/applications/ukui-control-center.desktop 2=/usr/share/applications/kylin-weather.desktop 3=/usr/share/applications/kylin-software-center.desktop 4=/usr/share/applications/kylin-screenshot.desktop ukui-menu/data/org.ukui.menu.service0000664000175000017500000000007315160463353016426 0ustar fengfeng[D-BUS Service] Name=org.ukui.menu Exec=/usr/bin/ukui-menu ukui-menu/data/org.ukui.menu.settings.gschema.xml0000664000175000017500000000223615160463365021041 0ustar fengfeng 300 animation duration Control the duration of animation 842 width of ukui-menu Control the width of the ukui-menu 688 height of ukui-menu Control the height of the ukui-menu 8 margin of ukui-menu Control the margin of the ukui-menu true follow ukui-panel Control whether to follow the ukui-panel ukui-menu/data/org.ukui.menu.xml0000664000175000017500000000116415160463365015573 0ustar fengfeng ukui-menu/data/ukui-menu.desktop0000664000175000017500000000052215160463353015647 0ustar fengfeng[Desktop Entry] Name=ukui-menu Name[zh_CN]=开始菜单 Name[tr_TR]=Başlangıç menüsü Comment=A simple application launcher Comment[zh_CN]=简易的应用程序启动器 Exec=/usr/bin/ukui-menu %u Icon=ukui-menu Terminal=false Type=Application NoDisplay=true OnlyShowIn=UKUI X-UKUI-AutoRestart=true X-UKUI-Autostart-Phase=Application