1 /* This file is part of Clementine.
2 Copyright 2010, David Sansome <me@davidsansome.com>
3
4 Clementine is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 Clementine is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with Clementine. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19 #include "osdpretty.h"
20 #include "ui_osdpretty.h"
21
22 #include <QApplication>
23 #include <QBitmap>
24 #include <QColor>
25 #include <QDesktopWidget>
26 #include <QLayout>
27 #include <QMouseEvent>
28 #include <QPainter>
29 #include <QPainterPath>
30 #include <QSettings>
31 #include <QTimer>
32 #include <QTimeLine>
33
34 #include <QtDebug>
35
36 #ifdef HAVE_X11
37 #include <QX11Info>
38 #endif
39 #ifdef Q_OS_WIN32
40 # include <QtWin>
41 #endif
42
43 #ifdef Q_OS_WIN32
44 #include <windows.h>
45 #endif
46
47 const char* OSDPretty::kSettingsGroup = "OSDPretty";
48
49 const int OSDPretty::kDropShadowSize = 13;
50 const int OSDPretty::kBorderRadius = 10;
51 const int OSDPretty::kMaxIconSize = 100;
52
53 const int OSDPretty::kSnapProximity = 20;
54
55 const QRgb OSDPretty::kPresetBlue = qRgb(102, 150, 227);
56 const QRgb OSDPretty::kPresetOrange = qRgb(254, 156, 67);
57
OSDPretty(Mode mode,QWidget * parent)58 OSDPretty::OSDPretty(Mode mode, QWidget* parent)
59 : QWidget(parent),
60 ui_(new Ui_OSDPretty),
61 mode_(mode),
62 background_color_(kPresetBlue),
63 background_opacity_(0.85),
64 popup_display_(0),
65 font_(QFont()),
66 disable_duration_(false),
67 timeout_(new QTimer(this)),
68 fading_enabled_(false),
69 fader_(new QTimeLine(300, this)),
70 toggle_mode_(false) {
71 Qt::WindowFlags flags = Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint |
72 Qt::X11BypassWindowManagerHint;
73
74 setWindowFlags(flags);
75 setAttribute(Qt::WA_TranslucentBackground, true);
76 setAttribute(Qt::WA_X11NetWmWindowTypeNotification, true);
77 setAttribute(Qt::WA_ShowWithoutActivating, true);
78 ui_->setupUi(this);
79
80 #ifdef Q_OS_WIN32
81 // Don't show the window in the taskbar. Qt::ToolTip does this too, but it
82 // adds an extra ugly shadow.
83 int ex_style = GetWindowLong((HWND)winId(), GWL_EXSTYLE);
84 ex_style |= WS_EX_NOACTIVATE;
85 SetWindowLong((HWND)winId(), GWL_EXSTYLE, ex_style);
86 #endif
87
88 // Mode settings
89 switch (mode_) {
90 case Mode_Popup:
91 setCursor(QCursor(Qt::ArrowCursor));
92 break;
93
94 case Mode_Draggable:
95 setCursor(QCursor(Qt::OpenHandCursor));
96 break;
97 }
98
99 // Timeout
100 timeout_->setSingleShot(true);
101 timeout_->setInterval(5000);
102 connect(timeout_, SIGNAL(timeout()), SLOT(hide()));
103
104 ui_->icon->setMaximumSize(kMaxIconSize, kMaxIconSize);
105
106 // Fader
107 connect(fader_, SIGNAL(valueChanged(qreal)), SLOT(FaderValueChanged(qreal)));
108 connect(fader_, SIGNAL(finished()), SLOT(FaderFinished()));
109
110 #ifdef Q_OS_WIN32
111 set_fading_enabled(true);
112 #endif
113
114 // Load the show edges and corners
115 QImage shadow_edge(":osd_shadow_edge.png");
116 QImage shadow_corner(":osd_shadow_corner.png");
117 for (int i = 0; i < 4; ++i) {
118 QTransform rotation = QTransform().rotate(90 * i);
119 shadow_edge_[i] = QPixmap::fromImage(shadow_edge.transformed(rotation));
120 shadow_corner_[i] = QPixmap::fromImage(shadow_corner.transformed(rotation));
121 }
122 background_ = QPixmap(":osd_background.png");
123
124 // Set the margins to allow for the drop shadow
125 QBoxLayout* l = static_cast<QBoxLayout*>(layout());
126 int margin = l->margin() + kDropShadowSize;
127 l->setMargin(margin);
128
129 // Get current screen resolution
130 QRect screenResolution = QApplication::desktop()->screenGeometry();
131 // Leave 200 px for icon
132 ui_->summary->setMaximumWidth(screenResolution.width() - 200);
133 ui_->message->setMaximumWidth(screenResolution.width() - 200);
134 // Set maximum size for the OSD, a little margin here too
135 setMaximumSize(screenResolution.width() - 100,
136 screenResolution.height() - 100);
137
138 // Don't load settings here, they will be reloaded anyway on creation
139 }
140
~OSDPretty()141 OSDPretty::~OSDPretty() { delete ui_; }
142
IsTransparencyAvailable()143 bool OSDPretty::IsTransparencyAvailable() {
144 #if defined(HAVE_X11) && (QT_VERSION >= QT_VERSION_CHECK(5, 7, 0))
145 return QX11Info::isCompositingManagerRunning();
146 #endif
147 return true;
148 }
149
Load()150 void OSDPretty::Load() {
151 QSettings s;
152 s.beginGroup(kSettingsGroup);
153
154 foreground_color_ = QColor(s.value("foreground_color", 0).toInt());
155 background_color_ = QColor(s.value("background_color", kPresetBlue).toInt());
156 background_opacity_ = s.value("background_opacity", 0.85).toDouble();
157 popup_display_ = s.value("popup_display", -1).toInt();
158 popup_pos_ = s.value("popup_pos", QPoint(0, 0)).toPoint();
159 font_.fromString(s.value("font", "Verdana,9,-1,5,50,0,0,0,0,0").toString());
160 disable_duration_ = s.value("disable_duration", false).toBool();
161
162 set_font(font());
163 set_foreground_color(foreground_color());
164 }
165
ReloadSettings()166 void OSDPretty::ReloadSettings() {
167 Load();
168 if (isVisible()) update();
169 }
170
BoxBorder() const171 QRect OSDPretty::BoxBorder() const {
172 return rect().adjusted(kDropShadowSize, kDropShadowSize, -kDropShadowSize,
173 -kDropShadowSize);
174 }
175
paintEvent(QPaintEvent *)176 void OSDPretty::paintEvent(QPaintEvent*) {
177 QPainter p(this);
178 p.setRenderHint(QPainter::Antialiasing);
179 p.setRenderHint(QPainter::HighQualityAntialiasing);
180
181 QRect box(BoxBorder());
182
183 // Shadow corners
184 const int kShadowCornerSize = kDropShadowSize + kBorderRadius;
185 p.drawPixmap(0, 0, shadow_corner_[0]);
186 p.drawPixmap(width() - kShadowCornerSize, 0, shadow_corner_[1]);
187 p.drawPixmap(width() - kShadowCornerSize, height() - kShadowCornerSize,
188 shadow_corner_[2]);
189 p.drawPixmap(0, height() - kShadowCornerSize, shadow_corner_[3]);
190
191 // Shadow edges
192 p.drawTiledPixmap(kShadowCornerSize, 0, width() - kShadowCornerSize * 2,
193 kDropShadowSize, shadow_edge_[0]);
194 p.drawTiledPixmap(width() - kDropShadowSize, kShadowCornerSize,
195 kDropShadowSize, height() - kShadowCornerSize * 2,
196 shadow_edge_[1]);
197 p.drawTiledPixmap(kShadowCornerSize, height() - kDropShadowSize,
198 width() - kShadowCornerSize * 2, kDropShadowSize,
199 shadow_edge_[2]);
200 p.drawTiledPixmap(0, kShadowCornerSize, kDropShadowSize,
201 height() - kShadowCornerSize * 2, shadow_edge_[3]);
202
203 // Box background
204 p.setBrush(background_color_);
205 p.setPen(QPen());
206 p.setOpacity(background_opacity_);
207 p.drawRoundedRect(box, kBorderRadius, kBorderRadius);
208
209 // Background pattern
210 QPainterPath background_path;
211 background_path.addRoundedRect(box, kBorderRadius, kBorderRadius);
212 p.setClipPath(background_path);
213 p.setOpacity(1.0);
214 p.drawPixmap(box.right() - background_.width(),
215 box.bottom() - background_.height(), background_);
216 p.setClipping(false);
217
218 // Gradient overlay
219 QLinearGradient gradient(0, 0, 0, height());
220 gradient.setColorAt(0, QColor(255, 255, 255, 130));
221 gradient.setColorAt(1, QColor(255, 255, 255, 50));
222 p.setBrush(gradient);
223 p.drawRoundedRect(box, kBorderRadius, kBorderRadius);
224
225 // Box border
226 p.setBrush(QBrush());
227 p.setPen(QPen(background_color_.darker(150), 2));
228 p.drawRoundedRect(box, kBorderRadius, kBorderRadius);
229 }
230
SetMessage(const QString & summary,const QString & message,const QImage & image)231 void OSDPretty::SetMessage(const QString& summary, const QString& message,
232 const QImage& image) {
233
234 if (!image.isNull()) {
235 QImage scaled_image =
236 image.scaled(kMaxIconSize, kMaxIconSize, Qt::KeepAspectRatio,
237 Qt::SmoothTransformation);
238 ui_->icon->setPixmap(QPixmap::fromImage(scaled_image));
239 ui_->icon->show();
240 } else {
241 ui_->icon->hide();
242 }
243
244 ui_->summary->setText(summary);
245 ui_->message->setText(message);
246
247 if (isVisible()) Reposition();
248 }
249
250 // Set the desired message and then show the OSD
ShowMessage(const QString & summary,const QString & message,const QImage & image)251 void OSDPretty::ShowMessage(const QString& summary, const QString& message,
252 const QImage& image) {
253 SetMessage(summary, message, image);
254
255 if (isVisible() && mode_ == Mode_Popup) {
256 // The OSD is already visible, toggle or restart the timer
257 if (toggle_mode()) {
258 set_toggle_mode(false);
259 // If timeout is disabled, timer hadn't been started
260 if (!disable_duration()) timeout_->stop();
261 hide();
262 } else {
263 if (!disable_duration()) timeout_->start(); // Restart the timer
264 }
265 } else {
266 if (toggle_mode()) set_toggle_mode(false);
267 // The OSD is not visible, show it
268 show();
269 }
270 }
271
showEvent(QShowEvent * e)272 void OSDPretty::showEvent(QShowEvent* e) {
273 setWindowOpacity(fading_enabled_ ? 0.0 : 1.0);
274
275 QWidget::showEvent(e);
276
277 Reposition();
278
279 if (fading_enabled_) {
280 fader_->setDirection(QTimeLine::Forward);
281 fader_->start(); // Timeout will be started in FaderFinished
282 } else if (mode_ == Mode_Popup) {
283 if (!disable_duration()) timeout_->start();
284 // Ensures it is above when showing the preview
285 raise();
286 }
287 }
288
setVisible(bool visible)289 void OSDPretty::setVisible(bool visible) {
290 if (!visible && fading_enabled_ &&
291 fader_->direction() == QTimeLine::Forward) {
292 fader_->setDirection(QTimeLine::Backward);
293 fader_->start();
294 } else {
295 QWidget::setVisible(visible);
296 }
297 }
298
FaderFinished()299 void OSDPretty::FaderFinished() {
300 if (fader_->direction() == QTimeLine::Backward)
301 hide();
302 else if (mode_ == Mode_Popup && !disable_duration())
303 timeout_->start();
304 }
305
FaderValueChanged(qreal value)306 void OSDPretty::FaderValueChanged(qreal value) { setWindowOpacity(value); }
307
Reposition()308 void OSDPretty::Reposition() {
309 QDesktopWidget* desktop = QApplication::desktop();
310
311 // Make the OSD the proper size
312 layout()->activate();
313 resize(sizeHint());
314
315 // Work out where to place the OSD. -1 for x or y means "on the right or
316 // bottom edge".
317 QRect geometry(desktop->availableGeometry(popup_display_));
318
319 int x = popup_pos_.x() < 0 ? geometry.right() - width()
320 : geometry.left() + popup_pos_.x();
321 int y = popup_pos_.y() < 0 ? geometry.bottom() - height()
322 : geometry.top() + popup_pos_.y();
323
324 #ifndef Q_OS_WIN32
325 // windows needs negative coordinates for monitors
326 // to the left or above the primary
327 x = qBound(0, x, geometry.right() - width());
328 y = qBound(0, y, geometry.bottom() - height());
329 #endif
330
331 move(x, y);
332
333 // Create a mask for the actual area of the OSD
334 QBitmap mask(size());
335 mask.clear();
336
337 QPainter p(&mask);
338 p.setBrush(Qt::color1);
339 p.drawRoundedRect(BoxBorder().adjusted(-1, -1, 0, 0), kBorderRadius,
340 kBorderRadius);
341 p.end();
342
343 // If there's no compositing window manager running then we have to set an
344 // XShape mask.
345 if (IsTransparencyAvailable())
346 clearMask();
347 else {
348 setMask(mask);
349 }
350
351 #ifdef Q_OS_WIN32
352 // On windows, enable blurbehind on the masked area
353 QtWin::enableBlurBehindWindow(this, QRegion(mask));
354 #endif
355 }
356
enterEvent(QEvent *)357 void OSDPretty::enterEvent(QEvent*) {
358 if (mode_ == Mode_Popup) setWindowOpacity(0.25);
359 }
360
leaveEvent(QEvent *)361 void OSDPretty::leaveEvent(QEvent*) { setWindowOpacity(1.0); }
362
mousePressEvent(QMouseEvent * e)363 void OSDPretty::mousePressEvent(QMouseEvent* e) {
364 if (mode_ == Mode_Popup)
365 hide();
366 else {
367 original_window_pos_ = pos();
368 drag_start_pos_ = e->globalPos();
369 }
370 }
371
mouseMoveEvent(QMouseEvent * e)372 void OSDPretty::mouseMoveEvent(QMouseEvent* e) {
373 if (mode_ == Mode_Draggable) {
374 QPoint delta = e->globalPos() - drag_start_pos_;
375 QPoint new_pos = original_window_pos_ + delta;
376
377 // Keep it to the bounds of the desktop
378 QDesktopWidget* desktop = QApplication::desktop();
379 QRect geometry(desktop->availableGeometry(e->globalPos()));
380
381 new_pos.setX(
382 qBound(geometry.left(), new_pos.x(), geometry.right() - width()));
383 new_pos.setY(
384 qBound(geometry.top(), new_pos.y(), geometry.bottom() - height()));
385
386 // Snap to center
387 int snap_x = geometry.center().x() - width() / 2;
388 if (new_pos.x() > snap_x - kSnapProximity &&
389 new_pos.x() < snap_x + kSnapProximity) {
390 new_pos.setX(snap_x);
391 }
392
393 move(new_pos);
394
395 popup_display_ = current_display();
396 popup_pos_ = current_pos();
397 }
398 }
399
current_pos() const400 QPoint OSDPretty::current_pos() const {
401 QDesktopWidget* desktop = QApplication::desktop();
402 QRect geometry(desktop->availableGeometry(current_display()));
403
404 int x = pos().x() >= geometry.right() - width() ? -1
405 : pos().x() - geometry.left();
406 int y = pos().y() >= geometry.bottom() - height() ? -1 : pos().y() -
407 geometry.top();
408
409 return QPoint(x, y);
410 }
411
current_display() const412 int OSDPretty::current_display() const {
413 QDesktopWidget* desktop = QApplication::desktop();
414 return desktop->screenNumber(pos());
415 }
416
set_background_color(QRgb color)417 void OSDPretty::set_background_color(QRgb color) {
418 background_color_ = color;
419 if (isVisible()) update();
420 }
421
set_background_opacity(qreal opacity)422 void OSDPretty::set_background_opacity(qreal opacity) {
423 background_opacity_ = opacity;
424 if (isVisible()) update();
425 }
426
set_foreground_color(QRgb color)427 void OSDPretty::set_foreground_color(QRgb color) {
428 foreground_color_ = QColor(color);
429
430 QPalette p;
431 p.setColor(QPalette::WindowText, foreground_color_);
432
433 ui_->summary->setPalette(p);
434 ui_->message->setPalette(p);
435 }
436
set_popup_duration(int msec)437 void OSDPretty::set_popup_duration(int msec) { timeout_->setInterval(msec); }
438
mouseReleaseEvent(QMouseEvent *)439 void OSDPretty::mouseReleaseEvent(QMouseEvent*) {
440 if (mode_ == Mode_Draggable) {
441 popup_display_ = current_display();
442 popup_pos_ = current_pos();
443 }
444 }
445
set_font(QFont font)446 void OSDPretty::set_font(QFont font) {
447 font_ = font;
448
449 // Update the UI
450 ui_->summary->setFont(font);
451 ui_->message->setFont(font);
452 // Now adjust OSD size so everything fits
453 ui_->verticalLayout->activate();
454 resize(sizeHint());
455 // Update the position after font change
456 Reposition();
457 }
458