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