diff --git a/src/multizimbutton.cpp b/src/multizimbutton.cpp index 25b5379b..d14a8efd 100644 --- a/src/multizimbutton.cpp +++ b/src/multizimbutton.cpp @@ -1,8 +1,7 @@ #include #include #include -#include -#include +#include #include "kiwixapp.h" #include "multizimbutton.h" #include "css_constants.h" @@ -11,8 +10,7 @@ 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_buttonList(new QListWidget) { setMenu(new QMenu(this)); setPopupMode(QToolButton::InstantPopup); @@ -23,17 +21,82 @@ MultiZimButton::MultiZimButton(QWidget *parent) : 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); + connect(mp_buttonList, &QListWidget::itemActivated, this, [=](QListWidgetItem *item) { + if (const auto widget = getZimWidget(item)) + widget->toggle(); + }); + connect(mp_buttonList, &QListWidget::itemClicked, this, [=](QListWidgetItem *item) { + if (const auto widget = getZimWidget(item)) + widget->toggle(); }); } void MultiZimButton::updateDisplay() { + const auto library = KiwixApp::instance()->getLibrary(); + 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 (int row = 0; row < mp_buttonList->count(); row++) + { + const auto item = mp_buttonList->item(row); + const auto bookId = item->data(Qt::UserRole).toString(); + const QString bookTitle = QString::fromStdString(library->getBookById(bookId).getTitle()); + const QIcon zimIcon = library->getBookIcon(bookId); + item->setData(Qt::DisplayRole, bookTitle); + item->setSizeHint(QSize(0, itemHeight)); + + if (view && view->zimId() == bookId) + { + currentItem = item; + currentIcon = zimIcon; + continue; + } + + setItemZimWidget(item, bookTitle, zimIcon); + } + + mp_buttonList->sortItems(); + if (currentItem) + { + const auto checked = getZimWidget(currentItem)->isChecked(); + const auto title = currentItem->data(Qt::DisplayRole).toString(); + const auto currentRow = mp_buttonList->row(currentItem); + if (currentRow > 0) { + currentItem = mp_buttonList->takeItem(currentRow); + mp_buttonList->insertItem(0, currentItem); + currentItem = mp_buttonList->item(0); + setItemZimWidget(currentItem, "*" + title, currentIcon); + if (checked) + getZimWidget(currentItem)->setChecked(checked); + } + else { + setItemZimWidget(currentItem, "*" + title, currentIcon); + } + } + + /* 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); + + 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()); +} + +void MultiZimButton::updateBooks() +{ + auto checkedZimIds = getCheckedZimIds(); mp_buttonList->clear(); - for (const auto& button : mp_radioButtonGroup->buttons()) - mp_radioButtonGroup->removeButton(button); const auto library = KiwixApp::instance()->getLibrary(); const auto view = KiwixApp::instance()->getTabWidget()->currentWebView(); @@ -65,6 +128,10 @@ void MultiZimButton::updateDisplay() mp_buttonList->addItem(item); setItemZimWidget(item, bookTitle, zimIcon); + if (checkedZimIds.contains(bookId)) { + checkedZimIds.removeOne(bookId); + getZimWidget(item)->setChecked(true); + } } mp_buttonList->sortItems(); @@ -77,6 +144,7 @@ void MultiZimButton::updateDisplay() } /* 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()); @@ -86,6 +154,7 @@ void MultiZimButton::updateDisplay() 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()); @@ -94,10 +163,30 @@ void MultiZimButton::updateDisplay() QStringList MultiZimButton::getZimIds() const { QStringList idList; + bool someChecked = false; for (int row = 0; row < mp_buttonList->count(); row++) { const auto widget = getZimWidget(row); - if (widget && widget->getRadioButton()->isChecked()) + if (widget && widget->isChecked()) { + if (!someChecked) { + someChecked = true; + idList.clear(); + } + idList.append(mp_buttonList->item(row)->data(Qt::UserRole).toString()); + } + else if (!someChecked) + idList.append(mp_buttonList->item(row)->data(Qt::UserRole).toString()); + } + return idList; +} + +QStringList MultiZimButton::getCheckedZimIds() const +{ + QStringList idList; + for (int row = 0; row < mp_buttonList->count(); row++) + { + const auto widget = getZimWidget(row); + if (widget && widget->isChecked()) idList.append(mp_buttonList->item(row)->data(Qt::UserRole).toString()); } return idList; @@ -109,19 +198,30 @@ ZimItemWidget *MultiZimButton::getZimWidget(int row) const return qobject_cast(widget); } +ZimItemWidget *MultiZimButton::getZimWidget(QListWidgetItem *item) const +{ + const auto widget = mp_buttonList->itemWidget(item); + return qobject_cast(widget); +} + void MultiZimButton::setItemZimWidget(QListWidgetItem *item, const QString &title, const QIcon &icon) { + const auto oldWidget = getZimWidget(item); + const auto checked = oldWidget + ? oldWidget->isChecked() + : false; const auto zimWidget = new ZimItemWidget(title, icon); - mp_radioButtonGroup->addButton(zimWidget->getRadioButton()); mp_buttonList->setItemWidget(item, zimWidget); + if (checked) + zimWidget->setChecked(true); } ZimItemWidget::ZimItemWidget(QString text, QIcon icon, QWidget *parent) : QWidget(parent), textLabel(new QLabel(this)), iconLabel(new QLabel(this)), - radioBt(new QRadioButton(this)) + checkBx(new QCheckBox(this)) { setLayout(new QHBoxLayout); @@ -163,5 +263,24 @@ ZimItemWidget::ZimItemWidget(QString text, QIcon icon, QWidget *parent) : layout()->addWidget(iconLabel); layout()->addWidget(textLabel); - layout()->addWidget(radioBt); + layout()->addWidget(checkBx); +} + +bool ZimItemWidget::isChecked() const { + const auto cb = getCheckBox(); + if (cb) + return cb->isChecked(); + return false; +} + +void ZimItemWidget::setChecked(bool checked) { + const auto cb = getCheckBox(); + if (cb) + cb->setChecked(checked); +} + +void ZimItemWidget::toggle() { + const auto cb = getCheckBox(); + if (cb) + cb->toggle(); } diff --git a/src/multizimbutton.h b/src/multizimbutton.h index 0f20ffeb..6dde3113 100644 --- a/src/multizimbutton.h +++ b/src/multizimbutton.h @@ -6,7 +6,7 @@ class QListWidget; class QButtonGroup; class QListWidgetItem; -class QRadioButton; +class QCheckBox; class QLabel; class ZimItemWidget : public QWidget { @@ -15,12 +15,17 @@ class ZimItemWidget : public QWidget { public: ZimItemWidget(QString text, QIcon icon, QWidget *parent = nullptr); - QRadioButton* getRadioButton() const { return radioBt; } + QCheckBox* getCheckBox() const { return checkBx; } + bool isChecked() const; + +public slots: + void setChecked(bool checked); + void toggle(); private: QLabel* textLabel; QLabel* iconLabel; - QRadioButton* radioBt; + QCheckBox* checkBx; }; class MultiZimButton : public QToolButton { @@ -31,13 +36,15 @@ class MultiZimButton : public QToolButton { public slots: void updateDisplay(); + void updateBooks(); QStringList getZimIds() const; + QStringList getCheckedZimIds() const; private: QListWidget* mp_buttonList; - QButtonGroup* mp_radioButtonGroup; ZimItemWidget* getZimWidget(int row) const; + ZimItemWidget* getZimWidget(QListWidgetItem *item) const; void setItemZimWidget(QListWidgetItem* item, const QString& title, const QIcon& icon); }; diff --git a/src/searchbar.cpp b/src/searchbar.cpp index 3ecd1834..f1b97239 100644 --- a/src/searchbar.cpp +++ b/src/searchbar.cpp @@ -437,7 +437,7 @@ SearchBar::SearchBar(QWidget *parent) : &BookmarkButton::update_display); connect(KiwixApp::instance()->getContentManager(), &ContentManager::booksChanged, &m_multiZimButton, - &MultiZimButton::updateDisplay); + &MultiZimButton::updateBooks); connect(this, &SearchBar::currentTitleChanged, &m_multiZimButton, &MultiZimButton::updateDisplay); } diff --git a/src/suggestionlistworker.cpp b/src/suggestionlistworker.cpp index 5f328b04..913d81a3 100644 --- a/src/suggestionlistworker.cpp +++ b/src/suggestionlistworker.cpp @@ -18,42 +18,43 @@ void SuggestionListWorker::run() const auto selectedIdList = app->getSearchBar().getMultiZimButton().getZimIds(); /* TODO: re-implement this after introducing the actual Multi-Zim. */ - const auto currentZimId = selectedIdList[0]; - try { - const auto archive = app->getLibrary()->getArchive(currentZimId); - QUrl url; - url.setScheme("zim"); - url.setHost(currentZimId + ".zim"); - auto prefix = m_text.toStdString(); - auto suggestionSearcher = zim::SuggestionSearcher(*archive); - auto suggestionSearch = suggestionSearcher.suggest(prefix); - const auto suggestions = suggestionSearch.getResults(m_start, getFetchSize()); - for (auto current : suggestions) { - QString path = QString("/") + QString::fromStdString(current.getPath()); - url.setPath(path); - const auto text = QString::fromStdString(current.getTitle()); - suggestionList.append({text, url}); - } + for (const auto ¤tZimId : selectedIdList) { + try { + const auto archive = app->getLibrary()->getArchive(currentZimId); + QUrl url; + url.setScheme("zim"); + url.setHost(currentZimId + ".zim"); + auto prefix = m_text.toStdString(); + auto suggestionSearcher = zim::SuggestionSearcher(*archive); + auto suggestionSearch = suggestionSearcher.suggest(prefix); + const auto suggestions = suggestionSearch.getResults(m_start, getFetchSize()); + for (auto current : suggestions) { + QString path = QString("/") + QString::fromStdString(current.getPath()); + url.setPath(path); + const auto text = QString::fromStdString(current.getTitle()); + suggestionList.append({text, url}); + } - // Propose fulltext search - url.setPath(""); - if (archive->hasFulltextIndex()) { - // The host is used to determine the currentZimId - // The content query item is used to know in which zim search (as for kiwix-serve) - url.setHost(currentZimId + ".search"); - QUrlQuery query; - query.addQueryItem("content", currentZimId); - query.addQueryItem("pattern", m_text); - url.setQuery(query); - const auto text = m_text + " (" + gt("fulltext-search") + ")"; - suggestionList.append({text, url}); + // Propose fulltext search + url.setPath(""); + if (archive->hasFulltextIndex()) { + // The host is used to determine the currentZimId + // The content query item is used to know in which zim search (as for kiwix-serve) + url.setHost(currentZimId + ".search"); + QUrlQuery query; + query.addQueryItem("content", currentZimId); + query.addQueryItem("pattern", m_text); + url.setQuery(query); + const auto text = m_text + " (" + gt("fulltext-search") + ")"; + suggestionList.append({text, url}); + } + } catch (std::out_of_range& e) { + // Impossible to find the requested archive (bug ?) + // We could propose a suggestion to do multi-zim search with: + // url.setHost("library.search"); + // but we don't have a correct UI to select on which zim search, how to display results, ... + // So do nothing for now } - } catch (std::out_of_range& e) { - // Impossible to find the requested archive (bug ?) - // We could propose a suggestion to do multi-zim search with: - // url.setHost("library.search"); - // but we don't have a correct UI to select on which zim search, how to display results, ... - // So do nothing for now } emit(searchFinished(suggestionList, m_token)); }