From 631148cdae1c00964d7703e146ee288f13b55e14 Mon Sep 17 00:00:00 2001 From: Daniel Hrabovcak Date: Wed, 27 May 2015 13:17:12 -0400 Subject: [PATCH] Basic contact grouping --- qtox.pro | 6 +- res.qrc | 1 + src/widget/circlewidget.cpp | 173 ++++++++++++++++++++++++++++ src/widget/circlewidget.hpp | 57 +++++++++ src/widget/friendlistwidget.cpp | 124 ++++++++++++++------ src/widget/friendlistwidget.h | 19 ++- src/widget/genericchatroomwidget.h | 3 +- src/widget/widget.cpp | 73 +++++------- src/widget/widget.h | 3 +- ui/chatroomWidgets/circleWidget.css | 26 +++++ 10 files changed, 396 insertions(+), 89 deletions(-) create mode 100644 src/widget/circlewidget.cpp create mode 100644 src/widget/circlewidget.hpp create mode 100644 ui/chatroomWidgets/circleWidget.css diff --git a/qtox.pro b/qtox.pro index 83a6c22c1..68efea513 100644 --- a/qtox.pro +++ b/qtox.pro @@ -488,7 +488,8 @@ 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 HEADERS += \ src/audio/audio.h \ @@ -526,4 +527,5 @@ HEADERS += \ src/widget/translator.h \ src/persistence/settingsserializer.h \ src/widget/notificationscrollarea.h \ - src/widget/notificationedgewidget.h + src/widget/notificationedgewidget.h \ + src/widget/circlewidget.hpp diff --git a/res.qrc b/res.qrc index bcd04d50b..2dcc10a75 100644 --- a/res.qrc +++ b/res.qrc @@ -118,5 +118,6 @@ ui/volButton/volButtonDisabled.png img/login_logo.svg ui/notificationEdge/notificationEdge.css + ui/chatroomWidgets/circleWidget.css diff --git a/src/widget/circlewidget.cpp b/src/widget/circlewidget.cpp new file mode 100644 index 000000000..b9cc66041 --- /dev/null +++ b/src/widget/circlewidget.cpp @@ -0,0 +1,173 @@ +/* + 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 "circlewidget.hpp" +#include "src/misc/style.h" +#include "src/misc/settings.h" +#include "src/friendlist.h" +#include "src/friend.h" +#include "src/widget/friendwidget.h" +#include +#include +#include + +#include +#include + +#include + +#include + +CircleWidget::CircleWidget(QWidget *parent) + : QFrame(parent) +{ + setProperty("compact", Settings::getInstance().getCompactLayout()); + + setProperty("active", false); + + setStyleSheet(Style::getStylesheet(":/ui/chatroomWidgets/circleWidget.css")); + + QWidget *container = new QWidget(this); + container->setObjectName("circleWidgetContainer"); + container->setProperty("active", false); + mainLayout = new QVBoxLayout(this); + groupLayout = new QVBoxLayout(this); + QHBoxLayout *layout = new QHBoxLayout(); + QVBoxLayout *midLayout = new QVBoxLayout; + QHBoxLayout *topLayout = new QHBoxLayout; + + this->layout()->setSpacing(0); + this->layout()->setMargin(0); + container->setFixedHeight(55); + setLayoutDirection(Qt::LeftToRight); + + midLayout->addStretch(); + + arrowLabel = new QLabel(">", container); + arrowLabel->setPixmap(QPixmap(":/ui/chatArea/scrollBarRightArrow.svg")); + arrowLabel->setStyleSheet("color: white;"); + topLayout->addWidget(arrowLabel); + topLayout->addSpacing(5); + QLabel *nameLabel = new QLabel("Circle", container); + nameLabel->setObjectName("name"); + topLayout->addWidget(nameLabel); + QFrame *lineFrame = new QFrame(container); + lineFrame->setObjectName("line"); + lineFrame->setFrameShape(QFrame::HLine); + topLayout->addSpacing(5); + topLayout->addWidget(lineFrame, 1); + + midLayout->addLayout(topLayout); + + midLayout->addStretch(); + + QHBoxLayout *statusLayout = new QHBoxLayout(); + + QLabel *onlineIconLabel = new QLabel(container); + onlineIconLabel->setAlignment(Qt::AlignCenter); + onlineIconLabel->setPixmap(QPixmap(":img/status/dot_online.svg")); + QLabel *onlineLabel = new QLabel("0", container); + onlineLabel->setObjectName("status"); + + QLabel *awayIconLabel = new QLabel(container); + awayIconLabel->setAlignment(Qt::AlignCenter); + awayIconLabel->setPixmap(QPixmap(":img/status/dot_away.svg")); + QLabel *awayLabel = new QLabel("0", container); + awayLabel->setObjectName("status"); + + QLabel *offlineIconLabel = new QLabel(container); + offlineIconLabel->setAlignment(Qt::AlignCenter); + offlineIconLabel->setPixmap(QPixmap(":img/status/dot_offline.svg")); + QLabel *offlineLabel = new QLabel("0", container); + offlineLabel->setObjectName("status"); + + statusLayout->addWidget(onlineIconLabel); + statusLayout->addSpacing(5); + statusLayout->addWidget(onlineLabel); + statusLayout->addSpacing(10); + statusLayout->addWidget(awayIconLabel); + statusLayout->addSpacing(5); + statusLayout->addWidget(awayLabel); + statusLayout->addSpacing(10); + statusLayout->addWidget(offlineIconLabel); + statusLayout->addSpacing(5); + statusLayout->addWidget(offlineLabel); + statusLayout->addStretch(); + + midLayout->addLayout(statusLayout); + + midLayout->addStretch(); + + layout->addSpacing(10); + layout->addLayout(midLayout); + layout->addSpacing(10); + + container->setLayout(layout); + mainLayout->addWidget(container); + + setAcceptDrops(true); +} + +bool CircleWidget::isCompact() const +{ + return compact; +} + +void CircleWidget::setCompact(bool compact) +{ + this->compact = compact; + Style::repolish(this); +} + +void CircleWidget::toggle() +{ + visible = !visible; + if (visible) + { + mainLayout->addLayout(groupLayout); + arrowLabel->setPixmap(QPixmap(":/ui/chatArea/scrollBarDownArrow.svg")); + } + else + { + mainLayout->removeItem(groupLayout); + arrowLabel->setPixmap(QPixmap(":/ui/chatArea/scrollBarRightArrow.svg")); + } +} + +void CircleWidget::mousePressEvent(QMouseEvent *event) +{ + toggle(); +} + +void CircleWidget::dragEnterEvent(QDragEnterEvent *event) +{ + qDebug() << event->mimeData(); + if (event->mimeData()->hasFormat("friend")) + event->acceptProposedAction(); +} + +void CircleWidget::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); + + groupLayout->addWidget(widget); + } +} diff --git a/src/widget/circlewidget.hpp b/src/widget/circlewidget.hpp new file mode 100644 index 000000000..3bfc9b4c7 --- /dev/null +++ b/src/widget/circlewidget.hpp @@ -0,0 +1,57 @@ +/* + 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 CIRCLEWIDGET_H +#define CIRCLEWIDGET_H + +#include + +class QVBoxLayout; +class QHBoxLayout; +class QLabel; + +class CircleWidget : public QFrame +{ + Q_OBJECT +public: + CircleWidget(QWidget *parent = 0); + + bool isCompact() const; + void setCompact(bool compact); + + void toggle(); + + Q_PROPERTY(bool compact READ isCompact WRITE setCompact) + +protected: + + void mousePressEvent(QMouseEvent *event) override; + + void dragEnterEvent(QDragEnterEvent* event) override; + void dropEvent(QDropEvent* event) override; + +private: + enum FriendLayoutType + { + Online = 0, + Offline = 1 + }; + bool compact, visible = false; + QVBoxLayout *friendLayouts[2]; + QVBoxLayout *groupLayout; + QVBoxLayout *mainLayout; + QLabel *arrowLabel; +}; + +#endif // CIRCLEWIDGET_H diff --git a/src/widget/friendlistwidget.cpp b/src/widget/friendlistwidget.cpp index d28aeef3f..11f690bb7 100644 --- a/src/widget/friendlistwidget.cpp +++ b/src/widget/friendlistwidget.cpp @@ -22,11 +22,14 @@ #include "src/friend.h" #include "src/friendlist.h" #include "src/widget/friendwidget.h" +#include "groupwidget.h" +#include "circlewidget.hpp" +#include FriendListWidget::FriendListWidget(QWidget *parent, bool groupchatPosition) : QWidget(parent) { - mainLayout = new QGridLayout(); + mainLayout = new QVBoxLayout(); setLayout(mainLayout); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); layout()->setSpacing(0); @@ -36,57 +39,102 @@ FriendListWidget::FriendListWidget(QWidget *parent, bool groupchatPosition) : groupLayout->setSpacing(0); groupLayout->setMargin(0); - for (Status s : {Status::Online, Status::Offline}) - { - QVBoxLayout *l = new QVBoxLayout(); - l->setSpacing(0); - l->setMargin(0); + friendLayouts[Online] = new QVBoxLayout(); + friendLayouts[Online]->setSpacing(0); + friendLayouts[Online]->setMargin(0); - layouts[static_cast(s)] = l; - } + friendLayouts[Offline] = new QVBoxLayout(); + friendLayouts[Offline]->setSpacing(0); + friendLayouts[Offline]->setMargin(0); + + circleLayout = new QVBoxLayout(); + circleLayout->setSpacing(0); + circleLayout->setMargin(0); if (groupchatPosition) { - mainLayout->addLayout(groupLayout, 0, 0); - mainLayout->addLayout(layouts[static_cast(Status::Online)], 1, 0); - mainLayout->addLayout(layouts[static_cast(Status::Offline)], 2, 0); + mainLayout->addLayout(groupLayout); + mainLayout->addLayout(friendLayouts[Online]); + mainLayout->addLayout(friendLayouts[Offline]); } else { - mainLayout->addLayout(layouts[static_cast(Status::Online)], 0, 0); - mainLayout->addLayout(groupLayout, 1, 0); - mainLayout->addLayout(layouts[static_cast(Status::Offline)], 2, 0); + mainLayout->addLayout(friendLayouts[Online]); + mainLayout->addLayout(groupLayout); + mainLayout->addLayout(friendLayouts[Offline]); + } + mainLayout->addLayout(circleLayout); +} + +void FriendListWidget::addGroupWidget(GroupWidget *widget) +{ + groupLayout->addWidget(widget); +} + +void FriendListWidget::hideGroups(QString searchString, bool hideAll) +{ + QVBoxLayout* groups = groupLayout; + int groupCount = groups->count(), index; + + for (index = 0; index(groups->itemAt(index)->widget()); + QString groupName = groupWidget->getName(); + + if (!groupName.contains(searchString, Qt::CaseInsensitive) | hideAll) + groupWidget->setVisible(false); + else + groupWidget->setVisible(true); } } -QVBoxLayout* FriendListWidget::getGroupLayout() +void FriendListWidget::addCircleWidget(CircleWidget *widget) { - return groupLayout; + circleLayout->addWidget(widget); +} + +void FriendListWidget::hideFriends(QString searchString, Status status, bool hideAll) +{ + QVBoxLayout* friends = getFriendLayout(status); + int friendCount = friends->count(), index; + + for (index = 0; index(friends->itemAt(index)->widget()); + QString friendName = friendWidget->getName(); + + if (!friendName.contains(searchString, Qt::CaseInsensitive) | hideAll) + friendWidget->setVisible(false); + else + friendWidget->setVisible(true); + } } QVBoxLayout* FriendListWidget::getFriendLayout(Status s) { - auto res = layouts.find(static_cast(s)); - if (res != layouts.end()) - return res.value(); - - return layouts[static_cast(Status::Online)]; + if (s == Status::Offline) + { + return friendLayouts[Offline]; + } + return friendLayouts[Online]; } void FriendListWidget::onGroupchatPositionChanged(bool top) { + mainLayout->removeItem(circleLayout); mainLayout->removeItem(groupLayout); mainLayout->removeItem(getFriendLayout(Status::Online)); if (top) { - mainLayout->addLayout(groupLayout, 0, 0); - mainLayout->addLayout(layouts[static_cast(Status::Online)], 1, 0); + mainLayout->addLayout(groupLayout); + mainLayout->addLayout(friendLayouts[Online]); } else { - mainLayout->addLayout(layouts[static_cast(Status::Online)], 0, 0); - mainLayout->addLayout(groupLayout, 1, 0); + mainLayout->addLayout(friendLayouts[Online]); + mainLayout->addLayout(groupLayout); } + mainLayout->addLayout(circleLayout); } QList FriendListWidget::getAllFriends() @@ -118,20 +166,26 @@ QList FriendListWidget::getAllFriends() 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++) + l->removeWidget(w); // In case the widget is already in this layout. + Friend* g = FriendList::findFriend(static_cast(w)->friendId); + + // Binary search. + int min = 0, max = l->count(), mid; + while (min < max) { - FriendWidget* w1 = static_cast(l->itemAt(i)->widget()); + mid = (max - min) / 2 + min; + FriendWidget* w1 = static_cast(l->itemAt(mid)->widget()); + assert(w1 != nullptr); + Friend* f = FriendList::findFriend(w1->friendId); - if (f->getDisplayedName().localeAwareCompare(g->getDisplayedName()) > 0) - { - l->insertWidget(i,w); - return; - } + int compareValue = f->getDisplayedName().localeAwareCompare(g->getDisplayedName()); + if (compareValue > 0) + max = mid; + else + min = mid + 1; } static_assert(std::is_same(), "The layout must only contain FriendWidget*"); - l->addWidget(w); + l->insertWidget(min, w); } // update widget after add/delete/hide/show diff --git a/src/widget/friendlistwidget.h b/src/widget/friendlistwidget.h index 03dace16d..19e3f8e8f 100644 --- a/src/widget/friendlistwidget.h +++ b/src/widget/friendlistwidget.h @@ -31,12 +31,18 @@ class QGridLayout; class QPixmap; struct FriendWidget; +class GroupWidget; +class CircleWidget; + class FriendListWidget : public QWidget { Q_OBJECT public: explicit FriendListWidget(QWidget *parent = 0, bool groupchatPosition = true); - QVBoxLayout* getGroupLayout(); + void addGroupWidget(GroupWidget *widget); + void hideGroups(QString searchString, bool hideAll = false); + void addCircleWidget(CircleWidget *widget); + void hideFriends(QString searchString, Status status, bool hideAll = false); QVBoxLayout* getFriendLayout(Status s); QList getAllFriends(); @@ -48,9 +54,16 @@ public slots: void moveWidget(FriendWidget *w, Status s); private: - QHash layouts; + enum FriendLayoutType + { + Online = 0, + Offline = 1 + }; + QVBoxLayout *friendLayouts[2]; QVBoxLayout *groupLayout; - QGridLayout *mainLayout; + QVBoxLayout *circleLayout; + QVBoxLayout *mainLayout; + QList circles; }; #endif // FRIENDLISTWIDGET_H diff --git a/src/widget/genericchatroomwidget.h b/src/widget/genericchatroomwidget.h index 89a3d975a..35ed49b4a 100644 --- a/src/widget/genericchatroomwidget.h +++ b/src/widget/genericchatroomwidget.h @@ -21,8 +21,7 @@ #define GENERICCHATROOMWIDGET_H #include -#include -#include +#include #include class CroppingLabel; diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index afc4ddab8..34fa2563d 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -68,6 +68,8 @@ #include #include +#include "circlewidget.hpp" + #ifdef Q_OS_ANDROID #define IS_ON_DESKTOP_GUI 0 #else @@ -155,6 +157,7 @@ void Widget::init() contactListWidget = new FriendListWidget(0, Settings::getInstance().getGroupchatPosition()); ui->friendList->setWidget(contactListWidget); ui->friendList->setLayoutDirection(Qt::RightToLeft); + ui->friendList->setContextMenuPolicy(Qt::CustomContextMenu); ui->statusLabel->setEditable(true); @@ -204,6 +207,7 @@ void Widget::init() connect(offlineMsgTimer, &QTimer::timeout, this, &Widget::processOfflineMsgs); connect(ui->searchContactText, &QLineEdit::textChanged, this, &Widget::searchContacts); connect(ui->searchContactFilterCBox, &QComboBox::currentTextChanged, this, &Widget::searchContacts); + connect(ui->friendList, &QWidget::customContextMenuRequested, this, &Widget::friendListContextMenu); // keyboard shortcuts new QShortcut(Qt::CTRL + Qt::Key_Q, this, SLOT(close())); @@ -1033,8 +1037,9 @@ 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()); + //QLayout* layout = contactListWidget->getGroupLayout(); + //layout->addWidget(newgroup->getGroupWidget()); + contactListWidget->addGroupWidget(newgroup->getGroupWidget()); newgroup->getGroupWidget()->updateStatusLight(); connect(settingsWidget, &SettingsWidget::compactToggled, newgroup->getGroupWidget(), &GenericChatroomWidget::setCompact); @@ -1383,34 +1388,34 @@ void Widget::searchContacts() switch(filter) { case FilterCriteria::All: - hideFriends(searchString, Status::Online); - hideFriends(searchString, Status::Offline); + contactListWidget->hideFriends(searchString, Status::Online); + contactListWidget->hideFriends(searchString, Status::Offline); - hideGroups(searchString); + contactListWidget->hideGroups(searchString); break; case FilterCriteria::Online: - hideFriends(searchString, Status::Online); - hideFriends(QString(), Status::Offline, true); + contactListWidget->hideFriends(searchString, Status::Online); + contactListWidget->hideFriends(QString(), Status::Offline, true); - hideGroups(searchString); + contactListWidget->hideGroups(searchString); break; case FilterCriteria::Offline: - hideFriends(QString(), Status::Online, true); - hideFriends(searchString, Status::Offline); + contactListWidget->hideFriends(QString(), Status::Online, true); + contactListWidget->hideFriends(searchString, Status::Offline); - hideGroups(QString(), true); + contactListWidget->hideGroups(QString(), true); break; case FilterCriteria::Friends: - hideFriends(searchString, Status::Online); - hideFriends(searchString, Status::Offline); + contactListWidget->hideFriends(searchString, Status::Online); + contactListWidget->hideFriends(searchString, Status::Offline); - hideGroups(QString(), true); + contactListWidget->hideGroups(QString(), true); break; case FilterCriteria::Groups: - hideFriends(QString(), Status::Online, true); - hideFriends(QString(), Status::Offline, true); + contactListWidget->hideFriends(QString(), Status::Online, true); + contactListWidget->hideFriends(QString(), Status::Offline, true); - hideGroups(searchString); + contactListWidget->hideGroups(searchString); break; default: return; @@ -1419,37 +1424,15 @@ void Widget::searchContacts() contactListWidget->reDraw(); } -void Widget::hideFriends(QString searchString, Status status, bool hideAll) +void Widget::friendListContextMenu(const QPoint &pos) { - QVBoxLayout* friends = contactListWidget->getFriendLayout(status); - int friendCount = friends->count(), index; + QMenu menu(this); + QAction *addCircleAction = menu.addAction(tr("Add new circle...")); + QAction *chosenAction = menu.exec(ui->friendList->mapToGlobal(pos)); - for (index = 0; index(friends->itemAt(index)->widget()); - QString friendName = friendWidget->getName(); - - if (!friendName.contains(searchString, Qt::CaseInsensitive) || hideAll) - friendWidget->setVisible(false); - else - friendWidget->setVisible(true); - } -} - -void Widget::hideGroups(QString searchString, bool hideAll) -{ - QVBoxLayout* groups = contactListWidget->getGroupLayout(); - int groupCount = groups->count(), index; - - for (index = 0; index(groups->itemAt(index)->widget()); - QString groupName = groupWidget->getName(); - - if (!groupName.contains(searchString, Qt::CaseInsensitive) || hideAll) - groupWidget->setVisible(false); - else - groupWidget->setVisible(true); + contactListWidget->addCircleWidget(new CircleWidget); } } diff --git a/src/widget/widget.h b/src/widget/widget.h index f4451102c..1264a6c25 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -148,8 +148,7 @@ private slots: 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 { diff --git a/ui/chatroomWidgets/circleWidget.css b/ui/chatroomWidgets/circleWidget.css new file mode 100644 index 000000000..d41ccded6 --- /dev/null +++ b/ui/chatroomWidgets/circleWidget.css @@ -0,0 +1,26 @@ +QWidget#circleWidgetContainer > QFrame#line +{ + color: white; +} + +QWidget#circleWidgetContainer +{ + background-color: @themeMedium; +} + +QWidget#circleWidgetContainer:hover +{ + background-color: @themeLight; +} + +QWidget#circleWidgetContainer > QLabel#status +{ + font: @small; + color: @lightGrey; +} + +QWidget#circleWidgetContainer > QLabel#name +{ + font: @big; + color: @white; +}