Skip to content
Permalink
Browse files

Added channel hiding/filter feature

* To enhance usability of mumble on servers with lots of channels
(like mumble.piratenpartei-nrw.de)
  • Loading branch information...
dc6jgk authored and Kissaki committed Apr 13, 2013
1 parent 325226c commit 304bf438daecad36285b8647b9bc72b685cf31ca

Large diffs are not rendered by default.

@@ -0,0 +1,17 @@
http://commons.wikimedia.org/wiki/File:Filter.svg


I, the copyright holder of this work, hereby publish it under the following license:
w:en:Creative Commons
attribution share alike This file is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license.

You are free:

to share � to copy, distribute and transmit the work
to remix � to adapt the work

Under the following conditions:

attribution � You must attribute the work in the manner specified by the author or licensor (but not in any way that suggests that they endorse you or your use of the work).
share alike � If you alter, transform, or build upon this work, you may distribute the resulting work only under the same or similar license to this one.

@@ -51,6 +51,7 @@ Channel::Channel(int id, const QString &name, QObject *p) : QObject(p) {
cParent->addChannel(this);
#ifdef MUMBLE
uiPermissions = 0;
bHidden = false;
#endif
}

@@ -73,6 +73,7 @@ class Channel : public QObject {

#ifdef MUMBLE
unsigned int uiPermissions;
bool bHidden;

static QHash<int, Channel *> c_qhChannels;
static QReadWriteLock c_qrwlChannels;
@@ -166,6 +166,9 @@ Database::Database() {
execQueryAndLogFailure(query, QLatin1String("CREATE TABLE IF NOT EXISTS `muted` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `hash` TEXT)"));
execQueryAndLogFailure(query, QLatin1String("CREATE UNIQUE INDEX IF NOT EXISTS `muted_hash` ON `muted`(`hash`)"));

execQueryAndLogFailure(query, QLatin1String("CREATE TABLE IF NOT EXISTS `hidden` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `hash` TEXT)"));
execQueryAndLogFailure(query, QLatin1String("CREATE UNIQUE INDEX IF NOT EXISTS `hidden_hash` ON `hidden`(`hash`)"));

execQueryAndLogFailure(query, QLatin1String("CREATE TABLE IF NOT EXISTS `pingcache` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `hostname` TEXT, `port` INTEGER, `ping` INTEGER)"));
execQueryAndLogFailure(query, QLatin1String("CREATE UNIQUE INDEX IF NOT EXISTS `pingcache_host_port` ON `pingcache`(`hostname`,`port`)"));

@@ -280,6 +283,29 @@ void Database::setLocalMuted(const QString &hash, bool muted) {
execQueryAndLogFailure(query);
}

bool Database::isLocalHidden(const QString &hash) {
QSqlQuery query;

query.prepare(QLatin1String("SELECT `hash` FROM `hidden` WHERE `hash` = ?"));
query.addBindValue(hash);
execQueryAndLogFailure(query);
while (query.next()) {
return true;
}
return false;
}

void Database::setLocalHidden(const QString &hash, bool hidden) {
QSqlQuery query;

if (hidden)
query.prepare(QLatin1String("INSERT INTO `hidden` (`hash`) VALUES (?)"));
else
query.prepare(QLatin1String("DELETE FROM `hidden` WHERE `hash` = ?"));
query.addBindValue(hash);
execQueryAndLogFailure(query);
}

QMap<QPair<QString, unsigned short>, unsigned int> Database::getPingCache() {
QSqlQuery query;
QMap<QPair<QString, unsigned short>, unsigned int> map;
@@ -60,6 +60,9 @@ class Database : public QObject {
static bool isLocalMuted(const QString &hash);
static void setLocalMuted(const QString &hash, bool muted);

static bool isLocalHidden(const QString &hash);
static void setLocalHidden(const QString &hash, bool hidden);

static QMap<QPair<QString, unsigned short>, unsigned int> getPingCache();
static void setPingCache(const QMap<QPair<QString, unsigned short>, unsigned int> &cache);

@@ -134,6 +134,8 @@ void LookConfig::load(const Settings &r) {
loadCheckBox(qcbShowContextMenuInMenuBar, r.bShowContextMenuInMenuBar);
loadCheckBox(qcbHighContrast, r.bHighContrast);
loadCheckBox(qcbChatBarUseSelection, r.bChatBarUseSelection);
loadCheckBox(qcbFilterHidesEmptyChannels, r.bFilterHidesEmptyChannels);

}

void LookConfig::save() const {
@@ -174,6 +176,7 @@ void LookConfig::save() const {
s.bShowContextMenuInMenuBar = qcbShowContextMenuInMenuBar->isChecked();
s.bHighContrast = qcbHighContrast->isChecked();
s.bChatBarUseSelection = qcbChatBarUseSelection->isChecked();
s.bFilterHidesEmptyChannels = qcbFilterHidesEmptyChannels->isChecked();
}

void LookConfig::accept() const {
@@ -307,6 +307,13 @@
</property>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QCheckBox" name="qcbFilterHidesEmptyChannels">
<property name="text">
<string>Filter automatically hides empty channels</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@@ -65,6 +65,7 @@
#include "ViewCert.h"
#include "VoiceRecorderDialog.h"
#include "../SignalCurry.h"
#include "Settings.h"

#ifdef Q_OS_WIN
#include "TaskList.h"
@@ -261,6 +262,9 @@ void MainWindow::createActions() {
gsMetaChannel=new GlobalShortcut(this, idx++, tr("Join Channel", "Global Shortcut"));
gsMetaChannel->setObjectName(QLatin1String("MetaChannel"));

gsHideChannel=new GlobalShortcut(this, idx++, tr("Hide Channel", "Global Shortcut"));
gsHideChannel->setObjectName(QLatin1String("HideChannel"));

gsToggleOverlay=new GlobalShortcut(this, idx++, tr("Toggle Overlay", "Global Shortcut"), false);
gsToggleOverlay->setObjectName(QLatin1String("ToggleOverlay"));
gsToggleOverlay->qsToolTip = tr("Toggle state of in-game overlay.", "Global Shortcut");
@@ -324,6 +328,8 @@ void MainWindow::setupGui() {
qaAudioMute->setChecked(g.s.bMute);
qaAudioDeaf->setChecked(g.s.bDeaf);
qaAudioTTS->setChecked(g.s.bTTS);
qaFilterToggle->setChecked(g.s.bFilterActive);

qaHelpWhatsThis->setShortcuts(QKeySequence::WhatsThis);

qaConfigMinimal->setChecked(g.s.bMinimalView);
@@ -1626,6 +1632,12 @@ void MainWindow::qmChannel_aboutToShow() {
qmChannel->addAction(qaChannelCopyURL);
qmChannel->addAction(qaChannelSendMessage);

// hiding the root is nonsense
if(c && c->cParent) {
qmChannel->addSeparator();
qmChannel->addAction(qaChannelHide);
}

#ifndef Q_OS_MAC
if (g.s.bMinimalView) {
qmChannel->addSeparator();
@@ -1642,8 +1654,8 @@ void MainWindow::qmChannel_aboutToShow() {
qmChannel->addAction(a);
}

bool add, remove, acl, link, unlink, unlinkall, msg;
add = remove = acl = link = unlink = unlinkall = msg = false;
bool add, remove, acl, link, unlink, unlinkall, msg, hide;
add = remove = acl = link = unlink = unlinkall = msg = hide = false;

if (g.uiSession != 0) {
add = true;
@@ -1666,6 +1678,9 @@ void MainWindow::qmChannel_aboutToShow() {
}
}

if(c)
qaChannelHide->setChecked(c->bHidden);

qaChannelAdd->setEnabled(add);
qaChannelRemove->setEnabled(remove);
qaChannelACL->setEnabled(acl);
@@ -1684,6 +1699,17 @@ void MainWindow::on_qaChannelJoin_triggered() {
}
}

void MainWindow::on_qaChannelHide_triggered() {
Channel *c = getContextMenuChannel();

if (c) {
QByteArray ba = c->qsName.toLocal8Bit();

This comment has been minimized.

Copy link
@hacst

hacst Sep 19, 2013

Member

unused


UserModel *um = static_cast<UserModel *>(qtvUsers->model());
um->toggleHidden(c);
}
}

void MainWindow::on_qaChannelAdd_triggered() {
Channel *c = getContextMenuChannel();
if (aclEdit) {
@@ -1892,6 +1918,7 @@ void MainWindow::updateMenuPermissions() {
qaChannelUnlinkAll->setEnabled(p & (ChanACL::Write | ChanACL::LinkChannel));

qaChannelSendMessage->setEnabled(p & (ChanACL::Write | ChanACL::TextMessage));
qaChannelHide->setEnabled(true);
qteChat->setEnabled(p & (ChanACL::Write | ChanACL::TextMessage));
}

@@ -1923,6 +1950,13 @@ void MainWindow::on_qaAudioReset_triggered() {
ai->bResetProcessor = true;
}

void MainWindow::on_qaFilterToggle_triggered() {
g.s.bFilterActive = qaFilterToggle->isChecked();

UserModel *um = static_cast<UserModel *>(qtvUsers->model());
um->toggleHidden(NULL); // force a UI refresh

This comment has been minimized.

Copy link
@hacst

hacst Jul 14, 2013

Member

Not sure I like this re-use for refresh. A extra function/slot on the model for this would be cleaner. Also instead of getting the model from the view we can use the pmModel member.

}

void MainWindow::on_qaAudioMute_triggered() {
if (g.bInAudioWizard) {
qaAudioMute->setChecked(!qaAudioMute->isChecked());
@@ -2030,6 +2064,9 @@ void MainWindow::on_qaConfigDialog_triggered() {
if (dlg->exec() == QDialog::Accepted) {
setupView(false);
updateTrayIcon();

UserModel *um = static_cast<UserModel *>(qtvUsers->model());
um->toggleHidden(NULL); // force a UI refresh
}

delete dlg;
@@ -2608,6 +2645,8 @@ void MainWindow::trayAboutToShow() {
if (top) {
qmTray->addAction(qaQuit);
qmTray->addSeparator();
qmTray->addAction(qaFilterToggle);

This comment has been minimized.

Copy link
@hacst

hacst Jul 14, 2013

Member

Not quite seeing the point of this being in the system tray.

This comment has been minimized.

Copy link
@Kissaki

Kissaki Jul 14, 2013

Member

I think it’d be better off not in there as well.

qmTray->addSeparator();
qmTray->addAction(qaAudioDeaf);
qmTray->addAction(qaAudioMute);
qmTray->addSeparator();
@@ -2618,6 +2657,8 @@ void MainWindow::trayAboutToShow() {
qmTray->addAction(qaAudioMute);
qmTray->addAction(qaAudioDeaf);
qmTray->addSeparator();
qmTray->addAction(qaFilterToggle);
qmTray->addSeparator();
qmTray->addAction(qaQuit);
}
}
@@ -98,7 +98,7 @@ class MainWindow : public QMainWindow, public MessageHandler, public Ui::MainWin

GlobalShortcut *gsPushTalk, *gsResetAudio, *gsMuteSelf, *gsDeafSelf;
GlobalShortcut *gsUnlink, *gsPushMute, *gsMetaChannel, *gsToggleOverlay;
GlobalShortcut *gsMinimal, *gsVolumeUp, *gsVolumeDown, *gsWhisper, *gsMetaLink;
GlobalShortcut *gsMinimal, *gsVolumeUp, *gsVolumeDown, *gsWhisper, *gsMetaLink, *gsHideChannel;
GlobalShortcut *gsCycleTransmitMode;
DockTitleBar *dtbLogDockTitle, *dtbChatDockTitle;

@@ -210,6 +210,7 @@ class MainWindow : public QMainWindow, public MessageHandler, public Ui::MainWin
void on_qaChannelUnlink_triggered();
void on_qaChannelUnlinkAll_triggered();
void on_qaChannelSendMessage_triggered();
void on_qaChannelHide_triggered();
void on_qaChannelCopyURL_triggered();
void on_qaAudioReset_triggered();
void on_qaAudioMute_triggered();
@@ -264,6 +265,8 @@ class MainWindow : public QMainWindow, public MessageHandler, public Ui::MainWin
void pttReleased();
void whisperReleased(QVariant scdata);
void onResetAudio();
void on_qaFilterToggle_triggered();

public:
MainWindow(QWidget *parent);
~MainWindow();
@@ -183,6 +183,8 @@
<addaction name="qaSelfComment"/>
<addaction name="separator"/>
<addaction name="qaConfigDialog"/>
<addaction name="separator"/>
<addaction name="qaFilterToggle"/>
</widget>
<action name="qaQuit">
<property name="text">
@@ -534,6 +536,29 @@
<bool>false</bool>
</property>
</action>
<action name="qaFilterToggle">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="mumble.qrc">
<normaloff>skin:filter.svg</normaloff>
<normalon>skin:filter.svg</normalon>
<activeon>skin:filter.svg</activeon>filter.svg</iconset>
</property>
<property name="text">
<string>&amp;Filter on/off</string>
</property>
<property name="toolTip">
<string>Toggle the channel filter</string>
</property>
<property name="whatsThis">
<string>Enable or disable the filtering of empty channels.</string>
</property>
<property name="iconVisibleInMenu">
<bool>false</bool>
</property>
</action>
<action name="qaAudioWizard">
<property name="text">
<string>&amp;Audio Wizard</string>
@@ -762,6 +787,14 @@
<string>&amp;Join Channel</string>
</property>
</action>
<action name="qaChannelHide">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Hide Channel</string>
</property>
</action>
<action name="qaUserCommentView">
<property name="text">
<string>View Comment</string>
@@ -564,6 +564,9 @@ void MainWindow::msgChannelState(const MumbleProto::ChannelState &msg) {
pmModel->repositionChannel(c, msg.position());
}

if (Database::isLocalHidden(c->qsName))

This comment has been minimized.

Copy link
@hacst

hacst Jul 14, 2013

Member

I guess this is in line with the "identify" by name but I don't think we really want a check here. When comparing channel identity (aka server + channel id): Why should the channel be unhidden/hidden on rename or creation. What we should have is handling in msgChannelRemove to remove the hide entry if the channel it refers to is deleted.

EDIT: I kinda mixed up local channel creation and channels being pushed down from the server. Checking on creation here makes sense ofc.

c->bHidden = true;

if (msg.links_size()) {
QList<Channel *> ql;
pmModel->unlinkAll(c);
@@ -322,6 +322,9 @@ Settings::Settings() {
bUsage = true;
bShowUserCount = false;
bChatBarUseSelection = false;
bFilterHidesEmptyChannels = false;
bFilterActive = false;

wlWindowLayout = LayoutClassic;
bShowContextMenuInMenuBar = false;

@@ -666,6 +669,8 @@ void Settings::load(QSettings* settings_ptr) {
SAVELOAD(bUsage, "ui/usage");
SAVELOAD(bShowUserCount, "ui/showusercount");
SAVELOAD(bChatBarUseSelection, "ui/chatbaruseselection");
SAVELOAD(bFilterHidesEmptyChannels, "ui/filterhidesemptychannels");
SAVELOAD(bFilterActive, "ui/filteractive");
SAVELOAD(qsImagePath, "ui/imagepath");
SAVELOAD(bShowContextMenuInMenuBar, "ui/showcontextmenuinmenubar");
SAVELOAD(qbaConnectDialogGeometry, "ui/connect/geometry");
@@ -950,6 +955,8 @@ void Settings::save() {
SAVELOAD(bUsage, "ui/usage");
SAVELOAD(bShowUserCount, "ui/showusercount");
SAVELOAD(bChatBarUseSelection, "ui/chatbaruseselection");
SAVELOAD(bFilterHidesEmptyChannels, "ui/filterhidesemptychannels");
SAVELOAD(bFilterActive, "ui/filteractive");
SAVELOAD(qsImagePath, "ui/imagepath");
SAVELOAD(bShowContextMenuInMenuBar, "ui/showcontextmenuinmenubar");
SAVELOAD(qbaConnectDialogGeometry, "ui/connect/geometry");
@@ -258,6 +258,8 @@ struct Settings {
bool bUsage;
bool bShowUserCount;
bool bChatBarUseSelection;
bool bFilterHidesEmptyChannels;
bool bFilterActive;
QByteArray qbaConnectDialogHeader, qbaConnectDialogGeometry;
bool bShowContextMenuInMenuBar;

1 comment on commit 304bf43

@hacst

This comment has been minimized.

Copy link
Member

commented on 304bf43 Jul 14, 2013

Will need some work. The way channels are identified is broken. Should instead use server identification like we already use for other features and identify channel by channel id. The channel id isn't 100% unique for a channel at all times but in practice it will do. Channel flags are currently broken by this patch. Coding style will also have to be fixed. Imho the tray menu entry should be dropped.

I guess setting the rows hidden on the view is the easiest way to achieve this filtering effect with our current model/view setup. The clean way would probably be to have a filter model but I think it would break a lot of assumptions if we introduce it now. Not sure I like the current strategy of applying the hiding though. It's very inefficient. We should make sure it doesn't create slowdown with high channel and user-count (unlikely given the resources at hand but still...) and tweak it to be less wasteful where feasible. Updating everything every single time a user starts speaking just rubs me the wrong way ;)

Please sign in to comment.
You can’t perform that action at this time.