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 #include "notificationscrollarea.h"
20 #include "genericchatroomwidget.h"
21 #include "notificationedgewidget.h"
22 #include <QScrollBar>
23 #include <cassert>
24 
NotificationScrollArea(QWidget * parent)25 NotificationScrollArea::NotificationScrollArea(QWidget* parent)
26     : AdjustingScrollArea(parent)
27 {
28     connect(verticalScrollBar(), &QAbstractSlider::valueChanged, this,
29             &NotificationScrollArea::updateVisualTracking);
30     connect(verticalScrollBar(), &QAbstractSlider::rangeChanged, this,
31             &NotificationScrollArea::updateVisualTracking);
32 }
33 
trackWidget(GenericChatroomWidget * widget)34 void NotificationScrollArea::trackWidget(GenericChatroomWidget* widget)
35 {
36     if (trackedWidgets.find(widget) != trackedWidgets.end())
37         return;
38 
39     Visibility visibility = widgetVisible(widget);
40     if (visibility != Visible) {
41         if (visibility == Above) {
42             if (referencesAbove++ == 0) {
43                 assert(topEdge == nullptr);
44                 topEdge = new NotificationEdgeWidget(NotificationEdgeWidget::Top, this);
45                 connect(topEdge, &NotificationEdgeWidget::clicked, this,
46                         &NotificationScrollArea::findPreviousWidget);
47                 recalculateTopEdge();
48                 topEdge->show();
49             }
50             topEdge->updateNotificationCount(referencesAbove);
51         } else {
52             if (referencesBelow++ == 0) {
53                 assert(bottomEdge == nullptr);
54                 bottomEdge = new NotificationEdgeWidget(NotificationEdgeWidget::Bottom, this);
55                 connect(bottomEdge, &NotificationEdgeWidget::clicked, this,
56                         &NotificationScrollArea::findNextWidget);
57                 recalculateBottomEdge();
58                 bottomEdge->show();
59             }
60             bottomEdge->updateNotificationCount(referencesBelow);
61         }
62 
63         trackedWidgets.insert(widget, visibility);
64     }
65 }
66 
67 /**
68  * @brief Delete notification bar from visible elements on scroll area
69  */
updateVisualTracking()70 void NotificationScrollArea::updateVisualTracking()
71 {
72     updateTracking(nullptr);
73 }
74 
75 /**
76  * @brief Delete notification bar from visible elements and widget on scroll area
77  * @param widget Chatroom widget to remove from tracked widgets
78  */
updateTracking(GenericChatroomWidget * widget)79 void NotificationScrollArea::updateTracking(GenericChatroomWidget* widget)
80 {
81     QHash<GenericChatroomWidget*, Visibility>::iterator i = trackedWidgets.begin();
82     while (i != trackedWidgets.end()) {
83         if (i.key() == widget || widgetVisible(i.key()) == Visible) {
84             if (i.value() == Above) {
85                 if (--referencesAbove == 0) {
86                     topEdge->deleteLater();
87                     topEdge = nullptr;
88                 } else {
89                     topEdge->updateNotificationCount(referencesAbove);
90                 }
91             } else {
92                 if (--referencesBelow == 0) {
93                     bottomEdge->deleteLater();
94                     bottomEdge = nullptr;
95                 } else {
96                     bottomEdge->updateNotificationCount(referencesBelow);
97                 }
98             }
99             i = trackedWidgets.erase(i);
100             continue;
101         }
102         ++i;
103     }
104 }
105 
resizeEvent(QResizeEvent * event)106 void NotificationScrollArea::resizeEvent(QResizeEvent* event)
107 {
108     if (topEdge != nullptr)
109         recalculateTopEdge();
110     if (bottomEdge != nullptr)
111         recalculateBottomEdge();
112 
113     AdjustingScrollArea::resizeEvent(event);
114 }
115 
findNextWidget()116 void NotificationScrollArea::findNextWidget()
117 {
118     int value = 0;
119     GenericChatroomWidget* next = nullptr;
120     QHash<GenericChatroomWidget*, Visibility>::iterator i = trackedWidgets.begin();
121 
122     // Find the first next, to avoid nullptr.
123     for (; i != trackedWidgets.end(); ++i) {
124         if (i.value() == Below) {
125             next = i.key();
126             value = next->mapTo(viewport(), QPoint()).y();
127             break;
128         }
129     }
130 
131     // Try finding a closer one.
132     for (; i != trackedWidgets.end(); ++i) {
133         if (i.value() == Below) {
134             int y = i.key()->mapTo(viewport(), QPoint()).y();
135             if (y < value) {
136                 next = i.key();
137                 value = y;
138             }
139         }
140     }
141 
142     if (next != nullptr)
143         ensureWidgetVisible(next, 0, referencesBelow != 1 ? bottomEdge->height() : 0);
144 }
145 
findPreviousWidget()146 void NotificationScrollArea::findPreviousWidget()
147 {
148     int value = 0;
149     GenericChatroomWidget* next = nullptr;
150     QHash<GenericChatroomWidget*, Visibility>::iterator i = trackedWidgets.begin();
151 
152     // Find the first next, to avoid nullptr.
153     for (; i != trackedWidgets.end(); ++i) {
154         if (i.value() == Above) {
155             next = i.key();
156             value = next->mapTo(viewport(), QPoint()).y();
157             break;
158         }
159     }
160 
161     // Try finding a closer one.
162     for (; i != trackedWidgets.end(); ++i) {
163         if (i.value() == Above) {
164             int y = i.key()->mapTo(viewport(), QPoint()).y();
165             if (y > value) {
166                 next = i.key();
167                 value = y;
168             }
169         }
170     }
171 
172     if (next != nullptr)
173         ensureWidgetVisible(next, 0, referencesAbove != 1 ? topEdge->height() : 0);
174 }
175 
widgetVisible(QWidget * widget) const176 NotificationScrollArea::Visibility NotificationScrollArea::widgetVisible(QWidget* widget) const
177 {
178     int y = widget->mapTo(viewport(), QPoint()).y();
179 
180     if (y < 0)
181         return Above;
182     else if (y + widget->height() - 1 > viewport()->height())
183         return Below;
184 
185     return Visible;
186 }
187 
recalculateTopEdge()188 void NotificationScrollArea::recalculateTopEdge()
189 {
190     topEdge->move(viewport()->pos());
191     topEdge->resize(viewport()->width(), topEdge->height());
192 }
193 
recalculateBottomEdge()194 void NotificationScrollArea::recalculateBottomEdge()
195 {
196     QPoint position = viewport()->pos();
197     position.setY(position.y() + viewport()->height() - bottomEdge->height());
198     bottomEdge->move(position);
199     bottomEdge->resize(viewport()->width(), bottomEdge->height());
200 }
201