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