1 /*
2     Copyright © 2015-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 "categorywidget.h"
21 #include "friendlistlayout.h"
22 #include "friendlistwidget.h"
23 #include "friendwidget.h"
24 #include "src/model/status.h"
25 #include "src/widget/style.h"
26 #include "tool/croppinglabel.h"
27 #include <QBoxLayout>
28 #include <QMouseEvent>
29 
30 #include <QApplication>
31 
emitChatroomWidget(QLayout * layout,int index)32 void CategoryWidget::emitChatroomWidget(QLayout* layout, int index)
33 {
34     QWidget* widget = layout->itemAt(index)->widget();
35     GenericChatroomWidget* chatWidget = qobject_cast<GenericChatroomWidget*>(widget);
36     if (chatWidget != nullptr) {
37         emit chatWidget->chatroomWidgetClicked(chatWidget);
38     }
39 }
40 
CategoryWidget(bool compact,QWidget * parent)41 CategoryWidget::CategoryWidget(bool compact, QWidget* parent)
42     : GenericChatItemWidget(compact, parent)
43 {
44     container = new QWidget(this);
45     container->setObjectName("circleWidgetContainer");
46     container->setLayoutDirection(Qt::LeftToRight);
47 
48     statusLabel = new QLabel(this);
49     statusLabel->setObjectName("status");
50     statusLabel->setTextFormat(Qt::PlainText);
51 
52     statusPic.setPixmap(QPixmap(Style::getImagePath("chatArea/scrollBarRightArrow.svg")));
53 
54     fullLayout = new QVBoxLayout(this);
55     fullLayout->setSpacing(0);
56     fullLayout->setMargin(0);
57     fullLayout->addWidget(container);
58 
59     lineFrame = new QFrame(container);
60     lineFrame->setObjectName("line");
61     lineFrame->setFrameShape(QFrame::HLine);
62     lineFrame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
63     lineFrame->resize(0, 0);
64 
65     listLayout = new FriendListLayout();
66     listWidget = new QWidget(this);
67     listWidget->setLayout(listLayout);
68     fullLayout->addWidget(listWidget);
69 
70     setAcceptDrops(true);
71 
72     onCompactChanged(isCompact());
73 
74     setExpanded(true, false);
75     updateStatus();
76 }
77 
isExpanded() const78 bool CategoryWidget::isExpanded() const
79 {
80     return expanded;
81 }
82 
setExpanded(bool isExpanded,bool save)83 void CategoryWidget::setExpanded(bool isExpanded, bool save)
84 {
85     if (expanded == isExpanded) {
86         return;
87     }
88     expanded = isExpanded;
89     setMouseTracking(true);
90     listWidget->setVisible(isExpanded);
91 
92     QString pixmapPath;
93     if (isExpanded)
94         pixmapPath = Style::getImagePath("chatArea/scrollBarDownArrow.svg");
95     else
96         pixmapPath = Style::getImagePath("chatArea/scrollBarRightArrow.svg");
97     statusPic.setPixmap(QPixmap(pixmapPath));
98     // The listWidget will recieve a enterEvent for some reason if now visible.
99     // Using the following, we prevent that.
100     QApplication::processEvents(QEventLoop::ExcludeSocketNotifiers);
101     container->hide();
102     container->show();
103 
104     if (save)
105         onExpand();
106 }
107 
leaveEvent(QEvent * event)108 void CategoryWidget::leaveEvent(QEvent* event)
109 {
110     event->ignore();
111 }
112 
setName(const QString & name,bool save)113 void CategoryWidget::setName(const QString& name, bool save)
114 {
115     nameLabel->setText(name);
116 
117     if (isCompact())
118         nameLabel->minimizeMaximumWidth();
119 
120     if (save)
121         onSetName();
122 }
123 
editName()124 void CategoryWidget::editName()
125 {
126     nameLabel->editBegin();
127     nameLabel->setMaximumWidth(QWIDGETSIZE_MAX);
128 }
129 
addFriendWidget(FriendWidget * w,Status::Status s)130 void CategoryWidget::addFriendWidget(FriendWidget* w, Status::Status s)
131 {
132     listLayout->addFriendWidget(w, s);
133     updateStatus();
134     onAddFriendWidget(w);
135     w->reloadTheme(); // Otherwise theme will change when moving to another circle.
136 }
137 
removeFriendWidget(FriendWidget * w,Status::Status s)138 void CategoryWidget::removeFriendWidget(FriendWidget* w, Status::Status s)
139 {
140     listLayout->removeFriendWidget(w, s);
141     updateStatus();
142 }
143 
updateStatus()144 void CategoryWidget::updateStatus()
145 {
146     QString online = QString::number(listLayout->friendOnlineCount());
147     QString offline = QString::number(listLayout->friendTotalCount());
148     QString text = online + QStringLiteral(" / ") + offline;
149     statusLabel->setText(text);
150 }
151 
hasChatrooms() const152 bool CategoryWidget::hasChatrooms() const
153 {
154     return listLayout->hasChatrooms();
155 }
156 
search(const QString & searchString,bool updateAll,bool hideOnline,bool hideOffline)157 void CategoryWidget::search(const QString& searchString, bool updateAll, bool hideOnline,
158                             bool hideOffline)
159 {
160     if (updateAll) {
161         listLayout->searchChatrooms(searchString, hideOnline, hideOffline);
162     }
163     bool inCategory = searchString.isEmpty() && !(hideOnline && hideOffline);
164     setVisible(inCategory || listLayout->hasChatrooms());
165 }
166 
cycleContacts(bool forward)167 bool CategoryWidget::cycleContacts(bool forward)
168 {
169     if (listLayout->friendTotalCount() == 0) {
170         return false;
171     }
172     if (forward) {
173         if (!listLayout->getLayoutOnline()->isEmpty()) {
174             setExpanded(true);
175             emitChatroomWidget(listLayout->getLayoutOnline(), 0);
176             return true;
177         } else if (!listLayout->getLayoutOffline()->isEmpty()) {
178             setExpanded(true);
179             emitChatroomWidget(listLayout->getLayoutOffline(), 0);
180             return true;
181         }
182     } else {
183         if (!listLayout->getLayoutOffline()->isEmpty()) {
184             setExpanded(true);
185             emitChatroomWidget(listLayout->getLayoutOffline(),
186                                listLayout->getLayoutOffline()->count() - 1);
187             return true;
188         } else if (!listLayout->getLayoutOnline()->isEmpty()) {
189             setExpanded(true);
190             emitChatroomWidget(listLayout->getLayoutOnline(),
191                                listLayout->getLayoutOnline()->count() - 1);
192             return true;
193         }
194     }
195     return false;
196 }
197 
cycleContacts(FriendWidget * activeChatroomWidget,bool forward)198 bool CategoryWidget::cycleContacts(FriendWidget* activeChatroomWidget, bool forward)
199 {
200     int index = -1;
201     QLayout* currentLayout = nullptr;
202 
203     FriendWidget* friendWidget = qobject_cast<FriendWidget*>(activeChatroomWidget);
204     if (friendWidget == nullptr)
205         return false;
206 
207     currentLayout = listLayout->getLayoutOnline();
208     index = listLayout->indexOfFriendWidget(friendWidget, true);
209     if (index == -1) {
210         currentLayout = listLayout->getLayoutOffline();
211         index = listLayout->indexOfFriendWidget(friendWidget, false);
212     }
213 
214     index += forward ? 1 : -1;
215     for (;;) {
216         // Bounds checking.
217         if (index < 0) {
218             if (currentLayout == listLayout->getLayoutOffline())
219                 currentLayout = listLayout->getLayoutOnline();
220             else
221                 return false;
222 
223             index = currentLayout->count() - 1;
224             continue;
225         } else if (index >= currentLayout->count()) {
226             if (currentLayout == listLayout->getLayoutOnline())
227                 currentLayout = listLayout->getLayoutOffline();
228             else
229                 return false;
230 
231             index = 0;
232             continue;
233         }
234 
235         GenericChatroomWidget* chatWidget =
236             qobject_cast<GenericChatroomWidget*>(currentLayout->itemAt(index)->widget());
237         if (chatWidget != nullptr)
238             emit chatWidget->chatroomWidgetClicked(chatWidget);
239         return true;
240     }
241 
242     return false;
243 }
244 
onCompactChanged(bool _compact)245 void CategoryWidget::onCompactChanged(bool _compact)
246 {
247     delete topLayout;
248     delete mainLayout;
249 
250     topLayout = new QHBoxLayout;
251     topLayout->setSpacing(0);
252     topLayout->setMargin(0);
253 
254     Q_UNUSED(_compact);
255     setCompact(true);
256 
257     nameLabel->minimizeMaximumWidth();
258 
259     mainLayout = nullptr;
260 
261     container->setFixedHeight(25);
262     container->setLayout(topLayout);
263 
264     topLayout->addSpacing(18);
265     topLayout->addWidget(&statusPic);
266     topLayout->addSpacing(5);
267     topLayout->addWidget(nameLabel, 100);
268     topLayout->addWidget(lineFrame, 1);
269     topLayout->addSpacing(5);
270     topLayout->addWidget(statusLabel);
271     topLayout->addSpacing(5);
272     topLayout->activate();
273 
274     Style::repolish(this);
275 }
276 
mouseReleaseEvent(QMouseEvent * event)277 void CategoryWidget::mouseReleaseEvent(QMouseEvent* event)
278 {
279     if (event->button() == Qt::LeftButton)
280         setExpanded(!expanded);
281 }
282 
setContainerAttribute(Qt::WidgetAttribute attribute,bool enabled)283 void CategoryWidget::setContainerAttribute(Qt::WidgetAttribute attribute, bool enabled)
284 {
285     container->setAttribute(attribute, enabled);
286     Style::repolish(container);
287 }
288 
friendOfflineLayout() const289 QLayout* CategoryWidget::friendOfflineLayout() const
290 {
291     return listLayout->getLayoutOffline();
292 }
293 
friendOnlineLayout() const294 QLayout* CategoryWidget::friendOnlineLayout() const
295 {
296     return listLayout->getLayoutOnline();
297 }
298 
moveFriendWidgets(FriendListWidget * friendList)299 void CategoryWidget::moveFriendWidgets(FriendListWidget* friendList)
300 {
301     listLayout->moveFriendWidgets(friendList);
302 }
303