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