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 "OverlayUserGroup.h"
9 
10 #include "OverlayUser.h"
11 #include "OverlayClient.h"
12 #include "OverlayEditor.h"
13 #include "OverlayText.h"
14 #include "User.h"
15 #include "Channel.h"
16 #include "ClientUser.h"
17 #include "Message.h"
18 #include "Database.h"
19 #include "NetworkConfig.h"
20 #include "ServerHandler.h"
21 #include "MainWindow.h"
22 #include "GlobalShortcut.h"
23 
24 // 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.
25 #include "Global.h"
26 
27 template <typename T>
boundingRect() const28 QRectF OverlayGroup::boundingRect() const {
29 	QRectF qr;
30 	foreach(const QGraphicsItem *item, childItems())
31 		if (item->isVisible() && (item->type() == T::Type))
32 			qr |= item->boundingRect().translated(item->pos());
33 
34 	return qr;
35 }
36 
OverlayUserGroup(OverlaySettings * osptr)37 OverlayUserGroup::OverlayUserGroup(OverlaySettings *osptr) :
38 		OverlayGroup(),
39 		os(osptr),
40 		qgeiHandle(NULL),
41 		bShowExamples(false) { }
42 
~OverlayUserGroup()43 OverlayUserGroup::~OverlayUserGroup() {
44 	reset();
45 }
46 
reset()47 void OverlayUserGroup::reset() {
48 	foreach(OverlayUser *ou, qlExampleUsers)
49 		delete ou;
50 	qlExampleUsers.clear();
51 
52 	foreach(OverlayUser *ou, qmUsers)
53 		delete ou;
54 	qmUsers.clear();
55 
56 	delete qgeiHandle;
57 	qgeiHandle = NULL;
58 }
59 
type() const60 int OverlayUserGroup::type() const {
61 	return Type;
62 }
63 
contextMenuEvent(QGraphicsSceneContextMenuEvent * e)64 void OverlayUserGroup::contextMenuEvent(QGraphicsSceneContextMenuEvent *e) {
65 	e->accept();
66 
67 #ifdef Q_OS_MAC
68 	bool embed = g.ocIntercept != NULL;
69 	QMenu qm(embed ? NULL : e->widget());
70 	if (embed) {
71 		QGraphicsScene *scene = g.ocIntercept->qgv.scene();
72 		scene->addWidget(&qm);
73 	}
74 #else
75 	QMenu qm(g.ocIntercept ? g.mw : e->widget());
76 #endif
77 
78 	QMenu *qmShow = qm.addMenu(OverlayClient::tr("Filter"));
79 
80 	QAction *qaShowTalking = qmShow->addAction(OverlayClient::tr("Only talking"));
81 	qaShowTalking->setCheckable(true);
82 	if (os->osShow == OverlaySettings::Talking)
83 		qaShowTalking->setChecked(true);
84 
85 	QAction *qaShowActive = qmShow->addAction(OverlayClient::tr("Talking and recently active"));
86 	qaShowActive->setCheckable(true);
87 	if (os->osShow == OverlaySettings::Active)
88 		qaShowActive->setChecked(true);
89 
90 	QAction *qaShowHome = qmShow->addAction(OverlayClient::tr("All in current channel"));
91 	qaShowHome->setCheckable(true);
92 	if (os->osShow == OverlaySettings::HomeChannel)
93 		qaShowHome->setChecked(true);
94 
95 	QAction *qaShowLinked = qmShow->addAction(OverlayClient::tr("All in linked channels"));
96 	qaShowLinked->setCheckable(true);
97 	if (os->osShow == OverlaySettings::LinkedChannels)
98 		qaShowLinked->setChecked(true);
99 
100 	qmShow->addSeparator();
101 
102 	QAction *qaShowSelf = qmShow->addAction(OverlayClient::tr("Always show yourself"));
103 	qaShowSelf->setCheckable(true);
104 	qaShowSelf->setEnabled(os->osShow == OverlaySettings::Talking || os->osShow == OverlaySettings::Active);
105 	if (os->bAlwaysSelf)
106 		qaShowSelf->setChecked(true);
107 
108 	qmShow->addSeparator();
109 
110 	QAction *qaConfigureRecentlyActiveTime = qmShow->addAction(OverlayClient::tr("Configure recently active time (%1 seconds)...").arg(os->uiActiveTime));
111 	qaConfigureRecentlyActiveTime->setEnabled(os->osShow == OverlaySettings::Active);
112 
113 	QMenu *qmColumns = qm.addMenu(OverlayClient::tr("Columns"));
114 	QAction *qaColumns[6];
115 	for (unsigned int i=1;i<=5;++i) {
116 		qaColumns[i] = qmColumns->addAction(QString::number(i));
117 		qaColumns[i]->setCheckable(true);
118 		qaColumns[i]->setChecked(i == os->uiColumns);
119 	}
120 
121 	QMenu *qmSort = qm.addMenu(OverlayClient::tr("Sort"));
122 
123 	QAction *qaSortAlphabetically = qmSort->addAction(OverlayClient::tr("Alphabetically"));
124 	qaSortAlphabetically->setCheckable(true);
125 	if (os->osSort == OverlaySettings::Alphabetical)
126 		qaSortAlphabetically->setChecked(true);
127 
128 	QAction *qaSortLastStateChange = qmSort->addAction(OverlayClient::tr("Last state change"));
129 	qaSortLastStateChange->setCheckable(true);
130 	if (os->osSort == OverlaySettings::LastStateChange)
131 		qaSortLastStateChange->setChecked(true);
132 
133 	QAction *qaEdit = qm.addAction(OverlayClient::tr("Edit..."));
134 	QAction *qaZoom = qm.addAction(OverlayClient::tr("Reset Zoom"));
135 
136 	QAction *act = qm.exec(e->screenPos());
137 
138 	if (! act)
139 		return;
140 
141 	if (act == qaEdit) {
142 		if (g.ocIntercept) {
143 			QMetaObject::invokeMethod(g.ocIntercept, "openEditor", Qt::QueuedConnection);
144 		} else {
145 			OverlayEditor oe(qApp->activeModalWidget(), NULL, os);
146 			connect(&oe, SIGNAL(applySettings()), this, SLOT(updateLayout()));
147 			oe.exec();
148 		}
149 	} else if (act == qaZoom) {
150 		os->fZoom = 1.0f;
151 		updateLayout();
152 	} else if (act == qaShowTalking) {
153 		os->osShow = OverlaySettings::Talking;
154 		updateUsers();
155 	} else if (act == qaShowActive) {
156 		os->osShow = OverlaySettings::Active;
157 		updateUsers();
158 	} else if (act == qaShowHome) {
159 		os->osShow = OverlaySettings::HomeChannel;
160 		updateUsers();
161 	} else if (act == qaShowLinked) {
162 		os->osShow = OverlaySettings::LinkedChannels;
163 		updateUsers();
164 	} else if (act == qaShowSelf) {
165 		os->bAlwaysSelf = ! os->bAlwaysSelf;
166 		updateUsers();
167 	} else if (act == qaConfigureRecentlyActiveTime) {
168 		// FIXME: This might not be the best place to configure this setting, but currently
169 		// there's not really a suitable place to put this. In the future an additional tab
170 		// might be added for some advanced overlay options, which could then include this
171 		// setting.
172 		bool ok;
173 		int newValue = QInputDialog::getInt(
174 		                   qm.parentWidget(),
175 		                   OverlayClient::tr("Configure recently active time"),
176 		                   OverlayClient::tr("Amount of seconds users remain active after talking:"),
177 		                   os->uiActiveTime, 1, 2147483647, 1, &ok);
178 		if (ok) {
179 			os->uiActiveTime = newValue;
180 		}
181 		updateUsers();
182 	} else if (act == qaSortAlphabetically) {
183 		os->osSort = OverlaySettings::Alphabetical;
184 		updateUsers();
185 	} else if (act == qaSortLastStateChange) {
186 		os->osSort = OverlaySettings::LastStateChange;
187 		updateUsers();
188 	} else {
189 		for (int i=1;i<=5;++i) {
190 			if (act == qaColumns[i]) {
191 				os->uiColumns = i;
192 				updateLayout();
193 			}
194 		}
195 	}
196 }
197 
wheelEvent(QGraphicsSceneWheelEvent * e)198 void OverlayUserGroup::wheelEvent(QGraphicsSceneWheelEvent *e) {
199 	e->accept();
200 
201 	qreal scaleFactor = 0.875f;
202 
203 	if (e->delta() > 0)
204 		scaleFactor = 1.0f / 0.875f;
205 
206 	if ((scaleFactor < 1.0f) && (os->fZoom <= (1.0f / 4.0f)))
207 		return;
208 	else if ((scaleFactor > 1.0f) && (os->fZoom >= 4.0f))
209 		return;
210 
211 	os->fZoom *= scaleFactor;
212 
213 	updateLayout();
214 }
215 
sceneEventFilter(QGraphicsItem * watched,QEvent * e)216 bool OverlayUserGroup::sceneEventFilter(QGraphicsItem *watched, QEvent *e) {
217 	switch (e->type()) {
218 		case QEvent::GraphicsSceneMouseMove:
219 		case QEvent::GraphicsSceneMouseRelease:
220 			QMetaObject::invokeMethod(this, "moveUsers", Qt::QueuedConnection);
221 			break;
222 		default:
223 			break;
224 
225 	}
226 	return OverlayGroup::sceneEventFilter(watched, e);
227 }
228 
moveUsers()229 void OverlayUserGroup::moveUsers() {
230 	if (! qgeiHandle)
231 		return;
232 
233 	const QRectF &sr = scene()->sceneRect();
234 	const QPointF &p = qgeiHandle->pos();
235 
236 	os->fX = static_cast<float>(qBound(0.0, p.x() / sr.width(), 1.0));
237 	os->fY = static_cast<float>(qBound(0.0, p.y() / sr.height(), 1.0));
238 
239 	qgeiHandle->setPos(os->fX * sr.width(), os->fY * sr.height());
240 	updateUsers();
241 }
242 
updateLayout()243 void OverlayUserGroup::updateLayout() {
244 	prepareGeometryChange();
245 	reset();
246 	updateUsers();
247 }
248 
updateUsers()249 void OverlayUserGroup::updateUsers() {
250 	const QRectF &sr = scene()->sceneRect();
251 
252 	unsigned int uiHeight = iroundf(sr.height() + 0.5f);
253 
254 	QList<QGraphicsItem *> items;
255 	foreach(QGraphicsItem *qgi, childItems())
256 		items << qgi;
257 
258 	QList<OverlayUser *> users;
259 	if (bShowExamples) {
260 		if (qlExampleUsers.isEmpty()) {
261 			qlExampleUsers << new OverlayUser(Settings::Passive, uiHeight, os);
262 			qlExampleUsers << new OverlayUser(Settings::Talking, uiHeight, os);
263 			qlExampleUsers << new OverlayUser(Settings::Whispering, uiHeight, os);
264 			qlExampleUsers << new OverlayUser(Settings::Shouting, uiHeight, os);
265 		}
266 
267 		users = qlExampleUsers;
268 		foreach(OverlayUser *ou, users)
269 			items.removeAll(ou);
270 
271 		if (! qgeiHandle) {
272 			qgeiHandle = new QGraphicsEllipseItem(QRectF(-4.0f, -4.0f, 8.0f, 8.0f));
273 			qgeiHandle->setPen(QPen(Qt::darkRed, 0.0f));
274 			qgeiHandle->setBrush(Qt::red);
275 			qgeiHandle->setZValue(0.5f);
276 			qgeiHandle->setFlag(QGraphicsItem::ItemIsMovable);
277 			qgeiHandle->setFlag(QGraphicsItem::ItemIsSelectable);
278 			qgeiHandle->setPos(sr.width() * os->fX, sr.height() * os->fY);
279 			scene()->addItem(qgeiHandle);
280 			qgeiHandle->show();
281 			qgeiHandle->installSceneEventFilter(this);
282 		}
283 	} else {
284 		delete qgeiHandle;
285 		qgeiHandle = NULL;
286 	}
287 
288 	ClientUser *self = ClientUser::get(g.uiSession);
289 	if (self) {
290 		QList<ClientUser *> showusers;
291 		Channel *home = ClientUser::get(g.uiSession)->cChannel;
292 
293 		switch (os->osShow) {
294 			case OverlaySettings::LinkedChannels:
295 				foreach(Channel *c, home->allLinks())
296 					foreach(User *p, c->qlUsers)
297 						showusers << static_cast<ClientUser *>(p);
298 				foreach(ClientUser *cu, ClientUser::getTalking())
299 					if (! showusers.contains(cu))
300 						showusers << cu;
301 				break;
302 			case OverlaySettings::HomeChannel:
303 				foreach(User *p, home->qlUsers)
304 					showusers << static_cast<ClientUser *>(p);
305 				foreach(ClientUser *cu, ClientUser::getTalking())
306 					if (! showusers.contains(cu))
307 						showusers << cu;
308 				break;
309 			case OverlaySettings::Active:
310 				showusers = ClientUser::getActive();
311 				if (os->bAlwaysSelf && !showusers.contains(self))
312 					showusers << self;
313 				break;
314 			default:
315 				showusers = ClientUser::getTalking();
316 				if (os->bAlwaysSelf && (self->tsState == Settings::Passive))
317 					showusers << self;
318 				break;
319 		}
320 
321 		ClientUser::sortUsersOverlay(showusers);
322 
323 		foreach(ClientUser *cu, showusers) {
324 			OverlayUser *ou = qmUsers.value(cu);
325 			if (! ou) {
326 				ou = new OverlayUser(cu, uiHeight, os);
327 				connect(cu, SIGNAL(destroyed(QObject *)), this, SLOT(userDestroyed(QObject *)));
328 				qmUsers.insert(cu, ou);
329 				ou->hide();
330 			} else {
331 				items.removeAll(ou);
332 			}
333 			users << ou;
334 		}
335 	}
336 
337 	foreach(QGraphicsItem *qgi, items) {
338 		scene()->removeItem(qgi);
339 		qgi->hide();
340 	}
341 
342 	QRectF childrenBounds = os->qrfAvatar | os->qrfChannel | os->qrfMutedDeafened | os->qrfUserName;
343 
344 	int pad = os->bBox ? iroundf(uiHeight * os->fZoom * (os->fBoxPad + os->fBoxPenWidth) + 0.5f) : 0;
345 	int width = iroundf(childrenBounds.width() * uiHeight * os->fZoom + 0.5f) + 2 * pad;
346 	int height = iroundf(childrenBounds.height() * uiHeight * os->fZoom + 0.5f) + 2 * pad;
347 
348 	int xOffset = - iroundf(childrenBounds.left() * uiHeight * os->fZoom + 0.5f) + pad;
349 	int yOffset = - iroundf(childrenBounds.top() * uiHeight * os->fZoom + 0.5f) + pad;
350 
351 	unsigned int yPos = 0;
352 	unsigned int xPos = 0;
353 
354 	foreach(OverlayUser *ou, users) {
355 		if (ou->parentItem() == NULL)
356 			ou->setParentItem(this);
357 
358 		ou->setPos(xPos * (width+4) + xOffset, yPos * (height + 4) + yOffset);
359 		ou->updateUser();
360 		ou->show();
361 
362 		if (xPos >= (os->uiColumns - 1)) {
363 			xPos = 0;
364 			++yPos;
365 		} else {
366 			++xPos;
367 		}
368 	}
369 
370 	QRectF br = boundingRect<OverlayUser>();
371 
372 	int basex = qBound<int>(0, iroundf(sr.width() * os->fX + 0.5f), iroundf(sr.width() - br.width() + 0.5f));
373 	int basey = qBound<int>(0, iroundf(sr.height() * os->fY + 0.5f), iroundf(sr.height() - br.height() + 0.5f));
374 
375 	setPos(basex, basey);
376 }
377 
userDestroyed(QObject * obj)378 void OverlayUserGroup::userDestroyed(QObject *obj) {
379 	OverlayUser *ou = qmUsers.take(obj);
380 	delete ou;
381 }
382