FreeCAD

Форк
0
/
NotificationBox.cpp 
424 строки · 13.7 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2023 Abdullah Tahiri <abdullah.tahiri.yo@gmail.com>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
6
 *   This library is free software; you can redistribute it and/or         *
7
 *   modify it under the terms of the GNU Library General Public           *
8
 *   License as published by the Free Software Foundation; either          *
9
 *   version 2 of the License, or (at your option) any later version.      *
10
 *                                                                         *
11
 *   This library  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 Library General Public License for more details.                  *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU Library General Public     *
17
 *   License along with this library; see the file COPYING.LIB. If not,    *
18
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
19
 *   Suite 330, Boston, MA  02111-1307, USA                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23
#include "PreCompiled.h"
24
#ifndef _PreComp_
25
#include <QApplication>
26
#include <QEvent>
27
#include <QLabel>
28
#include <QScreen>
29
#include <QStyleOption>
30
#include <QStylePainter>
31
#include <QTextDocument>
32
#include <QTimer>
33
#include <memory>
34
#include <mutex>
35
#endif
36

37
#include "NotificationBox.h"
38

39

40
using namespace Gui;
41

42
namespace Gui
43
{
44

45
// https://stackoverflow.com/questions/41402152/stdunique-ptr-and-qobjectdeletelater
46
struct QObjectDeleteLater
47
{
48
    void operator()(QObject* o)
49
    {
50
        o->deleteLater();
51
    }
52
};
53

54
template<typename T>
55
using qobject_delete_later_unique_ptr = std::unique_ptr<T, QObjectDeleteLater>;
56

57
/** Class showing the notification as a label
58
 * */
59
class NotificationLabel: public QLabel
60
{
61
    Q_OBJECT
62
public:
63
    NotificationLabel(const QString& text, const QPoint& pos, int displayTime, int minShowTime = 0,
64
                      int width = 0);
65
    /// Reuse existing notification to show a new notification (with a new text)
66
    void reuseNotification(const QString& text, int displayTime, const QPoint& pos, int width);
67
    /// Hide notification after a hiding timer.
68
    void hideNotification();
69
    /// Update the size of the QLabel
70
    void updateSize(const QPoint& pos);
71
    /// Event filter
72
    bool eventFilter(QObject*, QEvent*) override;
73
    /// Return true if the text provided is the same as the one of an existing notification
74
    bool notificationLabelChanged(const QString& text);
75
    /// Place the notification at the given position
76
    void placeNotificationLabel(const QPoint& pos);
77
    /// Set the windowrect defining an area to which the label should be constrained
78
    void setTipRect(const QRect& restrictionarea);
79

80
    void setHideIfReferenceWidgetDeactivated(bool on);
81

82
    /// The instance
83
    static qobject_delete_later_unique_ptr<NotificationLabel> instance;
84

85
protected:
86
    void paintEvent(QPaintEvent* e) override;
87
    void resizeEvent(QResizeEvent* e) override;
88

89
private:
90
    /// Re-start the notification expiration timer
91
    void restartExpireTimer(int displayTime);
92
    /// Hide notification right away
93
    void hideNotificationImmediately();
94

95
private:
96
    int minShowTime;
97
    QTimer hideTimer;
98
    QTimer expireTimer;
99

100
    QRect restrictionArea;
101
    bool hideIfReferenceWidgetDeactivated;
102
};
103

104
qobject_delete_later_unique_ptr<NotificationLabel> NotificationLabel::instance = nullptr;
105

106
NotificationLabel::NotificationLabel(const QString& text, const QPoint& pos, int displayTime,
107
                                     int minShowTime, int width)
108
    : QLabel(nullptr, Qt::ToolTip | Qt::BypassGraphicsProxyWidget),
109
      minShowTime(minShowTime)
110
{
111
    instance.reset(this);
112
    setForegroundRole(QPalette::ToolTipText);// defaults to ToolTip QPalette
113
    setBackgroundRole(QPalette::ToolTipBase);// defaults to ToolTip QPalette
114
    setPalette(NotificationBox::palette());
115
    ensurePolished();
116
    setMargin(1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, nullptr, this));
117
    setFrameStyle(QFrame::NoFrame);
118
    setAlignment(Qt::AlignLeft);
119
    setIndent(1);
120
    qApp->installEventFilter(this);
121
    setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, nullptr, this) / 255.0);
122
    setMouseTracking(false);
123
    hideTimer.setSingleShot(true);
124
    expireTimer.setSingleShot(true);
125

126
    expireTimer.callOnTimeout([this]() {
127
        hideTimer.stop();
128
        hideNotificationImmediately();
129
    });
130

131
    hideTimer.callOnTimeout([this]() {
132
        expireTimer.stop();
133
        hideNotificationImmediately();
134
    });
135

136
    reuseNotification(text, displayTime, pos, width);
137
}
138

139
void NotificationLabel::restartExpireTimer(int displayTime)
140
{
141
    int time;
142

143
    if (displayTime > 0) {
144
        time = displayTime;
145
    }
146
    else {
147
        time = 10000 + 40 * qMax(0, text().length() - 100);
148
    }
149

150
    expireTimer.start(time);
151
    hideTimer.stop();
152
}
153

154
void NotificationLabel::reuseNotification(const QString& text, int displayTime, const QPoint& pos,
155
                                          int width)
156
{
157
    if (width > 0)
158
        setFixedWidth(width);
159

160
    setText(text);
161
    updateSize(pos);
162
    restartExpireTimer(displayTime);
163
}
164

165
void NotificationLabel::updateSize(const QPoint& pos)
166
{
167
    // Ensure that we get correct sizeHints by placing this window on the right screen.
168
    QFontMetrics fm(font());
169
    QSize extra(1, 0);
170
    // Make it look good with the default ToolTip font on Mac, which has a small descent.
171
    if (fm.descent() == 2 && fm.ascent() >= 11) {
172
        ++extra.rheight();
173
    }
174

175
    setWordWrap(Qt::mightBeRichText(text()));
176

177
    QSize sh = sizeHint();
178

179
    // ### When the above WinRT code is fixed, windowhandle should be used to find the screen.
180
    QScreen* screen = QGuiApplication::screenAt(pos);
181

182
    if (!screen) {
183
        screen = QGuiApplication::primaryScreen();
184
    }
185

186
    if (screen) {
187
        const qreal screenWidth = screen->geometry().width();
188
        if (!wordWrap() && sh.width() > screenWidth) {
189
            setWordWrap(true);
190
            sh = sizeHint();
191
        }
192
    }
193

194
    resize(sh + extra);
195
}
196

197
void NotificationLabel::paintEvent(QPaintEvent* ev)
198
{
199
    QStylePainter p(this);
200
    QStyleOptionFrame opt;
201
    opt.initFrom(this);
202
    p.drawPrimitive(QStyle::PE_PanelTipLabel, opt);
203
    p.end();
204
    QLabel::paintEvent(ev);
205
}
206

207
void NotificationLabel::resizeEvent(QResizeEvent* e)
208
{
209
    QStyleHintReturnMask frameMask;
210
    QStyleOption option;
211

212
    option.initFrom(this);
213

214
    if (style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask)) {
215
        setMask(frameMask.region);
216
    }
217

218
    QLabel::resizeEvent(e);
219
}
220

221
void NotificationLabel::hideNotification()
222
{
223
    if (!hideTimer.isActive()) {
224
        hideTimer.start(300);
225
    }
226
}
227

228
void NotificationLabel::hideNotificationImmediately()
229
{
230
    close();// to trigger QEvent::Close which stops the animation
231
    instance = nullptr;
232
}
233

234
bool NotificationLabel::eventFilter(QObject* o, QEvent* e)
235
{
236
    Q_UNUSED(o)
237

238
    switch (e->type()) {
239
        case QEvent::MouseButtonPress: {
240
            // If minimum on screen time has already lapsed - hide the notification no matter where
241
            // the click was done
242
            auto total = expireTimer.interval();
243
            auto remaining = expireTimer.remainingTime();
244
            auto lapsed = total - remaining;
245
            // ... or if the click is inside the notification, hide it no matter if the minimum
246
            // onscreen time has lapsed or not
247
            auto insideclick = this->underMouse();
248
            if (lapsed > minShowTime || insideclick) {
249
                hideNotification();
250

251
                return insideclick;
252
            }
253
        } break;
254
        case QEvent::WindowDeactivate:
255
            if (hideIfReferenceWidgetDeactivated)
256
                hideNotificationImmediately();
257
            break;
258

259
        default:
260
            break;
261
    }
262
    return false;
263
}
264

265
void NotificationLabel::placeNotificationLabel(const QPoint& pos)
266
{
267
    QPoint p = pos;
268
    const QScreen* screen = QGuiApplication::screenAt(pos);
269
    // a QScreen's handle *should* never be null, so this is a bit paranoid
270
    if (screen && screen->handle()) {
271
        const QSize cursorSize = QSize(16, 16);
272

273
        QPoint offset(2, cursorSize.height());
274
        // assuming an arrow shape, we can just move to the side for very large cursors
275
        if (cursorSize.height() > 2 * this->height())
276
            offset = QPoint(cursorSize.width() / 2, 0);
277

278
        p += offset;
279

280
        QRect actinglimit = screen->geometry();
281

282
        if (!restrictionArea.isNull())
283
            actinglimit = restrictionArea;
284

285
        const int standard_x_padding = 4;
286
        const int standard_y_padding = 24;
287

288
        if (p.x() + this->width() > actinglimit.x() + actinglimit.width())
289
            p.rx() -= standard_x_padding + this->width();
290
        if (p.y() + standard_y_padding + this->height() > actinglimit.y() + actinglimit.height())
291
            p.ry() -= standard_y_padding + this->height();
292
        if (p.y() < actinglimit.y())
293
            p.setY(actinglimit.y());
294
        if (p.x() + this->width() > actinglimit.x() + actinglimit.width())
295
            p.setX(actinglimit.x() + actinglimit.width() - this->width());
296
        if (p.x() < actinglimit.x())
297
            p.setX(actinglimit.x());
298
        if (p.y() + this->height() > actinglimit.y() + actinglimit.height())
299
            p.setY(actinglimit.y() + actinglimit.height() - this->height());
300
    }
301

302
    this->move(p);
303
}
304

305
void NotificationLabel::setTipRect(const QRect& restrictionarea)
306
{
307
    restrictionArea = restrictionarea;
308
}
309

310
void NotificationLabel::setHideIfReferenceWidgetDeactivated(bool on)
311
{
312
    hideIfReferenceWidgetDeactivated = on;
313
}
314

315
bool NotificationLabel::notificationLabelChanged(const QString& text)
316
{
317
    return NotificationLabel::instance->text() != text;
318
}
319

320
/***************************** NotificationBox **********************************/
321

322
bool NotificationBox::showText(const QPoint& pos, const QString& text, QWidget* referenceWidget,
323
                               int displayTime, unsigned int minShowTime, Options options,
324
                               int width)
325
{
326
    QRect restrictionarea = {};
327

328
    if (referenceWidget) {
329
        if (options & Options::OnlyIfReferenceActive) {
330
            if (!referenceWidget->isActiveWindow()) {
331
                return false;
332
            }
333
        }
334

335
        if (options & Options::RestrictAreaToReference) {
336
            // Calculate the main window QRect in global screen coordinates.
337
            auto mainwindowrect = referenceWidget->rect();
338

339
            restrictionarea = QRect(referenceWidget->mapToGlobal(mainwindowrect.topLeft()),
340
                                    mainwindowrect.size());
341
        }
342
    }
343

344
    // a label does already exist
345
    if (NotificationLabel::instance && NotificationLabel::instance->isVisible()) {
346
        if (text.isEmpty()) {// empty text means hide current label
347
            NotificationLabel::instance->hideNotification();
348
            return false;
349
        }
350
        else {
351
            // If the label has changed, reuse the one that is showing (removes flickering)
352
            if (NotificationLabel::instance->notificationLabelChanged(text)) {
353
                NotificationLabel::instance->setTipRect(restrictionarea);
354
                NotificationLabel::instance->setHideIfReferenceWidgetDeactivated(
355
                    options & Options::HideIfReferenceWidgetDeactivated);
356
                NotificationLabel::instance->reuseNotification(text, displayTime, pos, width);
357
                NotificationLabel::instance->placeNotificationLabel(pos);
358
            }
359
            return true;
360
        }
361
    }
362

363
    // no label can be reused, create new label:
364
    if (!text.isEmpty()) {
365
        // Note: The Label takes no parent, as on windows, we can't use the widget as parent
366
        // otherwise the window will be raised when the tooltip will be shown. We do not use
367
        // it on Linux either for consistency.
368
        new NotificationLabel(text,
369
                              pos,
370
                              displayTime,
371
                              minShowTime,
372
                              width);// sets NotificationLabel::instance to itself
373

374
        NotificationLabel::instance->setTipRect(restrictionarea);
375
        NotificationLabel::instance->setHideIfReferenceWidgetDeactivated(
376
            options & Options::HideIfReferenceWidgetDeactivated);
377
        NotificationLabel::instance->placeNotificationLabel(pos);
378
        NotificationLabel::instance->setObjectName(QLatin1String("NotificationBox_label"));
379

380
        NotificationLabel::instance->showNormal();
381
    }
382

383
    return true;
384
}
385

386
bool NotificationBox::isVisible()
387
{
388
    return (NotificationLabel::instance != nullptr && NotificationLabel::instance->isVisible());
389
}
390

391
QString NotificationBox::text()
392
{
393
    if (NotificationLabel::instance)
394
        return NotificationLabel::instance->text();
395
    return {};
396
}
397

398
Q_GLOBAL_STATIC(QPalette, notificationbox_palette)
399

400
QPalette NotificationBox::palette()
401
{
402
    return *notificationbox_palette();
403
}
404

405
QFont NotificationBox::font()
406
{
407
    return QApplication::font("NotificationLabel");
408
}
409

410
void NotificationBox::setPalette(const QPalette& palette)
411
{
412
    *notificationbox_palette() = palette;
413
    if (NotificationLabel::instance)
414
        NotificationLabel::instance->setPalette(palette);
415
}
416

417
void NotificationBox::setFont(const QFont& font)
418
{
419
    QApplication::setFont(font, "NotificationLabel");
420
}
421

422
}// namespace Gui
423

424
#include "NotificationBox.moc"
425

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.