1 /*
2     Copyright © 2014-2019 by The qTox Project Contributors
3 
4     This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6     qTox is libre software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
10 
11     qTox is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with qTox.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "friendlistwidget.h"
21 #include "circlewidget.h"
22 #include "friendlistlayout.h"
23 #include "friendwidget.h"
24 #include "groupwidget.h"
25 #include "widget.h"
26 #include "src/friendlist.h"
27 #include "src/model/friend.h"
28 #include "src/model/group.h"
29 #include "src/model/status.h"
30 #include "src/persistence/settings.h"
31 #include "src/widget/categorywidget.h"
32 
33 #include <QDragEnterEvent>
34 #include <QDragLeaveEvent>
35 #include <QGridLayout>
36 #include <QMimeData>
37 #include <QTimer>
38 #include <cassert>
39 
40 enum class Time
41 {
42     Today,
43     Yesterday,
44     ThisWeek,
45     ThisMonth,
46     Month1Ago,
47     Month2Ago,
48     Month3Ago,
49     Month4Ago,
50     Month5Ago,
51     LongAgo,
52     Never
53 };
54 
55 static const int LAST_TIME = static_cast<int>(Time::Never);
56 
getTimeBucket(const QDateTime & date)57 Time getTimeBucket(const QDateTime& date)
58 {
59     if (date == QDateTime()) {
60         return Time::Never;
61     }
62 
63     QDate today = QDate::currentDate();
64     // clang-format off
65     const QMap<Time, QDate> dates {
66         { Time::Today,     today.addDays(0)    },
67         { Time::Yesterday, today.addDays(-1)   },
68         { Time::ThisWeek,  today.addDays(-6)   },
69         { Time::ThisMonth, today.addMonths(-1) },
70         { Time::Month1Ago, today.addMonths(-2) },
71         { Time::Month2Ago, today.addMonths(-3) },
72         { Time::Month3Ago, today.addMonths(-4) },
73         { Time::Month4Ago, today.addMonths(-5) },
74         { Time::Month5Ago, today.addMonths(-6) },
75     };
76     // clang-format on
77 
78     for (Time time : dates.keys()) {
79         if (dates[time] <= date.date()) {
80             return time;
81         }
82     }
83 
84     return Time::LongAgo;
85 }
86 
getActiveTimeFriend(const Friend * contact)87 QDateTime getActiveTimeFriend(const Friend* contact)
88 {
89     return Settings::getInstance().getFriendActivity(contact->getPublicKey());
90 }
91 
timeUntilTomorrow()92 qint64 timeUntilTomorrow()
93 {
94     QDateTime now = QDateTime::currentDateTime();
95     QDateTime tomorrow = now.addDays(1); // Tomorrow.
96     tomorrow.setTime(QTime());           // Midnight.
97     return now.msecsTo(tomorrow);
98 }
99 
FriendListWidget(Widget * parent,bool groupsOnTop)100 FriendListWidget::FriendListWidget(Widget* parent, bool groupsOnTop)
101     : QWidget(parent)
102     , groupsOnTop(groupsOnTop)
103 {
104     listLayout = new FriendListLayout();
105     setLayout(listLayout);
106     setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
107 
108     groupLayout.getLayout()->setSpacing(0);
109     groupLayout.getLayout()->setMargin(0);
110 
111     // Prevent QLayout's add child warning before setting the mode.
112     listLayout->removeItem(listLayout->getLayoutOnline());
113     listLayout->removeItem(listLayout->getLayoutOffline());
114 
115     mode = Settings::getInstance().getFriendSortingMode();
116     sortByMode(mode);
117 
118     onGroupchatPositionChanged(groupsOnTop);
119     dayTimer = new QTimer(this);
120     dayTimer->setTimerType(Qt::VeryCoarseTimer);
121     connect(dayTimer, &QTimer::timeout, this, &FriendListWidget::dayTimeout);
122     dayTimer->start(timeUntilTomorrow());
123 
124     setAcceptDrops(true);
125 }
126 
~FriendListWidget()127 FriendListWidget::~FriendListWidget()
128 {
129     if (activityLayout != nullptr) {
130         QLayoutItem* item;
131         while ((item = activityLayout->takeAt(0)) != nullptr) {
132             delete item->widget();
133             delete item;
134         }
135         delete activityLayout;
136     }
137 
138     if (circleLayout != nullptr) {
139         QLayoutItem* item;
140         while ((item = circleLayout->getLayout()->takeAt(0)) != nullptr) {
141             delete item->widget();
142             delete item;
143         }
144         delete circleLayout;
145     }
146 }
147 
setMode(SortingMode mode)148 void FriendListWidget::setMode(SortingMode mode)
149 {
150     if (this->mode == mode)
151         return;
152 
153     this->mode = mode;
154     Settings::getInstance().setFriendSortingMode(mode);
155 
156     sortByMode(mode);
157 }
158 
sortByMode(SortingMode mode)159 void FriendListWidget::sortByMode(SortingMode mode)
160 {
161     if (mode == SortingMode::Name) {
162         circleLayout = new GenericChatItemLayout;
163         circleLayout->getLayout()->setSpacing(0);
164         circleLayout->getLayout()->setMargin(0);
165 
166         for (int i = 0; i < Settings::getInstance().getCircleCount(); ++i) {
167             addCircleWidget(i);
168             CircleWidget::getFromID(i)->setVisible(false);
169         }
170 
171         // Only display circles once all created to avoid artifacts.
172         for (int i = 0; i < Settings::getInstance().getCircleCount(); ++i)
173             CircleWidget::getFromID(i)->setVisible(true);
174 
175         int count = activityLayout ? activityLayout->count() : 0;
176         for (int i = 0; i < count; i++) {
177             QWidget* widget = activityLayout->itemAt(i)->widget();
178             CategoryWidget* categoryWidget = qobject_cast<CategoryWidget*>(widget);
179             if (categoryWidget) {
180                 categoryWidget->moveFriendWidgets(this);
181             } else {
182                 qWarning() << "Unexpected widget";
183             }
184         }
185 
186         listLayout->addLayout(listLayout->getLayoutOnline());
187         listLayout->addLayout(listLayout->getLayoutOffline());
188         listLayout->addLayout(circleLayout->getLayout());
189         onGroupchatPositionChanged(groupsOnTop);
190 
191         if (activityLayout != nullptr) {
192             QLayoutItem* item;
193             while ((item = activityLayout->takeAt(0)) != nullptr) {
194                 delete item->widget();
195                 delete item;
196             }
197             delete activityLayout;
198             activityLayout = nullptr;
199         }
200 
201         reDraw();
202     } else if (mode == SortingMode::Activity) {
203         QLocale ql(Settings::getInstance().getTranslation());
204         QDate today = QDate::currentDate();
205 #define COMMENT "Category for sorting friends by activity"
206         // clang-format off
207         const QMap<Time, QString> names {
208             { Time::Today,     tr("Today",                      COMMENT) },
209             { Time::Yesterday, tr("Yesterday",                  COMMENT) },
210             { Time::ThisWeek,  tr("Last 7 days",                COMMENT) },
211             { Time::ThisMonth, tr("This month",                 COMMENT) },
212             { Time::LongAgo,   tr("Older than 6 Months",        COMMENT) },
213             { Time::Never,     tr("Never",                      COMMENT) },
214             { Time::Month1Ago, ql.monthName(today.addMonths(-1).month()) },
215             { Time::Month2Ago, ql.monthName(today.addMonths(-2).month()) },
216             { Time::Month3Ago, ql.monthName(today.addMonths(-3).month()) },
217             { Time::Month4Ago, ql.monthName(today.addMonths(-4).month()) },
218             { Time::Month5Ago, ql.monthName(today.addMonths(-5).month()) },
219         };
220 // clang-format on
221 #undef COMMENT
222 
223         activityLayout = new QVBoxLayout();
224         bool compact = Settings::getInstance().getCompactLayout();
225         for (Time t : names.keys()) {
226             CategoryWidget* category = new CategoryWidget(compact, this);
227             category->setName(names[t]);
228             activityLayout->addWidget(category);
229         }
230 
231         moveFriends(listLayout->getLayoutOffline());
232         moveFriends(listLayout->getLayoutOnline());
233         if (circleLayout != nullptr) {
234             moveFriends(circleLayout->getLayout());
235         }
236 
237         for (int i = 0; i < activityLayout->count(); ++i) {
238             QWidget* widget = activityLayout->itemAt(i)->widget();
239             CategoryWidget* categoryWidget = qobject_cast<CategoryWidget*>(widget);
240             categoryWidget->setVisible(categoryWidget->hasChatrooms());
241         }
242 
243         listLayout->removeItem(listLayout->getLayoutOnline());
244         listLayout->removeItem(listLayout->getLayoutOffline());
245 
246         if (circleLayout != nullptr) {
247             listLayout->removeItem(circleLayout->getLayout());
248 
249             QLayoutItem* item;
250             while ((item = circleLayout->getLayout()->takeAt(0)) != nullptr) {
251                 delete item->widget();
252                 delete item;
253             }
254             delete circleLayout;
255             circleLayout = nullptr;
256         }
257 
258         listLayout->insertLayout(1, activityLayout);
259 
260         reDraw();
261     }
262 }
263 
moveFriends(QLayout * layout)264 void FriendListWidget::moveFriends(QLayout* layout)
265 {
266     while (!layout->isEmpty()) {
267         QWidget* widget = layout->itemAt(0)->widget();
268         FriendWidget* friendWidget = qobject_cast<FriendWidget*>(widget);
269         CircleWidget* circleWidget = qobject_cast<CircleWidget*>(widget);
270         if (circleWidget) {
271             circleWidget->moveFriendWidgets(this);
272         } else if (friendWidget) {
273             const Friend* contact = friendWidget->getFriend();
274             auto* categoryWidget = getTimeCategoryWidget(contact);
275             categoryWidget->addFriendWidget(friendWidget, contact->getStatus());
276         }
277     }
278 }
279 
getTimeCategoryWidget(const Friend * frd) const280 CategoryWidget* FriendListWidget::getTimeCategoryWidget(const Friend* frd) const
281 {
282     const auto activityTime = getActiveTimeFriend(frd);
283     int timeIndex = static_cast<int>(getTimeBucket(activityTime));
284     QWidget* widget = activityLayout->itemAt(timeIndex)->widget();
285     return qobject_cast<CategoryWidget*>(widget);
286 }
287 
getMode() const288 FriendListWidget::SortingMode FriendListWidget::getMode() const
289 {
290     return mode;
291 }
292 
addGroupWidget(GroupWidget * widget)293 void FriendListWidget::addGroupWidget(GroupWidget* widget)
294 {
295     groupLayout.addSortedWidget(widget);
296     Group* g = widget->getGroup();
297     connect(g, &Group::titleChanged, [=](const QString& author, const QString& name) {
298         Q_UNUSED(author);
299         renameGroupWidget(widget, name);
300     });
301 }
302 
addFriendWidget(FriendWidget * w,Status::Status s,int circleIndex)303 void FriendListWidget::addFriendWidget(FriendWidget* w, Status::Status s, int circleIndex)
304 {
305     CircleWidget* circleWidget = CircleWidget::getFromID(circleIndex);
306     if (circleWidget == nullptr)
307         moveWidget(w, s, true);
308     else
309         circleWidget->addFriendWidget(w, s);
310     connect(w, &FriendWidget::friendWidgetRenamed, this, &FriendListWidget::onFriendWidgetRenamed);
311 }
312 
removeGroupWidget(GroupWidget * w)313 void FriendListWidget::removeGroupWidget(GroupWidget* w)
314 {
315     groupLayout.removeSortedWidget(w);
316     w->deleteLater();
317 }
318 
removeFriendWidget(FriendWidget * w)319 void FriendListWidget::removeFriendWidget(FriendWidget* w)
320 {
321     const Friend* contact = w->getFriend();
322 
323     if (mode == SortingMode::Activity) {
324         auto* categoryWidget = getTimeCategoryWidget(contact);
325         categoryWidget->removeFriendWidget(w, contact->getStatus());
326         categoryWidget->setVisible(categoryWidget->hasChatrooms());
327     } else {
328         int id = Settings::getInstance().getFriendCircleID(contact->getPublicKey());
329         CircleWidget* circleWidget = CircleWidget::getFromID(id);
330         if (circleWidget != nullptr) {
331             circleWidget->removeFriendWidget(w, contact->getStatus());
332             emit searchCircle(*circleWidget);
333         }
334     }
335 }
336 
addCircleWidget(int id)337 void FriendListWidget::addCircleWidget(int id)
338 {
339     createCircleWidget(id);
340 }
341 
addCircleWidget(FriendWidget * friendWidget)342 void FriendListWidget::addCircleWidget(FriendWidget* friendWidget)
343 {
344     CircleWidget* circleWidget = createCircleWidget();
345     if (circleWidget != nullptr) {
346         if (friendWidget != nullptr) {
347             const Friend* f = friendWidget->getFriend();
348             ToxPk toxPk = f->getPublicKey();
349             int circleId = Settings::getInstance().getFriendCircleID(toxPk);
350             CircleWidget* circleOriginal = CircleWidget::getFromID(circleId);
351 
352             circleWidget->addFriendWidget(friendWidget, f->getStatus());
353             circleWidget->setExpanded(true);
354 
355             if (circleOriginal != nullptr)
356                 emit searchCircle(*circleOriginal);
357         }
358 
359         emit searchCircle(*circleWidget);
360 
361         if (window()->isActiveWindow())
362             circleWidget->editName();
363     }
364     reDraw();
365 }
366 
removeCircleWidget(CircleWidget * widget)367 void FriendListWidget::removeCircleWidget(CircleWidget* widget)
368 {
369     circleLayout->removeSortedWidget(widget);
370     widget->deleteLater();
371 }
372 
searchChatrooms(const QString & searchString,bool hideOnline,bool hideOffline,bool hideGroups)373 void FriendListWidget::searchChatrooms(const QString& searchString, bool hideOnline,
374                                        bool hideOffline, bool hideGroups)
375 {
376     groupLayout.search(searchString, hideGroups);
377     listLayout->searchChatrooms(searchString, hideOnline, hideOffline);
378 
379     if (circleLayout != nullptr) {
380         for (int i = 0; i != circleLayout->getLayout()->count(); ++i) {
381             CircleWidget* circleWidget =
382                 static_cast<CircleWidget*>(circleLayout->getLayout()->itemAt(i)->widget());
383             circleWidget->search(searchString, true, hideOnline, hideOffline);
384         }
385     } else if (activityLayout != nullptr) {
386         for (int i = 0; i != activityLayout->count(); ++i) {
387             CategoryWidget* categoryWidget =
388                 static_cast<CategoryWidget*>(activityLayout->itemAt(i)->widget());
389             categoryWidget->search(searchString, true, hideOnline, hideOffline);
390             categoryWidget->setVisible(categoryWidget->hasChatrooms());
391         }
392     }
393 }
394 
renameGroupWidget(GroupWidget * groupWidget,const QString & newName)395 void FriendListWidget::renameGroupWidget(GroupWidget* groupWidget, const QString& newName)
396 {
397     groupLayout.removeSortedWidget(groupWidget);
398     groupLayout.addSortedWidget(groupWidget);
399 }
400 
renameCircleWidget(CircleWidget * circleWidget,const QString & newName)401 void FriendListWidget::renameCircleWidget(CircleWidget* circleWidget, const QString& newName)
402 {
403     circleLayout->removeSortedWidget(circleWidget);
404     circleWidget->setName(newName);
405     circleLayout->addSortedWidget(circleWidget);
406 }
407 
onFriendWidgetRenamed(FriendWidget * friendWidget)408 void FriendListWidget::onFriendWidgetRenamed(FriendWidget* friendWidget)
409 {
410     const Friend* contact = friendWidget->getFriend();
411     auto status = contact->getStatus();
412     if (mode == SortingMode::Activity) {
413         auto* categoryWidget = getTimeCategoryWidget(contact);
414         categoryWidget->removeFriendWidget(friendWidget, status);
415         categoryWidget->addFriendWidget(friendWidget, status);
416     } else {
417         int id = Settings::getInstance().getFriendCircleID(contact->getPublicKey());
418         CircleWidget* circleWidget = CircleWidget::getFromID(id);
419         if (circleWidget != nullptr) {
420             circleWidget->removeFriendWidget(friendWidget, status);
421             circleWidget->addFriendWidget(friendWidget, status);
422             emit searchCircle(*circleWidget);
423         } else {
424             listLayout->removeFriendWidget(friendWidget, status);
425             listLayout->addFriendWidget(friendWidget, status);
426         }
427     }
428 }
429 
onGroupchatPositionChanged(bool top)430 void FriendListWidget::onGroupchatPositionChanged(bool top)
431 {
432     groupsOnTop = top;
433 
434     if (mode != SortingMode::Name)
435         return;
436 
437     listLayout->removeItem(groupLayout.getLayout());
438 
439     if (top)
440         listLayout->insertLayout(0, groupLayout.getLayout());
441     else
442         listLayout->insertLayout(1, groupLayout.getLayout());
443 
444     reDraw();
445 }
446 
cycleContacts(GenericChatroomWidget * activeChatroomWidget,bool forward)447 void FriendListWidget::cycleContacts(GenericChatroomWidget* activeChatroomWidget, bool forward)
448 {
449     if (!activeChatroomWidget) {
450         return;
451     }
452 
453     int index = -1;
454     FriendWidget* friendWidget = qobject_cast<FriendWidget*>(activeChatroomWidget);
455 
456     if (mode == SortingMode::Activity) {
457         if (!friendWidget) {
458             return;
459         }
460 
461         const auto activityTime = getActiveTimeFriend(friendWidget->getFriend());
462         index = static_cast<int>(getTimeBucket(activityTime));
463         QWidget* widget = activityLayout->itemAt(index)->widget();
464         CategoryWidget* categoryWidget = qobject_cast<CategoryWidget*>(widget);
465 
466         if (categoryWidget == nullptr || categoryWidget->cycleContacts(friendWidget, forward)) {
467             return;
468         }
469 
470         index += forward ? 1 : -1;
471 
472         for (;;) {
473             // Bounds checking.
474             if (index < 0) {
475                 index = LAST_TIME;
476                 continue;
477             } else if (index > LAST_TIME) {
478                 index = 0;
479                 continue;
480             }
481 
482             auto* widget = activityLayout->itemAt(index)->widget();
483             categoryWidget = qobject_cast<CategoryWidget*>(widget);
484 
485             if (categoryWidget != nullptr) {
486                 if (!categoryWidget->cycleContacts(forward)) {
487                     // Skip empty or finished categories.
488                     index += forward ? 1 : -1;
489                     continue;
490                 }
491             }
492 
493             break;
494         }
495 
496         return;
497     }
498 
499     QLayout* currentLayout = nullptr;
500     CircleWidget* circleWidget = nullptr;
501 
502     if (friendWidget != nullptr) {
503         const ToxPk& pk = friendWidget->getFriend()->getPublicKey();
504         uint32_t circleId = Settings::getInstance().getFriendCircleID(pk);
505         circleWidget = CircleWidget::getFromID(circleId);
506         if (circleWidget != nullptr) {
507             if (circleWidget->cycleContacts(friendWidget, forward)) {
508                 return;
509             }
510 
511             index = circleLayout->indexOfSortedWidget(circleWidget);
512             currentLayout = circleLayout->getLayout();
513         } else {
514             currentLayout = listLayout->getLayoutOnline();
515             index = listLayout->indexOfFriendWidget(friendWidget, true);
516             if (index == -1) {
517                 currentLayout = listLayout->getLayoutOffline();
518                 index = listLayout->indexOfFriendWidget(friendWidget, false);
519             }
520         }
521     } else {
522         GroupWidget* groupWidget = qobject_cast<GroupWidget*>(activeChatroomWidget);
523         if (groupWidget != nullptr) {
524             currentLayout = groupLayout.getLayout();
525             index = groupLayout.indexOfSortedWidget(groupWidget);
526         } else {
527             return;
528         };
529     }
530 
531     index += forward ? 1 : -1;
532 
533     for (;;) {
534         // Bounds checking.
535         if (index < 0) {
536             currentLayout = nextLayout(currentLayout, forward);
537             index = currentLayout->count() - 1;
538             continue;
539         } else if (index >= currentLayout->count()) {
540             currentLayout = nextLayout(currentLayout, forward);
541             index = 0;
542             continue;
543         }
544 
545         // Go to the actual next index.
546         if (currentLayout == listLayout->getLayoutOnline()
547             || currentLayout == listLayout->getLayoutOffline()
548             || currentLayout == groupLayout.getLayout()) {
549             GenericChatroomWidget* chatWidget =
550                 qobject_cast<GenericChatroomWidget*>(currentLayout->itemAt(index)->widget());
551 
552             if (chatWidget != nullptr)
553                 emit chatWidget->chatroomWidgetClicked(chatWidget);
554 
555             return;
556         } else if (currentLayout == circleLayout->getLayout()) {
557             circleWidget = qobject_cast<CircleWidget*>(currentLayout->itemAt(index)->widget());
558             if (circleWidget != nullptr) {
559                 if (!circleWidget->cycleContacts(forward)) {
560                     // Skip empty or finished circles.
561                     index += forward ? 1 : -1;
562                     continue;
563                 }
564             }
565             return;
566         } else {
567             return;
568         }
569     }
570 }
571 
dragEnterEvent(QDragEnterEvent * event)572 void FriendListWidget::dragEnterEvent(QDragEnterEvent* event)
573 {
574     if (!event->mimeData()->hasFormat("toxPk")) {
575         return;
576     }
577     ToxPk toxPk(event->mimeData()->data("toxPk"));
578     Friend* frnd = FriendList::findFriend(toxPk);
579     if (frnd)
580         event->acceptProposedAction();
581 }
582 
dropEvent(QDropEvent * event)583 void FriendListWidget::dropEvent(QDropEvent* event)
584 {
585     // Check, that the element is dropped from qTox
586     QObject* o = event->source();
587     FriendWidget* widget = qobject_cast<FriendWidget*>(o);
588     if (!widget)
589         return;
590 
591     // Check, that the user has a friend with the same ToxPk
592     assert(event->mimeData()->hasFormat("toxPk"));
593     const ToxPk toxPk{event->mimeData()->data("toxPk")};
594     Friend* f = FriendList::findFriend(toxPk);
595     if (!f)
596         return;
597 
598     // Save CircleWidget before changing the Id
599     int circleId = Settings::getInstance().getFriendCircleID(f->getPublicKey());
600     CircleWidget* circleWidget = CircleWidget::getFromID(circleId);
601 
602     moveWidget(widget, f->getStatus(), true);
603 
604     if (circleWidget)
605         circleWidget->updateStatus();
606 }
607 
dayTimeout()608 void FriendListWidget::dayTimeout()
609 {
610     if (mode == SortingMode::Activity) {
611         setMode(SortingMode::Name);
612         setMode(SortingMode::Activity); // Refresh all.
613     }
614 
615     dayTimer->start(timeUntilTomorrow());
616 }
617 
moveWidget(FriendWidget * widget,Status::Status s,bool add)618 void FriendListWidget::moveWidget(FriendWidget* widget, Status::Status s, bool add)
619 {
620     if (mode == SortingMode::Name) {
621         const Friend* f = widget->getFriend();
622         int circleId = Settings::getInstance().getFriendCircleID(f->getPublicKey());
623         CircleWidget* circleWidget = CircleWidget::getFromID(circleId);
624 
625         if (circleWidget == nullptr || add) {
626             if (circleId != -1)
627                 Settings::getInstance().setFriendCircleID(f->getPublicKey(), -1);
628 
629             listLayout->addFriendWidget(widget, s);
630             return;
631         }
632 
633         circleWidget->addFriendWidget(widget, s);
634     } else {
635         const Friend* contact = widget->getFriend();
636         auto* categoryWidget = getTimeCategoryWidget(contact);
637         categoryWidget->addFriendWidget(widget, contact->getStatus());
638         categoryWidget->show();
639     }
640 }
641 
updateActivityTime(const QDateTime & time)642 void FriendListWidget::updateActivityTime(const QDateTime& time)
643 {
644     if (mode != SortingMode::Activity)
645         return;
646 
647     int timeIndex = static_cast<int>(getTimeBucket(time));
648     QWidget* widget = activityLayout->itemAt(timeIndex)->widget();
649     CategoryWidget* categoryWidget = static_cast<CategoryWidget*>(widget);
650     categoryWidget->updateStatus();
651 
652     categoryWidget->setVisible(categoryWidget->hasChatrooms());
653 }
654 
655 // update widget after add/delete/hide/show
reDraw()656 void FriendListWidget::reDraw()
657 {
658     hide();
659     show();
660     resize(QSize()); // lifehack
661 }
662 
createCircleWidget(int id)663 CircleWidget* FriendListWidget::createCircleWidget(int id)
664 {
665     if (id == -1)
666         id = Settings::getInstance().addCircle();
667 
668     // Stop, after it has been created. Code after this is for displaying.
669     if (mode == SortingMode::Activity)
670         return nullptr;
671 
672     assert(circleLayout != nullptr);
673 
674     CircleWidget* circleWidget = new CircleWidget(this, id);
675     emit connectCircleWidget(*circleWidget);
676     circleLayout->addSortedWidget(circleWidget);
677     connect(this, &FriendListWidget::onCompactChanged, circleWidget, &CircleWidget::onCompactChanged);
678     connect(circleWidget, &CircleWidget::renameRequested, this, &FriendListWidget::renameCircleWidget);
679     circleWidget->show(); // Avoid flickering.
680 
681     return circleWidget;
682 }
683 
nextLayout(QLayout * layout,bool forward) const684 QLayout* FriendListWidget::nextLayout(QLayout* layout, bool forward) const
685 {
686     if (layout == groupLayout.getLayout()) {
687         if (forward) {
688             if (groupsOnTop)
689                 return listLayout->getLayoutOnline();
690 
691             return listLayout->getLayoutOffline();
692         } else {
693             if (groupsOnTop)
694                 return circleLayout->getLayout();
695 
696             return listLayout->getLayoutOnline();
697         }
698     } else if (layout == listLayout->getLayoutOnline()) {
699         if (forward) {
700             if (groupsOnTop)
701                 return listLayout->getLayoutOffline();
702 
703             return groupLayout.getLayout();
704         } else {
705             if (groupsOnTop)
706                 return groupLayout.getLayout();
707 
708             return circleLayout->getLayout();
709         }
710     } else if (layout == listLayout->getLayoutOffline()) {
711         if (forward)
712             return circleLayout->getLayout();
713         else if (groupsOnTop)
714             return listLayout->getLayoutOnline();
715 
716         return groupLayout.getLayout();
717     } else if (layout == circleLayout->getLayout()) {
718         if (forward) {
719             if (groupsOnTop)
720                 return groupLayout.getLayout();
721 
722             return listLayout->getLayoutOnline();
723         } else
724             return listLayout->getLayoutOffline();
725     }
726     return nullptr;
727 }
728