mirror of
https://github.com/mumble-voip/mumble.git
synced 2025-10-26 11:19:16 +00:00
FEAT(talking-ui): Make channels selectable
Before this commit it was only possible to select users in the TalkingUI (and thereby it was also only possible to trigger the context menu for users via the TalkingUI). This commit extends the functionality that currently exists for users to channels as well. This includes selection as well as the context menu.
This commit is contained in:
parent
6a7251951b
commit
4d05d9538e
@ -177,6 +177,8 @@ set(MUMBLE_SOURCES
|
||||
"SvgIcon.h"
|
||||
"TalkingUI.cpp"
|
||||
"TalkingUI.h"
|
||||
"TalkingUISelection.cpp"
|
||||
"TalkingUISelection.h"
|
||||
"TextMessage.cpp"
|
||||
"TextMessage.h"
|
||||
"TextMessage.ui"
|
||||
|
||||
@ -377,9 +377,10 @@ void TalkingUI::addUser(const ClientUser *user) {
|
||||
// If this user is currently selected, mark him/her as such
|
||||
if (g.mw && g.mw->pmModel && g.mw->pmModel->getSelectedUser() == user) {
|
||||
if (m_entries.contains(user->uiSession)) {
|
||||
setSelection(&m_entries[user->uiSession]);
|
||||
Entry &entry = m_entries[user->uiSession];
|
||||
setSelection(UserSelection(entry.background, entry.userSession));
|
||||
} else {
|
||||
setSelection(nullptr);
|
||||
setSelection(EmptySelection());
|
||||
qCritical("TalkingUI::addUser requested entry for unknown user!");
|
||||
}
|
||||
}
|
||||
@ -512,26 +513,30 @@ void TalkingUI::updateUI() {
|
||||
QTimer::singleShot(0, [this]() { adjustSize(); });
|
||||
}
|
||||
|
||||
void TalkingUI::setSelection(Entry *entry) {
|
||||
if (entry != m_currentSelection) {
|
||||
void TalkingUI::setSelection(const TalkingUISelection &selection) {
|
||||
if (dynamic_cast<const EmptySelection *>(&selection)) {
|
||||
// The selection is set to an empty selection
|
||||
if (m_currentSelection) {
|
||||
// Set the selected property to false and refresh the style of the widget (letting the
|
||||
// global StyleSheet (theme) take over).
|
||||
m_currentSelection->background->setProperty("selected", false);
|
||||
m_currentSelection->background->style()->unpolish(m_currentSelection->background);
|
||||
// There currently is a selection -> clear and remove it
|
||||
m_currentSelection->discard();
|
||||
m_currentSelection.reset();
|
||||
}
|
||||
if (entry) {
|
||||
// Set the selected property to true and refresh the style of the widget (letting the
|
||||
// global StyleSheet (theme) take over).
|
||||
entry->background->setProperty("selected", true);
|
||||
entry->background->style()->unpolish(entry->background);
|
||||
|
||||
if (g.mw && g.mw->pmModel) {
|
||||
g.mw->pmModel->setSelectedUser(entry->userSession);
|
||||
} else {
|
||||
if (m_currentSelection) {
|
||||
if (selection == *m_currentSelection) {
|
||||
// Selection hasn't actually changed
|
||||
return;
|
||||
}
|
||||
|
||||
// Discard old selection (it'll get deleted on re-assignment below)
|
||||
m_currentSelection->discard();
|
||||
}
|
||||
|
||||
m_currentSelection = entry;
|
||||
// Use the new selection (which at this point we know is not the empty selection)
|
||||
m_currentSelection = selection.cloneToHeap();
|
||||
|
||||
m_currentSelection->apply();
|
||||
m_currentSelection->syncToMainWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@ -541,36 +546,53 @@ void TalkingUI::mousePressEvent(QMouseEvent *event) {
|
||||
QWidget *widget = qApp->widgetAt(event->globalPos());
|
||||
|
||||
if (!widget) {
|
||||
setSelection(nullptr);
|
||||
setSelection(EmptySelection());
|
||||
return;
|
||||
}
|
||||
|
||||
bool foundTarget = false;
|
||||
|
||||
// iterate all available entries
|
||||
QMutableHashIterator<unsigned int, Entry> it(m_entries);
|
||||
QHashIterator<unsigned int, Entry> userIt(m_entries);
|
||||
while (userIt.hasNext()) {
|
||||
userIt.next();
|
||||
|
||||
Entry *entry = nullptr;
|
||||
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
|
||||
Entry *currentEntry = &it.value();
|
||||
if (currentEntry->talkingIcon == widget || currentEntry->name == widget || currentEntry->background == widget) {
|
||||
entry = currentEntry;
|
||||
const Entry ¤tEntry = userIt.value();
|
||||
if (currentEntry.talkingIcon == widget || currentEntry.name == widget || currentEntry.background == widget) {
|
||||
foundTarget = true;
|
||||
// Select user's entry
|
||||
setSelection(UserSelection(currentEntry.background, currentEntry.userSession));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setSelection(entry);
|
||||
if (!foundTarget) {
|
||||
QHashIterator<int, QGroupBox *> channelIt(m_channels);
|
||||
while (channelIt.hasNext()) {
|
||||
channelIt.next();
|
||||
if (widget == channelIt.value()) {
|
||||
foundTarget = true;
|
||||
// Select channel's box
|
||||
setSelection(ChannelSelection(channelIt.value(), channelIt.key()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entry && event->button() == Qt::RightButton && g.mw) {
|
||||
// If an entry is selected and the right mouse button was clicked, we pretend as if the user had clicked on the client in
|
||||
// the MainWindow. For this to work we map the global mouse position to the local coordinate system of the UserView in the
|
||||
// MainWindow. The function will use some internal logic to determine the user to invoke the context menu on but if that
|
||||
// fails (which in this case it will), it'll fall back to the currently selected item. This item we have updated to the
|
||||
// correct one with the setSelection() call above resulting in the proper context menu being shown at the position of the
|
||||
// mouse which in this case is in the TalkingUI.
|
||||
QMetaObject::invokeMethod(g.mw, "on_qtvUsers_customContextMenuRequested", Qt::QueuedConnection, Q_ARG(QPoint,
|
||||
g.mw->qtvUsers->mapFromGlobal(event->globalPos())));
|
||||
|
||||
if (foundTarget) {
|
||||
if (event->button() == Qt::RightButton && g.mw) {
|
||||
// If an entry is selected and the right mouse button was clicked, we pretend as if the user had clicked on the client in
|
||||
// the MainWindow. For this to work we map the global mouse position to the local coordinate system of the UserView in the
|
||||
// MainWindow. The function will use some internal logic to determine the user to invoke the context menu on but if that
|
||||
// fails (which in this case it will), it'll fall back to the currently selected item. This item we have updated to the
|
||||
// correct one with the setSelection() call above resulting in the proper context menu being shown at the position of the
|
||||
// mouse which in this case is in the TalkingUI.
|
||||
QMetaObject::invokeMethod(g.mw, "on_qtvUsers_customContextMenuRequested", Qt::QueuedConnection, Q_ARG(QPoint,
|
||||
g.mw->qtvUsers->mapFromGlobal(event->globalPos())));
|
||||
}
|
||||
} else {
|
||||
// Clear selection
|
||||
setSelection(EmptySelection());
|
||||
}
|
||||
|
||||
updateUI();
|
||||
@ -657,12 +679,23 @@ void TalkingUI::on_mainWindowSelectionChanged(const QModelIndex ¤t, const
|
||||
|
||||
// Sync the selection in the MainWindow to the TalkingUI
|
||||
if (g.mw && g.mw->pmModel) {
|
||||
ClientUser *user = g.mw->pmModel->getUser(current);
|
||||
const ClientUser *user = g.mw->pmModel->getUser(current);
|
||||
const Channel *channel = g.mw->pmModel->getChannel(current);
|
||||
|
||||
if (user && m_entries.contains(user->uiSession)) {
|
||||
setSelection(&m_entries[user->uiSession]);
|
||||
if (user) {
|
||||
addUser(user);
|
||||
|
||||
Entry &entry = m_entries[user->uiSession];
|
||||
setSelection(UserSelection(entry.background, entry.userSession));
|
||||
} else if (!user && channel) {
|
||||
addChannel(channel);
|
||||
|
||||
// if user != nullptr, the selection is actually a user, but UserModel::getChannel still returns
|
||||
// the channel of that user. However we only want to select the channel if the user has indeed
|
||||
// selected the channel and not just one of the users in it.
|
||||
setSelection(ChannelSelection(m_channels[channel->iId], channel->iId));
|
||||
} else {
|
||||
setSelection(nullptr);
|
||||
setSelection(EmptySelection());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -714,7 +747,7 @@ void TalkingUI::on_serverDisconnected() {
|
||||
}
|
||||
m_timers.clear();
|
||||
|
||||
setSelection(nullptr);
|
||||
setSelection(EmptySelection());
|
||||
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@ -11,7 +11,10 @@
|
||||
#include <QtCore/QSet>
|
||||
#include <QtGui/QIcon>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Settings.h"
|
||||
#include "TalkingUISelection.h"
|
||||
|
||||
class QLabel;
|
||||
class QGroupBox;
|
||||
@ -49,7 +52,7 @@ class TalkingUI : public QWidget {
|
||||
/// that have stopped speaking for a certain amount of time.
|
||||
QHash<unsigned int, QTimer *> m_timers;
|
||||
/// The Entry corresponding to the currently selected user
|
||||
Entry *m_currentSelection;
|
||||
std::unique_ptr<TalkingUISelection> m_currentSelection;
|
||||
|
||||
/// The icon for a talking user
|
||||
QIcon m_talkingIcon;
|
||||
@ -118,8 +121,8 @@ class TalkingUI : public QWidget {
|
||||
|
||||
/// Set the current selection
|
||||
///
|
||||
/// @param entry A pointer to the Entry that shall be selected or nullptr to clear the selection
|
||||
void setSelection(Entry *entry);
|
||||
/// @param selection The new selection
|
||||
void setSelection(const TalkingUISelection &selection);
|
||||
|
||||
void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
|
||||
|
||||
|
||||
86
src/mumble/TalkingUISelection.cpp
Normal file
86
src/mumble/TalkingUISelection.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2020 The Mumble Developers. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file at the root of the
|
||||
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
||||
|
||||
#include "TalkingUISelection.h"
|
||||
#include "MainWindow.h"
|
||||
#include "UserModel.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QVariant>
|
||||
|
||||
// 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.
|
||||
#include "Global.h"
|
||||
|
||||
TalkingUISelection::TalkingUISelection(QWidget *widget) : m_widget(widget) {
|
||||
}
|
||||
|
||||
|
||||
void TalkingUISelection::setActive(bool active) {
|
||||
if (m_widget) {
|
||||
m_widget->setProperty("selected", active);
|
||||
// Unpolish the widget's style so that the new property can take effect
|
||||
m_widget->style()->unpolish(m_widget);
|
||||
}
|
||||
}
|
||||
|
||||
void TalkingUISelection::apply() {
|
||||
setActive(true);
|
||||
}
|
||||
|
||||
void TalkingUISelection::discard() {
|
||||
setActive(false);
|
||||
}
|
||||
|
||||
bool TalkingUISelection::operator==(const TalkingUISelection &other) const {
|
||||
return m_widget == other.m_widget;
|
||||
}
|
||||
|
||||
bool TalkingUISelection::operator!=(const TalkingUISelection &other) const {
|
||||
return m_widget != other.m_widget;
|
||||
}
|
||||
|
||||
|
||||
|
||||
UserSelection::UserSelection(QWidget *widget, unsigned int userSession)
|
||||
: TalkingUISelection(widget),
|
||||
m_userSession(userSession) {
|
||||
}
|
||||
|
||||
void UserSelection::syncToMainWindow() const {
|
||||
if (g.mw && g.mw->pmModel) {
|
||||
g.mw->pmModel->setSelectedUser(m_userSession);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<TalkingUISelection> UserSelection::cloneToHeap() const {
|
||||
return std::make_unique<UserSelection>(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
ChannelSelection::ChannelSelection(QWidget *widget, int channelID)
|
||||
: TalkingUISelection(widget),
|
||||
m_channelID(channelID) {
|
||||
}
|
||||
|
||||
void ChannelSelection::syncToMainWindow() const {
|
||||
if (g.mw && g.mw->pmModel) {
|
||||
g.mw->pmModel->setSelectedChannel(m_channelID);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<TalkingUISelection> ChannelSelection::cloneToHeap() const {
|
||||
return std::make_unique<ChannelSelection>(*this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void EmptySelection::syncToMainWindow() const {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
std::unique_ptr<TalkingUISelection> EmptySelection::cloneToHeap() const {
|
||||
return std::make_unique<EmptySelection>(*this);
|
||||
}
|
||||
86
src/mumble/TalkingUISelection.h
Normal file
86
src/mumble/TalkingUISelection.h
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright 2020 The Mumble Developers. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license
|
||||
// that can be found in the LICENSE file at the root of the
|
||||
// Mumble source tree or at <https://www.mumble.info/LICENSE>.
|
||||
|
||||
#ifndef MUMBLE_MUMBLE_TALKINGUISELECTION_H_
|
||||
#define MUMBLE_MUMBLE_TALKINGUISELECTION_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
class QWidget;
|
||||
|
||||
/// Base class of all selections within the TalkingUI
|
||||
class TalkingUISelection {
|
||||
protected:
|
||||
/// The widget that is used to represent this selection (it'll be marked
|
||||
/// as selected).
|
||||
QWidget *m_widget;
|
||||
|
||||
explicit TalkingUISelection() = default;
|
||||
|
||||
public:
|
||||
explicit TalkingUISelection(QWidget *widget);
|
||||
virtual ~TalkingUISelection() = default;
|
||||
|
||||
/// Turns this selection on or off. Turning it on usually involves marking the
|
||||
/// associated Widget in a certain way while deactivating the selection reverts this effect.
|
||||
///
|
||||
/// @param active Whether to activate this selection
|
||||
virtual void setActive(bool active);
|
||||
|
||||
/// Applies this selection. This is a shortcut for setActive(true).
|
||||
virtual void apply() final;
|
||||
|
||||
/// Discards this selection. This is a shortcut for setActive(false).
|
||||
virtual void discard() final;
|
||||
|
||||
/// Synchronizes this selection to the MainWindow
|
||||
virtual void syncToMainWindow() const = 0;
|
||||
|
||||
bool operator==(const TalkingUISelection &other) const;
|
||||
bool operator!=(const TalkingUISelection &other) const;
|
||||
|
||||
virtual std::unique_ptr<TalkingUISelection> cloneToHeap() const = 0;
|
||||
};
|
||||
|
||||
/// A class representing the selection of a user in the TalkingUI
|
||||
class UserSelection : public TalkingUISelection {
|
||||
protected:
|
||||
const unsigned int m_userSession;
|
||||
|
||||
public:
|
||||
explicit UserSelection(QWidget *widget, unsigned int userSession);
|
||||
explicit UserSelection(const UserSelection &) = default;
|
||||
|
||||
virtual void syncToMainWindow() const override;
|
||||
|
||||
virtual std::unique_ptr<TalkingUISelection> cloneToHeap() const override;
|
||||
};
|
||||
|
||||
/// A class representing the selection of a channel in the TalkingUI
|
||||
class ChannelSelection : public TalkingUISelection {
|
||||
protected:
|
||||
const int m_channelID;
|
||||
|
||||
public:
|
||||
explicit ChannelSelection(QWidget *widget, int channelID);
|
||||
explicit ChannelSelection(const ChannelSelection &) = default;
|
||||
|
||||
virtual void syncToMainWindow() const override;
|
||||
|
||||
virtual std::unique_ptr<TalkingUISelection> cloneToHeap() const override;
|
||||
};
|
||||
|
||||
/// A class representing an empty selection in the TalkingUI
|
||||
class EmptySelection : public TalkingUISelection {
|
||||
public:
|
||||
explicit EmptySelection() = default;
|
||||
explicit EmptySelection(const EmptySelection &) = default;
|
||||
|
||||
virtual void syncToMainWindow() const override;
|
||||
|
||||
virtual std::unique_ptr<TalkingUISelection> cloneToHeap() const override;
|
||||
};
|
||||
|
||||
#endif // MUMBLE_MUMBLE_TALKINGUISELECTION_H_
|
||||
@ -135,7 +135,8 @@ HEADERS *= BanEditor.h \
|
||||
Screen.h \
|
||||
SvgIcon.h \
|
||||
Markdown.h \
|
||||
TalkingUI.h
|
||||
TalkingUI.h \
|
||||
TalkingUISelection.h
|
||||
|
||||
SOURCES *= BanEditor.cpp \
|
||||
ACLEditor.cpp \
|
||||
@ -198,7 +199,8 @@ SOURCES *= BanEditor.cpp \
|
||||
Screen.cpp \
|
||||
SvgIcon.cpp \
|
||||
Markdown.cpp \
|
||||
TalkingUI.cpp
|
||||
TalkingUI.cpp \
|
||||
TalkingUISelection.cpp
|
||||
|
||||
!CONFIG(no-overlay) {
|
||||
DEFINES *= USE_OVERLAY
|
||||
|
||||
Loading…
Reference in New Issue
Block a user