Implement A2L presets and custom threshold handling
[pulseview.git] / pv / widgets / popup.cpp
1 /*
2  * This file is part of the PulseView project.
3  *
4  * Copyright (C) 2013 Joel Holdsworth <joel@airwebreathe.org.uk>
5  *
6  * This program is free 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 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program 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 this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <algorithm>
21 #include <cassert>
22
23 #include <QApplication>
24 #include <QDesktopWidget>
25 #include <QLineEdit>
26 #include <QtGui>
27
28 #include "popup.hpp"
29
30 using std::max;
31 using std::min;
32
33 namespace pv {
34 namespace widgets {
35
36 const unsigned int Popup::ArrowLength = 10;
37 const unsigned int Popup::ArrowOverlap = 3;
38 const unsigned int Popup::MarginWidth = 6;
39
40 Popup::Popup(QWidget *parent) :
41         QWidget(parent, Qt::Popup | Qt::FramelessWindowHint),
42         point_(),
43         pos_(Left)
44 {
45 }
46
47 const QPoint& Popup::point() const
48 {
49         return point_;
50 }
51
52 Popup::Position Popup::position() const
53 {
54         return pos_;
55 }
56
57 void Popup::set_position(const QPoint point, Position pos)
58 {
59         point_ = point, pos_ = pos;
60
61         setContentsMargins(
62                 MarginWidth + ((pos == Right) ? ArrowLength : 0),
63                 MarginWidth + ((pos == Bottom) ? ArrowLength : 0),
64                 MarginWidth + ((pos == Left) ? ArrowLength : 0),
65                 MarginWidth + ((pos == Top) ? ArrowLength : 0));
66 }
67
68 bool Popup::eventFilter(QObject *obj, QEvent *event)
69 {
70         QKeyEvent *keyEvent;
71
72         (void)obj;
73
74         if (event->type() == QEvent::KeyPress) {
75                 keyEvent = static_cast<QKeyEvent*>(event);
76                 if (keyEvent->key() == Qt::Key_Enter ||
77                     keyEvent->key() == Qt::Key_Return) {
78                         close();
79                         return true;
80                 }
81         }
82
83         return false;
84 }
85
86 void Popup::show()
87 {
88         QLineEdit* le;
89
90         QWidget::show();
91
92         // We want to close the popup when the Enter key was
93         // pressed and the first editable widget had focus.
94         if ((le = this->findChild<QLineEdit*>())) {
95
96                 // For combo boxes we need to hook into the parent of
97                 // the line edit (i.e. the QComboBox). For edit boxes
98                 // we hook into the widget directly.
99                 if (le->parent()->metaObject()->className() ==
100                                 this->metaObject()->className())
101                         le->installEventFilter(this);
102                 else
103                         le->parent()->installEventFilter(this);
104
105                 le->selectAll();
106                 le->setFocus();
107         }
108 }
109
110 bool Popup::space_for_arrow() const
111 {
112         // Check if there is room for the arrow
113         switch (pos_) {
114         case Right:
115                 if (point_.x() > x())
116                         return false;
117                 return true;
118
119         case Bottom:
120                 if (point_.y() > y())
121                         return false;
122                 return true;
123
124         case Left:
125                 if (point_.x() < (x() + width()))
126                         return false;
127                 return true;
128
129         case Top:
130                 if (point_.y() < (y() + height()))
131                         return false;
132                 return true;
133         }
134
135         return true;
136 }
137
138 QPolygon Popup::arrow_polygon() const
139 {
140         QPolygon poly;
141
142         const QPoint p = mapFromGlobal(point_);
143         const int l = ArrowLength + ArrowOverlap;
144
145         switch (pos_) {
146         case Right:
147                 poly << QPoint(p.x() + l, p.y() - l);
148                 break;
149
150         case Bottom:
151                 poly << QPoint(p.x() - l, p.y() + l);
152                 break;
153
154         case Left:
155         case Top:
156                 poly << QPoint(p.x() - l, p.y() - l);
157                 break;
158         }
159
160         poly << p;
161
162         switch (pos_) {
163         case Right:
164         case Bottom:
165                 poly << QPoint(p.x() + l, p.y() + l);
166                 break;
167
168         case Left:
169                 poly << QPoint(p.x() - l, p.y() + l);
170                 break;
171
172         case Top:
173                 poly << QPoint(p.x() + l, p.y() - l);
174                 break;
175         }
176
177         return poly;
178 }
179
180 QRegion Popup::arrow_region() const
181 {
182         return QRegion(arrow_polygon());
183 }
184
185 QRect Popup::bubble_rect() const
186 {
187         return QRect(
188                 QPoint((pos_ == Right) ? ArrowLength : 0,
189                         (pos_ == Bottom) ? ArrowLength : 0),
190                 QSize(width() - ((pos_ == Left || pos_ == Right) ?
191                                 ArrowLength : 0),
192                         height() - ((pos_ == Top || pos_ == Bottom) ?
193                                 ArrowLength : 0)));
194 }
195
196 QRegion Popup::bubble_region() const
197 {
198         const QRect rect(bubble_rect());
199
200         const unsigned int r = MarginWidth;
201         const unsigned int d = 2 * r;
202         return QRegion(rect.adjusted(r, 0, -r, 0)).united(
203                 QRegion(rect.adjusted(0, r, 0, -r))).united(
204                 QRegion(rect.left(), rect.top(), d, d,
205                         QRegion::Ellipse)).united(
206                 QRegion(rect.right() - d, rect.top(), d, d,
207                         QRegion::Ellipse)).united(
208                 QRegion(rect.left(), rect.bottom() - d, d, d,
209                         QRegion::Ellipse)).united(
210                 QRegion(rect.right() - d, rect.bottom() - d, d, d,
211                         QRegion::Ellipse));
212 }
213
214 QRegion Popup::popup_region() const
215 {
216         if (space_for_arrow())
217                 return arrow_region().united(bubble_region());
218         else
219                 return bubble_region();
220 }
221
222 void Popup::reposition_widget()
223 {
224         QPoint o;
225
226         const QRect screen_rect = QApplication::desktop()->availableGeometry(
227                 QApplication::desktop()->screenNumber(point_));
228
229         if (pos_ == Right || pos_ == Left)
230                 o.ry() = -height() / 2;
231         else
232                 o.rx() = -width() / 2;
233
234         if (pos_ == Left)
235                 o.rx() = -width();
236         else if (pos_ == Top)
237                 o.ry() = -height();
238
239         o += point_;
240         move(max(min(o.x(), screen_rect.right() - width()),
241                         screen_rect.left()),
242                 max(min(o.y(), screen_rect.bottom() - height()),
243                         screen_rect.top()));
244 }
245
246 void Popup::closeEvent(QCloseEvent*)
247 {
248         closed();
249 }
250
251 void Popup::paintEvent(QPaintEvent*)
252 {
253         QPainter painter(this);
254         painter.setRenderHint(QPainter::Antialiasing);
255
256         const QColor outline_color(QApplication::palette().color(
257                 QPalette::Dark));
258
259         // Draw the bubble
260         const QRegion b = bubble_region();
261         const QRegion bubble_outline = QRegion(rect()).subtracted(
262                 b.translated(1, 0).intersected(b.translated(0, 1).intersected(
263                 b.translated(-1, 0).intersected(b.translated(0, -1)))));
264
265         painter.setPen(Qt::NoPen);
266         painter.setBrush(QApplication::palette().brush(QPalette::Window));
267         painter.drawRect(rect());
268
269         // Draw the arrow
270         if (!space_for_arrow())
271                 return;
272
273         const QPoint ArrowOffsets[] = {
274                 QPoint(1, 0), QPoint(0, -1), QPoint(-1, 0), QPoint(0, 1)};
275
276         const QRegion a(arrow_region());
277         const QRegion arrow_outline = a.subtracted(
278                 a.translated(ArrowOffsets[pos_]));
279
280         painter.setClipRegion(bubble_outline.subtracted(a).united(
281                 arrow_outline));
282         painter.setBrush(outline_color);
283         painter.drawRect(rect());
284 }
285
286 void Popup::resizeEvent(QResizeEvent*)
287 {
288         reposition_widget();
289         setMask(popup_region());
290 }
291
292 void Popup::mouseReleaseEvent(QMouseEvent *event)
293 {
294         assert(event);
295
296         // We need our own out-of-bounds click handler because QWidget counts
297         // the drop-shadow region as inside the widget
298         if (!bubble_rect().contains(event->pos()))
299                 close();
300 }
301
302 void Popup::showEvent(QShowEvent*)
303 {
304         reposition_widget();
305 }
306
307 } // namespace widgets
308 } // namespace pv