diff --git a/qtox.pro b/qtox.pro index 9ae5334ba..3477f319b 100644 --- a/qtox.pro +++ b/qtox.pro @@ -492,7 +492,8 @@ SOURCES += \ src/widget/circlewidget.cpp \ src/widget/genericchatitemwidget.cpp \ src/widget/friendlistlayout.cpp \ - src/widget/genericchatitemlayout.cpp + src/widget/genericchatitemlayout.cpp \ + src/widget/categorywidget.cpp HEADERS += \ src/audio/audio.h \ @@ -534,4 +535,5 @@ HEADERS += \ src/widget/circlewidget.h \ src/widget/genericchatitemwidget.h \ src/widget/friendlistlayout.h \ - src/widget/genericchatitemlayout.h + src/widget/genericchatitemlayout.h \ + src/widget/categorywidget.h diff --git a/src/persistence/settings.cpp b/src/persistence/settings.cpp index aeaf83cf5..39cf7b49f 100644 --- a/src/persistence/settings.cpp +++ b/src/persistence/settings.cpp @@ -296,6 +296,7 @@ void Settings::loadPersonnal(Profile* profile) fp.alias = ps.value("alias").toString(); fp.autoAcceptDir = ps.value("autoAcceptDir").toString(); fp.circleID = ps.value("circle", -1).toInt(); + fp.activity = ps.value("activity").toDate(); friendLst[ToxId(fp.addr).publicKey] = fp; } ps.endArray(); @@ -467,6 +468,7 @@ void Settings::savePersonal(QString profileName, QString password) ps.setValue("alias", frnd.alias); ps.setValue("autoAcceptDir", frnd.autoAcceptDir); ps.setValue("circle", frnd.circleID); + ps.setValue("activity", frnd.activity); index++; } ps.endArray(); @@ -1313,6 +1315,35 @@ void Settings::setFriendCircleID(const ToxId &id, int circleID) savePersonal(); } +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}; diff --git a/src/persistence/settings.h b/src/persistence/settings.h index a24425710..26689f109 100644 --- a/src/persistence/settings.h +++ b/src/persistence/settings.h @@ -25,6 +25,7 @@ #include #include #include +#include #include "src/core/corestructs.h" class ToxId; @@ -231,6 +232,9 @@ public: 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; @@ -369,6 +373,7 @@ private: QString addr; QString autoAcceptDir; int circleID = -1; + QDate activity = QDate(); }; struct circleProp diff --git a/src/widget/categorywidget.cpp b/src/widget/categorywidget.cpp new file mode 100644 index 000000000..169b0db62 --- /dev/null +++ b/src/widget/categorywidget.cpp @@ -0,0 +1,309 @@ +/* + 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 . +*/ + +#include "categorywidget.h" +#include "friendlistlayout.h" +#include "friendlistwidget.h" +#include "friendwidget.h" +#include "src/widget/style.h" +#include "tool/croppinglabel.h" +#include +#include + +void emitChatroomWidget(QLayout* layout, int index) +{ + GenericChatroomWidget* chatWidget = dynamic_cast(layout->itemAt(index)->widget()); + if (chatWidget != nullptr) + emit chatWidget->chatroomWidgetClicked(chatWidget); +} + +CategoryWidget::CategoryWidget(QWidget* parent) + : GenericChatItemWidget(parent) +{ + setStyleSheet(Style::getStylesheet(":/ui/chatroomWidgets/circleWidget.css")); + + 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()); +} + +bool CategoryWidget::isExpanded() const +{ + return expanded; +} + +void CategoryWidget::setExpanded(bool isExpanded) +{ + expanded = isExpanded; + listWidget->setVisible(isExpanded); + if (isExpanded) + { + statusPic.setPixmap(QPixmap(":/ui/chatArea/scrollBarDownArrow.svg")); + } + else + { + statusPic.setPixmap(QPixmap(":/ui/chatArea/scrollBarRightArrow.svg")); + } + + onExpand(); +} + +void CategoryWidget::setName(const QString &name) +{ + nameLabel->setText(name); + if (isCompact()) + nameLabel->minimizeMaximumWidth(); + onSetName(); +} + +void CategoryWidget::editName() +{ + nameLabel->editBegin(); + nameLabel->setMaximumWidth(QWIDGETSIZE_MAX); +} + +void CategoryWidget::addFriendWidget(FriendWidget* w, Status s) +{ + listLayout->addFriendWidget(w, s); + updateStatus(); + onAddFriendWidget(w); +} + +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(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(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); + + setCompact(_compact); + + if (isCompact()) + { + 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(55); + 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); +} diff --git a/src/widget/categorywidget.h b/src/widget/categorywidget.h new file mode 100644 index 000000000..0237e0388 --- /dev/null +++ b/src/widget/categorywidget.h @@ -0,0 +1,78 @@ +/* + 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 . +*/ + +#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); + void setName(const QString &name); + + void addFriendWidget(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 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 diff --git a/src/widget/circlewidget.cpp b/src/widget/circlewidget.cpp index fa4973de2..42737ee3d 100644 --- a/src/widget/circlewidget.cpp +++ b/src/widget/circlewidget.cpp @@ -1,26 +1,31 @@ /* + Copyright © 2015 by The qTox Project + 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 + 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. - 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. + 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 . */ #include "circlewidget.h" +#include "friendwidget.h" +#include "friendlistwidget.h" +#include "tool/croppinglabel.h" #include "src/widget/style.h" + #include "src/persistence/settings.h" #include "src/friendlist.h" #include "src/friend.h" -#include "friendwidget.h" -#include "friendlistlayout.h" -#include "friendlistwidget.h" -#include "src/widget/tool/croppinglabel.h" #include "widget.h" #include #include @@ -33,43 +38,10 @@ QHash CircleWidget::circleList; -CircleWidget::CircleWidget(FriendListWidget *parent, int id_) - : GenericChatItemWidget(parent) +CircleWidget::CircleWidget(FriendListWidget* parent, int id_) + : CategoryWidget(parent) , id(id_) { - setStyleSheet(Style::getStylesheet(":/ui/chatroomWidgets/circleWidget.css")); - - container = new QWidget(this); - container->setObjectName("circleWidgetContainer"); - container->setLayoutDirection(Qt::LeftToRight); - - // status text - 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()); - if (id != -1) { setName(Settings::getInstance().getCircleName(id)); @@ -102,159 +74,12 @@ CircleWidget::CircleWidget(FriendListWidget *parent, int id_) circleList[id] = this; if (isNew) - renameCircle(); + editName(); setExpanded(Settings::getInstance().getCircleExpanded(id)); - updateStatus(); } -void CircleWidget::addFriendWidget(FriendWidget *w, Status s) -{ - listLayout->addFriendWidget(w, s); - updateStatus(); - Settings::getInstance().setFriendCircleID(FriendList::findFriend(w->friendId)->getToxId(), id); -} - -void CircleWidget::setExpanded(bool isExpanded) -{ - expanded = isExpanded; - listWidget->setVisible(isExpanded); - if (isExpanded) - { - statusPic.setPixmap(QPixmap(":/ui/chatArea/scrollBarDownArrow.svg")); - } - else - { - statusPic.setPixmap(QPixmap(":/ui/chatArea/scrollBarRightArrow.svg")); - } - - Settings::getInstance().setCircleExpanded(id, isExpanded); -} - -void CircleWidget::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()); -} - -void CircleWidget::setName(const QString &name) -{ - nameLabel->setText(name); - Settings::getInstance().setCircleName(id, name); - if (isCompact()) - nameLabel->minimizeMaximumWidth(); -} - -void CircleWidget::renameCircle() -{ - nameLabel->editBegin(); - nameLabel->setMaximumWidth(QWIDGETSIZE_MAX); -} - -void emitChatroomWidget(QLayout* layout, int index) -{ - GenericChatroomWidget* chatWidget = dynamic_cast(layout->itemAt(index)->widget()); - if (chatWidget != nullptr) - emit chatWidget->chatroomWidgetClicked(chatWidget); -} - -bool CircleWidget::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 CircleWidget::cycleContacts(FriendWidget *activeChatroomWidget, bool forward) -{ - int index = -1; - QLayout* currentLayout = nullptr; - - FriendWidget* friendWidget = dynamic_cast(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(currentLayout->itemAt(index)->widget()); - if (chatWidget != nullptr) - emit chatWidget->chatroomWidgetClicked(chatWidget); - return true; - } - - return false; -} - CircleWidget* CircleWidget::getFromID(int id) { auto circleIt = circleList.find(id); @@ -263,77 +88,19 @@ CircleWidget* CircleWidget::getFromID(int id) return nullptr; } -void CircleWidget::onCompactChanged(bool _compact) -{ - delete topLayout; - delete mainLayout; - - topLayout = new QHBoxLayout; - topLayout->setSpacing(0); - topLayout->setMargin(0); - - setProperty("compact", _compact); - - if (property("compact").toBool()) - { - 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(55); - 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 CircleWidget::contextMenuEvent(QContextMenuEvent *event) +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* 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())); + QAction* selectedItem = menu.exec(mapToGlobal(event->pos())); if (selectedItem == renameAction) - renameCircle(); + editName(); else if (selectedItem == removeAction) { FriendListWidget* friendList = dynamic_cast(parentWidget()); - listLayout->moveFriendWidgets(friendList); + moveFriendWidgets(friendList); friendList->removeCircleWidget(this); @@ -344,43 +111,36 @@ void CircleWidget::contextMenuEvent(QContextMenuEvent *event) if (circleReplace != circleList.end()) circleReplace.value()->updateID(id); } + setContainerAttribute(Qt::WA_UnderMouse, false); } -void CircleWidget::mouseReleaseEvent(QMouseEvent *event) -{ - if (event->button() == Qt::LeftButton) - setExpanded(!expanded); -} - -void CircleWidget::dragEnterEvent(QDragEnterEvent *event) +void CircleWidget::dragEnterEvent(QDragEnterEvent* event) { if (event->mimeData()->hasFormat("friend")) event->acceptProposedAction(); - container->setAttribute(Qt::WA_UnderMouse, true); // Simulate hover. - Style::repolish(container); + setContainerAttribute(Qt::WA_UnderMouse, true); // Simulate hover. } -void CircleWidget::dragLeaveEvent(QDragLeaveEvent *) +void CircleWidget::dragLeaveEvent(QDragLeaveEvent* ) { - container->setAttribute(Qt::WA_UnderMouse, false); - Style::repolish(container); + setContainerAttribute(Qt::WA_UnderMouse, false); } -void CircleWidget::dropEvent(QDropEvent *event) +void CircleWidget::dropEvent(QDropEvent* event) { if (event->mimeData()->hasFormat("friend")) { setExpanded(true); int friendId = event->mimeData()->data("friend").toInt(); - Friend *f = FriendList::findFriend(friendId); + Friend* f = FriendList::findFriend(friendId); assert(f != nullptr); - FriendWidget *widget = f->getFriendWidget(); + FriendWidget* widget = f->getFriendWidget(); assert(widget != nullptr); // Update old circle after moved. - CircleWidget *circleWidget = getFromID(Settings::getInstance().getFriendCircleID(f->getToxId())); + CircleWidget* circleWidget = getFromID(Settings::getInstance().getFriendCircleID(f->getToxId())); addFriendWidget(widget, f->getStatus()); @@ -390,14 +150,23 @@ void CircleWidget::dropEvent(QDropEvent *event) Widget::getInstance()->searchCircle(circleWidget); } - container->setAttribute(Qt::WA_UnderMouse, false); - Style::repolish(container); + setContainerAttribute(Qt::WA_UnderMouse, false); } } -void CircleWidget::updateStatus() +void CircleWidget::onSetName() { - statusLabel->setText(QString::number(listLayout->friendOnlineCount()) + QStringLiteral(" / ") + QString::number(listLayout->friendTotalCount())); + Settings::getInstance().setCircleName(id, getName()); +} + +void CircleWidget::onExpand() +{ + Settings::getInstance().setCircleExpanded(id, isExpanded()); +} + +void CircleWidget::onAddFriendWidget(FriendWidget* w) +{ + Settings::getInstance().setFriendCircleID(FriendList::findFriend(w->friendId)->getToxId(), id); } void CircleWidget::updateID(int index) @@ -407,17 +176,17 @@ void CircleWidget::updateID(int index) id = index; circleList[id] = this; - for (int i = 0; i < listLayout->getLayoutOnline()->count(); ++i) + for (int i = 0; i < friendOnlineLayout()->count(); ++i) { - FriendWidget* friendWidget = dynamic_cast(listLayout->getLayoutOnline()->itemAt(i)); + FriendWidget* friendWidget = dynamic_cast(friendOnlineLayout()->itemAt(i)); if (friendWidget != nullptr) { Settings::getInstance().setFriendCircleID(FriendList::findFriend(friendWidget->friendId)->getToxId(), id); } } - for (int i = 0; i < listLayout->getLayoutOffline()->count(); ++i) + for (int i = 0; i < friendOfflineLayout()->count(); ++i) { - FriendWidget* friendWidget = dynamic_cast(listLayout->getLayoutOffline()->itemAt(i)); + FriendWidget* friendWidget = dynamic_cast(friendOfflineLayout()->itemAt(i)); if (friendWidget != nullptr) { Settings::getInstance().setFriendCircleID(FriendList::findFriend(friendWidget->friendId)->getToxId(), id); diff --git a/src/widget/circlewidget.h b/src/widget/circlewidget.h index 9c3834637..7c8fd9ef4 100644 --- a/src/widget/circlewidget.h +++ b/src/widget/circlewidget.h @@ -1,79 +1,52 @@ /* + Copyright © 2015 by The qTox Project + 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 + 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. - 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. + 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 . */ #ifndef CIRCLEWIDGET_H #define CIRCLEWIDGET_H -#include "genericchatitemwidget.h" -#include "src/core/corestructs.h" +#include "categorywidget.h" -class QVBoxLayout; -class QHBoxLayout; -class QLabel; -class FriendListWidget; -class FriendListLayout; -class FriendWidget; - -class CircleWidget final : public GenericChatItemWidget +class CircleWidget final : public CategoryWidget { Q_OBJECT public: CircleWidget(FriendListWidget* parent = 0, int id = -1); - void addFriendWidget(FriendWidget* w, Status s); - - - void expand(); - void setExpanded(bool isExpanded); - - void updateStatus(); - - void setName(const QString &name); - void renameCircle(); - - bool cycleContacts(bool forward); - bool cycleContacts(FriendWidget* activeChatroomWidget, bool forward); - void search(const QString &searchString, bool updateAll = false, bool hideOnline = false, bool hideOffline = false); - static CircleWidget* getFromID(int id); signals: void renameRequested(CircleWidget* circleWidget, const QString &newName); -public slots: - void onCompactChanged(bool compact); - protected: virtual void contextMenuEvent(QContextMenuEvent* event) final override; - virtual void mouseReleaseEvent(QMouseEvent* 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 circleList; int id; - bool expanded = false; - FriendListLayout* listLayout; - QVBoxLayout* fullLayout; - QVBoxLayout* mainLayout = nullptr; - QLabel* statusLabel; - QFrame* lineFrame; - QWidget* container; - QHBoxLayout* topLayout = nullptr; - QWidget* listWidget; }; #endif // CIRCLEWIDGET_H diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index ca74013a7..1e427dd7e 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -1048,6 +1048,10 @@ void ChatForm::SendMessageStr(QString msg) getOfflineMsgEngine()->registerReceipt(rec, id, ma); msgEdit->setLastMessage(msg); //set last message only when sending it + + QDate date = Settings::getInstance().getFriendActivity(f->getToxId()); + if (date != QDate::currentDate()) + Settings::getInstance().setFriendActivity(f->getToxId(), QDate::currentDate()); } } diff --git a/src/widget/friendlistwidget.cpp b/src/widget/friendlistwidget.cpp index 1e31dbae5..bd09f72b6 100644 --- a/src/widget/friendlistwidget.cpp +++ b/src/widget/friendlistwidget.cpp @@ -32,6 +32,84 @@ #include #include +enum Time : int +{ + Today = 0, + Yesterday = 1, + ThisWeek = 2, + ThisMonth = 3, + Month1Ago = 4, + Month2Ago = 5, + Month3Ago = 6, + Month4Ago = 7, + Month5Ago = 8, + LongAgo = 9, + Never = 10 +}; + +bool last7DaysWasLastMonth() +{ + return QDate::currentDate().addDays(-7).month() == QDate::currentDate().month(); +} + +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()) + { + if (date >= today) + return ThisMonth; + today = today.addMonths(-1); + } + + 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; +} + +int toIndex(Time time) +{ + if (time < ThisMonth) + return time; + + if (!last7DaysWasLastMonth()) + return time - 1; + + return time; +} + FriendListWidget::FriendListWidget(Widget* parent, bool groupsOnTop) : QWidget(parent) , groupsOnTop(groupsOnTop) @@ -46,13 +124,104 @@ FriendListWidget::FriendListWidget(Widget* parent, bool groupsOnTop) groupLayout.getLayout()->setSpacing(0); groupLayout.getLayout()->setMargin(0); - listLayout->addLayout(circleLayout.getLayout()); + setMode(Name); onGroupchatPositionChanged(groupsOnTop); setAcceptDrops(true); } +void FriendListWidget::setMode(Mode mode) +{ + this->mode = mode; + if (mode == Name) + { + for (int i = 0; i < Settings::getInstance().getCircleCount(); ++i) + { + addCircleWidget(i); + } + + listLayout->addLayout(circleLayout.getLayout()); + } + else if (mode == Activity) + { + activityLayout = new QVBoxLayout(this); + + CategoryWidget* categoryToday = new CategoryWidget(this); + 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("Never", "Category for sorting friends by activity")); + activityLayout->addWidget(categoryNever); + + QList friendList = FriendList::getAllFriends(); + for (Friend* contact : friendList) + { + QDate activityDate = Settings::getInstance().getFriendActivity(contact->getToxId()); + Time time = getTime(activityDate); + CategoryWidget* categoryWidget = dynamic_cast(activityLayout->itemAt(time)->widget()); + categoryWidget->addFriendWidget(contact->getFriendWidget(), contact->getStatus()); + } + + for (int i = 0; i < activityLayout->count(); ++i) + { + CategoryWidget* categoryWidget = dynamic_cast(activityLayout->itemAt(i)->widget()); + categoryWidget->setVisible(categoryWidget->hasChatrooms()); + } + + listLayout->removeItem(listLayout->getLayoutOnline()); + listLayout->removeItem(listLayout->getLayoutOffline()); + listLayout->removeItem(circleLayout.getLayout()); + listLayout->insertLayout(0, activityLayout); + } +} + void FriendListWidget::addGroupWidget(GroupWidget* widget) { groupLayout.addSortedWidget(widget); @@ -80,7 +249,7 @@ void FriendListWidget::addCircleWidget(int id) void FriendListWidget::addCircleWidget(FriendWidget* friendWidget) { CircleWidget* circleWidget = createCircleWidget(); - if (friendWidget != nullptr) + if (circleWidget != nullptr && friendWidget != nullptr) { CircleWidget* circleOriginal = CircleWidget::getFromID(Settings::getInstance().getFriendCircleID(FriendList::findFriend(friendWidget->friendId)->getToxId())); @@ -273,18 +442,29 @@ void FriendListWidget::dropEvent(QDropEvent* event) void FriendListWidget::moveWidget(FriendWidget* w, Status s, bool add) { - int circleId = Settings::getInstance().getFriendCircleID(FriendList::findFriend(w->friendId)->getToxId()); - CircleWidget* circleWidget = CircleWidget::getFromID(circleId); - - if (circleWidget == nullptr || add) + if (mode == Name) { - if (circleId != -1) - Settings::getInstance().setFriendCircleID(FriendList::findFriend(w->friendId)->getToxId(), -1); - listLayout->addFriendWidget(w, s); - return; - } + int circleId = Settings::getInstance().getFriendCircleID(FriendList::findFriend(w->friendId)->getToxId()); + CircleWidget* circleWidget = CircleWidget::getFromID(circleId); - circleWidget->addFriendWidget(w, s); + 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 = Settings::getInstance().getFriendActivity(contact->getToxId()); + Time time = getTime(activityDate); + CategoryWidget* categoryWidget = dynamic_cast(activityLayout->itemAt(time)->widget()); + categoryWidget->addFriendWidget(contact->getFriendWidget(), contact->getStatus()); + } } // update widget after add/delete/hide/show @@ -297,6 +477,15 @@ void FriendListWidget::reDraw() CircleWidget* FriendListWidget::createCircleWidget(int id) { + if (id == -1) + { + id = Settings::getInstance().addCircle(); + Settings::getInstance().setCircleName(id, tr("Circle #%1").arg(id + 1)); + } + + if (mode == Activity) + return nullptr; + CircleWidget* circleWidget = new CircleWidget(this, id); circleLayout.addSortedWidget(circleWidget); connect(this, &FriendListWidget::onCompactChanged, circleWidget, &CircleWidget::onCompactChanged); diff --git a/src/widget/friendlistwidget.h b/src/widget/friendlistwidget.h index 189eee263..13b258fe3 100644 --- a/src/widget/friendlistwidget.h +++ b/src/widget/friendlistwidget.h @@ -38,7 +38,14 @@ class FriendListWidget : public QWidget { Q_OBJECT public: + enum Mode : uint8_t + { + Name, + Activity, + }; + explicit FriendListWidget(Widget* parent, bool groupsOnTop = true); + void setMode(Mode mode); void addGroupWidget(GroupWidget* widget); void addFriendWidget(FriendWidget* w, Status s, int circleIndex); @@ -69,10 +76,12 @@ private: CircleWidget* createCircleWidget(int id = -1); QLayout* nextLayout(QLayout* layout, bool forward) const; + Mode mode; bool groupsOnTop; FriendListLayout* listLayout; GenericChatItemLayout circleLayout; GenericChatItemLayout groupLayout; + QVBoxLayout* activityLayout; }; #endif // FRIENDLISTWIDGET_H diff --git a/src/widget/genericchatitemwidget.cpp b/src/widget/genericchatitemwidget.cpp index 50f672ec8..05f0293ba 100644 --- a/src/widget/genericchatitemwidget.cpp +++ b/src/widget/genericchatitemwidget.cpp @@ -1,15 +1,20 @@ /* + Copyright © 2015 by The qTox Project + 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 + 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. - 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. + 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 . */ #include "genericchatitemwidget.h" diff --git a/src/widget/genericchatitemwidget.h b/src/widget/genericchatitemwidget.h index 73218d4de..3d03a4dd6 100644 --- a/src/widget/genericchatitemwidget.h +++ b/src/widget/genericchatitemwidget.h @@ -1,15 +1,20 @@ /* + Copyright © 2015 by The qTox Project + 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 + 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. - 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. + 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 . */ #ifndef GENERICCHATITEMWIDGET_H diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 07d634356..082761bc2 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -234,11 +234,6 @@ void Widget::init() if (!Settings::getInstance().getShowSystemTray()) show(); - - for (int i = 0; i < Settings::getInstance().getCircleCount(); ++i) - { - contactListWidget->addCircleWidget(i); - } } bool Widget::eventFilter(QObject *obj, QEvent *event) @@ -775,6 +770,11 @@ void Widget::onFriendMessageReceived(int friendId, const QString& message, bool windowTitle += " (" + f->getFriendWidget()->getStatusString() + ")"; setWindowTitle(windowTitle); } + + QDate activity = Settings::getInstance().getFriendActivity(f->getToxId()); + qDebug() << "YOLOOLOLOLO" << activity; + if (activity != QDate::currentDate()) + Settings::getInstance().setFriendActivity(f->getToxId(), QDate::currentDate()); } void Widget::onReceiptRecieved(int friendId, int receipt) @@ -1468,12 +1468,17 @@ void Widget::friendListContextMenu(const QPoint &pos) { QMenu menu(this); QAction *addCircleAction = menu.addAction(tr("Add new circle...")); + QAction *switchMode = menu.addAction("Switch to Recent (BETA)"); QAction *chosenAction = menu.exec(ui->friendList->mapToGlobal(pos)); if (chosenAction == addCircleAction) { contactListWidget->addCircleWidget(); } + else if (chosenAction == switchMode) + { + contactListWidget->setMode(FriendListWidget::Activity); + } } void Widget::setActiveToolMenuButton(ActiveToolMenuButton newActiveButton)