File indexing completed on 2024-05-19 08:24:42
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; */