1 #include "library/dlgcoverartfullsize.h"
2 
3 #include <QRect>
4 #include <QScreen>
5 #include <QStyle>
6 #include <QWheelEvent>
7 
8 #include "library/coverartcache.h"
9 #include "library/coverartutils.h"
10 #include "moc_dlgcoverartfullsize.cpp"
11 #include "track/track.h"
12 #include "util/widgethelper.h"
13 
DlgCoverArtFullSize(QWidget * parent,BaseTrackPlayer * pPlayer)14 DlgCoverArtFullSize::DlgCoverArtFullSize(QWidget* parent, BaseTrackPlayer* pPlayer)
15         : QDialog(parent),
16           m_pPlayer(pPlayer),
17           m_pCoverMenu(make_parented<WCoverArtMenu>(this)) {
18     CoverArtCache* pCache = CoverArtCache::instance();
19     if (pCache) {
20         connect(pCache,
21                 &CoverArtCache::coverFound,
22                 this,
23                 &DlgCoverArtFullSize::slotCoverFound);
24     }
25 
26     setContextMenuPolicy(Qt::CustomContextMenu);
27     connect(this,
28             &DlgCoverArtFullSize::customContextMenuRequested,
29             this,
30             &DlgCoverArtFullSize::slotCoverMenu);
31     connect(m_pCoverMenu,
32             &WCoverArtMenu::coverInfoSelected,
33             this,
34             &DlgCoverArtFullSize::slotCoverInfoSelected);
35     connect(m_pCoverMenu,
36             &WCoverArtMenu::reloadCoverArt,
37             this,
38             &DlgCoverArtFullSize::slotReloadCoverArt);
39 
40     if (m_pPlayer != nullptr) {
41         connect(pPlayer,
42                 &BaseTrackPlayer::newTrackLoaded,
43                 this,
44                 &DlgCoverArtFullSize::slotLoadTrack);
45     }
46 
47     setupUi(this);
48 }
49 
closeEvent(QCloseEvent * event)50 void DlgCoverArtFullSize::closeEvent(QCloseEvent* event) {
51     if (parentWidget()) {
52         // Since the widget has a parent, this instance will be reused again.
53         // We need to prevent qt from destroying it's children
54         hide();
55         slotLoadTrack(nullptr);
56         event->ignore();
57     } else {
58         QDialog::closeEvent(event);
59     }
60 }
61 
init(TrackPointer pTrack)62 void DlgCoverArtFullSize::init(TrackPointer pTrack) {
63     if (!pTrack) {
64         return;
65     }
66     // The real size will be calculated later.
67     // If you zoom in so the window is larger then the desktop, close the
68     // window and reopen, the window will not be resized correctly
69     // by slotCoverFound. Setting a small fixed size before show fixes this
70     resize(100, 100);
71     show();
72     raise();
73     activateWindow();
74 
75     // This must be called after show() to set the window title. Refer to the
76     // comment in slotLoadTrack for details.
77     slotLoadTrack(pTrack);
78 }
79 
slotLoadTrack(TrackPointer pTrack)80 void DlgCoverArtFullSize::slotLoadTrack(TrackPointer pTrack) {
81     if (m_pLoadedTrack != nullptr) {
82         disconnect(m_pLoadedTrack.get(),
83                 &Track::coverArtUpdated,
84                 this,
85                 &DlgCoverArtFullSize::slotTrackCoverArtUpdated);
86     }
87     m_pLoadedTrack = pTrack;
88     if (m_pLoadedTrack != nullptr) {
89         connect(m_pLoadedTrack.get(),
90                 &Track::coverArtUpdated,
91                 this,
92                 &DlgCoverArtFullSize::slotTrackCoverArtUpdated);
93 
94         // Somehow setting the widow title triggered a bug in Xlib that resulted
95         // in a deadlock before the check for isVisible() was added.
96         // Unfortunately the original bug was difficult to reproduce, so I am
97         // not sure if checking isVisible() before setting the window title
98         // actually works around the Xlib bug or merely makes it much less
99         // likely to be triggered. Before the isVisible() check was added,
100         // the window title was getting set on DlgCoverArtFullSize instances
101         // that had never been shown whenever a track was loaded.
102         // https://bugs.launchpad.net/mixxx/+bug/1789059
103         // https://gitlab.freedesktop.org/xorg/lib/libx11/issues/25#note_50985
104         if (isVisible()) {
105             QString windowTitle;
106             const QString albumArtist = m_pLoadedTrack->getAlbumArtist();
107             const QString artist = m_pLoadedTrack->getArtist();
108             const QString album = m_pLoadedTrack->getAlbum();
109             const QString year = m_pLoadedTrack->getYear();
110             if (!albumArtist.isEmpty()) {
111                 windowTitle = albumArtist;
112             } else if (!artist.isEmpty()) {
113                 windowTitle += artist;
114             }
115             if (!album.isEmpty()) {
116                 if (!windowTitle.isEmpty()) {
117                     windowTitle += " - ";
118                 }
119                 windowTitle += album;
120             }
121             if (!year.isEmpty()) {
122                 if (!windowTitle.isEmpty()) {
123                     windowTitle += " ";
124                 }
125                 windowTitle += QString("(%1)").arg(year);
126             }
127             setWindowTitle(windowTitle);
128         }
129     }
130     slotTrackCoverArtUpdated();
131 }
132 
slotTrackCoverArtUpdated()133 void DlgCoverArtFullSize::slotTrackCoverArtUpdated() {
134     if (m_pLoadedTrack) {
135         CoverArtCache::requestTrackCover(this, m_pLoadedTrack);
136     } else {
137         coverArt->setPixmap(QPixmap());
138     }
139 }
140 
slotCoverFound(const QObject * pRequestor,const CoverInfo & coverInfo,const QPixmap & pixmap,quint16 requestedHash,bool coverInfoUpdated)141 void DlgCoverArtFullSize::slotCoverFound(
142         const QObject* pRequestor,
143         const CoverInfo& coverInfo,
144         const QPixmap& pixmap,
145         quint16 requestedHash,
146         bool coverInfoUpdated) {
147     Q_UNUSED(requestedHash);
148     Q_UNUSED(coverInfoUpdated);
149     if (pRequestor != this || !m_pLoadedTrack ||
150             m_pLoadedTrack->getLocation() != coverInfo.trackLocation) {
151         return;
152     }
153 
154     m_pixmap = pixmap;
155 
156     if (m_pixmap.isNull()) {
157         coverArt->setPixmap(QPixmap());
158         hide();
159         return;
160     }
161 
162     // Scale down dialog if the pixmap is larger than the screen.
163     // Use 90% of screen size instead of 100% to prevent an issue with
164     // whitespace appearing on the side when resizing a window whose
165     // borders touch the edges of the screen.
166     QSize dialogSize = m_pixmap.size();
167     QWidget* centerOverWidget = parentWidget();
168     VERIFY_OR_DEBUG_ASSERT(centerOverWidget) {
169         qWarning() << "DlgCoverArtFullSize does not have a parent.";
170         centerOverWidget = this;
171     }
172 
173     const QScreen* const pScreen = mixxx::widgethelper::getScreen(*centerOverWidget);
174     QRect screenGeometry;
175     VERIFY_OR_DEBUG_ASSERT(pScreen) {
176         qWarning() << "Assuming screen size of 800x600px.";
177         screenGeometry = QRect(0, 0, 800, 600);
178     }
179     else {
180         screenGeometry = pScreen->geometry();
181     }
182 
183     const QSize availableScreenSpace = screenGeometry.size() * 0.9;
184     if (dialogSize.height() > availableScreenSpace.height()) {
185         dialogSize.scale(dialogSize.width(), screenGeometry.height(), Qt::KeepAspectRatio);
186     } else if (dialogSize.width() > screenGeometry.width()) {
187         dialogSize.scale(screenGeometry.width(), dialogSize.height(), Qt::KeepAspectRatio);
188     }
189     QPixmap resizedPixmap = m_pixmap.scaled(size() * getDevicePixelRatioF(this),
190             Qt::KeepAspectRatio,
191             Qt::SmoothTransformation);
192     resizedPixmap.setDevicePixelRatio(getDevicePixelRatioF(this));
193     coverArt->setPixmap(resizedPixmap);
194 
195     // center the window
196     setGeometry(QStyle::alignedRect(
197             Qt::LeftToRight,
198             Qt::AlignCenter,
199             dialogSize,
200             screenGeometry));
201 }
202 
203 // slots to handle signals from the context menu
slotReloadCoverArt()204 void DlgCoverArtFullSize::slotReloadCoverArt() {
205     if (!m_pLoadedTrack) {
206         return;
207     }
208     slotCoverInfoSelected(
209             CoverInfoGuesser().guessCoverInfoForTrack(
210                     *m_pLoadedTrack));
211 }
212 
slotCoverInfoSelected(const CoverInfoRelative & coverInfo)213 void DlgCoverArtFullSize::slotCoverInfoSelected(
214         const CoverInfoRelative& coverInfo) {
215     if (!m_pLoadedTrack) {
216         return;
217     }
218     m_pLoadedTrack->setCoverInfo(coverInfo);
219 }
220 
mousePressEvent(QMouseEvent * event)221 void DlgCoverArtFullSize::mousePressEvent(QMouseEvent* event) {
222     if (!m_pCoverMenu->isVisible() && event->button() == Qt::LeftButton) {
223         m_clickTimer.setSingleShot(true);
224         m_clickTimer.start(500);
225         m_coverPressed = true;
226         m_dragStartPosition = event->globalPos() - frameGeometry().topLeft();
227     }
228 }
229 
mouseReleaseEvent(QMouseEvent * event)230 void DlgCoverArtFullSize::mouseReleaseEvent(QMouseEvent* event) {
231     m_coverPressed = false;
232     if (m_pCoverMenu->isVisible()) {
233         return;
234     }
235 
236     if (event->button() == Qt::LeftButton && isVisible()) {
237         if (m_clickTimer.isActive()) {
238         // short press
239             close();
240         } else {
241         // long press
242             return;
243         }
244         event->accept();
245     }
246 }
247 
mouseMoveEvent(QMouseEvent * event)248 void DlgCoverArtFullSize::mouseMoveEvent(QMouseEvent* event) {
249     if (m_coverPressed) {
250         move(event->globalPos() - m_dragStartPosition);
251         event->accept();
252     } else {
253         return;
254     }
255 }
256 
slotCoverMenu(const QPoint & pos)257 void DlgCoverArtFullSize::slotCoverMenu(const QPoint& pos) {
258     m_pCoverMenu->popup(mapToGlobal(pos));
259 }
260 
resizeEvent(QResizeEvent * event)261 void DlgCoverArtFullSize::resizeEvent(QResizeEvent* event) {
262     Q_UNUSED(event);
263     if (m_pixmap.isNull()) {
264         return;
265     }
266     // qDebug() << "DlgCoverArtFullSize::resizeEvent" << size();
267     QPixmap resizedPixmap = m_pixmap.scaled(size() * getDevicePixelRatioF(this),
268         Qt::KeepAspectRatio, Qt::SmoothTransformation);
269     resizedPixmap.setDevicePixelRatio(getDevicePixelRatioF(this));
270     coverArt->setPixmap(resizedPixmap);
271 }
272 
wheelEvent(QWheelEvent * event)273 void DlgCoverArtFullSize::wheelEvent(QWheelEvent* event) {
274     // Scale the image size
275     int oldWidth = width();
276     int oldHeight = height();
277     auto newWidth = static_cast<int>(oldWidth + (0.2 * event->angleDelta().y()));
278     auto newHeight = static_cast<int>(oldHeight + (0.2 * event->angleDelta().y()));
279     QSize newSize = size();
280     newSize.scale(newWidth, newHeight, Qt::KeepAspectRatio);
281 
282     // To keep the same part of the image under the cursor, shift the
283     // origin (top left point) by the distance the point moves under the cursor.
284     QPoint oldOrigin = geometry().topLeft();
285 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
286     QPoint oldPointUnderCursor = event->position().toPoint();
287 #else
288     QPoint oldPointUnderCursor = event->pos();
289 #endif
290 
291     const auto newPointX = static_cast<int>(
292             static_cast<double>(oldPointUnderCursor.x()) / oldWidth * newSize.width());
293     const auto newPointY = static_cast<int>(
294             static_cast<double>(oldPointUnderCursor.y()) / oldHeight * newSize.height());
295     QPoint newOrigin = QPoint(
296         oldOrigin.x() + (oldPointUnderCursor.x() - newPointX),
297         oldOrigin.y() + (oldPointUnderCursor.y() - newPointY));
298 
299     // Calling resize() then move() causes flickering, so resize and move the window
300     // simultaneously with setGeometry().
301     setGeometry(QRect(newOrigin, newSize));
302 
303     event->accept();
304 }
305