From f5e8f22eac99484b2613a8e6f34c665984225109 Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Mon, 28 Oct 2024 18:16:02 -0400 Subject: [PATCH 01/13] Introduce MultiZim Search Button User can search zim other than the current tab. --- kiwix-desktop.pro | 2 + src/multizimbutton.cpp | 75 ++++++++++++++++++++++++++++++++++++ src/multizimbutton.h | 24 ++++++++++++ src/searchbar.cpp | 9 ++++- src/searchbar.h | 3 ++ src/suggestionlistworker.cpp | 12 +++--- 6 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 src/multizimbutton.cpp create mode 100644 src/multizimbutton.h diff --git a/kiwix-desktop.pro b/kiwix-desktop.pro index 9fb0c123e..8d370af09 100644 --- a/kiwix-desktop.pro +++ b/kiwix-desktop.pro @@ -91,6 +91,7 @@ SOURCES += \ src/fullscreenwindow.cpp \ src/fullscreennotification.cpp \ src/zimview.cpp \ + src/multizimbutton.cpp \ HEADERS += \ src/choiceitem.h \ @@ -143,6 +144,7 @@ HEADERS += \ src/zimview.h \ src/portutils.h \ src/css_constants.h \ + src/multizimbutton.h \ FORMS += \ src/choiceitem.ui \ diff --git a/src/multizimbutton.cpp b/src/multizimbutton.cpp new file mode 100644 index 000000000..10850e570 --- /dev/null +++ b/src/multizimbutton.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include "kiwixapp.h" +#include "multizimbutton.h" + +MultiZimButton::MultiZimButton(QWidget *parent) : + QToolButton(parent), + mp_buttonList(new QListWidget), + mp_radioButtonGroup(new QButtonGroup(this)) +{ + setMenu(new QMenu(this)); + setPopupMode(QToolButton::InstantPopup); + + const auto popupAction = new QWidgetAction(menu()); + popupAction->setDefaultWidget(mp_buttonList); + menu()->addAction(popupAction); +} + +void MultiZimButton::updateDisplay() +{ + mp_buttonList->clear(); + for (const auto& button : mp_radioButtonGroup->buttons()) + mp_radioButtonGroup->removeButton(button); + + const auto library = KiwixApp::instance()->getLibrary(); + for (const auto& bookId : library->getBookIds()) + { + try + { + library->getArchive(bookId); + } catch (...) { continue; } + + const QString bookTitle = QString::fromStdString(library->getBookById(bookId).getTitle()); + const QIcon zimIcon = library->getBookIcon(bookId); + + const auto item = new QListWidgetItem(); + item->setData(Qt::UserRole, bookId); + item->setData(Qt::DisplayRole, bookTitle); + + const auto radioBt = new QRadioButton(bookTitle); + radioBt->setIcon(zimIcon); + mp_radioButtonGroup->addButton(radioBt); + + mp_buttonList->addItem(item); + mp_buttonList->setItemWidget(item, radioBt); + } + + mp_buttonList->sortItems(); + + /* Display should not be used other than for sorting. */ + for (int i = 0; i < mp_buttonList->count(); i++) + mp_buttonList->item(i)->setData(Qt::DisplayRole, QVariant()); + + setDisabled(mp_buttonList->model()->rowCount() == 0); + + const auto firstWidget = mp_buttonList->itemWidget(mp_buttonList->item(0)); + if (const auto firstBt = qobject_cast(firstWidget)) + firstBt->setChecked(true); +} + +QStringList MultiZimButton::getZimIds() const +{ + QStringList idList; + for (int row = 0; row < mp_buttonList->count(); row++) + { + const auto listItem = mp_buttonList->item(row); + const auto radioBt = qobject_cast(mp_buttonList->itemWidget(listItem)); + if (radioBt && radioBt->isChecked()) + idList.append(listItem->data(Qt::UserRole).toString()); + } + return idList; +} diff --git a/src/multizimbutton.h b/src/multizimbutton.h new file mode 100644 index 000000000..e21b4235f --- /dev/null +++ b/src/multizimbutton.h @@ -0,0 +1,24 @@ +#ifndef MULTIZIMBUTTON_H +#define MULTIZIMBUTTON_H + +#include + +class QListWidget; +class QButtonGroup; + +class MultiZimButton : public QToolButton { + Q_OBJECT + +public: + explicit MultiZimButton(QWidget *parent = nullptr); + +public slots: + void updateDisplay(); + QStringList getZimIds() const; + +private: + QListWidget* mp_buttonList; + QButtonGroup* mp_radioButtonGroup; +}; + +#endif // MULTIZIMBUTTON_H diff --git a/src/searchbar.cpp b/src/searchbar.cpp index e1725c652..050edccdf 100644 --- a/src/searchbar.cpp +++ b/src/searchbar.cpp @@ -404,7 +404,8 @@ QRect SearchBarLineEdit::getCompleterRect() const SearchBar::SearchBar(QWidget *parent) : QToolBar(parent), m_searchBarLineEdit(this), - m_bookmarkButton(this) + m_bookmarkButton(this), + m_multiZimButton(this) { QLabel* searchIconLabel = new QLabel; searchIconLabel->setObjectName("searchIcon"); @@ -415,9 +416,15 @@ SearchBar::SearchBar(QWidget *parent) : addWidget(searchIconLabel); addWidget(&m_searchBarLineEdit); addWidget(&m_bookmarkButton); + addWidget(&m_multiZimButton); connect(this, &SearchBar::currentTitleChanged, &m_searchBarLineEdit, &SearchBarLineEdit::on_currentTitleChanged); connect(this, &SearchBar::currentTitleChanged, &m_bookmarkButton, &BookmarkButton::update_display); + connect(KiwixApp::instance()->getContentManager(), + &ContentManager::booksChanged, &m_multiZimButton, + &MultiZimButton::updateDisplay); + connect(this, &SearchBar::currentTitleChanged, &m_multiZimButton, + &MultiZimButton::updateDisplay); } diff --git a/src/searchbar.h b/src/searchbar.h index 516f5eb05..e79e0d7c6 100644 --- a/src/searchbar.h +++ b/src/searchbar.h @@ -11,6 +11,7 @@ #include #include #include "suggestionlistmodel.h" +#include "multizimbutton.h" class QTreeView; @@ -77,6 +78,7 @@ class SearchBar : public QToolBar { public: SearchBar(QWidget *parent = nullptr); SearchBarLineEdit& getLineEdit() { return m_searchBarLineEdit; }; + MultiZimButton& getMultiZimButton() { return m_multiZimButton; }; signals: void currentTitleChanged(const QString &title); @@ -84,5 +86,6 @@ class SearchBar : public QToolBar { private: SearchBarLineEdit m_searchBarLineEdit; BookmarkButton m_bookmarkButton; + MultiZimButton m_multiZimButton; }; #endif // SEARCHBAR_H diff --git a/src/suggestionlistworker.cpp b/src/suggestionlistworker.cpp index 9230ef0a4..5f328b046 100644 --- a/src/suggestionlistworker.cpp +++ b/src/suggestionlistworker.cpp @@ -14,13 +14,13 @@ void SuggestionListWorker::run() { QList suggestionList; - WebView *current = KiwixApp::instance()->getTabWidget()->currentWebView(); - if(!current) - return; - auto qurl = current->url(); - auto currentZimId = qurl.host().split(".")[0]; + const auto app = KiwixApp::instance(); + const auto selectedIdList = app->getSearchBar().getMultiZimButton().getZimIds(); + + /* TODO: re-implement this after introducing the actual Multi-Zim. */ + const auto currentZimId = selectedIdList[0]; try { - auto archive = KiwixApp::instance()->getLibrary()->getArchive(currentZimId); + const auto archive = app->getLibrary()->getArchive(currentZimId); QUrl url; url.setScheme("zim"); url.setHost(currentZimId + ".zim"); From 85bbd9121613d121c3fa06f4bf81e3fa2eb91f86 Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Thu, 31 Oct 2024 21:39:46 -0400 Subject: [PATCH 02/13] Introduce KiwixApp::OpenMultiZimAction Shortcut and Icon for MultiZim. Since window is popup, toggle is not possible. --- resources/i18n/en.json | 3 ++- resources/i18n/qqq.json | 3 ++- src/kiwixapp.cpp | 2 ++ src/kiwixapp.h | 1 + src/mainmenu.cpp | 1 + src/multizimbutton.cpp | 2 ++ 6 files changed, 10 insertions(+), 2 deletions(-) diff --git a/resources/i18n/en.json b/resources/i18n/en.json index 9dfc1d69c..b884f1293 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -172,5 +172,6 @@ "portable-disabled-tooltip": "Function disabled in portable mode", "scroll-next-tab": "Scroll to next tab", "scroll-previous-tab": "Scroll to previous tab", - "kiwix-search": "Kiwix search" + "kiwix-search": "Kiwix search", + "search-options": "Search Options" } diff --git a/resources/i18n/qqq.json b/resources/i18n/qqq.json index 55846a1b1..2fdc8c7b8 100644 --- a/resources/i18n/qqq.json +++ b/resources/i18n/qqq.json @@ -180,5 +180,6 @@ "portable-disabled-tooltip": "Tooltip used to explain disabled components in the portable version.", "scroll-next-tab": "Represents the action of scrolling to the next tab of the current tab which toward the end of the tab bar.", "scroll-previous-tab": "Represents the action of scrolling to the previous tab of the current tab which toward the start of the tab bar.", - "kiwix-search": "Title text for a list of search results, which notes to the user those are from Kiwix's Search Engine" + "kiwix-search": "Title text for a list of search results, which notes to the user those are from Kiwix's Search Engine", + "search-options": "The term for a collection of additional search filtering options to help users narrow down search results." } diff --git a/src/kiwixapp.cpp b/src/kiwixapp.cpp index f9be1da4d..c1b868932 100644 --- a/src/kiwixapp.cpp +++ b/src/kiwixapp.cpp @@ -438,6 +438,8 @@ void KiwixApp::createActions() CREATE_ACTION_SHORTCUT(ToggleTOCAction, gt("table-of-content"), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_1)); HIDE_ACTION(ToggleTOCAction); + CREATE_ACTION_ICON_SHORTCUT(OpenMultiZimAction, "filter", gt("search-options"), QKeySequence(Qt::CTRL | Qt::Key_M)); + CREATE_ACTION_ONOFF_ICON_SHORTCUT(ToggleReadingListAction, "reading-list-active", "reading-list", gt("reading-list"), QKeySequence(Qt::CTRL | Qt::Key_B)); CREATE_ACTION(ExportReadingListAction, gt("export-reading-list")); diff --git a/src/kiwixapp.h b/src/kiwixapp.h index fa422fc23..d8524c024 100644 --- a/src/kiwixapp.h +++ b/src/kiwixapp.h @@ -39,6 +39,7 @@ class KiwixApp : public QtSingleApplication BrowseLibraryAction, OpenFileAction, OpenRecentAction, + OpenMultiZimAction, SavePageAsAction, SearchArticleAction, SearchLibraryAction, diff --git a/src/mainmenu.cpp b/src/mainmenu.cpp index 07666716b..1d550914b 100644 --- a/src/mainmenu.cpp +++ b/src/mainmenu.cpp @@ -35,6 +35,7 @@ MainMenu::MainMenu(QWidget *parent) : m_editMenu.ADD_ACTION(SearchLibraryAction); m_editMenu.ADD_ACTION(FindInPageAction); m_editMenu.ADD_ACTION(ToggleAddBookmarkAction); + m_editMenu.ADD_ACTION(OpenMultiZimAction); addMenu(&m_editMenu); m_viewMenu.setTitle(gt("view")); diff --git a/src/multizimbutton.cpp b/src/multizimbutton.cpp index 10850e570..7341670c3 100644 --- a/src/multizimbutton.cpp +++ b/src/multizimbutton.cpp @@ -13,6 +13,8 @@ MultiZimButton::MultiZimButton(QWidget *parent) : { setMenu(new QMenu(this)); setPopupMode(QToolButton::InstantPopup); + setDefaultAction(KiwixApp::instance()->getAction(KiwixApp::OpenMultiZimAction)); + connect(this, &QToolButton::triggered, this, &MultiZimButton::showMenu); const auto popupAction = new QWidgetAction(menu()); popupAction->setDefaultWidget(mp_buttonList); From 125d497aa540814ea04918cf790ba44853f536ae Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Mon, 28 Oct 2024 22:50:18 -0400 Subject: [PATCH 03/13] Introduce ZimItemWidget to Delegate Item Display Decouple complex item creation from button. Used later for styling. --- src/multizimbutton.cpp | 53 ++++++++++++++++++++++++++++++++---------- src/multizimbutton.h | 20 ++++++++++++++++ 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/multizimbutton.cpp b/src/multizimbutton.cpp index 7341670c3..f70147e38 100644 --- a/src/multizimbutton.cpp +++ b/src/multizimbutton.cpp @@ -42,12 +42,8 @@ void MultiZimButton::updateDisplay() item->setData(Qt::UserRole, bookId); item->setData(Qt::DisplayRole, bookTitle); - const auto radioBt = new QRadioButton(bookTitle); - radioBt->setIcon(zimIcon); - mp_radioButtonGroup->addButton(radioBt); - mp_buttonList->addItem(item); - mp_buttonList->setItemWidget(item, radioBt); + setItemZimWidget(item, bookTitle, zimIcon); } mp_buttonList->sortItems(); @@ -58,9 +54,8 @@ void MultiZimButton::updateDisplay() setDisabled(mp_buttonList->model()->rowCount() == 0); - const auto firstWidget = mp_buttonList->itemWidget(mp_buttonList->item(0)); - if (const auto firstBt = qobject_cast(firstWidget)) - firstBt->setChecked(true); + if (const auto firstWidget = getZimWidget(0)) + firstWidget->getRadioButton()->setChecked(true); } QStringList MultiZimButton::getZimIds() const @@ -68,10 +63,44 @@ QStringList MultiZimButton::getZimIds() const QStringList idList; for (int row = 0; row < mp_buttonList->count(); row++) { - const auto listItem = mp_buttonList->item(row); - const auto radioBt = qobject_cast(mp_buttonList->itemWidget(listItem)); - if (radioBt && radioBt->isChecked()) - idList.append(listItem->data(Qt::UserRole).toString()); + const auto widget = getZimWidget(row); + if (widget && widget->getRadioButton()->isChecked()) + idList.append(mp_buttonList->item(row)->data(Qt::UserRole).toString()); } return idList; } + +ZimItemWidget *MultiZimButton::getZimWidget(int row) const +{ + const auto widget = mp_buttonList->itemWidget(mp_buttonList->item(row)); + return qobject_cast(widget); +} + +void MultiZimButton::setItemZimWidget(QListWidgetItem *item, + const QString &title, const QIcon &icon) +{ + const auto zimWidget = new ZimItemWidget(title, icon); + mp_radioButtonGroup->addButton(zimWidget->getRadioButton()); + mp_buttonList->setItemWidget(item, zimWidget); +} + +ZimItemWidget::ZimItemWidget(QString text, QIcon icon, QWidget *parent) : + QWidget(parent), + textLabel(new QLabel(this)), + iconLabel(new QLabel(this)), + radioBt(new QRadioButton(this)) +{ + setLayout(new QHBoxLayout); + layout()->setSpacing(0); + layout()->setContentsMargins(0, 0, 0, 0); + + textLabel->setText(text); + + /* TODO: change temporary values once size is defined laters */ + const QSize iconSize = QSize(24, 24); + iconLabel->setPixmap(icon.pixmap(iconSize)); + + layout()->addWidget(iconLabel); + layout()->addWidget(textLabel); + layout()->addWidget(radioBt); +} diff --git a/src/multizimbutton.h b/src/multizimbutton.h index e21b4235f..0f20ffeb8 100644 --- a/src/multizimbutton.h +++ b/src/multizimbutton.h @@ -5,6 +5,23 @@ class QListWidget; class QButtonGroup; +class QListWidgetItem; +class QRadioButton; +class QLabel; + +class ZimItemWidget : public QWidget { + Q_OBJECT + +public: + ZimItemWidget(QString text, QIcon icon, QWidget *parent = nullptr); + + QRadioButton* getRadioButton() const { return radioBt; } + +private: + QLabel* textLabel; + QLabel* iconLabel; + QRadioButton* radioBt; +}; class MultiZimButton : public QToolButton { Q_OBJECT @@ -19,6 +36,9 @@ public slots: private: QListWidget* mp_buttonList; QButtonGroup* mp_radioButtonGroup; + + ZimItemWidget* getZimWidget(int row) const; + void setItemZimWidget(QListWidgetItem* item, const QString& title, const QIcon& icon); }; #endif // MULTIZIMBUTTON_H From 4813de2c1603a2150eba6680effb9d45efd86fca Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Mon, 28 Oct 2024 22:57:19 -0400 Subject: [PATCH 04/13] LineEdit Should PopupFocus Missing this reason cause completer to do nothing --- src/searchbar.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/searchbar.cpp b/src/searchbar.cpp index 050edccdf..fdf835242 100644 --- a/src/searchbar.cpp +++ b/src/searchbar.cpp @@ -195,7 +195,8 @@ void SearchBarLineEdit::focusInEvent( QFocusEvent* event) } if (event->reason() == Qt::ActiveWindowFocusReason || event->reason() == Qt::MouseFocusReason || - event->reason() == Qt::ShortcutFocusReason) { + event->reason() == Qt::ShortcutFocusReason || + event->reason() == Qt::PopupFocusReason) { connect(&m_completer, QOverload::of(&QCompleter::activated), this, &QLineEdit::setText,Qt::UniqueConnection); From 02f7b99f9ee48876707624a47edae323ed29ae1e Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Wed, 30 Oct 2024 00:34:53 -0400 Subject: [PATCH 05/13] MultiZim Auto-Select Current Zim --- src/multizimbutton.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/multizimbutton.cpp b/src/multizimbutton.cpp index f70147e38..be7761be5 100644 --- a/src/multizimbutton.cpp +++ b/src/multizimbutton.cpp @@ -28,6 +28,9 @@ void MultiZimButton::updateDisplay() mp_radioButtonGroup->removeButton(button); const auto library = KiwixApp::instance()->getLibrary(); + const auto view = KiwixApp::instance()->getTabWidget()->currentWebView(); + QListWidgetItem* currentItem = nullptr; + QIcon currentIcon; for (const auto& bookId : library->getBookIds()) { try @@ -42,11 +45,25 @@ void MultiZimButton::updateDisplay() item->setData(Qt::UserRole, bookId); item->setData(Qt::DisplayRole, bookTitle); + if (view && view->zimId() == bookId) + { + currentItem = item; + currentIcon = zimIcon; + continue; + } + mp_buttonList->addItem(item); setItemZimWidget(item, bookTitle, zimIcon); } mp_buttonList->sortItems(); + if (currentItem) + { + mp_buttonList->insertItem(0, currentItem); + + const auto title = currentItem->data(Qt::DisplayRole).toString(); + setItemZimWidget(currentItem, "*" + title, currentIcon); + } /* Display should not be used other than for sorting. */ for (int i = 0; i < mp_buttonList->count(); i++) @@ -54,6 +71,7 @@ void MultiZimButton::updateDisplay() setDisabled(mp_buttonList->model()->rowCount() == 0); + mp_buttonList->scrollToTop(); if (const auto firstWidget = getZimWidget(0)) firstWidget->getRadioButton()->setChecked(true); } From 80ea956626f6b45326039dda3c3ba4d4b2d8f755 Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Wed, 30 Oct 2024 01:22:44 -0400 Subject: [PATCH 06/13] Check MultiZim Item When Clicking Anywhere Previously only checks when clicking Radiobutton --- src/multizimbutton.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/multizimbutton.cpp b/src/multizimbutton.cpp index be7761be5..9d7c5e5ed 100644 --- a/src/multizimbutton.cpp +++ b/src/multizimbutton.cpp @@ -19,6 +19,11 @@ MultiZimButton::MultiZimButton(QWidget *parent) : const auto popupAction = new QWidgetAction(menu()); popupAction->setDefaultWidget(mp_buttonList); menu()->addAction(popupAction); + + connect(mp_buttonList, &QListWidget::currentRowChanged, this, [=](int row){ + if (const auto widget = getZimWidget(row)) + widget->getRadioButton()->setChecked(true); + }); } void MultiZimButton::updateDisplay() @@ -72,8 +77,7 @@ void MultiZimButton::updateDisplay() setDisabled(mp_buttonList->model()->rowCount() == 0); mp_buttonList->scrollToTop(); - if (const auto firstWidget = getZimWidget(0)) - firstWidget->getRadioButton()->setChecked(true); + mp_buttonList->setCurrentRow(0); } QStringList MultiZimButton::getZimIds() const From 3c1753e4fee6ebded47bc8d7a5e26017afc5a1d1 Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Wed, 30 Oct 2024 01:18:13 -0400 Subject: [PATCH 07/13] Proper MultiZim List Sizing Set height&padding for list&items and stretch to menu width. --- resources/css/style.css | 12 ++++++++++++ src/css_constants.h | 12 ++++++++++++ src/multizimbutton.cpp | 13 +++++++++++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/resources/css/style.css b/resources/css/style.css index ee91655ef..b7e21d671 100644 --- a/resources/css/style.css +++ b/resources/css/style.css @@ -75,6 +75,18 @@ SearchBar > QToolButton:hover { border-radius: 3px; } +MultiZimButton QListWidget { + border: 0px; + outline: 0px; + padding: 5px 0px; /* XXX: duplicated in css_constants.h */ + background-color: white; +} + +ZimItemWidget QLabel { + font-size: 16px; + line-height: 24px; /* XXX: duplicated in css_constants.h */ +} + TopWidget QToolButton:pressed, TopWidget QToolButton::hover { border: 1px solid #3366CC; diff --git a/src/css_constants.h b/src/css_constants.h index dba10b0b2..2df0acf48 100644 --- a/src/css_constants.h +++ b/src/css_constants.h @@ -28,6 +28,18 @@ namespace SearchBar{ const int border = 1; } +namespace MultiZimButton { +namespace QListWidget { + const int paddingVertical = 5; +} +} + +namespace ZimItemWidget { +namespace QLabel { + const int lineHeight = 24; +} +} + namespace TopWidget { namespace QToolButton { namespace backButton { diff --git a/src/multizimbutton.cpp b/src/multizimbutton.cpp index 9d7c5e5ed..869c38200 100644 --- a/src/multizimbutton.cpp +++ b/src/multizimbutton.cpp @@ -5,6 +5,7 @@ #include #include "kiwixapp.h" #include "multizimbutton.h" +#include "css_constants.h" MultiZimButton::MultiZimButton(QWidget *parent) : QToolButton(parent), @@ -36,6 +37,8 @@ void MultiZimButton::updateDisplay() const auto view = KiwixApp::instance()->getTabWidget()->currentWebView(); QListWidgetItem* currentItem = nullptr; QIcon currentIcon; + const int paddingTopBot = CSS::MultiZimButton::QListWidget::paddingVertical * 2; + const int itemHeight = paddingTopBot + CSS::ZimItemWidget::QLabel::lineHeight; for (const auto& bookId : library->getBookIds()) { try @@ -49,6 +52,7 @@ void MultiZimButton::updateDisplay() const auto item = new QListWidgetItem(); item->setData(Qt::UserRole, bookId); item->setData(Qt::DisplayRole, bookTitle); + item->setSizeHint(QSize(0, itemHeight)); if (view && view->zimId() == bookId) { @@ -78,6 +82,11 @@ void MultiZimButton::updateDisplay() mp_buttonList->scrollToTop(); mp_buttonList->setCurrentRow(0); + + /* We set a maximum display height for list. Respect padding. */ + const int listHeight = itemHeight * std::min(7, mp_buttonList->count()); + mp_buttonList->setFixedHeight(listHeight + paddingTopBot); + mp_buttonList->setFixedWidth(menu()->width()); } QStringList MultiZimButton::getZimIds() const @@ -118,8 +127,8 @@ ZimItemWidget::ZimItemWidget(QString text, QIcon icon, QWidget *parent) : textLabel->setText(text); - /* TODO: change temporary values once size is defined laters */ - const QSize iconSize = QSize(24, 24); + const int iconWidth = CSS::ZimItemWidget::QLabel::lineHeight; + const QSize iconSize = QSize(iconWidth, iconWidth); iconLabel->setPixmap(icon.pixmap(iconSize)); layout()->addWidget(iconLabel); From e3b0efeaa10e1bb4b36668965a0c793691256e72 Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Wed, 30 Oct 2024 03:12:09 -0400 Subject: [PATCH 08/13] Proper Styling for MultiZim List --- resources/css/style.css | 34 ++++++++++++++++++++++++++++++++++ src/css_constants.h | 8 ++++++++ src/multizimbutton.cpp | 32 +++++++++++++++++++++++++++++--- 3 files changed, 71 insertions(+), 3 deletions(-) diff --git a/resources/css/style.css b/resources/css/style.css index b7e21d671..e3b08c85a 100644 --- a/resources/css/style.css +++ b/resources/css/style.css @@ -82,11 +82,45 @@ MultiZimButton QListWidget { background-color: white; } +MultiZimButton QListWidget::item { + padding: 0px 5px; /* XXX: duplicated in css_constants.h */ + border: 1px solid transparent; /* XXX: duplicated in css_constants.h */ + background-color: white; +} + +MultiZimButton QListWidget::item:hover, +MultiZimButton QListWidget::item:selected:active { + border: 1px solid #3366CC; + background-color: #D9E9FF; +} + +MultiZimButton QScrollBar { + width: 5px; /* XXX: duplicated in css_constants.h */ + border: none; + outline: none; +} + +MultiZimButton QScrollBar::handle { + background-color: grey; +} + +ZimItemWidget * { + background-color: transparent; +} + ZimItemWidget QLabel { font-size: 16px; line-height: 24px; /* XXX: duplicated in css_constants.h */ } +ZimItemWidget QRadioButton::indicator { + image: none; +} + +ZimItemWidget QRadioButton::indicator:checked { + image: url(:/icons/tick.svg); +} + TopWidget QToolButton:pressed, TopWidget QToolButton::hover { border: 1px solid #3366CC; diff --git a/src/css_constants.h b/src/css_constants.h index 2df0acf48..337b0f63f 100644 --- a/src/css_constants.h +++ b/src/css_constants.h @@ -30,8 +30,16 @@ namespace SearchBar{ namespace MultiZimButton { namespace QListWidget { +namespace item { + const int paddingHorizontal = 5; + const int border = 1; +} const int paddingVertical = 5; } + +namespace QScrollBar { + const int width = 5; +} } namespace ZimItemWidget { diff --git a/src/multizimbutton.cpp b/src/multizimbutton.cpp index 869c38200..963d2ad12 100644 --- a/src/multizimbutton.cpp +++ b/src/multizimbutton.cpp @@ -122,15 +122,41 @@ ZimItemWidget::ZimItemWidget(QString text, QIcon icon, QWidget *parent) : radioBt(new QRadioButton(this)) { setLayout(new QHBoxLayout); - layout()->setSpacing(0); - layout()->setContentsMargins(0, 0, 0, 0); - textLabel->setText(text); + const int paddingHorizontal = CSS::MultiZimButton::QListWidget::item::paddingHorizontal; + layout()->setSpacing(paddingHorizontal); + layout()->setContentsMargins(0, 0, 0, 0); const int iconWidth = CSS::ZimItemWidget::QLabel::lineHeight; const QSize iconSize = QSize(iconWidth, iconWidth); iconLabel->setPixmap(icon.pixmap(iconSize)); + /* Align text on same side irregardless of text direction. */ + const bool needAlignReverse = KiwixApp::isRightToLeft() == text.isRightToLeft(); + const auto align = needAlignReverse ? Qt::AlignLeft : Qt::AlignRight; + textLabel->setAlignment({Qt::AlignVCenter | align}); + + /* Need to align checkmark with select all button. Avoid scroller from + changing checkmark position by always leaving out space on scroller's + side. Do this by align items to the other side and reducing the total + length. textLabel is the only expandable element here. + + We set textLabel width to make sure the entire length always leave out + a fixed amount of white space for scroller. + */ + layout()->setAlignment({Qt::AlignVCenter | Qt::AlignLeading}); + const auto menu = KiwixApp::instance()->getSearchBar().getMultiZimButton().menu(); + const int iconAndCheckerWidth = iconWidth * 2; + const int totalSpacing = paddingHorizontal * 4; + + /* Add an extra border to counteract item border on one side */ + const int border = CSS::MultiZimButton::QListWidget::item::border; + const int scrollerWidth = CSS::MultiZimButton::QScrollBar::width; + const int contentWidthExcludeText = iconAndCheckerWidth + totalSpacing + scrollerWidth + border; + + textLabel->setFixedWidth(menu->width() - contentWidthExcludeText); + textLabel->setText(text); + layout()->addWidget(iconLabel); layout()->addWidget(textLabel); layout()->addWidget(radioBt); From a5c4ed135bdab4761c7c20f10775111d63bebabf Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Sun, 3 Nov 2024 01:48:04 -0400 Subject: [PATCH 09/13] Introduce getElidedText() in suggestionlistdelegate.cpp Helper function to retrieve text content that can fit inside a length given a custom "(...)" elide. --- src/suggestionlistdelegate.cpp | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/suggestionlistdelegate.cpp b/src/suggestionlistdelegate.cpp index 954f7782e..8ff8fbd2a 100644 --- a/src/suggestionlistdelegate.cpp +++ b/src/suggestionlistdelegate.cpp @@ -44,6 +44,26 @@ void SuggestionListDelegate::paintIcon(QPainter *p, p->drawPixmap(pixmapRect, pixmap); } +/** + * @brief Get the elided text using font that can fit inside the length when + * appended with the custom elide text "(...)". + * + * @param font + * @param textRect + * @param text + * @return QString the elided text without any marker. + */ +QString getElidedText(const QFont& font, int length, const QString& text) +{ + const QFontMetrics metrics(font); + const int elideMarkerLength = metrics.tightBoundingRect("(...)").width(); + const int textLength = length - elideMarkerLength; + QString elidedText = metrics.elidedText(text, Qt::ElideRight, textLength); + if (elidedText != text) + return elidedText.chopped(1); + return text; +} + void SuggestionListDelegate::paintText(QPainter *p, const QStyleOptionViewItem &opt, const QModelIndex &index) const @@ -70,15 +90,9 @@ void SuggestionListDelegate::paintText(QPainter *p, const QString text = index.data(Qt::DisplayRole).toString(); /* Custom text elide. */ - const QFontMetrics metrics = opt.fontMetrics; - const int elideMarkerLength = metrics.tightBoundingRect("(...)").width(); - const int textLength = textRect.width() - elideMarkerLength; - QString elidedText = metrics.elidedText(text, Qt::ElideRight, textLength); + QString elidedText = getElidedText(opt.font, textRect.width(), text); if (elidedText != text) { - /* Remove built-in elide marker */ - elidedText.chop(1); - /* drawText's Align direction determines text direction */ const bool textDirFlipped = KiwixApp::isRightToLeft() != text.isRightToLeft(); elidedText = textDirFlipped ? "(...)" + elidedText.trimmed() From 80743cb11d41a92824c6ab004f5dd2a10a5bc702 Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Wed, 30 Oct 2024 03:16:13 -0400 Subject: [PATCH 10/13] Proper Elide for MultiZim List Add Custom elide marker --- src/multizimbutton.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/multizimbutton.cpp b/src/multizimbutton.cpp index 963d2ad12..25b5379b1 100644 --- a/src/multizimbutton.cpp +++ b/src/multizimbutton.cpp @@ -7,6 +7,8 @@ #include "multizimbutton.h" #include "css_constants.h" +QString getElidedText(const QFont& font, int length, const QString& text); + MultiZimButton::MultiZimButton(QWidget *parent) : QToolButton(parent), mp_buttonList(new QListWidget), @@ -153,9 +155,11 @@ ZimItemWidget::ZimItemWidget(QString text, QIcon icon, QWidget *parent) : const int border = CSS::MultiZimButton::QListWidget::item::border; const int scrollerWidth = CSS::MultiZimButton::QScrollBar::width; const int contentWidthExcludeText = iconAndCheckerWidth + totalSpacing + scrollerWidth + border; + const int labelWidth = menu->width() - contentWidthExcludeText; + textLabel->setFixedWidth(labelWidth); - textLabel->setFixedWidth(menu->width() - contentWidthExcludeText); - textLabel->setText(text); + QString elidedText = getElidedText(textLabel->font(), labelWidth, text); + textLabel->setText(elidedText == text ? text : elidedText.trimmed() + "(...)"); layout()->addWidget(iconLabel); layout()->addWidget(textLabel); From 7fe06f5738a9af940d3a44eb5db2afd5f872990f Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Thu, 31 Oct 2024 21:57:18 -0400 Subject: [PATCH 11/13] Allow Search On Any Tab. With MultiZim, searching on library, settings, and blank tabs are reasonable. --- src/searchbar.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/searchbar.cpp b/src/searchbar.cpp index fdf835242..54d0070f9 100644 --- a/src/searchbar.cpp +++ b/src/searchbar.cpp @@ -228,8 +228,8 @@ void SearchBarLineEdit::updateCompletion() { mp_typingTimer->stop(); clearSuggestions(); - WebView* current = KiwixApp::instance()->getTabWidget()->currentWebView(); - if (!current || current->url().isEmpty() || m_searchbarInput.isEmpty()) { + const auto& multiZim = KiwixApp::instance()->getSearchBar().getMultiZimButton(); + if (multiZim.getZimIds().isEmpty() || m_searchbarInput.isEmpty()) { hideSuggestions(); return; } @@ -302,7 +302,9 @@ void SearchBarLineEdit::openCompletion(const QModelIndex &index) if (index.isValid()) { const QUrl url = index.data(Qt::UserRole).toUrl(); - QTimer::singleShot(0, [=](){KiwixApp::instance()->openUrl(url, false);}); + const auto app = KiwixApp::instance(); + const bool newTab = app->getTabWidget()->currentWebView() == nullptr; + QTimer::singleShot(0, [=](){KiwixApp::instance()->openUrl(url, newTab);}); } } From fdfd0efca02f76127aa71f066649cc0c0b14d91b Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Sun, 3 Nov 2024 11:54:26 -0500 Subject: [PATCH 12/13] Introduce Select All Button to MultiZim --- resources/css/style.css | 26 ++++++++++++++++++++++++++ resources/i18n/en.json | 3 ++- resources/i18n/qqq.json | 3 ++- src/multizimbutton.cpp | 15 +++++++++++---- src/multizimbutton.h | 2 ++ 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/resources/css/style.css b/resources/css/style.css index e3b08c85a..f334bb2ee 100644 --- a/resources/css/style.css +++ b/resources/css/style.css @@ -121,6 +121,32 @@ ZimItemWidget QRadioButton::indicator:checked { image: url(:/icons/tick.svg); } +#selectAllButton { + background-color: white; + font-size: 16px; + line-height: 24px; + font-weight: 500; + padding: 5px 10px 10px; + outline: none; +} + +#selectAllButton::indicator { + width: 24px; + height: 24px; +} + +#selectAllButton::indicator:focus { + background-color: #D9E9FF; +} + +#selectAllButton::indicator:unchecked { + image: url(:/icons/checkbox.svg); +} + +#selectAllButton::indicator:checked { + image: url(:/icons/checkbox-active.svg); +} + TopWidget QToolButton:pressed, TopWidget QToolButton::hover { border: 1px solid #3366CC; diff --git a/resources/i18n/en.json b/resources/i18n/en.json index b884f1293..5fd9359d6 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -173,5 +173,6 @@ "scroll-next-tab": "Scroll to next tab", "scroll-previous-tab": "Scroll to previous tab", "kiwix-search": "Kiwix search", - "search-options": "Search Options" + "search-options": "Search Options", + "select-all": "Select all" } diff --git a/resources/i18n/qqq.json b/resources/i18n/qqq.json index 2fdc8c7b8..e9ea37bc0 100644 --- a/resources/i18n/qqq.json +++ b/resources/i18n/qqq.json @@ -181,5 +181,6 @@ "scroll-next-tab": "Represents the action of scrolling to the next tab of the current tab which toward the end of the tab bar.", "scroll-previous-tab": "Represents the action of scrolling to the previous tab of the current tab which toward the start of the tab bar.", "kiwix-search": "Title text for a list of search results, which notes to the user those are from Kiwix's Search Engine", - "search-options": "The term for a collection of additional search filtering options to help users narrow down search results." + "search-options": "The term for a collection of additional search filtering options to help users narrow down search results.", + "select-all": "Represents the action of selecting all items in a collection." } diff --git a/src/multizimbutton.cpp b/src/multizimbutton.cpp index 25b5379b1..c991866d8 100644 --- a/src/multizimbutton.cpp +++ b/src/multizimbutton.cpp @@ -12,16 +12,23 @@ QString getElidedText(const QFont& font, int length, const QString& text); MultiZimButton::MultiZimButton(QWidget *parent) : QToolButton(parent), mp_buttonList(new QListWidget), - mp_radioButtonGroup(new QButtonGroup(this)) + mp_radioButtonGroup(new QButtonGroup(this)), + mp_selectAllButton(new QCheckBox(gt("select-all"), this)) { setMenu(new QMenu(this)); setPopupMode(QToolButton::InstantPopup); setDefaultAction(KiwixApp::instance()->getAction(KiwixApp::OpenMultiZimAction)); connect(this, &QToolButton::triggered, this, &MultiZimButton::showMenu); - const auto popupAction = new QWidgetAction(menu()); - popupAction->setDefaultWidget(mp_buttonList); - menu()->addAction(popupAction); + mp_selectAllButton->setObjectName("selectAllButton"); + const auto align = KiwixApp::isRightToLeft() ? Qt::LeftToRight : Qt::RightToLeft; + mp_selectAllButton->setLayoutDirection(align); + + const auto buttonListAction = new QWidgetAction(menu()); + const auto selectAllAction = new QWidgetAction(menu()); + buttonListAction->setDefaultWidget(mp_buttonList); + selectAllAction->setDefaultWidget(mp_selectAllButton); + menu()->addActions({buttonListAction, selectAllAction}); connect(mp_buttonList, &QListWidget::currentRowChanged, this, [=](int row){ if (const auto widget = getZimWidget(row)) diff --git a/src/multizimbutton.h b/src/multizimbutton.h index 0f20ffeb8..7444ba3f4 100644 --- a/src/multizimbutton.h +++ b/src/multizimbutton.h @@ -8,6 +8,7 @@ class QButtonGroup; class QListWidgetItem; class QRadioButton; class QLabel; +class QCheckBox; class ZimItemWidget : public QWidget { Q_OBJECT @@ -36,6 +37,7 @@ public slots: private: QListWidget* mp_buttonList; QButtonGroup* mp_radioButtonGroup; + QCheckBox* mp_selectAllButton; ZimItemWidget* getZimWidget(int row) const; void setItemZimWidget(QListWidgetItem* item, const QString& title, const QIcon& icon); From cc81532d7691c0d7eefbe56a3f04db918237f147 Mon Sep 17 00:00:00 2001 From: ShaopengLin Date: Sun, 3 Nov 2024 11:58:19 -0500 Subject: [PATCH 13/13] MultiZim Select-All Button Key Press Compatible QCheckbox does not provide support out of the box for key press checking. --- src/multizimbutton.cpp | 12 +++++++++++- src/multizimbutton.h | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/multizimbutton.cpp b/src/multizimbutton.cpp index c991866d8..e1824c9a5 100644 --- a/src/multizimbutton.cpp +++ b/src/multizimbutton.cpp @@ -13,7 +13,7 @@ MultiZimButton::MultiZimButton(QWidget *parent) : QToolButton(parent), mp_buttonList(new QListWidget), mp_radioButtonGroup(new QButtonGroup(this)), - mp_selectAllButton(new QCheckBox(gt("select-all"), this)) + mp_selectAllButton(new SelectAllButton(gt("select-all"), this)) { setMenu(new QMenu(this)); setPopupMode(QToolButton::InstantPopup); @@ -172,3 +172,13 @@ ZimItemWidget::ZimItemWidget(QString text, QIcon icon, QWidget *parent) : layout()->addWidget(textLabel); layout()->addWidget(radioBt); } + +void SelectAllButton::keyPressEvent(QKeyEvent *e) +{ + if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) + { + toggle(); + return; + } + QCheckBox::keyPressEvent(e); +} diff --git a/src/multizimbutton.h b/src/multizimbutton.h index 7444ba3f4..74284e2a9 100644 --- a/src/multizimbutton.h +++ b/src/multizimbutton.h @@ -2,13 +2,23 @@ #define MULTIZIMBUTTON_H #include +#include class QListWidget; class QButtonGroup; class QListWidgetItem; class QRadioButton; class QLabel; -class QCheckBox; + +class SelectAllButton : public QCheckBox { + Q_OBJECT + +public: + SelectAllButton(const QString &text, QWidget *parent = nullptr) : QCheckBox(text, parent) {} + +protected: + void keyPressEvent(QKeyEvent *e) override; +}; class ZimItemWidget : public QWidget { Q_OBJECT @@ -37,7 +47,7 @@ public slots: private: QListWidget* mp_buttonList; QButtonGroup* mp_radioButtonGroup; - QCheckBox* mp_selectAllButton; + SelectAllButton* mp_selectAllButton; ZimItemWidget* getZimWidget(int row) const; void setItemZimWidget(QListWidgetItem* item, const QString& title, const QIcon& icon);