1 /*
2     Copyright © 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 "genericchatitemlayout.h"
21 #include "genericchatitemwidget.h"
22 #include <QBoxLayout>
23 #include <QCollator>
24 #include <cassert>
25 
26 // As this layout sorts widget, extra care must be taken when inserting widgets.
27 // Prefer using the build in add and remove functions for modifying widgets.
28 // Inserting widgets other ways would cause this layout to be unable to sort.
29 // As such, they are protected using asserts.
30 
GenericChatItemLayout()31 GenericChatItemLayout::GenericChatItemLayout()
32     : layout(new QVBoxLayout())
33 {
34 }
35 
~GenericChatItemLayout()36 GenericChatItemLayout::~GenericChatItemLayout()
37 {
38     delete layout;
39 }
40 
addSortedWidget(GenericChatItemWidget * widget,int stretch,Qt::Alignment alignment)41 void GenericChatItemLayout::addSortedWidget(GenericChatItemWidget* widget, int stretch,
42                                             Qt::Alignment alignment)
43 {
44     int closest = indexOfClosestSortedWidget(widget);
45     layout->insertWidget(closest, widget, stretch, alignment);
46 }
47 
indexOfSortedWidget(GenericChatItemWidget * widget) const48 int GenericChatItemLayout::indexOfSortedWidget(GenericChatItemWidget* widget) const
49 {
50     if (layout->isEmpty())
51         return -1;
52 
53     int index = indexOfClosestSortedWidget(widget);
54 
55     if (index >= layout->count())
56         return -1;
57 
58     GenericChatItemWidget* atMid =
59         qobject_cast<GenericChatItemWidget*>(layout->itemAt(index)->widget());
60     assert(atMid != nullptr);
61 
62     if (atMid == widget)
63         return index;
64 
65     return -1;
66 }
67 
existsSortedWidget(GenericChatItemWidget * widget) const68 bool GenericChatItemLayout::existsSortedWidget(GenericChatItemWidget* widget) const
69 {
70     return indexOfSortedWidget(widget) != -1;
71 }
72 
removeSortedWidget(GenericChatItemWidget * widget)73 void GenericChatItemLayout::removeSortedWidget(GenericChatItemWidget* widget)
74 {
75     if (layout->isEmpty())
76         return;
77 
78     int index = indexOfClosestSortedWidget(widget);
79 
80     if (layout->itemAt(index) == nullptr)
81         return;
82 
83     GenericChatItemWidget* atMid =
84         qobject_cast<GenericChatItemWidget*>(layout->itemAt(index)->widget());
85     assert(atMid != nullptr);
86 
87     if (atMid == widget)
88         layout->removeWidget(widget);
89 }
90 
search(const QString & searchString,bool hideAll)91 void GenericChatItemLayout::search(const QString& searchString, bool hideAll)
92 {
93     for (int index = 0; index < layout->count(); ++index) {
94         GenericChatItemWidget* widgetAt =
95             qobject_cast<GenericChatItemWidget*>(layout->itemAt(index)->widget());
96         assert(widgetAt != nullptr);
97 
98         widgetAt->searchName(searchString, hideAll);
99     }
100 }
101 
getLayout() const102 QLayout* GenericChatItemLayout::getLayout() const
103 {
104     return layout;
105 }
106 
indexOfClosestSortedWidget(GenericChatItemWidget * widget) const107 int GenericChatItemLayout::indexOfClosestSortedWidget(GenericChatItemWidget* widget) const
108 {
109     // Binary search: Deferred test of equality.
110     int min = 0, max = layout->count();
111     while (min < max) {
112         int mid = (max - min) / 2 + min;
113         GenericChatItemWidget* atMid =
114             qobject_cast<GenericChatItemWidget*>(layout->itemAt(mid)->widget());
115         assert(atMid != nullptr);
116 
117         bool lessThan = false;
118 
119         QCollator collator;
120         collator.setNumericMode(true);
121 
122         int compareValue = collator.compare(atMid->getName(), widget->getName());
123 
124         if (compareValue < 0)
125             lessThan = true;
126         else if (compareValue == 0)
127             lessThan = atMid < widget; // Consistent ordering.
128 
129         if (lessThan)
130             min = mid + 1;
131         else
132             max = mid;
133     }
134     return min;
135 }
136