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