diff --git a/kiwix-desktop.pro b/kiwix-desktop.pro index 2fe14a1b5..9fb0c123e 100644 --- a/kiwix-desktop.pro +++ b/kiwix-desktop.pro @@ -62,6 +62,7 @@ SOURCES += \ src/rownode.cpp \ src/suggestionlistworker.cpp \ src/suggestionlistmodel.cpp \ + src/suggestionlistdelegate.cpp \ src/thumbnaildownloader.cpp \ src/translation.cpp \ src/main.cpp \ @@ -111,6 +112,7 @@ HEADERS += \ src/rownode.h \ src/suggestionlistworker.h \ src/suggestionlistmodel.h \ + src/suggestionlistdelegate.h \ src/thumbnaildownloader.h \ src/translation.h \ src/mainwindow.h \ @@ -140,6 +142,7 @@ HEADERS += \ src/menuproxystyle.h \ src/zimview.h \ src/portutils.h \ + src/css_constants.h \ FORMS += \ src/choiceitem.ui \ diff --git a/resources/css/_contentManager.css b/resources/css/_contentManager.css index 4218c1f1a..289ce8c7c 100644 --- a/resources/css/_contentManager.css +++ b/resources/css/_contentManager.css @@ -24,9 +24,10 @@ QTreeView::item:has-children { border-bottom: 1px solid #cccccc; } + QTreeView { font-family: 'Selawik'; - padding: 4px; + padding: 4px; /* XXX: duplicated in css_constants.h */ border: none; selection-color: black; qproperty-iconSize: 30px; diff --git a/resources/css/popup.css b/resources/css/popup.css index f1d5a9771..cb8114dcc 100644 --- a/resources/css/popup.css +++ b/resources/css/popup.css @@ -1,6 +1,34 @@ -QWidget { +QTreeView { background-color: white; border: none; + outline: none; +} + +QTreeView::item { + border: 1px solid transparent; +} + +QTreeView::item:selected { + border: 1px solid #3366CC; + background-color: #D9E9FF; + color: black; +} + +QHeaderView { + background-color: white; +} + +QHeaderView::section { + border: none; + color: #3b3b3b; + background-color: white; + + margin-top: 5px; /* XXX: duplicated in css_constants.h */ + padding: 5px 10px; /* XXX: duplicated in css_constants.h */ + + font-size: 16px; + line-height: 24px; /* XXX: duplicated in css_constants.h */ + font-weight: 400; } QScrollBar { diff --git a/resources/css/style.css b/resources/css/style.css index ae6593e16..ee91655ef 100644 --- a/resources/css/style.css +++ b/resources/css/style.css @@ -32,8 +32,8 @@ QToolButton { SearchBar { background-color: white; - margin: 2px 5px; - border: 1px solid #ccc; + margin: 2px; /* XXX: duplicated in css_constants.h */ + border: 1px solid #ccc; /* XXX: duplicated in css_constants.h */ border-radius: 3px; max-height: 40px; @@ -43,7 +43,7 @@ SearchBar > QLabel#searchIcon { padding: 0; border: none; background-color: none; - margin: 0px 4px 0px 9px; + margin: 0px 6px; max-height: 38px; max-width: 38px; @@ -68,11 +68,6 @@ SearchBar > QToolButton { max-width: 38px; } -SearchBar > BookmarkButton { - margin-right: 3px; - margin-left: 3px; -} - SearchBar > QToolButton:pressed, SearchBar > QToolButton:hover { border: 1px solid #3366CC; @@ -103,7 +98,7 @@ TopWidget QToolButton::menu-indicator { } TopWidget QToolButton#backButton { - margin-left: 6px; /* see also: void WebViewBackMenu::showEvent(QShowEvent *) { geo.setX(geo.x() + 6); } */ + margin-left: 6px; /* XXX: duplicated in css_constants.h */ } TopWidget QToolButton#fullScreenButton { @@ -178,11 +173,10 @@ QTabWidget::pane { border-top: 1px solid #ccc; } -/* paintEvent of src/tabbar.cpp references the value of border and padding */ QTabBar::tab { - border: 1px solid #ccc; + border: 1px solid #ccc; /* XXX: duplicated in css_constants.h */ border-radius: 0; - padding: 4px; + padding: 4px; /* XXX: duplicated in css_constants.h */ padding-top: 6px; } diff --git a/src/contentmanagerdelegate.cpp b/src/contentmanagerdelegate.cpp index ce6aee6eb..60a55e4de 100644 --- a/src/contentmanagerdelegate.cpp +++ b/src/contentmanagerdelegate.cpp @@ -8,6 +8,7 @@ #include "rownode.h" #include "descriptionnode.h" #include "portutils.h" +#include "css_constants.h" ContentManagerDelegate::ContentManagerDelegate(QObject *parent) : QStyledItemDelegate(parent), baseButton(new QPushButton) @@ -284,9 +285,8 @@ QSize ContentManagerDelegate::sizeHint(const QStyleOptionViewItem &option, const const auto treeView = KiwixApp::instance()->getContentManager()->getView()->getView(); const int width = treeView->header()->length() - 2*treeView->indentation(); - // XXX: see QTreeView::padding in resources/css/_contentManager.css - const int verticalPadding = 4; - const int horizontalPadding = 4; + const int verticalPadding = CSS::ContentManagerCSS::QTreeView::padding; + const int horizontalPadding = CSS::ContentManagerCSS::QTreeView::padding; QRect descRect(0, 0, width - 2 * horizontalPadding, 0); /* Based on the rectangle and text, find the best fitting size. */ diff --git a/src/css_constants.h b/src/css_constants.h new file mode 100644 index 000000000..dba10b0b2 --- /dev/null +++ b/src/css_constants.h @@ -0,0 +1,60 @@ +#ifndef CSS_CONSTANTS_H +#define CSS_CONSTANTS_H + +/** + * @brief The need for this file is due to the lack of support to retrieve CSS + * values of Qt Widgets. Such deficiency means every code that depend on a CSS + * values need to be updated on change to that value. This file makes it so that + * a dependent CSS value only need to be updated here instead of its every use. + * + * - The CSS values in this file should create appropriate namespaces similar to + * a CSS hierarchy. + * - The classes are defined in resources/style.css unless specified. + * - Naming convention should follow Javascript's style naming. + * - Comments should be added to the duplicated css properties in css files. + */ +namespace CSS +{ + +namespace QTabBar { +namespace tab { + const int padding = 4; + const int border = 1; +} +} + +namespace SearchBar{ + const int margin = 2; + const int border = 1; +} + +namespace TopWidget { +namespace QToolButton { +namespace backButton { + const int marginLeft = 6; +} +} +} + +/* In _contentManager.css */ +namespace ContentManagerCSS { +namespace QTreeView { + const int padding = 4; +} +} + +/* In popup.css */ +namespace PopupCSS { +namespace QHeaderView { +namespace section { + const int marginTop = 5; + const int lineHeight = 24; + const int paddingVertical = 5; + const int paddingLeft = 10; +} +} +} + +} + +#endif // CSS_CONSTANTS_H diff --git a/src/kiwixapp.cpp b/src/kiwixapp.cpp index ce0712908..f9be1da4d 100644 --- a/src/kiwixapp.cpp +++ b/src/kiwixapp.cpp @@ -133,7 +133,7 @@ KiwixApp::~KiwixApp() void KiwixApp::newTab() { getTabWidget()->createNewTab(true, false); - auto& searchBarLineEdit = mp_mainWindow->getTopWidget()->getSearchBar().getLineEdit(); + auto& searchBarLineEdit = getSearchBar().getLineEdit(); searchBarLineEdit.setFocus(Qt::MouseFocusReason); searchBarLineEdit.clear(); searchBarLineEdit.clearSuggestions(); diff --git a/src/kiwixapp.h b/src/kiwixapp.h index 5454150f9..fa422fc23 100644 --- a/src/kiwixapp.h +++ b/src/kiwixapp.h @@ -83,6 +83,7 @@ class KiwixApp : public QtSingleApplication MainWindow* getMainWindow() { return mp_mainWindow; } ContentManager* getContentManager() { return mp_manager; } TabBar* getTabWidget() { return getMainWindow()->getTabBar(); } + SearchBar& getSearchBar() { return getMainWindow()->getTopWidget()->getSearchBar(); } QAction* getAction(Actions action); QString getLibraryDirectory() { return m_libraryDirectory; }; kiwix::Server* getLocalServer() { return &m_server; } diff --git a/src/searchbar.cpp b/src/searchbar.cpp index 3a2e63c4f..e1725c652 100644 --- a/src/searchbar.cpp +++ b/src/searchbar.cpp @@ -3,9 +3,14 @@ #include #include #include +#include #include "kiwixapp.h" #include "suggestionlistworker.h" +#include "css_constants.h" +#include "suggestionlistdelegate.h" + +namespace HeaderSectionCSS = CSS::PopupCSS::QHeaderView::section; BookmarkButton::BookmarkButton(QWidget *parent) : QToolButton(parent) @@ -76,10 +81,30 @@ SearchBarLineEdit::SearchBarLineEdit(QWidget *parent) : /* QCompleter's uses default list views, which do not have headers. */ m_completer.setPopup(m_suggestionView); + /* The Delegate was overwritten by setPopup(), which is not style-aware */ + m_suggestionView->setItemDelegate(new SuggestionListDelegate(this)); m_suggestionView->header()->setStretchLastSection(true); m_suggestionView->setRootIsDecorated(false); m_suggestionView->setStyleSheet(KiwixApp::instance()->parseStyleFromFile(":/css/popup.css")); + const int contentHeight = HeaderSectionCSS::lineHeight; + m_suggestionView->setIconSize(QSize(contentHeight, contentHeight)); + + /* The suggestionView sizing unfortunately is not aware of headers. We + have to do this manually. We also sized header the same as items. + */ + connect(&m_suggestionModel, &QAbstractListModel::modelReset, [=](){ + /* +1 for header. */ + const int maxItem = m_completer.maxVisibleItems(); + const int count = std::min(m_suggestionModel.rowCount(), maxItem) + 1; + + const int itemHeight = m_suggestionView->sizeHintForRow(0); + + /* Extra space styling above header and below last suggestion item. */ + const int extraMargin = 2 * HeaderSectionCSS::marginTop; + m_suggestionView->setFixedHeight(itemHeight * count + extraMargin); + }); + connect(m_suggestionView->verticalScrollBar(), &QScrollBar::valueChanged, this, &SearchBarLineEdit::onScroll); @@ -285,7 +310,7 @@ void SearchBarLineEdit::onInitialSuggestions(int) if (m_returnPressed) { openCompletion(getDefaulSuggestionIndex()); } else { - m_completer.complete(); + m_completer.complete(getCompleterRect()); /* Make row 0 appear but do not highlight it */ const auto completerFirstIdx = m_suggestionView->model()->index(0, 0); @@ -339,6 +364,43 @@ QModelIndex SearchBarLineEdit::getDefaulSuggestionIndex() const return QModelIndex(); } +/* Line edit does not span the entire searchBar. Completer is displayed + based on line edit, and thus shifting and resizing is needed. +*/ +QRect SearchBarLineEdit::getCompleterRect() const +{ + auto& searchBar = KiwixApp::instance()->getSearchBar(); + const auto& searchGeo = searchBar.geometry(); + const auto& searchLineEditGeo = searchBar.getLineEdit().geometry(); + + const int margin = CSS::SearchBar::margin; + const int border = CSS::SearchBar::border; + const int spaceAround = margin + border; + + /* Border and margin are not accounted in height and width. */ + const int top = searchGeo.height() - 2 * spaceAround; + const int width = searchGeo.width() - 2 * spaceAround; + + /* Shift completer to one of the two laterals of search bar, where which + one it shifted to dependes on whether the line edit is flipped. + */ + int left = -searchLineEditGeo.left(); + + /* When not flipped, left() is relative to within the search bar border, + thus, we shift by spaceAround to match the side of search bar. + + When flipped, the completer starts at the right end of the search bar + We shift it by width to make the completer start at left lateral of + search bar. Since in a flipped state, left() also considered the opposite + side's border, which means we need to shift by a border width in + addition to spaceAround. + */ + left += isRightToLeft() ? -width + spaceAround + border : spaceAround; + + /* Can't set height to 0. Will cause rectangle to be ignored. */ + return QRect(QPoint(left, top), QSize(width, 1)); +} + SearchBar::SearchBar(QWidget *parent) : QToolBar(parent), m_searchBarLineEdit(this), diff --git a/src/searchbar.h b/src/searchbar.h index b75983008..516f5eb05 100644 --- a/src/searchbar.h +++ b/src/searchbar.h @@ -68,6 +68,7 @@ private slots: void fetchSuggestions(NewSuggestionHandlerFuncPtr callback); QModelIndex getDefaulSuggestionIndex() const; + QRect getCompleterRect() const; }; diff --git a/src/suggestionlistdelegate.cpp b/src/suggestionlistdelegate.cpp new file mode 100644 index 000000000..954f7782e --- /dev/null +++ b/src/suggestionlistdelegate.cpp @@ -0,0 +1,90 @@ +#include "suggestionlistdelegate.h" +#include "kiwixapp.h" +#include "css_constants.h" + +#include + +namespace HeaderSectionCSS = CSS::PopupCSS::QHeaderView::section; + +void SuggestionListDelegate::paint(QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + /* Paint without text and icon */ + QStyleOptionViewItem opt(option); + QStyledItemDelegate::paint(painter, opt, QModelIndex()); + + paintIcon(painter, opt, index); + paintText(painter, opt, index); +} + +void SuggestionListDelegate::paintIcon(QPainter *p, + const QStyleOptionViewItem &opt, + const QModelIndex &index) const +{ + QRect pixmapRect = opt.rect; + const int lineHeight = HeaderSectionCSS::lineHeight; + const int paddingLeft = HeaderSectionCSS::paddingLeft; + + const QSize mapSize = QSize(lineHeight, lineHeight); + auto pixmap = index.data(Qt::DecorationRole).value().pixmap(mapSize); + + /* Align icon to Header text */ + if (KiwixApp::isRightToLeft()) + { + const int rightEnd = pixmapRect.width() - mapSize.width(); + pixmapRect.setX(pixmapRect.x() + rightEnd - paddingLeft); + } + else + pixmapRect.setX(pixmapRect.x() + paddingLeft); + + /* Align middle */ + pixmapRect.setY(pixmapRect.y() + (pixmapRect.height() - mapSize.height()) / 2); + pixmapRect.setSize(mapSize); + p->drawPixmap(pixmapRect, pixmap); +} + +void SuggestionListDelegate::paintText(QPainter *p, + const QStyleOptionViewItem &opt, + const QModelIndex &index) const +{ + auto& searchBar = KiwixApp::instance()->getSearchBar(); + const auto& lineEditGeo = searchBar.getLineEdit().geometry(); + + /* Remove border from left() since left is is with respect to border. Detail + reason on how this calculation comes about can be seen in + SearchBarLineEdit::getCompleterRect(); + */ + const int left = lineEditGeo.left() - CSS::SearchBar::border; + QRect textRect = opt.rect; + if (KiwixApp::isRightToLeft()) + { + const auto& searchGeo = searchBar.geometry(); + const int right = searchGeo.width() - left - lineEditGeo.width(); + textRect.setWidth(textRect.width() - right); + } + else + textRect.setX(textRect.x() + left); + + const int flag = {Qt::AlignVCenter | Qt::AlignLeading}; + 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); + 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() + : elidedText.trimmed() + "(...)"; + p->drawText(textRect, flag, elidedText); + } + else + p->drawText(textRect, flag, text); +} diff --git a/src/suggestionlistdelegate.h b/src/suggestionlistdelegate.h new file mode 100644 index 000000000..426964eb2 --- /dev/null +++ b/src/suggestionlistdelegate.h @@ -0,0 +1,17 @@ +#ifndef SUGGESTIONLISTDELEGATE_H +#define SUGGESTIONLISTDELEGATE_H + +#include + +class SuggestionListDelegate : public QStyledItemDelegate +{ +public: + SuggestionListDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}; + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + +private: + void paintIcon(QPainter *p, const QStyleOptionViewItem &opt, const QModelIndex &index) const; + void paintText(QPainter *p, const QStyleOptionViewItem &opt, const QModelIndex &index) const; +}; + +#endif // SUGGESTIONLISTDELEGATE_H diff --git a/src/suggestionlistmodel.cpp b/src/suggestionlistmodel.cpp index 6fe99dad1..716b0f748 100644 --- a/src/suggestionlistmodel.cpp +++ b/src/suggestionlistmodel.cpp @@ -1,8 +1,11 @@ #include "suggestionlistmodel.h" #include "kiwixapp.h" +#include "css_constants.h" #include +namespace HeaderSectionCSS = CSS::PopupCSS::QHeaderView::section; + QString getZimIdFromUrl(QUrl url); SuggestionListModel::SuggestionListModel(QObject *parent) @@ -35,9 +38,20 @@ QVariant SuggestionListModel::data(const QModelIndex &index, int role) const case Qt::UserRole: return m_suggestions.at(row).url; case Qt::DecorationRole: + { const auto library = KiwixApp::instance()->getLibrary(); const auto zimId = getZimIdFromUrl(m_suggestions.at(row).url); return library->getBookIcon(zimId); + } + case Qt::SizeHintRole: + { + /* Padding in css can't change height, we have to achieve padding + by increasing height. + */ + const int padding = HeaderSectionCSS::paddingVertical; + const int lineHeight = HeaderSectionCSS::lineHeight; + return QSize(0, lineHeight + 2 * padding); + } } return QVariant(); } diff --git a/src/suggestionlistworker.h b/src/suggestionlistworker.h index 4f51b0234..18a80425d 100644 --- a/src/suggestionlistworker.h +++ b/src/suggestionlistworker.h @@ -2,8 +2,7 @@ #define SUGGESTIONLISTWORKER_H #include - -struct SuggestionData; +#include "suggestionlistmodel.h" class SuggestionListWorker : public QThread { diff --git a/src/tabbar.cpp b/src/tabbar.cpp index 71b5c538c..3c7632e09 100644 --- a/src/tabbar.cpp +++ b/src/tabbar.cpp @@ -3,6 +3,7 @@ class QMenu; #include "tabbar.h" #include "kiwixapp.h" +#include "css_constants.h" #include #include #include @@ -439,12 +440,10 @@ void TabBar::paintEvent(QPaintEvent *e) bool textRightToLeft = tab_title.isRightToLeft(); bool appRightToLeft = QWidget::isRightToLeft(); - // See QTabBar::tab::padding value in resources/css/style.css - const int padding = 4; + const int padding = CSS::QTabBar::tab::padding; QRect tabTextRect = style()->subElementRect(QStyle::SE_TabBarTabText, &tabopt, this); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - // See QTabBar::tab::border value in resources/css/style.css - const int border = 1; + const int border = CSS::QTabBar::tab::border; // Add Padding to left, right. Padding is 4px. Add 5 to account for // Extra pixel from border. diff --git a/src/webview.cpp b/src/webview.cpp index 378179949..de42da42c 100644 --- a/src/webview.cpp +++ b/src/webview.cpp @@ -7,6 +7,7 @@ class QMenu; #include #include "kiwixapp.h" #include "webpage.h" +#include "css_constants.h" #include #include #include @@ -35,7 +36,9 @@ void WebViewBackMenu::showEvent(QShowEvent *) */ QRect geo = geometry(); - geo.moveLeft(geo.left() + 6); // see also: style.css: QToolButton#backButton { margin-left: 6px; } + + const int marginLeft = CSS::TopWidget::QToolButton::backButton::marginLeft; + geo.moveLeft(geo.left() + marginLeft); geo.moveTop(geo.top() + 2); setGeometry(geo); }