1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Merge branch 'friendlist' from PR #1879

Closes #1879 by rebasing and merging it
This commit is contained in:
tux3 2015-06-26 13:47:29 +02:00
commit 47c4fab08b
No known key found for this signature in database
GPG Key ID: 7E086DD661263264
42 changed files with 2774 additions and 465 deletions

152
qtox.pro
View File

@ -38,7 +38,7 @@ FORMS += \
CONFIG += c++11
QMAKE_CXXFLAGS += -fno-exceptions -fno-rtti
QMAKE_CXXFLAGS += -fno-exceptions
# Rules for creating/updating {ts|qm}-files
include(translations/i18n.pri)
@ -108,9 +108,9 @@ contains(HIGH_DPI, YES) {
}
contains(JENKINS,YES) {
INCLUDEPATH += ./libs/include/
INCLUDEPATH += ./libs/include/
} else {
INCLUDEPATH += libs/include
INCLUDEPATH += libs/include
}
contains(DEFINES, QTOX_FILTER_AUDIO) {
@ -133,8 +133,8 @@ contains(DEFINES, QTOX_PLATFORM_EXT) {
# Rules for Windows, Mac OSX, and Linux
win32 {
RC_FILE = windows/qtox.rc
LIBS += -L$$PWD/libs/lib -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lsodium -lvpx -lpthread
LIBS += -L$$PWD/libs/lib -lavformat -lavdevice -lavcodec -lavutil -lswscale -lOpenAL32 -lopus
LIBS += -L$$PWD/libs/lib -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lsodium -lvpx -lpthread
LIBS += -L$$PWD/libs/lib -lavformat -lavdevice -lavcodec -lavutil -lswscale -lOpenAL32 -lopus
LIBS += -lopengl32 -lole32 -loleaut32 -lvfw32 -lws2_32 -liphlpapi -lz -luuid
LIBS += -lqrencode
LIBS += -lstrmiids # For DirectShow
@ -200,31 +200,31 @@ win32 {
# The systray Unity backend implements the system tray icon on Unity (Ubuntu) and GNOME desktops.
unix:!macx:!android {
contains(ENABLE_SYSTRAY_UNITY_BACKEND, YES) {
DEFINES += ENABLE_SYSTRAY_UNITY_BACKEND
DEFINES += ENABLE_SYSTRAY_UNITY_BACKEND
INCLUDEPATH += "/usr/include/glib-2.0"
INCLUDEPATH += "/usr/include/gtk-2.0"
INCLUDEPATH += "/usr/include/atk-1.0"
INCLUDEPATH += "/usr/include/cairo"
INCLUDEPATH += "/usr/include/ffmpeg"
INCLUDEPATH += "/usr/include/gdk-pixbuf-2.0"
INCLUDEPATH += "/usr/include/libappindicator-0.1"
INCLUDEPATH += "/usr/include/libdbusmenu-glib-0.4"
INCLUDEPATH += "/usr/include/pango-1.0"
equals(QT_ARCH, x86_64) {
INCLUDEPATH += "/usr/lib64/glib-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib64/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/gtk-2.0/include"
}
else {
INCLUDEPATH += "/usr/lib/glib-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/gtk-2.0/include"
}
INCLUDEPATH += "/usr/include/glib-2.0"
INCLUDEPATH += "/usr/include/gtk-2.0"
INCLUDEPATH += "/usr/include/atk-1.0"
INCLUDEPATH += "/usr/include/cairo"
INCLUDEPATH += "/usr/include/ffmpeg"
INCLUDEPATH += "/usr/include/gdk-pixbuf-2.0"
INCLUDEPATH += "/usr/include/libappindicator-0.1"
INCLUDEPATH += "/usr/include/libdbusmenu-glib-0.4"
INCLUDEPATH += "/usr/include/pango-1.0"
equals(QT_ARCH, x86_64) {
INCLUDEPATH += "/usr/lib64/glib-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib64/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/gtk-2.0/include"
}
else {
INCLUDEPATH += "/usr/lib/glib-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/gtk-2.0/include"
}
LIBS += -lgobject-2.0 -lappindicator -lgtk-x11-2.0
LIBS += -lgobject-2.0 -lappindicator -lgtk-x11-2.0
}
}
@ -232,30 +232,30 @@ contains(ENABLE_SYSTRAY_UNITY_BACKEND, YES) {
unix:!macx:!android {
contains(ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND, NO) {
} else {
DEFINES += ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND
DEFINES += ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND
INCLUDEPATH += "/usr/include/glib-2.0"
INCLUDEPATH += "/usr/include/gtk-2.0"
INCLUDEPATH += "/usr/include/atk-1.0"
INCLUDEPATH += "/usr/include/cairo"
INCLUDEPATH += "/usr/include/ffmpeg"
INCLUDEPATH += "/usr/include/gdk-pixbuf-2.0"
INCLUDEPATH += "/usr/include/pango-1.0"
equals(QT_ARCH, x86_64) {
INCLUDEPATH += "/usr/lib64/glib-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib64/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/gtk-2.0/include"
}
else {
INCLUDEPATH += "/usr/lib/glib-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/gtk-2.0/include"
}
INCLUDEPATH += "/usr/include/glib-2.0"
INCLUDEPATH += "/usr/include/gtk-2.0"
INCLUDEPATH += "/usr/include/atk-1.0"
INCLUDEPATH += "/usr/include/cairo"
INCLUDEPATH += "/usr/include/ffmpeg"
INCLUDEPATH += "/usr/include/gdk-pixbuf-2.0"
INCLUDEPATH += "/usr/include/pango-1.0"
equals(QT_ARCH, x86_64) {
INCLUDEPATH += "/usr/lib64/glib-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib64/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/gtk-2.0/include"
}
else {
INCLUDEPATH += "/usr/lib/glib-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/gtk-2.0/include"
}
LIBS += -lglib-2.0 -lgdk_pixbuf-2.0 -lgio-2.0 -lcairo -lgtk-x11-2.0 -lgdk-x11-2.0 -lgobject-2.0
LIBS += -lglib-2.0 -lgdk_pixbuf-2.0 -lgio-2.0 -lcairo -lgtk-x11-2.0 -lgdk-x11-2.0 -lgobject-2.0
SOURCES += src/platform/statusnotifier/closures.c \
src/platform/statusnotifier/enums.c \
@ -272,29 +272,29 @@ contains(ENABLE_SYSTRAY_STATUSNOTIFIER_BACKEND, NO) {
unix:!macx:!android {
contains(ENABLE_SYSTRAY_GTK_BACKEND, NO) {
} else {
DEFINES += ENABLE_SYSTRAY_GTK_BACKEND
DEFINES += ENABLE_SYSTRAY_GTK_BACKEND
INCLUDEPATH += "/usr/include/glib-2.0"
INCLUDEPATH += "/usr/include/gtk-2.0"
INCLUDEPATH += "/usr/include/atk-1.0"
INCLUDEPATH += "/usr/include/gdk-pixbuf-2.0"
INCLUDEPATH += "/usr/include/cairo"
INCLUDEPATH += "/usr/include/pango-1.0"
equals(QT_ARCH, x86_64) {
INCLUDEPATH += "/usr/lib64/glib-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib64/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/gtk-2.0/include"
}
else {
INCLUDEPATH += "/usr/lib/glib-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/gtk-2.0/include"
}
INCLUDEPATH += "/usr/include/glib-2.0"
INCLUDEPATH += "/usr/include/gtk-2.0"
INCLUDEPATH += "/usr/include/atk-1.0"
INCLUDEPATH += "/usr/include/gdk-pixbuf-2.0"
INCLUDEPATH += "/usr/include/cairo"
INCLUDEPATH += "/usr/include/pango-1.0"
equals(QT_ARCH, x86_64) {
INCLUDEPATH += "/usr/lib64/glib-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib64/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/x86_64-linux-gnu/gtk-2.0/include"
}
else {
INCLUDEPATH += "/usr/lib/glib-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/glib-2.0/include"
INCLUDEPATH += "/usr/lib/gtk-2.0/include"
INCLUDEPATH += "/usr/lib/i386-linux-gnu/gtk-2.0/include"
}
LIBS += -lglib-2.0 -lgdk_pixbuf-2.0 -lgio-2.0 -lcairo -lgtk-x11-2.0 -lgdk-x11-2.0 -lgobject-2.0
LIBS += -lglib-2.0 -lgdk_pixbuf-2.0 -lgio-2.0 -lcairo -lgtk-x11-2.0 -lgdk-x11-2.0 -lgobject-2.0
}
}
@ -488,7 +488,12 @@ SOURCES += \
src/widget/translator.cpp \
src/persistence/settingsserializer.cpp \
src/widget/notificationscrollarea.cpp \
src/widget/notificationedgewidget.cpp
src/widget/notificationedgewidget.cpp \
src/widget/circlewidget.cpp \
src/widget/genericchatitemwidget.cpp \
src/widget/friendlistlayout.cpp \
src/widget/genericchatitemlayout.cpp \
src/widget/categorywidget.cpp
HEADERS += \
src/audio/audio.h \
@ -526,4 +531,9 @@ HEADERS += \
src/widget/translator.h \
src/persistence/settingsserializer.h \
src/widget/notificationscrollarea.h \
src/widget/notificationedgewidget.h
src/widget/notificationedgewidget.h \
src/widget/circlewidget.h \
src/widget/genericchatitemwidget.h \
src/widget/friendlistlayout.h \
src/widget/genericchatitemlayout.h \
src/widget/categorywidget.h

View File

@ -533,6 +533,15 @@ QVector<ChatLine::Ptr> ChatLog::getLines()
return lines;
}
ChatLine::Ptr ChatLog::getLatestLine() const
{
if (!lines.empty())
{
return lines.last();
}
return nullptr;
}
void ChatLog::clear()
{
clearSelection();

View File

@ -61,6 +61,7 @@ public:
ChatLine::Ptr getTypingNotification() const;
QVector<ChatLine::Ptr> getLines();
ChatLine::Ptr getLatestLine() const;
// repetition interval sender name (sec)
const uint repNameAfter = 5*60;

View File

@ -602,6 +602,7 @@ void Core::acceptFriendRequest(const QString& userId)
{
profile.saveToxSave();
emit friendAdded(friendId, userId);
emit friendshipChanged(friendId);
}
}
@ -643,6 +644,7 @@ void Core::requestFriendship(const QString& friendAddress, const QString& messag
HistoryKeeper::getInstance()->addChatEntry(userId, inviteStr, getSelfId().publicKey, QDateTime::currentDateTime(), true);
emit friendAdded(friendId, userId);
emit friendshipChanged(friendId);
}
}
profile.saveToxSave();

View File

@ -161,6 +161,7 @@ signals:
void friendMessageReceived(uint32_t friendId, const QString& message, bool isAction);
void friendAdded(uint32_t friendId, const QString& userId);
void friendshipChanged(uint32_t friendId);
void friendStatusChanged(uint32_t friendId, Status status);
void friendStatusMessageChanged(uint32_t friendId, const QString& message);

View File

@ -25,7 +25,7 @@
#include "src/core/corestructs.h"
#include "core/toxid.h"
struct FriendWidget;
class FriendWidget;
class ChatForm;
class Friend : public QObject

View File

@ -43,7 +43,7 @@ Group::Group(int GroupId, QString Name, bool IsAvGroupchat)
Group::~Group()
{
delete chatForm;
delete widget;
widget->deleteLater();
}
/*
@ -88,7 +88,6 @@ void Group::updatePeer(int peerId, QString name)
void Group::setName(const QString& name)
{
widget->setName(name);
chatForm->setName(name);
if (widget->isActive())

View File

@ -1022,7 +1022,7 @@ QSplitter:handle{
<item>
<widget class="QLineEdit" name="searchContactText">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<sizepolicy hsizetype="Ignored" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
@ -1030,13 +1030,25 @@ QSplitter:handle{
</widget>
</item>
<item>
<widget class="QComboBox" name="searchContactFilterCBox">
<widget class="QToolButton" name="searchContactFilterBox">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>...</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="arrowType">
<enum>Qt::NoArrow</enum>
</property>
</widget>
</item>
</layout>
@ -1072,7 +1084,7 @@ QSplitter:handle{
<rect>
<x>0</x>
<y>0</y>
<width>292</width>
<width>284</width>
<height>353</height>
</rect>
</property>

View File

@ -163,6 +163,7 @@ void Nexus::showMainGUI()
connect(core, &Core::statusMessageSet, widget, &Widget::setStatusMessage);
connect(core, &Core::selfAvatarChanged, widget, &Widget::onSelfAvatarLoaded);
connect(core, &Core::friendAdded, widget, &Widget::addFriend);
connect(core, &Core::friendshipChanged, widget, &Widget::onFriendshipChanged);
connect(core, &Core::failedToAddFriend, widget, &Widget::addFriendFailed);
connect(core, &Core::friendUsernameChanged, widget, &Widget::onFriendUsernameChanged);
connect(core, &Core::friendStatusChanged, widget, &Widget::onFriendStatusChanged);

View File

@ -392,6 +392,26 @@ void HistoryKeeper::markAsSent(int m_id)
db->exec(QString("UPDATE sent_status SET status = 1 WHERE id = %1;").arg(m_id));
}
QDate HistoryKeeper::getLatestDate(const QString &chat)
{
int chat_id = getChatID(chat, ctSingle).first;
QSqlQuery dbAnswer;
dbAnswer = db->exec(QString("SELECT MAX(timestamp) FROM history LEFT JOIN sent_status ON history.id = sent_status.id ") +
QString("INNER JOIN aliases ON history.sender = aliases.id AND chat_id = %3;")
.arg(chat_id));
if (dbAnswer.first())
{
qint64 timeInt = dbAnswer.value(0).toLongLong();
if (timeInt != 0)
return QDateTime::fromMSecsSinceEpoch(timeInt).date();
}
return QDate();
}
void HistoryKeeper::setSyncType(Db::syncType sType)
{
QString syncCmd;

View File

@ -62,6 +62,7 @@ public:
qint64 addGroupChatEntry(const QString& chat, const QString& message, const QString& sender, const QDateTime &dt);
QList<HistMessage> getChatHistory(ChatType ct, const QString &chat, const QDateTime &time_from, const QDateTime &time_to);
void markAsSent(int m_id);
QDate getLatestDate(const QString& chat);
QList<HistMessage> exportMessages();
void importMessages(const QList<HistoryKeeper::HistMessage> &lst);

View File

@ -287,6 +287,7 @@ void Settings::loadPersonnal(Profile* profile)
friendLst.clear();
ps.beginGroup("Friends");
int size = ps.beginReadArray("Friend");
friendLst.reserve(size);
for (int i = 0; i < size; i ++)
{
ps.setArrayIndex(i);
@ -294,6 +295,11 @@ void Settings::loadPersonnal(Profile* profile)
fp.addr = ps.value("addr").toString();
fp.alias = ps.value("alias").toString();
fp.autoAcceptDir = ps.value("autoAcceptDir").toString();
fp.circleID = ps.value("circle", -1).toInt();
if (getEnableLogging())
fp.activity = ps.value("activity", QDate()).toDate();
friendLst[ToxId(fp.addr).publicKey] = fp;
}
ps.endArray();
@ -303,6 +309,20 @@ void Settings::loadPersonnal(Profile* profile)
compactLayout = ps.value("compactLayout", false).toBool();
ps.endGroup();
ps.beginGroup("Circles");
size = ps.beginReadArray("Circle");
circleLst.reserve(size);
for (int i = 0; i < size; i ++)
{
ps.setArrayIndex(i);
circleProp cp;
cp.name = ps.value("name").toString();
cp.expanded = ps.value("expanded", true).toBool();
circleLst.push_back(cp);
}
ps.endArray();
ps.endGroup();
ps.beginGroup("Privacy");
typingNotification = ps.value("typingNotification", false).toBool();
enableLogging = ps.value("enableLogging", false).toBool();
@ -450,6 +470,11 @@ void Settings::savePersonal(QString profileName, QString password)
ps.setValue("addr", frnd.addr);
ps.setValue("alias", frnd.alias);
ps.setValue("autoAcceptDir", frnd.autoAcceptDir);
ps.setValue("circle", frnd.circleID);
if (getEnableLogging())
ps.setValue("activity", frnd.activity);
index++;
}
ps.endArray();
@ -459,6 +484,19 @@ void Settings::savePersonal(QString profileName, QString password)
ps.setValue("compactLayout", compactLayout);
ps.endGroup();
ps.beginGroup("Circles");
ps.beginWriteArray("Circle", circleLst.size());
index = 0;
for (auto& circle : circleLst)
{
ps.setArrayIndex(index);
ps.setValue("name", circle.name);
ps.setValue("expanded", circle.expanded);
index++;
}
ps.endArray();
ps.endGroup();
ps.beginGroup("Privacy");
ps.setValue("typingNotification", typingNotification);
ps.setValue("enableLogging", enableLogging);
@ -1255,6 +1293,66 @@ void Settings::setFriendAlias(const ToxId &id, const QString &alias)
}
}
int Settings::getFriendCircleID(const ToxId &id) const
{
QString key = id.publicKey;
auto it = friendLst.find(key);
if (it != friendLst.end())
return it->circleID;
return -1;
}
void Settings::setFriendCircleID(const ToxId &id, int circleID)
{
QString key = id.publicKey;
auto it = friendLst.find(key);
if (it != friendLst.end())
{
it->circleID = circleID;
}
else
{
friendProp fp;
fp.addr = key;
fp.alias = "";
fp.autoAcceptDir = "";
fp.circleID = circleID;
friendLst[key] = fp;
}
}
QDate Settings::getFriendActivity(const ToxId &id) const
{
QString key = id.publicKey;
auto it = friendLst.find(key);
if (it != friendLst.end())
return it->activity;
return QDate();
}
void Settings::setFriendActivity(const ToxId &id, const QDate &activity)
{
QString key = id.publicKey;
auto it = friendLst.find(key);
if (it != friendLst.end())
{
it->activity = activity;
}
else
{
friendProp fp;
fp.addr = key;
fp.alias = "";
fp.autoAcceptDir = "";
fp.circleID = -1;
fp.activity = activity;
friendLst[key] = fp;
}
savePersonal();
}
void Settings::removeFriendSettings(const ToxId &id)
{
QMutexLocker locker{&bigLock};
@ -1298,6 +1396,57 @@ void Settings::setGroupchatPosition(bool value)
groupchatPosition = value;
}
int Settings::getCircleCount() const
{
return circleLst.size();
}
QString Settings::getCircleName(int id) const
{
return circleLst[id].name;
}
void Settings::setCircleName(int id, const QString &name)
{
circleLst[id].name = name;
savePersonal();
}
int Settings::addCircle(const QString &name)
{
circleProp cp;
cp.expanded = false;
if (name.isEmpty())
cp.name = tr("Circle #%1").arg(circleLst.count() + 1);
else
cp.name = name;
circleLst.append(cp);
savePersonal();
return circleLst.count() - 1;
}
bool Settings::getCircleExpanded(int id) const
{
return circleLst[id].expanded;
}
void Settings::setCircleExpanded(int id, bool expanded)
{
circleLst[id].expanded = expanded;
}
int Settings::removeCircle(int id)
{
// Replace index with last one and remove last one instead.
// This gives you contiguous ids all the time.
circleLst[id] = circleLst.last();
circleLst.pop_back();
savePersonal();
return circleLst.count();
}
int Settings::getThemeColor() const
{
QMutexLocker locker{&bigLock};

View File

@ -25,6 +25,7 @@
#include <QObject>
#include <QPixmap>
#include <QMutex>
#include <QDate>
#include "src/core/corestructs.h"
class ToxId;
@ -228,6 +229,12 @@ public:
QString getFriendAlias(const ToxId &id) const;
void setFriendAlias(const ToxId &id, const QString &alias);
int getFriendCircleID(const ToxId &id) const;
void setFriendCircleID(const ToxId &id, int circleID);
QDate getFriendActivity(const ToxId &id) const;
void setFriendActivity(const ToxId &id, const QDate &date);
void removeFriendSettings(const ToxId &id);
bool getFauxOfflineMessaging() const;
@ -242,6 +249,14 @@ public:
bool getAutoLogin() const;
void setAutoLogin(bool state);
int getCircleCount() const;
int addCircle(const QString &name = QString());
int removeCircle(int id);
QString getCircleName(int id) const;
void setCircleName(int id, const QString &name);
bool getCircleExpanded(int id) const;
void setCircleExpanded(int id, bool expanded);
// Assume all widgets have unique names
// Don't use it to save every single thing you want to save, use it
// for some general purpose widgets, such as MainWindows or Splitters,
@ -357,10 +372,20 @@ private:
QString alias;
QString addr;
QString autoAcceptDir;
int circleID = -1;
QDate activity = QDate();
};
struct circleProp
{
QString name;
bool expanded;
};
QHash<QString, friendProp> friendLst;
QVector<circleProp> circleLst;
int themeColor;
static QMutex bigLock;

View File

@ -0,0 +1,333 @@
/*
Copyright © 2015 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "categorywidget.h"
#include "friendlistlayout.h"
#include "friendlistwidget.h"
#include "friendwidget.h"
#include "src/widget/style.h"
#include "tool/croppinglabel.h"
#include <QBoxLayout>
#include <QMouseEvent>
#include <QApplication>
void emitChatroomWidget(QLayout* layout, int index)
{
GenericChatroomWidget* chatWidget = dynamic_cast<GenericChatroomWidget*>(layout->itemAt(index)->widget());
if (chatWidget != nullptr)
emit chatWidget->chatroomWidgetClicked(chatWidget);
}
CategoryWidget::CategoryWidget(QWidget* parent)
: GenericChatItemWidget(parent)
{
container = new QWidget(this);
container->setObjectName("circleWidgetContainer");
container->setLayoutDirection(Qt::LeftToRight);
statusLabel = new QLabel(this);
statusLabel->setObjectName("status");
statusLabel->setTextFormat(Qt::PlainText);
statusPic.setPixmap(QPixmap(":/ui/chatArea/scrollBarRightArrow.svg"));
fullLayout = new QVBoxLayout(this);
fullLayout->setSpacing(0);
fullLayout->setMargin(0);
fullLayout->addWidget(container);
lineFrame = new QFrame(container);
lineFrame->setObjectName("line");
lineFrame->setFrameShape(QFrame::HLine);
lineFrame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
lineFrame->resize(0, 0);
listLayout = new FriendListLayout();
listWidget = new QWidget(this);
listWidget->setLayout(listLayout);
fullLayout->addWidget(listWidget);
setAcceptDrops(true);
onCompactChanged(isCompact());
setExpanded(true, false);
updateStatus();
}
bool CategoryWidget::isExpanded() const
{
return expanded;
}
void CategoryWidget::setExpanded(bool isExpanded, bool save)
{
expanded = isExpanded;
setMouseTracking(true);
listWidget->setVisible(isExpanded);
if (isExpanded)
statusPic.setPixmap(QPixmap(":/ui/chatArea/scrollBarDownArrow.svg"));
else
statusPic.setPixmap(QPixmap(":/ui/chatArea/scrollBarRightArrow.svg"));
// The listWidget will recieve a enterEvent for some reason if now visible.
// Using the following, we prevent that.
QApplication::processEvents(QEventLoop::ExcludeSocketNotifiers);
container->hide();
container->show();
if (save)
onExpand();
}
void CategoryWidget::leaveEvent(QEvent *event)
{
event->ignore();
}
void CategoryWidget::setName(const QString &name, bool save)
{
nameLabel->setText(name);
if (isCompact())
nameLabel->minimizeMaximumWidth();
if (save)
onSetName();
}
void CategoryWidget::editName()
{
nameLabel->editBegin();
nameLabel->setMaximumWidth(QWIDGETSIZE_MAX);
}
void CategoryWidget::addFriendWidget(FriendWidget* w, Status s)
{
listLayout->addFriendWidget(w, s);
updateStatus();
onAddFriendWidget(w);
w->reloadTheme(); // Otherwise theme will change when moving to another circle.
}
void CategoryWidget::removeFriendWidget(FriendWidget* w, Status s)
{
listLayout->removeFriendWidget(w, s);
updateStatus();
}
void CategoryWidget::updateStatus()
{
statusLabel->setText(QString::number(listLayout->friendOnlineCount()) + QStringLiteral(" / ") + QString::number(listLayout->friendTotalCount()));
}
bool CategoryWidget::hasChatrooms() const
{
return listLayout->hasChatrooms();
}
void CategoryWidget::search(const QString &searchString, bool updateAll, bool hideOnline, bool hideOffline)
{
if (updateAll)
{
listLayout->searchChatrooms(searchString, hideOnline, hideOffline);
}
bool inCategory = searchString.isEmpty() && !(hideOnline && hideOffline);
setVisible(inCategory || listLayout->hasChatrooms());
}
bool CategoryWidget::cycleContacts(bool forward)
{
if (listLayout->friendTotalCount() == 0)
{
return false;
}
if (forward)
{
if (listLayout->getLayoutOnline()->count() != 0)
{
setExpanded(true);
emitChatroomWidget(listLayout->getLayoutOnline(), 0);
return true;
}
else if (listLayout->getLayoutOffline()->count() != 0)
{
setExpanded(true);
emitChatroomWidget(listLayout->getLayoutOffline(), 0);
return true;
}
}
else
{
if (listLayout->getLayoutOffline()->count() != 0)
{
setExpanded(true);
emitChatroomWidget(listLayout->getLayoutOffline(), listLayout->getLayoutOffline()->count() - 1);
return true;
}
else if (listLayout->getLayoutOnline()->count() != 0)
{
setExpanded(true);
emitChatroomWidget(listLayout->getLayoutOnline(), listLayout->getLayoutOnline()->count() - 1);
return true;
}
}
return false;
}
bool CategoryWidget::cycleContacts(FriendWidget* activeChatroomWidget, bool forward)
{
int index = -1;
QLayout* currentLayout = nullptr;
FriendWidget* friendWidget = dynamic_cast<FriendWidget*>(activeChatroomWidget);
if (friendWidget != nullptr)
{
currentLayout = listLayout->getLayoutOnline();
index = listLayout->indexOfFriendWidget(friendWidget, true);
if (index == -1)
{
currentLayout = listLayout->getLayoutOffline();
index = listLayout->indexOfFriendWidget(friendWidget, false);
}
}
else
return false;
index += forward ? 1 : -1;
for (;;)
{
// Bounds checking.
if (index < 0)
{
if (currentLayout == listLayout->getLayoutOffline())
currentLayout = listLayout->getLayoutOnline();
else
return false;
index = currentLayout->count() - 1;
continue;
}
else if (index >= currentLayout->count())
{
if (currentLayout == listLayout->getLayoutOnline())
currentLayout = listLayout->getLayoutOffline();
else
return false;
index = 0;
continue;
}
GenericChatroomWidget* chatWidget = dynamic_cast<GenericChatroomWidget*>(currentLayout->itemAt(index)->widget());
if (chatWidget != nullptr)
emit chatWidget->chatroomWidgetClicked(chatWidget);
return true;
}
return false;
}
void CategoryWidget::onCompactChanged(bool _compact)
{
delete topLayout;
delete mainLayout;
topLayout = new QHBoxLayout;
topLayout->setSpacing(0);
topLayout->setMargin(0);
(void)_compact;
setCompact(true);
if (true)
{
nameLabel->minimizeMaximumWidth();
mainLayout = nullptr;
container->setFixedHeight(25);
container->setLayout(topLayout);
topLayout->addSpacing(18);
topLayout->addWidget(&statusPic);
topLayout->addSpacing(5);
topLayout->addWidget(nameLabel, 100);
topLayout->addWidget(lineFrame, 1);
topLayout->addSpacing(5);
topLayout->addWidget(statusLabel);
topLayout->addSpacing(5);
topLayout->activate();
}
/*else
{
nameLabel->setMaximumWidth(QWIDGETSIZE_MAX);
mainLayout = new QVBoxLayout();
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(20, 0, 20, 0);
container->setFixedHeight(25);
container->setLayout(mainLayout);
topLayout->addWidget(&statusPic);
topLayout->addSpacing(10);
topLayout->addWidget(nameLabel, 1);
topLayout->addSpacing(5);
topLayout->addWidget(statusLabel);
topLayout->activate();
mainLayout->addStretch();
mainLayout->addLayout(topLayout);
mainLayout->addWidget(lineFrame);
mainLayout->addStretch();
mainLayout->activate();
}*/
Style::repolish(this);
}
void CategoryWidget::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
setExpanded(!expanded);
}
void CategoryWidget::setContainerAttribute(Qt::WidgetAttribute attribute, bool enabled)
{
container->setAttribute(attribute, enabled);
Style::repolish(container);
}
QLayout* CategoryWidget::friendOfflineLayout() const
{
return listLayout->getLayoutOffline();
}
QLayout* CategoryWidget::friendOnlineLayout() const
{
return listLayout->getLayoutOnline();
}
void CategoryWidget::moveFriendWidgets(FriendListWidget* friendList)
{
listLayout->moveFriendWidgets(friendList);
}

View File

@ -0,0 +1,80 @@
/*
Copyright © 2015 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CATEGORYWIDGET_H
#define CATEGORYWIDGET_H
#include "genericchatitemwidget.h"
#include "src/core/corestructs.h"
class FriendListLayout;
class FriendListWidget;
class FriendWidget;
class QVBoxLayout;
class QHBoxLayout;
class CategoryWidget : public GenericChatItemWidget
{
Q_OBJECT
public:
CategoryWidget(QWidget* parent = 0);
bool isExpanded() const;
void setExpanded(bool isExpanded, bool save = true);
void setName(const QString &name, bool save = true);
void addFriendWidget(FriendWidget* w, Status s);
void removeFriendWidget(FriendWidget* w, Status s);
void updateStatus();
bool hasChatrooms() const;
bool cycleContacts(bool forward);
bool cycleContacts(FriendWidget* activeChatroomWidget, bool forward);
void search(const QString &searchString, bool updateAll = false, bool hideOnline = false, bool hideOffline = false);
public slots:
void onCompactChanged(bool compact);
protected:
virtual void leaveEvent(QEvent* event) final override;
virtual void mouseReleaseEvent(QMouseEvent* event) final override;
void editName();
void setContainerAttribute(Qt::WidgetAttribute attribute, bool enabled);
QLayout* friendOnlineLayout() const;
QLayout* friendOfflineLayout() const;
void moveFriendWidgets(FriendListWidget* friendList);
private:
virtual void onSetName() {}
virtual void onExpand() {}
virtual void onAddFriendWidget(FriendWidget*) {}
QWidget* listWidget;
FriendListLayout* listLayout;
QVBoxLayout* fullLayout;
QVBoxLayout* mainLayout = nullptr;
QHBoxLayout* topLayout = nullptr;
QLabel* statusLabel;
QWidget* container;
QFrame* lineFrame;
bool expanded = false;
};
#endif // CATEGORYWIDGET_H

196
src/widget/circlewidget.cpp Normal file
View File

@ -0,0 +1,196 @@
/*
Copyright © 2015 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "circlewidget.h"
#include "friendwidget.h"
#include "friendlistwidget.h"
#include "tool/croppinglabel.h"
#include "src/persistence/settings.h"
#include "src/friendlist.h"
#include "src/friend.h"
#include "widget.h"
#include <QVariant>
#include <QBoxLayout>
#include <QMouseEvent>
#include <QDragEnterEvent>
#include <QMimeData>
#include <QMenu>
#include <cassert>
QHash<int, CircleWidget*> CircleWidget::circleList;
CircleWidget::CircleWidget(FriendListWidget* parent, int id)
: CategoryWidget(parent)
, id(id)
{
setName(Settings::getInstance().getCircleName(id), false);
circleList[id] = this;
connect(nameLabel, &CroppingLabel::editFinished, [this](const QString &newName)
{
if (!newName.isEmpty())
emit renameRequested(this, newName);
});
connect(nameLabel, &CroppingLabel::editRemoved, [this]()
{
if (isCompact())
nameLabel->minimizeMaximumWidth();
});
setExpanded(Settings::getInstance().getCircleExpanded(id), false);
updateStatus();
}
CircleWidget::~CircleWidget()
{
if (circleList[id] == this)
circleList.remove(id);
}
void CircleWidget::editName()
{
CategoryWidget::editName();
}
CircleWidget* CircleWidget::getFromID(int id)
{
auto circleIt = circleList.find(id);
if (circleIt != circleList.end())
return circleIt.value();
return nullptr;
}
void CircleWidget::contextMenuEvent(QContextMenuEvent* event)
{
QMenu menu;
QAction* renameAction = menu.addAction(tr("Rename circle", "Menu for renaming a circle"));
QAction* removeAction = menu.addAction(tr("Remove circle", "Menu for removing a circle"));
QAction* selectedItem = menu.exec(mapToGlobal(event->pos()));
if (selectedItem == renameAction)
{
editName();
}
else if (selectedItem == removeAction)
{
FriendListWidget* friendList = static_cast<FriendListWidget*>(parentWidget());
moveFriendWidgets(friendList);
friendList->removeCircleWidget(this);
int replacedCircle = Settings::getInstance().removeCircle(id);
auto circleReplace = circleList.find(replacedCircle);
if (circleReplace != circleList.end())
circleReplace.value()->updateID(id);
else
assert(true); // This should never happen.
circleList.remove(replacedCircle);
}
setContainerAttribute(Qt::WA_UnderMouse, false);
}
void CircleWidget::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasFormat("friend"))
event->acceptProposedAction();
setContainerAttribute(Qt::WA_UnderMouse, true); // Simulate hover.
}
void CircleWidget::dragLeaveEvent(QDragLeaveEvent* )
{
setContainerAttribute(Qt::WA_UnderMouse, false);
}
void CircleWidget::dropEvent(QDropEvent* event)
{
if (event->mimeData()->hasFormat("friend"))
{
setExpanded(true, false);
int friendId = event->mimeData()->data("friend").toInt();
Friend* f = FriendList::findFriend(friendId);
assert(f != nullptr);
FriendWidget* widget = f->getFriendWidget();
assert(widget != nullptr);
// Update old circle after moved.
CircleWidget* circleWidget = getFromID(Settings::getInstance().getFriendCircleID(f->getToxId()));
addFriendWidget(widget, f->getStatus());
Settings::getInstance().savePersonal();
if (circleWidget != nullptr)
{
circleWidget->updateStatus();
Widget::getInstance()->searchCircle(circleWidget);
}
setContainerAttribute(Qt::WA_UnderMouse, false);
}
}
void CircleWidget::onSetName()
{
Settings::getInstance().setCircleName(id, getName());
}
void CircleWidget::onExpand()
{
Settings::getInstance().setCircleExpanded(id, isExpanded());
Settings::getInstance().savePersonal();
}
void CircleWidget::onAddFriendWidget(FriendWidget* w)
{
Settings::getInstance().setFriendCircleID(FriendList::findFriend(w->friendId)->getToxId(), id);
}
void CircleWidget::updateID(int index)
{
// For when a circle gets destroyed, another takes its id.
// This function updates all friends widgets for this new id.
if (id == index)
return;
id = index;
circleList[id] = this;
for (int i = 0; i < friendOnlineLayout()->count(); ++i)
{
FriendWidget* friendWidget = dynamic_cast<FriendWidget*>(friendOnlineLayout()->itemAt(i));
if (friendWidget != nullptr)
Settings::getInstance().setFriendCircleID(FriendList::findFriend(friendWidget->friendId)->getToxId(), id);
}
for (int i = 0; i < friendOfflineLayout()->count(); ++i)
{
FriendWidget* friendWidget = dynamic_cast<FriendWidget*>(friendOfflineLayout()->itemAt(i));
if (friendWidget != nullptr)
Settings::getInstance().setFriendCircleID(FriendList::findFriend(friendWidget->friendId)->getToxId(), id);
}
}

54
src/widget/circlewidget.h Normal file
View File

@ -0,0 +1,54 @@
/*
Copyright © 2015 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CIRCLEWIDGET_H
#define CIRCLEWIDGET_H
#include "categorywidget.h"
class CircleWidget final : public CategoryWidget
{
Q_OBJECT
public:
CircleWidget(FriendListWidget* parent, int id);
~CircleWidget();
void editName();
static CircleWidget* getFromID(int id);
signals:
void renameRequested(CircleWidget* circleWidget, const QString &newName);
protected:
virtual void contextMenuEvent(QContextMenuEvent* event) final override;
virtual void dragEnterEvent(QDragEnterEvent* event) final override;
virtual void dragLeaveEvent(QDragLeaveEvent* event) final override;
virtual void dropEvent(QDropEvent* event) final override;
private:
virtual void onSetName() final override;
virtual void onExpand() final override;
virtual void onAddFriendWidget(FriendWidget* w) final override;
void updateID(int index);
static QHash<int, CircleWidget*> circleList;
int id;
};
#endif // CIRCLEWIDGET_H

View File

@ -18,6 +18,7 @@
*/
#include <QDebug>
#include <QBoxLayout>
#include <QScrollBar>
#include <QFileDialog>
#include <QMessageBox>
@ -103,8 +104,10 @@ ChatForm::ChatForm(Friend* chatFriend)
Core::getInstance()->sendTyping(f->getFriendID(), false);
isTyping = false;
} );
connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig) {
if (text != orig) emit aliasChanged(text);
connect(nameLabel, &CroppingLabel::editFinished, this, [=](const QString& newName)
{
nameLabel->setText(newName);
emit aliasChanged(newName);
} );
setAcceptDrops(true);
@ -199,6 +202,8 @@ void ChatForm::startFileSend(ToxFile file)
}
insertChatMessage(ChatMessage::createFileTransferMessage(name, file, true, QDateTime::currentDateTime()));
Widget::getInstance()->updateFriendActivity(f);
}
void ChatForm::onFileRecvRequest(ToxFile file)
@ -233,6 +238,8 @@ void ChatForm::onFileRecvRequest(ToxFile file)
FileTransferWidget* tfWidget = static_cast<FileTransferWidget*>(proxy->getWidget());
tfWidget->autoAcceptTransfer(Settings::getInstance().getAutoAcceptDir(f->getToxId()));
}
Widget::getInstance()->updateFriendActivity(f);
}
void ChatForm::onAvInvite(uint32_t FriendId, int CallId, bool video)
@ -407,6 +414,8 @@ void ChatForm::onAvRinging(uint32_t FriendId, int CallId, bool video)
}
addSystemInfoMessage(tr("Calling to %1").arg(f->getDisplayedName()), ChatMessage::INFO, QDateTime::currentDateTime());
Widget::getInstance()->updateFriendActivity(f);
}
void ChatForm::onAvStarting(uint32_t FriendId, int CallId, bool video)
@ -1045,6 +1054,8 @@ void ChatForm::SendMessageStr(QString msg)
getOfflineMsgEngine()->registerReceipt(rec, id, ma);
msgEdit->setLastMessage(msg); //set last message only when sending it
Widget::getInstance()->updateFriendActivity(f);
}
}

View File

@ -232,6 +232,23 @@ ChatLog *GenericChatForm::getChatLog() const
return chatWidget;
}
QDate GenericChatForm::getLatestDate() const
{
ChatLine::Ptr chatLine = chatWidget->getLatestLine();
if (chatLine)
{
Timestamp* timestamp = dynamic_cast<Timestamp*>(chatLine->getContent(2));
if (timestamp)
return timestamp->getTime().date();
else
return QDate::currentDate();
}
return QDate();
}
void GenericChatForm::setName(const QString &newName)
{
nameLabel->setText(newName);
@ -360,7 +377,11 @@ void GenericChatForm::onSaveLogClicked()
auto lines = chatWidget->getLines();
for (ChatLine::Ptr l : lines)
{
Timestamp* rightCol = static_cast<Timestamp*>(l->getContent(2));
Timestamp* rightCol = dynamic_cast<Timestamp*>(l->getContent(2));
if (!rightCol)
return;
ChatLineContent* middleCol = l->getContent(1);
ChatLineContent* leftCol = l->getContent(0);

View File

@ -64,6 +64,7 @@ public:
bool isEmpty();
ChatLog* getChatLog() const;
QDate getLatestDate() const;
signals:
void sendMessage(uint32_t, QString);

View File

@ -96,8 +96,14 @@ GroupChatForm::GroupChatForm(Group* chatGroup)
connect(callButton, &QPushButton::clicked, this, &GroupChatForm::onCallClicked);
connect(micButton, SIGNAL(clicked()), this, SLOT(onMicMuteToggle()));
connect(volButton, SIGNAL(clicked()), this, SLOT(onVolMuteToggle()));
connect(nameLabel, &CroppingLabel::textChanged, this, [=](QString text, QString orig)
{if (text != orig) emit groupTitleChanged(group->getGroupId(), text.left(128));} );
connect(nameLabel, &CroppingLabel::editFinished, this, [=](const QString& newName)
{
if (!newName.isEmpty())
{
nameLabel->setText(newName);
emit groupTitleChanged(group->getGroupId(), newName.left(128));
}
});
setAcceptDrops(true);
Translator::registerHandler(std::bind(&GroupChatForm::retranslateUi, this), this);

View File

@ -43,6 +43,7 @@
#include <QFileDialog>
#include <QBuffer>
#include <QMessageBox>
#include <QComboBox>
ProfileForm::ProfileForm(QWidget *parent) :
QWidget{parent}, qr{nullptr}

View File

@ -0,0 +1,124 @@
/*
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#include "friendlistlayout.h"
#include "src/friend.h"
#include "src/friendlist.h"
#include "friendwidget.h"
#include "friendlistwidget.h"
#include <cassert>
FriendListLayout::FriendListLayout()
{
setSpacing(0);
setMargin(0);
friendOnlineLayout.getLayout()->setSpacing(0);
friendOnlineLayout.getLayout()->setMargin(0);
friendOfflineLayout.getLayout()->setSpacing(0);
friendOfflineLayout.getLayout()->setMargin(0);
addLayout(friendOnlineLayout.getLayout());
addLayout(friendOfflineLayout.getLayout());
}
FriendListLayout::FriendListLayout(QWidget* parent)
: QVBoxLayout(parent)
{
FriendListLayout();
}
void FriendListLayout::addFriendWidget(FriendWidget* w, Status s)
{
friendOfflineLayout.removeSortedWidget(w);
friendOnlineLayout.removeSortedWidget(w);
if (s == Status::Offline)
{
friendOfflineLayout.addSortedWidget(w);
return;
}
friendOnlineLayout.addSortedWidget(w);
}
void FriendListLayout::removeFriendWidget(FriendWidget *widget, Status s)
{
if (s == Status::Offline)
friendOfflineLayout.removeSortedWidget(widget);
else
friendOnlineLayout.removeSortedWidget(widget);
}
int FriendListLayout::indexOfFriendWidget(FriendWidget* widget, bool online) const
{
if (online)
return friendOnlineLayout.indexOfSortedWidget(widget);
return friendOfflineLayout.indexOfSortedWidget(widget);
}
void FriendListLayout::moveFriendWidgets(FriendListWidget* listWidget)
{
while (friendOnlineLayout.getLayout()->count() != 0)
{
QWidget* getWidget = friendOnlineLayout.getLayout()->takeAt(0)->widget();
FriendWidget* friendWidget = dynamic_cast<FriendWidget*>(getWidget);
listWidget->moveWidget(friendWidget, FriendList::findFriend(friendWidget->friendId)->getStatus(), true);
}
while (friendOfflineLayout.getLayout()->count() != 0)
{
QWidget* getWidget = friendOfflineLayout.getLayout()->takeAt(0)->widget();
FriendWidget* friendWidget = dynamic_cast<FriendWidget*>(getWidget);
listWidget->moveWidget(friendWidget, FriendList::findFriend(friendWidget->friendId)->getStatus(), true);
}
}
int FriendListLayout::friendOnlineCount() const
{
return friendOnlineLayout.getLayout()->count();
}
int FriendListLayout::friendTotalCount() const
{
return friendOfflineLayout.getLayout()->count() + friendOnlineCount();
}
bool FriendListLayout::hasChatrooms() const
{
return !(friendOfflineLayout.getLayout()->isEmpty() && friendOnlineLayout.getLayout()->isEmpty());
}
void FriendListLayout::searchChatrooms(const QString& searchString, bool hideOnline, bool hideOffline)
{
friendOnlineLayout.search(searchString, hideOnline);
friendOfflineLayout.search(searchString, hideOffline);
}
QLayout* FriendListLayout::getLayoutOnline() const
{
return friendOnlineLayout.getLayout();
}
QLayout* FriendListLayout::getLayoutOffline() const
{
return friendOfflineLayout.getLayout();
}
QLayout* FriendListLayout::getFriendLayout(Status s) const
{
return s == Status::Offline ? friendOfflineLayout.getLayout() : friendOnlineLayout.getLayout();
}

View File

@ -0,0 +1,52 @@
/*
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#ifndef FRIENDLISTLAYOUT_H
#define FRIENDLISTLAYOUT_H
#include <QBoxLayout>
#include "src/core/corestructs.h"
#include "genericchatitemlayout.h"
class FriendWidget;
class FriendListWidget;
class FriendListLayout : public QVBoxLayout
{
Q_OBJECT
public:
explicit FriendListLayout();
explicit FriendListLayout(QWidget* parent);
void addFriendWidget(FriendWidget* widget, Status s);
void removeFriendWidget(FriendWidget* widget, Status s);
int indexOfFriendWidget(FriendWidget* widget, bool online) const;
void moveFriendWidgets(FriendListWidget* listWidget);
int friendOnlineCount() const;
int friendTotalCount() const;
bool hasChatrooms() const;
void searchChatrooms(const QString& searchString, bool hideOnline = false, bool hideOffline = false);
QLayout* getLayoutOnline() const;
QLayout* getLayoutOffline() const;
private:
QLayout* getFriendLayout(Status s) const;
GenericChatItemLayout friendOnlineLayout;
GenericChatItemLayout friendOfflineLayout;
};
#endif // FRIENDLISTLAYOUT_H

View File

@ -16,122 +16,635 @@
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "friendlistwidget.h"
#include <QDebug>
#include <QGridLayout>
#include "friendlistlayout.h"
#include "src/friend.h"
#include "src/friendlist.h"
#include "src/widget/friendwidget.h"
#include "src/persistence/settings.h"
#include "friendwidget.h"
#include "groupwidget.h"
#include "circlewidget.h"
#include "widget.h"
#include "src/persistence/historykeeper.h"
#include <QGridLayout>
#include <QMimeData>
#include <QDragEnterEvent>
#include <QDragLeaveEvent>
#include <cassert>
FriendListWidget::FriendListWidget(QWidget *parent, bool groupchatPosition) :
QWidget(parent)
enum Time : int
{
mainLayout = new QGridLayout();
setLayout(mainLayout);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
layout()->setSpacing(0);
layout()->setMargin(0);
Today = 0,
Yesterday = 1,
ThisWeek = 2,
ThisMonth = 3,
Month1Ago = 4,
Month2Ago = 5,
Month3Ago = 6,
Month4Ago = 7,
Month5Ago = 8,
LongAgo = 9,
Never = 10
};
groupLayout = new QVBoxLayout();
groupLayout->setSpacing(0);
groupLayout->setMargin(0);
bool last7DaysWasLastMonth()
{
return QDate::currentDate().addDays(-7).month() == QDate::currentDate().month();
}
for (Status s : {Status::Online, Status::Offline})
Time getTime(const QDate date)
{
if (date == QDate())
return Never;
QDate today = QDate::currentDate();
if (date == today)
return Today;
today = today.addDays(-1);
if (date == today)
return Yesterday;
today = today.addDays(-6);
if (date >= today)
return ThisWeek;
today = today.addDays(-today.day() + 1); // Go to the beginning of the month.
if (last7DaysWasLastMonth())
{
QVBoxLayout *l = new QVBoxLayout();
l->setSpacing(0);
l->setMargin(0);
if (date >= today)
return ThisMonth;
layouts[static_cast<int>(s)] = l;
today = today.addMonths(-1);
}
if (groupchatPosition)
if (date >= today)
return Month1Ago;
today = today.addMonths(-1);
if (date >= today)
return Month2Ago;
today = today.addMonths(-1);
if (date >= today)
return Month3Ago;
today = today.addMonths(-1);
if (date >= today)
return Month4Ago;
today = today.addMonths(-1);
if (date >= today)
return Month5Ago;
return LongAgo;
}
QDate getDateFriend(Friend* contact)
{
return Settings::getInstance().getFriendActivity(contact->getToxId());
}
FriendListWidget::FriendListWidget(Widget* parent, bool groupsOnTop)
: QWidget(parent)
// Prevent Valgrind from complaining. We're changing this to Name here.
// Must be Activity for change to take effect.
, mode(Activity)
, groupsOnTop(groupsOnTop)
{
listLayout = new FriendListLayout();
setLayout(listLayout);
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
groupLayout.getLayout()->setSpacing(0);
groupLayout.getLayout()->setMargin(0);
// Prevent QLayout's add child warning before setting the mode.
listLayout->removeItem(listLayout->getLayoutOnline());
listLayout->removeItem(listLayout->getLayoutOffline());
setMode(Name);
onGroupchatPositionChanged(groupsOnTop);
setAcceptDrops(true);
}
FriendListWidget::~FriendListWidget()
{
if (activityLayout != nullptr)
{
mainLayout->addLayout(groupLayout, 0, 0);
mainLayout->addLayout(layouts[static_cast<int>(Status::Online)], 1, 0);
mainLayout->addLayout(layouts[static_cast<int>(Status::Offline)], 2, 0);
QLayoutItem* item;
while ((item = activityLayout->takeAt(0)) != nullptr)
{
delete item->widget();
delete item;
}
delete activityLayout;
}
if (circleLayout != nullptr)
{
QLayoutItem* item;
while ((item = circleLayout->getLayout()->takeAt(0)) != nullptr)
{
delete item->widget();
delete item;
}
delete circleLayout;
}
}
void FriendListWidget::setMode(Mode mode)
{
if (this->mode == mode)
return;
this->mode = mode;
if (mode == Name)
{
circleLayout = new GenericChatItemLayout;
circleLayout->getLayout()->setSpacing(0);
circleLayout->getLayout()->setMargin(0);
for (int i = 0; i < Settings::getInstance().getCircleCount(); ++i)
{
addCircleWidget(i);
CircleWidget::getFromID(i)->setVisible(false);
}
// Only display circles once all created to avoid artifacts.
for (int i = 0; i < Settings::getInstance().getCircleCount(); ++i)
CircleWidget::getFromID(i)->setVisible(true);
QList<Friend*> friendList = FriendList::getAllFriends();
for (Friend* contact : friendList)
{
int circleId = Settings::getInstance().getFriendCircleID(contact->getToxId());
addFriendWidget(contact->getFriendWidget(), contact->getStatus(), circleId);
}
listLayout->addLayout(listLayout->getLayoutOnline());
listLayout->addLayout(listLayout->getLayoutOffline());
listLayout->addLayout(circleLayout->getLayout());
onGroupchatPositionChanged(groupsOnTop);
if (activityLayout != nullptr)
{
QLayoutItem* item;
while ((item = activityLayout->takeAt(0)) != nullptr)
{
delete item->widget();
delete item;
}
delete activityLayout;
activityLayout = nullptr;
}
reDraw();
}
else if (mode == Activity)
{
activityLayout = new QVBoxLayout();
CategoryWidget* categoryToday = new CategoryWidget(this);
categoryToday->setObjectName("Todddd");
categoryToday->setName(tr("Today", "Category for sorting friends by activity"));
activityLayout->addWidget(categoryToday);
CategoryWidget* categoryYesterday = new CategoryWidget(this);
categoryYesterday->setName(tr("Yesterday", "Category for sorting friends by activity"));
activityLayout->addWidget(categoryYesterday);
CategoryWidget* categoryLastWeek = new CategoryWidget(this);
categoryLastWeek->setName(tr("Last 7 days", "Category for sorting friends by activity"));
activityLayout->addWidget(categoryLastWeek);
QDate currentDate = QDate::currentDate();
if (last7DaysWasLastMonth())
{
CategoryWidget* categoryThisMonth = new CategoryWidget(this);
categoryThisMonth ->setName(tr("This month", "Category for sorting friends by activity"));
activityLayout->addWidget(categoryThisMonth);
currentDate = currentDate.addMonths(-1);
}
CategoryWidget* categoryLast1Month = new CategoryWidget(this);
categoryLast1Month ->setName(QDate::longMonthName(currentDate.month()));
activityLayout->addWidget(categoryLast1Month);
currentDate = currentDate.addMonths(-1);
CategoryWidget* categoryLast2Month = new CategoryWidget(this);
categoryLast2Month ->setName(QDate::longMonthName(currentDate.month()));
activityLayout->addWidget(categoryLast2Month);
currentDate = currentDate.addMonths(-1);
CategoryWidget* categoryLast3Month = new CategoryWidget(this);
categoryLast3Month ->setName(QDate::longMonthName(currentDate.month()));
activityLayout->addWidget(categoryLast3Month);
currentDate = currentDate.addMonths(-1);
CategoryWidget* categoryLast4Month = new CategoryWidget(this);
categoryLast4Month ->setName(QDate::longMonthName(currentDate.month()));
activityLayout->addWidget(categoryLast4Month);
currentDate = currentDate.addMonths(-1);
CategoryWidget* categoryLast5Month = new CategoryWidget(this);
categoryLast5Month ->setName(QDate::longMonthName(currentDate.month()));
activityLayout->addWidget(categoryLast5Month);
CategoryWidget* categoryOlder = new CategoryWidget(this);
categoryOlder->setName(tr("Older than 6 Months", "Category for sorting friends by activity"));
activityLayout->addWidget(categoryOlder);
CategoryWidget* categoryNever = new CategoryWidget(this);
categoryNever->setName(tr("Unknown", "Category for sorting friends by activity"));
activityLayout->addWidget(categoryNever);
QList<Friend*> friendList = FriendList::getAllFriends();
for (Friend* contact : friendList)
{
QDate activityDate = getDateFriend(contact);
Time time = getTime(activityDate);
CategoryWidget* categoryWidget = dynamic_cast<CategoryWidget*>(activityLayout->itemAt(time)->widget());
categoryWidget->addFriendWidget(contact->getFriendWidget(), contact->getStatus());
}
for (int i = 0; i < activityLayout->count(); ++i)
{
CategoryWidget* categoryWidget = dynamic_cast<CategoryWidget*>(activityLayout->itemAt(i)->widget());
categoryWidget->setVisible(categoryWidget->hasChatrooms());
}
listLayout->removeItem(listLayout->getLayoutOnline());
listLayout->removeItem(listLayout->getLayoutOffline());
listLayout->removeItem(circleLayout->getLayout());
listLayout->insertLayout(1, activityLayout);
if (circleLayout != nullptr)
{
QLayoutItem* item;
while ((item = circleLayout->getLayout()->takeAt(0)) != nullptr)
{
delete item->widget();
delete item;
}
delete circleLayout;
circleLayout = nullptr;
}
reDraw();
}
}
FriendListWidget::Mode FriendListWidget::getMode() const
{
return mode;
}
void FriendListWidget::addGroupWidget(GroupWidget* widget)
{
groupLayout.addSortedWidget(widget);
connect(widget, &GroupWidget::renameRequested, this, &FriendListWidget::renameGroupWidget);
}
void FriendListWidget::addFriendWidget(FriendWidget* w, Status s, int circleIndex)
{
CircleWidget* circleWidget = CircleWidget::getFromID(circleIndex);
if (circleWidget == nullptr)
moveWidget(w, s, true);
else
circleWidget->addFriendWidget(w, s);
}
void FriendListWidget::removeFriendWidget(FriendWidget* w)
{
Friend* contact = FriendList::findFriend(w->friendId);
if (mode == Activity)
{
QDate activityDate = getDateFriend(contact);
Time time = getTime(activityDate);
CategoryWidget* categoryWidget = dynamic_cast<CategoryWidget*>(activityLayout->itemAt(time)->widget());
categoryWidget->removeFriendWidget(w, contact->getStatus());
categoryWidget->setVisible(categoryWidget->hasChatrooms());
}
else
{
mainLayout->addLayout(layouts[static_cast<int>(Status::Online)], 0, 0);
mainLayout->addLayout(groupLayout, 1, 0);
mainLayout->addLayout(layouts[static_cast<int>(Status::Offline)], 2, 0);
int id = Settings::getInstance().getFriendCircleID(contact->getToxId());
CircleWidget* circleWidget = CircleWidget::getFromID(id);
if (circleWidget != nullptr)
{
circleWidget->removeFriendWidget(w, contact->getStatus());
Widget::getInstance()->searchCircle(circleWidget);
}
}
}
QVBoxLayout* FriendListWidget::getGroupLayout()
void FriendListWidget::addCircleWidget(int id)
{
return groupLayout;
createCircleWidget(id);
}
QVBoxLayout* FriendListWidget::getFriendLayout(Status s)
void FriendListWidget::addCircleWidget(FriendWidget* friendWidget)
{
auto res = layouts.find(static_cast<int>(s));
if (res != layouts.end())
return res.value();
CircleWidget* circleWidget = createCircleWidget();
if (circleWidget != nullptr)
{
if (friendWidget != nullptr)
{
CircleWidget* circleOriginal = CircleWidget::getFromID(Settings::getInstance().getFriendCircleID(FriendList::findFriend(friendWidget->friendId)->getToxId()));
return layouts[static_cast<int>(Status::Online)];
circleWidget->addFriendWidget(friendWidget, FriendList::findFriend(friendWidget->friendId)->getStatus());
circleWidget->setExpanded(true);
if (circleOriginal != nullptr)
Widget::getInstance()->searchCircle(circleOriginal);
}
Widget::getInstance()->searchCircle(circleWidget);
circleWidget->editName();
}
}
void FriendListWidget::removeCircleWidget(CircleWidget* widget)
{
circleLayout->removeSortedWidget(widget);
widget->deleteLater();
}
void FriendListWidget::searchChatrooms(const QString &searchString, bool hideOnline, bool hideOffline, bool hideGroups)
{
groupLayout.search(searchString, hideGroups);
listLayout->searchChatrooms(searchString, hideOnline, hideOffline);
if (circleLayout != nullptr)
{
for (int i = 0; i != circleLayout->getLayout()->count(); ++i)
{
CircleWidget* circleWidget = static_cast<CircleWidget*>(circleLayout->getLayout()->itemAt(i)->widget());
circleWidget->search(searchString, true, hideOnline, hideOffline);
}
}
else if (activityLayout != nullptr)
{
for (int i = 0; i != activityLayout->count(); ++i)
{
CategoryWidget* categoryWidget = static_cast<CategoryWidget*>(activityLayout->itemAt(i)->widget());
categoryWidget->search(searchString, true, hideOnline, hideOffline);
categoryWidget->setVisible(categoryWidget->hasChatrooms());
}
}
}
void FriendListWidget::renameGroupWidget(GroupWidget* groupWidget, const QString &newName)
{
groupLayout.removeSortedWidget(groupWidget);
groupWidget->setName(newName);
groupLayout.addSortedWidget(groupWidget);
}
void FriendListWidget::renameCircleWidget(CircleWidget* circleWidget, const QString &newName)
{
circleLayout->removeSortedWidget(circleWidget);
circleWidget->setName(newName);
circleLayout->addSortedWidget(circleWidget);
}
void FriendListWidget::onGroupchatPositionChanged(bool top)
{
mainLayout->removeItem(groupLayout);
mainLayout->removeItem(getFriendLayout(Status::Online));
groupsOnTop = top;
if (mode != Name)
return;
listLayout->removeItem(groupLayout.getLayout());
if (top)
listLayout->insertLayout(0, groupLayout.getLayout());
else
listLayout->insertLayout(1, groupLayout.getLayout());
reDraw();
}
void FriendListWidget::cycleContacts(GenericChatroomWidget* activeChatroomWidget, bool forward)
{
if (activeChatroomWidget == nullptr)
return;
int index = -1;
FriendWidget* friendWidget = dynamic_cast<FriendWidget*>(activeChatroomWidget);
if (mode == Activity)
{
mainLayout->addLayout(groupLayout, 0, 0);
mainLayout->addLayout(layouts[static_cast<int>(Status::Online)], 1, 0);
if (friendWidget == nullptr)
return;
QDate activityDate = getDateFriend(FriendList::findFriend(friendWidget->friendId));
index = getTime(activityDate);
CategoryWidget* categoryWidget = dynamic_cast<CategoryWidget*>(activityLayout->itemAt(index)->widget());
if (categoryWidget == nullptr || categoryWidget->cycleContacts(friendWidget, forward))
return;
index += forward ? 1 : -1;
for (;;)
{
// Bounds checking.
if (index < Today)
{
index = Never;
continue;
}
else if (index > Never)
{
index = Today;
continue;
}
CategoryWidget* categoryWidget = dynamic_cast<CategoryWidget*>(activityLayout->itemAt(index)->widget());
if (categoryWidget != nullptr)
{
if (!categoryWidget->cycleContacts(forward))
{
// Skip empty or finished categories.
index += forward ? 1 : -1;
continue;
}
}
break;
}
return;
}
QLayout* currentLayout = nullptr;
CircleWidget* circleWidget = nullptr;
if (friendWidget != nullptr)
{
circleWidget = CircleWidget::getFromID(Settings::getInstance().getFriendCircleID(FriendList::findFriend(friendWidget->friendId)->getToxId()));
if (circleWidget != nullptr)
{
if (circleWidget->cycleContacts(friendWidget, forward))
return;
index = circleLayout->indexOfSortedWidget(circleWidget);
currentLayout = circleLayout->getLayout();
}
else
{
currentLayout = listLayout->getLayoutOnline();
index = listLayout->indexOfFriendWidget(friendWidget, true);
if (index == -1)
{
currentLayout = listLayout->getLayoutOffline();
index = listLayout->indexOfFriendWidget(friendWidget, false);
}
}
}
else
{
mainLayout->addLayout(layouts[static_cast<int>(Status::Online)], 0, 0);
mainLayout->addLayout(groupLayout, 1, 0);
}
}
QList<GenericChatroomWidget*> FriendListWidget::getAllFriends()
{
QList<GenericChatroomWidget*> friends;
for (int i = 0; i < mainLayout->count(); ++i)
{
QLayout* subLayout = mainLayout->itemAt(i)->layout();
if(!subLayout)
continue;
for (int j = 0; j < subLayout->count(); ++j)
GroupWidget* groupWidget = dynamic_cast<GroupWidget*>(activeChatroomWidget);
if (groupWidget != nullptr)
{
GenericChatroomWidget* widget =
reinterpret_cast<GenericChatroomWidget*>(subLayout->itemAt(j)->widget());
if(!widget)
continue;
friends.append(widget);
currentLayout = groupLayout.getLayout();
index = groupLayout.indexOfSortedWidget(groupWidget);
}
else
{
return;
};
}
return friends;
}
index += forward ? 1 : -1;
void FriendListWidget::moveWidget(FriendWidget *w, Status s)
{
QVBoxLayout* l = getFriendLayout(s);
l->removeWidget(w);
Friend* g = FriendList::findFriend(w->friendId);
for (int i = 0; i < l->count(); i++)
for (;;)
{
FriendWidget* w1 = static_cast<FriendWidget*>(l->itemAt(i)->widget());
Friend* f = FriendList::findFriend(w1->friendId);
if (f->getDisplayedName().localeAwareCompare(g->getDisplayedName()) > 0)
// Bounds checking.
if (index < 0)
{
currentLayout = nextLayout(currentLayout, forward);
index = currentLayout->count() - 1;
continue;
}
else if (index >= currentLayout->count())
{
currentLayout = nextLayout(currentLayout, forward);
index = 0;
continue;
}
// Go to the actual next index.
if (currentLayout == listLayout->getLayoutOnline() || currentLayout == listLayout->getLayoutOffline() || currentLayout == groupLayout.getLayout())
{
GenericChatroomWidget* chatWidget = dynamic_cast<GenericChatroomWidget*>(currentLayout->itemAt(index)->widget());
if (chatWidget != nullptr)
emit chatWidget->chatroomWidgetClicked(chatWidget);
return;
}
else if (currentLayout == circleLayout->getLayout())
{
circleWidget = dynamic_cast<CircleWidget*>(currentLayout->itemAt(index)->widget());
if (circleWidget != nullptr)
{
if (!circleWidget->cycleContacts(forward))
{
// Skip empty or finished circles.
index += forward ? 1 : -1;
continue;
}
}
return;
}
else
{
l->insertWidget(i,w);
return;
}
}
static_assert(std::is_same<decltype(w), FriendWidget*>(), "The layout must only contain FriendWidget*");
l->addWidget(w);
}
QVector<CircleWidget*> FriendListWidget::getAllCircles()
{
QVector<CircleWidget*> vec;
vec.reserve(circleLayout->getLayout()->count());
for (int i = 0; i < circleLayout->getLayout()->count(); ++i)
{
vec.push_back(dynamic_cast<CircleWidget*>(circleLayout->getLayout()->itemAt(i)->widget()));
}
return vec;
}
void FriendListWidget::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasFormat("friend"))
event->acceptProposedAction();
}
void FriendListWidget::dropEvent(QDropEvent* event)
{
if (event->mimeData()->hasFormat("friend"))
{
int friendId = event->mimeData()->data("friend").toInt();
Friend* f = FriendList::findFriend(friendId);
assert(f != nullptr);
FriendWidget* widget = f->getFriendWidget();
assert(widget != nullptr);
// Update old circle after moved.
CircleWidget* circleWidget = CircleWidget::getFromID(Settings::getInstance().getFriendCircleID(f->getToxId()));
moveWidget(widget, f->getStatus(), true);
if (circleWidget != nullptr)
circleWidget->updateStatus();
}
}
void FriendListWidget::moveWidget(FriendWidget* w, Status s, bool add)
{
if (mode == Name)
{
int circleId = Settings::getInstance().getFriendCircleID(FriendList::findFriend(w->friendId)->getToxId());
CircleWidget* circleWidget = CircleWidget::getFromID(circleId);
if (circleWidget == nullptr || add)
{
if (circleId != -1)
Settings::getInstance().setFriendCircleID(FriendList::findFriend(w->friendId)->getToxId(), -1);
listLayout->addFriendWidget(w, s);
return;
}
circleWidget->addFriendWidget(w, s);
}
else
{
Friend* contact = FriendList::findFriend(w->friendId);
QDate activityDate = getDateFriend(contact);
Time time = getTime(activityDate);
CategoryWidget* categoryWidget = dynamic_cast<CategoryWidget*>(activityLayout->itemAt(time)->widget());
categoryWidget->addFriendWidget(contact->getFriendWidget(), contact->getStatus());
categoryWidget->show();
}
}
// update widget after add/delete/hide/show
@ -141,3 +654,83 @@ void FriendListWidget::reDraw()
show();
resize(QSize()); //lifehack
}
CircleWidget* FriendListWidget::createCircleWidget(int id)
{
if (id == -1)
id = Settings::getInstance().addCircle();
// Stop, after it has been created. Code after this is for displaying.
if (mode == Activity)
return nullptr;
assert(circleLayout != nullptr);
CircleWidget* circleWidget = new CircleWidget(this, id);
circleLayout->addSortedWidget(circleWidget);
connect(this, &FriendListWidget::onCompactChanged, circleWidget, &CircleWidget::onCompactChanged);
connect(circleWidget, &CircleWidget::renameRequested, this, &FriendListWidget::renameCircleWidget);
circleWidget->show(); // Avoid flickering.
return circleWidget;
}
QLayout* FriendListWidget::nextLayout(QLayout* layout, bool forward) const
{
if (layout == groupLayout.getLayout())
{
if (forward)
{
if (groupsOnTop)
return listLayout->getLayoutOnline();
return listLayout->getLayoutOffline();
}
else
{
if (groupsOnTop)
return circleLayout->getLayout();
return listLayout->getLayoutOnline();
}
}
else if (layout == listLayout->getLayoutOnline())
{
if (forward)
{
if (groupsOnTop)
return listLayout->getLayoutOffline();
return groupLayout.getLayout();
}
else
{
if (groupsOnTop)
return groupLayout.getLayout();
return circleLayout->getLayout();
}
}
else if (layout == listLayout->getLayoutOffline())
{
if (forward)
return circleLayout->getLayout();
else if (groupsOnTop)
return listLayout->getLayoutOnline();
return groupLayout.getLayout();
}
else if (layout == circleLayout->getLayout())
{
if (forward)
{
if (groupsOnTop)
return groupLayout.getLayout();
return listLayout->getLayoutOnline();
}
else
return listLayout->getLayoutOffline();
}
return nullptr;
}

View File

@ -21,36 +21,70 @@
#define FRIENDLISTWIDGET_H
#include <QWidget>
#include <QHash>
#include <QList>
#include "src/core/corestructs.h"
#include "src/widget/genericchatroomwidget.h"
#include "genericchatitemlayout.h"
class QVBoxLayout;
class QGridLayout;
class QPixmap;
struct FriendWidget;
class Widget;
class FriendWidget;
class GroupWidget;
class CircleWidget;
class FriendListLayout;
class GenericChatroomWidget;
class FriendListWidget : public QWidget
{
Q_OBJECT
public:
explicit FriendListWidget(QWidget *parent = 0, bool groupchatPosition = true);
QVBoxLayout* getGroupLayout();
QVBoxLayout* getFriendLayout(Status s);
enum Mode : uint8_t
{
Name,
Activity,
};
explicit FriendListWidget(Widget* parent, bool groupsOnTop = true);
~FriendListWidget();
void setMode(Mode mode);
Mode getMode() const;
void addGroupWidget(GroupWidget* widget);
void addFriendWidget(FriendWidget* w, Status s, int circleIndex);
void removeFriendWidget(FriendWidget* w);
void addCircleWidget(int id);
void addCircleWidget(FriendWidget* widget = nullptr);
void removeCircleWidget(CircleWidget* widget);
void searchChatrooms(const QString &searchString, bool hideOnline = false, bool hideOffline = false, bool hideGroups = false);
void cycleContacts(GenericChatroomWidget* activeChatroomWidget, bool forward);
QVector<CircleWidget*> getAllCircles();
QList<GenericChatroomWidget*> getAllFriends();
void reDraw();
signals:
void onCompactChanged(bool compact);
public slots:
void renameGroupWidget(GroupWidget* groupWidget, const QString& newName);
void renameCircleWidget(CircleWidget* circleWidget, const QString& newName);
void onGroupchatPositionChanged(bool top);
void moveWidget(FriendWidget *w, Status s);
void moveWidget(FriendWidget* w, Status s, bool add = false);
protected:
void dragEnterEvent(QDragEnterEvent* event) override;
void dropEvent(QDropEvent* event) override;
private:
QHash<int, QVBoxLayout*> layouts;
QVBoxLayout *groupLayout;
QGridLayout *mainLayout;
CircleWidget* createCircleWidget(int id = -1);
QLayout* nextLayout(QLayout* layout, bool forward) const;
Mode mode;
bool groupsOnTop;
FriendListLayout* listLayout;
GenericChatItemLayout* circleLayout = nullptr;
GenericChatItemLayout groupLayout;
QVBoxLayout* activityLayout = nullptr;
};
#endif // FRIENDLISTWIDGET_H

View File

@ -19,6 +19,8 @@
#include "src/group.h"
#include "src/grouplist.h"
#include "groupwidget.h"
#include "circlewidget.h"
#include "friendlistwidget.h"
#include "src/friendlist.h"
#include "src/friend.h"
#include "src/core/core.h"
@ -37,6 +39,8 @@
#include <QFileDialog>
#include <QDebug>
#include <QInputDialog>
#include <QCollator>
#include <cassert>
FriendWidget::FriendWidget(int FriendId, QString id)
: friendId(FriendId)
@ -48,28 +52,78 @@ FriendWidget::FriendWidget(int FriendId, QString id)
statusPic.setMargin(3);
nameLabel->setText(id);
nameLabel->setTextFormat(Qt::PlainText);
connect(nameLabel, &CroppingLabel::editFinished, this, &FriendWidget::setAlias);
statusMessageLabel->setTextFormat(Qt::PlainText);
}
void FriendWidget::contextMenuEvent(QContextMenuEvent * event)
{
if (!active)
setBackgroundRole(QPalette::Highlight);
installEventFilter(this); // Disable leave event.
QPoint pos = event->globalPos();
ToxId id = FriendList::findFriend(friendId)->getToxId();
QString dir = Settings::getInstance().getAutoAcceptDir(id);
QMenu menu;
QMenu* inviteMenu = menu.addMenu(tr("Invite to group","Menu to invite a friend to a groupchat"));
QAction* copyId = menu.addAction(tr("Copy friend ID","Menu to copy the Tox ID of that friend"));
QMap<QAction*, Group*> groupActions;
for (Group* group : GroupList::getAllGroups())
{
QAction* groupAction = inviteMenu->addAction(group->getGroupWidget()->getName());
groupActions[groupAction] = group;
}
if (groupActions.isEmpty())
inviteMenu->setEnabled(false);
int circleId = Settings::getInstance().getFriendCircleID(FriendList::findFriend(friendId)->getToxId());
CircleWidget *circleWidget = CircleWidget::getFromID(circleId);
QMenu* circleMenu = nullptr;
QAction* newCircleAction = nullptr;
QAction *removeCircleAction = nullptr;
QMap<QAction*, int> circleActions;
FriendListWidget *friendList;
if (circleWidget == nullptr)
friendList = dynamic_cast<FriendListWidget*>(parentWidget());
else
friendList = dynamic_cast<FriendListWidget*>(circleWidget->parentWidget());
circleMenu = menu.addMenu(tr("Move to circle...", "Menu to move a friend into a different circle"));
newCircleAction = circleMenu->addAction(tr("To new circle"));
if (circleId != -1)
removeCircleAction = circleMenu->addAction(tr("Remove from circle '%1'").arg(Settings::getInstance().getCircleName(circleId)));
circleMenu->addSeparator();
QList<QAction*> circleActionList;
for (int i = 0; i < Settings::getInstance().getCircleCount(); ++i)
{
if (i != circleId)
{
circleActionList.push_back(new QAction(tr("Move to circle \"%1\"").arg(Settings::getInstance().getCircleName(i)), circleMenu));
circleActions[circleActionList.back()] = i;
}
}
std::sort(circleActionList.begin(), circleActionList.end(), [](const QAction* lhs, const QAction* rhs) -> bool
{
QCollator collator;
collator.setNumericMode(true);
return collator.compare(lhs->text(), rhs->text()) < 0;
});
circleMenu->addActions(circleActionList);
QAction* copyId = menu.addAction(tr("Copy friend ID","Menu to copy the Tox ID of that friend"));
QAction* setAlias = menu.addAction(tr("Set alias..."));
menu.addSeparator();
@ -77,10 +131,16 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event)
autoAccept->setCheckable(true);
autoAccept->setChecked(!dir.isEmpty());
menu.addSeparator();
QAction* removeFriendAction = menu.addAction(tr("Remove friend", "Menu to remove the friend from our friendlist"));
QAction* selectedItem = menu.exec(pos);
removeEventFilter(this);
if (!active)
setBackgroundRole(QPalette::Window);
if (selectedItem)
{
if (selectedItem == copyId)
@ -89,7 +149,7 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event)
return;
} else if (selectedItem == setAlias)
{
setFriendAlias();
nameLabel->editBegin();
}
else if (selectedItem == removeFriendAction)
{
@ -105,7 +165,7 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event)
autoAccept->setChecked(false);
Settings::getInstance().setAutoAcceptDir(id, "");
}
if (autoAccept->isChecked())
{
dir = QFileDialog::getExistingDirectory(0, tr("Choose an auto accept directory","popup title"), dir);
@ -114,11 +174,54 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event)
Settings::getInstance().setAutoAcceptDir(id, dir);
}
}
else if (selectedItem == newCircleAction)
{
if (circleWidget != nullptr)
circleWidget->updateStatus();
if (friendList != nullptr)
friendList->addCircleWidget(this);
else
Settings::getInstance().setFriendCircleID(id, Settings::getInstance().addCircle());
}
else if (groupActions.contains(selectedItem))
{
Group* group = groupActions[selectedItem];
Core::getInstance()->groupInviteFriend(friendId, group->getGroupId());
}
else if (removeCircleAction != nullptr && selectedItem == removeCircleAction)
{
if (friendList != nullptr)
friendList->moveWidget(this, FriendList::findFriend(friendId)->getStatus(), true);
else
Settings::getInstance().setFriendCircleID(id, -1);
if (circleWidget)
{
circleWidget->updateStatus();
Widget::getInstance()->searchCircle(circleWidget);
}
}
else if (circleActions.contains(selectedItem))
{
CircleWidget* circle = CircleWidget::getFromID(circleActions[selectedItem]);
if (circle != nullptr)
{
circle->addFriendWidget(this, FriendList::findFriend(friendId)->getStatus());
circle->setExpanded(true);
Widget::getInstance()->searchCircle(circle);
Settings::getInstance().savePersonal();
}
else
Settings::getInstance().setFriendCircleID(id, circleActions[selectedItem]);
if (circleWidget != nullptr)
{
circleWidget->updateStatus();
Widget::getInstance()->searchCircle(circleWidget);
}
}
}
}
@ -160,6 +263,15 @@ void FriendWidget::updateStatusLight()
else if (status == Status::Offline && f->getEventFlag() == 1)
statusPic.setPixmap(QPixmap(":img/status/dot_offline_notification.svg"));
if (f->getEventFlag())
{
CircleWidget* circleWidget = CircleWidget::getFromID(Settings::getInstance().getFriendCircleID(FriendList::findFriend(friendId)->getToxId()));
if (circleWidget != nullptr)
circleWidget->setExpanded(true);
Widget::getInstance()->updateFriendActivity(FriendList::findFriend(friendId));
}
if (!f->getEventFlag())
statusPic.setMargin(3);
else
@ -184,6 +296,14 @@ QString FriendWidget::getStatusString()
return QString::null;
}
void FriendWidget::search(const QString &searchString, bool hide)
{
searchName(searchString, hide);
CircleWidget* circleWidget = CircleWidget::getFromID(Settings::getInstance().getFriendCircleID(FriendList::findFriend(friendId)->getToxId()));
if (circleWidget != nullptr)
circleWidget->search(searchString);
}
void FriendWidget::setChatForm(Ui::MainWindow &ui)
{
Friend* f = FriendList::findFriend(friendId);
@ -247,25 +367,9 @@ void FriendWidget::mouseMoveEvent(QMouseEvent *ev)
void FriendWidget::setAlias(const QString& _alias)
{
QString alias = _alias.trimmed();
alias.remove(QRegExp("[\\t\\n\\v\\f\\r\\x0000]")); // we should really treat regular names this way as well (*ahem* zetok)
alias = alias.left(128); // same as TOX_MAX_NAME_LENGTH
QString alias = _alias.left(128); // same as TOX_MAX_NAME_LENGTH
Friend* f = FriendList::findFriend(friendId);
f->setAlias(alias);
Settings::getInstance().setFriendAlias(f->getToxId(), alias);
Settings::getInstance().savePersonal();
hide();
show();
}
void FriendWidget::setFriendAlias()
{
bool ok;
Friend* f = FriendList::findFriend(friendId);
QString alias = QInputDialog::getText(nullptr, tr("User alias"), tr("You can also set this by clicking the chat form name.\nAlias:"), QLineEdit::Normal,
f->getDisplayedName(), &ok);
if (ok)
setAlias(alias);
}

View File

@ -18,14 +18,12 @@
#ifndef FRIENDWIDGET_H
#define FRIENDWIDGET_H
#include <QLabel>
#include "genericchatroomwidget.h"
class QPixmap;
class MaskablePixmapWidget;
struct FriendWidget final : public GenericChatroomWidget
class FriendWidget : public GenericChatroomWidget
{
Q_OBJECT
public:
@ -37,6 +35,7 @@ public:
virtual void setChatForm(Ui::MainWindow &) override;
virtual void resetEventFlags() override;
virtual QString getStatusString() override;
void search(const QString &searchString, bool hide = false);
signals:
void friendWidgetClicked(FriendWidget* widget);
@ -49,8 +48,8 @@ public slots:
void setAlias(const QString& alias);
protected:
virtual void mousePressEvent(QMouseEvent* ev) final override;
virtual void mouseMoveEvent(QMouseEvent* ev) final override;
virtual void mousePressEvent(QMouseEvent* ev) override;
virtual void mouseMoveEvent(QMouseEvent* ev) override;
void setFriendAlias();
public:

View File

@ -0,0 +1,127 @@
/*
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#include "genericchatitemlayout.h"
#include "genericchatitemwidget.h"
#include <QBoxLayout>
#include <QCollator>
#include <cassert>
// As this layout sorts widget, extra care must be taken when inserting widgets.
// Prefer using the build in add and remove functions for modifying widgets.
// Inserting widgets other ways would cause this layout to be unable to sort.
// As such, they are protected using asserts.
GenericChatItemLayout::GenericChatItemLayout()
: layout(new QVBoxLayout())
{
}
GenericChatItemLayout::~GenericChatItemLayout()
{
delete layout;
}
void GenericChatItemLayout::addSortedWidget(GenericChatItemWidget* widget, int stretch, Qt::Alignment alignment)
{
int closest = indexOfClosestSortedWidget(widget);
layout->insertWidget(closest, widget, stretch, alignment);
}
int GenericChatItemLayout::indexOfSortedWidget(GenericChatItemWidget* widget) const
{
if (layout->count() == 0)
return -1;
int index = indexOfClosestSortedWidget(widget);
if (index >= layout->count())
return -1;
GenericChatItemWidget* atMid = dynamic_cast<GenericChatItemWidget*>(layout->itemAt(index)->widget());
assert(atMid != nullptr);
if (atMid == widget)
return index;
return -1;
}
bool GenericChatItemLayout::existsSortedWidget(GenericChatItemWidget* widget) const
{
return indexOfSortedWidget(widget) != -1;
}
void GenericChatItemLayout::removeSortedWidget(GenericChatItemWidget* widget)
{
if (layout->isEmpty())
return;
int index = indexOfClosestSortedWidget(widget);
if (layout->itemAt(index) == nullptr)
return;
GenericChatItemWidget* atMid = dynamic_cast<GenericChatItemWidget*>(layout->itemAt(index)->widget());
assert(atMid != nullptr);
if (atMid == widget)
layout->removeWidget(widget);
}
void GenericChatItemLayout::search(const QString &searchString, bool hideAll)
{
for (int index = 0; index < layout->count(); ++index)
{
GenericChatItemWidget* widgetAt = dynamic_cast<GenericChatItemWidget*>(layout->itemAt(index)->widget());
assert(widgetAt != nullptr);
widgetAt->searchName(searchString, hideAll);
}
}
QLayout* GenericChatItemLayout::getLayout() const
{
return layout;
}
int GenericChatItemLayout::indexOfClosestSortedWidget(GenericChatItemWidget* widget) const
{
// Binary search: Deferred test of equality.
int min = 0, max = layout->count(), mid;
while (min < max)
{
mid = (max - min) / 2 + min;
GenericChatItemWidget* atMid = dynamic_cast<GenericChatItemWidget*>(layout->itemAt(mid)->widget());
assert(atMid != nullptr);
bool lessThan = false;
QCollator collator;
collator.setNumericMode(true);
int compareValue = collator.compare(atMid->getName(), widget->getName());
if (compareValue < 0)
lessThan = true;
else if (compareValue == 0)
lessThan = atMid < widget; // Consistent ordering.
if (lessThan)
min = mid + 1;
else
max = mid;
}
return min;
}

View File

@ -0,0 +1,43 @@
/*
This file is part of qTox, a Qt-based graphical interface for Tox.
This program is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the COPYING file for more details.
*/
#ifndef GENERICCHATITEMLAYOUT_H
#define GENERICCHATITEMLAYOUT_H
#include <Qt>
class QLayout;
class QVBoxLayout;
class GenericChatItemWidget;
class GenericChatItemLayout
{
public:
GenericChatItemLayout();
~GenericChatItemLayout();
void addSortedWidget(GenericChatItemWidget* widget, int stretch = 0, Qt::Alignment alignment = 0);
int indexOfSortedWidget(GenericChatItemWidget* widget) const;
bool existsSortedWidget(GenericChatItemWidget* widget) const;
void removeSortedWidget(GenericChatItemWidget* widget);
void search(const QString &searchString, bool hideAll = false);
QLayout* getLayout() const;
private:
int indexOfClosestSortedWidget(GenericChatItemWidget* widget) const;
QVBoxLayout* layout;
};
#endif // GENERICCHATITEMLAYOUT_H

View File

@ -0,0 +1,54 @@
/*
Copyright © 2015 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#include "genericchatitemwidget.h"
#include "src/widget/style.h"
#include "src/persistence/settings.h"
#include "src/widget/tool/croppinglabel.h"
#include <QVariant>
GenericChatItemWidget::GenericChatItemWidget(QWidget *parent)
: QFrame(parent)
{
setProperty("compact", Settings::getInstance().getCompactLayout());
nameLabel = new CroppingLabel(this);
nameLabel->setObjectName("name");
nameLabel->setTextFormat(Qt::PlainText);
}
bool GenericChatItemWidget::isCompact() const
{
return compact;
}
void GenericChatItemWidget::setCompact(bool compact)
{
this->compact = compact;
}
QString GenericChatItemWidget::getName() const
{
return nameLabel->fullText();
}
void GenericChatItemWidget::searchName(const QString &searchString, bool hide)
{
setVisible(!hide && getName().contains(searchString, Qt::CaseInsensitive));
}

View File

@ -0,0 +1,58 @@
/*
Copyright © 2015 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
qTox is libre software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
qTox is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with qTox. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GENERICCHATITEMWIDGET_H
#define GENERICCHATITEMWIDGET_H
#include <QFrame>
#include <QLabel>
class CroppingLabel;
class GenericChatItemWidget : public QFrame
{
Q_OBJECT
public:
enum ItemType
{
GroupItem,
FriendOfflineItem,
FriendOnlineItem
};
GenericChatItemWidget(QWidget *parent = 0);
bool isCompact() const;
void setCompact(bool compact);
QString getName() const;
void searchName(const QString &searchString, bool hideAll);
Q_PROPERTY(bool compact READ isCompact WRITE setCompact)
protected:
CroppingLabel* nameLabel;
QLabel statusPic;
private:
bool compact;
};
#endif // GENERICCHATITEMWIDGET_H

View File

@ -22,14 +22,14 @@
#include "src/persistence/settings.h"
#include "maskablepixmapwidget.h"
#include "src/widget/tool/croppinglabel.h"
#include <QBoxLayout>
#include <QMouseEvent>
GenericChatroomWidget::GenericChatroomWidget(QWidget *parent)
: QFrame(parent), compact{Settings::getInstance().getCompactLayout()},
active{false}
: GenericChatItemWidget(parent), active{false}
{
// avatar
if (compact)
if (isCompact())
avatar = new MaskablePixmapWidget(this, QSize(20,20), ":/img/avatar_mask.svg");
else
avatar = new MaskablePixmapWidget(this, QSize(40,40), ":/img/avatar_mask.svg");
@ -39,49 +39,50 @@ GenericChatroomWidget::GenericChatroomWidget(QWidget *parent)
statusMessageLabel->setTextFormat(Qt::PlainText);
statusMessageLabel->setForegroundRole(QPalette::WindowText);
// name text
nameLabel = new CroppingLabel(this);
nameLabel->setTextFormat(Qt::PlainText);
nameLabel->setForegroundRole(QPalette::WindowText);
setAutoFillBackground(true);
reloadTheme();
setCompact(compact);
compactChange(isCompact());
}
void GenericChatroomWidget::setCompact(bool _compact)
bool GenericChatroomWidget::eventFilter(QObject *, QEvent *)
{
compact = _compact;
return true; // Disable all events.
}
void GenericChatroomWidget::compactChange(bool _compact)
{
setCompact(_compact);
delete textLayout; // has to be first, deleted by layout
delete layout;
delete mainLayout;
compact = _compact;
layout = new QHBoxLayout;
mainLayout = new QHBoxLayout;
textLayout = new QVBoxLayout;
setLayout(layout);
layout->setSpacing(0);
layout->setMargin(0);
setLayout(mainLayout);
mainLayout->setSpacing(0);
mainLayout->setMargin(0);
textLayout->setSpacing(0);
textLayout->setMargin(0);
setLayoutDirection(Qt::LeftToRight); // parent might have set Qt::RightToLeft
// avatar
if (compact)
if (isCompact())
{
setFixedHeight(25);
avatar->setSize(QSize(20,20));
layout->addSpacing(18);
layout->addWidget(avatar);
layout->addSpacing(5);
layout->addWidget(nameLabel);
layout->addWidget(statusMessageLabel);
layout->addSpacing(5);
layout->addWidget(&statusPic);
layout->addSpacing(5);
layout->activate();
mainLayout->addSpacing(18);
mainLayout->addWidget(avatar);
mainLayout->addSpacing(5);
mainLayout->addWidget(nameLabel);
mainLayout->addWidget(statusMessageLabel);
mainLayout->addSpacing(5);
mainLayout->addWidget(&statusPic);
mainLayout->addSpacing(5);
mainLayout->activate();
statusMessageLabel->setFont(Style::getFont(Style::Small));
nameLabel->setFont(Style::getFont(Style::Medium));
}
@ -93,14 +94,14 @@ void GenericChatroomWidget::setCompact(bool _compact)
textLayout->addWidget(nameLabel);
textLayout->addWidget(statusMessageLabel);
textLayout->addStretch();
layout->addSpacing(20);
layout->addWidget(avatar);
layout->addSpacing(10);
layout->addLayout(textLayout);
layout->addSpacing(10);
layout->addWidget(&statusPic);
layout->addSpacing(10);
layout->activate();
mainLayout->addSpacing(20);
mainLayout->addWidget(avatar);
mainLayout->addSpacing(10);
mainLayout->addLayout(textLayout);
mainLayout->addSpacing(10);
mainLayout->addWidget(&statusPic);
mainLayout->addSpacing(10);
mainLayout->activate();
statusMessageLabel->setFont(Style::getFont(Style::Medium));
nameLabel->setFont(Style::getFont(Style::Big));
}
@ -138,11 +139,6 @@ void GenericChatroomWidget::setStatusMsg(const QString &status)
statusMessageLabel->setText(status);
}
QString GenericChatroomWidget::getName() const
{
return nameLabel->fullText();
}
QString GenericChatroomWidget::getStatusMsg() const
{
return statusMessageLabel->text();
@ -169,17 +165,6 @@ void GenericChatroomWidget::reloadTheme()
setPalette(p);
}
bool GenericChatroomWidget::isCompact() const
{
return compact;
}
void GenericChatroomWidget::mousePressEvent(QMouseEvent* event)
{
if (!active && event->button() == Qt::RightButton)
setBackgroundRole(QPalette::Window);
}
void GenericChatroomWidget::mouseReleaseEvent(QMouseEvent*)
{
emit chatroomWidgetClicked(this);
@ -191,8 +176,9 @@ void GenericChatroomWidget::enterEvent(QEvent*)
setBackgroundRole(QPalette::Highlight);
}
void GenericChatroomWidget::leaveEvent(QEvent*)
void GenericChatroomWidget::leaveEvent(QEvent* event)
{
if (!active)
setBackgroundRole(QPalette::Window);
QWidget::leaveEvent(event);
}

View File

@ -20,64 +20,59 @@
#ifndef GENERICCHATROOMWIDGET_H
#define GENERICCHATROOMWIDGET_H
#include <QFrame>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>
#include "genericchatitemwidget.h"
class CroppingLabel;
class MaskablePixmapWidget;
class QVBoxLayout;
class QHBoxLayout;
namespace Ui {
class MainWindow;
}
class GenericChatroomWidget : public QFrame
class GenericChatroomWidget : public GenericChatItemWidget
{
Q_OBJECT
public:
GenericChatroomWidget(QWidget *parent = 0);
virtual void setAsActiveChatroom(){;}
virtual void setAsInactiveChatroom(){;}
virtual void updateStatusLight(){;}
virtual void setChatForm(Ui::MainWindow &){;}
virtual void resetEventFlags(){;}
virtual QString getStatusString(){return QString::null;}
virtual void setAsActiveChatroom() = 0;
virtual void setAsInactiveChatroom() = 0;
virtual void updateStatusLight() = 0;
virtual void setChatForm(Ui::MainWindow &) = 0;
virtual void resetEventFlags() = 0;
virtual QString getStatusString() = 0;
virtual bool eventFilter(QObject *, QEvent *) final override;
bool isActive();
void setActive(bool active);
void setName(const QString& name);
void setStatusMsg(const QString& status);
QString getName() const;
QString getStatusMsg() const;
void reloadTheme();
bool isCompact() const;
void reloadTheme();
public slots:
void setCompact(bool compact);
void compactChange(bool compact);
signals:
void chatroomWidgetClicked(GenericChatroomWidget* widget);
protected:
virtual void mousePressEvent(QMouseEvent* event) override;
virtual void mouseReleaseEvent (QMouseEvent* event) override;
virtual void mouseReleaseEvent(QMouseEvent* event) override;
virtual void enterEvent(QEvent* e) override;
virtual void leaveEvent(QEvent* e) override;
protected:
QColor lastColor;
QHBoxLayout* layout = nullptr;
QHBoxLayout* mainLayout = nullptr;
QVBoxLayout* textLayout = nullptr;
MaskablePixmapWidget* avatar;
QLabel statusPic;
CroppingLabel* nameLabel, *statusMessageLabel;
bool compact, active;
CroppingLabel* statusMessageLabel;
bool active;
};
#endif // GENERICCHATROOMWIDGET_H

View File

@ -18,22 +18,18 @@
*/
#include "groupwidget.h"
#include "maskablepixmapwidget.h"
#include "src/grouplist.h"
#include "src/group.h"
#include "src/persistence/settings.h"
#include "form/groupchatform.h"
#include "maskablepixmapwidget.h"
#include "src/widget/style.h"
#include "src/core/core.h"
#include "tool/croppinglabel.h"
#include <QPalette>
#include <QMenu>
#include <QContextMenuEvent>
#include <QMimeData>
#include <QDragEnterEvent>
#include <QInputDialog>
#include "ui_mainwindow.h"
#include <QMimeData>
GroupWidget::GroupWidget(int GroupId, QString Name)
: groupId{GroupId}
@ -43,23 +39,39 @@ GroupWidget::GroupWidget(int GroupId, QString Name)
statusPic.setMargin(3);
nameLabel->setText(Name);
Group* g = GroupList::findGroup(groupId);
if (g)
statusMessageLabel->setText(GroupWidget::tr("%1 users in chat").arg(g->getPeersCount()));
else
statusMessageLabel->setText(GroupWidget::tr("0 users in chat"));
onUserListChanged();
setAcceptDrops(true);
connect(nameLabel, &CroppingLabel::editFinished, [this](const QString &newName)
{
if (!newName.isEmpty())
{
Group* g = GroupList::findGroup(groupId);
emit renameRequested(this, newName);
emit g->getChatForm()->groupTitleChanged(groupId, newName.left(128));
}
});
}
void GroupWidget::contextMenuEvent(QContextMenuEvent * event)
void GroupWidget::contextMenuEvent(QContextMenuEvent* event)
{
QPoint pos = event->globalPos();
QMenu menu;
if (!active)
setBackgroundRole(QPalette::Highlight);
installEventFilter(this); // Disable leave event.
QMenu menu(this);
QAction* setTitle = menu.addAction(tr("Set title..."));
QAction* quitGroup = menu.addAction(tr("Quit group","Menu to quit a groupchat"));
QAction* selectedItem = menu.exec(pos);
QAction* selectedItem = menu.exec(event->globalPos());
removeEventFilter(this);
if (!active)
setBackgroundRole(QPalette::Window);
if (selectedItem)
{
if (selectedItem == quitGroup)
@ -68,21 +80,7 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent * event)
}
else if (selectedItem == setTitle)
{
bool ok;
Group* g = GroupList::findGroup(groupId);
QString alias = QInputDialog::getText(nullptr, tr("Group title"), tr("You can also set this by clicking the chat form name.\nTitle:"), QLineEdit::Normal,
nameLabel->fullText(), &ok);
if (ok && alias != nameLabel->fullText())
emit g->getChatForm()->groupTitleChanged(groupId, alias.left(128));
/* according to agilob:
* Moving mouse pointer over groupwidget results in CSS effect
* mouse-over(?). Changing group title repaints only changed
* element - title, the rest of the widget stays in the same CSS as it
* was on mouse over. Repainting whole widget fixes style problem.
*/
this->repaint();
editName();
}
}
}
@ -134,6 +132,11 @@ QString GroupWidget::getStatusString()
return "New Message";
}
void GroupWidget::editName()
{
nameLabel->editBegin();
}
void GroupWidget::setChatForm(Ui::MainWindow &ui)
{
Group* g = GroupList::findGroup(groupId);
@ -151,6 +154,15 @@ void GroupWidget::dragEnterEvent(QDragEnterEvent *ev)
{
if (ev->mimeData()->hasFormat("friend"))
ev->acceptProposedAction();
if (!active)
setBackgroundRole(QPalette::Highlight);
}
void GroupWidget::dragLeaveEvent(QDragLeaveEvent *)
{
if (!active)
setBackgroundRole(QPalette::Window);
}
void GroupWidget::dropEvent(QDropEvent *ev)
@ -159,6 +171,9 @@ void GroupWidget::dropEvent(QDropEvent *ev)
{
int friendId = ev->mimeData()->data("friend").toInt();
Core::getInstance()->groupInviteFriend(friendId, groupId);
if (!active)
setBackgroundRole(QPalette::Window);
}
}

View File

@ -20,15 +20,13 @@
#ifndef GROUPWIDGET_H
#define GROUPWIDGET_H
#include <QLabel>
#include "genericchatroomwidget.h"
class GroupWidget : public GenericChatroomWidget
class GroupWidget final : public GenericChatroomWidget
{
Q_OBJECT
public:
GroupWidget(int GroupId, QString Name);
virtual void contextMenuEvent(QContextMenuEvent * event) final override;
virtual void setAsInactiveChatroom() final override;
virtual void setAsActiveChatroom() final override;
virtual void updateStatusLight() final override;
@ -37,15 +35,18 @@ public:
virtual QString getStatusString() final override;
void setName(const QString& name);
void onUserListChanged();
void editName();
signals:
void groupWidgetClicked(GroupWidget* widget);
void renameRequested(GroupWidget* widget, const QString& newName);
void removeGroup(int groupId);
protected:
// drag & drop
virtual void dragEnterEvent(QDragEnterEvent* ev) final override;
virtual void dropEvent(QDropEvent* ev) final override;
virtual void contextMenuEvent(QContextMenuEvent * event) final override;
virtual void dragEnterEvent(QDragEnterEvent* ev) override;
virtual void dragLeaveEvent(QDragLeaveEvent* ev);
virtual void dropEvent(QDropEvent* ev) override;
public:
int groupId;

View File

@ -35,8 +35,13 @@ CroppingLabel::CroppingLabel(QWidget* parent)
| Qt::ImhNoPredictiveText
| Qt::ImhPreferLatin);
installEventFilter(this);
textEdit->installEventFilter(this);
connect(textEdit, &QLineEdit::editingFinished, this, &CroppingLabel::editingFinished);
}
void CroppingLabel::editBegin()
{
showTextEdit();
textEdit->selectAll();
}
void CroppingLabel::setEditable(bool editable)
@ -88,36 +93,14 @@ void CroppingLabel::mouseReleaseEvent(QMouseEvent *e)
QLabel::mouseReleaseEvent(e);
}
bool CroppingLabel::eventFilter(QObject *obj, QEvent *e)
void CroppingLabel::paintEvent(QPaintEvent* paintEvent)
{
// catch paint events if needed
if (obj == this)
if (blockPaintEvents)
{
if (e->type() == QEvent::Paint && blockPaintEvents)
return true;
paintEvent->ignore();
return;
}
// events fired by the QLineEdit
if (obj == textEdit)
{
if (!textEdit->isVisible())
return false;
if (e->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(e);
if (keyEvent->key() == Qt::Key_Return)
hideTextEdit(true);
if (keyEvent->key() == Qt::Key_Escape)
hideTextEdit(false);
}
if (e->type() == QEvent::FocusOut)
hideTextEdit(true);
}
return false;
QLabel::paintEvent(paintEvent);
}
void CroppingLabel::setElidedText()
@ -131,28 +114,35 @@ void CroppingLabel::setElidedText()
QLabel::setText(elidedText);
}
void CroppingLabel::hideTextEdit(bool acceptText)
{
if (acceptText)
{
QString oldOrigText = origText;
setText(textEdit->text()); // set before emitting so we don't override external reactions to signal
emit textChanged(textEdit->text(), oldOrigText);
}
textEdit->hide();
blockPaintEvents = false;
}
void CroppingLabel::showTextEdit()
{
blockPaintEvents = true;
textEdit->show();
textEdit->setFocus();
textEdit->setText(origText);
textEdit->setFocusPolicy(Qt::ClickFocus);
}
QString CroppingLabel::fullText()
{
return origText;
}
void CroppingLabel::minimizeMaximumWidth()
{
// This function chooses the smallest possible maximum width.
// Text width + padding. Without padding, we'll have elipses.
setMaximumWidth(fontMetrics().width(origText) + fontMetrics().width("..."));
}
void CroppingLabel::editingFinished()
{
QString newText = textEdit->text().trimmed().remove(QRegExp("[\\t\\n\\v\\f\\r\\x0000]"));
if (origText != newText)
emit editFinished(textEdit->text());
textEdit->hide();
blockPaintEvents = false;
emit editRemoved();
}

View File

@ -1,5 +1,5 @@
/*
Copyright © 2014 by The qTox Project
Copyright © 2014-2015 by The qTox Project
This file is part of qTox, a Qt-based graphical interface for Tox.
@ -24,23 +24,30 @@
class QLineEdit;
class CroppingLabel final : public QLabel
class CroppingLabel : public QLabel
{
Q_OBJECT
public:
explicit CroppingLabel(QWidget *parent = 0);
explicit CroppingLabel(QWidget* parent = 0);
void editBegin();
void setEditable(bool editable);
void setEdlideMode(Qt::TextElideMode elide);
void setText(const QString& text);
QString fullText(); ///< Returns the un-cropped text
public slots:
void minimizeMaximumWidth();
signals:
void textChanged(QString newText, QString oldText);
void editFinished(const QString& newText);
void editRemoved();
void clicked();
protected:
void paintEvent(QPaintEvent* paintEvent) override;
void setElidedText();
void hideTextEdit(bool acceptText);
void showTextEdit();
@ -48,7 +55,9 @@ protected:
virtual QSize sizeHint() const final override;
virtual QSize minimumSizeHint() const final override;
virtual void mouseReleaseEvent(QMouseEvent *e) final override;
virtual bool eventFilter(QObject *obj, QEvent *e) final override;
private slots:
void editingFinished();
private:
QString origText;

View File

@ -29,6 +29,7 @@
#include "src/group.h"
#include "groupwidget.h"
#include "form/groupchatform.h"
#include "circlewidget.h"
#include "src/widget/style.h"
#include "friendlistwidget.h"
#include "form/chatform.h"
@ -133,6 +134,45 @@ void Widget::init()
ui->myProfile->insertWidget(0, profilePicture);
ui->myProfile->insertSpacing(1, 7);
filterMenu = new QMenu(this);
filterGroup = new QActionGroup(this);
filterDisplayGroup = new QActionGroup(this);
filterDisplayName = new QAction(this);
filterDisplayName->setCheckable(true);
filterDisplayName->setChecked(true);
filterDisplayGroup->addAction(filterDisplayName);
filterMenu->addAction(filterDisplayName);
filterDisplayActivity = new QAction(this);
filterDisplayActivity->setCheckable(true);
filterDisplayGroup->addAction(filterDisplayActivity);
filterMenu->addAction(filterDisplayActivity);
filterMenu->addSeparator();
filterAllAction = new QAction(this);
filterAllAction->setCheckable(true);
filterAllAction->setChecked(true);
filterGroup->addAction(filterAllAction);
filterMenu->addAction(filterAllAction);
filterOnlineAction = new QAction(this);
filterOnlineAction->setCheckable(true);
filterGroup->addAction(filterOnlineAction);
filterMenu->addAction(filterOnlineAction);
filterOfflineAction = new QAction(this);
filterOfflineAction->setCheckable(true);
filterGroup->addAction(filterOfflineAction);
filterMenu->addAction(filterOfflineAction);
filterFriendsAction = new QAction(this);
filterFriendsAction->setCheckable(true);
filterGroup->addAction(filterFriendsAction);
filterMenu->addAction(filterFriendsAction);
filterGroupsAction = new QAction(this);
filterGroupsAction->setCheckable(true);
filterGroup->addAction(filterGroupsAction);
filterMenu->addAction(filterGroupsAction);
ui->searchContactFilterBox->setMenu(filterMenu);
ui->mainContent->setLayout(new QVBoxLayout());
ui->mainHead->setLayout(new QVBoxLayout());
ui->mainHead->layout()->setMargin(0);
@ -152,9 +192,10 @@ void Widget::init()
ui->statusPanel->setStyleSheet(Style::getStylesheet(":/ui/window/statusPanel.css"));
#endif
contactListWidget = new FriendListWidget(0, Settings::getInstance().getGroupchatPosition());
contactListWidget = new FriendListWidget(this, Settings::getInstance().getGroupchatPosition());
ui->friendList->setWidget(contactListWidget);
ui->friendList->setLayoutDirection(Qt::RightToLeft);
ui->friendList->setContextMenuPolicy(Qt::CustomContextMenu);
ui->statusLabel->setEditable(true);
@ -195,7 +236,7 @@ void Widget::init()
connect(ui->settingsButton, &QPushButton::clicked, this, &Widget::onSettingsClicked);
connect(profilePicture, &MaskablePixmapWidget::clicked, this, &Widget::showProfile);
connect(ui->nameLabel, &CroppingLabel::clicked, this, &Widget::showProfile);
connect(ui->statusLabel, &CroppingLabel::textChanged, this, &Widget::onStatusMessageChanged);
connect(ui->statusLabel, &CroppingLabel::editFinished, this, &Widget::onStatusMessageChanged);
connect(ui->mainSplitter, &QSplitter::splitterMoved, this, &Widget::onSplitterMoved);
connect(addFriendForm, &AddFriendForm::friendRequested, this, &Widget::friendRequested);
connect(timer, &QTimer::timeout, this, &Widget::onUserAwayCheck);
@ -203,7 +244,9 @@ void Widget::init()
connect(timer, &QTimer::timeout, this, &Widget::onTryCreateTrayIcon);
connect(offlineMsgTimer, &QTimer::timeout, this, &Widget::processOfflineMsgs);
connect(ui->searchContactText, &QLineEdit::textChanged, this, &Widget::searchContacts);
connect(ui->searchContactFilterCBox, &QComboBox::currentTextChanged, this, &Widget::searchContacts);
connect(filterGroup, &QActionGroup::triggered, this, &Widget::searchContacts);
connect(filterDisplayGroup, &QActionGroup::triggered, this, &Widget::changeDisplayMode);
connect(ui->friendList, &QWidget::customContextMenuRequested, this, &Widget::friendListContextMenu);
// keyboard shortcuts
new QShortcut(Qt::CTRL + Qt::Key_Q, this, SLOT(close()));
@ -219,6 +262,7 @@ void Widget::init()
ui->settingsButton->setCheckable(true);
setActiveToolMenuButton(Widget::AddButton);
connect(settingsWidget, &SettingsWidget::compactToggled, contactListWidget, &FriendListWidget::onCompactChanged);
connect(settingsWidget, &SettingsWidget::groupchatPositionToggled, contactListWidget, &FriendListWidget::onGroupchatPositionChanged);
#if (AUTOUPDATE_ENABLED)
if (Settings::getInstance().getCheckUpdates())
@ -552,10 +596,9 @@ void Widget::setUsername(const QString& username)
sanitizedNameMention = QRegExp("\\b" + QRegExp::escape(sanename) + "\\b", Qt::CaseInsensitive);
}
void Widget::onStatusMessageChanged(const QString& newStatusMessage, const QString& oldStatusMessage)
void Widget::onStatusMessageChanged(const QString& newStatusMessage)
{
ui->statusLabel->setText(oldStatusMessage); // restore old status message until Core tells us to set it
ui->statusLabel->setToolTip(oldStatusMessage); // for overlength messsages
// Keep old status message until Core tells us to set it.
Nexus::getCore()->setStatusMessage(newStatusMessage);
}
@ -575,11 +618,18 @@ void Widget::addFriend(int friendId, const QString &userId)
{
ToxId userToxId = ToxId(userId);
Friend* newfriend = FriendList::addFriend(friendId, userToxId);
contactListWidget->moveWidget(newfriend->getFriendWidget(),Status::Offline);
QDate activityDate = Settings::getInstance().getFriendActivity(newfriend->getToxId());
QDate chatDate = newfriend->getChatForm()->getLatestDate();
if (chatDate > activityDate && chatDate.isValid())
Settings::getInstance().setFriendActivity(newfriend->getToxId(), chatDate);
contactListWidget->addFriendWidget(newfriend->getFriendWidget(),Status::Offline,Settings::getInstance().getFriendCircleID(newfriend->getToxId()));
Core* core = Nexus::getCore();
connect(newfriend, &Friend::displayedNameChanged, contactListWidget, &FriendListWidget::moveWidget);
connect(settingsWidget, &SettingsWidget::compactToggled, newfriend->getFriendWidget(), &GenericChatroomWidget::setCompact);
connect(newfriend, &Friend::displayedNameChanged, this, &Widget::onFriendDisplayChanged);
connect(settingsWidget, &SettingsWidget::compactToggled, newfriend->getFriendWidget(), &GenericChatroomWidget::compactChange);
connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*)));
connect(newfriend->getFriendWidget(), SIGNAL(removeFriend(int)), this, SLOT(removeFriend(int)));
connect(newfriend->getFriendWidget(), SIGNAL(copyFriendIdToClipboard(int)), this, SLOT(copyFriendIdToClipboard(int)));
@ -621,7 +671,9 @@ void Widget::addFriend(int friendId, const QString &userId)
newfriend->getFriendWidget()->onAvatarChange(friendId, avatar);
}
searchContacts();
int filter = getFilterCriteria();
newfriend->getFriendWidget()->search(ui->searchContactText->text(), filterOffline(filter));
}
void Widget::addFriendFailed(const QString&, const QString& errorInfo)
@ -634,6 +686,12 @@ void Widget::addFriendFailed(const QString&, const QString& errorInfo)
QMessageBox::critical(0,"Error",info);
}
void Widget::onFriendshipChanged(int friendId)
{
Friend* who = FriendList::findFriend(friendId);
updateFriendActivity(who);
}
void Widget::onFriendStatusChanged(int friendId, Status status)
{
Friend* f = FriendList::findFriend(friendId);
@ -709,7 +767,20 @@ void Widget::onFriendUsernameChanged(int friendId, const QString& username)
QString str = username; str.replace('\n', ' ');
str.remove('\r'); str.remove(QChar((char)0)); // null terminator...
f->setName(str);
searchContacts();
}
void Widget::onFriendDisplayChanged(FriendWidget *friendWidget, Status s)
{
contactListWidget->moveWidget(friendWidget, s);
int filter = getFilterCriteria();
switch (s)
{
case Status::Offline:
friendWidget->searchName(ui->searchContactText->text(), filterOffline(filter));
default:
friendWidget->searchName(ui->searchContactText->text(), filterOnline(filter));
}
}
void Widget::onChatroomWidgetClicked(GenericChatroomWidget *widget)
@ -831,6 +902,16 @@ void Widget::onFriendRequestReceived(const QString& userId, const QString& messa
emit friendRequestAccepted(userId);
}
void Widget::updateFriendActivity(Friend *frnd)
{
QDate date = Settings::getInstance().getFriendActivity(frnd->getToxId());
if (date != QDate::currentDate())
{
Settings::getInstance().setFriendActivity(frnd->getToxId(), QDate::currentDate());
contactListWidget->moveWidget(frnd->getFriendWidget(), frnd->getStatus());
}
}
void Widget::removeFriend(Friend* f, bool fake)
{
if (!fake)
@ -853,6 +934,8 @@ void Widget::removeFriend(Friend* f, bool fake)
onAddClicked();
}
contactListWidget->removeFriendWidget(f->getFriendWidget());
FriendList::removeFriend(f->getFriendID(), fake);
Nexus::getCore()->removeFriend(f->getFriendID(), fake);
@ -983,10 +1066,13 @@ void Widget::onGroupTitleChanged(int groupnumber, const QString& author, const Q
if (!g)
return;
g->setName(title);
if (!author.isEmpty())
g->getChatForm()->addSystemInfoMessage(tr("%1 has set the title to %2").arg(author, title), ChatMessage::INFO, QDateTime::currentDateTime());
searchContacts();
contactListWidget->renameGroupWidget(g->getGroupWidget(), title);
g->setName(title);
int filter = getFilterCriteria();
g->getGroupWidget()->searchName(ui->searchContactText->text(), filterGroups(filter));
}
void Widget::onGroupPeerAudioPlaying(int groupnumber, int peernumber)
@ -1033,24 +1119,31 @@ Group *Widget::createGroup(int groupId)
QString groupName = QString("Groupchat #%1").arg(groupId);
Group* newgroup = GroupList::addGroup(groupId, groupName, core->isGroupAvEnabled(groupId));
QLayout* layout = contactListWidget->getGroupLayout();
layout->addWidget(newgroup->getGroupWidget());
contactListWidget->addGroupWidget(newgroup->getGroupWidget());
newgroup->getGroupWidget()->updateStatusLight();
connect(settingsWidget, &SettingsWidget::compactToggled, newgroup->getGroupWidget(), &GenericChatroomWidget::setCompact);
connect(settingsWidget, &SettingsWidget::compactToggled, newgroup->getGroupWidget(), &GenericChatroomWidget::compactChange);
connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*)));
connect(newgroup->getGroupWidget(), SIGNAL(removeGroup(int)), this, SLOT(removeGroup(int)));
connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newgroup->getChatForm(), SLOT(focusInput()));
connect(newgroup->getChatForm(), &GroupChatForm::sendMessage, core, &Core::sendGroupMessage);
connect(newgroup->getChatForm(), &GroupChatForm::sendAction, core, &Core::sendGroupAction);
connect(newgroup->getChatForm(), &GroupChatForm::groupTitleChanged, core, &Core::changeGroupTitle);
searchContacts();
int filter = getFilterCriteria();
newgroup->getGroupWidget()->searchName(ui->searchContactText->text(), filterGroups(filter));
return newgroup;
}
void Widget::onEmptyGroupCreated(int groupId)
{
createGroup(groupId);
Group* group = createGroup(groupId);
// Only rename group if groups are visible.
if (Widget::getInstance()->groupsVisible())
group->getGroupWidget()->editName();
}
bool Widget::isFriendWidgetCurActiveWidget(const Friend* f) const
@ -1254,21 +1347,45 @@ void Widget::onSplitterMoved(int pos, int index)
saveSplitterGeometry();
}
void Widget::cycleContacts(int offset)
void Widget::cycleContacts(bool forward)
{
if (!activeChatroomWidget)
return;
contactListWidget->cycleContacts(activeChatroomWidget, forward);
}
FriendListWidget* friendList = static_cast<FriendListWidget*>(ui->friendList->widget());
QList<GenericChatroomWidget*> friends = friendList->getAllFriends();
bool Widget::filterGroups(int index)
{
switch (index)
{
case FilterCriteria::Offline:
case FilterCriteria::Friends:
return true;
default:
return false;
}
}
int activeIndex = friends.indexOf(activeChatroomWidget);
int bounded = (activeIndex + offset) % friends.length();
bool Widget::filterOffline(int index)
{
switch (index)
{
case FilterCriteria::Online:
case FilterCriteria::Groups:
return true;
default:
return false;
}
}
if(bounded < 0)
bounded += friends.length();
emit friends[bounded]->chatroomWidgetClicked(friends[bounded]);
bool Widget::filterOnline(int index)
{
switch (index)
{
case FilterCriteria::Offline:
case FilterCriteria::Groups:
return true;
default:
return false;
}
}
void Widget::processOfflineMsgs()
@ -1298,6 +1415,7 @@ void Widget::reloadTheme()
ui->statusHead->setStyleSheet(statusPanelStyle);
ui->friendList->setStyleSheet(Style::getStylesheet(":ui/friendList/friendList.css"));
ui->statusButton->setStyleSheet(Style::getStylesheet(":ui/statusButton/statusButton.css"));
contactListWidget->reDraw();
for (Friend* f : FriendList::getAllFriends())
f->getFriendWidget()->reloadTheme();
@ -1308,12 +1426,12 @@ void Widget::reloadTheme()
void Widget::nextContact()
{
cycleContacts(1);
cycleContacts(true);
}
void Widget::previousContact()
{
cycleContacts(-1);
cycleContacts(false);
}
QString Widget::getStatusIconPath(Status status)
@ -1378,79 +1496,87 @@ Status Widget::getStatusFromString(QString status)
void Widget::searchContacts()
{
QString searchString = ui->searchContactText->text();
int filter = ui->searchContactFilterCBox->currentIndex();
int filter = getFilterCriteria();
switch(filter)
{
case FilterCriteria::All:
hideFriends(searchString, Status::Online);
hideFriends(searchString, Status::Offline);
contactListWidget->searchChatrooms(searchString, filterOnline(filter), filterOffline(filter), filterGroups(filter));
hideGroups(searchString);
break;
case FilterCriteria::Online:
hideFriends(searchString, Status::Online);
hideFriends(QString(), Status::Offline, true);
hideGroups(searchString);
break;
case FilterCriteria::Offline:
hideFriends(QString(), Status::Online, true);
hideFriends(searchString, Status::Offline);
hideGroups(QString(), true);
break;
case FilterCriteria::Friends:
hideFriends(searchString, Status::Online);
hideFriends(searchString, Status::Offline);
hideGroups(QString(), true);
break;
case FilterCriteria::Groups:
hideFriends(QString(), Status::Online, true);
hideFriends(QString(), Status::Offline, true);
hideGroups(searchString);
break;
default:
return;
}
updateFilterText();
contactListWidget->reDraw();
}
void Widget::hideFriends(QString searchString, Status status, bool hideAll)
void Widget::changeDisplayMode()
{
QVBoxLayout* friends = contactListWidget->getFriendLayout(status);
int friendCount = friends->count(), index;
filterDisplayGroup->setEnabled(false);
for (index = 0; index<friendCount; index++)
{
FriendWidget* friendWidget = static_cast<FriendWidget*>(friends->itemAt(index)->widget());
QString friendName = friendWidget->getName();
if (filterDisplayGroup->checkedAction() == filterDisplayActivity)
contactListWidget->setMode(FriendListWidget::Activity);
else if (filterDisplayGroup->checkedAction() == filterDisplayName)
contactListWidget->setMode(FriendListWidget::Name);
if (!friendName.contains(searchString, Qt::CaseInsensitive) || hideAll)
friendWidget->setVisible(false);
else
friendWidget->setVisible(true);
}
searchContacts();
filterDisplayGroup->setEnabled(true);
updateFilterText();
}
void Widget::hideGroups(QString searchString, bool hideAll)
void Widget::updateFilterText()
{
QVBoxLayout* groups = contactListWidget->getGroupLayout();
int groupCount = groups->count(), index;
ui->searchContactFilterBox->setText(filterDisplayGroup->checkedAction()->text() + QStringLiteral(" | ") + filterGroup->checkedAction()->text());
}
for (index = 0; index<groupCount; index++)
int Widget::getFilterCriteria() const
{
QAction* checked = filterGroup->checkedAction();
if (checked == filterOnlineAction)
return Online;
else if (checked == filterOfflineAction)
return Offline;
else if (checked == filterFriendsAction)
return Friends;
else if (checked == filterGroupsAction)
return Groups;
return All;
}
void Widget::searchCircle(CircleWidget *circleWidget)
{
int filter = getFilterCriteria();
circleWidget->search(ui->searchContactText->text(), true, filterOnline(filter), filterOffline(filter));
}
void Widget::searchItem(GenericChatItemWidget *chatItem, GenericChatItemWidget::ItemType type)
{
bool hide;
int filter = getFilterCriteria();
switch (type)
{
GroupWidget* groupWidget = static_cast<GroupWidget*>(groups->itemAt(index)->widget());
QString groupName = groupWidget->getName();
if (!groupName.contains(searchString, Qt::CaseInsensitive) || hideAll)
groupWidget->setVisible(false);
else
groupWidget->setVisible(true);
case GenericChatItemWidget::GroupItem:
hide = filterGroups(filter);
break;
default:
hide = true;
}
chatItem->searchName(ui->searchContactText->text(), hide);
}
bool Widget::groupsVisible() const
{
int filter = getFilterCriteria();
return !filterGroups(filter);
}
void Widget::friendListContextMenu(const QPoint &pos)
{
QMenu menu(this);
QAction *addCircleAction = menu.addAction(tr("Add new circle..."));
QAction *chosenAction = menu.exec(ui->friendList->mapToGlobal(pos));
if (chosenAction == addCircleAction)
contactListWidget->addCircleWidget();
}
void Widget::setActiveToolMenuButton(ActiveToolMenuButton newActiveButton)
@ -1471,12 +1597,17 @@ void Widget::retranslateUi()
ui->retranslateUi(this);
ui->nameLabel->setText(name);
ui->statusLabel->setText(status);
ui->searchContactFilterCBox->clear();
ui->searchContactFilterCBox->addItem(tr("All"));
ui->searchContactFilterCBox->addItem(tr("Online"));
ui->searchContactFilterCBox->addItem(tr("Offline"));
ui->searchContactFilterCBox->addItem(tr("Friends"));
ui->searchContactFilterCBox->addItem(tr("Groups"));
filterDisplayName->setText(tr("By Name"));
filterDisplayActivity->setText(tr("By Activity"));
filterAllAction->setText(tr("All"));
filterOnlineAction->setText(tr("Online"));
filterOfflineAction->setText(tr("Offline"));
filterFriendsAction->setText(tr("Friends"));
filterGroupsAction->setText(tr("Groups"));
ui->searchContactText->setPlaceholderText(tr("Search Contacts"));
updateFilterText();
ui->searchContactText->setPlaceholderText(tr("Search Contacts"));
statusOnline->setText(tr("Online", "Button to set your status to 'Online'"));
statusAway->setText(tr("Away", "Button to set your status to 'Away'"));

View File

@ -24,6 +24,7 @@
#include <QSystemTrayIcon>
#include <QFileInfo>
#include "src/core/corestructs.h"
#include "genericchatitemwidget.h"
#define PIXELS_TO_ACT 7
@ -32,6 +33,7 @@ class MainWindow;
}
class GenericChatroomWidget;
class FriendWidget;
class Group;
class Friend;
class QSplitter;
@ -47,6 +49,8 @@ class FilesForm;
class ProfileForm;
class SettingsWidget;
class AddFriendForm;
class CircleWidget;
class QActionGroup;
class Widget final : public QMainWindow
{
@ -77,6 +81,10 @@ public:
static QString getStatusTitle(Status status);
static Status getStatusFromString(QString status);
void searchCircle(CircleWidget* circleWidget);
void searchItem(GenericChatItemWidget* chatItem, GenericChatItemWidget::ItemType type);
bool groupsVisible() const;
public slots:
void onSettingsClicked();
void setWindowTitle(const QString& title);
@ -91,11 +99,14 @@ public slots:
void setStatusMessage(const QString &statusMessage);
void addFriend(int friendId, const QString& userId);
void addFriendFailed(const QString& userId, const QString& errorInfo = QString());
void onFriendshipChanged(int friendId);
void onFriendStatusChanged(int friendId, Status status);
void onFriendStatusMessageChanged(int friendId, const QString& message);
void onFriendUsernameChanged(int friendId, const QString& username);
void onFriendDisplayChanged(FriendWidget* friendWidget, Status s);
void onFriendMessageReceived(int friendId, const QString& message, bool isAction);
void onFriendRequestReceived(const QString& userId, const QString& message);
void updateFriendActivity(Friend* frnd);
void onMessageSendResult(uint32_t friendId, const QString& message, int messageId);
void onReceiptRecieved(int friendId, int receipt);
void onEmptyGroupCreated(int groupId);
@ -132,7 +143,7 @@ private slots:
void onTransferClicked();
void showProfile();
void onUsernameChanged(const QString& newUsername, const QString& oldUsername);
void onStatusMessageChanged(const QString& newStatusMessage, const QString& oldStatusMessage);
void onStatusMessageChanged(const QString& newStatusMessage);
void onChatroomWidgetClicked(GenericChatroomWidget *);
void removeFriend(int friendId);
void copyFriendIdToClipboard(int friendId);
@ -147,9 +158,7 @@ private slots:
void onSetShowSystemTray(bool newValue);
void onSplitterMoved(int pos, int index);
void processOfflineMsgs();
void searchContacts();
void hideFriends(QString searchString, Status status, bool hideAll = false);
void hideGroups(QString searchString, bool hideAll = false);
void friendListContextMenu(const QPoint &pos);
private:
enum ActiveToolMenuButton {
@ -177,16 +186,36 @@ private:
void removeGroup(Group* g, bool fake = false);
void saveWindowGeometry();
void saveSplitterGeometry();
void cycleContacts(int offset);
void cycleContacts(bool forward);
void searchContacts();
void changeDisplayMode();
void updateFilterText();
int getFilterCriteria() const;
static bool filterGroups(int index);
static bool filterOnline(int index);
static bool filterOffline(int index);
void retranslateUi();
private:
SystemTrayIcon *icon;
QMenu *trayMenu;
QAction *statusOnline,
*statusAway,
*statusBusy,
*actionQuit;
QAction *statusOnline;
QAction *statusAway;
QAction *statusBusy;
QAction *actionQuit;
QMenu* filterMenu;
QActionGroup* filterGroup;
QAction* filterAllAction;
QAction* filterOnlineAction;
QAction* filterOfflineAction;
QAction* filterFriendsAction;
QAction* filterGroupsAction;
QActionGroup* filterDisplayGroup;
QAction* filterDisplayName;
QAction* filterDisplayActivity;
Ui::MainWindow *ui;
QSplitter *centralLayout;

View File

@ -30,3 +30,35 @@ QScrollBar:sub-line:vertical {height: 0px;subcontrol-position: top;subcontrol-or
QScrollBar:add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
QWidget#circleWidgetContainer > QFrame#line
{
color: white;
}
QWidget#circleWidgetContainer
{
background-color: @themeMedium;
}
QWidget#circleWidgetContainer:hover
{
background-color: @themeLight;
}
QWidget#circleWidgetContainer QLineEdit
{
background-color: @themeLight;
}
QWidget#circleWidgetContainer > QLabel#status
{
font: @small;
color: @lightGrey;
}
QWidget#circleWidgetContainer > QLabel#name
{
font: @big;
color: @white;
}

View File

@ -7,7 +7,7 @@ QLineEdit
border-radius: 4px;
}
QComboBox {
QToolButton {
background: none;
background-color: @themeMedium;
color: white;
@ -15,14 +15,14 @@ QComboBox {
border-radius: 4px;
}
QComboBox:on {
QToolButton:pressed {
background-color: @themeMediumDark;
border-radius: 4px;
color: white;
}
QComboBox:drop-down {
border-style: none;
border-radius: 4px;
QToolButton::menu-indicator {
image: none
}
/**