1 // Copyright 2005-2019 The Mumble Developers. All rights reserved.
2 // Use of this source code is governed by a BSD-style license
3 // that can be found in the LICENSE file at the root of the
4 // Mumble source tree or at <https://www.mumble.info/LICENSE>.
5
6 #include "mumble_pch.hpp"
7
8 #include "OverlayEditorScene.h"
9
10 #include "OverlayClient.h"
11 #include "OverlayUser.h"
12 #include "OverlayText.h"
13 #include "User.h"
14 #include "Channel.h"
15 #include "Message.h"
16 #include "Database.h"
17 #include "NetworkConfig.h"
18 #include "ServerHandler.h"
19 #include "MainWindow.h"
20 #include "GlobalShortcut.h"
21
22 // We define a global macro called 'g'. This can lead to issues when included code uses 'g' as a type or parameter name (like protobuf 3.7 does). As such, for now, we have to make this our last include.
23 #include "Global.h"
24
OverlayEditorScene(const OverlaySettings & srcos,QObject * p)25 OverlayEditorScene::OverlayEditorScene(const OverlaySettings &srcos, QObject *p) : QGraphicsScene(p), os(srcos) {
26 tsColor = Settings::Talking;
27 uiZoom = 2;
28
29 if (g.ocIntercept)
30 uiSize = g.ocIntercept->uiHeight;
31 else
32 uiSize = 1080.f;
33
34 qgiGroup = new OverlayGroup();
35 qgiGroup->setAcceptHoverEvents(true);
36 qgiGroup->setPos(0.0f, 0.0f);
37 addItem(qgiGroup);
38
39 qgpiMuted = new QGraphicsPixmapItem(qgiGroup);
40 qgpiMuted->hide();
41
42 qgpiAvatar = new QGraphicsPixmapItem(qgiGroup);
43 qgpiAvatar->hide();
44
45 qgpiName = new QGraphicsPixmapItem(qgiGroup);
46 qgpiName->hide();
47
48 qgpiChannel = new QGraphicsPixmapItem(qgiGroup);
49 qgpiChannel->hide();
50
51 qgpiBox = new QGraphicsPathItem(qgiGroup);
52 qgpiBox->hide();
53
54 qgpiSelected = NULL;
55
56 qgriSelected = new QGraphicsRectItem;
57 qgriSelected->hide();
58
59 qgriSelected->setFlag(QGraphicsItem::ItemIgnoresParentOpacity, true);
60 qgriSelected->setOpacity(1.0f);
61 qgriSelected->setBrush(Qt::NoBrush);
62 qgriSelected->setPen(QPen(Qt::black, 4.0f));
63 qgriSelected->setZValue(5.0f);
64
65 addItem(qgriSelected);
66
67 qgpiChannel->setZValue(2.0f);
68 qgpiName->setZValue(1.0f);
69 qgpiMuted->setZValue(3.0f);
70
71 qgpiBox->setZValue(-1.0f);
72
73 resync();
74 }
75
76 #define SCALESIZE(var) iroundf(uiSize * uiZoom * os.qrf##var .width() + 0.5f), iroundf(uiSize * uiZoom * os.qrf##var .height() + 0.5f)
77
updateMuted()78 void OverlayEditorScene::updateMuted() {
79 QImageReader qir(QLatin1String("skin:muted_self.svg"));
80 QSize sz = qir.size();
81 sz.scale(SCALESIZE(MutedDeafened), Qt::KeepAspectRatio);
82 qir.setScaledSize(sz);
83 qgpiMuted->setPixmap(QPixmap::fromImage(qir.read()));
84
85 moveMuted();
86 }
87
moveMuted()88 void OverlayEditorScene::moveMuted() {
89 qgpiMuted->setVisible(os.bMutedDeafened);
90 qgpiMuted->setPos(OverlayUser::alignedPosition(OverlayUser::scaledRect(os.qrfMutedDeafened, uiSize * uiZoom), qgpiMuted->boundingRect(), os.qaMutedDeafened));
91 qgpiMuted->setOpacity(os.fMutedDeafened);
92 }
93
updateUserName()94 void OverlayEditorScene::updateUserName() {
95 QString qsName;
96
97 switch (tsColor) {
98 case Settings::Passive:
99 qsName = Overlay::tr("Silent");
100 break;
101 case Settings::Talking:
102 qsName = Overlay::tr("Talking");
103 break;
104 case Settings::Whispering:
105 qsName = Overlay::tr("Whisper");
106 break;
107 case Settings::Shouting:
108 qsName = Overlay::tr("Shout");
109 break;
110 }
111
112 const QPixmap &pm = OverlayTextLine(qsName, os.qfUserName).createPixmap(SCALESIZE(UserName), os.qcUserName[tsColor]);
113 qgpiName->setPixmap(pm);
114
115 moveUserName();
116 }
117
moveUserName()118 void OverlayEditorScene::moveUserName() {
119 qgpiName->setVisible(os.bUserName);
120 qgpiName->setPos(OverlayUser::alignedPosition(OverlayUser::scaledRect(os.qrfUserName, uiSize * uiZoom), qgpiName->boundingRect(), os.qaUserName));
121 qgpiName->setOpacity(os.fUserName);
122 }
123
updateChannel()124 void OverlayEditorScene::updateChannel() {
125 const QPixmap &pm = OverlayTextLine(Overlay::tr("Channel"), os.qfChannel).createPixmap(SCALESIZE(Channel), os.qcChannel);
126 qgpiChannel->setPixmap(pm);
127
128 moveChannel();
129 }
130
moveChannel()131 void OverlayEditorScene::moveChannel() {
132 qgpiChannel->setVisible(os.bChannel);
133 qgpiChannel->setPos(OverlayUser::alignedPosition(OverlayUser::scaledRect(os.qrfChannel, uiSize * uiZoom), qgpiChannel->boundingRect(), os.qaChannel));
134 qgpiChannel->setOpacity(os.fChannel);
135 }
136
updateAvatar()137 void OverlayEditorScene::updateAvatar() {
138 QImage img;
139 QImageReader qir(QLatin1String("skin:default_avatar.svg"));
140 QSize sz = qir.size();
141 sz.scale(SCALESIZE(Avatar), Qt::KeepAspectRatio);
142 qir.setScaledSize(sz);
143 img = qir.read();
144 qgpiAvatar->setPixmap(QPixmap::fromImage(img));
145
146 moveAvatar();
147 }
148
moveAvatar()149 void OverlayEditorScene::moveAvatar() {
150 qgpiAvatar->setVisible(os.bAvatar);
151 qgpiAvatar->setPos(OverlayUser::alignedPosition(OverlayUser::scaledRect(os.qrfAvatar, uiSize * uiZoom), qgpiAvatar->boundingRect(), os.qaAvatar));
152 qgpiAvatar->setOpacity(os.fAvatar);
153 }
154
moveBox()155 void OverlayEditorScene::moveBox() {
156 QRectF childrenBounds = os.qrfAvatar | os.qrfChannel | os.qrfMutedDeafened | os.qrfUserName;
157
158 bool haspen = (os.qcBoxPen != os.qcBoxFill) && (! qFuzzyCompare(os.qcBoxPen.alphaF(), static_cast<qreal>(0.0f)));
159 qreal pw = haspen ? qMax<qreal>(1.0f, os.fBoxPenWidth * uiSize * uiZoom) : 0.0f;
160 qreal pad = os.fBoxPad * uiSize * uiZoom;
161
162 QPainterPath pp;
163 pp.addRoundedRect(childrenBounds.x() * uiSize * uiZoom + -pw / 2.0f - pad, childrenBounds.y() * uiSize * uiZoom + -pw / 2.0f - pad, childrenBounds.width() * uiSize * uiZoom + pw + 2.0f * pad, childrenBounds.height() * uiSize * uiZoom + pw + 2.0f * pad, 2.0f * pw, 2.0f * pw);
164 qgpiBox->setPath(pp);
165 qgpiBox->setPos(0.0f, 0.0f);
166 qgpiBox->setPen(haspen ? QPen(os.qcBoxPen, pw) : Qt::NoPen);
167 qgpiBox->setBrush(qFuzzyCompare(os.qcBoxFill.alphaF(), static_cast<qreal>(0.0f)) ? Qt::NoBrush : os.qcBoxFill);
168 qgpiBox->setOpacity(1.0f);
169
170 qgpiBox->setVisible(os.bBox);
171 }
172
updateSelected()173 void OverlayEditorScene::updateSelected() {
174 if (qgpiSelected == qgpiAvatar)
175 updateAvatar();
176 else if (qgpiSelected == qgpiName)
177 updateUserName();
178 else if (qgpiSelected == qgpiMuted)
179 updateMuted();
180 }
181
resync()182 void OverlayEditorScene::resync() {
183 QRadialGradient gradient(0, 0, 10 * uiZoom);
184 gradient.setSpread(QGradient::ReflectSpread);
185 gradient.setColorAt(0.0f, QColor(255, 255, 255, 64));
186 gradient.setColorAt(0.2f, QColor(0, 0, 0, 64));
187 gradient.setColorAt(0.4f, QColor(255, 128, 0, 64));
188 gradient.setColorAt(0.6f, QColor(0, 0, 0, 64));
189 gradient.setColorAt(0.8f, QColor(0, 128, 255, 64));
190 gradient.setColorAt(1.0f, QColor(0, 0, 0, 64));
191 setBackgroundBrush(gradient);
192
193 updateMuted();
194 updateUserName();
195 updateChannel();
196 updateAvatar();
197
198 moveMuted();
199 moveUserName();
200 moveChannel();
201 moveAvatar();
202
203 moveBox();
204
205 qgiGroup->setOpacity(os.fUser[tsColor]);
206
207 qgpiSelected = NULL;
208 qgriSelected->setVisible(false);
209 }
210
drawBackground(QPainter * p,const QRectF & rect)211 void OverlayEditorScene::drawBackground(QPainter *p, const QRectF &rect) {
212 p->setBrushOrigin(0, 0);
213 p->fillRect(rect, backgroundBrush());
214
215 QRectF upscaled = OverlayUser::scaledRect(rect, 128.f / static_cast<float>(uiSize * uiZoom));
216
217 {
218 int min = iroundf(upscaled.left());
219 int max = iroundf(ceil(upscaled.right()));
220
221 for (int i=min;i<=max;++i) {
222 qreal v = (i / 128) * static_cast<qreal>(uiSize * uiZoom);
223
224 if (i != 0)
225 p->setPen(QPen(QColor(128, 128, 128, 255), 0.0f));
226 else
227 p->setPen(QPen(QColor(0, 0, 0, 255), 2.0f));
228
229 p->drawLine(QPointF(v, rect.top()), QPointF(v, rect.bottom()));
230 }
231 }
232
233 {
234 int min = iroundf(upscaled.top());
235 int max = iroundf(ceil(upscaled.bottom()));
236
237 for (int i=min;i<=max;++i) {
238 qreal v = (i / 128) * static_cast<qreal>(uiSize * uiZoom);
239
240 if (i != 0)
241 p->setPen(QPen(QColor(128, 128, 128, 255), 0.0f));
242 else
243 p->setPen(QPen(QColor(0, 0, 0, 255), 2.0f));
244
245 p->drawLine(QPointF(rect.left(), v), QPointF(rect.right(), v));
246 }
247 }
248 }
249
childAt(const QPointF & pos)250 QGraphicsPixmapItem *OverlayEditorScene::childAt(const QPointF &pos) {
251 QGraphicsItem *item = NULL;
252
253 if (qgriSelected->isVisible()) {
254 if (qgriSelected->rect().contains(pos)) {
255 return qgpiSelected;
256 }
257 }
258
259 foreach(QGraphicsItem *qgi, items(Qt::AscendingOrder)) {
260 if (!qgi->isVisible() || ! qgraphicsitem_cast<QGraphicsPixmapItem *>(qgi))
261 continue;
262
263 QPointF qp = pos - qgi->pos();
264 if (qgi->contains(qp)) {
265 item = qgi;
266 }
267 }
268 return static_cast<QGraphicsPixmapItem *>(item);
269 }
270
selectedRect() const271 QRectF OverlayEditorScene::selectedRect() const {
272 const QRectF *qrf = NULL;
273
274 if (qgpiSelected == qgpiMuted)
275 qrf = & os.qrfMutedDeafened;
276 else if (qgpiSelected == qgpiAvatar)
277 qrf = & os.qrfAvatar;
278 else if (qgpiSelected == qgpiChannel)
279 qrf = & os.qrfChannel;
280 else if (qgpiSelected == qgpiName)
281 qrf = & os.qrfUserName;
282
283 if (! qrf)
284 return QRectF();
285
286 return OverlayUser::scaledRect(*qrf, uiSize * uiZoom).toAlignedRect();
287 }
288
289
mousePressEvent(QGraphicsSceneMouseEvent * e)290 void OverlayEditorScene::mousePressEvent(QGraphicsSceneMouseEvent *e) {
291 QGraphicsScene::mousePressEvent(e);
292
293 if (e->isAccepted())
294 return;
295
296 if (e->button() == Qt::LeftButton) {
297 e->accept();
298
299 if (wfsHover == Qt::NoSection) {
300 qgpiSelected = childAt(e->scenePos());
301 if (qgpiSelected) {
302 qgriSelected->setRect(selectedRect());
303 qgriSelected->show();
304 } else {
305 qgriSelected->hide();
306 }
307 }
308
309 updateCursorShape(e->scenePos());
310 }
311 }
312
mouseReleaseEvent(QGraphicsSceneMouseEvent * e)313 void OverlayEditorScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) {
314 QGraphicsScene::mouseReleaseEvent(e);
315
316 if (e->isAccepted())
317 return;
318
319 if (e->button() == Qt::LeftButton) {
320 e->accept();
321
322 QRectF rect = qgriSelected->rect();
323
324 if (! qgpiSelected || (rect == selectedRect())) {
325 return;
326 }
327
328 QRectF scaled(rect.x() / (uiSize * uiZoom), rect.y() / (uiSize * uiZoom), rect.width() / (uiSize * uiZoom), rect.height() / (uiSize * uiZoom));
329
330 if (qgpiSelected == qgpiMuted) {
331 os.qrfMutedDeafened = scaled;
332 updateMuted();
333 } else if (qgpiSelected == qgpiAvatar) {
334 os.qrfAvatar = scaled;
335 updateAvatar();
336 } else if (qgpiSelected == qgpiChannel) {
337 os.qrfChannel = scaled;
338 updateChannel();
339 } else if (qgpiSelected == qgpiName) {
340 os.qrfUserName = scaled;
341 updateUserName();
342 }
343
344 moveBox();
345 }
346 }
347
mouseMoveEvent(QGraphicsSceneMouseEvent * e)348 void OverlayEditorScene::mouseMoveEvent(QGraphicsSceneMouseEvent *e) {
349 QGraphicsScene::mouseMoveEvent(e);
350
351 if (e->isAccepted())
352 return;
353
354 if (qgpiSelected && (e->buttons() & Qt::LeftButton)) {
355 e->accept();
356
357 if (wfsHover == Qt::NoSection)
358 return;
359
360 QPointF delta = e->scenePos() - e->buttonDownScenePos(Qt::LeftButton);
361
362 bool square = e->modifiers() & Qt::ShiftModifier;
363
364 QRectF orig = selectedRect();
365 switch (wfsHover) {
366 case Qt::TitleBarArea:
367 orig.translate(delta);
368 break;
369 case Qt::TopSection:
370 orig.setTop(orig.top() + delta.y());
371 if (orig.height() < 8.0f)
372 orig.setTop(orig.bottom() - 8.0f);
373 if (square)
374 orig.setRight(orig.left() + orig.height());
375 break;
376 case Qt::BottomSection:
377 orig.setBottom(orig.bottom() + delta.y());
378 if (orig.height() < 8.0f)
379 orig.setBottom(orig.top() + 8.0f);
380 if (square)
381 orig.setRight(orig.left() + orig.height());
382 break;
383 case Qt::LeftSection:
384 orig.setLeft(orig.left() + delta.x());
385 if (orig.width() < 8.0f)
386 orig.setLeft(orig.right() - 8.0f);
387 if (square)
388 orig.setBottom(orig.top() + orig.width());
389 break;
390 case Qt::RightSection:
391 orig.setRight(orig.right() + delta.x());
392 if (orig.width() < 8.0f)
393 orig.setRight(orig.left() + 8.0f);
394 if (square)
395 orig.setBottom(orig.top() + orig.width());
396 break;
397 case Qt::TopLeftSection:
398 orig.setTopLeft(orig.topLeft() + delta);
399 if (orig.height() < 8.0f)
400 orig.setTop(orig.bottom() - 8.0f);
401 if (orig.width() < 8.0f)
402 orig.setLeft(orig.right() - 8.0f);
403 if (square) {
404 qreal size = qMin(orig.width(), orig.height());
405 QPointF sz(-size, -size);
406 orig.setTopLeft(orig.bottomRight() + sz);
407 }
408 break;
409 case Qt::TopRightSection:
410 orig.setTopRight(orig.topRight() + delta);
411 if (orig.height() < 8.0f)
412 orig.setTop(orig.bottom() - 8.0f);
413 if (orig.width() < 8.0f)
414 orig.setRight(orig.left() + 8.0f);
415 if (square) {
416 qreal size = qMin(orig.width(), orig.height());
417 QPointF sz(size, -size);
418 orig.setTopRight(orig.bottomLeft() + sz);
419 }
420 break;
421 case Qt::BottomLeftSection:
422 orig.setBottomLeft(orig.bottomLeft() + delta);
423 if (orig.height() < 8.0f)
424 orig.setBottom(orig.top() + 8.0f);
425 if (orig.width() < 8.0f)
426 orig.setLeft(orig.right() - 8.0f);
427 if (square) {
428 qreal size = qMin(orig.width(), orig.height());
429 QPointF sz(-size, size);
430 orig.setBottomLeft(orig.topRight() + sz);
431 }
432 break;
433 case Qt::BottomRightSection:
434 orig.setBottomRight(orig.bottomRight() + delta);
435 if (orig.height() < 8.0f)
436 orig.setBottom(orig.top() + 8.0f);
437 if (orig.width() < 8.0f)
438 orig.setRight(orig.left() + 8.0f);
439 if (square) {
440 qreal size = qMin(orig.width(), orig.height());
441 QPointF sz(size, size);
442 orig.setBottomRight(orig.topLeft() + sz);
443 }
444 break;
445 case Qt::NoSection:
446 // Handled above, but this makes the compiler happy.
447 return;
448 }
449
450 qgriSelected->setRect(orig);
451 } else {
452 updateCursorShape(e->scenePos());
453 }
454 }
455
updateCursorShape(const QPointF & point)456 void OverlayEditorScene::updateCursorShape(const QPointF &point) {
457 Qt::CursorShape cs;
458
459 if (qgriSelected->isVisible()) {
460 wfsHover = rectSection(qgriSelected->rect(), point);
461 } else {
462 wfsHover = Qt::NoSection;
463 }
464
465 switch (wfsHover) {
466 case Qt::TopLeftSection:
467 case Qt::BottomRightSection:
468 cs = Qt::SizeFDiagCursor;
469 break;
470 case Qt::TopRightSection:
471 case Qt::BottomLeftSection:
472 cs = Qt::SizeBDiagCursor;
473 break;
474 case Qt::TopSection:
475 case Qt::BottomSection:
476 cs = Qt::SizeVerCursor;
477 break;
478 case Qt::LeftSection:
479 case Qt::RightSection:
480 cs = Qt::SizeHorCursor;
481 break;
482 case Qt::TitleBarArea:
483 cs = Qt::OpenHandCursor;
484 break;
485 default:
486 cs = Qt::ArrowCursor;
487 break;
488 }
489
490
491 foreach(QGraphicsView *v, views()) {
492 if (v->viewport()->cursor().shape() != cs) {
493 v->viewport()->setCursor(cs);
494
495 // But an embedded, injected GraphicsView doesn't propagage mouse cursors...
496 QWidget *p = v->parentWidget();
497 if (p) {
498 QGraphicsProxyWidget *qgpw = p->graphicsProxyWidget();
499 if (qgpw) {
500 qgpw->setCursor(cs);
501 if (g.ocIntercept)
502 g.ocIntercept->updateMouse();
503 }
504 }
505 }
506 }
507 }
508
contextMenuEvent(QGraphicsSceneContextMenuEvent * e)509 void OverlayEditorScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *e) {
510 QGraphicsScene::contextMenuEvent(e);
511
512 if (e->isAccepted())
513 return;
514
515 if (! e->widget())
516 return;
517
518 QGraphicsPixmapItem *item = childAt(e->scenePos());
519
520 QMenu qm(e->widget());
521
522 QMenu *qmLayout = qm.addMenu(tr("Layout preset"));
523 QAction *qaLayoutLargeAvatar = qmLayout->addAction(tr("Large square avatar"));
524 QAction *qaLayoutText = qmLayout->addAction(tr("Avatar and Name"));
525
526 QMenu *qmTrans = qm.addMenu(tr("User Opacity"));
527 QActionGroup *qagUser = new QActionGroup(&qm);
528 QAction *userOpacity[8];
529 for (int i=0;i<8;++i) {
530 qreal o = (i + 1) / 8.0;
531
532 userOpacity[i] = new QAction(tr("%1%").arg(o * 100.0f, 0, 'f', 1), qagUser);
533 userOpacity[i]->setCheckable(true);
534 userOpacity[i]->setData(o);
535
536 if (qFuzzyCompare(qgiGroup->opacity(), o))
537 userOpacity[i]->setChecked(true);
538
539 qmTrans->addAction(userOpacity[i]);
540 }
541
542 QAction *color = NULL;
543 QAction *fontAction = NULL;
544 QAction *objectOpacity[8];
545 for (int i=0;i<8;++i)
546 objectOpacity[i] = NULL;
547 QAction *boxpen[4] = { NULL, NULL, NULL, NULL};
548 QAction *boxpad[4] = { NULL, NULL, NULL, NULL};
549 QAction *boxpencolor = NULL;
550 QAction *boxfillcolor = NULL;
551
552 QAction *align[6];
553 for (int i=0;i<6;++i)
554 align[i] = NULL;
555
556 if (item) {
557 qm.addSeparator();
558 QMenu *qmObjTrans = qm.addMenu(tr("Object Opacity"));
559 QActionGroup *qagObject = new QActionGroup(&qm);
560 for (int i=0;i<8;++i) {
561 qreal o = (i + 1) / 8.0;
562
563 objectOpacity[i] = new QAction(tr("%1%").arg(o * 100.0f, 0, 'f', 1), qagObject);
564 objectOpacity[i]->setCheckable(true);
565 objectOpacity[i]->setData(o);
566 if (qFuzzyCompare(item->opacity(), o))
567 objectOpacity[i]->setChecked(true);
568 qmObjTrans->addAction(objectOpacity[i]);
569 }
570
571 QMenu *qmObjAlign = qm.addMenu(tr("Alignment"));
572 Qt::Alignment a;
573 if (item == qgpiAvatar)
574 a = os.qaAvatar;
575 else if (item == qgpiChannel)
576 a = os.qaChannel;
577 else if (item == qgpiMuted)
578 a = os.qaMutedDeafened;
579 else
580 a = os.qaUserName;
581
582 align[0] = qmObjAlign->addAction(tr("Left"));
583 align[0]->setCheckable(true);
584 align[0]->setData(Qt::AlignLeft);
585 if (a & Qt::AlignLeft)
586 align[0]->setChecked(true);
587 align[1] = qmObjAlign->addAction(tr("Center"));
588 align[1]->setCheckable(true);
589 align[1]->setData(Qt::AlignHCenter);
590 if (a & Qt::AlignHCenter)
591 align[1]->setChecked(true);
592 align[2] = qmObjAlign->addAction(tr("Right"));
593 align[2]->setCheckable(true);
594 align[2]->setData(Qt::AlignRight);
595 if (a & Qt::AlignRight)
596 align[2]->setChecked(true);
597
598 qmObjAlign->addSeparator();
599
600 align[3] = qmObjAlign->addAction(tr("Top"));
601 align[3]->setCheckable(true);
602 align[3]->setData(Qt::AlignTop);
603 if (a & Qt::AlignTop)
604 align[3]->setChecked(true);
605 align[4] = qmObjAlign->addAction(tr("Center"));
606 align[4]->setCheckable(true);
607 align[4]->setData(Qt::AlignVCenter);
608 if (a & Qt::AlignVCenter)
609 align[4]->setChecked(true);
610 align[5] = qmObjAlign->addAction(tr("Bottom"));
611 align[5]->setCheckable(true);
612 align[5]->setData(Qt::AlignBottom);
613 if (a & Qt::AlignBottom)
614 align[5]->setChecked(true);
615
616 if ((item != qgpiAvatar) && (item != qgpiMuted)) {
617 color = qm.addAction(tr("Color..."));
618 fontAction = qm.addAction(tr("Font..."));
619 }
620 }
621
622 if (qgpiBox->isVisible()) {
623 qm.addSeparator();
624 QMenu *qmBox = qm.addMenu(tr("Bounding box"));
625 QMenu *qmPen = qmBox->addMenu(tr("Pen width"));
626 QMenu *qmPad = qmBox->addMenu(tr("Padding"));
627 boxpencolor = qmBox->addAction(tr("Pen color"));
628 boxfillcolor = qmBox->addAction(tr("Fill color"));
629
630 QActionGroup *qagPen = new QActionGroup(qmPen);
631 QActionGroup *qagPad = new QActionGroup(qmPad);
632 for (int i=0;i<4;++i) {
633 qreal v = (i) ? powf(2.0f, static_cast<float>(-10 + i)) : 0.0f;
634 boxpen[i] = new QAction(QString::number(i), qagPen);
635 boxpen[i]->setData(v);
636 boxpen[i]->setCheckable(true);
637 if (qFuzzyCompare(os.fBoxPenWidth, v))
638 boxpen[i]->setChecked(true);
639 qmPen->addAction(boxpen[i]);
640
641 boxpad[i] = new QAction(QString::number(i), qagPad);
642 boxpad[i]->setData(v);
643 boxpad[i]->setCheckable(true);
644 if (qFuzzyCompare(os.fBoxPad, v))
645 boxpad[i]->setChecked(true);
646 qmPad->addAction(boxpad[i]);
647 }
648 }
649
650 QAction *act = qm.exec(e->screenPos());
651
652 if (! act)
653 return;
654
655 for (int i=0;i<8;++i) {
656 if (userOpacity[i] == act) {
657 float o = static_cast<float>(act->data().toReal());
658 os.fUser[tsColor] = o;
659
660 qgiGroup->setOpacity(o);
661 }
662 }
663
664 for (int i=0;i<8;++i) {
665 if (objectOpacity[i] == act) {
666 qreal o = act->data().toReal();
667
668 if (item == qgpiMuted)
669 os.fMutedDeafened = o;
670 else if (item == qgpiAvatar)
671 os.fAvatar = o;
672 else if (item == qgpiChannel)
673 os.fChannel = o;
674 else if (item == qgpiName)
675 os.fUserName = o;
676
677 item->setOpacity(o);
678 }
679 }
680
681 for (int i=0;i<4;++i) {
682 if (boxpen[i] == act) {
683 os.fBoxPenWidth = act->data().toReal();
684 moveBox();
685 } else if (boxpad[i] == act) {
686 os.fBoxPad = act->data().toReal();
687 moveBox();
688 }
689 }
690
691 for (int i=0;i<6;++i) {
692 if (align[i] == act) {
693 Qt::Alignment *aptr;
694 if (item == qgpiAvatar)
695 aptr = & os.qaAvatar;
696 else if (item == qgpiChannel)
697 aptr = & os.qaChannel;
698 else if (item == qgpiMuted)
699 aptr = & os.qaMutedDeafened;
700 else
701 aptr = & os.qaUserName;
702
703 Qt::Alignment a = static_cast<Qt::Alignment>(act->data().toInt());
704 if (a & Qt::AlignHorizontal_Mask) {
705 *aptr = (*aptr & ~Qt::AlignHorizontal_Mask) | a;
706 } else {
707 *aptr = (*aptr & ~Qt::AlignVertical_Mask) | a;
708 }
709
710 updateSelected();
711 }
712 }
713
714 if (act == boxpencolor) {
715 QColor qc = QColorDialog::getColor(os.qcBoxPen, e->widget(), tr("Pick pen color"), QColorDialog::DontUseNativeDialog | QColorDialog::ShowAlphaChannel);
716 if (! qc.isValid())
717 return;
718 os.qcBoxPen = qc;
719 moveBox();
720 } else if (act == boxfillcolor) {
721 QColor qc = QColorDialog::getColor(os.qcBoxFill, e->widget(), tr("Pick fill color"), QColorDialog::DontUseNativeDialog | QColorDialog::ShowAlphaChannel);
722 if (! qc.isValid())
723 return;
724 os.qcBoxFill = qc;
725 moveBox();
726 } else if (act == color) {
727 QColor *col = NULL;
728 if (item == qgpiChannel)
729 col = & os.qcChannel;
730 else if (item == qgpiName)
731 col = & os.qcUserName[tsColor];
732 if (! col)
733 return;
734
735 QColor qc = QColorDialog::getColor(*col, e->widget(), tr("Pick color"), QColorDialog::DontUseNativeDialog);
736 if (! qc.isValid())
737 return;
738 qc.setAlpha(255);
739
740 if (qc == *col)
741 return;
742
743 *col = qc;
744 updateSelected();
745 } else if (act == fontAction) {
746 QFont *fontptr = (item == qgpiChannel) ? &os.qfChannel : &os.qfUserName;
747
748 qgpiSelected = NULL;
749 qgriSelected->hide();
750
751 // QFontDialog doesn't really like graphics view. At all.
752
753 QFontDialog qfd;
754 qfd.setOptions(QFontDialog::DontUseNativeDialog);
755 qfd.setCurrentFont(*fontptr);
756 qfd.setWindowTitle(tr("Pick font"));
757
758 int ret;
759 if (g.ocIntercept) {
760 QGraphicsProxyWidget *qgpw = new QGraphicsProxyWidget(NULL, Qt::Window);
761 qgpw->setWidget(&qfd);
762
763 addItem(qgpw);
764
765 qgpw->setZValue(3.0f);
766 qgpw->setPanelModality(QGraphicsItem::PanelModal);
767 qgpw->setPos(- qgpw->boundingRect().width() / 2.0f, - qgpw->boundingRect().height() / 2.0f);
768 qgpw->show();
769
770 ret = qfd.exec();
771
772 qgpw->hide();
773 qgpw->setWidget(NULL);
774 delete qgpw;
775 } else {
776 Qt::WindowFlags wf = g.mw->windowFlags();
777 if (wf.testFlag(Qt::WindowStaysOnTopHint))
778 qfd.setWindowFlags(qfd.windowFlags() | Qt::WindowStaysOnTopHint);
779 ret = qfd.exec();
780 }
781
782 if (! ret)
783 return;
784 *fontptr = qfd.selectedFont();
785
786 resync();
787 } else if (act == qaLayoutLargeAvatar) {
788 os.setPreset(OverlaySettings::LargeSquareAvatar);
789 resync();
790 } else if (act == qaLayoutText) {
791 os.setPreset(OverlaySettings::AvatarAndName);
792 resync();
793 }
794 }
795
distancePointLine(const QPointF & a,const QPointF & b,const QPointF & p)796 static qreal distancePointLine(const QPointF &a, const QPointF &b, const QPointF &p) {
797 qreal xda = a.x() - p.x();
798 qreal xdb = p.x() - b.x();
799
800 qreal xd = 0;
801
802 if (xda > 0)
803 xd = xda;
804 if (xdb > 0)
805 xd = qMax(xd, xdb);
806
807 qreal yda = a.y() - p.y();
808 qreal ydb = p.y() - b.y();
809
810 qreal yd = 0;
811
812 if (yda > 0)
813 yd = yda;
814 if (ydb > 0)
815 yd = qMax(yd, ydb);
816
817 return qMax(xd, yd);
818 }
819
rectSection(const QRectF & qrf,const QPointF & qp,qreal dist)820 Qt::WindowFrameSection OverlayEditorScene::rectSection(const QRectF &qrf, const QPointF &qp, qreal dist) {
821 qreal left, right, top, bottom;
822
823 top = distancePointLine(qrf.topLeft(), qrf.topRight(), qp);
824 bottom = distancePointLine(qrf.bottomLeft(), qrf.bottomRight(), qp);
825 left = distancePointLine(qrf.topLeft(), qrf.bottomLeft(), qp);
826 right = distancePointLine(qrf.topRight(), qrf.bottomRight(), qp);
827
828 if ((top < dist) && (top < bottom)) {
829 if ((left < dist) && (left < right))
830 return Qt::TopLeftSection;
831 else if (right < dist)
832 return Qt::TopRightSection;
833 return Qt::TopSection;
834 } else if (bottom < dist) {
835 if ((left < dist) && (left < right))
836 return Qt::BottomLeftSection;
837 else if (right < dist)
838 return Qt::BottomRightSection;
839 return Qt::BottomSection;
840 } else if (left < dist) {
841 return Qt::LeftSection;
842 } else if (right < dist) {
843 return Qt::RightSection;
844 }
845 if (qrf.contains(qp))
846 return Qt::TitleBarArea;
847
848 return Qt::NoSection;
849 }
850