File indexing completed on 2024-04-28 15:51:45

0001 /*
0002     SPDX-FileCopyrightText: 2005 Enrico Ros <eros.kde@email.it>
0003     SPDX-FileCopyrightText: 2006 Albert Astals Cid <aacid@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "minibar.h"
0009 
0010 // qt / kde includes
0011 #include <KLocalizedString>
0012 #include <QIcon>
0013 #include <QToolButton>
0014 #include <kacceleratormanager.h>
0015 #include <kicontheme.h>
0016 #include <klineedit.h>
0017 #include <qapplication.h>
0018 #include <qevent.h>
0019 #include <qframe.h>
0020 #include <qlabel.h>
0021 #include <qlayout.h>
0022 #include <qpainter.h>
0023 #include <qpushbutton.h>
0024 #include <qtoolbar.h>
0025 #include <qvalidator.h>
0026 
0027 // local includes
0028 #include "core/document.h"
0029 #include "core/page.h"
0030 
0031 // [private widget] a flat qpushbutton that enlights on hover
0032 class HoverButton : public QToolButton
0033 {
0034     Q_OBJECT
0035 public:
0036     explicit HoverButton(QWidget *parent);
0037 };
0038 
0039 MiniBarLogic::MiniBarLogic(QObject *parent, Okular::Document *document)
0040     : QObject(parent)
0041     , m_document(document)
0042 {
0043 }
0044 
0045 MiniBarLogic::~MiniBarLogic()
0046 {
0047     m_document->removeObserver(this);
0048 }
0049 
0050 void MiniBarLogic::addMiniBar(MiniBar *miniBar)
0051 {
0052     m_miniBars.insert(miniBar);
0053 }
0054 
0055 void MiniBarLogic::removeMiniBar(MiniBar *miniBar)
0056 {
0057     m_miniBars.remove(miniBar);
0058 }
0059 
0060 Okular::Document *MiniBarLogic::document() const
0061 {
0062     return m_document;
0063 }
0064 
0065 int MiniBarLogic::currentPage() const
0066 {
0067     return m_document->currentPage();
0068 }
0069 
0070 void MiniBarLogic::notifySetup(const QVector<Okular::Page *> &pageVector, int setupFlags)
0071 {
0072     // only process data when document changes
0073     if (!(setupFlags & Okular::DocumentObserver::DocumentChanged)) {
0074         return;
0075     }
0076 
0077     // if document is closed or has no pages, hide widget
0078     const int pages = pageVector.count();
0079     if (pages < 1) {
0080         for (MiniBar *miniBar : std::as_const(m_miniBars)) {
0081             miniBar->setEnabled(false);
0082         }
0083         return;
0084     }
0085 
0086     bool labelsDiffer = false;
0087     for (const Okular::Page *page : pageVector) {
0088         if (!page->label().isEmpty()) {
0089             if (page->label().toInt() != (page->number() + 1)) {
0090                 labelsDiffer = true;
0091             }
0092         }
0093     }
0094 
0095     const QString pagesString = QString::number(pages);
0096 
0097     // In some documents, there may be labels which are longer than pagesString. Here, we check all the page labels, and if any of the labels are longer than pagesString, we use that string for sizing m_pageLabelEdit
0098     QString pagesOrLabelString = pagesString;
0099     if (labelsDiffer) {
0100         for (const Okular::Page *page : pageVector) {
0101             if (!page->label().isEmpty()) {
0102                 MiniBar *miniBar = *m_miniBars.constBegin(); // We assume all the minibars have the same font, font size etc, so we just take one minibar for the purpose of calculating the displayed length of the page labels.
0103                 if (miniBar->fontMetrics().horizontalAdvance(page->label()) > miniBar->fontMetrics().horizontalAdvance(pagesOrLabelString)) {
0104                     pagesOrLabelString = page->label();
0105                 }
0106             }
0107         }
0108     }
0109 
0110     for (MiniBar *miniBar : std::as_const(m_miniBars)) {
0111         // resize width of widgets
0112         miniBar->resizeForPage(pages, pagesOrLabelString);
0113 
0114         // update child widgets
0115         miniBar->m_pageLabelEdit->setPageLabels(pageVector);
0116         miniBar->m_pageNumberEdit->setPagesNumber(pages);
0117         miniBar->m_pagesButton->setText(pagesString);
0118         miniBar->m_prevButton->setEnabled(false);
0119         miniBar->m_nextButton->setEnabled(false);
0120         miniBar->m_pageLabelEdit->setVisible(labelsDiffer);
0121         miniBar->m_pageNumberLabel->setVisible(labelsDiffer);
0122         miniBar->m_pageNumberEdit->setVisible(!labelsDiffer);
0123 
0124         miniBar->adjustSize();
0125 
0126         miniBar->setEnabled(true);
0127     }
0128 }
0129 
0130 void MiniBarLogic::notifyCurrentPageChanged(int previousPage, int currentPage)
0131 {
0132     Q_UNUSED(previousPage)
0133 
0134     // get current page number
0135     const int pages = m_document->pages();
0136 
0137     // if the document is opened and page is changed
0138     if (pages > 0) {
0139         const QString pageNumber = QString::number(currentPage + 1);
0140         const QString pageLabel = m_document->page(currentPage)->label();
0141 
0142         for (MiniBar *miniBar : std::as_const(m_miniBars)) {
0143             // update prev/next button state
0144             miniBar->m_prevButton->setEnabled(currentPage > 0);
0145             miniBar->m_nextButton->setEnabled(currentPage < (pages - 1));
0146             // update text on widgets
0147             miniBar->m_pageNumberEdit->setText(pageNumber);
0148             miniBar->m_pageNumberLabel->setText(pageNumber);
0149             miniBar->m_pageLabelEdit->setText(pageLabel);
0150         }
0151     }
0152 }
0153 
0154 /** MiniBar **/
0155 
0156 MiniBar::MiniBar(QWidget *parent, MiniBarLogic *miniBarLogic)
0157     : QWidget(parent)
0158     , m_miniBarLogic(miniBarLogic)
0159     , m_oldToolbarParent(nullptr)
0160 {
0161     setObjectName(QStringLiteral("miniBar"));
0162 
0163     m_miniBarLogic->addMiniBar(this);
0164 
0165     QHBoxLayout *horLayout = new QHBoxLayout(this);
0166 
0167     horLayout->setContentsMargins(0, 0, 0, 0);
0168     horLayout->setSpacing(3);
0169 
0170     QSize buttonSize(KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium);
0171     // bottom: left prev_page button
0172     m_prevButton = new HoverButton(this);
0173     m_prevButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
0174     m_prevButton->setIconSize(buttonSize);
0175     horLayout->addWidget(m_prevButton);
0176     // bottom: left lineEdit (current page box)
0177     m_pageNumberEdit = new PageNumberEdit(this);
0178     horLayout->addWidget(m_pageNumberEdit);
0179     m_pageNumberEdit->installEventFilter(this);
0180     // bottom: left labelWidget (current page label)
0181     m_pageLabelEdit = new PageLabelEdit(this);
0182     horLayout->addWidget(m_pageLabelEdit);
0183     m_pageLabelEdit->installEventFilter(this);
0184     // bottom: left labelWidget (current page label)
0185     m_pageNumberLabel = new QLabel(this);
0186     m_pageNumberLabel->setAlignment(Qt::AlignCenter);
0187     horLayout->addWidget(m_pageNumberLabel);
0188     // bottom: central 'of' label
0189     horLayout->addSpacing(5);
0190     horLayout->addWidget(new QLabel(i18nc("Layouted like: '5 [pages] of 10'", "of"), this));
0191     // bottom: right button
0192     m_pagesButton = new HoverButton(this);
0193     horLayout->addWidget(m_pagesButton);
0194     // bottom: right next_page button
0195     m_nextButton = new HoverButton(this);
0196     m_nextButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
0197     m_nextButton->setIconSize(buttonSize);
0198     horLayout->addWidget(m_nextButton);
0199 
0200     QSizePolicy sp = sizePolicy();
0201     sp.setHorizontalPolicy(QSizePolicy::Fixed);
0202     sp.setVerticalPolicy(QSizePolicy::Fixed);
0203     setSizePolicy(sp);
0204 
0205     // resize width of widgets
0206     resizeForPage(0, QString());
0207 
0208     // connect signals from child widgets to internal handlers / signals bouncers
0209     connect(m_pageNumberEdit, &PageNumberEdit::returnKeyPressed, this, &MiniBar::slotChangePageFromReturn);
0210     connect(m_pageLabelEdit, &PageLabelEdit::pageNumberChosen, this, &MiniBar::slotChangePage);
0211     connect(m_pagesButton, &QAbstractButton::clicked, this, &MiniBar::gotoPage);
0212     connect(m_prevButton, &QAbstractButton::clicked, this, &MiniBar::prevPage);
0213     connect(m_nextButton, &QAbstractButton::clicked, this, &MiniBar::nextPage);
0214 
0215     adjustSize();
0216 
0217     // widget starts disabled (will be enabled after opening a document)
0218     setEnabled(false);
0219 }
0220 
0221 MiniBar::~MiniBar()
0222 {
0223     m_miniBarLogic->removeMiniBar(this);
0224 }
0225 
0226 void MiniBar::changeEvent(QEvent *event)
0227 {
0228     if (event->type() == QEvent::ParentChange) {
0229         QToolBar *tb = dynamic_cast<QToolBar *>(parent());
0230         if (tb != m_oldToolbarParent) {
0231             if (m_oldToolbarParent) {
0232                 disconnect(m_oldToolbarParent, &QToolBar::iconSizeChanged, this, &MiniBar::slotToolBarIconSizeChanged);
0233             }
0234             m_oldToolbarParent = tb;
0235             if (tb) {
0236                 connect(tb, &QToolBar::iconSizeChanged, this, &MiniBar::slotToolBarIconSizeChanged);
0237                 slotToolBarIconSizeChanged();
0238             }
0239         }
0240     }
0241 }
0242 
0243 bool MiniBar::eventFilter(QObject *target, QEvent *event)
0244 {
0245     if (target == m_pageNumberEdit || target == m_pageLabelEdit) {
0246         if (event->type() == QEvent::KeyPress) {
0247             QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
0248             int key = keyEvent->key();
0249             if (key == Qt::Key_PageUp || key == Qt::Key_PageDown || key == Qt::Key_Up || key == Qt::Key_Down) {
0250                 Q_EMIT forwardKeyPressEvent(keyEvent);
0251                 return true;
0252             }
0253         }
0254     }
0255     return false;
0256 }
0257 
0258 void MiniBar::slotChangePageFromReturn()
0259 {
0260     // get text from the lineEdit
0261     const QString pageNumber = m_pageNumberEdit->text();
0262 
0263     // convert it to page number and go to that page
0264     bool ok;
0265     int number = pageNumber.toInt(&ok) - 1;
0266     if (ok && number >= 0 && number < (int)m_miniBarLogic->document()->pages() && number != m_miniBarLogic->currentPage()) {
0267         slotChangePage(number);
0268     }
0269 }
0270 
0271 void MiniBar::slotChangePage(int pageNumber)
0272 {
0273     m_miniBarLogic->document()->setViewportPage(pageNumber);
0274     m_pageNumberEdit->clearFocus();
0275     m_pageLabelEdit->clearFocus();
0276 }
0277 
0278 void MiniBar::slotEmitNextPage()
0279 {
0280     // Q_EMIT signal
0281     Q_EMIT nextPage();
0282 }
0283 
0284 void MiniBar::slotEmitPrevPage()
0285 {
0286     // Q_EMIT signal
0287     Q_EMIT prevPage();
0288 }
0289 
0290 void MiniBar::slotToolBarIconSizeChanged()
0291 {
0292     const QSize buttonSize = m_oldToolbarParent->iconSize();
0293     m_prevButton->setIconSize(buttonSize);
0294     m_nextButton->setIconSize(buttonSize);
0295 }
0296 
0297 void MiniBar::resizeForPage(int pages, const QString &pagesOrLabelString)
0298 {
0299     const int numberWidth = 10 + fontMetrics().horizontalAdvance(QString::number(pages));
0300     const int labelWidth = 10 + fontMetrics().horizontalAdvance(pagesOrLabelString);
0301     m_pageNumberEdit->setMinimumWidth(numberWidth);
0302     m_pageNumberEdit->setMaximumWidth(2 * numberWidth);
0303     m_pageLabelEdit->setMinimumWidth(labelWidth);
0304     m_pageLabelEdit->setMaximumWidth(2 * labelWidth);
0305     m_pageNumberLabel->setMinimumWidth(numberWidth);
0306     m_pageNumberLabel->setMaximumWidth(2 * numberWidth);
0307     m_pagesButton->setMinimumWidth(numberWidth);
0308     m_pagesButton->setMaximumWidth(2 * numberWidth);
0309 }
0310 
0311 /** ProgressWidget **/
0312 
0313 ProgressWidget::ProgressWidget(QWidget *parent, Okular::Document *document)
0314     : QWidget(parent)
0315     , m_document(document)
0316     , m_progressPercentage(-1)
0317 {
0318     setObjectName(QStringLiteral("progress"));
0319     setAttribute(Qt::WA_OpaquePaintEvent, true);
0320     setFixedHeight(4);
0321     setMouseTracking(true);
0322 }
0323 
0324 ProgressWidget::~ProgressWidget()
0325 {
0326     m_document->removeObserver(this);
0327 }
0328 
0329 void ProgressWidget::notifyCurrentPageChanged(int previousPage, int currentPage)
0330 {
0331     Q_UNUSED(previousPage)
0332 
0333     // get current page number
0334     int pages = m_document->pages();
0335 
0336     // if the document is opened and page is changed
0337     if (pages > 0) {
0338         // update percentage
0339         const float percentage = pages < 2 ? 1.0 : (float)currentPage / (float)(pages - 1);
0340         setProgress(percentage);
0341     }
0342 }
0343 
0344 void ProgressWidget::setProgress(float percentage)
0345 {
0346     m_progressPercentage = percentage;
0347     update();
0348 }
0349 
0350 void ProgressWidget::slotGotoNormalizedPage(float index)
0351 {
0352     // figure out page number and go to that page
0353     int number = (int)(index * (float)m_document->pages());
0354     if (number >= 0 && number < (int)m_document->pages() && number != (int)m_document->currentPage()) {
0355         m_document->setViewportPage(number);
0356     }
0357 }
0358 
0359 void ProgressWidget::mouseMoveEvent(QMouseEvent *e)
0360 {
0361     if ((QApplication::mouseButtons() & Qt::LeftButton) && width() > 0) {
0362         slotGotoNormalizedPage((float)(QApplication::isRightToLeft() ? width() - e->position().x() : e->position().x()) / (float)width());
0363     }
0364 }
0365 
0366 void ProgressWidget::mousePressEvent(QMouseEvent *e)
0367 {
0368     if (e->button() == Qt::LeftButton && width() > 0) {
0369         slotGotoNormalizedPage((float)(QApplication::isRightToLeft() ? width() - e->position().x() : e->position().x()) / (float)width());
0370     }
0371 }
0372 
0373 void ProgressWidget::wheelEvent(QWheelEvent *e)
0374 {
0375     if (e->angleDelta().y() > 0) {
0376         Q_EMIT nextPage();
0377     } else {
0378         Q_EMIT prevPage();
0379     }
0380 }
0381 
0382 void ProgressWidget::paintEvent(QPaintEvent *e)
0383 {
0384     QPainter p(this);
0385 
0386     if (m_progressPercentage < 0.0) {
0387         p.fillRect(rect(), palette().color(QPalette::Active, QPalette::HighlightedText));
0388         return;
0389     }
0390 
0391     // find out the 'fill' and the 'clear' rectangles
0392     int w = width(), h = height(), l = (int)((float)w * m_progressPercentage);
0393     QRect cRect = (QApplication::isRightToLeft() ? QRect(0, 0, w - l, h) : QRect(l, 0, w - l, h)).intersected(e->rect());
0394     QRect fRect = (QApplication::isRightToLeft() ? QRect(w - l, 0, l, h) : QRect(0, 0, l, h)).intersected(e->rect());
0395 
0396     QPalette pal = palette();
0397     // paint clear rect
0398     if (cRect.isValid()) {
0399         p.fillRect(cRect, pal.color(QPalette::Active, QPalette::HighlightedText));
0400     }
0401     // draw a frame-like outline
0402     // p.setPen( palette().active().mid() );
0403     // p.drawRect( 0,0, w, h );
0404     // paint fill rect
0405     if (fRect.isValid()) {
0406         p.fillRect(fRect, pal.color(QPalette::Active, QPalette::Highlight));
0407     }
0408     if (l && l != w) {
0409         p.setPen(pal.color(QPalette::Active, QPalette::Highlight).darker(120));
0410         int delta = QApplication::isRightToLeft() ? w - l : l;
0411         p.drawLine(delta, 0, delta, h);
0412     }
0413 }
0414 
0415 /** PageLabelEdit **/
0416 
0417 PageLabelEdit::PageLabelEdit(MiniBar *parent)
0418     : PagesEdit(parent)
0419 {
0420     setVisible(false);
0421     connect(this, &PageLabelEdit::returnKeyPressed, this, &PageLabelEdit::pageChosen);
0422 }
0423 
0424 void PageLabelEdit::setText(const QString &newText)
0425 {
0426     m_lastLabel = newText;
0427     PagesEdit::setText(newText);
0428 }
0429 
0430 void PageLabelEdit::setPageLabels(const QVector<Okular::Page *> &pageVector)
0431 {
0432     m_labelPageMap.clear();
0433     completionObject()->clear();
0434     for (const Okular::Page *page : pageVector) {
0435         if (!page->label().isEmpty()) {
0436             m_labelPageMap.insert(page->label(), page->number());
0437             bool ok;
0438             page->label().toInt(&ok);
0439             if (!ok) {
0440                 // Only add to the completion objects labels that are not numbers
0441                 completionObject()->addItem(page->label());
0442             }
0443         }
0444     }
0445 }
0446 
0447 void PageLabelEdit::pageChosen()
0448 {
0449     const QString newInput = text();
0450     const int pageNumber = m_labelPageMap.value(newInput, -1);
0451     if (pageNumber != -1) {
0452         Q_EMIT pageNumberChosen(pageNumber);
0453     } else {
0454         setText(m_lastLabel);
0455     }
0456 }
0457 
0458 /** PageNumberEdit **/
0459 
0460 PageNumberEdit::PageNumberEdit(MiniBar *miniBar)
0461     : PagesEdit(miniBar)
0462 {
0463     // use an integer validator
0464     m_validator = new QIntValidator(1, 1, this);
0465     setValidator(m_validator);
0466 }
0467 
0468 void PageNumberEdit::setPagesNumber(int pages)
0469 {
0470     m_validator->setTop(pages);
0471 }
0472 
0473 /** PagesEdit **/
0474 
0475 PagesEdit::PagesEdit(MiniBar *parent)
0476     : KLineEdit(parent)
0477     , m_miniBar(parent)
0478     , m_eatClick(false)
0479 {
0480     // customize text properties
0481     setAlignment(Qt::AlignCenter);
0482 
0483     // send a focus out event
0484     QFocusEvent fe(QEvent::FocusOut);
0485     QApplication::sendEvent(this, &fe);
0486 }
0487 
0488 void PagesEdit::setText(const QString &newText)
0489 {
0490     // call default handler if hasn't focus
0491     if (!hasFocus()) {
0492         KLineEdit::setText(newText);
0493     }
0494     // else preserve existing selection
0495     else {
0496         // save selection and adapt it to the new text length
0497         int selectionLength = selectedText().length();
0498         const bool allSelected = (selectionLength == text().length());
0499         if (allSelected) {
0500             KLineEdit::setText(newText);
0501             selectAll();
0502         } else {
0503             int newSelectionStart = newText.length() - text().length() + selectionStart();
0504             if (newSelectionStart < 0) {
0505                 // the new text is shorter than the old one, and the front part, which is "cut off", is selected
0506                 // shorten the selection accordingly
0507                 selectionLength += newSelectionStart;
0508                 newSelectionStart = 0;
0509             }
0510             KLineEdit::setText(newText);
0511             setSelection(newSelectionStart, selectionLength);
0512         }
0513     }
0514 }
0515 
0516 void PagesEdit::updatePalette()
0517 {
0518     QPalette pal;
0519 
0520     if (hasFocus()) {
0521         pal.setColor(QPalette::Active, QPalette::Base, QApplication::palette().color(QPalette::Active, QPalette::Base));
0522     } else {
0523         pal.setColor(QPalette::Base, QApplication::palette().color(QPalette::Base).darker(102));
0524     }
0525 
0526     setPalette(pal);
0527 }
0528 
0529 bool PagesEdit::event(QEvent *e)
0530 {
0531     if (e->type() == QEvent::ApplicationPaletteChange) {
0532         updatePalette();
0533     }
0534     return KLineEdit::event(e);
0535 }
0536 
0537 void PagesEdit::focusInEvent(QFocusEvent *e)
0538 {
0539     // select all text
0540     selectAll();
0541     if (e->reason() == Qt::MouseFocusReason) {
0542         m_eatClick = true;
0543     }
0544     // change background color to the default 'edit' color
0545     updatePalette();
0546     // call default handler
0547     KLineEdit::focusInEvent(e);
0548 }
0549 
0550 void PagesEdit::focusOutEvent(QFocusEvent *e)
0551 {
0552     // change background color to a dark tone
0553     updatePalette();
0554     // call default handler
0555     KLineEdit::focusOutEvent(e);
0556 }
0557 
0558 void PagesEdit::mousePressEvent(QMouseEvent *e)
0559 {
0560     // if this click got the focus in, don't process the event
0561     if (!m_eatClick) {
0562         KLineEdit::mousePressEvent(e);
0563     }
0564     m_eatClick = false;
0565 }
0566 
0567 void PagesEdit::wheelEvent(QWheelEvent *e)
0568 {
0569     if (e->angleDelta().y() > 0) {
0570         m_miniBar->slotEmitNextPage();
0571     } else {
0572         m_miniBar->slotEmitPrevPage();
0573     }
0574 }
0575 
0576 /** HoverButton **/
0577 
0578 HoverButton::HoverButton(QWidget *parent)
0579     : QToolButton(parent)
0580 {
0581     setAutoRaise(true);
0582     setFocusPolicy(Qt::NoFocus);
0583     setToolButtonStyle(Qt::ToolButtonIconOnly);
0584     KAcceleratorManager::setNoAccel(this);
0585 }
0586 
0587 #include "minibar.moc"
0588 
0589 /* kate: replace-tabs on; indent-width 4; */