File indexing completed on 2024-05-12 08:20:31
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"