]> git.lyx.org Git - features.git/blob - src/frontends/qt/FancyLineEdit.cpp
HiDPI support for search indicators (#12162)
[features.git] / src / frontends / qt / FancyLineEdit.cpp
1 /**
2  * \file fancylineedit.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Nokia Corporation (qt-info@nokia.com)
7  *
8  * Full author contact details are available in file CREDITS.
9  *
10  */
11
12 // Code taken from the Qt Creator project and customized a little
13
14 #include <config.h>
15
16 #include "FancyLineEdit.h"
17
18 #if QT_VERSION < 0x050200
19 #include "GuiApplication.h"
20 #endif
21
22 #if QT_VERSION >= 0x040600
23
24 #include <QEvent>
25 #include <QDebug>
26 #include <QString>
27 #include <QPropertyAnimation>
28 #include <QApplication>
29 #include <QMenu>
30 #include <QMouseEvent>
31 #include <QLabel>
32 #include <QAbstractButton>
33 #include <QPainter>
34 #include <QStyle>
35 #include <QPaintEvent>
36 #if QT_VERSION >= 0x050000
37 #include <QWindow>
38 #endif
39
40 enum { margin = 6 };
41
42 #define ICONBUTTON_HEIGHT 18
43 #define FADE_TIME 160
44
45
46 namespace lyx {
47 namespace frontend {
48
49 ////////////////////////////////////////////////////////////////////////
50 //
51 // FancyLineEditPrivate
52 //
53 ////////////////////////////////////////////////////////////////////////
54
55 class FancyLineEditPrivate : public QObject {
56 public:
57         explicit FancyLineEditPrivate(FancyLineEdit *parent);
58
59         bool eventFilter(QObject *obj, QEvent *event) override;
60
61         FancyLineEdit  *m_lineEdit;
62         QPixmap m_pixmap[2];
63         QMenu *m_menu[2];
64         bool m_menuTabFocusTrigger[2];
65         IconButton *m_iconbutton[2];
66         bool m_iconEnabled[2];
67 };
68
69
70 FancyLineEditPrivate::FancyLineEditPrivate(FancyLineEdit *parent)
71         : QObject(parent), m_lineEdit(parent)
72 {
73         for (int i = 0; i < 2; ++i) {
74                 m_menu[i] = nullptr;
75                 m_menuTabFocusTrigger[i] = false;
76                 m_iconbutton[i] = new IconButton(parent);
77                 m_iconbutton[i]->installEventFilter(this);
78                 m_iconbutton[i]->hide();
79                 m_iconbutton[i]->setAutoHide(false);
80                 m_iconEnabled[i] = false;
81         }
82 }
83
84
85 bool FancyLineEditPrivate::eventFilter(QObject *obj, QEvent *event)
86 {
87         int buttonIndex = -1;
88         for (int i = 0; i < 2; ++i) {
89                 if (obj == m_iconbutton[i]) {
90                         buttonIndex = i;
91                         break;
92                 }
93         }
94         if (buttonIndex == -1)
95                 return QObject::eventFilter(obj, event);
96         switch (event->type()) {
97         case QEvent::FocusIn:
98                 if (m_menuTabFocusTrigger[buttonIndex] && m_menu[buttonIndex]) {
99                         m_lineEdit->setFocus();
100                         m_menu[buttonIndex]->exec(m_iconbutton[buttonIndex]->mapToGlobal(
101                                                           m_iconbutton[buttonIndex]->rect().center()));
102                         return true;
103                 }
104         default:
105                 break;
106         }
107         return QObject::eventFilter(obj, event);
108 }
109
110
111 ////////////////////////////////////////////////////////////////////////
112 //
113 // FancyLineEdit
114 //
115 ////////////////////////////////////////////////////////////////////////
116
117 FancyLineEdit::FancyLineEdit(QWidget *parent) :
118     QLineEdit(parent),
119     m_d(new FancyLineEditPrivate(this))
120 {
121         ensurePolished();
122         updateMargins();
123         
124         connect(this, SIGNAL(textChanged(QString)),
125                 this, SLOT(checkButtons(QString)));
126         connect(m_d->m_iconbutton[Left], SIGNAL(clicked()),
127                 this, SLOT(iconClicked()));
128         connect(m_d->m_iconbutton[Right], SIGNAL(clicked()),
129                 this, SLOT(iconClicked()));
130 }
131
132
133 void FancyLineEdit::checkButtons(const QString &text)
134 {
135         if (m_oldText.isEmpty() || text.isEmpty()) {
136                 for (int i = 0; i < 2; ++i) {
137                         if (m_d->m_iconbutton[i]->hasAutoHide())
138                                 m_d->m_iconbutton[i]->animateShow(!text.isEmpty());
139                 }
140                 m_oldText = text;
141         }
142 }
143
144
145 void FancyLineEdit::setClearButton(bool visible)
146 {
147 // QLineEdit::setClearButtonEnabled() has been implemented in Qt 5.2.
148 // In earlier Qt versions, we roll our own button
149 #if QT_VERSION < 0x050200
150         setButtonPixmap(FancyLineEdit::Right, getPixmap("images/", "editclear", "svgz,png"));
151         setButtonVisible(FancyLineEdit::Right, visible);
152         setAutoHideButton(FancyLineEdit::Right, true);
153 #else
154         setClearButtonEnabled(visible);
155 #endif
156 }
157
158
159 void FancyLineEdit::setButtonVisible(Side side, bool visible)
160 {
161         m_d->m_iconbutton[side]->setVisible(visible);
162         m_d->m_iconEnabled[side] = visible;
163         updateMargins();
164 }
165
166
167 bool FancyLineEdit::isButtonVisible(Side side) const
168 {
169         return m_d->m_iconEnabled[side];
170 }
171
172
173 void FancyLineEdit::iconClicked()
174 {
175         IconButton *button = qobject_cast<IconButton *>(sender());
176         int index = -1;
177         for (int i = 0; i < 2; ++i)
178                 if (m_d->m_iconbutton[i] == button)
179                         index = i;
180         if (index == -1)
181                 return;
182         if (m_d->m_menu[index]) {
183                 m_d->m_menu[index]->exec(QCursor::pos());
184         } else {
185                 buttonClicked((Side)index);
186                 if (index == Left)
187                         leftButtonClicked();
188                 else if (index == Right)
189                         rightButtonClicked();
190         }
191 }
192
193
194 void FancyLineEdit::updateMargins()
195 {
196         bool leftToRight = (layoutDirection() == Qt::LeftToRight);
197         Side realLeft = (leftToRight ? Left : Right);
198         Side realRight = (leftToRight ? Right : Left);
199
200         qreal dpr = 1.0;
201 #if QT_VERSION >= 0x050000
202         // Consider device/pixel ratio (HiDPI)
203         dpr = devicePixelRatio();
204 #endif
205         int leftMargin = (m_d->m_iconbutton[realLeft]->pixmap().width() / dpr ) + 8;
206         int rightMargin = (m_d->m_iconbutton[realRight]->pixmap().width() / dpr) + 8;
207         // Note KDE does not reserve space for the highlight color
208         if (style()->inherits("OxygenStyle")) {
209                 leftMargin = qMax(24, leftMargin);
210                 rightMargin = qMax(24, rightMargin);
211         }
212
213         QMargins margins((m_d->m_iconEnabled[realLeft] ? leftMargin : 0), 0,
214                          (m_d->m_iconEnabled[realRight] ? rightMargin : 0), 0);
215
216         setTextMargins(margins);
217 }
218
219
220 void FancyLineEdit::updateButtonPositions()
221 {
222         QRect contentRect = rect();
223         for (int i = 0; i < 2; ++i) {
224                 Side iconpos = (Side)i;
225                 if (layoutDirection() == Qt::RightToLeft)
226                         iconpos = (iconpos == Left ? Right : Left);
227                 
228                 if (iconpos == FancyLineEdit::Right) {
229                         const int iconoffset = textMargins().right() + 4;
230                         m_d->m_iconbutton[i]->setGeometry(
231                                                 contentRect.adjusted(width() - iconoffset,
232                                                                      0, 0, 0));
233                 } else {
234                         const int iconoffset = textMargins().left() + 4;
235                         m_d->m_iconbutton[i]->setGeometry(
236                                                 contentRect.adjusted(0, 0,
237                                                                      -width() + iconoffset, 0));
238                 }
239         }
240 }
241
242
243 void FancyLineEdit::resizeEvent(QResizeEvent *)
244 {
245         updateButtonPositions();
246 }
247
248
249 void FancyLineEdit::keyPressEvent(QKeyEvent * e)
250 {
251         if (e->type() == QEvent::KeyPress && e->key() == Qt::Key_Down)
252                 Q_EMIT downPressed();
253         else
254                 QLineEdit::keyPressEvent(e);
255 }
256
257
258 void FancyLineEdit::setButtonPixmap(Side side, const QPixmap &buttonPixmap)
259 {
260         m_d->m_iconbutton[side]->setPixmap(buttonPixmap);
261         updateMargins();
262         updateButtonPositions();
263         update();
264 }
265
266
267 QPixmap FancyLineEdit::buttonPixmap(Side side) const
268 {
269         return m_d->m_pixmap[side];
270 }
271
272
273 void FancyLineEdit::setButtonMenu(Side side, QMenu *buttonMenu)
274 {
275         m_d->m_menu[side] = buttonMenu;
276         m_d->m_iconbutton[side]->setIconOpacity(1.0);
277 }
278
279 QMenu *FancyLineEdit::buttonMenu(Side side) const
280 {
281         return  m_d->m_menu[side];
282 }
283
284
285 bool FancyLineEdit::hasMenuTabFocusTrigger(Side side) const
286 {
287         return m_d->m_menuTabFocusTrigger[side];
288 }
289
290
291 void FancyLineEdit::setMenuTabFocusTrigger(Side side, bool v)
292 {
293         if (m_d->m_menuTabFocusTrigger[side] == v)
294                 return;
295
296         m_d->m_menuTabFocusTrigger[side] = v;
297         m_d->m_iconbutton[side]->setFocusPolicy(v ? Qt::TabFocus : Qt::NoFocus);
298 }
299
300
301 bool FancyLineEdit::hasAutoHideButton(Side side) const
302 {
303         return m_d->m_iconbutton[side]->hasAutoHide();
304 }
305
306
307 void FancyLineEdit::setAutoHideButton(Side side, bool h)
308 {
309         m_d->m_iconbutton[side]->setAutoHide(h);
310         if (h)
311                 m_d->m_iconbutton[side]->setIconOpacity(text().isEmpty() ?  0.0 : 1.0);
312         else
313                 m_d->m_iconbutton[side]->setIconOpacity(1.0);
314 }
315
316
317 void FancyLineEdit::setButtonToolTip(Side side, const QString &tip)
318 {
319         m_d->m_iconbutton[side]->setToolTip(tip);
320 }
321
322
323 void FancyLineEdit::setButtonFocusPolicy(Side side, Qt::FocusPolicy policy)
324 {
325         m_d->m_iconbutton[side]->setFocusPolicy(policy);
326 }
327
328
329 ////////////////////////////////////////////////////////////////////////
330 //
331 // IconButton - helper class to represent a clickable icon
332 //
333 ////////////////////////////////////////////////////////////////////////
334
335 IconButton::IconButton(QWidget *parent)
336         : QAbstractButton(parent), m_iconOpacity(0.0), m_autoHide(false)
337 {
338         setCursor(Qt::ArrowCursor);
339         setFocusPolicy(Qt::NoFocus);
340 }
341
342
343 void IconButton::paintEvent(QPaintEvent *)
344 {
345         qreal dpr = 1.0;
346 #if QT_VERSION >= 0x050000
347         // Consider device/pixel ratio (HiDPI)
348         QWindow * window = this->window()->windowHandle();
349         dpr = window->devicePixelRatio();
350 #endif
351         QRect pixmapRect(QPoint(), m_pixmap.size() / dpr);
352         pixmapRect.moveCenter(rect().center());
353         QPixmap pm = m_pixmap;
354
355         QPainter painter(this);
356         if (m_autoHide)
357                 painter.setOpacity(m_iconOpacity);
358
359         painter.drawPixmap(pixmapRect, pm);
360 }
361
362
363 void IconButton::animateShow(bool visible)
364 {
365         if (visible) {
366                 QPropertyAnimation *animation =
367                         new QPropertyAnimation(this, "iconOpacity");
368                 animation->setDuration(FADE_TIME);
369                 animation->setEndValue(1.0);
370                 animation->start(QAbstractAnimation::DeleteWhenStopped);
371         } else {
372                 QPropertyAnimation *animation =
373                         new QPropertyAnimation(this, "iconOpacity");
374                 animation->setDuration(FADE_TIME);
375                 animation->setEndValue(0.0);
376                 animation->start(QAbstractAnimation::DeleteWhenStopped);
377         }
378 }
379
380 } // namespace frontend
381
382 } // namespace lyx
383
384 #endif // QT_VERSION >= 0x040600
385
386 #include "moc_FancyLineEdit.cpp"