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