diff --git a/frontend/control/README.md b/frontend/control/README.md new file mode 100644 index 0000000..e69de29 diff --git a/frontend/control/control.pri b/frontend/control/control.pri new file mode 100644 index 0000000..c9c9820 --- /dev/null +++ b/frontend/control/control.pri @@ -0,0 +1,17 @@ +include(stack-pages/stack-pages.pri) +include(flow-layout/flow-layout.pri) +include(list-labels/list-labels.pri) + +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/settings-widget.h \ + $$PWD/create-index-ask-dialog.h \ + $$PWD/stacked-widget.h \ + $$PWD/input-box.h \ + +SOURCES += \ + $$PWD/settings-widget.cpp \ + $$PWD/create-index-ask-dialog.cpp \ + $$PWD/stacked-widget.cpp \ + $$PWD/input-box.cpp \ diff --git a/frontend/control/create-index-ask-dialog.cpp b/frontend/control/create-index-ask-dialog.cpp new file mode 100644 index 0000000..6488a7c --- /dev/null +++ b/frontend/control/create-index-ask-dialog.cpp @@ -0,0 +1,150 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ + +#include "create-index-ask-dialog.h" +#include + +#define MAIN_SIZE QSize(380, 202) +#define MAIN_SPACING 0 +#define MAIN_MARGINS 0,0,0,0 +#define TITLE_MARGINS 8,8,8,8 +#define TITLE_HEIGHT 40 +#define ICON_SIZE QSize(24, 24) +#define TIP_LABEL_HEIGHT 64 +#define CLOSE_BTN_SIZE QSize(24, 24) +#define CHECK_BOX_MARGINS 0,0,0,0 +#define BTN_FRAME_MARGINS 0,0,0,0 +#define CONTENT_MARGINS 32,0,32,24 + +using namespace Zeeker; +CreateIndexAskDialog::CreateIndexAskDialog(QWidget *parent) : QDialog(parent) { +// this->setWindowIcon(QIcon::fromTheme("kylin-search")); + this->setWindowTitle(tr("ukui-search")); + + initUi(); +} + +void CreateIndexAskDialog::initUi() { + this->setFixedSize(MAIN_SIZE); + m_mainLyt = new QVBoxLayout(this); + this->setLayout(m_mainLyt); + m_mainLyt->setContentsMargins(MAIN_MARGINS); + m_mainLyt->setSpacing(MAIN_SPACING); + + //标题栏 + m_titleFrame = new QFrame(this); + m_titleFrame->setFixedHeight(TITLE_HEIGHT); + m_titleLyt = new QHBoxLayout(m_titleFrame); + m_titleLyt->setContentsMargins(TITLE_MARGINS); + m_titleFrame->setLayout(m_titleLyt); + m_iconLabel = new QLabel(m_titleFrame); + m_iconLabel->setFixedSize(ICON_SIZE); + m_iconLabel->setPixmap(QIcon::fromTheme("kylin-search").pixmap(QSize(24, 24))); + //主题改变时,更新自定义标题栏的图标 + connect(qApp, &QApplication::paletteChanged, this, [ = ]() { + m_iconLabel->setPixmap(QIcon::fromTheme("kylin-search").pixmap(QSize(24, 24))); + }); + m_titleLabel = new QLabel(m_titleFrame); + m_titleLabel->setText(tr("Search")); + m_closeBtn = new QPushButton(m_titleFrame); + m_closeBtn->setFixedSize(CLOSE_BTN_SIZE); + m_closeBtn->setIcon(QIcon::fromTheme("window-close-symbolic")); + m_closeBtn->setProperty("isWindowButton", 0x02); + m_closeBtn->setProperty("useIconHighlightEffect", 0x08); + m_closeBtn->setFlat(true); + connect(m_closeBtn, &QPushButton::clicked, this, [ = ]() { + this->hide(); + Q_EMIT this->closed(); + }); + m_titleLyt->addWidget(m_iconLabel); + m_titleLyt->addWidget(m_titleLabel); + m_titleLyt->addStretch(); + m_titleLyt->addWidget(m_closeBtn); + + m_mainLyt->addWidget(m_titleFrame); + + //内容区域 + m_contentFrame = new QFrame(this); + m_contentLyt = new QVBoxLayout(m_contentFrame); + m_contentFrame->setLayout(m_contentLyt); + m_contentLyt->setContentsMargins(CONTENT_MARGINS); + + m_tipLabel = new QLabel(m_contentFrame); + m_tipLabel->setText(tr("Creating index can help you getting results quickly, whether to create or not?")); + m_tipLabel->setWordWrap(true); + m_tipLabel->setAlignment(Qt::AlignVCenter); + m_tipLabel->setMinimumHeight(TIP_LABEL_HEIGHT); + m_contentLyt->addWidget(m_tipLabel); + + m_checkFrame = new QFrame(m_contentFrame); + m_checkLyt = new QHBoxLayout(m_checkFrame); + m_checkLyt->setContentsMargins(CHECK_BOX_MARGINS); + m_checkFrame->setLayout(m_checkLyt); + m_checkBox = new QCheckBox(m_checkFrame); + m_checkBox->setText(tr("Don't remind")); + m_checkLyt->addWidget(m_checkBox); + m_checkLyt->addStretch(); + m_contentLyt->addWidget(m_checkFrame); + m_contentLyt->addStretch(); + + m_btnFrame = new QFrame(m_contentFrame); + m_btnLyt = new QHBoxLayout(m_btnFrame); + m_btnFrame->setLayout(m_btnLyt); + m_btnLyt->setContentsMargins(BTN_FRAME_MARGINS); + m_cancelBtn = new QPushButton(m_btnFrame); + m_cancelBtn->setText(tr("No")); + m_confirmBtn = new QPushButton(m_btnFrame); + m_confirmBtn->setText(tr("Yes")); + connect(m_cancelBtn, &QPushButton::clicked, this, [ = ]() { + Q_EMIT this->btnClicked(false, m_checkBox->isChecked()); + this->hide(); + Q_EMIT this->closed(); + }); + connect(m_confirmBtn, &QPushButton::clicked, this, [ = ]() { + Q_EMIT this->btnClicked(true, m_checkBox->isChecked()); + this->hide(); + Q_EMIT this->closed(); + }); + m_btnLyt->addStretch(); + m_btnLyt->addWidget(m_cancelBtn); + m_btnLyt->addWidget(m_confirmBtn); + m_contentLyt->addWidget(m_btnFrame); + + m_mainLyt->addWidget(m_contentFrame); +#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0)) + m_titleFrame->hide(); + this->setFixedSize(380, 162); +#endif +} + +/** + * @brief CreateIndexAskDialog::paintEvent 绘制窗口背景(默认背景较暗) + */ +void CreateIndexAskDialog::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + QPainterPath rectPath; + rectPath.addRect(this->rect()); + p.save(); + p.fillPath(rectPath, palette().color(QPalette::Base)); + p.restore(); + return QDialog::paintEvent(event); +} diff --git a/frontend/control/create-index-ask-dialog.h b/frontend/control/create-index-ask-dialog.h new file mode 100644 index 0000000..4719181 --- /dev/null +++ b/frontend/control/create-index-ask-dialog.h @@ -0,0 +1,75 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ + +#ifndef CREATEINDEXASKDIALOG_H +#define CREATEINDEXASKDIALOG_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Zeeker { +class CreateIndexAskDialog : public QDialog { + Q_OBJECT +public: + CreateIndexAskDialog(QWidget *parent = nullptr); + ~CreateIndexAskDialog() = default; + +private: + void initUi(); + + QVBoxLayout * m_mainLyt = nullptr; + //标题栏 + QFrame * m_titleFrame = nullptr; + QHBoxLayout * m_titleLyt = nullptr; + QLabel * m_iconLabel = nullptr; + QLabel * m_titleLabel = nullptr; + QPushButton * m_closeBtn = nullptr; + + //内容区域 + QFrame * m_contentFrame = nullptr; + QVBoxLayout * m_contentLyt = nullptr; + QLabel * m_tipLabel = nullptr; //提示语 + QFrame * m_checkFrame = nullptr; //"不再提示"选项框区域 + QHBoxLayout * m_checkLyt = nullptr; + QCheckBox * m_checkBox = nullptr; + QFrame * m_btnFrame = nullptr; //底部按钮区域 + QHBoxLayout * m_btnLyt = nullptr; + QPushButton * m_cancelBtn = nullptr; + QPushButton * m_confirmBtn = nullptr; + + void paintEvent(QPaintEvent *); + +Q_SIGNALS: + void closed(); + void btnClicked(const bool&, const bool&); + +}; +} + +#endif // CREATEINDEXASKDIALOG_H diff --git a/frontend/control/flow-layout/flow-layout.cpp b/frontend/control/flow-layout/flow-layout.cpp new file mode 100644 index 0000000..981d14a --- /dev/null +++ b/frontend/control/flow-layout/flow-layout.cpp @@ -0,0 +1,162 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * Copyright (C) 2019 Tianjin KYLIN Information Technology 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "flow-layout.h" + +#include +#include + +FlowLayout::FlowLayout(QWidget *parent, int margin, int hSpacing, int vSpacing) : + QLayout(parent), + m_hSpace(hSpacing), + m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::FlowLayout(int margin, int hSpacing, int vSpacing) + : m_hSpace(hSpacing), + m_vSpace(vSpacing) +{ + setContentsMargins(margin, margin, margin, margin); +} + +FlowLayout::~FlowLayout(){ + QLayoutItem * item; + while ((item = takeAt(0))) { + + } +} + +void FlowLayout::addItem(QLayoutItem *item){ + itemList.append(item); +} + +int FlowLayout::horizontalSpacing() const{ + if (m_hSpace >= 0) { + return m_hSpace; + } else { + return smartSpacing(QStyle::PM_LayoutHorizontalSpacing); + } +} + +int FlowLayout::verticalSpacing() const{ + if (m_vSpace >= 0) { + return m_vSpace; + } else { + return smartSpacing(QStyle::PM_LayoutVerticalSpacing); + } +} + +int FlowLayout::count() const{ + return itemList.size(); +} + +QLayoutItem * FlowLayout::itemAt(int index) const{ + return itemList.value(index); +} + +QLayoutItem * FlowLayout::takeAt(int index){ + if (index >= 0 && index < itemList.size()) + return itemList.takeAt(index); + else + return 0; +} + +Qt::Orientations FlowLayout::expandingDirections() const{ + return 0; +} + +bool FlowLayout::hasHeightForWidth() const{ + return true; +} + +int FlowLayout::heightForWidth(int width) const{ + int height = doLayout(QRect(0, 0, width, 0), true); + return height; +} + +void FlowLayout::setGeometry(const QRect &rect){ + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize FlowLayout::sizeHint() const{ + return minimumSize(); +} + +QSize FlowLayout::minimumSize() const{ + QSize size; + QLayoutItem *item; + Q_FOREACH (item, itemList) + size = size.expandedTo(item->minimumSize()); + + size += QSize(2*margin(), 2*margin()); + return size; +} + +int FlowLayout::doLayout(const QRect &rect, bool testOnly) const{ + int left, top, right, bottom; + getContentsMargins(&left, &top, &right, &bottom); + QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom); + int x = effectiveRect.x(); + int y = effectiveRect.y(); + int lineHeight = 0; + + QLayoutItem *item; + Q_FOREACH (item, itemList) { + QWidget *wid = item->widget(); + int spaceX = horizontalSpacing(); + if (spaceX == -1) + spaceX = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal); + int spaceY = verticalSpacing(); + if (spaceY == -1) + spaceY = wid->style()->layoutSpacing( + QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical); + + int nextX = x + item->sizeHint().width() + spaceX; + if (nextX - spaceX > effectiveRect.right() && lineHeight > 0) { + x = effectiveRect.x(); + y = y + lineHeight + spaceY; + nextX = x + item->sizeHint().width() + spaceX; + lineHeight = 0; + } + + if (!testOnly) + item->setGeometry(QRect(QPoint(x, y), item->sizeHint())); + + x = nextX; + lineHeight = qMax(lineHeight, item->sizeHint().height()); + } + return y + lineHeight - rect.y() + bottom; +} + +int FlowLayout::smartSpacing(QStyle::PixelMetric pm) const{ + QObject *parent = this->parent(); + if (!parent) { + return -1; + } else if (parent->isWidgetType()) { + QWidget *pw = static_cast(parent); + return pw->style()->pixelMetric(pm, 0, pw); + } else { + return static_cast(parent)->spacing(); + } +} diff --git a/frontend/control/flow-layout/flow-layout.h b/frontend/control/flow-layout/flow-layout.h new file mode 100644 index 0000000..70df2e7 --- /dev/null +++ b/frontend/control/flow-layout/flow-layout.h @@ -0,0 +1,61 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * Copyright (C) 2019 Tianjin KYLIN Information Technology 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef FLOWLAYOUT_H +#define FLOWLAYOUT_H + +#include +#include +#include + +class FlowLayout : public QLayout +{ + +public: + explicit FlowLayout(QWidget *parent, int margin = -1, int hSpacing = -1, int vSpacing = -1); + explicit FlowLayout(int margin = -1, int hSpacing = -1, int vSpacing = -1); + ~FlowLayout(); + +public: + void addItem(QLayoutItem *item) Q_DECL_OVERRIDE; + int horizontalSpacing() const; + int verticalSpacing() const; + Qt::Orientations expandingDirections() const Q_DECL_OVERRIDE; + bool hasHeightForWidth() const Q_DECL_OVERRIDE; + int heightForWidth(int) const Q_DECL_OVERRIDE; + int count() const Q_DECL_OVERRIDE; + QLayoutItem *itemAt(int index) const Q_DECL_OVERRIDE; + QSize minimumSize() const Q_DECL_OVERRIDE; + void setGeometry(const QRect &rect) Q_DECL_OVERRIDE; + QSize sizeHint() const Q_DECL_OVERRIDE; + QLayoutItem *takeAt(int index) Q_DECL_OVERRIDE; + +private: + int doLayout(const QRect &rect, bool testOnly) const; + int smartSpacing(QStyle::PixelMetric pm) const; + +private: + QList itemList; + int m_hSpace; + int m_vSpace; + + +}; + +#endif // FLOWLAYOUT_H diff --git a/frontend/control/flow-layout/flow-layout.pri b/frontend/control/flow-layout/flow-layout.pri new file mode 100644 index 0000000..d0e147a --- /dev/null +++ b/frontend/control/flow-layout/flow-layout.pri @@ -0,0 +1,5 @@ +SOURCES += \ + $$PWD/flow-layout.cpp \ + +HEADERS += \ + $$PWD/flow-layout.h \ diff --git a/frontend/control/input-box.cpp b/frontend/control/input-box.cpp new file mode 100644 index 0000000..5b87dc5 --- /dev/null +++ b/frontend/control/input-box.cpp @@ -0,0 +1,265 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#include "input-box.h" + +using namespace Zeeker; +/** + * @brief ukui-search顶部搜索界面 + */ +SeachBarWidget::SeachBarWidget(QWidget *parent): QWidget(parent) { +} + +SeachBarWidget::~SeachBarWidget() { +} + +/** + * @brief ukui-search 顶部搜索框的ui,包含设置按钮 + */ +SeachBar::SeachBar() { + setFocusPolicy(Qt::NoFocus); +} + +SearchBarWidgetLayout::SearchBarWidgetLayout() { +} + +SearchBarWidgetLayout::~SearchBarWidgetLayout() { +} + +SeachBar::~SeachBar() { +} + +/** + * @brief 顶部搜索框所在界面的布局 + */ +SearchBarHLayout::SearchBarHLayout(QWidget *parent): QHBoxLayout(parent) { + initUI(); + + m_timer = new QTimer; + connect(m_timer, &QTimer::timeout, this, [ = ]() { + m_timer->stop(); + Q_EMIT this->requestSearchKeyword(m_queryLineEdit->text()); + }); + connect(m_queryLineEdit, &SearchLineEdit::textChanged, this, [ = ](QString text) { + if(m_isEmpty) { + m_isEmpty = false; + Q_EMIT this->requestSearchKeyword(text); + } else { + if(text == "") { + m_isEmpty = true; + m_timer->stop(); + Q_EMIT this->requestSearchKeyword(m_queryLineEdit->text()); + return; + } + m_timer->stop(); + m_timer->start(0.1 * 1000); + } + }); +} + +SearchBarHLayout::~SearchBarHLayout() { + if(m_timer) { + delete m_timer; + m_timer = NULL; + } + if(m_queryLineEdit) { + delete m_queryLineEdit; + m_queryLineEdit = nullptr; + } +} + +/** + * @brief 初始化ui + */ +void SearchBarHLayout::initUI() { + m_queryLineEdit = new SearchLineEdit(); + m_queryLineEdit->installEventFilter(this); + m_queryLineEdit->setTextMargins(30, 1, 0, 1); + this->setContentsMargins(0, 0, 0, 0); + this->setAlignment(m_queryLineEdit, Qt::AlignCenter); + this->addWidget(m_queryLineEdit); + + m_queryWidget = new QWidget(m_queryLineEdit); + m_queryWidget->setFocusPolicy(Qt::NoFocus); + + QHBoxLayout* queryWidLayout = new QHBoxLayout; + queryWidLayout->setContentsMargins(8, 4, 0, 0); + queryWidLayout->setAlignment(Qt::AlignJustify); + queryWidLayout->setSpacing(5); + m_queryWidget->setLayout(queryWidLayout); + + + QPixmap pixmap(QIcon::fromTheme("system-search-symbolic").pixmap(QSize(20, 20))); + m_queryIcon = new QLabel; + m_queryIcon->setFixedSize(pixmap.size()); + m_queryIcon->setPixmap(pixmap); + + m_queryText = new QLabel; + m_queryText->setText(tr("Search")); + m_queryText->setEnabled(false); + m_queryText->setContentsMargins(0, 0, 0, 4); + m_queryText->adjustSize(); + + queryWidLayout->addWidget(m_queryIcon); + queryWidLayout->addWidget(m_queryText); + m_queryWidget->setGeometry(QRect((m_queryLineEdit->width() - (m_queryIcon->width() + m_queryText->width() + 15)) / 2 - 10, 0, + m_queryIcon->width() + m_queryText->width() + 20, 35)); //设置图标初始位置 + + m_animation = new QPropertyAnimation(m_queryWidget, "geometry"); + m_animation->setDuration(100); //动画时长 + connect(m_animation, &QPropertyAnimation::finished, this, [ = ]() { + if(m_isSearching) { + m_queryWidget->layout()->removeWidget(m_queryText); + m_queryText->setParent(nullptr); + } else { + m_queryWidget->layout()->addWidget(m_queryText); + } + }); +} + +void SearchBarHLayout::effectiveSearchRecord() { + m_queryLineEdit->record(); +} + +void SearchBarHLayout::focusIn() { + m_queryLineEdit->setFocus(); +} + +void SearchBarHLayout::focusOut() { + m_queryLineEdit->clearFocus(); + if(! m_queryText->parent()) { + m_queryWidget->layout()->addWidget(m_queryText); + m_queryText->adjustSize(); + } + m_queryWidget->setGeometry(QRect((m_queryLineEdit->width() - (m_queryIcon->width() + m_queryText->width() + 15)) / 2 - 10, 0, + m_queryIcon->width() + m_queryText->width() + 20, 35)); //使图标回到初始位置 +} + +void SearchBarHLayout::reSearch() +{ + Q_EMIT this->requestSearchKeyword(m_queryLineEdit->text()); +} + +void SearchBarHLayout::clearText() { + m_queryLineEdit->setText(""); +} + +QString SearchBarHLayout::text() { + return m_queryLineEdit->text(); +} + +bool SearchBarHLayout::eventFilter(QObject *watched, QEvent *event) { + if(watched == m_queryLineEdit) { + if(event->type() == QEvent::FocusIn) { + if(m_queryLineEdit->text().isEmpty()) { + m_animation->stop(); + m_animation->setStartValue(m_queryWidget->geometry()); + m_animation->setEndValue(QRect(0, 0, m_queryIcon->width() + 10, 35)); + m_animation->setEasingCurve(QEasingCurve::OutQuad); + m_animation->start(); + } + m_isSearching = true; + } else if(event->type() == QEvent::FocusOut) { + if(m_queryLineEdit->text().isEmpty()) { + if(m_isSearching) { + m_animation->stop(); + m_queryText->adjustSize(); + m_animation->setStartValue(QRect(0, 0, m_queryIcon->width() + 5, 35)); + m_animation->setEndValue(QRect((m_queryLineEdit->width() - (m_queryIcon->width() + m_queryText->width() + 10)) / 2, 0, + m_queryIcon->width() + m_queryText->width() + 20, 35)); + m_animation->setEasingCurve(QEasingCurve::InQuad); + m_animation->start(); + } + } + m_isSearching = false; + } + } + return QObject::eventFilter(watched, event); +} + +/** + * @brief UKuiSearchLineEdit 全局搜索的输入框 + */ +SearchLineEdit::SearchLineEdit() { + this->setFocusPolicy(Qt::ClickFocus); + this->installEventFilter(this); +// this->setContextMenuPolicy(Qt::NoContextMenu); + this->setMaxLength(100); + + m_completer = new QCompleter(this); + m_model = new QStringListModel(this); + m_model->setStringList(GlobalSettings::getInstance()->getSearchRecord()); + m_completer->setModel(m_model); + m_completer->setCompletionMode(QCompleter::InlineCompletion); + //TODO make a popup window to show the completer. +// QListView *popView = new QListView(this); +// popView->setFocusPolicy(Qt::NoFocus); +// popView->setProperty("useCustomShadow", true); +// popView->setProperty("customShadowDarkness", 0.5); +// popView->setProperty("customShadowWidth", 20); +// popView->setProperty("customShadowRadius", QVector4D(6, 6, 6, 6)); +// popView->setProperty("customShadowMargins", QVector4D(20, 20, 20, 20)); +// popView->setAttribute(Qt::WA_TranslucentBackground); +// m_completer->setPopup(popView); + m_completer->setMaxVisibleItems(14); + + setCompleter(m_completer); + + //这是搜索框图标,要改 +// QAction *searchAction = new QAction(this); +// searchAction->setIcon(QIcon(":/res/icons/edit-find-symbolic.svg")); +// this->addAction(searchAction,QLineEdit::LeadingPosition); + + /*发送输入框文字改变的dbus*/ + QDBusConnection::sessionBus().unregisterService("org.ukui.search.service"); + QDBusConnection::sessionBus().registerService("org.ukui.search.service"); + QDBusConnection::sessionBus().registerObject("/lineEdit/textChanged", this, QDBusConnection :: ExportAllSlots | QDBusConnection :: ExportAllSignals); + + connect(this, &QLineEdit::textChanged, this, &SearchLineEdit::lineEditTextChanged); + connect(this, &QLineEdit::textChanged, this, [ = ]() { + m_isRecorded = false; + }); +} + +void SearchLineEdit::record() { + if(m_isRecorded == true || text().size() <= 1 || text().isEmpty()) + return; + GlobalSettings::getInstance()->setSearchRecord(text(), QDateTime::currentDateTime()); + m_isRecorded = true; + m_model->setStringList(GlobalSettings::getInstance()->getSearchRecord()); +} + +SearchLineEdit::~SearchLineEdit() { + +} + +/** + * @brief lineEditTextChange 监听到搜索框文字的textChanged信号,发送dbus信号给其他程序 + * @param arg 搜索框的文本 + * + * 需要此点击信号的应用需要做如下绑定 + * QDBusConnection::sessionBus().connect(QString(), QString("/lineEdit/textChanged"), "org.ukui.search.inputbox", "InputBoxTextChanged", this, SLOT(client_get(QString))); + * 在槽函数client_get(void) 中处理接受到的点击信号 + */ +void SearchLineEdit::lineEditTextChanged(QString arg) { + QDBusMessage message = QDBusMessage::createSignal("/lineEdit/textChanged", "org.ukui.search.inputbox", "InputBoxTextChanged"); + message << arg; + QDBusConnection::sessionBus().send(message); +} diff --git a/frontend/control/input-box.h b/frontend/control/input-box.h new file mode 100644 index 0000000..b581d96 --- /dev/null +++ b/frontend/control/input-box.h @@ -0,0 +1,119 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#ifndef INPUTBOX_H +#define INPUTBOX_H +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "global-settings.h" + +namespace Zeeker { +class SearchLineEdit; + +class SeachBarWidget: public QWidget { +public: + SeachBarWidget(QWidget *parent = nullptr); + ~SeachBarWidget(); +}; + +class SeachBar: public QWidget { +public: + SeachBar(); + ~SeachBar(); + +private: +// QLineEdit *m_queryLineEdit=nullptr; +}; + +class SearchBarHLayout : public QHBoxLayout { + Q_OBJECT +public: + SearchBarHLayout(QWidget *parent = nullptr); + ~SearchBarHLayout(); + void clearText(); + QString text(); + void focusIn(); + void focusOut(); + void reSearch(); + +protected: + bool eventFilter(QObject *watched, QEvent *event); + +private: + void initUI(); + bool m_isEmpty = true; + QTimer * m_timer = nullptr; + + SearchLineEdit * m_queryLineEdit = nullptr; + QPropertyAnimation * m_animation = nullptr; + QWidget * m_queryWidget = nullptr; + QLabel * m_queryIcon = nullptr; + QLabel * m_queryText = nullptr; + bool m_isSearching = false; + +Q_SIGNALS: + void requestSearchKeyword(QString text); +public Q_SLOTS: + void effectiveSearchRecord(); + +}; +class SearchBarWidgetLayout : public QHBoxLayout { +public: + SearchBarWidgetLayout(); + ~SearchBarWidgetLayout(); +private: + void initUI(); + +}; + +class SearchLineEdit : public QLineEdit { + Q_OBJECT + + /* + * 负责与ukui桌面环境应用通信的dbus + * 搜索框文本改变的时候发送信号 +    */ + Q_CLASSINFO("D-Bus Interface", "org.ukui.search.inputbox") +public: + SearchLineEdit(); + void record(); + ~SearchLineEdit(); + +private Q_SLOTS: + void lineEditTextChanged(QString arg); +private: + QStringListModel *m_model = nullptr; + QCompleter *m_completer = nullptr; + bool m_isRecorded = false; +}; +} + +#endif //INPUTBOX_H diff --git a/frontend/control/list-labels/list-labels.pri b/frontend/control/list-labels/list-labels.pri new file mode 100644 index 0000000..8a10490 --- /dev/null +++ b/frontend/control/list-labels/list-labels.pri @@ -0,0 +1,9 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/show-more-label.h \ + $$PWD/title-label.h \ + +SOURCES += \ + $$PWD/show-more-label.cpp \ + $$PWD/title-label.cpp \ diff --git a/frontend/control/list-labels/show-more-label.cpp b/frontend/control/list-labels/show-more-label.cpp new file mode 100644 index 0000000..8c33de7 --- /dev/null +++ b/frontend/control/list-labels/show-more-label.cpp @@ -0,0 +1,77 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#include "show-more-label.h" +#include +#include + +using namespace Zeeker; +ShowMoreLabel::ShowMoreLabel(QWidget *parent) : QWidget(parent) { + initUi(); + m_timer = new QTimer; +} + +void ShowMoreLabel::resetLabel() { + m_isOpen = false; + m_textLabel->setText(tr("Show More...")); +} + +/** + * @brief ShowMoreLabel::getExpanded 获取当前是否是展开状态 + * @return true已展开,false已收起 + */ +bool ShowMoreLabel::getExpanded() { + return m_isOpen; +} + +void ShowMoreLabel::initUi() { + QPalette pal = palette(); + pal.setColor(QPalette::WindowText, QColor(55, 144, 250, 255)); + m_layout = new QHBoxLayout(this); + m_layout->setContentsMargins(0, 0, 0, 6); + m_textLabel = new QLabel(this); + m_textLabel->setText(tr("Show More...")); + m_textLabel->setCursor(QCursor(Qt::PointingHandCursor)); + m_textLabel->installEventFilter(this); +// m_loadingIconLabel = new QLabel(this); //使用图片显示加载状态时,取消此label的注释 +// m_loadingIconLabel->setFixedSize(18, 18); +// m_loadingIconLabel->hide(); + m_layout->setAlignment(Qt::AlignRight); + m_layout->addWidget(m_textLabel); + m_textLabel->setPalette(pal); +// m_layout->addWidget(m_loadingIconLabel); +} + +bool ShowMoreLabel::eventFilter(QObject *watched, QEvent *event) { + if(watched == m_textLabel) { + if(event->type() == QEvent::MouseButtonPress) { + if(! m_timer->isActive()) { + if(!m_isOpen) { + m_isOpen = true; + Q_EMIT this->showMoreClicked(); + } else { + m_isOpen = false; + Q_EMIT this->retractClicked(); + } + } + } + } + return QWidget::eventFilter(watched, event); +} diff --git a/frontend/control/list-labels/show-more-label.h b/frontend/control/list-labels/show-more-label.h new file mode 100644 index 0000000..3df7c18 --- /dev/null +++ b/frontend/control/list-labels/show-more-label.h @@ -0,0 +1,59 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#ifndef SHOWMORELABEL_H +#define SHOWMORELABEL_H + +#include +#include +#include +#include + +namespace Zeeker { +class ShowMoreLabel : public QWidget { + Q_OBJECT +public: + explicit ShowMoreLabel(QWidget *parent = nullptr); + ~ShowMoreLabel() = default; + void resetLabel(); + bool getExpanded(); + +protected: + bool eventFilter(QObject *, QEvent *); + +private: + QHBoxLayout * m_layout = nullptr; + QLabel * m_textLabel = nullptr; + QLabel * m_loadingIconLabel = nullptr; + QTimer * m_timer = nullptr; + bool m_isOpen = false; + int m_currentState = 0; + + void initUi(); + +Q_SIGNALS: + void showMoreClicked(); + void retractClicked(); + +public Q_SLOTS: +}; +} + +#endif // SHOWMORELABEL_H diff --git a/frontend/control/list-labels/title-label.cpp b/frontend/control/list-labels/title-label.cpp new file mode 100644 index 0000000..0d8aaef --- /dev/null +++ b/frontend/control/list-labels/title-label.cpp @@ -0,0 +1,50 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#include "title-label.h" +#include +#include + +using namespace Zeeker; +TitleLabel::TitleLabel(QWidget * parent) : QLabel(parent) { + this->setContentsMargins(8, 0, 0, 0); + this->setFixedHeight(24); +} + +TitleLabel::~TitleLabel() { + +} + +void TitleLabel::paintEvent(QPaintEvent * event) { + Q_UNUSED(event) + + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + + QRect rect = this->rect(); + p.setRenderHint(QPainter::Antialiasing); // 反锯齿; + p.setBrush(opt.palette.color(QPalette::Text)); + p.setOpacity(0.04); + p.setPen(Qt::NoPen); + p.drawRoundedRect(rect, 0, 0); + return QLabel::paintEvent(event); +} diff --git a/frontend/control/list-labels/title-label.h b/frontend/control/list-labels/title-label.h new file mode 100644 index 0000000..791189d --- /dev/null +++ b/frontend/control/list-labels/title-label.h @@ -0,0 +1,37 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#ifndef TITLELABEL_H +#define TITLELABEL_H + +#include + +namespace Zeeker { +class TitleLabel : public QLabel { +public: + TitleLabel(QWidget * parent = nullptr); + ~TitleLabel(); + +protected: + void paintEvent(QPaintEvent *); +}; +} + +#endif // TITLELABEL_H diff --git a/frontend/control/settings-widget.cpp b/frontend/control/settings-widget.cpp new file mode 100644 index 0000000..a827e25 --- /dev/null +++ b/frontend/control/settings-widget.cpp @@ -0,0 +1,594 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#include "settings-widget.h" +#include +#include +#include +#include +#include +#include +#include "global-settings.h" +#include "file-utils.h" + +using namespace Zeeker; +extern void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed); +SettingsWidget::SettingsWidget(QWidget *parent) : QWidget(parent) { +// this->setWindowIcon(QIcon::fromTheme("kylin-search")); + this->setWindowTitle(tr("ukui-search-settings")); +// this->setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint); +// this->setAttribute(Qt::WA_TranslucentBackground); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + m_hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; + m_hints.functions = MWM_FUNC_ALL; + m_hints.decorations = MWM_DECOR_BORDER; + XAtomHelper::getInstance()->setWindowMotifHint(winId(), m_hints); +#endif + + initUi(); + refreshIndexState(); + setupBlackList(GlobalSettings::getInstance()->getBlockDirs()); + resetWebEngine(); +} + +SettingsWidget::~SettingsWidget() { +} + +/** + * @brief SettingsWidget::initUi 初始化界面UI + */ +void SettingsWidget::initUi() { + QPalette pal = palette(); + pal.setColor(QPalette::Window, QColor(0, 0, 0, 0)); +// this->setFixedWidth(528); +// this->setMinimumHeight(460); +// this->setMaximumHeight(680); + m_mainLyt = new QVBoxLayout(this); + m_mainLyt->setContentsMargins(16, 8, 16, 24); + this->setLayout(m_mainLyt); + + //标题栏 + m_titleFrame = new QFrame(this); + m_titleFrame->setFixedHeight(40); + m_titleLyt = new QHBoxLayout(m_titleFrame); + m_titleLyt->setContentsMargins(0, 0, 0, 0); + m_titleFrame->setLayout(m_titleLyt); + m_titleIcon = new QLabel(m_titleFrame); + m_titleIcon->setPixmap(QIcon::fromTheme("kylin-search").pixmap(QSize(24, 24))); + //主题改变时,更新自定义标题栏的图标 + connect(qApp, &QApplication::paletteChanged, this, [ = ]() { + m_titleIcon->setPixmap(QIcon::fromTheme("kylin-search").pixmap(QSize(24, 24))); + }); + m_titleLabel = new QLabel(m_titleFrame); + m_titleLabel->setText(tr("Search")); + m_closeBtn = new QPushButton(m_titleFrame); + m_closeBtn->setFixedSize(24, 24); +// m_closeBtn->setIcon(QIcon(":/res/icons/close.svg")); + m_closeBtn->setIcon(QIcon::fromTheme("window-close-symbolic")); + m_closeBtn->setProperty("isWindowButton", 0x02); + m_closeBtn->setProperty("useIconHighlightEffect", 0x08); + m_closeBtn->setFlat(true); + connect(m_closeBtn, &QPushButton::clicked, this, [ = ]() { + Q_EMIT this->settingWidgetClosed(); + m_timer->stop(); + this->close(); + }); + m_titleLyt->addWidget(m_titleIcon); + m_titleLyt->addWidget(m_titleLabel); + m_titleLyt->addStretch(); + m_titleLyt->addWidget(m_closeBtn); + m_mainLyt->addWidget(m_titleFrame); + + m_contentFrame = new QFrame(this); + m_contentLyt = new QVBoxLayout(m_contentFrame); + m_contentFrame->setLayout(m_contentLyt); + m_contentLyt->setContentsMargins(8, 0, 8, 0); + m_mainLyt->addWidget(m_contentFrame); + + //设置 + m_settingLabel = new QLabel(m_contentFrame); + m_settingLabel->setText(tr("

Settings

")); + m_contentLyt->addWidget(m_settingLabel); + + //文件索引 + m_indexTitleLabel = new QLabel(m_contentFrame); + m_indexTitleLabel->setText(tr("

Index State

")); + m_indexStateLabel = new QLabel(m_contentFrame); + m_indexStateLabel->setText(tr("...")); + m_indexNumLabel = new QLabel(m_contentFrame); + m_indexNumLabel->setText(tr("...")); + m_contentLyt->addWidget(m_indexTitleLabel); + m_contentLyt->addWidget(m_indexStateLabel); +// m_mainLyt->addWidget(m_indexNumLabel); + m_indexNumLabel->hide(); + + //文件索引设置(黑名单) + m_indexSettingLabel = new QLabel(m_contentFrame); + m_indexSettingLabel->setText(tr("

File Index Settings

")); + m_indexDescLabel = new QLabel(m_contentFrame); + m_indexDescLabel->setText(tr("Following folders will not be searched. You can set it by adding and removing folders.")); + m_indexDescLabel->setWordWrap(true); + m_indexBtnFrame = new QFrame(m_contentFrame); + m_indexBtnLyt = new QHBoxLayout(m_indexBtnFrame); + m_indexBtnLyt->setContentsMargins(0, 0, 0, 0); + m_indexBtnFrame->setLayout(m_indexBtnLyt); + m_addDirBtn = new QPushButton(m_indexBtnFrame); + m_addDirBtn->setFixedHeight(32); + m_addDirBtn->setMinimumWidth(164); + m_addDirBtn->setText(tr("Add ignored folders")); + connect(m_addDirBtn, &QPushButton::clicked, this, &SettingsWidget::onBtnAddClicked); + m_indexBtnLyt->addWidget(m_addDirBtn); + m_indexBtnLyt->addStretch(); + m_dirListArea = new QScrollArea(m_contentFrame); + m_dirListArea->setPalette(pal); + m_dirListArea->setFrameShape(QFrame::Shape::NoFrame); + m_dirListWidget = new QWidget(m_contentFrame); + m_dirListLyt = new QVBoxLayout(m_dirListWidget); + m_dirListLyt->setContentsMargins(0, 0, 0, 0); + m_dirListLyt->setSpacing(0); + m_dirListWidget->setLayout(m_dirListLyt); + m_dirListArea->setWidget(m_dirListWidget); + m_dirListArea->setWidgetResizable(m_contentFrame); + m_contentLyt->addWidget(m_indexSettingLabel); + m_contentLyt->addWidget(m_indexDescLabel); + m_contentLyt->addWidget(m_indexBtnFrame); + m_contentLyt->addWidget(m_dirListArea); + + //搜索引擎设置 + m_searchEngineLabel = new QLabel(m_contentFrame); + m_searchEngineLabel->setText(tr("

Search Engine Settings

")); + m_engineDescLabel = new QLabel(m_contentFrame); + m_engineDescLabel->setText(tr("Please select search engine you preferred.")); + m_engineDescLabel->setWordWrap(true); + m_engineBtnGroup = new QButtonGroup(m_contentFrame); + m_baiduBtn = new QRadioButton(m_contentFrame); + m_sougouBtn = new QRadioButton(m_contentFrame); + m_360Btn = new QRadioButton(m_contentFrame); + m_baiduBtn->setFixedSize(16, 16); + m_sougouBtn->setFixedSize(16, 16); + m_360Btn->setFixedSize(16, 16); + m_radioBtnFrame = new QFrame(m_contentFrame); + m_radioBtnLyt = new QHBoxLayout(m_radioBtnFrame); + m_radioBtnFrame->setLayout(m_radioBtnLyt); + m_baiduLabel = new QLabel(); + m_baiduLabel->setText(tr("baidu")); + m_sougouLabel = new QLabel(); + m_sougouLabel->setText(tr("sougou")); + m_360Label = new QLabel(); + m_360Label->setText(tr("360")); + m_radioBtnLyt->setContentsMargins(0, 0, 0, 0); + m_radioBtnLyt->addWidget(m_baiduBtn); + m_radioBtnLyt->addWidget(m_baiduLabel); + m_radioBtnLyt->addWidget(m_sougouBtn); + m_radioBtnLyt->addWidget(m_sougouLabel); + m_radioBtnLyt->addWidget(m_360Btn); + m_radioBtnLyt->addWidget(m_360Label); + m_radioBtnLyt->addStretch(); + m_engineBtnGroup->setExclusive(true); + m_engineBtnGroup->addButton(m_baiduBtn); + m_engineBtnGroup->addButton(m_sougouBtn); + m_engineBtnGroup->addButton(m_360Btn); +// m_engineBtnGroup->setId(m_baiduBtn, WebEngine::Baidu); +// m_engineBtnGroup->setId(m_sougouBtn, WebEngine::Sougou); +// m_engineBtnGroup->setId(m_360Btn, WebEngine::_360); +// connect(m_engineBtnGroup, QOverload::of(&QButtonGroup::buttonClicked), [ = ] (int id) { +// setWebEngine(id); +// }); + connect(m_baiduBtn, &QRadioButton::clicked, [ = ](bool checked) { + if(checked) setWebEngine("baidu"); + }); + connect(m_sougouBtn, &QRadioButton::clicked, [ = ](bool checked) { + if(checked) setWebEngine("sougou"); + }); + connect(m_360Btn, &QRadioButton::clicked, [ = ](bool checked) { + if(checked) setWebEngine("360"); + }); + + m_contentLyt->addWidget(m_searchEngineLabel); + m_contentLyt->addWidget(m_engineDescLabel); + m_contentLyt->addWidget(m_radioBtnFrame); + + //取消与确认按钮 (隐藏) +// m_bottomBtnFrame = new QFrame(this); +// m_bottomBtnLyt = new QHBoxLayout(m_bottomBtnFrame); +// m_bottomBtnFrame->setLayout(m_bottomBtnLyt); +// m_bottomBtnLyt->setSpacing(20); +// m_cancelBtn = new QPushButton(m_bottomBtnFrame); +// m_cancelBtn->setText(tr("Cancel")); +// m_cancelBtn->setFixedSize(80, 32); +// connect(m_cancelBtn, &QPushButton::clicked, this, &SettingsWidget::onBtnCancelClicked); +// m_confirmBtn = new QPushButton(m_bottomBtnFrame); +// m_confirmBtn->setText(tr("Confirm")); +// m_confirmBtn->setFixedSize(80, 32); +// connect(m_confirmBtn, &QPushButton::clicked, this, &SettingsWidget::onBtnConfirmClicked); +// m_bottomBtnLyt->addStretch(); +// m_bottomBtnLyt->addWidget(m_cancelBtn); +// m_bottomBtnLyt->addWidget(m_confirmBtn); +// m_mainLyt->addWidget(m_bottomBtnFrame); + + m_contentLyt->addStretch(); + +#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0)) + this->m_titleFrame->hide(); + setAttribute(Qt::WA_DeleteOnClose); +#endif +} + +/** + * @brief SettingsWidget::setupBlackList 创建黑名单列表 + * @param list 文件夹路径列表 + */ +void SettingsWidget::setupBlackList(const QStringList& list) { + clearLayout(m_dirListLyt); + m_blockdirs = 0; + Q_FOREACH(QString path, list) { + FolderListItem * item = new FolderListItem(m_dirListWidget, path); + m_dirListLyt->addWidget(item); + item->setMaximumWidth(this->width() - 52); + connect(item, &FolderListItem::onDelBtnClicked, this, &SettingsWidget::onBtnDelClicked); + m_blockdirs ++; + } + this->resize(); + m_dirListWidget->setFixedWidth(this->width() - 68); +// m_dirListLyt->addStretch(); +} + +/** + * @brief SettingsWidget::clearLayout 清空某个布局 + * @param layout 需要清空的布局 + */ +void SettingsWidget::clearLayout(QLayout * layout) { + if(! layout) return; + QLayoutItem * child; + while((child = layout->takeAt(0)) != 0) { + if(child->widget()) { + child->widget()->setParent(NULL); + } + delete child; + } + child = NULL; +} + +/** + * @brief SettingsWidget::refreshIndexState 定时刷新索引项 + */ +void SettingsWidget::refreshIndexState() { +// qDebug()<<"FileUtils::_index_status: "<setIndexState(true); + } else { + this->setIndexState(false); + } + m_indexNumLabel->setText(QString("%1/%2").arg(QString::number(SearchManager::getCurrentIndexCount())).arg(QString::number(FileUtils::_max_index_count))); + m_timer = new QTimer; + connect(m_timer, &QTimer::timeout, this, [ = ]() { + qDebug() << "FileUtils::_index_status: " << FileUtils::_index_status; + if(FileUtils::_index_status != 0) { + this->setIndexState(true); + } else { + this->setIndexState(false); + } + m_indexNumLabel->setText(QString("%1/%2").arg(QString::number(SearchManager::getCurrentIndexCount())).arg(QString::number(FileUtils::_max_index_count))); + }); + m_timer->start(0.5 * 1000); +} + +/** + * @brief SettingsWidget::onBtnDelClicked 删除黑名单中的目录 + * @param path 文件夹路径 + */ +void SettingsWidget::onBtnDelClicked(const QString& path) { + QMessageBox message(QMessageBox::Question, tr("Search"), tr("Whether to delete this directory?")); + QPushButton * buttonYes = message.addButton(tr("Yes"), QMessageBox::YesRole); + message.addButton(tr("No"), QMessageBox::NoRole); + message.exec(); + if(message.clickedButton() != buttonYes) { + return; + } + + int returnCode = 0; + if(GlobalSettings::getInstance()->setBlockDirs(path, returnCode, true)) { + qDebug() << "Remove block dir in onBtnDelClicked() successed."; + Q_FOREACH(FolderListItem * item, m_dirListWidget->findChildren()) { + if(item->getPath() == path) { + item->deleteLater(); + item = NULL; + m_blockdirs --; + this->resize(); + return; + } + } + } else { + showWarningDialog(returnCode); + } +} + +/** + * @brief SettingsWidget::resetWebEngine 获取当前的搜索引擎并反映在UI控件上 + */ +void SettingsWidget::resetWebEngine() { + QString engine = GlobalSettings::getInstance()->getValue(WEB_ENGINE).toString(); + m_engineBtnGroup->blockSignals(true); + if(!engine.isEmpty()) { + if(engine == "360") { + m_360Btn->setChecked(true); + } else if(engine == "sougou") { + m_sougouBtn->setChecked(true); + } else { + m_baiduBtn->setChecked(true); + } + } else { + m_baiduBtn->setChecked(true); + } + m_engineBtnGroup->blockSignals(false); +} + +/** + * @brief SettingsWidget::setWebEngine + * @param engine 选择的搜索引擎 + */ +void SettingsWidget::setWebEngine(const QString& engine) { +// GlobalSettings::getInstance()->setValue(WEB_ENGINE, engine); + Q_EMIT this->webEngineChanged(engine); +} + +/** + * @brief setIndexState 设置当前索引状态 + * @param isCreatingIndex 是否正在创建索引 + */ +void SettingsWidget::setIndexState(bool isCreatingIndex) { + if(isCreatingIndex) { + m_indexStateLabel->setText(tr("Creating ...")); + return; + } + m_indexStateLabel->setText(tr("Done")); +} + +/** + * @brief SettingsWidget::setIndexNum 设置当前索引项数量 + * @param num 索引项数量 + */ +void SettingsWidget::setIndexNum(int num) { + m_indexNumLabel->setText(QString(tr("Index Entry: %1")).arg(QString::number(num))); +} + +/** + * @brief SettingsWidget::showWidget 显示此窗口 + */ +void SettingsWidget::showWidget() { + Qt::WindowFlags flags = this->windowFlags(); + flags |= Qt::WindowStaysOnTopHint; + this->setWindowFlags(flags); + flags &= ~Qt::WindowStaysOnTopHint; + this->setWindowFlags(flags); + m_timer->start(); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + XAtomHelper::getInstance()->setWindowMotifHint(winId(), m_hints); +#endif + this->show(); +} + +///** +// * @brief SettingsWidget::onBtnConfirmClicked 点击确认按钮的槽函数 +// */ +//void SettingsWidget::onBtnConfirmClicked() { +// Q_EMIT this->settingWidgetClosed(); +// m_timer->stop(); +// this->close(); +//} + +///** +// * @brief SettingsWidget::onBtnCancelClicked 点击取消按钮的槽函数 +// */ +//void SettingsWidget::onBtnCancelClicked() { +// Q_EMIT this->settingWidgetClosed(); +// m_timer->stop(); +// this->close(); +//} + +/** + * @brief SettingsWidget::onBtnAddClicked 点击添加黑名单按钮的槽函数 + */ +void SettingsWidget::onBtnAddClicked() { + QFileDialog * fileDialog = new QFileDialog(this); +// fileDialog->setFileMode(QFileDialog::Directory); //允许查看文件和文件夹,但只允许选择文件夹 + fileDialog->setFileMode(QFileDialog::DirectoryOnly); //只允许查看文件夹 +// fileDialog->setViewMode(QFileDialog::Detail); + fileDialog->setDirectory(QDir::homePath()); + fileDialog->setNameFilter(tr("Directories")); + fileDialog->setWindowTitle(tr("select blocked folder")); + fileDialog->setLabelText(QFileDialog::Accept, tr("Select")); + fileDialog->setLabelText(QFileDialog::LookIn, tr("Position: ")); + fileDialog->setLabelText(QFileDialog::FileName, tr("FileName: ")); + fileDialog->setLabelText(QFileDialog::FileType, tr("FileType: ")); + fileDialog->setLabelText(QFileDialog::Reject, tr("Cancel")); + if(fileDialog->exec() != QDialog::Accepted) { + fileDialog->deleteLater(); + return; + } + QString selectedDir = 0; + int returnCode; + selectedDir = fileDialog->selectedFiles().first(); + qDebug() << "Selected a folder in onBtnAddClicked(): " << selectedDir << ". ->settings-widget.cpp #238"; + if(GlobalSettings::getInstance()->setBlockDirs(selectedDir, returnCode)) { + setupBlackList(GlobalSettings::getInstance()->getBlockDirs()); + qDebug() << "Add block dir in onBtnAddClicked() successed. ->settings-widget.cpp #238"; + } else { + showWarningDialog(returnCode); + } +} + +/** + * @brief SettingsWidget::paintEvent 绘制弹窗阴影 + * @param event + */ +void SettingsWidget::paintEvent(QPaintEvent *event) { + Q_UNUSED(event) + + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + QPainterPath rectPath; + rectPath.addRect(this->rect()); + + // 绘制一个背景 + p.save(); + p.fillPath(rectPath, palette().color(QPalette::Base)); + p.restore(); +} + +/** + * @brief SettingsWidget::resize 重新计算窗口应有大小 + */ +void SettingsWidget::resize() { +// if (m_blockdirs <= 1) { +// this->setFixedSize(528, 455); +// } else if (m_blockdirs <= 3) { +// this->setFixedSize(528, 425 + 30 * m_blockdirs); +// } else { +// this->setFixedSize(528, 515); +// } + if(m_blockdirs <= 4) { + m_dirListArea->setFixedHeight(32 * m_blockdirs + 4); + m_dirListWidget->setFixedHeight(32 * m_blockdirs); + } else { + m_dirListWidget->setFixedHeight(32 * m_blockdirs); + m_dirListArea->setFixedHeight(32 * 4 + 4); + } + this->setFixedSize(528, 410 + m_dirListArea->height()); +} + +/** + * @brief SettingsWidget::showWarningDialog 显示警告弹窗 + * @param errorCode 错误码 + */ +void SettingsWidget::showWarningDialog(const int & errorCode) { + qWarning() << "Add block dir in onBtnAddClicked() failed. Code: " << errorCode << " ->settings-widget.cpp #238"; + QString errorMessage; + switch(errorCode) { + case 1: { + errorMessage = tr("Choosen path is Empty!"); + break; + } + case 2: { + errorMessage = tr("Choosen path is not in \"home\"!"); + break; + } + case 3: { + errorMessage = tr("Its' parent folder has been blocked!"); + break; + } + default: { + errorMessage = tr("Set blocked folder failed!"); + break; + } + } + QMessageBox message(QMessageBox::Warning, tr("Search"), errorMessage); + message.addButton(tr("OK"), QMessageBox::AcceptRole); + message.exec(); +} + + +FolderListItem::FolderListItem(QWidget *parent, const QString &path) : QWidget(parent) { + m_path = path; + initUi(); +} + +FolderListItem::~FolderListItem() { + +} + +/** + * @brief FolderListItem::initUi 构建ui + */ +void FolderListItem::initUi() { + m_layout = new QVBoxLayout(this); + m_layout->setSpacing(0); + m_layout->setContentsMargins(0, 0, 0, 0); + m_widget = new QWidget(this); + m_widget->setObjectName("mWidget"); + this->setFixedHeight(32); + m_layout->addWidget(m_widget); + m_widgetlayout = new QHBoxLayout(m_widget); + m_widgetlayout->setContentsMargins(8, 4, 8, 4); + m_widget->setLayout(m_widgetlayout); + + m_iconLabel = new QLabel(m_widget); + m_pathLabel = new QLabel(m_widget); + m_delLabel = new QLabel(m_widget); + m_iconLabel->setPixmap(QIcon::fromTheme("inode-directory").pixmap(QSize(16, 16))); + m_pathLabel->setText(m_path); + m_delLabel->setText(tr("Delete the folder out of blacklist")); + QPalette pal = palette(); + pal.setColor(QPalette::WindowText, QColor(55, 144, 250, 255)); + m_delLabel->setPalette(pal); + m_delLabel->setCursor(QCursor(Qt::PointingHandCursor)); + m_delLabel->installEventFilter(this); + m_delLabel->hide(); + m_widgetlayout->addWidget(m_iconLabel); + m_widgetlayout->addWidget(m_pathLabel); + m_widgetlayout->addStretch(); + m_widgetlayout->addWidget(m_delLabel); +} + +/** + * @brief FolderListItem::getPath 获取当前文件夹路径 + * @return + */ +QString FolderListItem::getPath() { + return m_path; +} + +/** + * @brief FolderListItem::enterEvent 鼠标移入事件 + * @param event + */ +void FolderListItem::enterEvent(QEvent *event) { + m_delLabel->show(); + m_widget->setStyleSheet("QWidget#mWidget{background: rgba(0,0,0,0.1);}"); + QWidget::enterEvent(event); +} + +/** + * @brief FolderListItem::leaveEvent 鼠标移出事件 + * @param event + */ +void FolderListItem::leaveEvent(QEvent *event) { + m_delLabel->hide(); + m_widget->setStyleSheet("QWidget#mWidget{background: transparent;}"); + QWidget::leaveEvent(event); +} + + +/** + * @brief FolderListItem::eventFilter 处理删除按钮点击事件 + * @param watched + * @param event + * @return + */ +bool FolderListItem::eventFilter(QObject *watched, QEvent *event) { + if(watched == m_delLabel) { + if(event->type() == QEvent::MouseButtonPress) { +// qDebug()<<"pressed!"; + Q_EMIT this->onDelBtnClicked(m_path); + } + } + return QObject::eventFilter(watched, event); +} diff --git a/frontend/control/settings-widget.h b/frontend/control/settings-widget.h new file mode 100644 index 0000000..2f4ef05 --- /dev/null +++ b/frontend/control/settings-widget.h @@ -0,0 +1,154 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#ifndef SETTINGSWIDGET_H +#define SETTINGSWIDGET_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) +#include "xatom-helper.h" +#endif + +namespace Zeeker { +class FolderListItem : public QWidget { + Q_OBJECT +public: + explicit FolderListItem(QWidget *parent = nullptr, const QString &path = 0); + ~FolderListItem(); + QString getPath(); + +protected: + virtual void enterEvent(QEvent *); + virtual void leaveEvent(QEvent *); + bool eventFilter(QObject *, QEvent *); + +private: + void initUi(); + + QString m_path; + + QVBoxLayout * m_layout = nullptr; + QWidget * m_widget = nullptr; + QHBoxLayout * m_widgetlayout = nullptr; + QLabel * m_iconLabel = nullptr; + QLabel * m_pathLabel = nullptr; + QLabel * m_delLabel = nullptr; +Q_SIGNALS: + void onDelBtnClicked(const QString&); +}; + +class SettingsWidget : public QWidget { + Q_OBJECT +public: + explicit SettingsWidget(QWidget *parent = nullptr); + ~SettingsWidget(); + + void setIndexState(bool); + void setIndexNum(int); + void showWidget(); + void resetWebEngine(); + +private: + void initUi(); + void setupBlackList(const QStringList &); + void clearLayout(QLayout *); + void refreshIndexState(); + void paintEvent(QPaintEvent *); + void resize(); + void showWarningDialog(const int&); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + MotifWmHints m_hints; +#endif + + //标题栏 + QVBoxLayout * m_mainLyt = nullptr; + QFrame * m_contentFrame = nullptr; + QVBoxLayout * m_contentLyt = nullptr; + QFrame * m_titleFrame = nullptr; + QHBoxLayout * m_titleLyt = nullptr; + QLabel * m_titleIcon = nullptr; + QLabel * m_titleLabel = nullptr; + QPushButton * m_closeBtn = nullptr; + + //设置 + QLabel * m_settingLabel = nullptr; + //文件索引 + QLabel * m_indexTitleLabel = nullptr; + QLabel * m_indexStateLabel = nullptr; + QLabel * m_indexNumLabel = nullptr; + //文件索引设置(黑名单) + QLabel * m_indexSettingLabel = nullptr; + QLabel * m_indexDescLabel = nullptr; + QFrame * m_indexBtnFrame = nullptr; + QHBoxLayout * m_indexBtnLyt = nullptr; + QPushButton * m_addDirBtn = nullptr; + QScrollArea * m_dirListArea = nullptr; + QWidget * m_dirListWidget = nullptr; + QVBoxLayout * m_dirListLyt = nullptr; + + //搜索引擎设置 + QLabel * m_searchEngineLabel = nullptr; + QLabel * m_engineDescLabel = nullptr; + QButtonGroup * m_engineBtnGroup = nullptr; + QFrame * m_radioBtnFrame = nullptr; + QHBoxLayout * m_radioBtnLyt = nullptr; + QRadioButton * m_baiduBtn = nullptr; + QLabel * m_baiduLabel = nullptr; + QRadioButton * m_sougouBtn = nullptr; + QLabel * m_sougouLabel = nullptr; + QRadioButton * m_360Btn = nullptr; + QLabel * m_360Label = nullptr; + + //取消与确认按钮 + QFrame * m_bottomBtnFrame = nullptr; + QHBoxLayout * m_bottomBtnLyt = nullptr; + QPushButton * m_cancelBtn = nullptr; + QPushButton * m_confirmBtn = nullptr; + + QTimer * m_timer; + + int m_blockdirs = 0; //黑名单文件夹数量 + +Q_SIGNALS: + void settingWidgetClosed(); + void webEngineChanged(const QString&); + +private Q_SLOTS: +// void onBtnConfirmClicked(); +// void onBtnCancelClicked(); + void onBtnAddClicked(); + void onBtnDelClicked(const QString&); + void setWebEngine(const QString&); +}; +} + +#endif // SETTINGSWIDGET_H diff --git a/frontend/control/stack-pages/home-page-section.cpp b/frontend/control/stack-pages/home-page-section.cpp new file mode 100644 index 0000000..1f1c1e7 --- /dev/null +++ b/frontend/control/stack-pages/home-page-section.cpp @@ -0,0 +1,271 @@ +#include "home-page-section.h" + +#define SQUARE_WIDTH 116 +#define SQUARE_HEIGHT 116 +#define SQUARE_ICON_SIZE 48 +#define SQUARE_NAME_SIZE 108 +#define BAR_WIDTH 300 +#define BAR_HEIGHT 48 +#define BAR_ICON_SIZE 24 +#define BAR_NAME_SIZE 250 +#define TITLE_HEIGHT 24 +#define MAIN_LAYOUT_SPACING 6 +#define ITEM_LAYOUT_SPACING 8 +#define ITEM_RADIUS 4 +#define ITEM_TRANS_NORMAL 0.06 +#define ITEM_TRANS_HOVER 0.15 +#define ITEM_TRANS_PRESS 0.06 +#define ITEM_MARGINS 0,0,0,0 +#define FLOW_LAYOUT_MARGIN 0 + +using namespace Zeeker; + +HomePageSection::HomePageSection(QString title, HomePageItemShape shape, QWidget *parent) : QWidget(parent) +{ + m_title = title; + m_shape = shape; + initUi(); +} + +HomePageSection::~HomePageSection() +{ + if (m_itemsLyt) { + delete m_itemsLyt; + m_itemsLyt = NULL; + } +} + +/** + * @brief HomePageSection::setItems 设置所有的项 + * @param itemList 显示的Item列表 + */ +void HomePageSection::setItems(QVector itemList) +{ + clear(); + if (m_shape == HomePageItemShape::Square) { + Q_FOREACH (HomePageItem item, itemList) { + m_itemsLyt->addWidget(createSquareItem(item)); + } + int line_num = ceil(double(itemList.length())/5); + this->setFixedHeight(TITLE_HEIGHT + MAIN_LAYOUT_SPACING + ITEM_LAYOUT_SPACING*(line_num-1) + SQUARE_HEIGHT*line_num); + } else { + Q_FOREACH (HomePageItem item, itemList) { + m_itemsLyt->addWidget(createBarItem(item)); + } + int line_num = ceil(double(itemList.length())/2); + this->setFixedHeight(TITLE_HEIGHT + MAIN_LAYOUT_SPACING + ITEM_LAYOUT_SPACING*(line_num-1) + BAR_HEIGHT*line_num); + } + m_length = itemList.length(); +} + +int HomePageSection::length() +{ + return m_length; +} + +///** +// * @brief HomePageSection::appendItem 添加一项 +// * @param item 要添加的item +// */ +//void HomePageSection::appendItem(HomePageItem item) +//{ +// if (m_shape == HomePageItemShape::Square) { +// m_itemsLyt->addWidget(createSquareItem(item)); +// } else { +// m_itemsLyt->addWidget(createBarItem(item)); +// } +// m_length++; +//} + +//NEW_TODO +///** +// * @brief HomePageSection::insertItem 插入一项 +// * @param index 插入位置 +// * @param item 要插入的item +// */ +//void HomePageSection::insertItem(const int &index, const HomePageSection::HomePageItem &item) +//{ + +//} + +///** +// * @brief HomePageSection::removeOne 删除一项 +// * @param key HomePageSection的key,用来寻找item +// */ +//void HomePageSection::removeOne(const QString &key) +//{ +// Q_FOREACH (ItemWidget * item, m_itemsLyt->findChildren()) { +// if (item->objectName() == key) { +// delete item; +// item = NULL; +// break; +// } +// } +//} + +/** + * @brief HomePageSection::clear 清空 + */ +void HomePageSection::clear() +{ + if(! m_itemsLyt) return; + QLayoutItem * item; + while((item = m_itemsLyt->takeAt(0)) != 0) { + if(item->widget()) { + item->widget()->setParent(NULL); //防止删除后窗口看上去没有消失 + } + delete item; + } + item = NULL; + this->setFixedHeight(0); + m_length = 0; +} + +/** + * @brief HomePageSection::initUi 初始化固有控件 + */ +void HomePageSection::initUi() +{ + m_mainLyt = new QVBoxLayout(this); + m_mainLyt->setContentsMargins(ITEM_MARGINS); + this->setLayout(m_mainLyt); + m_titleLabel = new QLabel(this); + m_titleLabel->setFixedHeight(TITLE_HEIGHT); + m_titleLabel->setText(m_title); + m_itemWidget = new QWidget(this); + m_mainLyt->setSpacing(MAIN_LAYOUT_SPACING); + m_itemsLyt = new FlowLayout(m_itemWidget, FLOW_LAYOUT_MARGIN, ITEM_LAYOUT_SPACING, ITEM_LAYOUT_SPACING); + m_itemWidget->setLayout(m_itemsLyt); + m_mainLyt->addWidget(m_titleLabel); + m_mainLyt->addWidget(m_itemWidget); +} + +///** +// * @brief HomePageSection::resize 重新计算此窗口size +// */ +//void HomePageSection::resize() +//{ + +//} + +/** + * @brief HomePageSection::createSquareItem 创建一个方形小窗格 + * @param item 数据 + * @return + */ +ItemWidget *HomePageSection::createSquareItem(const HomePageItem &item) +{ + ItemWidget * square_item = new ItemWidget(m_itemWidget, HomePageItemShape::Square, item); + square_item->setObjectName(item.key); + connect(square_item, &ItemWidget::clicked, this, &HomePageSection::requestAction); + return square_item; +} + +/** + * @brief HomePageSection::createBarItem 创建一个条状小窗格 + * @param item 数据 + * @return + */ +ItemWidget *HomePageSection::createBarItem(const HomePageItem &item) +{ + ItemWidget * bar_item = new ItemWidget(m_itemWidget, HomePageItemShape::Bar, item); + bar_item->setObjectName(item.key); + connect(bar_item, &ItemWidget::clicked, this, &HomePageSection::requestAction); + return bar_item; +} + +/** + * @brief ItemWidget::ItemWidget 显示在flowlayout中的每个小窗格 + * @param parent 父窗口 + * @param shape 形状 + * @param item 数据 + */ +ItemWidget::ItemWidget(QWidget *parent, const HomePageItemShape &shape, const HomePageItem &item) : QWidget(parent) { + this->initUi(shape, item.name, item.icon); + m_item = item; + this->installEventFilter(this); + this->setToolTip(item.name); + m_transparency = ITEM_TRANS_NORMAL; + connect(qApp, &QApplication::paletteChanged, this, [ = ]() { + if(m_namelabel) { + QString name = this->toolTip(); + if(shape == HomePageItemShape::Square) { + m_namelabel->setText(m_namelabel->fontMetrics().elidedText(name, Qt::ElideRight, SQUARE_NAME_SIZE)); + } else { + m_namelabel->setText(m_namelabel->fontMetrics().elidedText(name, Qt::ElideRight, BAR_NAME_SIZE)); + } + } + }); +} + +/** + * @brief HomePageItem::setupUi 根据不同的形状创建item + * @param shape 形状 + * @param name 名称 + * @param icon 图标 + */ +void ItemWidget::initUi(HomePageItemShape shape, const QString& name, const QIcon& icon) { + m_iconlabel = new QLabel(this); + m_namelabel = new QLabel(this); + m_iconlabel->setAlignment(Qt::AlignCenter); + m_namelabel->setAlignment(Qt::AlignCenter); + if (shape == HomePageItemShape::Square) { + m_vlayout = new QVBoxLayout(this); + this->setLayout(m_vlayout); + m_iconlabel->setPixmap(icon.pixmap(icon.actualSize(QSize(SQUARE_ICON_SIZE, SQUARE_ICON_SIZE)))); + m_namelabel->setText(m_namelabel->fontMetrics().elidedText(name, Qt::ElideRight, SQUARE_NAME_SIZE)); + m_vlayout->addWidget(m_iconlabel); + m_vlayout->addWidget(m_namelabel); + this->setFixedSize(SQUARE_WIDTH, SQUARE_HEIGHT); + } else { + m_hlayout = new QHBoxLayout(this); + this->setLayout(m_hlayout); + m_iconlabel->setPixmap(icon.pixmap(icon.actualSize(QSize(BAR_ICON_SIZE, BAR_ICON_SIZE)))); + m_namelabel->setText(m_namelabel->fontMetrics().elidedText(name, Qt::ElideRight, BAR_NAME_SIZE)); + m_hlayout->addWidget(m_iconlabel); + m_hlayout->addWidget(m_namelabel); + m_hlayout->addStretch(); + this->setFixedSize(BAR_WIDTH, BAR_HEIGHT); + } +} + +bool ItemWidget::eventFilter(QObject *watched, QEvent *event) { + if(watched == this) { + if(event->type() == QEvent::MouseButtonPress) { + m_transparency = ITEM_TRANS_PRESS; + this->update(); + return true; + } else if(event->type() == QEvent::MouseButtonRelease) { + Q_EMIT this->clicked(m_item.key, m_item.action, m_item.pluginId); + m_transparency = ITEM_TRANS_NORMAL; + this->update(); + return true; + } else if(event->type() == QEvent::Enter) { + m_transparency = ITEM_TRANS_HOVER; + this->update(); + return true; + } else if(event->type() == QEvent::Leave) { + m_transparency = ITEM_TRANS_NORMAL; + this->update(); + return true; + } + } + + return QObject::eventFilter(watched, event); +} + +void ItemWidget::paintEvent(QPaintEvent *event) { + Q_UNUSED(event) + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + QRect rect = this->rect(); + p.setRenderHint(QPainter::Antialiasing); // 反锯齿; + p.setBrush(opt.palette.color(QPalette::Text)); + p.setOpacity(m_transparency); + p.setPen(Qt::NoPen); + p.drawRoundedRect(rect, ITEM_RADIUS, ITEM_RADIUS); + return QWidget::paintEvent(event); +} + diff --git a/frontend/control/stack-pages/home-page-section.h b/frontend/control/stack-pages/home-page-section.h new file mode 100644 index 0000000..241ba1f --- /dev/null +++ b/frontend/control/stack-pages/home-page-section.h @@ -0,0 +1,90 @@ +#ifndef HOMEPAGESECTION_H +#define HOMEPAGESECTION_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "flow-layout/flow-layout.h" + +namespace Zeeker { + +enum class HomePageItemShape { + Square = 0, + Bar +}; +struct HomePageItem +{ + QIcon icon; + QString name; + QString key; + QString action; + QString pluginId; +}; + +class ItemWidget : public QWidget { + Q_OBJECT +public: + explicit ItemWidget(QWidget *, const HomePageItemShape &, const HomePageItem &); + ~ItemWidget() = default; + +protected: + bool eventFilter(QObject *, QEvent *); + void paintEvent(QPaintEvent *); + +private: + void initUi(HomePageItemShape, const QString&, const QIcon&); + + QHBoxLayout * m_hlayout = nullptr; + QVBoxLayout * m_vlayout = nullptr; + QLabel * m_iconlabel = nullptr; + QLabel * m_namelabel = nullptr; + double m_transparency = 0; + HomePageItem m_item; + +Q_SIGNALS: + void clicked(const QString &key, const QString &action, const QString &pluginId); +}; + +class HomePageSection : public QWidget +{ + Q_OBJECT +public: + explicit HomePageSection(QString title, HomePageItemShape shape = HomePageItemShape::Square, QWidget *parent = nullptr); + ~HomePageSection(); + + void setItems(QVector itemList); + int length(); +// void appendItem(HomePageItem item); +// void insertItem(const int &index, const HomePageItem &item); +// void removeOne(const QString &key); + void clear(); + +Q_SIGNALS: + //emit on HomePageItem clicked + void requestAction(const QString &key, const QString &action, const QString &pluginId); + +private: + QString m_title; + QVector m_items; + void initUi(); +// void resize(); + QVBoxLayout * m_mainLyt = nullptr; + QLabel * m_titleLabel = nullptr; + QWidget *m_itemWidget = nullptr; + FlowLayout * m_itemsLyt = nullptr; + HomePageItemShape m_shape; + int m_length; + + ItemWidget* createSquareItem(const HomePageItem &item); + ItemWidget* createBarItem(const HomePageItem &item); +}; +} + +#endif // HOMEPAGESECTION_H diff --git a/frontend/control/stack-pages/home-page.cpp b/frontend/control/stack-pages/home-page.cpp new file mode 100644 index 0000000..218e3ce --- /dev/null +++ b/frontend/control/stack-pages/home-page.cpp @@ -0,0 +1,131 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#include "home-page.h" +using namespace Zeeker; + +#define BACKGROUND_COLOR QColor(0, 0, 0, 0) +#define MAIN_SPACING 16 +#define MAIN_MARGINS 0,0,0,0 + +HomePage::HomePage(QWidget *parent) : QScrollArea(parent) +{ + initUi(); + registerSections(); +} + +void HomePage::initUi() +{ + QPalette pal = palette(); + pal.setColor(QPalette::Base, BACKGROUND_COLOR); + pal.setColor(QPalette::Window, BACKGROUND_COLOR); //使用此palette的窗口背景将为透明 + m_widget = new QWidget(this); + this->setWidget(m_widget); + m_mainLyt = new QVBoxLayout(m_widget); + m_mainLyt->setSpacing(MAIN_SPACING); + m_mainLyt->setContentsMargins(MAIN_MARGINS); + m_widget->setLayout(m_mainLyt); + m_widget->setFixedWidth(this->width()); + this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + this->setPalette(pal); + this->setFrameShape(QFrame::Shape::NoFrame); +} + +/** + * @brief HomePage::appendSection 向homepgae添加一个版块 + * @param section + */ +void HomePage::appendSection(HomePageSection *section) +{ + section->setFixedWidth(this->width()); + m_mainLyt->addWidget(section); + if (m_widget->height()) { + m_widget->setFixedHeight(m_widget->height() + section->height() + MAIN_SPACING); + } else { + m_widget->setFixedHeight(m_widget->height() + section->height()); + } +} + +//以下为homepage各版块的信息获取的回调 + +//获取快速打开应用的列表 +QVector get_quickly_cb() +{ + QVector quickly_list; + QStringList quicklyOpenList; + quicklyOpenList << "/usr/share/applications/ksc-defender.desktop" + << "/usr/share/applications/ukui-notebook.desktop" + << "/usr/share/applications/eom.desktop" + << "/usr/share/applications/pluma.desktop" + << "/usr/share/applications/claws-mail.desktop" ; + Q_FOREACH (QString path, quicklyOpenList) { + if (QString::compare(FileUtils::getAppName(path), "Unknown App") == 0) + continue; + HomePageItem item; + item.icon = FileUtils::getAppIcon(path); + item.name = FileUtils::getAppName(path); + item.key = path; + item.action = "open"; + item.pluginId = "applications"; + quickly_list.append(item); + } + return quickly_list; +} + +//NEW_TODO 需要读写配置文件 +//获取最近打开的列表 +QVector get_recently_cb() +{ +// QVector recently_list; +// return recently_list; + return get_quickly_cb(); +} + +//NEW_TODO 需要读写配置文件 +//获取常用应用的列表 +QVector get_commonly_cb() +{ +// QVector commonly_list; +// return commonly_list; + return get_quickly_cb(); +} + +/** + * @brief HomePage::registerSections 在此注册所有的版块 + */ +void HomePage::registerSections() +{ + //快速打开 + HomePageSection *quickly_section = new HomePageSection(tr("Open Quickly"), HomePageItemShape::Square, m_widget); + quickly_section->setItems(get_quickly_cb()); + if (quickly_section->length()) + this->appendSection(quickly_section); + //最近打开 + HomePageSection *recently_section = new HomePageSection(tr("Recently Opened"), HomePageItemShape::Bar, m_widget); + recently_section->setItems(get_recently_cb()); + if (recently_section->length()) + this->appendSection(recently_section); + //常用应用 + HomePageSection *commonly_section = new HomePageSection(tr("Commonly Used"), HomePageItemShape::Square, m_widget); + commonly_section->setItems(get_commonly_cb()); + if (commonly_section->length()) + this->appendSection(commonly_section); +} diff --git a/frontend/control/stack-pages/home-page.h b/frontend/control/stack-pages/home-page.h new file mode 100644 index 0000000..22d8ff5 --- /dev/null +++ b/frontend/control/stack-pages/home-page.h @@ -0,0 +1,48 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#ifndef HOMEPAGE_H +#define HOMEPAGE_H + +#include +#include "home-page-section.h" +#include "file-utils.h" + +namespace Zeeker { +class HomePage : public QScrollArea +{ + Q_OBJECT +public: + explicit HomePage(QWidget *parent = nullptr); + ~HomePage() = default; + +private: + void initUi(); + void appendSection(HomePageSection *); + QWidget * m_widget = nullptr; + QVBoxLayout * m_mainLyt = nullptr; + + void registerSections(); +Q_SIGNALS: + +}; +} + +#endif // HOMEPAGE_H diff --git a/frontend/control/stack-pages/search-page-section.cpp b/frontend/control/stack-pages/search-page-section.cpp new file mode 100644 index 0000000..ca0cb83 --- /dev/null +++ b/frontend/control/stack-pages/search-page-section.cpp @@ -0,0 +1,241 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#include "search-page-section.h" +#include +using namespace Zeeker; + +#define RESULT_LAYOUT_MARGINS 0,0,0,0 +#define RESULT_BACKGROUND_COLOR QColor(0, 0, 0, 0) +#define DETAIL_BACKGROUND_COLOR QColor(0, 0, 0, 0) +#define DETAIL_WIDGET_TRANSPARENT 0.04 +#define DETAIL_WIDGET_BORDER_RADIUS 4 +#define DETAIL_WIDGET_MARGINS 8,40,40,8 +#define DETAIL_FRAME_MARGINS 8,0,8,0 +#define DETAIL_ICON_HEIGHT 120 +#define NAME_LABEL_WIDTH 280 +#define ICON_SIZE QSize(96, 96) +#define LINE_STYLE "QFrame{background: rgba(0,0,0,0.2);}" + +ResultArea::ResultArea(QWidget *parent) : QScrollArea(parent) +{ + qRegisterMetaType("SearchPluginIface::ResultInfo"); + initUi(); +} + +void ResultArea::appendWidet(ResultWidget *widget) +{ + //NEW_TODO + m_mainLyt->addWidget(widget); + setupConnectionsForWidget(widget); + m_widget->setFixedHeight(m_widget->height() + widget->height()); +} + +/** + * @brief ResultArea::setVisibleList 设置哪些插件可见,默认全部可见 + * @param list + */ +void ResultArea::setVisibleList(const QStringList &list) +{ + Q_FOREACH (auto widget, m_widget_list) { + if (list.contains(widget->pluginId())) { + widget->setEnabled(true); + } else { + widget->setEnabled(false); + } + } +} + +void ResultArea::initUi() +{ + QPalette pal = palette(); + pal.setColor(QPalette::Base, RESULT_BACKGROUND_COLOR); + pal.setColor(QPalette::Window, RESULT_BACKGROUND_COLOR); + this->setFrameShape(QFrame::Shape::NoFrame); + this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + this->setPalette(pal); + this->setWidgetResizable(true); + this->setFrameShape(QFrame::Shape::NoFrame); + m_widget = new QWidget(this); + this->setWidget(m_widget); + m_mainLyt = new QVBoxLayout(m_widget); + m_widget->setLayout(m_mainLyt); + m_mainLyt->setContentsMargins(RESULT_LAYOUT_MARGINS); +} + +void ResultArea::setupConnectionsForWidget(ResultWidget *widget) +{ + connect(this, &ResultArea::startSearch, widget, &ResultWidget::startSearch); + connect(this, &ResultArea::stopSearch, widget, &ResultWidget::stopSearch); +} + +DetailArea::DetailArea(QWidget *parent) : QScrollArea(parent) +{ + initUi(); + connect(this, &DetailArea::setWidgetInfo, m_detailWidget, &DetailWidget::setWidgetInfo); +} + +void DetailArea::initUi() +{ + QPalette pal = palette(); + pal.setColor(QPalette::Base, DETAIL_BACKGROUND_COLOR); + pal.setColor(QPalette::Window, DETAIL_BACKGROUND_COLOR); + this->setPalette(pal); + this->setFrameShape(QFrame::Shape::NoFrame); + this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + this->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + this->setWidgetResizable(true); + m_detailWidget = new DetailWidget(this); + this->setWidget(m_detailWidget); +} + +DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) +{ + initUi(); +} + +QString escapeHtml(const QString & str) { + QString temp = str; + temp.replace("<", "<"); + temp.replace(">", ">"); + return temp; +} + +void DetailWidget::setWidgetInfo(const QString &plugin_name, const SearchPluginIface::ResultInfo &info) +{ + m_iconLabel->setPixmap(info.icon.pixmap(info.icon.actualSize(ICON_SIZE))); + m_iconLabel->show(); + QFontMetrics fontMetrics = m_nameLabel->fontMetrics(); + QString name = fontMetrics.elidedText(info.name, Qt::ElideRight, NAME_LABEL_WIDTH - 8); + m_nameLabel->setText(QString("

%1

").arg(escapeHtml(name))); + m_nameLabel->setToolTip(info.name); + m_pluginLabel->setText(plugin_name); + m_nameFrame->show(); + m_line_1->show(); + if (info.description.length() > 0) { + //NEW_TODO + clearLayout(m_descFrameLyt); + Q_FOREACH (SearchPluginIface::DescriptionInfo desc, info.description) { + QLabel * descLabel = new QLabel(m_descFrame); + QString show_desc = desc.key + ": " + desc.value; + descLabel->setText(show_desc); + m_descFrameLyt->addWidget(descLabel); + } + m_line_2->show(); + } + clearLayout(m_actionFrameLyt); + Q_FOREACH (auto action, info.actionMap) { + //NEW_TODO + QLabel * actionLabel = new QLabel(m_actionFrame); + actionLabel->setText(action); + m_actionFrameLyt->addWidget(actionLabel); + } + m_actionFrame->show(); +} + +void DetailWidget::clear() +{ + m_iconLabel->hide(); + m_nameFrame->hide(); + m_line_1->hide(); + m_descFrame->hide(); + m_line_2->hide(); + m_actionFrame->hide(); +} + +void DetailWidget::initUi() +{ + m_mainLyt = new QVBoxLayout(this); + this->setLayout(m_mainLyt); + m_mainLyt->setContentsMargins(DETAIL_WIDGET_MARGINS); + m_mainLyt->setAlignment(Qt::AlignHCenter); + + m_iconLabel = new QLabel(this); + m_iconLabel->setFixedHeight(DETAIL_ICON_HEIGHT); + m_iconLabel->setAlignment(Qt::AlignCenter); + + m_nameFrame = new QFrame(this); + m_nameFrameLyt = new QHBoxLayout(m_nameFrame); + m_nameFrame->setLayout(m_nameFrameLyt); + m_nameFrameLyt->setContentsMargins(DETAIL_FRAME_MARGINS); + m_nameLabel = new QLabel(m_nameFrame); + m_nameLabel->setMaximumWidth(NAME_LABEL_WIDTH); + m_pluginLabel = new QLabel(m_nameFrame); + m_nameFrameLyt->addWidget(m_nameLabel); + m_nameFrameLyt->addStretch(); + m_nameFrameLyt->addWidget(m_pluginLabel); + + m_line_1 = new QFrame(this); + m_line_1->setFixedHeight(1); + m_line_1->setLineWidth(0); + m_line_1->setStyleSheet(LINE_STYLE); + m_line_2 = new QFrame(this); + m_line_2->setFixedHeight(1); + m_line_2->setLineWidth(0); + m_line_2->setStyleSheet(LINE_STYLE); + + m_descFrame = new QFrame(this); + m_descFrameLyt = new QVBoxLayout(m_descFrame); + m_descFrame->setLayout(m_descFrameLyt); + m_descFrameLyt->setContentsMargins(DETAIL_FRAME_MARGINS); + + m_actionFrame = new QFrame(this); + m_actionFrameLyt = new QVBoxLayout(m_actionFrame); + m_actionFrame->setLayout(m_actionFrameLyt); + m_actionFrameLyt->setContentsMargins(DETAIL_FRAME_MARGINS); + + m_mainLyt->addWidget(m_iconLabel); + m_mainLyt->addWidget(m_nameFrame); + m_mainLyt->addWidget(m_line_1); + m_mainLyt->addWidget(m_descFrame); + m_mainLyt->addWidget(m_line_2); + m_mainLyt->addWidget(m_actionFrame); + m_mainLyt->addStretch(); +} + +void DetailWidget::paintEvent(QPaintEvent * event) +{ + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + + QRect rect = this->rect(); + p.setRenderHint(QPainter::Antialiasing); // 反锯齿; + p.setBrush(opt.palette.color(QPalette::Text)); + p.setOpacity(DETAIL_WIDGET_TRANSPARENT); + p.setPen(Qt::NoPen); + p.drawRoundedRect(rect, DETAIL_WIDGET_BORDER_RADIUS, DETAIL_WIDGET_BORDER_RADIUS); + return QWidget::paintEvent(event); +} + +void DetailWidget::clearLayout(QLayout *layout) +{ + if(! layout) return; + QLayoutItem * child; + while((child = layout->takeAt(0)) != 0) { + if(child->widget()) { + child->widget()->setParent(NULL); + } + delete child; + } + child = NULL; +} diff --git a/frontend/control/stack-pages/search-page-section.h b/frontend/control/stack-pages/search-page-section.h new file mode 100644 index 0000000..342ab1b --- /dev/null +++ b/frontend/control/stack-pages/search-page-section.h @@ -0,0 +1,96 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#ifndef SEARCHPAGESECTION_H +#define SEARCHPAGESECTION_H +#include +#include +#include +#include +#include "result-view.h" +#include "plugininterface/search-plugin-iface.h" + +namespace Zeeker { +class ResultArea : public QScrollArea +{ + Q_OBJECT +public: + ResultArea(QWidget *parent = nullptr); + ~ResultArea() = default; + void appendWidet(ResultWidget *); + void setVisibleList(const QStringList &); +private: + void initUi(); + void setupConnectionsForWidget(ResultWidget *); + QWidget * m_widget = nullptr; + QVBoxLayout * m_mainLyt = nullptr; + QList m_widget_list; + +Q_SIGNALS: + void startSearch(const QString &); + void stopSearch(); + +}; + +class DetailWidget : public QWidget +{ + Q_OBJECT +public: + DetailWidget(QWidget *parent = nullptr); + ~DetailWidget() = default; + void clear(); + +public Q_SLOTS: + void setWidgetInfo(const QString&, const SearchPluginIface::ResultInfo&); + +private: + void initUi(); + void paintEvent(QPaintEvent *); + void clearLayout(QLayout *); + QVBoxLayout * m_mainLyt = nullptr; + QLabel * m_iconLabel = nullptr; + QFrame * m_nameFrame = nullptr; + QHBoxLayout * m_nameFrameLyt = nullptr; + QLabel * m_nameLabel = nullptr; + QLabel * m_pluginLabel = nullptr; + QFrame * m_line_1 = nullptr; + QFrame * m_descFrame = nullptr; + QVBoxLayout * m_descFrameLyt = nullptr; + QFrame * m_line_2 = nullptr; + QFrame * m_actionFrame = nullptr; + QVBoxLayout * m_actionFrameLyt = nullptr; +}; + +class DetailArea : public QScrollArea +{ + Q_OBJECT +public: + DetailArea(QWidget *parent = nullptr); + ~DetailArea() = default; +private: + void initUi(); + DetailWidget * m_detailWidget = nullptr; + +Q_SIGNALS: + void setWidgetInfo(const QString&, const SearchPluginIface::ResultInfo&); +}; +} + +#endif // SEARCHPAGESECTION_H diff --git a/frontend/control/stack-pages/search-page.cpp b/frontend/control/stack-pages/search-page.cpp new file mode 100644 index 0000000..3c3938e --- /dev/null +++ b/frontend/control/stack-pages/search-page.cpp @@ -0,0 +1,79 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#include "search-page.h" +using namespace Zeeker; + +#define RESULT_WIDTH 240 +#define DETAIL_WIDTH 400 + +SearchPage::SearchPage(QWidget *parent) : QWidget(parent) +{ + initUi(); + initConnections(); +} + +void SearchPage::setSize(const int&width, const int&height) +{ + m_splitter->setFixedSize(width, height); +} + +void SearchPage::setPlugins(const QStringList &plugins_id) +{ + Q_FOREACH (QString plugin_id, plugins_id) { + ResultWidget * widget = new ResultWidget(plugin_id, m_resultArea); + m_resultArea->appendWidet(widget); + setupConnectionsForWidget(widget); + } +} + +void SearchPage::appendPlugin(const QString &plugin_id) +{ + ResultWidget * widget = new ResultWidget(plugin_id, m_resultArea); + m_resultArea->appendWidet(widget); + setupConnectionsForWidget(widget); +} + +void SearchPage::initUi() +{ + m_splitter = new QSplitter(this); + m_resultArea = new ResultArea(m_splitter); + m_detailArea = new DetailArea(m_splitter); + m_splitter->addWidget(m_resultArea); + m_splitter->addWidget(m_detailArea); + m_splitter->setOpaqueResize(false); + QList size_list; + size_list<setSizes(size_list); + m_splitter->handle(1)->setEnabled(false); //暂时禁止拖动分隔条 +} + +void SearchPage::initConnections() +{ + connect(this, &SearchPage::startSearch, m_resultArea, &ResultArea::startSearch); + connect(this, &SearchPage::stopSearch, m_resultArea, &ResultArea::stopSearch); +} + +void SearchPage::setupConnectionsForWidget(ResultWidget *widget) +{ + connect(widget, &ResultWidget::currentRowChanged, m_detailArea, &DetailArea::setWidgetInfo); + connect(widget, &ResultWidget::currentRowChanged, this, &SearchPage::currentRowChanged); + connect(this, &SearchPage::currentRowChanged, widget, &ResultWidget::clearSelectedRow); +} diff --git a/frontend/control/stack-pages/search-page.h b/frontend/control/stack-pages/search-page.h new file mode 100644 index 0000000..2740da5 --- /dev/null +++ b/frontend/control/stack-pages/search-page.h @@ -0,0 +1,53 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#ifndef SEARCHPAGE_H +#define SEARCHPAGE_H + +#include +#include "search-page-section.h" + +namespace Zeeker { +class SearchPage : public QWidget +{ + Q_OBJECT +public: + explicit SearchPage(QWidget *parent = nullptr); + ~SearchPage() = default; + void setSize(const int&, const int&); + void setPlugins(const QStringList &plugins_id); + void appendPlugin(const QString &plugin_id); + +private: + void initUi(); + void initConnections(); + void setupConnectionsForWidget(ResultWidget *); + QSplitter * m_splitter = nullptr; + ResultArea * m_resultArea = nullptr; + DetailArea * m_detailArea = nullptr; + +Q_SIGNALS: + void startSearch(const QString &); + void stopSearch(); + void currentRowChanged(const QString &, const SearchPluginIface::ResultInfo&); +}; +} + +#endif // SEARCHPAGE_H diff --git a/frontend/control/stack-pages/stack-pages.pri b/frontend/control/stack-pages/stack-pages.pri new file mode 100644 index 0000000..924e1a9 --- /dev/null +++ b/frontend/control/stack-pages/stack-pages.pri @@ -0,0 +1,13 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/home-page-section.h \ + $$PWD/home-page.h \ + $$PWD/search-page-section.h \ + $$PWD/search-page.h \ + +SOURCES += \ + $$PWD/home-page-section.cpp \ + $$PWD/home-page.cpp \ + $$PWD/search-page-section.cpp \ + $$PWD/search-page.cpp \ diff --git a/frontend/control/stacked-widget.cpp b/frontend/control/stacked-widget.cpp new file mode 100644 index 0000000..2bc4040 --- /dev/null +++ b/frontend/control/stacked-widget.cpp @@ -0,0 +1,91 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#include "stacked-widget.h" + +using namespace Zeeker; +Zeeker::StackedWidget::StackedWidget(QWidget *) +{ + this->initWidgets(); + this->initConnections(); +} + +Zeeker::StackedWidget::~StackedWidget() +{ + if (m_homePage) { + delete m_homePage; + m_homePage = NULL; + } + if (m_searchPage) { + delete m_searchPage; + m_searchPage = NULL; + } +} + +/** + * @brief StackedWidget::setPage 设置StackedWidget所显示页面 + * @param type 页面类型 + */ +void StackedWidget::setPage(const int & type) +{ + this->setCurrentIndex(type); +} + +int StackedWidget::currentPage() +{ + return this->currentIndex(); +} + +/** + * @brief StackedWidget::setPlugins 设置所有插件 + */ +void StackedWidget::setPlugins(const QStringList &plugins) +{ + m_searchPage->setPlugins(plugins); +} + +/** + * @brief StackedWidget::addPlugin 添加一个插件 + */ +void StackedWidget::appendPlugin(const QString &plugin) +{ + m_searchPage->appendPlugin(plugin); +} + +/** + * @brief StackedWidget::initWidgets 初始化向stackedwidget添加窗口 + */ +void StackedWidget::initWidgets() +{ + //NEW_TODO + m_homePage = new HomePage; + this->insertWidget(int(StackedPage::HomePage), m_homePage); + this->setPage(int(StackedPage::HomePage)); + + m_searchPage = new SearchPage; + m_searchPage->setSize(this->width(), this->height()); + this->insertWidget(int(StackedPage::SearchPage), m_searchPage); +} + +void StackedWidget::initConnections() +{ + connect(this, &StackedWidget::startSearch, m_searchPage, &SearchPage::startSearch); + connect(this, &StackedWidget::stopSearch, m_searchPage, &SearchPage::stopSearch); +} diff --git a/frontend/control/stacked-widget.h b/frontend/control/stacked-widget.h new file mode 100644 index 0000000..4caf68d --- /dev/null +++ b/frontend/control/stacked-widget.h @@ -0,0 +1,62 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#ifndef STACKEDWIDGET_H +#define STACKEDWIDGET_H + +#include +#include +#include "input-box.h" +#include "home-page.h" +#include "search-page.h" + +namespace Zeeker { + +enum class StackedPage { + HomePage = 0, + SearchPage +}; + +class StackedWidget : public QStackedWidget { + Q_OBJECT +public: + StackedWidget(QWidget *); + ~StackedWidget(); + + void setPage(const int &); + int currentPage(); + void setPlugins(const QStringList&); + void appendPlugin(const QString&); + +Q_SIGNALS: + void startSearch(const QString &); + void stopSearch(); + +private: + void initWidgets(); + void initConnections(); + QString m_keyword; + HomePage * m_homePage = nullptr; + SearchPage * m_searchPage = nullptr; +// StackedPage m_current_page; +}; +} + +#endif // STACKEDWIDGET_H diff --git a/frontend/frontend.pro b/frontend/frontend.pro new file mode 100644 index 0000000..7922326 --- /dev/null +++ b/frontend/frontend.pro @@ -0,0 +1,83 @@ +QT += core gui dbus KWindowSystem xml x11extras + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +VERSION = 0.0.1 +TARGET = ukui-search +TEMPLATE = app + +PKGCONFIG += gio-2.0 glib-2.0 gio-unix-2.0 +CONFIG += c++11 link_pkgconfig no_keywords lrelease +LIBS += -lxapian -lgsettings-qt -lquazip5 -lX11 +LIBS += -lukui-log4qt +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +include(control/control.pri) +include(model/model.pri) +include(xatom/xatom.pri) +include(singleapplication/qt-single-application.pri) +include(view/view.pri) + +SOURCES += \ + main.cpp \ + mainwindow.cpp + + +HEADERS += \ + mainwindow.h \ + +# Default rules for deployment. + +target.path = /usr/bin +!isEmpty(target.path): INSTALLS += target + +data-menu.path = /usr/share/applications +data-menu.files += ../data/ukui-search-menu.desktop +data.path = /etc/xdg/autostart +data.files += ../data/ukui-search.desktop + +INSTALLS += data data-menu + +RESOURCES += \ + resource.qrc + +TRANSLATIONS += \ + ../translations/ukui-search/zh_CN.ts \ + ../translations/ukui-search/tr.ts \ + ../translations/ukui-search/bo.ts + +qm_files.path = /usr/share/ukui-search/translations/ +qm_files.files = $$OUT_PWD/.qm/*.qm + +schemes.path = /usr/share/glib-2.0/schemas/ +schemes.files += ../data/org.ukui.search.data.gschema.xml ../data/org.ukui.log4qt.ukui-search.gschema.xml + +INSTALLS += qm_files schemes + +LIBS += -L$$OUT_PWD/../libchinese-segmentation -lchinese-segmentation \ + -L$$OUT_PWD/../libsearch -lukui-search + +INCLUDEPATH += $$PWD/../libchinese-segmentation +DEPENDPATH += $$PWD/../libchinese-segmentation + +INCLUDEPATH += $$PWD/../libsearch +DEPENDPATH += $$PWD/../libsearch + +#DISTFILES += \ +# ../data/ukui-search-menu.desktop \ +# $$OUT_PWD/.qm/bo.qm \ +# $$OUT_PWD/.qm/tr.qm \ +# $$OUT_PWD/.qm/zh_CN.qm + +DISTFILES += \ + ../data/org.ukui.log4qt.ukui-search.gschema.xml \ + ../data/org.ukui.search.data.gschema.xml diff --git a/frontend/main.cpp b/frontend/main.cpp new file mode 100644 index 0000000..c703f26 --- /dev/null +++ b/frontend/main.cpp @@ -0,0 +1,295 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * Modified by: zhangpengfei + * Modified by: zhangzihao + * + */ + +#include "mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) +#include +#endif +#include +#include +#include "qt-single-application.h" +#include "qt-local-peer.h" +#include "libsearch.h" +#include "global-settings.h" + +using namespace Zeeker; + +void messageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + QByteArray localMsg = msg.toLocal8Bit(); + QByteArray currentTime = QTime::currentTime().toString().toLocal8Bit(); + + bool showDebug = true; +// QString logFilePath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/ukui-search.log"; +// QString logFilePath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.config/org.ukui/ukui-search/ukui-search.log"; + QString logFilePath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/.config/org.ukui/ukui-search.log"; + if (!QFile::exists(logFilePath)) { + showDebug = false; + } + FILE *log_file = nullptr; + + if (showDebug) { + log_file = fopen(logFilePath.toLocal8Bit().constData(), "a+"); + } + + const char *file = context.file ? context.file : ""; + const char *function = context.function ? context.function : ""; + 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); +} + +void centerToScreen(QWidget* widget) { + if(!widget) + return; + QDesktopWidget* m = QApplication::desktop(); + QRect desk_rect = m->screenGeometry(m->screenNumber(QCursor::pos())); + int desk_x = desk_rect.width(); + int desk_y = desk_rect.height(); + int x = widget->width(); + int y = widget->height(); + widget->move(desk_x / 2 - x / 2 + desk_rect.left(), desk_y / 2 - y / 2 + desk_rect.top()); +} +/* +void searchMethod(FileUtils::SearchMethod sm){ + qWarning() << "searchMethod start: " << static_cast(sm); + if (FileUtils::SearchMethod::INDEXSEARCH == sm || FileUtils::SearchMethod::DIRECTSEARCH == sm) { + FileUtils::searchMethod = sm; + } else { + printf("enum class error!!!\n"); + qWarning("enum class error!!!\n"); + } + if (FileUtils::SearchMethod::INDEXSEARCH == sm && 0 == FileUtils::_index_status) { + qWarning() << "start first index"; + FirstIndex fi("/home/zhangzihao/Desktop"); + fi.start(); + qWarning() << "start inotify index"; +// InotifyIndex ii("/home"); +// ii.start(); + InotifyIndex* ii = InotifyIndex::getInstance("/home"); + if (!ii->isRunning()) { + ii->start(); + } + qDebug()<<"Search method has been set to INDEXSEARCH"; + } + qWarning() << "searchMethod end: " << static_cast(FileUtils::searchMethod); +} +*/ +int main(int argc, char *argv[]) { +//v101日志模块 +//#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) +// //Init log module +// initUkuiLog4qt("ukui-search"); +//#endif + + // Determine whether the home directory has been created, and if not, keep waiting. + char *p_home = NULL; + + unsigned int i = 0; + while(p_home == NULL) { + ::sleep(1); + ++i; + p_home = getenv("HOME"); + if(i % 5 == 0) { + qWarning() << "I can't find home! I'm done here!!"; + printf("I can't find home! I'm done here!!"); + syslog(LOG_ERR, "I can't find home! I'm done here!!\n"); + } + } + p_home = NULL; + while(!QDir(QDir::homePath()).exists()) { + qWarning() << "Home is not exits!!"; + printf("Home is not exits!!"); + syslog(LOG_ERR, "Home is not exits!!\n"); + ::sleep(1); + } + + // Output log to file + qInstallMessageHandler(messageOutput); +//若使用v101日志模块,可以解放如下判断条件 +//#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0)) +// // Output log to file +// qInstallMessageHandler(messageOutput); +//#endif + + // Register meta type + qDebug() << "ukui-search main start"; + qRegisterMetaType>("QPair"); + qRegisterMetaType("Document"); + + // If qt version bigger than 5.12, enable high dpi scaling and use high dpi pixmaps? +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); +#endif +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#endif + + // Make sure only one ukui-search is running. + QtSingleApplication app("ukui-search", argc, argv); + app.setQuitOnLastWindowClosed(false); + + if(app.isRunning()) { + app.sendMessage(QApplication::arguments().length() > 1 ? QApplication::arguments().at(1) : app.applicationFilePath()); + qDebug() << QObject::tr("ukui-search is already running!"); + return EXIT_SUCCESS; + }/*else { + QCommandLineParser parser; + QCommandLineOption debugOption({"d", "debug"}, QObject::tr("Display debug information")); + QCommandLineOption showsearch({"s", "show"}, QObject::tr("show search widget")); + parser.addOptions({debugOption, showsearch}); + parser.process(app); + }*/ + /* + // Create a fifo at ~/.config/org.ukui/ukui-search, the fifo is used to control the order of child processes' running. + QDir fifoDir = QDir(QDir::homePath()+"/.config/org.ukui/ukui-search"); + if(!fifoDir.exists()) + qDebug()<<"create fifo path"<start(); +// QTime t2 = QTime::currentTime(); +// qDebug() << t1; +// qDebug() << t2; + /*-------------InotyifyRefact Test End-----------------*/ + + /*-------------content index Test Start---------------*/ +// QTime t3 = QTime::currentTime(); +// FileTypeFilter* ftf = new FileTypeFilter("/home"); +// ftf->Test(); +// QTime t4 = QTime::currentTime(); +// delete ftf; +// ftf = nullptr; +// qDebug() << t3; +// qDebug() << t4; + /*-------------content index Test End-----------------*/ + /*-------------文本搜索 Test start-----------------*/ +// FileSearcher *search = new FileSearcher(); +// search->onKeywordSearchContent("重要器官移植⑤白血病"); +// search->onKeywordSearchContent("g,e,x"); + /*-------------文本搜索 Test End-----------------*/ + + // Load translations + QTranslator translator; + try { + if(! translator.load("/usr/share/ukui-search/translations/" + QLocale::system().name())) throw - 1; + app.installTranslator(&translator); + } catch(...) { + qDebug() << "Load translations file" << QLocale() << "failed!"; + } + + QTranslator qt_translator; + try { + if(! qt_translator.load(":/res/qt-translations/qt_zh_CN.qm")) throw - 1; + app.installTranslator(&qt_translator); + } catch(...) { + qDebug() << "Load translations file" << QLocale() << "failed!"; + } + + //set main window to the center of screen + MainWindow *w = new MainWindow; + qApp->setWindowIcon(QIcon::fromTheme("kylin-search")); + centerToScreen(w); + + //请务必在connect之后初始化mainwindow的Gsettings,为了保证gsettings第一次读取到的配置值能成功应用 + w->initGsettings(); + + app.setActivationWindow(w); + + // Processing startup parameters + if(QString::compare(QString("-s"), QString(QLatin1String(argv[1]))) == 0) { + centerToScreen(w); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + XAtomHelper::getInstance()->setWindowMotifHint(w->winId(), w->m_hints); +#endif + w->show(); + } + + QObject::connect(&app, &QtSingleApplication::messageReceived, w, &MainWindow::bootOptionsFilter); + + // Start app search thread + AppMatch::getAppMatch()->start(); + + // NEW_TODO + // Set threads which in global thread pool expiry time in 5ms, some prolems here + QThreadPool::globalInstance()->setExpiryTimeout(5); + + // NEW_TODO + // First insdex start, the parameter us useless, should remove the parameter +// FirstIndex fi("/home/zhangzihao/Desktop"); +// fi.start(); + + // NEW_TODO + // Inotify index start, the parameter us useless, should remove the parameter +// InotifyIndex* ii = InotifyIndex::getInstance("/home"); +// ii->start(); + + return app.exec(); +} diff --git a/frontend/mainwindow.cpp b/frontend/mainwindow.cpp new file mode 100644 index 0000000..25c60a4 --- /dev/null +++ b/frontend/mainwindow.cpp @@ -0,0 +1,577 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * Modified by: zhangpengfei + * + */ + +#include "mainwindow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) +#include +#include "kwindowsystem.h" +#endif +#include "qt-single-application.h" +#include "global-settings.h" + +#define MAIN_MARGINS 16,8,16,16 +#define TITLE_MARGINS 0,0,0,0 +#define UKUI_SEARCH_SCHEMAS "org.ukui.search.settings" +#define SEARCH_METHOD_KEY "indexSearch" +#define WEB_ENGINE_KEY "webEngine" +#define WINDOW_WIDTH 640 +#define WINDOW_HEIGHT 590 +#define TITLE_HEIGHT 40 +#define WINDOW_ICON_SIZE 24 +#define SETTING_BTN_SIZE 30 +#define SEARCH_BAR_SIZE 44 +#define ASK_INDEX_TIME 5*1000 +#define RESEARCH_TIME 10*1000 + +using namespace Zeeker; +extern void qt_blurImage(QImage &blurImage, qreal radius, bool quality, int transposed); +/** + * @brief MainWindow 主界面 + * @param parent + * + * 慎用KWindowSystem::setShowingDesktop(!KWindowSystem::showingDesktop()); + * 可能造成窗口属性的混乱 + */ +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent) { + this->setAttribute(Qt::WA_TranslucentBackground, true); + this->setAutoFillBackground(false); + this->setFocusPolicy(Qt::StrongFocus); + this->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + this->setWindowTitle(tr("ukui-search")); + initUi(); + initTimer(); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) +// setProperty("useStyleWindowManager", false); //禁止拖动 + m_hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; + m_hints.functions = MWM_FUNC_ALL; + m_hints.decorations = MWM_DECOR_BORDER; + XAtomHelper::getInstance()->setWindowMotifHint(winId(), m_hints); + + QPainterPath path; + auto rect = this->rect(); + rect.adjust(1, 1, -1, -1); + path.addRect(rect); + setProperty("blurRegion", QRegion(path.toFillPolygon().toPolygon())); + KWindowEffects::enableBlurBehind(this->winId(), true, QRegion(path.toFillPolygon().toPolygon())); +#endif + + m_sys_tray_icon = new QSystemTrayIcon(this); + m_sys_tray_icon->setIcon(QIcon::fromTheme("system-search-symbolic")); + m_sys_tray_icon->setToolTip(tr("Global Search")); + m_sys_tray_icon->show(); + installEventFilter(this); + initConnections(); + + //NEW_TODO, register plugins +// SearchPluginManager::getInstance()->registerPlugin(\\); +// m_stackedWidget->setPlugins(SearchPluginManager::getInstance()->getPluginIds()); + m_stackedWidget->setPlugins(QStringList()<<"File"<<"Folder"); +} + +MainWindow::~MainWindow() { +#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0)) + if(m_settingsWidget) { + delete m_settingsWidget; + m_settingsWidget = NULL; + } +#endif + if(m_askDialog) { + delete m_askDialog; + m_askDialog = NULL; + } + if(m_askTimer) { + delete m_askTimer; + m_askTimer = NULL; + } + if(m_search_gsettings) { + delete m_search_gsettings; + m_search_gsettings = NULL; + } + if(m_searchWidget) { + delete m_searchWidget; + m_searchWidget = NULL; + } + if(m_searchLayout) { + delete m_searchLayout; + m_searchLayout = NULL; + } +} + +/** + * @brief initUi 初始化主界面主要ui控件 + */ +void MainWindow::initUi() { + this->setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT); + + m_frame = new QFrame(this); + + this->setCentralWidget(m_frame); + QVBoxLayout * mainlayout = new QVBoxLayout(m_frame); + mainlayout->setContentsMargins(MAIN_MARGINS); + m_frame->setLayout(mainlayout); + + m_titleFrame = new QFrame(m_frame);//标题栏 + m_titleFrame->setFixedHeight(TITLE_HEIGHT); + m_titleLyt = new QHBoxLayout(m_titleFrame); + m_titleLyt->setContentsMargins(TITLE_MARGINS); + m_iconLabel = new QLabel(m_titleFrame); + m_iconLabel->setFixedSize(WINDOW_ICON_SIZE, WINDOW_ICON_SIZE); + m_iconLabel->setPixmap(QIcon::fromTheme("kylin-search").pixmap(QSize(WINDOW_ICON_SIZE, WINDOW_ICON_SIZE))); + m_titleLabel = new QLabel(m_titleFrame); + m_titleLabel->setText(tr("Search")); + m_settingsBtn = new QPushButton(m_titleFrame); + m_settingsBtn->setFixedSize(SETTING_BTN_SIZE, SETTING_BTN_SIZE); + m_settingsBtn->setIcon(QIcon::fromTheme("document-properties-symbolic")); + m_settingsBtn->setProperty("useIconHighlightEffect", 0x2); + m_settingsBtn->setProperty("isWindowButton", 0x01); + m_settingsBtn->setFlat(true); + m_titleLyt->addWidget(m_iconLabel); + m_titleLyt->addWidget(m_titleLabel); + m_titleLyt->addStretch(); + m_titleLyt->addWidget(m_settingsBtn); + m_stackedWidget = new StackedWidget(m_frame);//内容栏 + + m_searchWidget = new SeachBarWidget(this); + m_searchLayout = new SearchBarHLayout(this); + m_searchWidget->setLayout(m_searchLayout); + m_searchWidget->setFixedHeight(SEARCH_BAR_SIZE); + + mainlayout->addWidget(m_titleFrame); + mainlayout->addWidget(m_stackedWidget); + mainlayout->addWidget(m_searchWidget); + + //创建索引询问弹窗 + m_askDialog = new CreateIndexAskDialog(this); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + MotifWmHints ask_dialog_hints; + ask_dialog_hints.flags = MWM_HINTS_FUNCTIONS | MWM_HINTS_DECORATIONS; + ask_dialog_hints.functions = MWM_FUNC_ALL; + ask_dialog_hints.decorations = MWM_DECOR_BORDER; + XAtomHelper::getInstance()->setWindowMotifHint(m_askDialog->winId(), ask_dialog_hints); +#endif +} + +void MainWindow::initConnections() +{ + connect(m_sys_tray_icon, &QSystemTrayIcon::activated, this, &MainWindow::trayIconActivatedSlot); + QObject::connect(this, &MainWindow::searchMethodChanged, this, [ = ](FileUtils::SearchMethod sm) { + this->m_searchMethodManager.searchMethod(sm); + }); + connect(QApplication::primaryScreen(), &QScreen::geometryChanged, this, &MainWindow::monitorResolutionChange); + connect(qApp, &QApplication::primaryScreenChanged, this, &MainWindow::primaryScreenChangedSlot); + connect(m_askDialog, &CreateIndexAskDialog::closed, this, [ = ]() { + m_isAskDialogVisible = false; + }); + connect(m_askDialog, &CreateIndexAskDialog::btnClicked, this, [ = ](const bool &is_create_index, const bool &is_ask_again) { + setSearchMethodConfig(is_create_index, is_ask_again); + }); + connect(m_settingsBtn, &QPushButton::clicked, this, &MainWindow::settingsBtnClickedSlot); + //主题改变时,更新自定义标题栏的图标 + connect(qApp, &QApplication::paletteChanged, this, [ = ]() { + m_iconLabel->setPixmap(QIcon::fromTheme("kylin-search").pixmap(QSize(WINDOW_ICON_SIZE, WINDOW_ICON_SIZE))); + }); + connect(m_searchLayout, &SearchBarHLayout::requestSearchKeyword, this, &MainWindow::searchKeywordSlot); +} + +/** + * @brief bootOptionsFilter 过滤终端命令 + * @param opt + */ +void MainWindow::bootOptionsFilter(QString opt) { + if(opt == "-s" || opt == "--show") { + clearSearchResult(); + centerToScreen(this); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + XAtomHelper::getInstance()->setWindowMotifHint(winId(), m_hints); +#endif + this->show(); + this->m_searchLayout->focusIn(); + this->raise(); + this->activateWindow(); + } +} + +/** + * @brief clearSearchResult 清空搜索结果 + */ +void MainWindow::clearSearchResult() { + m_searchLayout->clearText(); + m_searchLayout->focusOut(); +} + +/** + * @brief MainWindow::trayIconActivatedSlot 点击任务栏图标的槽函数 + * @param reason + */ +void MainWindow::trayIconActivatedSlot(QSystemTrayIcon::ActivationReason reason) +{ + if(reason == QSystemTrayIcon::Trigger) { + if(!this->isVisible()) { + clearSearchResult(); + centerToScreen(this); +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + XAtomHelper::getInstance()->setWindowMotifHint(winId(), m_hints); +#endif + this->show(); + this->m_searchLayout->focusIn(); //打开主界面时输入框夺焦,可直接输入 + this->raise(); + this->activateWindow(); + } else { + tryHideMainwindow(); + } + } +} + +/** + * @brief setSearchMethodConfig 在询问弹窗点击按钮后执行 + * @param create_index 是否同意创建索引 + * @param no_longer_ask 是否勾选了不再提示 + */ +void MainWindow::setSearchMethodConfig(const bool &create_index, const bool &no_longer_ask) +{ + if(no_longer_ask) { + GlobalSettings::getInstance()->setValue(ENABLE_CREATE_INDEX_ASK_DIALOG, "false"); + } else { + GlobalSettings::getInstance()->setValue(ENABLE_CREATE_INDEX_ASK_DIALOG, "true"); + } + if(create_index) { + if(m_search_gsettings && m_search_gsettings->keys().contains(SEARCH_METHOD_KEY)) { + m_search_gsettings->set(SEARCH_METHOD_KEY, true); + } else { + //调用创建索引接口 + Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::INDEXSEARCH); + //创建索引十秒后重新搜索一次(如果用户十秒内没有退出搜索界面且没有重新搜索) + m_researchTimer->start(); + } + } +} + +/** + * @brief MainWindow::settingsBtnClickedSlot 点击设置按钮的槽函数 + */ +void MainWindow::settingsBtnClickedSlot() +{ +#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0)) + if(m_settingsWidget) { //当此窗口已存在时,仅需置顶 + if(!m_settingsWidget->isVisible()) { + centerToScreen(m_settingsWidget); + } + m_settingsWidget->showWidget(); + return; + } + m_settingsWidget = new SettingsWidget(); + connect(this, &MainWindow::webEngineChanged, m_settingsWidget, [ = ]() { + m_settingsWidget->resetWebEngine(); + }); + connect(m_settingsWidget, &SettingsWidget::webEngineChanged, this, [ = ](const QString & engine) { + if(m_search_gsettings && m_search_gsettings->keys().contains(WEB_ENGINE_KEY)) { + m_search_gsettings->set(WEB_ENGINE_KEY, engine); + } else { + GlobalSettings::getInstance()->setValue(WEB_ENGINE, engine); + } + }); + centerToScreen(m_settingsWidget); + m_settingsWidget->show(); + connect(m_settingsWidget, &SettingsWidget::settingWidgetClosed, this, [ = ]() { + QTimer::singleShot(100, this, [ = ] { +// clearSearchResult(); //现暂定从设置页返回主页面不清空搜索结果 + this->setWindowState(this->windowState() & ~Qt::WindowMinimized); + this->raise(); + this->showNormal(); + this->activateWindow(); + }); + }); +#endif +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + //打开控制面板的设置页 + QProcess process; + process.startDetached("ukui-control-center --search"); +#endif +} + +/** + * @brief MainWindow::searchKeywordSlot 执行搜索的槽函数 + * @param keyword 关键词 + */ +void MainWindow::searchKeywordSlot(const QString &keyword) +{ + //NEW_TODO + if(keyword == "") { + m_stackedWidget->setPage(int(StackedPage::HomePage)); + m_askTimer->stop(); + Q_EMIT m_stackedWidget->stopSearch(); + } else { + m_stackedWidget->setPage(int(StackedPage::SearchPage)); + QTimer::singleShot(10, this, [ = ]() { + //允许弹窗且当前次搜索(为关闭主界面,算一次搜索过程)未询问且当前为暴力搜索 + if(GlobalSettings::getInstance()->getValue(ENABLE_CREATE_INDEX_ASK_DIALOG).toString() != "false" && !m_currentSearchAsked && FileUtils::searchMethod == FileUtils::SearchMethod::DIRECTSEARCH) + m_askTimer->start(); + Q_EMIT m_stackedWidget->startSearch(keyword); + }); + } + m_researchTimer->stop(); //如果搜索内容发生改变,则停止建索引后重新搜索的倒计时 +} + +/** + * @brief monitorResolutionChange 监听屏幕改变 + * @param rect + */ +void MainWindow::monitorResolutionChange(QRect rect) { + Q_UNUSED(rect); +} + +/** + * @brief primaryScreenChangedSlot 监听分辨率改变 + * @param screen + */ +void MainWindow::primaryScreenChangedSlot(QScreen *screen) { + Q_UNUSED(screen); + +} + +/** + * @brief MainWindow::moveToPanel 将主界面移动到任务栏旁边(跟随任务栏位置) + */ +void MainWindow::moveToPanel() { + QRect availableGeometry = qApp->primaryScreen()->availableGeometry(); + QRect screenGeometry = qApp->primaryScreen()->geometry(); + + QDBusInterface primaryScreenInterface("org.ukui.SettingsDaemon", + "/org/ukui/SettingsDaemon/wayland", + "org.ukui.SettingsDaemon.wayland", + QDBusConnection::sessionBus()); + if(QDBusReply(primaryScreenInterface.call("x")).isValid()) { + QDBusReply x = primaryScreenInterface.call("x"); + QDBusReply y = primaryScreenInterface.call("y"); + QDBusReply width = primaryScreenInterface.call("width"); + QDBusReply height = primaryScreenInterface.call("height"); + screenGeometry.setX(x); + screenGeometry.setY(y); + screenGeometry.setWidth(width); + screenGeometry.setHeight(height); + availableGeometry.setX(x); + availableGeometry.setY(y); + availableGeometry.setWidth(width); + availableGeometry.setHeight(height); + } + + QDesktopWidget * desktopWidget = QApplication::desktop(); + QRect screenMainRect = desktopWidget->screenGeometry(0);//获取设备屏幕大小 + + QDBusInterface interface("com.ukui.panel.desktop", + "/", + "com.ukui.panel.desktop", + QDBusConnection::sessionBus()); + + int position = QDBusReply(interface.call("GetPanelPosition", "position")); + int height = QDBusReply(interface.call("GetPanelSize", "height")); + int d = 8; //窗口边沿到任务栏距离 + + if(position == 0) { + //任务栏在下侧 + this->move(availableGeometry.x() + availableGeometry.width() - this->width() - d, screenGeometry.y() + screenGeometry.height() - this->height() - height - d); + } else if(position == 1) { + //任务栏在上侧 + this->move(availableGeometry.x() + availableGeometry.width() - this->width() - d, screenGeometry.y() + height + d); + } else if(position == 2) { + //任务栏在左侧 + this->move(screenGeometry.x() + height + d, screenGeometry.y() + screenGeometry.height() - this->height() - d); + } else if(position == 3) { + //任务栏在右侧 + this->move(screenGeometry.x() + screenGeometry.width() - this->width() - height - d, screenGeometry.y() + screenGeometry.height() - this->height() - d); + } +} + +/** + * @brief MainWindow::centerToScreen 使窗口显示在屏幕中间 + * @param widget + */ +void MainWindow::centerToScreen(QWidget* widget) { + if(!widget) + return; + QDesktopWidget* m = QApplication::desktop(); + QRect desk_rect = m->screenGeometry(m->screenNumber(QCursor::pos())); + int desk_x = desk_rect.width(); + int desk_y = desk_rect.height(); + int x = widget->width(); + int y = widget->height(); + QDBusInterface primaryScreenInterface("org.ukui.SettingsDaemon", + "/org/ukui/SettingsDaemon/wayland", + "org.ukui.SettingsDaemon.wayland", + QDBusConnection::sessionBus()); + if(QDBusReply(primaryScreenInterface.call("x")).isValid()) { + QDBusReply width = primaryScreenInterface.call("width"); + QDBusReply height = primaryScreenInterface.call("height"); + desk_x = width; + desk_y = height; + } + widget->move(desk_x / 2 - x / 2 + desk_rect.left(), desk_y / 2 - y / 2 + desk_rect.top()); +} + +void MainWindow::initGsettings() { + const QByteArray id(UKUI_SEARCH_SCHEMAS); + if(QGSettings::isSchemaInstalled(id)) { + m_search_gsettings = new QGSettings(id); + connect(m_search_gsettings, &QGSettings::changed, this, [ = ](const QString & key) { + if(key == SEARCH_METHOD_KEY) { + bool is_index_search = m_search_gsettings->get(SEARCH_METHOD_KEY).toBool(); + this->setSearchMethod(is_index_search); + } else if(key == WEB_ENGINE_KEY) { + QString web_engine = m_search_gsettings->get(WEB_ENGINE_KEY).toString(); + GlobalSettings::getInstance()->setValue(WEB_ENGINE, web_engine); + Q_EMIT this->webEngineChanged(); + } + }); + if(m_search_gsettings->keys().contains(SEARCH_METHOD_KEY)) { + bool is_index_search = m_search_gsettings->get(SEARCH_METHOD_KEY).toBool(); + this->setSearchMethod(is_index_search); + } + if(m_search_gsettings->keys().contains(WEB_ENGINE_KEY)) { + QString web_engine = m_search_gsettings->get(WEB_ENGINE_KEY).toString(); + GlobalSettings::getInstance()->setValue(WEB_ENGINE, web_engine); + } + } +} + +//使用GSetting获取当前窗口应该使用的透明度 +double MainWindow::getTransparentData() { + return GlobalSettings::getInstance()->getValue(TRANSPARENCY_KEY).toDouble(); +} + +void MainWindow::initTimer() { + m_askTimer = new QTimer; + m_askTimer->setInterval(ASK_INDEX_TIME); + connect(m_askTimer, &QTimer::timeout, this, [ = ]() { + if(this->isVisible()) { + m_isAskDialogVisible = true; + m_askDialog->show(); + m_currentSearchAsked = true; + } + m_askTimer->stop(); + }); + m_researchTimer = new QTimer; + m_researchTimer->setInterval(RESEARCH_TIME); + connect(m_researchTimer, &QTimer::timeout, this, [ = ]() { + if(this->isVisible()) { + m_searchLayout->reSearch(); + } + m_researchTimer->stop(); + }); + connect(m_searchLayout, &SearchBarHLayout::requestSearchKeyword, this, [ = ](QString text) { + if(text == "" || text.isEmpty()) { + m_askTimer->stop(); + } else { + //允许弹窗且当前次搜索(为关闭主界面,算一次搜索过程)未询问且当前为暴力搜索 + if(GlobalSettings::getInstance()->getValue(ENABLE_CREATE_INDEX_ASK_DIALOG).toString() != "false" && !m_currentSearchAsked && FileUtils::searchMethod == FileUtils::SearchMethod::DIRECTSEARCH) + m_askTimer->start(); + } + }); +} + +/** + * @brief MainWindow::tryHideMainwindow 尝试隐藏主界面并停止部分未完成的动作,重置部分状态值 + */ +bool MainWindow::tryHideMainwindow() +{ + if (!m_isAskDialogVisible) { + qDebug()<<"Mainwindow will be hidden"; + m_currentSearchAsked = false; + this->hide(); + m_askTimer->stop(); + m_researchTimer->stop(); + Q_EMIT m_stackedWidget->stopSearch(); + return true; + } else { + //有上层弹窗未关闭,不允许隐藏主界面 + qWarning()<<"There is a dialog onside, so that mainwindow can not be hidden."; + return false; + } +} + +/** + * @brief MainWindow::setSearchMethod 设置搜索模式 + * @param is_index_search true为索引搜索,false为暴力搜索 + */ +void MainWindow::setSearchMethod(const bool &is_index_search) { + if(is_index_search) { + //调用创建索引接口 + Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::INDEXSEARCH); + //创建索引十秒后重新搜索一次(如果用户十秒内没有退出搜索界面且没有重新搜索) + m_researchTimer->start(); + } else { + Q_EMIT this->searchMethodChanged(FileUtils::SearchMethod::DIRECTSEARCH); + m_researchTimer->stop(); + } +} + +/** + * @brief MainWindow::keyPressEvent 按esc键关闭主界面 + * @param event + */ +void MainWindow::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) { + tryHideMainwindow(); + } + return QWidget::keyPressEvent(event); +} + +bool MainWindow::eventFilter(QObject *watched, QEvent *event) +{ + if (event->type() == QEvent::ActivationChange) { + if(QApplication::activeWindow() != this) { + tryHideMainwindow(); + } + } + return QMainWindow::eventFilter(watched,event); +} + +void MainWindow::paintEvent(QPaintEvent *event) { + Q_UNUSED(event) + + double trans = getTransparentData(); + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + + QRect rect = this->rect(); + p.setRenderHint(QPainter::Antialiasing); // 反锯齿; + p.setBrush(opt.palette.color(QPalette::Base)); + p.setOpacity(trans); + p.setPen(Qt::NoPen); +// p.drawRoundedRect(rect, 6, 6); + p.drawRect(rect); + return QWidget::paintEvent(event); + +} diff --git a/frontend/mainwindow.h b/frontend/mainwindow.h new file mode 100644 index 0000000..1951fa2 --- /dev/null +++ b/frontend/mainwindow.h @@ -0,0 +1,153 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * Modified by: zhangpengfei + * + */ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "index/index-generator.h" +#include "libsearch.h" +#include "create-index-ask-dialog.h" +#include "stacked-widget.h" +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) +#include "xatom-helper.h" +#endif +#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0)) +#include "settings-widget.h" +#endif + +namespace Zeeker { +class SearchResult; +class MainWindow : public QMainWindow { + friend class SearchResult; + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + /** + * @brief Load the main window + */ + + // The position which mainwindow shows follow the ukui-panel. + void moveToPanel(); + + // The position which mainwindow shows in the center of screen where the cursor in. + void centerToScreen(QWidget* widget); + void initGsettings(); + +#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) + MotifWmHints m_hints; +#endif + +private: + + QFrame * m_frame = nullptr; // Main frame + QFrame * m_titleFrame = nullptr; // Title bar frame + QHBoxLayout * m_titleLyt = nullptr; // Title layout + QLabel * m_iconLabel = nullptr; // Icon lable + QLabel * m_titleLabel = nullptr; // Title lable + QPushButton * m_settingsBtn = nullptr; // Menu button + StackedWidget * m_stackedWidget = nullptr; // Stacked widget + SearchBarHLayout * m_searchLayout = nullptr; // Search bar layout + SeachBarWidget * m_searchWidget = nullptr; // Search bar +#if (QT_VERSION < QT_VERSION_CHECK(5, 12, 0)) + SettingsWidget * m_settingsWidget = nullptr; // Settings Widget +#endif + + QStringList m_dirList; + + QQueue *m_search_result_file = nullptr; + QQueue *m_search_result_dir = nullptr; + QQueue> *m_search_result_content = nullptr; + + QSystemTrayIcon *m_sys_tray_icon = nullptr; + CreateIndexAskDialog * m_askDialog = nullptr; + bool m_isAskDialogVisible = false; + + QTimer * m_askTimer = nullptr; //询问是否创建索引弹窗弹出的计时器 + QTimer * m_researchTimer = nullptr; //创建索引后重新执行一次搜索的计时器 + bool m_currentSearchAsked = false; //本次搜索是否已经询问过是否创建索引了 + QGSettings * m_search_gsettings = nullptr; + + SearchMethodManager m_searchMethodManager; + + void setSearchMethod(const bool&); + double getTransparentData(); + void initTimer(); + bool tryHideMainwindow(); + void setSearchMethodConfig(const bool&, const bool&); + +protected: + void paintEvent(QPaintEvent *); + void keyPressEvent(QKeyEvent *event); + bool eventFilter(QObject *watched, QEvent *event) override; + void initUi(); + void initConnections(); + +Q_SIGNALS: + void searchMethodChanged(FileUtils::SearchMethod); + void webEngineChanged(); + +public Q_SLOTS: + /** + * @brief Monitor screen resolution + * @param rect: Screen resolution + */ + void monitorResolutionChange(QRect rect); + /** + * @brief Monitor primary screen changes + * @param screen: Primary screen + */ + void primaryScreenChangedSlot(QScreen *screen); + + void bootOptionsFilter(QString opt); // 过滤终端命令 + void clearSearchResult(); //清空搜索结果 + void trayIconActivatedSlot(QSystemTrayIcon::ActivationReason reason); + void settingsBtnClickedSlot(); + void searchKeywordSlot(const QString&); +}; +} + +#endif // MAINWINDOW_H diff --git a/frontend/model/README.md b/frontend/model/README.md new file mode 100644 index 0000000..e69de29 diff --git a/frontend/model/model.pri b/frontend/model/model.pri new file mode 100644 index 0000000..05b21b8 --- /dev/null +++ b/frontend/model/model.pri @@ -0,0 +1,9 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/search-result-manager.h \ + $$PWD/search-result-model.h \ + +SOURCES += \ + $$PWD/search-result-manager.cpp \ + $$PWD/search-result-model.cpp \ diff --git a/frontend/model/search-result-manager.cpp b/frontend/model/search-result-manager.cpp new file mode 100644 index 0000000..fbcbc9a --- /dev/null +++ b/frontend/model/search-result-manager.cpp @@ -0,0 +1,126 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#include "search-result-manager.h" + +using namespace Zeeker; +SearchResultManager::SearchResultManager(const QString& plugin_id, QObject *parent) : QObject(parent) +{ + m_plugin_id = plugin_id; + m_result_queue = new QQueue; + m_get_result_thread = new ReceiveResultThread(m_result_queue); + initConnections(); +} + +void SearchResultManager::startSearch(const QString &keyword) +{ + //NEW_TODO 加锁?停止线程?重新搜索? + stopSearch(); + m_result_queue->clear(); + SearchPluginIface *plugin = SearchPluginManager::getInstance()->getPlugin(m_plugin_id); +// plugin->KeywordSearch(keyword, m_result_queue); + /*********************测试用数据*********************/ + SearchPluginIface::ResultInfo test_info; + if (m_plugin_id == "File") { + test_info.icon = QIcon::fromTheme("ukui-control-center"); + test_info.name = "搜索"; + QVector desc; + SearchPluginIface::DescriptionInfo desc_1; + desc_1.key = "描述"; + desc_1.value = "控制面板搜索插件"; + desc.append(desc_1); + QMap actions; + actions.insert("ukcc-search","打开"); + test_info.description = desc; + test_info.actionMap = actions; + m_result_queue->append(test_info); + } else { + test_info.icon = QIcon::fromTheme("ukui-control-center"); + test_info.name = "文件"; + QVector desc; + SearchPluginIface::DescriptionInfo desc_1; + SearchPluginIface::DescriptionInfo desc_2; + desc_1.key = "描述"; + desc_1.value = "一个文件"; + desc_2.key = "路径"; + desc_2.value = "一个路径"; + desc.append(desc_1); + desc.append(desc_2); + QMap actions; + actions.insert("file1","打开"); + actions.insert("file2","复制路径"); + test_info.description = desc; + test_info.actionMap = actions; + m_result_queue->append(test_info); + } + /********************测试用数据********************/ + + qWarning()<start by others"; + m_get_result_thread->start(); +} + +/** + * @brief SearchResultManager::stopSearch 停止搜索,开始一次新搜索前或主界面退出时执行 + */ +void SearchResultManager::stopSearch() +{ + if(m_get_result_thread->isRunning()) { + qWarning()<stopped by others"; + m_get_result_thread->requestInterruption(); + m_get_result_thread->quit(); + } +} + +void SearchResultManager::initConnections() +{ + connect(m_get_result_thread, &ReceiveResultThread::gotResultInfo, this, &SearchResultManager::gotResultInfo); +} + +ReceiveResultThread::ReceiveResultThread(QQueue * result_queue, QObject *parent) +{ + m_result_queue = result_queue; +} + +//NEW_TODO 还未对队列加锁 +void ReceiveResultThread::run() +{ + QTimer * m_timer = new QTimer; + m_timer->setInterval(3000); + bool is_empty; + while(!isInterruptionRequested()) { + is_empty = false; + if(!m_result_queue->isEmpty()) { + Q_EMIT this->gotResultInfo(m_result_queue->dequeue()); + } else { + is_empty = true; + } + if(m_timer->isActive() && m_timer->remainingTime() < 0.01) { + this->requestInterruption(); + qWarning()<<"-------------->stopped by self"; + } + if(is_empty && !m_timer->isActive()) { + m_timer->start(); + } else if(!is_empty) { + m_timer->stop(); + } else { + msleep(100); + } + } +} diff --git a/frontend/model/search-result-manager.h b/frontend/model/search-result-manager.h new file mode 100644 index 0000000..1941173 --- /dev/null +++ b/frontend/model/search-result-manager.h @@ -0,0 +1,71 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#ifndef SEARCHRESULTMANAGER_H +#define SEARCHRESULTMANAGER_H + +#include +#include +#include +#include +#include "pluginmanage/search-plugin-manager.h" + +namespace Zeeker { + +class ReceiveResultThread : public QThread { + Q_OBJECT +public: + ReceiveResultThread(QQueue * result_queue, QObject * parent = nullptr); + ~ReceiveResultThread() = default; +protected: + void run() override; + +private: + QQueue * m_result_queue; + + +Q_SIGNALS: + void gotResultInfo(const SearchPluginIface::ResultInfo&); + +}; + +class SearchResultManager : public QObject +{ + Q_OBJECT +public: + explicit SearchResultManager(const QString &plugin_id, QObject *parent = nullptr); + ~SearchResultManager() = default; + +public Q_SLOTS: + void startSearch(const QString &); + void stopSearch(); + +private: + void initConnections(); + QString m_plugin_id; + QQueue * m_result_queue; + ReceiveResultThread * m_get_result_thread = nullptr; + +Q_SIGNALS: + void gotResultInfo(const SearchPluginIface::ResultInfo&); +}; +} + +#endif // SEARCHRESULTMANAGER_H diff --git a/frontend/model/search-result-model.cpp b/frontend/model/search-result-model.cpp new file mode 100644 index 0000000..922515f --- /dev/null +++ b/frontend/model/search-result-model.cpp @@ -0,0 +1,102 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#include "search-result-model.h" +using namespace Zeeker; + +SearchResultModel::SearchResultModel(const QString &plugin_id) +{ + m_item = new SearchResultItem; + m_plugin_id = plugin_id; + m_search_manager = new SearchResultManager(plugin_id); + initConnections(); +} + +QModelIndex SearchResultModel::index(int row, int column, const QModelIndex &parent) const +{ + if(row < 0 || row > m_item->m_result_info_list.length() - 1) + return QModelIndex(); +// QVector * m_info = &m_result_info_list; + return createIndex(row, column, m_item); +} + +QModelIndex SearchResultModel::parent(const QModelIndex &child) const +{ + return QModelIndex(); +} + +int SearchResultModel::rowCount(const QModelIndex &index) const +{ + return index.isValid() ? 0 : m_item->m_result_info_list.length(); +} + +int SearchResultModel::columnCount(const QModelIndex &index) const +{ + return index.isValid() ? 0 : 1; +} + +QVariant SearchResultModel::data(const QModelIndex &index, int role) const +{ + if(!index.isValid()) + return QVariant(); + switch(role) { + case Qt::DecorationRole: { + return m_item->m_result_info_list.at(index.row()).icon; + } + case Qt::DisplayRole: { + return m_item->m_result_info_list.at(index.row()).name; + } + default: + return QVariant(); + } + return QVariant(); +} + +//bool SearchResultModel::insertRows(int row, int count, const QModelIndex &parent) +//{ +// this->beginInsertRows(parent, row, count); +// this->endInsertRows(); +// return true; +//} + +void SearchResultModel::appendInfo(const SearchPluginIface::ResultInfo &info) +{ + this->beginResetModel(); + qWarning()<<"Got a result. name ="<m_result_info_list.append(info); + this->endResetModel(); +} + +void SearchResultModel::initConnections() +{ + connect(this, &SearchResultModel::startSearch, m_search_manager, &SearchResultManager::startSearch); + connect(this, &SearchResultModel::stopSearch, m_search_manager, &SearchResultManager::stopSearch); + connect(m_search_manager, &SearchResultManager::gotResultInfo, this, &SearchResultModel::appendInfo); +} + +const SearchPluginIface::ResultInfo &SearchResultModel::getInfo(const QModelIndex &index) +{ + return m_item->m_result_info_list.at(index.row()); +} + +SearchResultItem::SearchResultItem(QObject *parent) : QObject(parent) +{ + +} diff --git a/frontend/model/search-result-model.h b/frontend/model/search-result-model.h new file mode 100644 index 0000000..f6d9bca --- /dev/null +++ b/frontend/model/search-result-model.h @@ -0,0 +1,67 @@ +/* + * + * Copyright (C) 2020, 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: zhangjiaping + * + */ +#ifndef SEARCHRESULTMODEL_H +#define SEARCHRESULTMODEL_H +#include +#include "search-result-manager.h" + +namespace Zeeker { + +class SearchResultItem : public QObject { + friend class SearchResultModel; + Q_OBJECT +public: + explicit SearchResultItem(QObject *parent = nullptr); + ~SearchResultItem() = default; +private: + //此插件所有搜索结果<具体信息> + QVector m_result_info_list; +}; +class SearchResultModel : public QAbstractItemModel +{ + Q_OBJECT +public: + SearchResultModel(const QString &plugin_id); + ~SearchResultModel() = default; + 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; + QVariant data(const QModelIndex &index, int role) const override; +// bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex())override; + const SearchPluginIface::ResultInfo & getInfo(const QModelIndex&); + +public Q_SLOTS: + void appendInfo(const SearchPluginIface::ResultInfo &); + +Q_SIGNALS: + void startSearch(const QString &); + void stopSearch(); + +private: + void initConnections(); + SearchResultItem * m_item = nullptr; + QString m_plugin_id; + SearchResultManager * m_search_manager = nullptr; +}; +} + +#endif // SEARCHRESULTMODEL_H diff --git a/frontend/res/icons/close.svg b/frontend/res/icons/close.svg new file mode 100644 index 0000000..06a5a23 --- /dev/null +++ b/frontend/res/icons/close.svg @@ -0,0 +1 @@ +close \ No newline at end of file diff --git a/frontend/res/icons/desktop.png b/frontend/res/icons/desktop.png new file mode 100644 index 0000000..92c38ee Binary files /dev/null and b/frontend/res/icons/desktop.png differ diff --git a/frontend/res/icons/edit-find-symbolic.svg b/frontend/res/icons/edit-find-symbolic.svg new file mode 100644 index 0000000..82e1356 --- /dev/null +++ b/frontend/res/icons/edit-find-symbolic.svg @@ -0,0 +1,14 @@ + + + edit-find-symbolic + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/res/icons/net-disconnected.svg b/frontend/res/icons/net-disconnected.svg new file mode 100644 index 0000000..4d5f99f --- /dev/null +++ b/frontend/res/icons/net-disconnected.svg @@ -0,0 +1,30 @@ + + + + image-viewer-app-symbolic + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/resource.qrc b/frontend/resource.qrc new file mode 100644 index 0000000..018ebdd --- /dev/null +++ b/frontend/resource.qrc @@ -0,0 +1,9 @@ + + + res/icons/edit-find-symbolic.svg + res/icons/desktop.png + res/icons/close.svg + res/qt-translations/qt_zh_CN.qm + res/icons/net-disconnected.svg + + diff --git a/frontend/singleapplication/qt-local-peer.cpp b/frontend/singleapplication/qt-local-peer.cpp new file mode 100644 index 0000000..4a62537 --- /dev/null +++ b/frontend/singleapplication/qt-local-peer.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies) +** 2020 KylinSoft Co., Ltd. +** Contact: http://www.qt-project.org/legal +** +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 Digia Plc 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." +** +** $QT_END_LICENSE$ +** +** +****************************************************************************/ + + +#include "qt-local-peer.h" +#include +#include +#include + +#if defined(Q_OS_UNIX) +#include +#include +#include +#endif + +namespace QtLP_Private { +#include "qt-locked-file.cpp" +#include "qt-locked-file-unix.cpp" +} + +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(QRegExp("[^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"); //tmp目录下的锁文件 + 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; + + //由于文件锁的存在,仅当本进程第一次启动时能执行到此并使server进行监听和关联槽函数 + 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, &QLocalServer::newConnection, this, &QtLocalPeer::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; +} + +/** + * @brief QtLocalPeer::receiveConnection 当新进程启动时,会尝试连接此进程server,server接收到newConnection信号并触发此槽函数 + */ +void QtLocalPeer::receiveConnection() { + QLocalSocket* socket = server->nextPendingConnection(); //获取新进程的socket + 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; + Q_EMIT messageReceived(message); //获取新进程的启动信息并作为信号发送给前端 +} diff --git a/frontend/singleapplication/qt-local-peer.h b/frontend/singleapplication/qt-local-peer.h new file mode 100644 index 0000000..883aec2 --- /dev/null +++ b/frontend/singleapplication/qt-local-peer.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies) +** 2020 KylinSoft Co., Ltd. +** Contact: http://www.qt-project.org/legal +** +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 Digia Plc 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." +** +** $QT_END_LICENSE$ +** +** +****************************************************************************/ + +#ifndef QTLOCALPEER_H +#define QTLOCALPEER_H + +#include +#include +#include + +#include "qt-locked-file.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 diff --git a/frontend/singleapplication/qt-locked-file-unix.cpp b/frontend/singleapplication/qt-locked-file-unix.cpp new file mode 100644 index 0000000..5147252 --- /dev/null +++ b/frontend/singleapplication/qt-locked-file-unix.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies) +** 2020 KylinSoft Co., Ltd. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 Digia Plc 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." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include +#include + +#include "qt-locked-file.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(); +} + diff --git a/frontend/singleapplication/qt-locked-file.cpp b/frontend/singleapplication/qt-locked-file.cpp new file mode 100644 index 0000000..707b4cf --- /dev/null +++ b/frontend/singleapplication/qt-locked-file.cpp @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies) +** 2020 KylinSoft Co., Ltd. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 Digia Plc 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." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qt-locked-file.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. +*/ diff --git a/frontend/singleapplication/qt-locked-file.h b/frontend/singleapplication/qt-locked-file.h new file mode 100644 index 0000000..332d648 --- /dev/null +++ b/frontend/singleapplication/qt-locked-file.h @@ -0,0 +1,97 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies) +** 2020 KylinSoft Co., Ltd. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 Digia Plc 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." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#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 diff --git a/frontend/singleapplication/qt-single-application.cpp b/frontend/singleapplication/qt-single-application.cpp new file mode 100644 index 0000000..95b1900 --- /dev/null +++ b/frontend/singleapplication/qt-single-application.cpp @@ -0,0 +1,357 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies) +** 2020 KylinSoft Co., Ltd. +** Contact: http://www.qt-project.org/legal +** +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 Digia Plc 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." +** +** $QT_END_LICENSE$ +** +** +****************************************************************************/ + + +#include "qt-single-application.h" +#include "qt-local-peer.h" +#include +#include +#include +#include +#include +#include "mainwindow.h" + + +/*! + \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&))); + connect(peer, &QtLocalPeer::messageReceived, this, &QtSingleApplication::messageReceived); +} + + +/*! + 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, &QtLocalPeer::messageReceived, this, &QtSingleApplication::activateWindow); +// else +// disconnect(peer, &QtLocalPeer::messageReceived, this, &QtSingleApplication::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() { + //单例置顶策略,由于bootOptionsFilter in mainwindow自带置顶策略,故注掉此处 +// if (actWin) { +// if(this->applicationState() & Qt::ApplicationInactive) +// { +// MainWindow* w=qobject_cast(actWin); +//// w->loadMainWindow(); +// w->clearSearchResult(); +// actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized); +// actWin->raise(); +// actWin->showNormal(); +// actWin->activateWindow(); +// } +// else { +// actWin->setWindowState(actWin->windowState() & Qt::WindowMinimized); +// actWin->hide(); +// } + +// } +} + + +/*! + \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 +*/ diff --git a/frontend/singleapplication/qt-single-application.h b/frontend/singleapplication/qt-single-application.h new file mode 100644 index 0000000..15df5a8 --- /dev/null +++ b/frontend/singleapplication/qt-single-application.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies) +** 2020 KylinSoft Co., Ltd. +** Contact: http://www.qt-project.org/legal +** +** +** This file is part of the Qt Solutions component. +** +** $QT_BEGIN_LICENSE:BSD$ +** 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 Digia Plc 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." +** +** $QT_END_LICENSE$ +** +** +****************************************************************************/ + +#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 diff --git a/frontend/singleapplication/qt-single-application.pri b/frontend/singleapplication/qt-single-application.pri new file mode 100644 index 0000000..0a4ca9a --- /dev/null +++ b/frontend/singleapplication/qt-single-application.pri @@ -0,0 +1,27 @@ +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 += + HEADERS += +} + +win32 { + contains(TEMPLATE, lib):contains(CONFIG, shared):DEFINES += QT_QTSINGLEAPPLICATION_EXPORT + else:qtsingleapplication-uselib:DEFINES += QT_QTSINGLEAPPLICATION_IMPORT +} + +HEADERS += \ + $$PWD/qt-local-peer.h \ + $$PWD/qt-locked-file.h \ + $$PWD/qt-single-application.h + +SOURCES += \ + $$PWD/qt-local-peer.cpp \ + $$PWD/qt-locked-file-unix.cpp \ + $$PWD/qt-single-application.cpp \ + $$PWD/qt-locked-file.cpp diff --git a/frontend/view/result-view.cpp b/frontend/view/result-view.cpp new file mode 100644 index 0000000..5fe8022 --- /dev/null +++ b/frontend/view/result-view.cpp @@ -0,0 +1,104 @@ +#include "result-view.h" +#define MAIN_MARGINS 0,0,0,0 +#define MAIN_SPACING 0 +#define TITLE_HEIGHT 30 +#define UNFOLD_LABEL_HEIGHT 24 + +using namespace Zeeker; +ResultWidget::ResultWidget(const QString &plugin_id, QWidget *parent) : QWidget(parent) +{ + m_plugin_id = plugin_id; + this->initUi(); + initConnections(); +} + +QString ResultWidget::pluginId() +{ + return m_plugin_id; +} + +void ResultWidget::setEnabled(const bool &enabled) +{ + m_enabled = enabled; +} + +void ResultWidget::initUi() +{ + m_mainLyt = new QVBoxLayout(this); + this->setLayout(m_mainLyt); + m_mainLyt->setContentsMargins(MAIN_MARGINS); + m_mainLyt->setSpacing(MAIN_SPACING); + + m_titleLabel = new TitleLabel(this); + m_titleLabel->setText(m_plugin_id); + m_titleLabel->setFixedHeight(TITLE_HEIGHT); + + m_resultView = new ResultView(m_plugin_id, this); + + //NEW_TODO + m_showMoreLabel = new ShowMoreLabel(this); + m_showMoreLabel->setFixedHeight(UNFOLD_LABEL_HEIGHT); + + m_mainLyt->addWidget(m_titleLabel); + m_mainLyt->addWidget(m_resultView); + m_mainLyt->addWidget(m_showMoreLabel); + this->setFixedHeight(m_resultView->height() + TITLE_HEIGHT + UNFOLD_LABEL_HEIGHT); +} + +void ResultWidget::initConnections() +{ + connect(this, &ResultWidget::startSearch, m_resultView, &ResultView::startSearch); + connect(this, &ResultWidget::stopSearch, m_resultView, &ResultView::stopSearch); + connect(m_resultView, &ResultView::currentRowChanged, this, &ResultWidget::currentRowChanged); + connect(this, &ResultWidget::clearSelectedRow, m_resultView, &ResultView::clearSelectedRow); +} + +ResultView::ResultView(const QString &plugin_id, QWidget *parent) : QTreeView(parent) +{ + this->setFrameShape(QFrame::NoFrame); + this->viewport()->setAutoFillBackground(false); + this->setRootIsDecorated(false); + this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + this->setSelectionBehavior(QAbstractItemView::SelectRows); + this->setSelectionMode(QAbstractItemView::SingleSelection); + this->setHeaderHidden(true); + m_model = new SearchResultModel(plugin_id); + this->setModel(m_model); + initConnections(); + m_plugin_id = plugin_id; +} + +bool ResultView::isSelected() +{ + return m_is_selected; +} + +void ResultView::clearSelectedRow() +{ + if (!m_is_selected) { + this->blockSignals(true); + this->clearSelection(); + this->blockSignals(false); + } +} + +void ResultView::initConnections() +{ + connect(this, &ResultView::startSearch, m_model, &SearchResultModel::startSearch); + connect(this, &ResultView::stopSearch, m_model, &SearchResultModel::stopSearch); + connect(this->selectionModel(), &QItemSelectionModel::selectionChanged, this, [ = ](const QItemSelection & selected, const QItemSelection & deselected) { + //NEW_TODO 处理选中事件 + m_is_selected = true; + Q_EMIT this->currentRowChanged(m_plugin_id, m_model->getInfo(this->currentIndex())); + m_is_selected = false; + if(!selected.isEmpty()) { + QRegion region = visualRegionForSelection(selected); + QRect rect = region.boundingRect(); +// Q_EMIT this->currentSelectPos(mapToParent(rect.topLeft())); + } + }); + + connect(this, &ResultView::activated, this, [ = ](const QModelIndex & index) { + //NEW_TODO 处理双击打开事件 + }); +} diff --git a/frontend/view/result-view.h b/frontend/view/result-view.h new file mode 100644 index 0000000..6fac408 --- /dev/null +++ b/frontend/view/result-view.h @@ -0,0 +1,63 @@ +#ifndef RESULTVIEW_H +#define RESULTVIEW_H +#include +#include +#include +#include "search-result-model.h" +#include "show-more-label.h" +#include "title-label.h" + +namespace Zeeker { + +class ResultView : public QTreeView +{ + Q_OBJECT +public: + ResultView(const QString &plugin_id, QWidget *parent = nullptr); + ~ResultView() = default; + bool isSelected(); + +public Q_SLOTS: + void clearSelectedRow(); + +private: + void initConnections(); + SearchResultModel * m_model = nullptr; + QString m_plugin_id; + bool m_is_selected = false; + +Q_SIGNALS: + void startSearch(const QString &); + void stopSearch(); + void currentRowChanged(const QString &, const SearchPluginIface::ResultInfo&); + +}; + +class ResultWidget : public QWidget +{ + Q_OBJECT +public: + ResultWidget(const QString &plugin_id, QWidget *parent = nullptr); + ~ResultWidget() = default; + QString pluginId(); + void setEnabled(const bool&); +private: + QString m_plugin_id; + bool m_enabled = true; + + void initUi(); + void initConnections(); + QVBoxLayout * m_mainLyt = nullptr; + TitleLabel * m_titleLabel = nullptr; + ResultView * m_resultView = nullptr; + ShowMoreLabel * m_showMoreLabel = nullptr; + +Q_SIGNALS: + void startSearch(const QString &); + void stopSearch(); + void currentRowChanged(const QString &, const SearchPluginIface::ResultInfo&); + void clearSelectedRow(); +}; +} + +#endif // RESULTVIEW_H diff --git a/frontend/view/view.pri b/frontend/view/view.pri new file mode 100644 index 0000000..5ee6282 --- /dev/null +++ b/frontend/view/view.pri @@ -0,0 +1,7 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/result-view.h \ + +SOURCES += \ + $$PWD/result-view.cpp \ diff --git a/frontend/xatom/xatom-helper.cpp b/frontend/xatom/xatom-helper.cpp new file mode 100644 index 0000000..9c17419 --- /dev/null +++ b/frontend/xatom/xatom-helper.cpp @@ -0,0 +1,200 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, 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: Yue Lan + * + */ + +#include "xatom-helper.h" + +#include + +#include + +#include +#include +#include + +static XAtomHelper *global_instance = nullptr; + +XAtomHelper *XAtomHelper::getInstance() { + if(!global_instance) + global_instance = new XAtomHelper; + return global_instance; +} + +bool XAtomHelper::isFrameLessWindow(int winId) { + auto hints = getInstance()->getWindowMotifHint(winId); + if(hints.flags == MWM_HINTS_DECORATIONS && hints.functions == 1) { + return true; + } + return false; +} + +bool XAtomHelper::isWindowDecorateBorderOnly(int winId) { + return isWindowMotifHintDecorateBorderOnly(getInstance()->getWindowMotifHint(winId)); +} + +bool XAtomHelper::isWindowMotifHintDecorateBorderOnly(const MotifWmHints &hint) { + bool isDeco = false; + if(hint.flags & MWM_HINTS_DECORATIONS && hint.flags != MWM_HINTS_DECORATIONS) { + if(hint.decorations == MWM_DECOR_BORDER) + isDeco = true; + } + return isDeco; +} + +bool XAtomHelper::isUKUICsdSupported() { + // fixme: + return false; +} + +bool XAtomHelper::isUKUIDecorationWindow(int winId) { + if(m_ukuiDecorationAtion == None) + return false; + + Atom type; + int format; + ulong nitems; + ulong bytes_after; + uchar *data; + + bool isUKUIDecoration = false; + + XGetWindowProperty(QX11Info::display(), winId, m_ukuiDecorationAtion, + 0, LONG_MAX, false, + m_ukuiDecorationAtion, &type, + &format, &nitems, + &bytes_after, &data); + + if(type == m_ukuiDecorationAtion) { + if(nitems == 1) { + isUKUIDecoration = data[0]; + } + } + + return isUKUIDecoration; +} + +UnityCorners XAtomHelper::getWindowBorderRadius(int winId) { + UnityCorners corners; + + Atom type; + int format; + ulong nitems; + ulong bytes_after; + uchar *data; + + if(m_unityBorderRadiusAtom != None) { + XGetWindowProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom, + 0, LONG_MAX, false, + XA_CARDINAL, &type, + &format, &nitems, + &bytes_after, &data); + + if(type == XA_CARDINAL) { + if(nitems == 4) { + corners.topLeft = static_cast(data[0]); + corners.topRight = static_cast(data[1 * sizeof(ulong)]); + corners.bottomLeft = static_cast(data[2 * sizeof(ulong)]); + corners.bottomRight = static_cast(data[3 * sizeof(ulong)]); + } + XFree(data); + } + } + + return corners; +} + +void XAtomHelper::setWindowBorderRadius(int winId, const UnityCorners &data) { + if(m_unityBorderRadiusAtom == None) + return; + + ulong corners[4] = {data.topLeft, data.topRight, data.bottomLeft, data.bottomRight}; + + XChangeProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom, XA_CARDINAL, + 32, XCB_PROP_MODE_REPLACE, (const unsigned char *) &corners, sizeof(corners) / sizeof(corners[0])); +} + +void XAtomHelper::setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight) { + if(m_unityBorderRadiusAtom == None) + return; + + ulong corners[4] = {(ulong)topLeft, (ulong)topRight, (ulong)bottomLeft, (ulong)bottomRight}; + + XChangeProperty(QX11Info::display(), winId, m_unityBorderRadiusAtom, XA_CARDINAL, + 32, XCB_PROP_MODE_REPLACE, (const unsigned char *) &corners, sizeof(corners) / sizeof(corners[0])); +} + +void XAtomHelper::setUKUIDecoraiontHint(int winId, bool set) { + if(m_ukuiDecorationAtion == None) + return; + + XChangeProperty(QX11Info::display(), winId, m_ukuiDecorationAtion, m_ukuiDecorationAtion, 32, XCB_PROP_MODE_REPLACE, (const unsigned char *) &set, 1); +} + +void XAtomHelper::setWindowMotifHint(int winId, const MotifWmHints &hints) { + if(m_unityBorderRadiusAtom == None) + return; + + XChangeProperty(QX11Info::display(), winId, m_motifWMHintsAtom, m_motifWMHintsAtom, + 32, XCB_PROP_MODE_REPLACE, (const unsigned char *)&hints, sizeof(MotifWmHints) / sizeof(ulong)); +} + +MotifWmHints XAtomHelper::getWindowMotifHint(int winId) { + MotifWmHints hints; + + if(m_unityBorderRadiusAtom == None) + return hints; + + uchar *data; + Atom type; + int format; + ulong nitems; + ulong bytes_after; + + XGetWindowProperty(QX11Info::display(), winId, m_motifWMHintsAtom, + 0, sizeof(MotifWmHints) / sizeof(long), false, AnyPropertyType, &type, + &format, &nitems, &bytes_after, &data); + + if(type == None) { + return hints; + } else { + hints = *(MotifWmHints *)data; + XFree(data); + } + return hints; +} + +XAtomHelper::XAtomHelper(QObject *parent) : QObject(parent) { + if(!QX11Info::isPlatformX11()) + return; + + m_motifWMHintsAtom = XInternAtom(QX11Info::display(), "_MOTIF_WM_HINTS", true); + m_unityBorderRadiusAtom = XInternAtom(QX11Info::display(), "_UNITY_GTK_BORDER_RADIUS", false); + m_ukuiDecorationAtion = XInternAtom(QX11Info::display(), "_KWIN_UKUI_DECORAION", false); +} + +Atom XAtomHelper::registerUKUICsdNetWmSupportAtom() { + // fixme: + return None; +} + +void XAtomHelper::unregisterUKUICsdNetWmSupportAtom() { + // fixme: +} diff --git a/frontend/xatom/xatom-helper.h b/frontend/xatom/xatom-helper.h new file mode 100644 index 0000000..615ce6d --- /dev/null +++ b/frontend/xatom/xatom-helper.h @@ -0,0 +1,110 @@ +/* + * KWin Style UKUI + * + * Copyright (C) 2020, 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: Yue Lan + * + */ + +#ifndef XATOMHELPER_H +#define XATOMHELPER_H + +#include + +#include +#include + +struct UnityCorners { + ulong topLeft = 0; + ulong topRight = 0; + ulong bottomLeft = 0; + ulong bottomRight = 0; +}; + +typedef struct { + ulong flags = 0; + ulong functions = 0; + ulong decorations = 0; + long input_mode = 0; + ulong status = 0; +} MotifWmHints, MwmHints; + +#define MWM_HINTS_FUNCTIONS (1L << 0) +#define MWM_HINTS_DECORATIONS (1L << 1) +#define MWM_HINTS_INPUT_MODE (1L << 2) +#define MWM_HINTS_STATUS (1L << 3) + +#define MWM_FUNC_ALL (1L << 0) +#define MWM_FUNC_RESIZE (1L << 1) +#define MWM_FUNC_MOVE (1L << 2) +#define MWM_FUNC_MINIMIZE (1L << 3) +#define MWM_FUNC_MAXIMIZE (1L << 4) +#define MWM_FUNC_CLOSE (1L << 5) + +#define MWM_DECOR_ALL (1L << 0) +#define MWM_DECOR_BORDER (1L << 1) +#define MWM_DECOR_RESIZEH (1L << 2) +#define MWM_DECOR_TITLE (1L << 3) +#define MWM_DECOR_MENU (1L << 4) +#define MWM_DECOR_MINIMIZE (1L << 5) +#define MWM_DECOR_MAXIMIZE (1L << 6) + +#define MWM_INPUT_MODELESS 0 +#define MWM_INPUT_PRIMARY_APPLICATION_MODAL 1 +#define MWM_INPUT_SYSTEM_MODAL 2 +#define MWM_INPUT_FULL_APPLICATION_MODAL 3 +#define MWM_INPUT_APPLICATION_MODAL MWM_INPUT_PRIMARY_APPLICATION_MODAL + +#define MWM_TEAROFF_WINDOW (1L<<0) + +namespace UKUI { +class Decoration; +} + +class XAtomHelper : public QObject { + // friend class UKUI::Decoration; + Q_OBJECT +public: + static XAtomHelper *getInstance(); + + static bool isFrameLessWindow(int winId); + + static bool isWindowDecorateBorderOnly(int winId); + static bool isWindowMotifHintDecorateBorderOnly(const MotifWmHints &hint); + bool isUKUICsdSupported(); + bool isUKUIDecorationWindow(int winId); + + UnityCorners getWindowBorderRadius(int winId); + void setWindowBorderRadius(int winId, const UnityCorners &data); + void setWindowBorderRadius(int winId, int topLeft, int topRight, int bottomLeft, int bottomRight); + void setUKUIDecoraiontHint(int winId, bool set = true); + + void setWindowMotifHint(int winId, const MotifWmHints &hints); + MotifWmHints getWindowMotifHint(int winId); + +private: + explicit XAtomHelper(QObject *parent = nullptr); + + Atom registerUKUICsdNetWmSupportAtom(); + void unregisterUKUICsdNetWmSupportAtom(); + + Atom m_motifWMHintsAtom = None; + Atom m_unityBorderRadiusAtom = None; + Atom m_ukuiDecorationAtion = None; +}; + +#endif // XATOMHELPER_H diff --git a/frontend/xatom/xatom.pri b/frontend/xatom/xatom.pri new file mode 100644 index 0000000..2d5b140 --- /dev/null +++ b/frontend/xatom/xatom.pri @@ -0,0 +1,7 @@ +INCLUDEPATH += $$PWD + +HEADERS += \ + $$PWD/xatom-helper.h \ + +SOURCES += \ + $$PWD/xatom-helper.cpp \ diff --git a/src/control/title-label.cpp b/src/control/title-label.cpp index 3a1af24..0d8aaef 100644 --- a/src/control/title-label.cpp +++ b/src/control/title-label.cpp @@ -43,7 +43,7 @@ void TitleLabel::paintEvent(QPaintEvent * event) { QRect rect = this->rect(); p.setRenderHint(QPainter::Antialiasing); // 反锯齿; p.setBrush(opt.palette.color(QPalette::Text)); - p.setOpacity(0.06); + p.setOpacity(0.04); p.setPen(Qt::NoPen); p.drawRoundedRect(rect, 0, 0); return QLabel::paintEvent(event); diff --git a/ukui-search.pro b/ukui-search.pro index 47447cd..f23af01 100644 --- a/ukui-search.pro +++ b/ukui-search.pro @@ -2,6 +2,7 @@ TEMPLATE = subdirs SUBDIRS += $$PWD/libchinese-segmentation \ $$PWD/libsearch \ $$PWD/src \ + $$PWD/frontend \ $$PWD/ukuisearch-systemdbus # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings @@ -17,5 +18,8 @@ DEFINES += QT_DEPRECATED_WARNINGS libsearch.depends += libchinese-segmentation src.depends = libsearch -CONFIG += ordered +CONFIG += ordered \ + qt + +QT += widgets