diff --git a/kiwix-desktop.pro b/kiwix-desktop.pro index 1e993414..32903431 100644 --- a/kiwix-desktop.pro +++ b/kiwix-desktop.pro @@ -34,13 +34,18 @@ DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ + src/choiceitem.cpp \ src/contentmanagerdelegate.cpp \ src/contentmanagerheader.cpp \ src/contentmanagermodel.cpp \ src/contenttypefilter.cpp \ src/descriptionnode.cpp \ src/findinpagebar.cpp \ + src/flowlayout.cpp \ + src/kiwixchoicebox.cpp \ src/kiwixconfirmbox.cpp \ + src/kiwixlineedit.cpp \ + src/kiwixlistwidget.cpp \ src/kiwixloader.cpp \ src/rownode.cpp \ src/suggestionlistworker.cpp \ @@ -74,6 +79,7 @@ SOURCES += \ src/zimview.cpp \ HEADERS += \ + src/choiceitem.h \ src/contentmanagerdelegate.h \ src/contentmanagerheader.h \ src/contentmanagermodel.h \ @@ -81,7 +87,11 @@ HEADERS += \ src/contenttypefilter.h \ src/descriptionnode.h \ src/findinpagebar.h \ + src/flowlayout.h \ + src/kiwixchoicebox.h \ src/kiwixconfirmbox.h \ + src/kiwixlineedit.h \ + src/kiwixlistwidget.h \ src/kiwixloader.h \ src/node.h \ src/rownode.h \ @@ -116,8 +126,10 @@ HEADERS += \ src/zimview.h \ FORMS += \ + src/choiceitem.ui \ src/contentmanagerview.ui \ src/findinpagebar.ui \ + ui/kiwixchoicebox.ui \ ui/kiwixconfirmbox.ui \ ui/mainwindow.ui \ ui/about.ui \ diff --git a/resources/css/_contentManager.css b/resources/css/_contentManager.css index 93e31adc..f2e2a3bb 100644 --- a/resources/css/_contentManager.css +++ b/resources/css/_contentManager.css @@ -1,3 +1,7 @@ +#contentmanagerside { + background-color: red; +} + QTreeView::branch:open:has-children { image: url(:/icons/caret-down-solid.svg); padding: 6px; @@ -64,14 +68,3 @@ QMenu::item { QMenu::item:selected { background-color: #cccccc; } - -QLineEdit { - font-family: 'Selawik'; - padding: 4px; - border: none; - border-bottom: 1px solid #cccccc; - color: #666666; - font-size: 16px; - height: 32px; - line-height: 24px; -} diff --git a/resources/css/choiceBox.css b/resources/css/choiceBox.css new file mode 100644 index 00000000..20d85dea --- /dev/null +++ b/resources/css/choiceBox.css @@ -0,0 +1,71 @@ +QListWidget::item { + padding: 0; + padding-top: 6px; + padding-bottom: 6px; + padding-left: 2px; +} + +QListWidget { + padding: 0; + border: 1px solid #ccc; +} + +QListWidget::item::selected { + color: #444444; + background-color: transparent; +} + +QLineEdit { + padding: 4px; + border: 0; +} + +#choiceLabel { + font-size: 16px; +} + +#currentChoices { + margin: 0; + padding: 0; + border: 1px solid #ccc; + border-radius: 1px; +} + +#closeButton { + margin: 0; + background: transparent; + color: #eaecf0; + font-size: 12px; +} + +ChoiceItem > QFrame { + border: 1px solid #ccc; + border-radius: 2px; + padding: 0; + background-color: #666; + padding-left: 2px; + padding-right: 2px; + margin:0; +} + +#itemLabel { + margin: 0; + height: 6px; + background-color: #666; + color: #eaecf0; + font-size: 13px; +} + +#clearButton { + background-color: white; + color: #3366cc; + padding: 4px; + font: bold; + font-size: 14px; + border-radius: 4px; +} + +#clearButton:hover { + background-color: #3366cc; + color: white; +} diff --git a/resources/css/contentmanagerside.css b/resources/css/contentmanagerside.css new file mode 100644 index 00000000..df16f702 --- /dev/null +++ b/resources/css/contentmanagerside.css @@ -0,0 +1,23 @@ +#searcher { + font-family: 'Selawik'; + padding: 4px; + border: none; + border-bottom: 1px solid #cccccc; + color: #666666; + font-size: 16px; + height: 32px; + line-height: 24px; +} + +QScrollArea { + border: 0; +} + +#allFileButton, #localFileButton, #contentTypeButton { + padding-left: 2px; +} + +#allFileButton::indicator, #localFileButton::indicator, #contentTypeButton::indicator { + height: 0; + width: 0; +} diff --git a/resources/i18n/en.json b/resources/i18n/en.json index bcae634b..6f493455 100644 --- a/resources/i18n/en.json +++ b/resources/i18n/en.json @@ -90,8 +90,8 @@ "fullscreen-notification":"You are now in full screen mode. Press ESC to quit!", "online-files":"Online Files", "local-files":"Local Files", - "browse-by-category":"Browse By Category", - "browse-by-language":"Browse By Language", + "category":"Category", + "language":"Language", "hide":"Hide", "open-in-browser":"Open in browser", "start-kiwix-server":"Start Kiwix Server", @@ -154,5 +154,12 @@ "couldnt-open-location-text": "Kiwix is not able to open folder {{FOLDER}}", "move-files-to-trash": "Move deleted files to trash", "move-files-to-trash-text": "This action will move the file to trash.", - "perma-delete-files-text": "This action will permanently delete the file." + "perma-delete-files-text": "This action will permanently delete the file.", + "clear-filter": "Clear the currently set filters", + "language-searcher-placeholder": "Filter language", + "category-searcher-placeholder": "Filter category", + "content-type-searcher-placeholder": "Filter content type", + "no-details": "Introduction only", + "no-pictures": "No Pictures", + "no-videos": "No Videos" } diff --git a/resources/icons/xmark-solid.svg b/resources/icons/xmark-solid.svg new file mode 100644 index 00000000..c693bd00 --- /dev/null +++ b/resources/icons/xmark-solid.svg @@ -0,0 +1 @@ + diff --git a/resources/kiwix.qrc b/resources/kiwix.qrc index c25bd435..05a2e32c 100644 --- a/resources/kiwix.qrc +++ b/resources/kiwix.qrc @@ -61,5 +61,6 @@ icons/caret-up-solid.svg icons/kiwix-logo.svg icons/check-solid.svg + icons/xmark-solid.svg diff --git a/resources/style.qrc b/resources/style.qrc index 565b93fa..bb0bef4f 100644 --- a/resources/style.qrc +++ b/resources/style.qrc @@ -4,5 +4,7 @@ css/popup.css css/localServer.css css/confirmBox.css + css/contentmanagerside.css + css/choiceBox.css diff --git a/src/choiceitem.cpp b/src/choiceitem.cpp new file mode 100644 index 00000000..e6346458 --- /dev/null +++ b/src/choiceitem.cpp @@ -0,0 +1,32 @@ +#include "choiceitem.h" +#include "ui_choiceitem.h" +#include +#include +#include "kiwixapp.h" + +ChoiceItem::ChoiceItem(QString key, QString value, QWidget *parent) : + QWidget(parent), + ui(new Ui::ChoiceItem), + m_key(key), + m_value(value) +{ + ui->setupUi(this); + this->setStyleSheet(KiwixApp::instance()->parseStyleFromFile(":/css/choiceBox.css")); + ui->itemLabel->setText(key); + ui->itemLabel->setToolTip(key); + connect(ui->closeButton, &QPushButton::clicked, [=](){ + emit(closeButtonClicked(ui->itemLabel->text())); + }); + ui->closeButton->setCursor(Qt::PointingHandCursor); +} + +ChoiceItem::~ChoiceItem() +{ + delete ui; +} + +void ChoiceItem::mousePressEvent(QMouseEvent *event) +{ + Q_UNUSED(event); + return; +} diff --git a/src/choiceitem.h b/src/choiceitem.h new file mode 100644 index 00000000..b03b11db --- /dev/null +++ b/src/choiceitem.h @@ -0,0 +1,32 @@ +#ifndef CHOICEITEM_H +#define CHOICEITEM_H + +#include + +namespace Ui { +class ChoiceItem; +} + +class ChoiceItem : public QWidget +{ + Q_OBJECT + +public: + explicit ChoiceItem(QString key, QString value, QWidget *parent = nullptr); + ~ChoiceItem(); + QString getKey() { return m_key; } + QString getValue() { return m_value; } + +protected: + void mousePressEvent(QMouseEvent *event) override; + +private: + Ui::ChoiceItem *ui; + QString m_key; + QString m_value; + +signals: + void closeButtonClicked(QString); +}; + +#endif // CHOICEITEM_H diff --git a/src/choiceitem.ui b/src/choiceitem.ui new file mode 100644 index 00000000..19aae85c --- /dev/null +++ b/src/choiceitem.ui @@ -0,0 +1,143 @@ + + + ChoiceItem + + + + 0 + 0 + 119 + 35 + + + + + 0 + 0 + + + + + 0 + 30 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + TextLabel + + + + + + + true + + + + 0 + 0 + + + + + 0 + 0 + + + + + 10 + 16777215 + + + + + + + + :/icons/xmark-solid.svg:/icons/xmark-solid.svg + + + + 10 + 20 + + + + true + + + + + + + + + + + + + diff --git a/src/contentmanager.cpp b/src/contentmanager.cpp index 1c454676..c71ef8e2 100644 --- a/src/contentmanager.cpp +++ b/src/contentmanager.cpp @@ -56,7 +56,9 @@ ContentManager::ContentManager(Library* library, kiwix::Downloader* downloader, treeView->setColumnWidth(5, 120); // TODO: set width for all columns based on viewport - setCurrentLanguage(QLocale().name().split("_").at(0)); + setCurrentLanguage(KiwixApp::instance()->getSettingsManager()->getLanguageList()); + setCurrentCategoryFilter(KiwixApp::instance()->getSettingsManager()->getCategoryList()); + setCurrentContentTypeFilter(KiwixApp::instance()->getSettingsManager()->getContentType()); connect(mp_library, &Library::booksChanged, this, [=]() {emit(this->booksChanged());}); connect(this, &ContentManager::filterParamsChanged, this, &ContentManager::updateLibrary); connect(this, &ContentManager::booksChanged, this, [=]() { @@ -184,7 +186,6 @@ void ContentManager::setCategories() QStringList categories; if (m_local) { auto categoryData = mp_library->getKiwixLibrary().getBooksCategories(); - categories.push_back("all"); for (auto category : categoryData) { auto categoryName = QString::fromStdString(category); categories.push_back(categoryName); @@ -618,32 +619,52 @@ QStringList ContentManager::getDownloadIds() return list; } -void ContentManager::setCurrentLanguage(QString language) +void ContentManager::setCurrentLanguage(FilterList langPairList) { - if (language.length() == 2) { - try { - language = QString::fromStdString( - kiwix::converta2toa3(language.toStdString())); - } catch (std::out_of_range&) {} + QStringList languageList; + for (auto &langPair : langPairList) { + languageList.append(langPair.second); + } + languageList.sort(); + for (auto &language : languageList) { + if (language.length() == 2) { + try { + language = QString::fromStdString( + kiwix::converta2toa3(language.toStdString())); + } catch (std::out_of_range&) {} + } } - if (m_currentLanguage == language) + auto newLanguage = languageList.join(","); + if (m_currentLanguage == newLanguage) return; - m_currentLanguage = language; + m_currentLanguage = newLanguage; + KiwixApp::instance()->getSettingsManager()->setLanguage(langPairList); emit(currentLangChanged()); emit(filterParamsChanged()); } -void ContentManager::setCurrentCategoryFilter(QString category) +void ContentManager::setCurrentCategoryFilter(FilterList categoryPairList) { - if (m_categoryFilter == category) + QStringList categoryList; + for (auto &catPair : categoryPairList) { + categoryList.append(catPair.second); + } + categoryList.sort(); + if (m_categoryFilter == categoryList.join(",")) return; - m_categoryFilter = category.toLower(); + m_categoryFilter = categoryList.join(","); + KiwixApp::instance()->getSettingsManager()->setCategory(categoryPairList); emit(filterParamsChanged()); } -void ContentManager::setCurrentContentTypeFilter(QList& contentTypeFilters) +void ContentManager::setCurrentContentTypeFilter(FilterList contentTypeFiltersPairList) { + QStringList contentTypeFilters; + for (auto &ctfPair : contentTypeFiltersPairList) { + contentTypeFilters.append(ctfPair.second); + } m_contentTypeFilters = contentTypeFilters; + KiwixApp::instance()->getSettingsManager()->setContentType(contentTypeFiltersPairList); emit(filterParamsChanged()); } @@ -686,7 +707,6 @@ void ContentManager::updateLanguages(const QString& content) { void ContentManager::updateCategories(const QString& content) {; auto categories = kiwix::readCategoriesFromFeed(content.toStdString()); QStringList tempCategories; - tempCategories.push_back("all"); for (auto catg : categories) { tempCategories.push_back(QString::fromStdString(catg)); } @@ -704,32 +724,18 @@ QStringList ContentManager::getBookIds() { kiwix::Filter filter; std::vector acceptTags, rejectTags; - if (m_categoryFilter != "all" && m_categoryFilter != "other") { - acceptTags.push_back("_category:"+m_categoryFilter.toStdString()); - } - if (m_categoryFilter == "other") { - for (auto& category: m_categories) { - if (category != "other" && category != "all") { - rejectTags.push_back("_category:"+category.toStdString()); - } - } - } for (auto &contentTypeFilter : m_contentTypeFilters) { - auto state = contentTypeFilter->checkState(); - auto filter = contentTypeFilter->getName(); - if (state == Qt::PartiallyChecked) { - acceptTags.push_back("_" + filter.toStdString() +":yes"); - } else if (state == Qt::Checked) { - acceptTags.push_back("_" + filter.toStdString() +":no"); - } + acceptTags.push_back(contentTypeFilter.toStdString()); } filter.acceptTags(acceptTags); filter.rejectTags(rejectTags); filter.query(m_searchQuery.toStdString()); - if (m_currentLanguage != "*") + if (m_currentLanguage != "") filter.lang(m_currentLanguage.toStdString()); + if (m_categoryFilter != "") + filter.category(m_categoryFilter.toStdString()); if (m_local) { filter.local(true); diff --git a/src/contentmanager.h b/src/contentmanager.h index ae283a2e..b7f1051d 100644 --- a/src/contentmanager.h +++ b/src/contentmanager.h @@ -15,20 +15,20 @@ class ContentManager : public QObject Q_OBJECT Q_PROPERTY(QStringList bookIds READ getBookIds NOTIFY booksChanged) Q_PROPERTY(QStringList downloadIds READ getDownloadIds NOTIFY downloadsChanged) - Q_PROPERTY(QString currentLanguage MEMBER m_currentLanguage WRITE setCurrentLanguage NOTIFY currentLangChanged) Q_PROPERTY(bool isLocal MEMBER m_local READ isLocal WRITE setLocal NOTIFY localChanged) public: typedef QList> LanguageList; + typedef QList> FilterList; explicit ContentManager(Library* library, kiwix::Downloader *downloader, QObject *parent = nullptr); virtual ~ContentManager() {} ContentManagerView* getView() { return mp_view; } void setLocal(bool local); QStringList getDownloadIds(); - void setCurrentLanguage(QString language); - void setCurrentCategoryFilter(QString category); - void setCurrentContentTypeFilter(QList& contentTypeFilter); + void setCurrentLanguage(FilterList languageList); + void setCurrentCategoryFilter(FilterList category); + void setCurrentContentTypeFilter(FilterList contentTypeFilter); bool isLocal() const { return m_local; } QStringList getCategories() const { return m_categories; } LanguageList getLanguages() const { return m_languages; } @@ -43,7 +43,7 @@ class ContentManager : public QObject QString m_currentLanguage; QString m_searchQuery; QString m_categoryFilter = "all"; - QList m_contentTypeFilters; + QStringList m_contentTypeFilters; kiwix::supportedListSortBy m_sortBy = kiwix::UNSORTED; bool m_sortOrderAsc = true; LanguageList m_languages; diff --git a/src/contentmanagerside.cpp b/src/contentmanagerside.cpp index d2da4bc5..0bde3a96 100644 --- a/src/contentmanagerside.cpp +++ b/src/contentmanagerside.cpp @@ -1,7 +1,7 @@ #include "contentmanagerside.h" #include "ui_contentmanagerside.h" #include "kiwixapp.h" - +#include "kiwixchoicebox.h" #include #include @@ -11,7 +11,10 @@ ContentManagerSide::ContentManagerSide(QWidget *parent) : QWidget(parent), mp_ui(new Ui::contentmanagerside) { + setFocusPolicy(Qt::FocusPolicy::StrongFocus); mp_ui->setupUi(this); + this->setStyleSheet(KiwixApp::instance()->parseStyleFromFile(":/css/contentmanagerside.css")); + mp_ui->buttonGroup->setId(mp_ui->allFileButton, CatalogButtonId::ALL); mp_ui->buttonGroup->setId(mp_ui->localFileButton, CatalogButtonId::LOCAL); connect(mp_ui->buttonGroup, QOverload::of(&QButtonGroup::buttonClicked), [=](QAbstractButton *btn) { @@ -28,70 +31,35 @@ ContentManagerSide::ContentManagerSide(QWidget *parent) : mp_ui->allFileButton->setText(gt("online-files")); mp_ui->localFileButton ->setText(gt("local-files")); - mp_ui->languageButton->setText(gt("browse-by-language")); - mp_ui->categoryButton->setText(gt("browse-by-category")); - mp_ui->contentTypeButton->setText(gt("content-type")); - - mp_languageButton = mp_ui->languageButton; - mp_languageSelector = mp_ui->languageSelector; - connect(mp_languageButton, &QCheckBox::toggled, this, [=](bool checked) { mp_languageSelector->setHidden(!checked); }); - mp_languageSelector->setHidden(true); - - mp_categoryButton = mp_ui->categoryButton; - mp_categorySelector = mp_ui->categorySelector; - connect(mp_categoryButton, &QCheckBox::toggled, this, [=](bool checked) { mp_categorySelector->setHidden(!checked); }); - mp_categorySelector->setHidden(true); - - mp_contentTypeButton = mp_ui->contentTypeButton; - connect(mp_contentTypeButton, &QCheckBox::toggled, this, [=](bool checked) { mp_ui->contentTypeSelector->setHidden(!checked); }); - mp_ui->contentTypeSelector->setHidden(true); - - mp_ui->contentTypeAllButton->setText(gt("all")); - mp_ui->contentTypeAllButton->setStyleSheet("*{font-weight: bold}"); - connect(mp_ui->contentTypeAllButton, &QCheckBox::clicked, this, [=](bool checked) { - Q_UNUSED(checked); - mp_ui->contentTypeAllButton->setStyleSheet("*{font-weight: bold}"); - for (auto &contentTypeFilter : m_contentTypeFilters) { - contentTypeFilter->setCheckState(Qt::Unchecked); - } - mp_contentManager->setCurrentContentTypeFilter(m_contentTypeFilters); - }); + + mp_categories = mp_ui->categories; + mp_categories->setType("category"); + + mp_languages = mp_ui->languages; + mp_languages->setType("language"); + + mp_contentType = mp_ui->contentType; + mp_contentType->setType("content-type"); auto searcher = mp_ui->searcher; searcher->setPlaceholderText(gt("search-files")); - QFile file(QString::fromUtf8(":/css/_contentManager.css")); - file.open(QFile::ReadOnly); - QString styleSheet = QString(file.readAll()); - searcher->setStyleSheet(styleSheet); QIcon searchIcon = QIcon(":/icons/search.svg"); + searcher->addAction(searchIcon, QLineEdit::LeadingPosition); connect(searcher, &QLineEdit::textChanged, [searcher](){ KiwixApp::instance()->getContentManager()->setSearch(searcher->text()); }); - - ContentTypeFilter* videosFilter = new ContentTypeFilter("pictures", this); - ContentTypeFilter* picturesFilter = new ContentTypeFilter("videos", this); - ContentTypeFilter* detailsFilter = new ContentTypeFilter("details", this); - m_contentTypeFilters.push_back(videosFilter); - m_contentTypeFilters.push_back(picturesFilter); - m_contentTypeFilters.push_back(detailsFilter); - - auto layout = static_cast(mp_ui->contentTypeSelector->layout()); - for (auto &contentTypeFilter : m_contentTypeFilters) { - layout->addWidget(contentTypeFilter, 0, Qt::AlignTop); - connect(contentTypeFilter, &QCheckBox::clicked, this, [=](bool checked) { - Q_UNUSED(checked); - bool activeFilter = false; - for (auto &contentTypeFilter : m_contentTypeFilters) { - if (contentTypeFilter->checkState() != Qt::Unchecked) { - activeFilter = true; - break; - } - } - mp_ui->contentTypeAllButton->setStyleSheet(activeFilter ? "" : "*{font-weight: bold}"); - mp_contentManager->setCurrentContentTypeFilter(m_contentTypeFilters); - }); - } + + FilterList contentTypeList = { + {"_pictures:yes", gt("pictures")}, + {"_pictures:no", gt("no-pictures")}, + {"_videos:yes", gt("videos")}, + {"_videos:no", gt("no-videos")}, + {"_details:yes", gt("details")}, + {"_details:no", gt("no-details")} + }; + + mp_contentType->setSelections(contentTypeList, KiwixApp::instance()->getSettingsManager()->getContentType()); setCategories(KiwixApp::instance()->getContentManager()->getCategories()); setLanguages(KiwixApp::instance()->getContentManager()->getLanguages()); @@ -111,69 +79,23 @@ void ContentManagerSide::setContentManager(ContentManager *contentManager) const auto checkedButton = mp_ui->buttonGroup->button(isLocal == CatalogButtonId::LOCAL); checkedButton->setChecked(true); checkedButton->setStyleSheet("*{font-weight: bold}"); - connect(mp_languageSelector, &QListWidget::itemSelectionChanged, - this, [=]() { - auto item = mp_languageSelector->selectedItems().at(0); - if (!item) return; - auto lang = item->data(Qt::UserRole).toString(); - if (lang == "all") { - mp_contentManager->setCurrentLanguage("*"); - return; - } - mp_contentManager->setCurrentLanguage(lang); + connect(mp_languages, &KiwixChoiceBox::choiceUpdated, this, [=](FilterList values) { + mp_contentManager->setCurrentLanguage(values); }); - connect(mp_categorySelector, &QListWidget::itemSelectionChanged, - this, [=]() { - auto item = mp_categorySelector->selectedItems().at(0); - if (!item) return; - auto category = item->data(Qt::UserRole).toString(); - mp_contentManager->setCurrentCategoryFilter(category); + connect(mp_categories, &KiwixChoiceBox::choiceUpdated, this, [=](FilterList values) { + mp_contentManager->setCurrentCategoryFilter(values); + }); + connect(mp_contentType, &KiwixChoiceBox::choiceUpdated, this, [=](FilterList values) { + mp_contentManager->setCurrentContentTypeFilter(values); }); -} - -QString beautify(QString word) -{ - word = word.replace("_", " "); - word[0] = word[0].toUpper(); - return word; } void ContentManagerSide::setCategories(QStringList categories) { - mp_categorySelector->blockSignals(true); - mp_categorySelector->setHidden(true); - mp_categorySelector->clear(); - mp_categorySelector->blockSignals(false); - for (auto category: categories) - { - auto item = new KListWidgetItem(beautify(category)); - item->setData(Qt::UserRole, category); - mp_categorySelector->addItem(item); - if (category == "all") - { - item->setSelected(true); - } - } + mp_categories->setSelections(categories, KiwixApp::instance()->getSettingsManager()->getCategoryList()); } void ContentManagerSide::setLanguages(ContentManager::LanguageList langList) { - mp_languageSelector->blockSignals(true); - mp_languageSelector->setHidden(true); - mp_languageSelector->clear(); - mp_languageSelector->blockSignals(false); - for(auto lang: langList) - { - auto currentLang = QLocale().language(); - auto item = new KListWidgetItem(lang.second); - item->setData(Qt::UserRole, lang.first); - mp_languageSelector->addItem(item); - if (lang.second == QLocale::languageToString(currentLang)) { - item->setSelected(true); - } - } - mp_languageSelector->sortItems(); - auto item = new KListWidgetItem("All"); - item->setData(Qt::UserRole, "all"); - mp_languageSelector->insertItem(0, item); + mp_languages->setSelections(langList, KiwixApp::instance()->getSettingsManager()->getLanguageList()); } diff --git a/src/contentmanagerside.h b/src/contentmanagerside.h index 5af981d9..a751f616 100644 --- a/src/contentmanagerside.h +++ b/src/contentmanagerside.h @@ -11,6 +11,9 @@ namespace Ui { class contentmanagerside; } +class KiwixChoiceBox; +using FilterList = ContentManager::FilterList; + class ContentManagerSide : public QWidget { Q_OBJECT @@ -28,12 +31,11 @@ class ContentManagerSide : public QWidget private: Ui::contentmanagerside *mp_ui; ContentManager* mp_contentManager; - QCheckBox* mp_languageButton; - QListWidget* mp_languageSelector; - QCheckBox* mp_categoryButton; - QListWidget* mp_categorySelector; + KiwixChoiceBox *mp_categories; + KiwixChoiceBox *mp_languages; + KiwixChoiceBox *mp_contentType; QCheckBox* mp_contentTypeButton; - QList m_contentTypeFilters; + QStringList m_contentTypeFilters; public slots: void setCategories(QStringList); diff --git a/src/contentmanagerside.ui b/src/contentmanagerside.ui index 9dfd13b4..b06e1595 100644 --- a/src/contentmanagerside.ui +++ b/src/contentmanagerside.ui @@ -6,8 +6,8 @@ 0 0 - 197 - 368 + 300 + 386 @@ -16,15 +16,21 @@ 0 + + + 250 + 0 + + Form + + true + - - 0 - - QLayout::SetMaximumSize + QLayout::SetNoConstraint 0 @@ -39,229 +45,174 @@ 0 - - - + + + true - + true - - - 0 - - - QLayout::SetMinimumSize - - - 0 - - - 0 + + + + 0 + 0 + 298 + 384 + - - 0 - - - 0 - - - - - - - - - 0 - 0 - - - - All Files - - - buttonGroup - - - - - - - - 0 - 0 - - - - Local Files - - - true - - - buttonGroup - - - - - - - - 0 - 0 - - - - Browse By Language - - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - Qt::ScrollBarAlwaysOn - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - - - - - true - - - - 0 - 0 - - - - Browse By Category - - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - Qt::ScrollBarAlwaysOff - - - QAbstractScrollArea::AdjustToContents - - - - - - - - 0 - 0 - - - - Content Type - - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Sunken - - - 0 - - - - 0 + + + 9 + + + + + + 0 + 0 + - - 0 + + PointingHandCursor + + + All Files + + + buttonGroup + + + + 0 + 0 + + + + + + + + + 0 + 0 + - + + PointingHandCursor + + + Local Files + + + true + + + buttonGroup + + + + + + + + + + + 0 + 0 + + + + true + + + + + + + + 0 + 0 + + + + true + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + PointingHandCursor + + + QFrame::NoFrame + + + QFrame::Sunken + + 0 - - - - - 0 - 0 - - - - All - - - false - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - + + + 0 + + + 0 + + + 0 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 0 + + + + + + + + + KiwixChoiceBox + QWidget +
src/kiwixchoicebox.h
+ 1 +
+
diff --git a/src/contentmanagerview.cpp b/src/contentmanagerview.cpp index f3281eea..24ba1f9b 100644 --- a/src/contentmanagerview.cpp +++ b/src/contentmanagerview.cpp @@ -10,10 +10,7 @@ ContentManagerView::ContentManagerView(QWidget *parent) { mp_ui->setupUi(this); mp_ui->m_view->setSortingEnabled(true); - QFile file(QString::fromUtf8(":/css/_contentManager.css")); - file.open(QFile::ReadOnly); - QString styleSheet = QString(file.readAll()); - mp_ui->m_view->setStyleSheet(styleSheet); + mp_ui->m_view->setStyleSheet(KiwixApp::instance()->parseStyleFromFile(":/css/_contentManager.css")); mp_ui->m_view->setContextMenuPolicy(Qt::CustomContextMenu); auto managerDelegate = new ContentManagerDelegate(); mp_ui->m_view->setItemDelegate(managerDelegate); diff --git a/src/contenttypefilter.cpp b/src/contenttypefilter.cpp index 4a10f77d..97612c3c 100644 --- a/src/contenttypefilter.cpp +++ b/src/contenttypefilter.cpp @@ -11,11 +11,12 @@ ContentTypeFilter::ContentTypeFilter(QString name, QWidget *parent) m_states[Qt::PartiallyChecked] = gt("yes"); m_states[Qt::Checked] = gt("no"); setText(gt(m_name) + " : " + m_states[checkState()]); + setStyleSheet("* { color: #666666; }"); connect(this, &QCheckBox::stateChanged, this, &ContentTypeFilter::onStateChanged); } void ContentTypeFilter::onStateChanged(int state) { setText(gt(m_name) + " : " + m_states[static_cast(state)]); - setStyleSheet((state == 0) ? "" : "*{font-weight: bold}"); -} \ No newline at end of file + setStyleSheet((state == 0) ? "*{color: #666666;}" : "*{font-weight: bold; color: black;}"); +} diff --git a/src/flowlayout.cpp b/src/flowlayout.cpp new file mode 100644 index 00000000..caea2849 --- /dev/null +++ b/src/flowlayout.cpp @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, 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 The Qt Company Ltd 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 "flowlayout.h" +//! [1] +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); +} +//! [1] + +//! [2] +FlowLayout::~FlowLayout() +{ + QLayoutItem *item; + while ((item = takeAt(0))) + delete item; +} +//! [2] + +//! [3] +void FlowLayout::addItem(QLayoutItem *item) +{ + itemList.append(item); +} +//! [3] + +//! [4] +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); + } +} +//! [4] + +//! [5] +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); + return nullptr; +} +//! [5] + +//! [6] +Qt::Orientations FlowLayout::expandingDirections() const +{ + return { }; +} +//! [6] + +//! [7] +bool FlowLayout::hasHeightForWidth() const +{ + return true; +} + +int FlowLayout::heightForWidth(int width) const +{ + int height = doLayout(QRect(0, 0, width, 0), true); + return height; +} +//! [7] + +//! [8] +void FlowLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + doLayout(rect, false); +} + +QSize FlowLayout::sizeHint() const +{ + return minimumSize(); +} + +QSize FlowLayout::minimumSize() const +{ + QSize size; + for (const QLayoutItem *item : qAsConst(itemList)) + size = size.expandedTo(item->minimumSize()); + + const QMargins margins = contentsMargins(); + size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom()); + return size; +} +//! [8] + +//! [9] +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; +//! [9] + +//! [10] + for (QLayoutItem *item : qAsConst(itemList)) { + const 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); +//! [10] +//! [11] + 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; +} +//! [11] +//! [12] +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, nullptr, pw); + } else { + return static_cast(parent)->spacing(); + } +} +//! [12] + +void FlowLayout::insertWidget(int index, QWidget *w) { + addWidget(w); + if (index < 0) + index = 0; + if (index >= itemList.size()) + index = itemList.size() - 1; + itemList.move(indexOf(w), index); +} diff --git a/src/flowlayout.h b/src/flowlayout.h new file mode 100644 index 00000000..d98defca --- /dev/null +++ b/src/flowlayout.h @@ -0,0 +1,89 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** BSD License Usage +** Alternatively, 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 The Qt Company Ltd 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 FLOWLAYOUT_H +#define FLOWLAYOUT_H + +#include +#include +#include +//! [0] +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(); + + void addItem(QLayoutItem *item) override; + int horizontalSpacing() const; + int verticalSpacing() const; + Qt::Orientations expandingDirections() const override; + bool hasHeightForWidth() const override; + int heightForWidth(int) const override; + int count() const override; + QLayoutItem *itemAt(int index) const override; + QSize minimumSize() const override; + void setGeometry(const QRect &rect) override; + QSize sizeHint() const override; + QLayoutItem *takeAt(int index) override; + void insertWidget(int index, QWidget *w); + +private: + int doLayout(const QRect &rect, bool testOnly) const; + int smartSpacing(QStyle::PixelMetric pm) const; + + QList itemList; + int m_hSpace; + int m_vSpace; +}; +//! [0] + +#endif // FLOWLAYOUT_H diff --git a/src/kiwixapp.cpp b/src/kiwixapp.cpp index eff5b07c..a47274bc 100644 --- a/src/kiwixapp.cpp +++ b/src/kiwixapp.cpp @@ -72,13 +72,7 @@ void KiwixApp::init() setApplicationName("Kiwix"); setDesktopFileName("kiwix.desktop"); - - QFile styleFile(":/css/style.css"); - styleFile.open(QIODevice::ReadOnly); - auto byteContent = styleFile.readAll(); - QString style(byteContent); - setStyleSheet(style); - + setStyleSheet(parseStyleFromFile(":/css/style.css")); createAction(); mp_mainWindow = new MainWindow; @@ -474,3 +468,12 @@ void KiwixApp::printVersions(std::ostream& out) { out << std::endl; zim::printVersions(out); } + +QString KiwixApp::parseStyleFromFile(QString filePath) +{ + QFile file(filePath); + file.open(QFile::ReadOnly); + QString styleSheet = QString(file.readAll()); + file.close(); + return styleSheet; +} diff --git a/src/kiwixapp.h b/src/kiwixapp.h index 212f1680..4a4befbb 100644 --- a/src/kiwixapp.h +++ b/src/kiwixapp.h @@ -86,6 +86,7 @@ class KiwixApp : public QtSingleApplication QString getText(const QString &key) { return m_translation.getText(key); }; void setMonitorDir(const QString &dir); bool isCurrentArticleBookmarked(); + QString parseStyleFromFile(QString filePath); public slots: void openZimFile(const QString& zimfile=""); diff --git a/src/kiwixchoicebox.cpp b/src/kiwixchoicebox.cpp new file mode 100644 index 00000000..68841837 --- /dev/null +++ b/src/kiwixchoicebox.cpp @@ -0,0 +1,290 @@ +#include "kiwixchoicebox.h" +#include "ui_kiwixchoicebox.h" +#include "klistwidgetitem.h" +#include "kiwixapp.h" +#include "choiceitem.h" +#include +#include "choiceitem.h" +#include "kiwixapp.h" +#include +#include +#include +#include +#include "kiwixlineedit.h" +#include "kiwixlistwidget.h" + +KiwixChoiceBox::KiwixChoiceBox(QWidget *parent) : + QWidget(parent), + ui(new Ui::kiwixchoicebox) +{ + ui->setupUi(this); + auto styleSheet = KiwixApp::instance()->parseStyleFromFile(":/css/choiceBox.css"); + this->setStyleSheet(styleSheet); + ui->clearButton->setText(gt("clear")); + ui->clearButton->setToolTip(gt("clear-filter")); + + choiceLabel = ui->choiceLabel; + choiceLabel->setText(gt("undefined")); + + choiceSelector = new KiwixListWidget(parent); + choiceSelector->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + choiceSelector->setMaximumWidth(250); + // allow maximum 6 elements + choiceSelector->setMaximumHeight(KListWidgetItem::getItemHeight() * 6); + choiceSelector->setCursor(Qt::PointingHandCursor); + choiceSelector->setVerticalScrollMode(QAbstractItemView::ScrollMode::ScrollPerPixel); + choiceSelector->setFocusPolicy(Qt::FocusPolicy::NoFocus); + choiceSelector->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + choiceSelector->setStyleSheet(styleSheet); + choiceSelector->setSelectionMode(QAbstractItemView::SelectionMode::MultiSelection); + + currentChoicesLayout = new FlowLayout(ui->currentChoices, 4, 2, 2); + searcher = new KiwixLineEdit(); + searcher->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + searcher->setFixedWidth(20); + currentChoicesLayout->addWidget(searcher); + connect(choiceSelector, &QListWidget::itemPressed, this, [=](QListWidgetItem *item) { + searcher->clear(); + if (item->isSelected()) { + addSelection(item); + } else { + removeSelection(item); + } + }); + + connect(searcher, &QLineEdit::textChanged, [=](QString search) { + searcher->setStyleSheet("QLineEdit{color: #666;}"); + QFontMetrics fm = searcher->fontMetrics(); + auto w = fm.horizontalAdvance(search) + 20; + if (w + 4 < ui->currentChoices->width()) { + searcher->setFixedWidth(w); + ui->currentChoices->resize(ui->currentChoices->width(), currentChoicesLayout->minimumHeightForWidth(ui->currentChoices->width())); + } + int visibleItems = 0; + for (auto i = 0; i < choiceSelector->count(); i++) { + auto itemAtRow = choiceSelector->item(i); + itemAtRow->setHidden(!itemAtRow->text().contains(search, Qt::CaseSensitivity::CaseInsensitive)); + visibleItems += !(itemAtRow->isHidden()); + } + choiceSelector->setVisibleItems(visibleItems); + adjustSize(); + choiceSelector->setVisible(true); + }); + + connect(searcher, &KiwixLineEdit::clicked, [=]() { + showOptions(); + }); + + choiceSelector->setVisible(false); + searcher->setStyleSheet("QLineEdit{color: #999;}"); + + connect(searcher, &KiwixLineEdit::focusedOut, [=]() { + hideOptions(); + }); + + connect(searcher, &KiwixLineEdit::focusedIn, [=]() { + showOptions(); + }); + + ui->clearButton->setCursor(Qt::PointingHandCursor); + connect(ui->clearButton, &QPushButton::clicked, [=]() { + clearSelections(); + emit(choiceUpdated(getCurrentSelected())); + hideOptions(); + }); + + connect(this, &KiwixChoiceBox::choiceUpdated, [=]() { + if (choiceSelector->selectedItems().isEmpty()) + showPlaceholder(); + choiceSelector->setVisible(false); + }); + + connect(this, &KiwixChoiceBox::clicked, [=]() { + showOptions(); + }); +} + +KiwixChoiceBox::~KiwixChoiceBox() +{ + delete ui; +} + +/* +When the lineEdit is currently focused, +if the outer widget is pressed, lineEdit loses focus and hides the options. +When mouseRelease event is called, it will create a flicker effect: + - Clicking and holding causes the lineEdit to lose focus and hide the options + - Release causes the options to show up again. +Showing the options on a mousePress doesn't allow this +*/ +void KiwixChoiceBox::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + emit(clicked()); + } +} + +void KiwixChoiceBox::hideOptions() +{ + if (choiceSelector->selectedItems().isEmpty()) { + showPlaceholder(); + } + searcher->setStyleSheet("QLineEdit{color: #999;}"); + choiceSelector->setVisible(false); + ui->currentChoices->setStyleSheet("#currentChoices{border: 1px solid #ccc;}"); + searcher->clearFocus(); +} + +void KiwixChoiceBox::showOptions() +{ + ui->currentChoices->setStyleSheet("#currentChoices{border: 2px solid #4e63ad;}"); + adjustSize(); + choiceSelector->setVisible(true); + choiceSelector->raise(); + searcher->setPlaceholderText(""); + searcher->setFocus(); +} + +void KiwixChoiceBox::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) { + hideOptions(); + } else if (event->key() == Qt::Key_Down) { + if (!choiceSelector->isVisible()) + showOptions(); + choiceSelector->moveDown(); + } else if (event->key() == Qt::Key_Up) { + choiceSelector->moveUp(); + } else if ((event->key() == Qt::Key_Enter) || (event->key() == Qt::Key_Return)) { + choiceSelector->selectCurrent(); + } +} + +bool KiwixChoiceBox::addSelection(QListWidgetItem *item, bool updateRequired) +{ + auto key = item->text(); + auto value = item->data(Qt::UserRole).toString(); + auto chItem = new ChoiceItem(key, value); + connect(chItem, &ChoiceItem::closeButtonClicked, [=](QString text) { + auto selectionItems = choiceSelector->findItems(text, Qt::MatchExactly); + if (selectionItems.size() != 1) return; + removeSelection(selectionItems[0]); + }); + chItem->setObjectName(key); + currentChoicesLayout->insertWidget(ui->currentChoices->children().count() - 2, chItem); + searcher->setFixedWidth(20); + // put on top of list + item = choiceSelector->takeItem(choiceSelector->row(item)); + choiceSelector->insertItem(0, item); + item->setSelected(true); + + searcher->setFixedWidth(20); + if (updateRequired) { + searcher->setFocus(); + emit(choiceUpdated(getCurrentSelected())); + } + return true; +} + +bool KiwixChoiceBox::removeSelection(QListWidgetItem *item) +{ + auto chItem = ui->currentChoices->findChild(item->text()); + chItem->deleteLater(); + // selected items are always shown at top, put it after the last selected item + item->setSelected(false); + auto selItems = choiceSelector->selectedItems(); + item = choiceSelector->takeItem(choiceSelector->row(item)); + choiceSelector->insertItem(selItems.size(), item); + emit(choiceUpdated(getCurrentSelected())); + return true; +} + +void KiwixChoiceBox::clearSelections() +{ + for (auto &item : choiceSelector->selectedItems()) { + item->setSelected(false); + auto chItem = ui->currentChoices->findChild(item->text()); + ui->currentChoices->layout()->removeWidget(chItem); + delete chItem; + } +} + +void KiwixChoiceBox::showPlaceholder() +{ + searcher->clear(); + searcher->setPlaceholderText(gt(m_type + "-searcher-placeholder")); + // Putting width based on placeholder contents + QFontMetrics fm = searcher->fontMetrics(); + auto w = fm.boundingRect(gt(m_type + "-searcher-placeholder")).width(); + searcher->setFixedWidth(w + 20); +} + +QString beautifyString(QString word) +{ + word = word.replace("_", " "); + word[0] = word[0].toUpper(); + return word; +} + +void KiwixChoiceBox::setSelections(QStringList selections, SelectionList defaultSelection) +{ + SelectionList sList; + for (const auto &sel : selections) { + sList.append({sel, sel}); + } + setSelections(sList, defaultSelection); +} + +void KiwixChoiceBox::setSelections(SelectionList selections, SelectionList defaultSelection) +{ + clearSelections(); + choiceSelector->clear(); + for (const auto &selection: selections) + { + auto item = new KListWidgetItem(beautifyString(selection.second)); + item->setData(Qt::UserRole, selection.first); + choiceSelector->addItem(item); + } + + for (const auto &defSel : defaultSelection) { + auto itemList = choiceSelector->findItems(defSel.first, Qt::MatchExactly); + if (itemList.isEmpty()) { + auto item = new KListWidgetItem(defSel.first); + item->setData(Qt::UserRole, defSel.second); + choiceSelector->addItem(item); + addSelection(item, false); + } else { + addSelection(itemList[0], false); + } + } + + if (choiceSelector->selectedItems().isEmpty()) + showPlaceholder(); + choiceSelector->setVisibleItems(choiceSelector->count()); + adjustSize(); +} + +void KiwixChoiceBox::adjustSize() +{ + QWidget::adjustSize(); + choiceSelector->adjustSize(); + choiceSelector->setGeometry(this->x() + ui->currentChoices->x(), + this->y() + ui->currentChoices->y() + ui->currentChoices->height(), + choiceSelector->width(), + choiceSelector->getVisibleItems() * KListWidgetItem::getItemHeight() + 2); // 2 is for the border so that all elements are visible +} + +void KiwixChoiceBox::setType(QString type) +{ + ui->choiceLabel->setText(gt(type)); + m_type = type; +} + +KiwixChoiceBox::SelectionList KiwixChoiceBox::getCurrentSelected() +{ + SelectionList selections; + for (auto &item : choiceSelector->selectedItems()) { + selections.append({item->text(), item->data(Qt::UserRole).toString()}); + } + return selections; +} diff --git a/src/kiwixchoicebox.h b/src/kiwixchoicebox.h new file mode 100644 index 00000000..18cc8cb0 --- /dev/null +++ b/src/kiwixchoicebox.h @@ -0,0 +1,63 @@ +#ifndef KIWIXCHOICEBOX_H +#define KIWIXCHOICEBOX_H + +#include +#include +#include +#include +#include +#include +#include +#include "flowlayout.h" +#include +#include + +class ChoiceItem; +class KiwixLineEdit; +class KiwixListWidget; + +namespace Ui { +class kiwixchoicebox; +} + +class KiwixChoiceBox : public QWidget +{ + Q_OBJECT + + typedef QList> SelectionList; + +public: + explicit KiwixChoiceBox(QWidget *parent = nullptr); + void setType(QString type); + void setSelections(SelectionList selections, SelectionList defaultSelection); + void setSelections(QStringList selections, SelectionList defaultSelection); + ~KiwixChoiceBox(); + void adjustSize(); + +protected: + void keyPressEvent(QKeyEvent* event) override; + void mousePressEvent(QMouseEvent *event) override; + +signals: + void choiceUpdated(SelectionList); + void clicked(); + +private: + Ui::kiwixchoicebox *ui; + QLabel *choiceLabel; + QLineEdit *choiceSearch; + KiwixListWidget *choiceSelector; + FlowLayout *currentChoicesLayout; + KiwixLineEdit *searcher; + SelectionList getCurrentSelected(); + bool removeSelection(QListWidgetItem *item); + void clearSelections(); + bool addSelection(QListWidgetItem *item, bool updateRequired = true); + void showOptions(); + void hideOptions(); + void showPlaceholder(); + QString m_type; + bool m_sliderMoved = false; +}; + +#endif // KIWIXCHOICEBOX_H diff --git a/src/kiwixconfirmbox.cpp b/src/kiwixconfirmbox.cpp index 094339a1..d855669e 100644 --- a/src/kiwixconfirmbox.cpp +++ b/src/kiwixconfirmbox.cpp @@ -9,13 +9,7 @@ KiwixConfirmBox::KiwixConfirmBox(QString confirmTitle, QString confirmText, bool { ui->setupUi(this); setWindowFlag(Qt::FramelessWindowHint, true); - - QFile styleFile(":/css/confirmBox.css"); - styleFile.open(QIODevice::ReadOnly); - auto byteContent = styleFile.readAll(); - styleFile.close(); - QString style(byteContent); - setStyleSheet(style); + setStyleSheet(KiwixApp::instance()->parseStyleFromFile(":/css/confirmBox.css")); connect(ui->yesButton, &QPushButton::clicked, [=]() { emit yesClicked(); }); diff --git a/src/kiwixlineedit.cpp b/src/kiwixlineedit.cpp new file mode 100644 index 00000000..ca5ca1e4 --- /dev/null +++ b/src/kiwixlineedit.cpp @@ -0,0 +1,27 @@ +#include "kiwixlineedit.h" + +KiwixLineEdit::KiwixLineEdit(QWidget *parent) : QLineEdit(parent) +{ + installEventFilter(this); +} + +KiwixLineEdit::~KiwixLineEdit() +{ +} + +void KiwixLineEdit::resizeEvent(QResizeEvent *event) +{ + QLineEdit::resizeEvent(event); + adjustSize(); +} +bool KiwixLineEdit::eventFilter(QObject* object, QEvent* event) +{ + if (event->type() == QEvent::MouseButtonPress) { + emit(clicked()); + } else if (event->type() == QEvent::FocusIn) { + emit(focusedIn()); + } else if (event->type() == QEvent::FocusOut) { + emit(focusedOut()); + } + return false; +} diff --git a/src/kiwixlineedit.h b/src/kiwixlineedit.h new file mode 100644 index 00000000..6b25b859 --- /dev/null +++ b/src/kiwixlineedit.h @@ -0,0 +1,20 @@ +#ifndef KIWIXLINEEDIT_H +#define KIWIXLINEEDIT_H + +#include +#include + +class KiwixLineEdit : public QLineEdit { + Q_OBJECT; +public: + KiwixLineEdit(QWidget *parent = nullptr); + ~KiwixLineEdit(); +protected: + void resizeEvent(QResizeEvent *event) override; + bool eventFilter(QObject* object, QEvent* event) override; +signals: + void clicked(); + void focusedIn(); + void focusedOut(); +}; +#endif // KIWIXLINEEDIT_H diff --git a/src/kiwixlistwidget.cpp b/src/kiwixlistwidget.cpp new file mode 100644 index 00000000..0d1f58bc --- /dev/null +++ b/src/kiwixlistwidget.cpp @@ -0,0 +1,108 @@ +#include "kiwixlistwidget.h" +#include +#include +#include +#include +#include "kiwixapp.h" +#include "klistwidgetitem.h" + +KiwixListWidget::KiwixListWidget(QWidget *parent) + : QListWidget(parent), currRow(0), m_visibleItems(count()) +{ + connect(this, &KiwixListWidget::currRowChanged, this, &KiwixListWidget::handleCurrRowChange); + setMouseTracking(true); +} + +void KiwixListWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + selectCurrent(item(m_mouseIndex)); + } +} + +void KiwixListWidget::mouseMoveEvent(QMouseEvent *event) +{ + int oldRow = currRow; + m_mouseIndex = row(itemAt(event->pos())); + currRow = m_mouseIndex; + emit(currRowChanged(oldRow, currRow)); +} + +void KiwixListWidget::hideEvent(QHideEvent *event) +{ + auto currItem = dynamic_cast(item(currRow)); + if (currItem) + currItem->disableHighlight(); + QListWidget::hideEvent(event); +} + +void KiwixListWidget::resizeEvent(QResizeEvent *e) +{ + int oldRow = currRow; + for (auto i = 0; i < count(); i++) { + auto itemAtRow = item(i); + if (!itemAtRow->isHidden() && !itemAtRow->isSelected()) { + currRow = i; + break; + } + } + emit(currRowChanged(oldRow, currRow)); + QListWidget::resizeEvent(e); +} + +void KiwixListWidget::handleCurrRowChange(int oldRow, int newRow) +{ + auto prevItem = dynamic_cast(item(oldRow)); + if (prevItem) { + prevItem->disableHighlight(); + } + auto currItem = dynamic_cast(item(newRow)); + if (currItem) { + currItem->enableHighlight(); + scrollToItem(currItem, QAbstractItemView::EnsureVisible); + } + emit(dataChanged(QModelIndex(), QModelIndex())); +} + +void KiwixListWidget::moveUp() +{ + if (selectedItems().size() == count()) + return; + KListWidgetItem *currItem = dynamic_cast(item(currRow)); + int oldRow = currRow; + do { + currRow--; + if (currRow < 0) currRow = count() - 1; + currItem = dynamic_cast(item(currRow)); + } while (currItem->isSelected() || currItem->isHidden()); + emit(currRowChanged(oldRow, currRow)); +} + +void KiwixListWidget::moveDown() +{ + if (selectedItems().size() == count()) + return; + KListWidgetItem *currItem = dynamic_cast(item(currRow)); + int oldRow = currRow; + do { + currRow++; + if (currRow == count()) currRow = 0; + currItem = dynamic_cast(item(currRow)); + } while (currItem->isSelected() || currItem->isHidden()); + emit(currRowChanged(oldRow, currRow)); +} + +void KiwixListWidget::selectCurrent() +{ + selectCurrent(item(currRow)); +} + +void KiwixListWidget::selectCurrent(QListWidgetItem *item) +{ + auto currItem = dynamic_cast(item); + if (currItem && !currItem->isSelected() && !currItem->isHidden()) { + currItem->disableHighlight(); + currItem->setSelected(!currItem->isSelected()); + emit(itemPressed(currItem)); + } +} diff --git a/src/kiwixlistwidget.h b/src/kiwixlistwidget.h new file mode 100644 index 00000000..0f4cd078 --- /dev/null +++ b/src/kiwixlistwidget.h @@ -0,0 +1,36 @@ +#ifndef KIWIXLISTWIDGET_H +#define KIWIXLISTWIDGET_H + +#include + +class KiwixListWidget : public QListWidget { + Q_OBJECT + +public: + KiwixListWidget(QWidget *parent = nullptr); + void moveUp(); + void moveDown(); + void selectCurrent(); + void selectCurrent(QListWidgetItem *item); + void setVisibleItems(int visibleItems) { m_visibleItems = visibleItems; } + int getVisibleItems() { return m_visibleItems; } + +protected: + void hideEvent(QHideEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + +signals: + void currRowChanged(int oldRow, int newRow); + +private slots: + void handleCurrRowChange(int oldRow, int newRow); + +private: + int currRow; + int m_visibleItems; + int m_mouseIndex; +}; + +#endif // KIWIXLISTWIDGET_H diff --git a/src/klistwidgetitem.cpp b/src/klistwidgetitem.cpp index 574626a4..b77ef607 100644 --- a/src/klistwidgetitem.cpp +++ b/src/klistwidgetitem.cpp @@ -1,18 +1,41 @@ #include "klistwidgetitem.h" +int KListWidgetItem::m_itemHeight = 35; + KListWidgetItem::KListWidgetItem(QString text) : QListWidgetItem (text) { + setSizeHint(QSize(200, m_itemHeight)); + setBackground(QColor("white")); + setForeground(QColor("#666666")); +} + +void KListWidgetItem::disableHighlight() +{ + isHighlighted = false; +} + +void KListWidgetItem::enableHighlight() +{ + isHighlighted = true; } QVariant KListWidgetItem::data(int role) const { QVariant v = QListWidgetItem::data(role); - if( isSelected() && role == Qt::FontRole ) - { - QFont font = v.value(); - font.setBold( true ); - v = QVariant::fromValue( font ); + if( isSelected()) { + if (role == Qt::FontRole) { + QFont font = v.value(); + font.setBold( true ); + v = QVariant::fromValue( font ); + } + } + if (isHighlighted) { + if (role == Qt::BackgroundRole) { + v = QVariant::fromValue(QColor("#4e63ad")); + } else if (role == Qt::ForegroundRole) { + v = QVariant::fromValue(QColor("white")); + } } return v; } diff --git a/src/klistwidgetitem.h b/src/klistwidgetitem.h index 2208c20d..493fe073 100644 --- a/src/klistwidgetitem.h +++ b/src/klistwidgetitem.h @@ -8,6 +8,12 @@ class KListWidgetItem : public QListWidgetItem public: KListWidgetItem(QString text); QVariant data(int role) const; + static int getItemHeight() { return m_itemHeight; }; + void disableHighlight(); + void enableHighlight(); +private: + static int m_itemHeight; + bool isHighlighted = false; }; #endif // KLISTWIDGETITEM_H diff --git a/src/opdsrequestmanager.cpp b/src/opdsrequestmanager.cpp index 209ee3bf..8a243028 100644 --- a/src/opdsrequestmanager.cpp +++ b/src/opdsrequestmanager.cpp @@ -15,7 +15,7 @@ void OpdsRequestManager::doUpdate(const QString& currentLanguage, const QString& QStringList excludeTags("_sw:yes"); // Add filter by language (if necessary) - if (currentLanguage != "*") { + if (currentLanguage != "") { query.addQueryItem("lang", currentLanguage); } @@ -23,20 +23,10 @@ void OpdsRequestManager::doUpdate(const QString& currentLanguage, const QString& query.addQueryItem("count", QString::number(-1)); // Add filter by category (if necessary) - if (categoryFilter != "all" && categoryFilter != "other") { - query.addQueryItem("tag", "_category:"+categoryFilter); + if (categoryFilter != "") { + query.addQueryItem("category", categoryFilter); } - // Add "special negative" filter for "other" category (if necessary) - if (categoryFilter == "other") { - for (auto& category: KiwixApp::instance()->getContentManager()->getCategories()) { - if (category != "other" && category != "all") { - excludeTags += "_category:"+category; - } - } - } - query.addQueryItem("notag", excludeTags.join(";")); - auto mp_reply = opdsResponseFromPath("/catalog/search", query); connect(mp_reply, &QNetworkReply::finished, this, [=]() { receiveContent(mp_reply); diff --git a/src/searchbar.cpp b/src/searchbar.cpp index d82a688e..c7a643c0 100644 --- a/src/searchbar.cpp +++ b/src/searchbar.cpp @@ -71,12 +71,7 @@ SearchBar::SearchBar(QWidget *parent) : m_completer.setMaxVisibleItems(16); setCompleter(&m_completer); - QFile styleFile(":/css/popup.css"); - styleFile.open(QIODevice::ReadOnly); - auto byteContent = styleFile.readAll(); - styleFile.close(); - QString style(byteContent); - m_completer.popup()->setStyleSheet(style); + m_completer.popup()->setStyleSheet(KiwixApp::instance()->parseStyleFromFile(":/css/popup.css")); qRegisterMetaType>("QVector"); connect(mp_typingTimer, &QTimer::timeout, this, &SearchBar::updateCompletion); diff --git a/src/settingsmanager.cpp b/src/settingsmanager.cpp index a0a7605c..b77657e6 100644 --- a/src/settingsmanager.cpp +++ b/src/settingsmanager.cpp @@ -4,6 +4,8 @@ #include #include #include +#include + SettingsManager::SettingsManager(QObject *parent) : QObject(parent), m_settings("Kiwix", "Kiwix-desktop"), @@ -96,6 +98,47 @@ void SettingsManager::setMoveToTrash(bool moveToTrash) emit(moveToTrashChanged(m_moveToTrash)); } +QList SettingsManager::flattenPair(FilterList pairList) +{ + QList res; + for (auto &pair : pairList) { + res.push_back(pair.first+"|"+pair.second); + } + return res; +} + +SettingsManager::FilterList SettingsManager::deducePair(QList variantList) +{ + FilterList pairList; + for (auto &variant : variantList) { + QString str = variant.toString(); + auto pairs = str.split('|'); + pairList.push_back({pairs[0], pairs[1]}); + } + return pairList; +} + +void SettingsManager::setLanguage(FilterList langList) +{ + m_langList = flattenPair(langList); + setSettings("language", m_langList); + emit(languageChanged(m_langList)); +} + +void SettingsManager::setCategory(FilterList categoryList) +{ + m_categoryList = flattenPair(categoryList); + setSettings("category", m_categoryList); + emit(categoryChanged(m_categoryList)); +} + +void SettingsManager::setContentType(FilterList contentTypeList) +{ + m_contentTypeList = flattenPair(contentTypeList); + setSettings("contentType", m_contentTypeList); + emit(contentTypeChanged(m_contentTypeList)); +} + void SettingsManager::initSettings() { m_kiwixServerPort = m_settings.value("localKiwixServer/port", 8080).toInt(); @@ -104,4 +147,8 @@ void SettingsManager::initSettings() m_kiwixServerIpAddress = m_settings.value("localKiwixServer/ipAddress", QString("0.0.0.0")).toString(); m_monitorDir = m_settings.value("monitor/dir", QString("")).toString(); m_moveToTrash = m_settings.value("moveToTrash", true).toBool(); + QVariant defaultLang = QVariant::fromValue(QLocale::languageToString(QLocale().language()) + '|' + QLocale().name().split("_").at(0)); + m_langList = m_settings.value("language", {defaultLang}).toList(); + m_categoryList = m_settings.value("category", {}).toList(); + m_contentTypeList = m_settings.value("contentType", {}).toList(); } diff --git a/src/settingsmanager.h b/src/settingsmanager.h index 501fe7eb..5646008d 100644 --- a/src/settingsmanager.h +++ b/src/settingsmanager.h @@ -13,6 +13,7 @@ class SettingsManager : public QObject Q_PROPERTY(QString downloadDir MEMBER m_downloadDir WRITE setDownloadDir NOTIFY downloadDirChanged) public: + typedef QList> FilterList; explicit SettingsManager(QObject *parent = nullptr); virtual ~SettingsManager() {}; @@ -28,6 +29,9 @@ class SettingsManager : public QObject QString getDownloadDir() const { return m_downloadDir; } QString getMonitorDir() const { return m_monitorDir; } bool getMoveToTrash() const { return m_moveToTrash; } + FilterList getLanguageList() { return deducePair(m_langList); } + FilterList getCategoryList() { return deducePair(m_categoryList); } + FilterList getContentType() { return deducePair(m_contentTypeList); } public slots: void setKiwixServerPort(int port); @@ -36,8 +40,13 @@ public slots: void setDownloadDir(QString downloadDir); void setMonitorDir(QString monitorDir); void setMoveToTrash(bool moveToTrash); + void setLanguage(FilterList langList); + void setCategory(FilterList categoryList); + void setContentType(FilterList contentTypeList); private: void initSettings(); + QList flattenPair(FilterList pairList); + FilterList deducePair(QList); signals: void portChanged(int port); @@ -45,6 +54,9 @@ public slots: void downloadDirChanged(QString downloadDir); void monitorDirChanged(QString monitorDir); void moveToTrashChanged(bool moveToTrash); + void languageChanged(QList langList); + void categoryChanged(QList categoryList); + void contentTypeChanged(QList contentTypeList); private: QSettings m_settings; @@ -55,6 +67,9 @@ public slots: QString m_downloadDir; QString m_monitorDir; bool m_moveToTrash; + QList m_langList; + QList m_categoryList; + QList m_contentTypeList; }; #endif // SETTINGSMANAGER_H diff --git a/src/settingsview.cpp b/src/settingsview.cpp index 30f608b0..7b59b1a0 100644 --- a/src/settingsview.cpp +++ b/src/settingsview.cpp @@ -9,10 +9,7 @@ SettingsView::SettingsView(QWidget *parent) , ui(new Ui::Settings) { ui->setupUi(this); - QFile file(QString::fromUtf8(":/css/_settingsManager.css")); - file.open(QFile::ReadOnly); - QString styleSheet = QString(file.readAll()); - ui->widget->setStyleSheet(styleSheet); + ui->widget->setStyleSheet(KiwixApp::instance()->parseStyleFromFile(":/css/_settingsManager.css")); connect(ui->zoomPercentSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &SettingsView::setZoom); connect(ui->moveToTrashToggle, &QCheckBox::clicked, this, &SettingsView::setMoveToTrash); connect(ui->browseButton, &QPushButton::clicked, this, &SettingsView::browseDownloadDir); diff --git a/ui/kiwixchoicebox.ui b/ui/kiwixchoicebox.ui new file mode 100644 index 00000000..4d78a3a7 --- /dev/null +++ b/ui/kiwixchoicebox.ui @@ -0,0 +1,115 @@ + + + kiwixchoicebox + + + + 0 + 0 + 268 + 54 + + + + + 0 + 0 + + + + + 0 + 0 + + + + Form + + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + TextLabel + + + + + + + + 50 + 16777215 + + + + Clear + + + + + + + + + + + 0 + 0 + + + + + 250 + 0 + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 5 + + + + + + + + +