Falkon Develop
Cross-platform Qt-based web browser
lineedit.cpp
Go to the documentation of this file.
1/* ============================================================
2* Falkon - Qt web browser
3* Copyright (C) 2010-2017 David Rosca <nowrep@gmail.com>
4*
5* This program is free software: you can redistribute it and/or modify
6* it under the terms of the GNU General Public License as published by
7* the Free Software Foundation, either version 3 of the License, or
8* (at your option) any later version.
9*
10* This program is distributed in the hope that it will be useful,
11* but WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13* GNU General Public License for more details.
14*
15* You should have received a copy of the GNU General Public License
16* along with this program. If not, see <http://www.gnu.org/licenses/>.
17* ============================================================ */
18#include "lineedit.h"
19#include "qzsettings.h"
20
21#include <QMenu>
22#include <QEvent>
23#include <QLayout>
24#include <QPainter>
25#include <QClipboard>
26#include <QFocusEvent>
27#include <QStyleOption>
28#include <QApplication>
29
30SideWidget::SideWidget(QWidget* parent)
31 : QWidget(parent)
32{
33 setCursor(Qt::ArrowCursor);
34 setFocusPolicy(Qt::ClickFocus);
35}
36
37bool SideWidget::event(QEvent* event)
38{
39 switch (event->type()) {
40 case QEvent::LayoutRequest:
41 Q_EMIT sizeHintChanged();
42 break;
43
44 case QEvent::MouseButtonPress:
45 case QEvent::MouseButtonRelease:
46 case QEvent::MouseButtonDblClick:
47 case QEvent::MouseMove:
48 event->accept();
49 return true;
50
51 default:
52 break;
53 }
54
55 return QWidget::event(event);
56}
57
58LineEdit::LineEdit(QWidget* parent)
59 : QLineEdit(parent)
60 , m_leftLayout(nullptr)
61 , m_rightLayout(nullptr)
62 , m_minHeight(0)
63 , m_leftMargin(-1)
64 , m_ignoreMousePress(false)
65{
66 init();
67}
68
70{
71 m_leftMargin = margin;
72}
73
74void LineEdit::init()
75{
76 mainLayout = new QHBoxLayout(this);
77 mainLayout->setContentsMargins(0, 0, 0, 0);
78 mainLayout->setSpacing(0);
79
80 m_leftWidget = new SideWidget(this);
81 m_leftWidget->resize(0, 0);
82 m_leftLayout = new QHBoxLayout(m_leftWidget);
83 m_leftLayout->setContentsMargins(0, 0, 0, 0);
84 m_leftLayout->setDirection(isRightToLeft() ? QBoxLayout::RightToLeft : QBoxLayout::LeftToRight);
85
86 m_rightWidget = new SideWidget(this);
87 m_rightWidget->resize(0, 0);
88 m_rightLayout = new QHBoxLayout(m_rightWidget);
89 m_rightLayout->setDirection(isRightToLeft() ? QBoxLayout::RightToLeft : QBoxLayout::LeftToRight);
90 m_rightLayout->setContentsMargins(0, 0, 2, 0);
91
92 mainLayout->addWidget(m_leftWidget, 0, Qt::AlignVCenter | Qt::AlignLeft);
93 mainLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
94 mainLayout->addWidget(m_rightWidget, 0, Qt::AlignVCenter | Qt::AlignRight);
95 mainLayout->setDirection(isRightToLeft() ? QBoxLayout::RightToLeft : QBoxLayout::LeftToRight);
96
98
99 connect(m_leftWidget, &SideWidget::sizeHintChanged, this, &LineEdit::updateTextMargins);
100 connect(m_rightWidget, &SideWidget::sizeHintChanged, this, &LineEdit::updateTextMargins);
101
102 auto* undoAction = new QAction(QIcon::fromTheme(QSL("edit-undo")), tr("&Undo"), this);
103 undoAction->setShortcut(QKeySequence(QSL("Ctrl+Z")));
104 undoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
105 connect(undoAction, &QAction::triggered, this, &QLineEdit::undo);
106
107 auto* redoAction = new QAction(QIcon::fromTheme(QSL("edit-redo")), tr("&Redo"), this);
108 redoAction->setShortcut(QKeySequence(QSL("Ctrl+Shift+Z")));
109 redoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
110 connect(redoAction, &QAction::triggered, this, &QLineEdit::redo);
111
112 auto* cutAction = new QAction(QIcon::fromTheme(QSL("edit-cut")), tr("Cu&t"), this);
113 cutAction->setShortcut(QKeySequence(QSL("Ctrl+X")));
114 cutAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
115 connect(cutAction, &QAction::triggered, this, &QLineEdit::cut);
116
117 auto* copyAction = new QAction(QIcon::fromTheme(QSL("edit-copy")), tr("&Copy"), this);
118 copyAction->setShortcut(QKeySequence(QSL("Ctrl+C")));
119 copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
120 connect(copyAction, &QAction::triggered, this, &QLineEdit::copy);
121
122 auto* pasteAction = new QAction(QIcon::fromTheme(QSL("edit-paste")), tr("&Paste"), this);
123 pasteAction->setShortcut(QKeySequence(QSL("Ctrl+V")));
124 pasteAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
125 connect(pasteAction, &QAction::triggered, this, &QLineEdit::paste);
126
127 auto* pasteAndGoAction = new QAction(this);
128 pasteAndGoAction->setShortcut(QKeySequence(QSL("Ctrl+Shift+V")));
129 pasteAndGoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
130
131 auto* deleteAction = new QAction(QIcon::fromTheme(QSL("edit-delete")), tr("Delete"), this);
132 connect(deleteAction, &QAction::triggered, this, &LineEdit::slotDelete);
133
134 auto* clearAllAction = new QAction(QIcon::fromTheme(QSL("edit-clear")), tr("Clear All"), this);
135 connect(clearAllAction, &QAction::triggered, this, &QLineEdit::clear);
136
137 auto* selectAllAction = new QAction(QIcon::fromTheme(QSL("edit-select-all")), tr("Select All"), this);
138 selectAllAction->setShortcut(QKeySequence(QSL("Ctrl+A")));
139 selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
140 connect(selectAllAction, &QAction::triggered, this, &QLineEdit::selectAll);
141
142 m_editActions[Undo] = undoAction;
143 m_editActions[Redo] = redoAction;
144 m_editActions[Cut] = cutAction;
145 m_editActions[Copy] = copyAction;
146 m_editActions[Paste] = pasteAction;
147 m_editActions[PasteAndGo] = pasteAndGoAction;
148 m_editActions[Delete] = deleteAction;
149 m_editActions[ClearAll] = clearAllAction;
150 m_editActions[SelectAll] = selectAllAction;
151
152 // Make action shortcuts available for webview
153 addAction(undoAction);
154 addAction(redoAction);
155 addAction(cutAction);
156 addAction(copyAction);
157 addAction(pasteAction);
158 addAction(pasteAndGoAction);
159 addAction(deleteAction);
160 addAction(clearAllAction);
161 addAction(selectAllAction);
162
163 // Connections to update edit actions
164 connect(this, &QLineEdit::textChanged, this, &LineEdit::updateActions);
165 connect(this, &QLineEdit::selectionChanged, this, &LineEdit::updateActions);
166
167 updateActions();
168}
169
170bool LineEdit::event(QEvent* event)
171{
172 if (event->type() == QEvent::LayoutDirectionChange) {
173 // By this we undo reversing of layout when direction is RTL.
174 if (isRightToLeft()) {
175 mainLayout->setDirection(QBoxLayout::RightToLeft);
176 m_leftLayout->setDirection(QBoxLayout::RightToLeft);
177 m_rightLayout->setDirection(QBoxLayout::RightToLeft);
178 }
179 else {
180 mainLayout->setDirection(QBoxLayout::LeftToRight);
181 m_leftLayout->setDirection(QBoxLayout::LeftToRight);
182 m_rightLayout->setDirection(QBoxLayout::LeftToRight);
183 }
184 }
185 return QLineEdit::event(event);
186}
187
188#define ACCEL_KEY(k) QLatin1Char('\t') + QKeySequence(k).toString()
189
190// Modified QLineEdit::createStandardContextMenu to support icons and PasteAndGo action
192{
193 auto* popup = new QMenu(this);
194 popup->setObjectName(QSL("qt_edit_menu"));
195
196 if (!isReadOnly()) {
197 popup->addAction(m_editActions[Undo]);
198 popup->addAction(m_editActions[Redo]);
199 popup->addSeparator();
200 popup->addAction(m_editActions[Cut]);
201 }
202
203 popup->addAction(m_editActions[Copy]);
204
205 if (!isReadOnly()) {
206 updatePasteActions();
207 popup->addAction(m_editActions[Paste]);
208 if (!m_editActions[PasteAndGo]->text().isEmpty()) {
209 popup->addAction(m_editActions[PasteAndGo]);
210 }
211 popup->addAction(m_editActions[Delete]);
212 popup->addAction(m_editActions[ClearAll]);
213 }
214
215 popup->addSeparator();
216 popup->addAction(m_editActions[SelectAll]);
217
218 // Hack to get QUnicodeControlCharacterMenu
219 QMenu* tmp = createStandardContextMenu();
220 tmp->setParent(popup);
221 tmp->hide();
222 QAction* lastAction = !tmp->actions().isEmpty() ? tmp->actions().constLast() : nullptr;
223
224 if (lastAction && lastAction->menu() && lastAction->menu()->inherits("QUnicodeControlCharacterMenu")) {
225 popup->addAction(lastAction);
226 }
227
228 return popup;
229}
230
231void LineEdit::updateActions()
232{
233 m_editActions[Undo]->setEnabled(!isReadOnly() && isUndoAvailable());
234 m_editActions[Redo]->setEnabled(!isReadOnly() && isRedoAvailable());
235 m_editActions[Cut]->setEnabled(!isReadOnly() && hasSelectedText() && echoMode() == QLineEdit::Normal);
236 m_editActions[Copy]->setEnabled(hasSelectedText() && echoMode() == QLineEdit::Normal);
237 m_editActions[Delete]->setEnabled(!isReadOnly() && hasSelectedText());
238 m_editActions[SelectAll]->setEnabled(!text().isEmpty() && selectedText() != text());
239 m_editActions[Paste]->setEnabled(true);
240 m_editActions[PasteAndGo]->setEnabled(true);
241}
242
243void LineEdit::updatePasteActions()
244{
245 // Paste actions are updated in separate slot because accessing clipboard is expensive
246 bool pasteEnabled = !isReadOnly() && !QApplication::clipboard()->text().isEmpty();
247
248 m_editActions[Paste]->setEnabled(pasteEnabled);
249 m_editActions[PasteAndGo]->setEnabled(pasteEnabled);
250}
251
252void LineEdit::slotDelete()
253{
254 if (hasSelectedText()) {
255 del();
256 }
257}
258
259void LineEdit::addWidget(QWidget* widget, WidgetPosition position)
260{
261 if (!widget) {
262 return;
263 }
264 if (position == LeftSide) {
265 m_leftLayout->addWidget(widget);
266 }
267 else {
268 m_rightLayout->addWidget(widget);
269 }
270}
271
272void LineEdit::removeWidget(QWidget* widget)
273{
274 if (!widget) {
275 return;
276 }
277
278 m_leftLayout->removeWidget(widget);
279 m_rightLayout->removeWidget(widget);
280 widget->hide();
281}
282
284{
285 m_leftLayout->setSpacing(spacing);
286 m_rightLayout->setSpacing(spacing);
288}
289
291{
292 return m_leftLayout->spacing();
293}
294
296{
297 return m_leftMargin;
298}
299
300// http://stackoverflow.com/a/14424003
302{
303 QList<QInputMethodEvent::Attribute> attributes;
304
305 for (const QTextLayout::FormatRange &fr : format) {
306 QInputMethodEvent::AttributeType type = QInputMethodEvent::TextFormat;
307 int start = fr.start - cursorPosition();
308 int length = fr.length;
309 QVariant value = fr.format;
310 attributes.append(QInputMethodEvent::Attribute(type, start, length, value));
311 }
312
313 QInputMethodEvent ev(QString(), attributes);
314 event(&ev);
315}
316
318{
320}
321
323{
324 return m_minHeight;
325}
326
328{
329 m_minHeight = height;
330}
331
333{
334 QSize s = QLineEdit::sizeHint();
335
336 if (s.height() < m_minHeight) {
337 s.setHeight(m_minHeight);
338 }
339
340 return s;
341}
342
343QAction* LineEdit::editAction(EditAction action) const
344{
345 return m_editActions[action];
346}
347
349{
350 int left = m_leftWidget->sizeHint().width();
351 int right = m_rightWidget->sizeHint().width();
352 int top = 0;
353 int bottom = 0;
354
355 if (m_leftMargin >= 0) {
356 left = m_leftMargin;
357 }
358
359 setTextMargins(left, top, right, bottom);
360}
361
362void LineEdit::focusInEvent(QFocusEvent* event)
363{
364 if (event->reason() == Qt::MouseFocusReason && qzSettings->selectAllOnClick) {
365 m_ignoreMousePress = true;
366 selectAll();
367 }
368
369 QLineEdit::focusInEvent(event);
370}
371
372void LineEdit::mousePressEvent(QMouseEvent* event)
373{
374 if (m_ignoreMousePress) {
375 m_ignoreMousePress = false;
376 return;
377 }
378
379 QLineEdit::mousePressEvent(event);
380}
381
382void LineEdit::mouseReleaseEvent(QMouseEvent* event)
383{
384 // Workaround issue in QLineEdit::setDragEnabled(true)
385 // It will incorrectly set cursor position at the end
386 // of selection when clicking (and not dragging) into selected text
387
388 if (!dragEnabled()) {
389 QLineEdit::mouseReleaseEvent(event);
390 return;
391 }
392
393 bool wasSelectedText = !selectedText().isEmpty();
394
395 QLineEdit::mouseReleaseEvent(event);
396
397 bool isSelectedText = !selectedText().isEmpty();
398
399 if (wasSelectedText && !isSelectedText) {
400 QMouseEvent ev(QEvent::MouseButtonPress, event->position(), event->globalPosition(), event->button(),
401 event->buttons(), event->modifiers());
402 mousePressEvent(&ev);
403 }
404}
405
406void LineEdit::mouseDoubleClickEvent(QMouseEvent* event)
407{
408 if (event->buttons() == Qt::LeftButton && qzSettings->selectAllOnDoubleClick) {
409 selectAll();
410 return;
411 }
412
413 QLineEdit::mouseDoubleClickEvent(event);
414}
415
416void LineEdit::resizeEvent(QResizeEvent *event)
417{
418 QLineEdit::resizeEvent(event);
419
420 m_leftWidget->setFixedHeight(height());
421 m_rightWidget->setFixedHeight(height());
422}
QAction * editAction(EditAction action) const
Definition: lineedit.cpp:343
void addWidget(QWidget *widget, WidgetPosition position)
Definition: lineedit.cpp:259
void setTextFormat(const TextFormat &format)
Definition: lineedit.cpp:301
int leftMargin
Definition: lineedit.h:70
void resizeEvent(QResizeEvent *event) override
Definition: lineedit.cpp:416
QSize sizeHint() const override
Definition: lineedit.cpp:332
WidgetPosition
Definition: lineedit.h:78
@ LeftSide
Definition: lineedit.h:79
void mouseReleaseEvent(QMouseEvent *event) override
Definition: lineedit.cpp:382
QMenu * createContextMenu()
Definition: lineedit.cpp:191
void removeWidget(QWidget *widget)
Definition: lineedit.cpp:272
void setMinHeight(int height)
Definition: lineedit.cpp:327
int widgetSpacing() const
Definition: lineedit.cpp:290
int minHeight
Definition: lineedit.h:73
void updateTextMargins()
Definition: lineedit.cpp:348
void mousePressEvent(QMouseEvent *event) override
Definition: lineedit.cpp:372
void focusInEvent(QFocusEvent *event) override
Definition: lineedit.cpp:362
void mouseDoubleClickEvent(QMouseEvent *event) override
Definition: lineedit.cpp:406
void setWidgetSpacing(int spacing)
Definition: lineedit.cpp:283
LineEdit(QWidget *parent=nullptr)
Definition: lineedit.cpp:58
bool event(QEvent *event) override
Definition: lineedit.cpp:170
EditAction
Definition: lineedit.h:83
@ Redo
Definition: lineedit.h:85
@ Copy
Definition: lineedit.h:87
@ ClearAll
Definition: lineedit.h:91
@ SelectAll
Definition: lineedit.h:92
@ Paste
Definition: lineedit.h:88
@ Undo
Definition: lineedit.h:84
@ PasteAndGo
Definition: lineedit.h:89
@ Delete
Definition: lineedit.h:90
void setLeftMargin(int margin)
Definition: lineedit.cpp:69
void clearTextFormat()
Definition: lineedit.cpp:317
QList< QTextLayout::FormatRange > TextFormat
Definition: lineedit.h:76
void sizeHintChanged()
SideWidget(QWidget *parent=nullptr)
Definition: lineedit.cpp:30
bool event(QEvent *event) override
Definition: lineedit.cpp:37
int value(const QColor &c)
Definition: colors.cpp:238
#define QSL(x)
Definition: qzcommon.h:40
#define qzSettings
Definition: qzsettings.h:69