|
| 1 | +#include <QListWidget> |
| 2 | +#include <QMenu> |
| 3 | +#include <QWidgetAction> |
| 4 | +#include <QButtonGroup> |
| 5 | +#include <QRadioButton> |
| 6 | +#include "kiwixapp.h" |
| 7 | +#include "multizimbutton.h" |
| 8 | +#include "css_constants.h" |
| 9 | + |
| 10 | +QString getElidedText(const QFont& font, int length, const QString& text); |
| 11 | + |
| 12 | +MultiZimButton::MultiZimButton(QWidget *parent) : |
| 13 | + QToolButton(parent), |
| 14 | + mp_buttonList(new QListWidget), |
| 15 | + mp_radioButtonGroup(new QButtonGroup(this)) |
| 16 | +{ |
| 17 | + setMenu(new QMenu(this)); |
| 18 | + setPopupMode(QToolButton::InstantPopup); |
| 19 | + setDefaultAction(KiwixApp::instance()->getAction(KiwixApp::OpenMultiZimAction)); |
| 20 | + connect(this, &QToolButton::triggered, this, &MultiZimButton::showMenu); |
| 21 | + |
| 22 | + const auto popupAction = new QWidgetAction(menu()); |
| 23 | + popupAction->setDefaultWidget(mp_buttonList); |
| 24 | + menu()->addAction(popupAction); |
| 25 | + |
| 26 | + connect(mp_buttonList, &QListWidget::currentRowChanged, this, [=](int row){ |
| 27 | + if (const auto widget = getZimWidget(row)) |
| 28 | + widget->getRadioButton()->setChecked(true); |
| 29 | + }); |
| 30 | +} |
| 31 | + |
| 32 | +void MultiZimButton::updateDisplay() |
| 33 | +{ |
| 34 | + mp_buttonList->clear(); |
| 35 | + for (const auto& button : mp_radioButtonGroup->buttons()) |
| 36 | + mp_radioButtonGroup->removeButton(button); |
| 37 | + |
| 38 | + const auto library = KiwixApp::instance()->getLibrary(); |
| 39 | + const auto view = KiwixApp::instance()->getTabWidget()->currentWebView(); |
| 40 | + QListWidgetItem* currentItem = nullptr; |
| 41 | + QIcon currentIcon; |
| 42 | + const int paddingTopBot = CSS::MultiZimButton::QListWidget::paddingVertical * 2; |
| 43 | + const int itemHeight = paddingTopBot + CSS::ZimItemWidget::QLabel::lineHeight; |
| 44 | + for (const auto& bookId : library->getBookIds()) |
| 45 | + { |
| 46 | + try |
| 47 | + { |
| 48 | + library->getArchive(bookId); |
| 49 | + } catch (...) { continue; } |
| 50 | + |
| 51 | + const QString bookTitle = QString::fromStdString(library->getBookById(bookId).getTitle()); |
| 52 | + const QIcon zimIcon = library->getBookIcon(bookId); |
| 53 | + |
| 54 | + const auto item = new QListWidgetItem(); |
| 55 | + item->setData(Qt::UserRole, bookId); |
| 56 | + item->setData(Qt::DisplayRole, bookTitle); |
| 57 | + item->setSizeHint(QSize(0, itemHeight)); |
| 58 | + |
| 59 | + if (view && view->zimId() == bookId) |
| 60 | + { |
| 61 | + currentItem = item; |
| 62 | + currentIcon = zimIcon; |
| 63 | + continue; |
| 64 | + } |
| 65 | + |
| 66 | + mp_buttonList->addItem(item); |
| 67 | + setItemZimWidget(item, bookTitle, zimIcon); |
| 68 | + } |
| 69 | + |
| 70 | + mp_buttonList->sortItems(); |
| 71 | + if (currentItem) |
| 72 | + { |
| 73 | + mp_buttonList->insertItem(0, currentItem); |
| 74 | + |
| 75 | + const auto title = currentItem->data(Qt::DisplayRole).toString(); |
| 76 | + setItemZimWidget(currentItem, "*" + title, currentIcon); |
| 77 | + } |
| 78 | + |
| 79 | + /* Display should not be used other than for sorting. */ |
| 80 | + for (int i = 0; i < mp_buttonList->count(); i++) |
| 81 | + mp_buttonList->item(i)->setData(Qt::DisplayRole, QVariant()); |
| 82 | + |
| 83 | + setDisabled(mp_buttonList->model()->rowCount() == 0); |
| 84 | + |
| 85 | + mp_buttonList->scrollToTop(); |
| 86 | + mp_buttonList->setCurrentRow(0); |
| 87 | + |
| 88 | + /* We set a maximum display height for list. Respect padding. */ |
| 89 | + const int listHeight = itemHeight * std::min(7, mp_buttonList->count()); |
| 90 | + mp_buttonList->setFixedHeight(listHeight + paddingTopBot); |
| 91 | + mp_buttonList->setFixedWidth(menu()->width()); |
| 92 | +} |
| 93 | + |
| 94 | +QStringList MultiZimButton::getZimIds() const |
| 95 | +{ |
| 96 | + QStringList idList; |
| 97 | + for (int row = 0; row < mp_buttonList->count(); row++) |
| 98 | + { |
| 99 | + const auto widget = getZimWidget(row); |
| 100 | + if (widget && widget->getRadioButton()->isChecked()) |
| 101 | + idList.append(mp_buttonList->item(row)->data(Qt::UserRole).toString()); |
| 102 | + } |
| 103 | + return idList; |
| 104 | +} |
| 105 | + |
| 106 | +ZimItemWidget *MultiZimButton::getZimWidget(int row) const |
| 107 | +{ |
| 108 | + const auto widget = mp_buttonList->itemWidget(mp_buttonList->item(row)); |
| 109 | + return qobject_cast<ZimItemWidget *>(widget); |
| 110 | +} |
| 111 | + |
| 112 | +void MultiZimButton::setItemZimWidget(QListWidgetItem *item, |
| 113 | + const QString &title, const QIcon &icon) |
| 114 | +{ |
| 115 | + const auto zimWidget = new ZimItemWidget(title, icon); |
| 116 | + mp_radioButtonGroup->addButton(zimWidget->getRadioButton()); |
| 117 | + mp_buttonList->setItemWidget(item, zimWidget); |
| 118 | +} |
| 119 | + |
| 120 | +ZimItemWidget::ZimItemWidget(QString text, QIcon icon, QWidget *parent) : |
| 121 | + QWidget(parent), |
| 122 | + textLabel(new QLabel(this)), |
| 123 | + iconLabel(new QLabel(this)), |
| 124 | + radioBt(new QRadioButton(this)) |
| 125 | +{ |
| 126 | + setLayout(new QHBoxLayout); |
| 127 | + |
| 128 | + const int paddingHorizontal = CSS::MultiZimButton::QListWidget::item::paddingHorizontal; |
| 129 | + layout()->setSpacing(paddingHorizontal); |
| 130 | + layout()->setContentsMargins(0, 0, 0, 0); |
| 131 | + |
| 132 | + const int iconWidth = CSS::ZimItemWidget::QLabel::lineHeight; |
| 133 | + const QSize iconSize = QSize(iconWidth, iconWidth); |
| 134 | + iconLabel->setPixmap(icon.pixmap(iconSize)); |
| 135 | + |
| 136 | + /* Align text on same side irregardless of text direction. */ |
| 137 | + const bool needAlignReverse = KiwixApp::isRightToLeft() == text.isRightToLeft(); |
| 138 | + const auto align = needAlignReverse ? Qt::AlignLeft : Qt::AlignRight; |
| 139 | + textLabel->setAlignment({Qt::AlignVCenter | align}); |
| 140 | + |
| 141 | + /* Need to align checkmark with select all button. Avoid scroller from |
| 142 | + changing checkmark position by always leaving out space on scroller's |
| 143 | + side. Do this by align items to the other side and reducing the total |
| 144 | + length. textLabel is the only expandable element here. |
| 145 | +
|
| 146 | + We set textLabel width to make sure the entire length always leave out |
| 147 | + a fixed amount of white space for scroller. |
| 148 | + */ |
| 149 | + layout()->setAlignment({Qt::AlignVCenter | Qt::AlignLeading}); |
| 150 | + const auto menu = KiwixApp::instance()->getSearchBar().getMultiZimButton().menu(); |
| 151 | + const int iconAndCheckerWidth = iconWidth * 2; |
| 152 | + const int totalSpacing = paddingHorizontal * 4; |
| 153 | + |
| 154 | + /* Add an extra border to counteract item border on one side */ |
| 155 | + const int border = CSS::MultiZimButton::QListWidget::item::border; |
| 156 | + const int scrollerWidth = CSS::MultiZimButton::QScrollBar::width; |
| 157 | + const int contentWidthExcludeText = iconAndCheckerWidth + totalSpacing + scrollerWidth + border; |
| 158 | + const int labelWidth = menu->width() - contentWidthExcludeText; |
| 159 | + textLabel->setFixedWidth(labelWidth); |
| 160 | + |
| 161 | + QString elidedText = getElidedText(textLabel->font(), labelWidth, text); |
| 162 | + textLabel->setText(elidedText == text ? text : elidedText.trimmed() + "(...)"); |
| 163 | + |
| 164 | + layout()->addWidget(iconLabel); |
| 165 | + layout()->addWidget(textLabel); |
| 166 | + layout()->addWidget(radioBt); |
| 167 | +} |
0 commit comments