diff --git a/CMakeLists.txt b/CMakeLists.txt index e39890543..8dfc0ccee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -277,6 +277,9 @@ set(${PROJECT_NAME}_SOURCES src/model/dialogs/idialogs.h src/model/exiftransform.cpp src/model/exiftransform.h + src/model/friendlist/friendlistmanager.cpp + src/model/friendlist/friendlistmanager.h + src/model/friendlist/ifriendlistitem.h src/model/friendmessagedispatcher.cpp src/model/friendmessagedispatcher.h src/model/friend.cpp diff --git a/src/model/friendlist/friendlistmanager.cpp b/src/model/friendlist/friendlistmanager.cpp new file mode 100644 index 000000000..7b3b9652c --- /dev/null +++ b/src/model/friendlist/friendlistmanager.cpp @@ -0,0 +1,215 @@ +/* + Copyright © 2021 by The qTox Project Contributors + + 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 "friendlistmanager.h" +#include "src/widget/genericchatroomwidget.h" + +FriendListManager::FriendListManager(QObject *parent) : QObject(parent) +{ + +} + +QVector FriendListManager::getItems() const +{ + return items; +} + +bool FriendListManager::needHideCircles() const +{ + return hideCircles; +} + +void FriendListManager::addFriendListItem(IFriendListItem *item) +{ + if (item->isGroup()) { + items.push_back(IFriendListItemPtr(item, [](IFriendListItem* groupItem){ + groupItem->getWidget()->deleteLater();})); + } else { + items.push_back(IFriendListItemPtr(item)); + } + + updatePositions(); + emit itemsChanged(); +} + +void FriendListManager::removeFriendListItem(IFriendListItem *item) +{ + removeAll(item); + updatePositions(); + emit itemsChanged(); +} + +void FriendListManager::sortByName() +{ + byName = true; + updatePositions(); +} + +void FriendListManager::sortByActivity() +{ + byName = false; + updatePositions(); +} + +void FriendListManager::resetParents() +{ + for (int i = 0; i < items.size(); ++i) { + IFriendListItem* itemTmp = items[i].get(); + itemTmp->getWidget()->setParent(nullptr); + } +} + +void FriendListManager::setFilter(const QString &searchString, bool hideOnline, bool hideOffline, + bool hideGroups) +{ + if (filterParams.searchString == searchString && filterParams.hideOnline == hideOnline && + filterParams.hideOffline == hideOffline && filterParams.hideGroups == hideGroups) { + return; + } + filterParams.searchString = searchString; + filterParams.hideOnline = hideOnline; + filterParams.hideOffline = hideOffline; + filterParams.hideGroups = hideGroups; + + emit itemsChanged(); +} + +void FriendListManager::applyFilter() +{ + QString searchString = filterParams.searchString; + + for (IFriendListItemPtr itemTmp : items) { + if (searchString.isEmpty()) { + itemTmp->getWidget()->setVisible(true); + } else { + QString tmp_name = itemTmp->getNameItem(); + itemTmp->getWidget()->setVisible(tmp_name.contains(searchString, Qt::CaseInsensitive)); + } + + if (filterParams.hideOnline && itemTmp->isOnline()) { + if (itemTmp->isFriend()) { + itemTmp->getWidget()->setVisible(false); + } + } + + if (filterParams.hideOffline && !itemTmp->isOnline()) { + itemTmp->getWidget()->setVisible(false); + } + + if (filterParams.hideGroups && itemTmp->isGroup()) { + itemTmp->getWidget()->setVisible(false); + } + } + + if (filterParams.hideOnline && filterParams.hideOffline) { + hideCircles = true; + } else { + hideCircles = false; + } +} + +void FriendListManager::updatePositions() +{ + if (byName) { + std::sort(items.begin(), items.end(), + [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) { + return cmpByName(a, b, groupsOnTop); + } + ); + } else { + std::sort(items.begin(), items.end(), + [&](const IFriendListItemPtr &a, const IFriendListItemPtr &b) { + return cmpByActivity(a, b); + } + ); + } +} + +void FriendListManager::setGroupsOnTop(bool v) +{ + groupsOnTop = v; +} + +void FriendListManager::removeAll(IFriendListItem* item) +{ + for (int i = 0; i < items.size(); ++i) { + if (items[i].get() == item) { + items.remove(i); + --i; + } + } +} + +bool FriendListManager::cmpByName(const IFriendListItemPtr &a, const IFriendListItemPtr &b, + bool groupsOnTop) +{ + if (a->isGroup() && !b->isGroup()) { + if (groupsOnTop) { + return true; + } + return !b->isOnline(); + } + + if (!a->isGroup() && b->isGroup()) { + if (groupsOnTop) { + return false; + } + return a->isOnline(); + } + + if (a->isOnline() && !b->isOnline()) { + return true; + } + + if (!a->isOnline() && b->isOnline()) { + return false; + } + + return a->getNameItem().toUpper() < b->getNameItem().toUpper(); +} + +bool FriendListManager::cmpByActivity(const IFriendListItemPtr &a, const IFriendListItemPtr &b) +{ + if (a->isGroup() || b->isGroup()) { + if (a->isGroup() && !b->isGroup()) { + return true; + } + + if (!a->isGroup() && b->isGroup()) { + return false; + } + return a->getNameItem().toUpper() < b->getNameItem().toUpper(); + } + + QDateTime dateA = a->getLastActivity(); + QDateTime dateB = b->getLastActivity(); + if (dateA.date() == dateB.date()) { + if (a->isOnline() && !b->isOnline()) { + return true; + } + + if (!a->isOnline() && b->isOnline()) { + return false; + } + return a->getNameItem().toUpper() < b->getNameItem().toUpper(); + } + + return a->getLastActivity() > b->getLastActivity(); +} + diff --git a/src/model/friendlist/friendlistmanager.h b/src/model/friendlist/friendlistmanager.h new file mode 100644 index 000000000..a3c3dd2b9 --- /dev/null +++ b/src/model/friendlist/friendlistmanager.h @@ -0,0 +1,72 @@ +/* + Copyright © 2021 by The qTox Project Contributors + + 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 . +*/ + +#pragma once + +#include "ifriendlistitem.h" + +#include +#include + +#include + +class FriendListManager : public QObject +{ + Q_OBJECT +public: + using IFriendListItemPtr = std::shared_ptr; + + explicit FriendListManager(QObject *parent = nullptr); + + QVector getItems() const; + bool needHideCircles() const; + + void addFriendListItem(IFriendListItem* item); + void removeFriendListItem(IFriendListItem* item); + void sortByName(); + void sortByActivity(); + void resetParents(); + void setFilter(const QString& searchString, bool hideOnline, + bool hideOffline, bool hideGroups); + void applyFilter(); + void updatePositions(); + + void setGroupsOnTop(bool v); + +signals: + void itemsChanged(); + +private: + struct FilterParams { + QString searchString = ""; + bool hideOnline = false; + bool hideOffline = false; + bool hideGroups = false; + } filterParams; + + void removeAll(IFriendListItem*); + bool cmpByName(const IFriendListItemPtr&, const IFriendListItemPtr&, bool groupsOnTop); + bool cmpByActivity(const IFriendListItemPtr&, const IFriendListItemPtr&); + + bool byName = true; + bool hideCircles = false; + bool groupsOnTop; + QVector items; + +}; diff --git a/src/model/friendlist/ifriendlistitem.h b/src/model/friendlist/ifriendlistitem.h new file mode 100644 index 000000000..e74b746c5 --- /dev/null +++ b/src/model/friendlist/ifriendlistitem.h @@ -0,0 +1,56 @@ +/* + Copyright © 2021 by The qTox Project Contributors + + 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 . +*/ + +#pragma once + +#include + +class QWidget; + +class IFriendListItem +{ +public: + + virtual ~IFriendListItem() = default; + + virtual bool isFriend() const = 0; + virtual bool isGroup() const = 0; + virtual bool isOnline() const = 0; + virtual QString getNameItem() const = 0; + virtual QDateTime getLastActivity() const = 0; + virtual QWidget* getWidget() = 0; + + virtual int getCircleId() const + { + return -1; + } + + int getNameSortedPos() const + { + return nameSortedPos; + } + + void setNameSortedPos(int pos) + { + nameSortedPos = pos; + } + +private: + int nameSortedPos = -1; +}; diff --git a/src/widget/categorywidget.cpp b/src/widget/categorywidget.cpp index d0c3129c6..5120892a6 100644 --- a/src/widget/categorywidget.cpp +++ b/src/widget/categorywidget.cpp @@ -87,7 +87,12 @@ void CategoryWidget::setExpanded(bool isExpanded, bool save) } expanded = isExpanded; setMouseTracking(true); + + // The listWidget will recieve a enterEvent for some reason if now visible. + // Using the following, we prevent that. + listWidget->setAttribute(Qt::WA_TransparentForMouseEvents, true); listWidget->setVisible(isExpanded); + listWidget->setAttribute(Qt::WA_TransparentForMouseEvents, false); QString pixmapPath; if (isExpanded) @@ -95,11 +100,6 @@ void CategoryWidget::setExpanded(bool isExpanded, bool save) else pixmapPath = Style::getImagePath("chatArea/scrollBarRightArrow.svg"); statusPic.setPixmap(QPixmap(pixmapPath)); - // 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(); diff --git a/src/widget/circlewidget.cpp b/src/widget/circlewidget.cpp index 0f3c8c629..cca859905 100644 --- a/src/widget/circlewidget.cpp +++ b/src/widget/circlewidget.cpp @@ -187,7 +187,6 @@ void CircleWidget::dropEvent(QDropEvent* event) if (circleWidget != nullptr) { circleWidget->updateStatus(); - emit searchCircle(*circleWidget); } setContainerAttribute(Qt::WA_UnderMouse, false); diff --git a/src/widget/circlewidget.h b/src/widget/circlewidget.h index 30673879b..bb83b21e5 100644 --- a/src/widget/circlewidget.h +++ b/src/widget/circlewidget.h @@ -36,7 +36,6 @@ public: signals: void renameRequested(CircleWidget* circleWidget, const QString& newName); - void searchCircle(CircleWidget& circletWidget); void newContentDialog(ContentDialog& contentDialog); protected: diff --git a/src/widget/friendlistwidget.cpp b/src/widget/friendlistwidget.cpp index 71617badd..e358e8676 100644 --- a/src/widget/friendlistwidget.cpp +++ b/src/widget/friendlistwidget.cpp @@ -19,7 +19,6 @@ #include "friendlistwidget.h" #include "circlewidget.h" -#include "friendlistlayout.h" #include "friendwidget.h" #include "groupwidget.h" #include "widget.h" @@ -27,6 +26,7 @@ #include "src/model/friend.h" #include "src/model/group.h" #include "src/model/status.h" +#include "src/model/friendlist/friendlistmanager.h" #include "src/persistence/settings.h" #include "src/widget/categorywidget.h" @@ -99,25 +99,19 @@ qint64 timeUntilTomorrow() FriendListWidget::FriendListWidget(const Core &_core, Widget* parent, bool groupsOnTop) : QWidget(parent) - , groupsOnTop(groupsOnTop) , core{_core} { - listLayout = new FriendListLayout(); + manager = new FriendListManager(this); + manager->setGroupsOnTop(groupsOnTop); + connect(manager, &FriendListManager::itemsChanged, this, &FriendListWidget::itemsChanged); + + listLayout = new QVBoxLayout; 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()); + listLayout->setSpacing(0); + listLayout->setMargin(0); mode = Settings::getInstance().getFriendSortingMode(); - sortByMode(mode); - if (mode != SortingMode::Name) { - listLayout->insertLayout(0, groupLayout.getLayout()); - } dayTimer = new QTimer(this); dayTimer->setTimerType(Qt::VeryCoarseTimer); @@ -129,22 +123,9 @@ FriendListWidget::FriendListWidget(const Core &_core, Widget* parent, bool group FriendListWidget::~FriendListWidget() { - if (activityLayout != nullptr) { - 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; + for (int i = 0; i < Settings::getInstance().getCircleCount(); ++i) { + CircleWidget* circle = CircleWidget::getFromID(i); + delete circle; } } @@ -161,47 +142,65 @@ void FriendListWidget::setMode(SortingMode mode) void FriendListWidget::sortByMode(SortingMode mode) { + cleanMainLayout(); + if (mode == SortingMode::Name) { - circleLayout = new GenericChatItemLayout; - circleLayout->getLayout()->setSpacing(0); - circleLayout->getLayout()->setMargin(0); + manager->sortByName(); 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); + QVector> itemsTmp = manager->getItems(); // Sorted items + QVector friendItems; // Items that are not included in the circle + int posByName = 0; // Needed for scroll contacts + // Linking a friend with a circle and setting scroll position + for (int i = 0; i < itemsTmp.size(); ++i) { + if (itemsTmp[i]->isFriend() && itemsTmp[i]->getCircleId() >= 0) { + CircleWidget* circleWgt = CircleWidget::getFromID(itemsTmp[i]->getCircleId()); + if (circleWgt != nullptr) { + // Place a friend in the circle and continue + FriendWidget* frndTmp = + qobject_cast((itemsTmp[i].get())->getWidget()); + circleWgt->addFriendWidget(frndTmp, frndTmp->getFriend()->getStatus()); + continue; + } + } + // Place the item without the circle in the vector and set the position + itemsTmp[i]->setNameSortedPos(posByName++); + friendItems.push_back(itemsTmp[i].get()); + } - int count = activityLayout ? activityLayout->count() : 0; - for (int i = 0; i < count; i++) { - QWidget* widget = activityLayout->itemAt(i)->widget(); - CategoryWidget* categoryWidget = qobject_cast(widget); - if (categoryWidget) { - categoryWidget->moveFriendWidgets(this); - } else { - qWarning() << "Unexpected widget"; + // Add groups and friends without circles + for (int i = 0; i < friendItems.size(); ++i) { + listLayout->addWidget(friendItems[i]->getWidget()); + } + + // TODO: Try to remove + manager->applyFilter(); + + if (!manager->needHideCircles()) { + //Sorts circles alphabetically and adds them to the layout + QVector circles; + for (int i = 0; i < Settings::getInstance().getCircleCount(); ++i) { + circles.push_back(CircleWidget::getFromID(i)); + } + + std::sort(circles.begin(), circles.end(), + [](CircleWidget* a, CircleWidget* b) { + return a->getName().toUpper() < b->getName().toUpper(); + }); + + for (int i = 0; i < circles.size(); ++i) { + + QVector> itemsInCircle = getItemsFromCircle(circles.at(i)); + for (int i = 0; i < itemsInCircle.size(); ++i) { + itemsInCircle.at(i)->setNameSortedPos(posByName++); + } + + listLayout->addWidget(circles.at(i)); } } - - 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 == SortingMode::Activity) { QLocale ql(Settings::getInstance().getTranslation()); QDate today = QDate::currentDate(); @@ -223,6 +222,13 @@ void FriendListWidget::sortByMode(SortingMode mode) // clang-format on #undef COMMENT + manager->sortByActivity(); + QVector> itemsTmp = manager->getItems(); + + for (int i = 0; i < itemsTmp.size(); ++i) { + listLayout->addWidget(itemsTmp[i]->getWidget()); + } + activityLayout = new QVBoxLayout(); bool compact = Settings::getInstance().getCompactLayout(); for (Time t : names.keys()) { @@ -231,53 +237,89 @@ void FriendListWidget::sortByMode(SortingMode mode) activityLayout->addWidget(category); } - moveFriends(listLayout->getLayoutOffline()); - moveFriends(listLayout->getLayoutOnline()); - if (circleLayout != nullptr) { - moveFriends(circleLayout->getLayout()); + // TODO: Try to remove + manager->applyFilter(); + + // Insert widgets to CategoryWidget + for (int i = 0; i < itemsTmp.size(); ++i) { + if (itemsTmp[i]->isFriend()) { + int timeIndex = static_cast(getTimeBucket(itemsTmp[i]->getLastActivity())); + QWidget* widget = activityLayout->itemAt(timeIndex)->widget(); + CategoryWidget* categoryWidget = qobject_cast(widget); + FriendWidget* frnd = qobject_cast((itemsTmp[i].get())->getWidget()); + if (!isVisible() || (isVisible() && frnd->isVisible())) { + categoryWidget->addFriendWidget(frnd, frnd->getFriend()->getStatus()); + } + } } + //Hide empty categories for (int i = 0; i < activityLayout->count(); ++i) { QWidget* widget = activityLayout->itemAt(i)->widget(); CategoryWidget* categoryWidget = qobject_cast(widget); categoryWidget->setVisible(categoryWidget->hasChatrooms()); } - listLayout->removeItem(listLayout->getLayoutOnline()); - listLayout->removeItem(listLayout->getLayoutOffline()); - - if (circleLayout != nullptr) { - listLayout->removeItem(circleLayout->getLayout()); - - QLayoutItem* item; - while ((item = circleLayout->getLayout()->takeAt(0)) != nullptr) { - delete item->widget(); - delete item; - } - delete circleLayout; - circleLayout = nullptr; - } - - listLayout->insertLayout(1, activityLayout); - - reDraw(); + listLayout->addLayout(activityLayout); } } -void FriendListWidget::moveFriends(QLayout* layout) +/** + * @brief Clears the listLayout by performing the creation and ownership inverse of sortByMode. + */ +void FriendListWidget::cleanMainLayout() { - while (!layout->isEmpty()) { - QWidget* widget = layout->itemAt(0)->widget(); - FriendWidget* friendWidget = qobject_cast(widget); - CircleWidget* circleWidget = qobject_cast(widget); - if (circleWidget) { - circleWidget->moveFriendWidgets(this); - } else if (friendWidget) { - const Friend* contact = friendWidget->getFriend(); - auto* categoryWidget = getTimeCategoryWidget(contact); - categoryWidget->addFriendWidget(friendWidget, contact->getStatus()); + manager->resetParents(); + + QLayoutItem* itemForDel; + while ((itemForDel = listLayout->takeAt(0)) != nullptr) { + listLayout->removeWidget(itemForDel->widget()); + QWidget* wgt = itemForDel->widget(); + if (wgt != nullptr) { + wgt->setParent(nullptr); + } else if (itemForDel->layout() != nullptr) { + QLayout* layout = itemForDel->layout(); + QLayoutItem* itemTmp; + while ((itemTmp = layout->takeAt(0)) != nullptr) { + wgt = itemTmp->widget(); + delete wgt; + delete itemTmp; + } + } + delete itemForDel; + } +} + +QWidget* FriendListWidget::getNextWidgetForName(IFriendListItem *currentPos, bool forward) const +{ + int pos = currentPos->getNameSortedPos(); + int nextPos = forward ? pos + 1 : pos - 1; + if (nextPos >= manager->getItems().size()) { + nextPos = 0; + } else if (nextPos < 0) { + nextPos = manager->getItems().size() - 1; + } + + for (int i = 0; i < manager->getItems().size(); ++i) { + if (manager->getItems().at(i)->getNameSortedPos() == nextPos) { + return manager->getItems().at(i)->getWidget(); } } + return nullptr; +} + +QVector> +FriendListWidget::getItemsFromCircle(CircleWidget *circle) const +{ + QVector> itemsTmp = manager->getItems(); + QVector> itemsInCircle; + for (int i = 0; i < itemsTmp.size(); ++i) { + int circleId = itemsTmp.at(i)->getCircleId(); + if (CircleWidget::getFromID(circleId) == circle) { + itemsInCircle.push_back(itemsTmp.at(i)); + } + } + return itemsInCircle; } CategoryWidget* FriendListWidget::getTimeCategoryWidget(const Friend* frd) const @@ -295,46 +337,36 @@ FriendListWidget::SortingMode FriendListWidget::getMode() const void FriendListWidget::addGroupWidget(GroupWidget* widget) { - groupLayout.addSortedWidget(widget); Group* g = widget->getGroup(); connect(g, &Group::titleChanged, [=](const QString& author, const QString& name) { Q_UNUSED(author) renameGroupWidget(widget, name); }); + + manager->addFriendListItem(widget); } -void FriendListWidget::addFriendWidget(FriendWidget* w, Status::Status s, int circleIndex) +void FriendListWidget::addFriendWidget(FriendWidget* w) { - CircleWidget* circleWidget = CircleWidget::getFromID(circleIndex); - if (circleWidget == nullptr) - moveWidget(w, s, true); - else - circleWidget->addFriendWidget(w, s); - connect(w, &FriendWidget::friendWidgetRenamed, this, &FriendListWidget::onFriendWidgetRenamed); + manager->addFriendListItem(w); } void FriendListWidget::removeGroupWidget(GroupWidget* w) { - groupLayout.removeSortedWidget(w); - w->deleteLater(); + manager->removeFriendListItem(w); } void FriendListWidget::removeFriendWidget(FriendWidget* w) { const Friend* contact = w->getFriend(); - - if (mode == SortingMode::Activity) { - auto* categoryWidget = getTimeCategoryWidget(contact); - categoryWidget->removeFriendWidget(w, contact->getStatus()); - categoryWidget->setVisible(categoryWidget->hasChatrooms()); - } else { - int id = Settings::getInstance().getFriendCircleID(contact->getPublicKey()); - CircleWidget* circleWidget = CircleWidget::getFromID(id); - if (circleWidget != nullptr) { - circleWidget->removeFriendWidget(w, contact->getStatus()); - emit searchCircle(*circleWidget); - } + int id = Settings::getInstance().getFriendCircleID(contact->getPublicKey()); + CircleWidget* circleWidget = CircleWidget::getFromID(id); + if (circleWidget != nullptr) { + circleWidget->removeFriendWidget(w, contact->getStatus()); + emit searchCircle(*circleWidget); } + + manager->removeFriendListItem(w); } void FriendListWidget::addCircleWidget(int id) @@ -348,103 +380,51 @@ void FriendListWidget::addCircleWidget(FriendWidget* friendWidget) if (circleWidget != nullptr) { if (friendWidget != nullptr) { const Friend* f = friendWidget->getFriend(); - ToxPk toxPk = f->getPublicKey(); - int circleId = Settings::getInstance().getFriendCircleID(toxPk); - CircleWidget* circleOriginal = CircleWidget::getFromID(circleId); - circleWidget->addFriendWidget(friendWidget, f->getStatus()); circleWidget->setExpanded(true); - - if (circleOriginal != nullptr) - emit searchCircle(*circleOriginal); } - emit searchCircle(*circleWidget); - if (window()->isActiveWindow()) circleWidget->editName(); } - reDraw(); + + itemsChanged(); } 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(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(activityLayout->itemAt(i)->widget()); - categoryWidget->search(searchString, true, hideOnline, hideOffline); - categoryWidget->setVisible(categoryWidget->hasChatrooms()); - } - } + manager->setFilter(searchString, hideOnline, hideOffline, hideGroups); } void FriendListWidget::renameGroupWidget(GroupWidget* groupWidget, const QString& newName) { - groupLayout.removeSortedWidget(groupWidget); - groupLayout.addSortedWidget(groupWidget); + itemsChanged(); } void FriendListWidget::renameCircleWidget(CircleWidget* circleWidget, const QString& newName) { - circleLayout->removeSortedWidget(circleWidget); circleWidget->setName(newName); - circleLayout->addSortedWidget(circleWidget); -} -void FriendListWidget::onFriendWidgetRenamed(FriendWidget* friendWidget) -{ - const Friend* contact = friendWidget->getFriend(); - auto status = contact->getStatus(); - if (mode == SortingMode::Activity) { - auto* categoryWidget = getTimeCategoryWidget(contact); - categoryWidget->removeFriendWidget(friendWidget, status); - categoryWidget->addFriendWidget(friendWidget, status); - } else { - int id = Settings::getInstance().getFriendCircleID(contact->getPublicKey()); - CircleWidget* circleWidget = CircleWidget::getFromID(id); - if (circleWidget != nullptr) { - circleWidget->removeFriendWidget(friendWidget, status); - circleWidget->addFriendWidget(friendWidget, status); - emit searchCircle(*circleWidget); - } else { - listLayout->removeFriendWidget(friendWidget, status); - listLayout->addFriendWidget(friendWidget, status); - } + if (mode == SortingMode::Name) { + itemsChanged(); } } void FriendListWidget::onGroupchatPositionChanged(bool top) { - groupsOnTop = top; + manager->setGroupsOnTop(top); if (mode != SortingMode::Name) return; - listLayout->removeItem(groupLayout.getLayout()); - - if (top) - listLayout->insertLayout(0, groupLayout.getLayout()); - else - listLayout->insertLayout(1, groupLayout.getLayout()); - - reDraw(); + manager->updatePositions(); + itemsChanged(); } void FriendListWidget::cycleContacts(GenericChatroomWidget* activeChatroomWidget, bool forward) @@ -499,77 +479,26 @@ void FriendListWidget::cycleContacts(GenericChatroomWidget* activeChatroomWidget return; } - QLayout* currentLayout = nullptr; - CircleWidget* circleWidget = nullptr; + QWidget* wgt = nullptr; if (friendWidget != nullptr) { - const ToxPk& pk = friendWidget->getFriend()->getPublicKey(); - uint32_t circleId = Settings::getInstance().getFriendCircleID(pk); - circleWidget = CircleWidget::getFromID(circleId); - 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); - } - } + wgt = getNextWidgetForName(friendWidget, forward); } else { GroupWidget* groupWidget = qobject_cast(activeChatroomWidget); - if (groupWidget != nullptr) { - currentLayout = groupLayout.getLayout(); - index = groupLayout.indexOfSortedWidget(groupWidget); - } else { - return; + wgt = getNextWidgetForName(groupWidget, forward); + } + + FriendWidget* friendTmp = qobject_cast(wgt); + if (friendTmp != nullptr) { + CircleWidget* circleWidget = CircleWidget::getFromID(friendTmp->getCircleId()); + if (circleWidget != nullptr) { + circleWidget->setExpanded(true); } } - index += forward ? 1 : -1; - - for (;;) { - // 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 = - qobject_cast(currentLayout->itemAt(index)->widget()); - - if (chatWidget != nullptr) - emit chatWidget->chatroomWidgetClicked(chatWidget); - - return; - } else if (currentLayout == circleLayout->getLayout()) { - circleWidget = qobject_cast(currentLayout->itemAt(index)->widget()); - if (circleWidget != nullptr) { - if (!circleWidget->cycleContacts(forward)) { - // Skip empty or finished circles. - index += forward ? 1 : -1; - continue; - } - } - return; - } else { - return; - } - } + GenericChatroomWidget* chatWidget = qobject_cast(wgt); + if (chatWidget != nullptr) + emit chatWidget->chatroomWidgetClicked(chatWidget); } void FriendListWidget::dragEnterEvent(QDragEnterEvent* event) @@ -611,13 +540,17 @@ void FriendListWidget::dropEvent(QDropEvent* event) void FriendListWidget::dayTimeout() { if (mode == SortingMode::Activity) { - setMode(SortingMode::Name); - setMode(SortingMode::Activity); // Refresh all. + itemsChanged(); } dayTimer->start(timeUntilTomorrow()); } +void FriendListWidget::itemsChanged() +{ + sortByMode(mode); +} + void FriendListWidget::moveWidget(FriendWidget* widget, Status::Status s, bool add) { if (mode == SortingMode::Name) { @@ -629,7 +562,7 @@ void FriendListWidget::moveWidget(FriendWidget* widget, Status::Status s, bool a if (circleId != -1) Settings::getInstance().setFriendCircleID(f->getPublicKey(), -1); - listLayout->addFriendWidget(widget, s); + itemsChanged(); return; } @@ -640,6 +573,7 @@ void FriendListWidget::moveWidget(FriendWidget* widget, Status::Status s, bool a categoryWidget->addFriendWidget(widget, contact->getStatus()); categoryWidget->show(); } + itemsChanged(); } void FriendListWidget::updateActivityTime(const QDateTime& time) @@ -655,76 +589,19 @@ void FriendListWidget::updateActivityTime(const QDateTime& time) categoryWidget->setVisible(categoryWidget->hasChatrooms()); } -// update widget after add/delete/hide/show -void FriendListWidget::reDraw() -{ - hide(); - 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 == SortingMode::Activity) - return nullptr; - - assert(circleLayout != nullptr); + if (CircleWidget::getFromID(id) != nullptr) { + return CircleWidget::getFromID(id); + } CircleWidget* circleWidget = new CircleWidget(core, this, id); emit connectCircleWidget(*circleWidget); - 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; -} diff --git a/src/widget/friendlistwidget.h b/src/widget/friendlistwidget.h index 98f59f79d..713e5a876 100644 --- a/src/widget/friendlistwidget.h +++ b/src/widget/friendlistwidget.h @@ -23,6 +23,7 @@ #include "src/core/core.h" #include "src/model/status.h" #include "src/persistence/settings.h" + #include class QVBoxLayout; @@ -32,10 +33,11 @@ class Widget; class FriendWidget; class GroupWidget; class CircleWidget; -class FriendListLayout; +class FriendListManager; class GenericChatroomWidget; class CategoryWidget; class Friend; +class IFriendListItem; class FriendListWidget : public QWidget { @@ -48,7 +50,7 @@ public: SortingMode getMode() const; void addGroupWidget(GroupWidget* widget); - void addFriendWidget(FriendWidget* w, Status::Status s, int circleIndex); + void addFriendWidget(FriendWidget* w); void removeGroupWidget(GroupWidget* w); void removeFriendWidget(FriendWidget* w); void addCircleWidget(int id); @@ -60,7 +62,6 @@ public: void cycleContacts(GenericChatroomWidget* activeChatroomWidget, bool forward); void updateActivityTime(const QDateTime& date); - void reDraw(); signals: void onCompactChanged(bool compact); @@ -70,9 +71,9 @@ signals: public slots: void renameGroupWidget(GroupWidget* groupWidget, const QString& newName); void renameCircleWidget(CircleWidget* circleWidget, const QString& newName); - void onFriendWidgetRenamed(FriendWidget* friendWidget); void onGroupchatPositionChanged(bool top); void moveWidget(FriendWidget* w, Status::Status s, bool add = false); + void itemsChanged(); protected: void dragEnterEvent(QDragEnterEvent* event) override; @@ -83,18 +84,17 @@ private slots: private: CircleWidget* createCircleWidget(int id = -1); - QLayout* nextLayout(QLayout* layout, bool forward) const; - void moveFriends(QLayout* layout); CategoryWidget* getTimeCategoryWidget(const Friend* frd) const; void sortByMode(SortingMode mode); + void cleanMainLayout(); + QWidget* getNextWidgetForName(IFriendListItem* currentPos, bool forward) const; + QVector > getItemsFromCircle(CircleWidget* circle) const; SortingMode mode; - bool groupsOnTop; - FriendListLayout* listLayout; - GenericChatItemLayout* circleLayout = nullptr; - GenericChatItemLayout groupLayout; + QVBoxLayout* listLayout = nullptr; QVBoxLayout* activityLayout = nullptr; QTimer* dayTimer; + FriendListManager* manager; const Core& core; }; diff --git a/src/widget/friendwidget.cpp b/src/widget/friendwidget.cpp index a462d584b..068d39a55 100644 --- a/src/widget/friendwidget.cpp +++ b/src/widget/friendwidget.cpp @@ -41,7 +41,6 @@ #include #include #include -#include #include #include #include @@ -72,8 +71,6 @@ FriendWidget::FriendWidget(std::shared_ptr chatroom, bool compac connect(nameLabel, &CroppingLabel::editFinished, frnd, &Friend::setAlias); // update on changes of the displayed name connect(frnd, &Friend::displayedNameChanged, nameLabel, &CroppingLabel::setText); - connect(frnd, &Friend::displayedNameChanged, this, - [this](const QString /* &newName */) { emit friendWidgetRenamed(this); }); connect(chatroom.get(), &FriendChatroom::activeChanged, this, &FriendWidget::setActive); statusMessageLabel->setTextFormat(Qt::PlainText); } @@ -241,7 +238,6 @@ void FriendWidget::removeFromCircle() if (circleWidget != nullptr) { circleWidget->updateStatus(); - emit searchCircle(*circleWidget); } } @@ -257,7 +253,6 @@ void FriendWidget::moveToCircle(int newCircleId) if (newCircleWidget) { newCircleWidget->addFriendWidget(this, frnd->getStatus()); newCircleWidget->setExpanded(true); - emit searchCircle(*newCircleWidget); s.savePersonal(); } else { s.setFriendCircleID(pk, newCircleId); @@ -265,7 +260,6 @@ void FriendWidget::moveToCircle(int newCircleId) if (oldCircleWidget) { oldCircleWidget->updateStatus(); - emit searchCircle(*oldCircleWidget); } } @@ -360,16 +354,41 @@ const Contact* FriendWidget::getContact() const return getFriend(); } -void FriendWidget::search(const QString& searchString, bool hide) +bool FriendWidget::isFriend() const +{ + return true; +} + +bool FriendWidget::isGroup() const +{ + return false; +} + +bool FriendWidget::isOnline() const +{ + const auto frnd = getFriend(); + return Status::isOnline(frnd->getStatus()); +} + +QString FriendWidget::getNameItem() const +{ + return nameLabel->fullText(); +} + +QDateTime FriendWidget::getLastActivity() const { const auto frnd = chatroom->getFriend(); - searchName(searchString, hide); - const Settings& s = Settings::getInstance(); - const uint32_t circleId = s.getFriendCircleID(frnd->getPublicKey()); - CircleWidget* circleWidget = CircleWidget::getFromID(circleId); - if (circleWidget) { - circleWidget->search(searchString); - } + return Settings::getInstance().getFriendActivity(frnd->getPublicKey()); +} + +QWidget *FriendWidget::getWidget() +{ + return this; +} + +int FriendWidget::getCircleId() const +{ + return chatroom->getCircleId(); } void FriendWidget::resetEventFlags() diff --git a/src/widget/friendwidget.h b/src/widget/friendwidget.h index 962c95565..e6b1f43b1 100644 --- a/src/widget/friendwidget.h +++ b/src/widget/friendwidget.h @@ -21,6 +21,7 @@ #include "genericchatroomwidget.h" #include "src/core/toxpk.h" +#include "src/model/friendlist/ifriendlistitem.h" #include @@ -29,7 +30,7 @@ class QPixmap; class MaskablePixmapWidget; class CircleWidget; -class FriendWidget : public GenericChatroomWidget +class FriendWidget : public GenericChatroomWidget, public IFriendListItem { Q_OBJECT public: @@ -44,7 +45,13 @@ public: const Friend* getFriend() const final; const Contact* getContact() const final; - void search(const QString& searchString, bool hide = false); + bool isFriend() const final; + bool isGroup() const final; + bool isOnline() const final; + QString getNameItem() const final; + QDateTime getLastActivity() const final; + int getCircleId() const final; + QWidget* getWidget() final; signals: void friendWidgetClicked(FriendWidget* widget); @@ -52,8 +59,6 @@ signals: void copyFriendIdToClipboard(const ToxPk& friendPk); void contextMenuCalled(QContextMenuEvent* event); void friendHistoryRemoved(); - void friendWidgetRenamed(FriendWidget* friendWidget); - void searchCircle(CircleWidget& circleWidget); void updateFriendActivity(Friend& frnd); public slots: diff --git a/src/widget/groupwidget.cpp b/src/widget/groupwidget.cpp index 18b85f7f3..f0bb961aa 100644 --- a/src/widget/groupwidget.cpp +++ b/src/widget/groupwidget.cpp @@ -189,6 +189,36 @@ void GroupWidget::editName() nameLabel->editBegin(); } +bool GroupWidget::isFriend() const +{ + return false; +} + +bool GroupWidget::isGroup() const +{ + return true; +} + +QString GroupWidget::getNameItem() const +{ + return nameLabel->fullText(); +} + +bool GroupWidget::isOnline() const +{ + return true; +} + +QDateTime GroupWidget::getLastActivity() const +{ + return QDateTime::currentDateTime(); +} + +QWidget *GroupWidget::getWidget() +{ + return this; +} + // TODO: Remove Group* GroupWidget::getGroup() const { diff --git a/src/widget/groupwidget.h b/src/widget/groupwidget.h index fce990e84..2d9dfbdb9 100644 --- a/src/widget/groupwidget.h +++ b/src/widget/groupwidget.h @@ -22,17 +22,18 @@ #include "genericchatroomwidget.h" #include "src/model/chatroom/groupchatroom.h" +#include "src/model/friendlist/ifriendlistitem.h" #include "src/core/groupid.h" #include -class GroupWidget final : public GenericChatroomWidget +class GroupWidget final : public GenericChatroomWidget, public IFriendListItem { Q_OBJECT public: GroupWidget(std::shared_ptr chatroom, bool compact); ~GroupWidget(); - void setAsInactiveChatroom() final; + void setAsInactiveChatroom() final; void setAsActiveChatroom() final; void updateStatusLight() final; void resetEventFlags() final; @@ -42,6 +43,13 @@ public: void setName(const QString& name); void editName(); + bool isFriend() const final; + bool isGroup() const final; + QString getNameItem() const final; + bool isOnline() const final; + QDateTime getLastActivity() const final; + QWidget* getWidget() final; + signals: void groupWidgetClicked(GroupWidget* widget); void removeGroup(const GroupId& groupId); diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 8baa35c77..a0984f8b2 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -1178,8 +1178,7 @@ void Widget::addFriend(uint32_t friendId, const ToxPk& friendPk) settings.setFriendActivity(friendPk, chatTime); } - contactListWidget->addFriendWidget(widget, Status::Status::Offline, - settings.getFriendCircleID(friendPk)); + contactListWidget->addFriendWidget(widget); auto notifyReceivedCallback = [this, friendPk](const ToxPk& author, const Message& message) { @@ -1218,9 +1217,6 @@ void Widget::addFriend(uint32_t friendId, const ToxPk& friendPk) friendForm->onAvatarChanged(friendPk, avatar); widget->onAvatarSet(friendPk, avatar); } - - FilterCriteria filter = getFilterCriteria(); - widget->search(ui->searchContactText->text(), filterOffline(filter)); } void Widget::addFriendFailed(const ToxPk&, const QString& errorInfo) @@ -1308,6 +1304,8 @@ void Widget::onFriendDisplayedNameChanged(const QString& displayed) if (friendWidget->isActive()) { GUI::setWindowTitle(displayed); } + + contactListWidget->itemsChanged(); } void Widget::onFriendUsernameChanged(int friendId, const QString& username) @@ -1325,16 +1323,6 @@ void Widget::onFriendUsernameChanged(int friendId, const QString& username) void Widget::onFriendAliasChanged(const ToxPk& friendId, const QString& alias) { - Friend* f = qobject_cast(sender()); - - // TODO(sudden6): don't update the contact list here, make it update itself - FriendWidget* friendWidget = friendWidgets[friendId]; - Status::Status status = f->getStatus(); - contactListWidget->moveWidget(friendWidget, status); - FilterCriteria criteria = getFilterCriteria(); - bool filter = status == Status::Status::Offline ? filterOffline(criteria) : filterOnline(criteria); - friendWidget->searchName(ui->searchContactText->text(), filter); - settings.setFriendAlias(friendId, alias); settings.savePersonal(); } @@ -1779,7 +1767,6 @@ void Widget::removeFriend(Friend* f, bool fake) } friendWidgets.remove(friendPk); - delete widget; auto chatForm = chatForms[friendPk]; chatForms.remove(friendPk); @@ -1789,8 +1776,6 @@ void Widget::removeFriend(Friend* f, bool fake) if (contentLayout && contentLayout->mainHead->layout()->isEmpty()) { onAddClicked(); } - - contactListWidget->reDraw(); } void Widget::removeFriend(const ToxPk& friendId) @@ -2032,8 +2017,7 @@ void Widget::onGroupTitleChanged(uint32_t groupnumber, const QString& author, co } g->setTitle(author, title); - FilterCriteria filter = getFilterCriteria(); - widget->searchName(ui->searchContactText->text(), filterGroups(filter)); + contactListWidget->itemsChanged(); } void Widget::titleChangedByUser(const QString& title) @@ -2092,8 +2076,6 @@ void Widget::removeGroup(Group* g, bool fake) } groupAlertConnections.remove(groupId); - - contactListWidget->reDraw(); } void Widget::removeGroup(const GroupId& groupId) @@ -2184,9 +2166,6 @@ Group* Widget::createGroup(uint32_t groupnumber, const GroupId& groupId) connect(newgroup, &Group::titleChangedByUser, this, &Widget::titleChangedByUser); connect(core, &Core::usernameSet, newgroup, &Group::setSelfName); - FilterCriteria filter = getFilterCriteria(); - widget->searchName(ui->searchContactText->text(), filterGroups(filter)); - return newgroup; } @@ -2467,7 +2446,6 @@ void Widget::reloadTheme() ui->statusHead->setStyleSheet(statusPanelStyle); ui->friendList->setStyleSheet(Style::getStylesheet("friendList/friendList.css")); ui->statusButton->setStyleSheet(Style::getStylesheet("statusButton/statusButton.css")); - contactListWidget->reDraw(); profilePicture->setStyleSheet(Style::getStylesheet("window/profile.css")); } @@ -2518,8 +2496,6 @@ void Widget::searchContacts() filterGroups(filter)); updateFilterText(); - - contactListWidget->reDraw(); } void Widget::changeDisplayMode() @@ -2564,9 +2540,11 @@ Widget::FilterCriteria Widget::getFilterCriteria() const void Widget::searchCircle(CircleWidget& circleWidget) { - FilterCriteria filter = getFilterCriteria(); - QString text = ui->searchContactText->text(); - circleWidget.search(text, true, filterOnline(filter), filterOffline(filter)); + if (contactListWidget->getMode() == FriendListWidget::SortingMode::Name) { + FilterCriteria filter = getFilterCriteria(); + QString text = ui->searchContactText->text(); + circleWidget.search(text, true, filterOnline(filter), filterOffline(filter)); + } } bool Widget::groupsVisible() const @@ -2716,12 +2694,10 @@ void Widget::refreshPeerListsLocal(const QString& username) void Widget::connectCircleWidget(CircleWidget& circleWidget) { - connect(&circleWidget, &CircleWidget::searchCircle, this, &Widget::searchCircle); connect(&circleWidget, &CircleWidget::newContentDialog, this, &Widget::registerContentDialog); } void Widget::connectFriendWidget(FriendWidget& friendWidget) { - connect(&friendWidget, &FriendWidget::searchCircle, this, &Widget::searchCircle); connect(&friendWidget, &FriendWidget::updateFriendActivity, this, &Widget::updateFriendActivity); }