File indexing completed on 2024-04-28 04:21:26

0001 // SPDX-FileCopyrightText: 2003-2020 The KPhotoAlbum Development Team
0002 // SPDX-FileCopyrightText: 2021-2022 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 //
0004 // SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006 #include "InfoBox.h"
0007 
0008 // Local includes
0009 #include <Browser/BrowserWidget.h>
0010 #include <DB/ImageDB.h>
0011 #include <DB/ImageInfo.h>
0012 #include <MainWindow/Window.h>
0013 #ifdef HAVE_MARBLE
0014 #include <Map/MapView.h>
0015 #endif
0016 #include "VisibleOptionsMenu.h"
0017 
0018 #include <KActionCollection>
0019 #include <KLocalizedString>
0020 #include <KRatingWidget>
0021 #include <QApplication>
0022 #include <QBitmap>
0023 #include <QDebug>
0024 #include <QDesktopWidget>
0025 #include <QMouseEvent>
0026 #include <QScrollBar>
0027 #include <QToolButton>
0028 
0029 using namespace Settings;
0030 
0031 Viewer::InfoBox::InfoBox(Viewer::ViewerWidget *viewer)
0032     : QTextBrowser(viewer)
0033     , m_viewer(viewer)
0034     , m_hoveringOverLink(false)
0035     , m_infoBoxResizer(this)
0036     , m_menu(nullptr)
0037 #ifdef HAVE_MARBLE
0038     , m_map(nullptr)
0039 #endif
0040 {
0041     setFrameStyle(Box | Plain);
0042     setLineWidth(1);
0043     setMidLineWidth(0);
0044     setAutoFillBackground(false);
0045 
0046     updatePalette();
0047 
0048     m_jumpToContext = new QToolButton(this);
0049     m_jumpToContext->setIcon(QIcon::fromTheme(QString::fromUtf8("kphotoalbum")));
0050     m_jumpToContext->setFixedSize(16, 16);
0051     connect(m_jumpToContext, &QToolButton::clicked, this, &InfoBox::jumpToContext);
0052     // TODO(jzarl): remove QOverload once we don't care about Debian 11 anymore:
0053     connect(this, QOverload<const QUrl &>::of(&QTextBrowser::highlighted), this, &InfoBox::linkHovered);
0054 
0055 #ifdef HAVE_MARBLE
0056     m_showOnMap = new QToolButton(this);
0057     m_showOnMap->setIcon(QIcon::fromTheme(QString::fromUtf8("atmosphere")));
0058     m_showOnMap->setFixedSize(16, 16);
0059     m_showOnMap->setCursor(Qt::ArrowCursor);
0060     m_showOnMap->setToolTip(i18n("Show the geographic position of this image on a map"));
0061     connect(m_showOnMap, &QToolButton::clicked, this, &InfoBox::launchMapView);
0062     m_showOnMap->hide();
0063 
0064     connect(m_viewer, &ViewerWidget::soughtTo, this, &InfoBox::updateMapForCurrentImage);
0065 #endif
0066 
0067     KRatingWidget *rating = new KRatingWidget;
0068 
0069     // Unfortunately, the KRatingWidget now thinks that it has some absurdly big
0070     // dimensions. This call will persuade it to stay reasonably small.
0071     rating->adjustSize();
0072 
0073     for (int i = 0; i <= 10; ++i) {
0074         rating->setRating(i);
0075         // QWidget::grab() does not create an alpha channel
0076         // Therefore, we need to create a mask using heuristics (yes, this is slow, but we only do it once)
0077         QPixmap pixmap = rating->grab();
0078         pixmap.setMask(pixmap.createHeuristicMask());
0079         m_ratingPixmap.append(pixmap);
0080     }
0081 
0082     delete rating;
0083 }
0084 
0085 QVariant Viewer::InfoBox::loadResource(int type, const QUrl &name)
0086 {
0087     if (name.scheme() == QString::fromUtf8("kratingwidget")) {
0088         int rating = name.port();
0089         Q_ASSERT(0 <= rating && rating <= 10);
0090         return m_ratingPixmap[rating];
0091     }
0092     return QTextBrowser::loadResource(type, name);
0093 }
0094 
0095 void Viewer::InfoBox::setSource(const QUrl &source)
0096 {
0097     int index = source.path().toInt();
0098     QPair<QString, QString> p = m_linkMap[index];
0099     QString category = p.first;
0100     QString value = p.second;
0101     Browser::BrowserWidget::instance()->load(category, value);
0102     showBrowser();
0103 }
0104 
0105 void Viewer::InfoBox::setInfo(const QString &text, const QMap<int, QPair<QString, QString>> &linkMap)
0106 {
0107     m_linkMap = linkMap;
0108     setText(text);
0109 
0110     hackLinkColorForQt52();
0111 
0112 #ifdef HAVE_MARBLE
0113     if (m_viewer->currentInfo()->coordinates().hasCoordinates()) {
0114         m_showOnMap->show();
0115     } else {
0116         m_showOnMap->hide();
0117     }
0118 #endif
0119 
0120     setSize();
0121 }
0122 
0123 void Viewer::InfoBox::setSize()
0124 {
0125     const int maxWidth = Settings::SettingsData::instance()->infoBoxWidth();
0126     const int maxHeight = Settings::SettingsData::instance()->infoBoxHeight();
0127 
0128     document()->setPageSize(QSize(maxWidth, maxHeight));
0129     bool showVerticalBar = document()->size().height() > maxHeight;
0130 
0131     setVerticalScrollBarPolicy(showVerticalBar ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
0132     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0133     const int realWidth = static_cast<int>(document()->idealWidth())
0134         + (showVerticalBar ? verticalScrollBar()->width() + frameWidth() : 0)
0135         + m_jumpToContext->width() + 10;
0136 
0137     resize(realWidth, qMin((int)document()->size().height(), maxHeight));
0138 }
0139 
0140 bool Viewer::InfoBox::event(QEvent *e)
0141 {
0142     if (e->type() == QEvent::PaletteChange)
0143         updatePalette();
0144     return QTextBrowser::event(e);
0145 }
0146 
0147 void Viewer::InfoBox::mousePressEvent(QMouseEvent *event)
0148 {
0149     if (event->button() == Qt::LeftButton) {
0150         possiblyStartResize(event->pos());
0151     }
0152     QTextBrowser::mousePressEvent(event);
0153 }
0154 
0155 void Viewer::InfoBox::mouseReleaseEvent(QMouseEvent *event)
0156 {
0157     if (m_infoBoxResizer.isActive()) {
0158         Settings::SettingsData::instance()->setInfoBoxWidth(width());
0159         Settings::SettingsData::instance()->setInfoBoxHeight(height());
0160     }
0161 
0162     m_infoBoxResizer.deactivate();
0163     QTextBrowser::mouseReleaseEvent(event);
0164 }
0165 
0166 void Viewer::InfoBox::mouseMoveEvent(QMouseEvent *event)
0167 {
0168     if (event->buttons() & Qt::LeftButton) {
0169         if (m_infoBoxResizer.isActive()) {
0170             m_infoBoxResizer.setPos(event->pos());
0171         } else {
0172             m_viewer->infoBoxMove();
0173         }
0174         // Do not tell QTextBrowser about the mouse movement, as this will just start a selection.
0175     } else {
0176         updateCursor(event->pos());
0177         QTextBrowser::mouseMoveEvent(event);
0178     }
0179 }
0180 
0181 void Viewer::InfoBox::linkHovered(const QUrl &link)
0182 {
0183     if (link.isEmpty()) {
0184         Q_EMIT noTagHovered();
0185     } else {
0186         Q_EMIT tagHovered(m_linkMap[link.path().toInt()]);
0187     }
0188 
0189     m_hoveringOverLink = !link.isEmpty();
0190 }
0191 
0192 void Viewer::InfoBox::jumpToContext()
0193 {
0194     Browser::BrowserWidget::instance()->addImageView(m_viewer->currentInfo()->fileName());
0195     showBrowser();
0196 }
0197 
0198 void Viewer::InfoBox::showBrowser()
0199 {
0200     QDesktopWidget *desktop = qApp->desktop();
0201     if (desktop->screenNumber(Browser::BrowserWidget::instance()) == desktop->screenNumber(m_viewer)) {
0202         if (m_viewer->showingFullScreen()) {
0203             m_viewer->setShowFullScreen(false);
0204         }
0205         MainWindow::Window::theMainWindow()->raise();
0206     }
0207 }
0208 
0209 /**
0210  * Update the cursor based on the cursors position in the info box
0211  */
0212 void Viewer::InfoBox::updateCursor(const QPoint &pos)
0213 {
0214     const int border = 25;
0215 
0216     bool left = (pos.x() < border);
0217     bool right = pos.x() > width() - border;
0218     bool top = pos.y() < border;
0219     bool bottom = pos.y() > height() - border;
0220 
0221     Settings::Position windowPos = Settings::SettingsData::instance()->infoBoxPosition();
0222 
0223     Qt::CursorShape shape = Qt::SizeAllCursor;
0224     if (m_hoveringOverLink) {
0225         shape = Qt::PointingHandCursor;
0226     } else if (atBlackoutPos(left, right, top, bottom, windowPos)) {
0227         shape = Qt::SizeAllCursor;
0228     } else if ((left && top) || (right && bottom)) {
0229         shape = Qt::SizeFDiagCursor;
0230     } else if ((left && bottom) || (right && top)) {
0231         shape = Qt::SizeBDiagCursor;
0232     } else if (top || bottom) {
0233         shape = Qt::SizeVerCursor;
0234     } else if (left || right) {
0235         shape = Qt::SizeHorCursor;
0236     }
0237 
0238     setCursor(shape);
0239     viewport()->setCursor(shape);
0240 }
0241 
0242 /**
0243  * Return true if we are at an edge of the image info box that is towards the edge of the viewer.
0244  * We can forexample not make the box taller at the bottom if the box is sitting at the bottom of the viewer.
0245  */
0246 bool Viewer::InfoBox::atBlackoutPos(bool left, bool right, bool top, bool bottom, Settings::Position pos) const
0247 {
0248     return (left && (pos == Left || pos == TopLeft || pos == BottomLeft))
0249         || (right && (pos == Right || pos == TopRight || pos == BottomRight))
0250         || (top && (pos == Top || pos == TopLeft || pos == TopRight))
0251         || (bottom && (pos == Bottom || pos == BottomLeft || pos == BottomRight));
0252 }
0253 
0254 void Viewer::InfoBox::possiblyStartResize(const QPoint &pos)
0255 {
0256     const int border = 25;
0257 
0258     bool left = (pos.x() < border);
0259     bool right = pos.x() > width() - border;
0260     bool top = pos.y() < border;
0261     bool bottom = pos.y() > height() - border;
0262 
0263     if (left || right || top || bottom) {
0264         m_infoBoxResizer.setup(left, right, top, bottom);
0265     }
0266 }
0267 
0268 void Viewer::InfoBox::resizeEvent(QResizeEvent *)
0269 {
0270     QPoint pos = viewport()->rect().adjusted(0, 2, -m_jumpToContext->width() - 2, 0).topRight();
0271     m_jumpToContext->move(pos);
0272 #ifdef HAVE_MARBLE
0273     pos.setY(pos.y() + 20);
0274     m_showOnMap->move(pos);
0275 #endif
0276 }
0277 
0278 /**
0279  * @brief override the color of links because the one in the palette isn't used.
0280  * As can be seen in the referenced links, QTextBrowser uses the global palette for setting the link color.
0281  * Until it uses the widget palette, we have to do this workaround.
0282  * @see https://bugreports.qt.io/browse/QTBUG-28998
0283  * @see https://codereview.qt-project.org/c/qt/qtbase/+/68874/2/src/gui/text/qtexthtmlparser.cpp
0284  */
0285 void Viewer::InfoBox::hackLinkColorForQt52()
0286 {
0287     QTextCursor cursor(document());
0288     QBrush linkColor = palette().link();
0289     Q_FOREVER
0290     {
0291         QTextCharFormat f = cursor.charFormat();
0292         if (f.isAnchor()) {
0293             f.setForeground(linkColor);
0294             QTextCursor c2 = cursor;
0295             c2.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
0296             c2.setCharFormat(f);
0297         }
0298         if (cursor.atEnd()) {
0299             break;
0300         }
0301         cursor.movePosition(QTextCursor::NextCharacter);
0302     }
0303 }
0304 
0305 void Viewer::InfoBox::updatePalette()
0306 {
0307     QPalette p = palette();
0308     // we want a transparent background, not the default widget grey
0309     QColor bgColor = palette().shadow().color();
0310     bgColor.setAlpha(170);
0311     p.setColor(QPalette::Base, bgColor);
0312     // adapt other colors as necessary
0313     p.setColor(QPalette::WindowText, palette().brightText().color());
0314     p.setColor(QPalette::Text, palette().brightText().color());
0315     p.setColor(QPalette::Link, p.color(QPalette::Link).lighter());
0316     setPalette(p);
0317     // re-enable palette propagation:
0318     setAttribute(Qt::WA_SetPalette);
0319 }
0320 
0321 void Viewer::InfoBox::contextMenuEvent(QContextMenuEvent *event)
0322 {
0323     if (!m_menu) {
0324         m_menu = new VisibleOptionsMenu(m_viewer, new KActionCollection((QObject *)nullptr));
0325         connect(m_menu, &VisibleOptionsMenu::visibleOptionsChanged, m_viewer, &ViewerWidget::updateInfoBox);
0326     }
0327     m_menu->exec(event->globalPos());
0328 }
0329 
0330 #ifdef HAVE_MARBLE
0331 void Viewer::InfoBox::launchMapView()
0332 {
0333     if (!m_map) {
0334         m_map = new Map::MapView(m_viewer, Map::UsageType::MapViewWindow);
0335     }
0336 
0337     m_map->addImage(m_viewer->currentInfo());
0338     m_map->buildImageClusters();
0339     m_map->setShowThumbnails(false);
0340     m_map->zoomToMarkers();
0341     m_map->show();
0342     m_map->raise();
0343 }
0344 
0345 void Viewer::InfoBox::updateMapForCurrentImage(DB::FileName)
0346 {
0347     if (!m_map) {
0348         return;
0349     }
0350 
0351     if (m_viewer->currentInfo()->coordinates().hasCoordinates()) {
0352         m_map->displayStatus(Map::MapStatus::ImageHasCoordinates);
0353         m_map->clear();
0354         m_map->addImage(m_viewer->currentInfo());
0355         m_map->buildImageClusters();
0356         m_map->setCenter(m_viewer->currentInfo());
0357     } else {
0358         m_map->displayStatus(Map::MapStatus::ImageHasNoCoordinates);
0359     }
0360 }
0361 #endif
0362 
0363 // vi:expandtab:tabstop=4 shiftwidth=4:
0364 
0365 #include "moc_InfoBox.cpp"