File indexing completed on 2024-05-19 04:58:31
0001 /* ============================================================ 0002 * Falkon - Qt web browser 0003 * Copyright (C) 2010-2017 David Rosca <nowrep@gmail.com> 0004 * 0005 * This program is free software: you can redistribute it and/or modify 0006 * it under the terms of the GNU General Public License as published by 0007 * the Free Software Foundation, either version 3 of the License, or 0008 * (at your option) any later version. 0009 * 0010 * This program is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0013 * GNU General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU General Public License 0016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 0017 * ============================================================ */ 0018 #include "lineedit.h" 0019 #include "qzsettings.h" 0020 0021 #include <QMenu> 0022 #include <QEvent> 0023 #include <QLayout> 0024 #include <QPainter> 0025 #include <QClipboard> 0026 #include <QFocusEvent> 0027 #include <QStyleOption> 0028 #include <QApplication> 0029 0030 SideWidget::SideWidget(QWidget* parent) 0031 : QWidget(parent) 0032 { 0033 setCursor(Qt::ArrowCursor); 0034 setFocusPolicy(Qt::ClickFocus); 0035 } 0036 0037 bool SideWidget::event(QEvent* event) 0038 { 0039 switch (event->type()) { 0040 case QEvent::LayoutRequest: 0041 Q_EMIT sizeHintChanged(); 0042 break; 0043 0044 case QEvent::MouseButtonPress: 0045 case QEvent::MouseButtonRelease: 0046 case QEvent::MouseButtonDblClick: 0047 case QEvent::MouseMove: 0048 event->accept(); 0049 return true; 0050 0051 default: 0052 break; 0053 } 0054 0055 return QWidget::event(event); 0056 } 0057 0058 LineEdit::LineEdit(QWidget* parent) 0059 : QLineEdit(parent) 0060 , m_leftLayout(nullptr) 0061 , m_rightLayout(nullptr) 0062 , m_minHeight(0) 0063 , m_leftMargin(-1) 0064 , m_ignoreMousePress(false) 0065 { 0066 init(); 0067 } 0068 0069 void LineEdit::setLeftMargin(int margin) 0070 { 0071 m_leftMargin = margin; 0072 } 0073 0074 void LineEdit::init() 0075 { 0076 mainLayout = new QHBoxLayout(this); 0077 mainLayout->setContentsMargins(0, 0, 0, 0); 0078 mainLayout->setSpacing(0); 0079 0080 m_leftWidget = new SideWidget(this); 0081 m_leftWidget->resize(0, 0); 0082 m_leftLayout = new QHBoxLayout(m_leftWidget); 0083 m_leftLayout->setContentsMargins(0, 0, 0, 0); 0084 m_leftLayout->setDirection(isRightToLeft() ? QBoxLayout::RightToLeft : QBoxLayout::LeftToRight); 0085 0086 m_rightWidget = new SideWidget(this); 0087 m_rightWidget->resize(0, 0); 0088 m_rightLayout = new QHBoxLayout(m_rightWidget); 0089 m_rightLayout->setDirection(isRightToLeft() ? QBoxLayout::RightToLeft : QBoxLayout::LeftToRight); 0090 m_rightLayout->setContentsMargins(0, 0, 2, 0); 0091 0092 mainLayout->addWidget(m_leftWidget, 0, Qt::AlignVCenter | Qt::AlignLeft); 0093 mainLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); 0094 mainLayout->addWidget(m_rightWidget, 0, Qt::AlignVCenter | Qt::AlignRight); 0095 mainLayout->setDirection(isRightToLeft() ? QBoxLayout::RightToLeft : QBoxLayout::LeftToRight); 0096 0097 setWidgetSpacing(3); 0098 0099 connect(m_leftWidget, &SideWidget::sizeHintChanged, this, &LineEdit::updateTextMargins); 0100 connect(m_rightWidget, &SideWidget::sizeHintChanged, this, &LineEdit::updateTextMargins); 0101 0102 auto* undoAction = new QAction(QIcon::fromTheme(QSL("edit-undo")), tr("&Undo"), this); 0103 undoAction->setShortcut(QKeySequence(QSL("Ctrl+Z"))); 0104 undoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0105 connect(undoAction, &QAction::triggered, this, &QLineEdit::undo); 0106 0107 auto* redoAction = new QAction(QIcon::fromTheme(QSL("edit-redo")), tr("&Redo"), this); 0108 redoAction->setShortcut(QKeySequence(QSL("Ctrl+Shift+Z"))); 0109 redoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0110 connect(redoAction, &QAction::triggered, this, &QLineEdit::redo); 0111 0112 auto* cutAction = new QAction(QIcon::fromTheme(QSL("edit-cut")), tr("Cu&t"), this); 0113 cutAction->setShortcut(QKeySequence(QSL("Ctrl+X"))); 0114 cutAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0115 connect(cutAction, &QAction::triggered, this, &QLineEdit::cut); 0116 0117 auto* copyAction = new QAction(QIcon::fromTheme(QSL("edit-copy")), tr("&Copy"), this); 0118 copyAction->setShortcut(QKeySequence(QSL("Ctrl+C"))); 0119 copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0120 connect(copyAction, &QAction::triggered, this, &QLineEdit::copy); 0121 0122 auto* pasteAction = new QAction(QIcon::fromTheme(QSL("edit-paste")), tr("&Paste"), this); 0123 pasteAction->setShortcut(QKeySequence(QSL("Ctrl+V"))); 0124 pasteAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0125 connect(pasteAction, &QAction::triggered, this, &QLineEdit::paste); 0126 0127 auto* pasteAndGoAction = new QAction(this); 0128 pasteAndGoAction->setShortcut(QKeySequence(QSL("Ctrl+Shift+V"))); 0129 pasteAndGoAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0130 0131 auto* deleteAction = new QAction(QIcon::fromTheme(QSL("edit-delete")), tr("Delete"), this); 0132 connect(deleteAction, &QAction::triggered, this, &LineEdit::slotDelete); 0133 0134 auto* clearAllAction = new QAction(QIcon::fromTheme(QSL("edit-clear")), tr("Clear All"), this); 0135 connect(clearAllAction, &QAction::triggered, this, &QLineEdit::clear); 0136 0137 auto* selectAllAction = new QAction(QIcon::fromTheme(QSL("edit-select-all")), tr("Select All"), this); 0138 selectAllAction->setShortcut(QKeySequence(QSL("Ctrl+A"))); 0139 selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); 0140 connect(selectAllAction, &QAction::triggered, this, &QLineEdit::selectAll); 0141 0142 m_editActions[Undo] = undoAction; 0143 m_editActions[Redo] = redoAction; 0144 m_editActions[Cut] = cutAction; 0145 m_editActions[Copy] = copyAction; 0146 m_editActions[Paste] = pasteAction; 0147 m_editActions[PasteAndGo] = pasteAndGoAction; 0148 m_editActions[Delete] = deleteAction; 0149 m_editActions[ClearAll] = clearAllAction; 0150 m_editActions[SelectAll] = selectAllAction; 0151 0152 // Make action shortcuts available for webview 0153 addAction(undoAction); 0154 addAction(redoAction); 0155 addAction(cutAction); 0156 addAction(copyAction); 0157 addAction(pasteAction); 0158 addAction(pasteAndGoAction); 0159 addAction(deleteAction); 0160 addAction(clearAllAction); 0161 addAction(selectAllAction); 0162 0163 // Connections to update edit actions 0164 connect(this, &QLineEdit::textChanged, this, &LineEdit::updateActions); 0165 connect(this, &QLineEdit::selectionChanged, this, &LineEdit::updateActions); 0166 0167 updateActions(); 0168 } 0169 0170 bool LineEdit::event(QEvent* event) 0171 { 0172 if (event->type() == QEvent::LayoutDirectionChange) { 0173 // By this we undo reversing of layout when direction is RTL. 0174 if (isRightToLeft()) { 0175 mainLayout->setDirection(QBoxLayout::RightToLeft); 0176 m_leftLayout->setDirection(QBoxLayout::RightToLeft); 0177 m_rightLayout->setDirection(QBoxLayout::RightToLeft); 0178 } 0179 else { 0180 mainLayout->setDirection(QBoxLayout::LeftToRight); 0181 m_leftLayout->setDirection(QBoxLayout::LeftToRight); 0182 m_rightLayout->setDirection(QBoxLayout::LeftToRight); 0183 } 0184 } 0185 return QLineEdit::event(event); 0186 } 0187 0188 #define ACCEL_KEY(k) QLatin1Char('\t') + QKeySequence(k).toString() 0189 0190 // Modified QLineEdit::createStandardContextMenu to support icons and PasteAndGo action 0191 QMenu* LineEdit::createContextMenu() 0192 { 0193 auto* popup = new QMenu(this); 0194 popup->setObjectName(QSL("qt_edit_menu")); 0195 0196 if (!isReadOnly()) { 0197 popup->addAction(m_editActions[Undo]); 0198 popup->addAction(m_editActions[Redo]); 0199 popup->addSeparator(); 0200 popup->addAction(m_editActions[Cut]); 0201 } 0202 0203 popup->addAction(m_editActions[Copy]); 0204 0205 if (!isReadOnly()) { 0206 updatePasteActions(); 0207 popup->addAction(m_editActions[Paste]); 0208 if (!m_editActions[PasteAndGo]->text().isEmpty()) { 0209 popup->addAction(m_editActions[PasteAndGo]); 0210 } 0211 popup->addAction(m_editActions[Delete]); 0212 popup->addAction(m_editActions[ClearAll]); 0213 } 0214 0215 popup->addSeparator(); 0216 popup->addAction(m_editActions[SelectAll]); 0217 0218 // Hack to get QUnicodeControlCharacterMenu 0219 QMenu* tmp = createStandardContextMenu(); 0220 tmp->setParent(popup); 0221 tmp->hide(); 0222 QAction* lastAction = !tmp->actions().isEmpty() ? tmp->actions().constLast() : nullptr; 0223 0224 if (lastAction && lastAction->menu() && lastAction->menu()->inherits("QUnicodeControlCharacterMenu")) { 0225 popup->addAction(lastAction); 0226 } 0227 0228 return popup; 0229 } 0230 0231 void LineEdit::updateActions() 0232 { 0233 m_editActions[Undo]->setEnabled(!isReadOnly() && isUndoAvailable()); 0234 m_editActions[Redo]->setEnabled(!isReadOnly() && isRedoAvailable()); 0235 m_editActions[Cut]->setEnabled(!isReadOnly() && hasSelectedText() && echoMode() == QLineEdit::Normal); 0236 m_editActions[Copy]->setEnabled(hasSelectedText() && echoMode() == QLineEdit::Normal); 0237 m_editActions[Delete]->setEnabled(!isReadOnly() && hasSelectedText()); 0238 m_editActions[SelectAll]->setEnabled(!text().isEmpty() && selectedText() != text()); 0239 m_editActions[Paste]->setEnabled(true); 0240 m_editActions[PasteAndGo]->setEnabled(true); 0241 } 0242 0243 void LineEdit::updatePasteActions() 0244 { 0245 // Paste actions are updated in separate slot because accessing clipboard is expensive 0246 bool pasteEnabled = !isReadOnly() && !QApplication::clipboard()->text().isEmpty(); 0247 0248 m_editActions[Paste]->setEnabled(pasteEnabled); 0249 m_editActions[PasteAndGo]->setEnabled(pasteEnabled); 0250 } 0251 0252 void LineEdit::slotDelete() 0253 { 0254 if (hasSelectedText()) { 0255 del(); 0256 } 0257 } 0258 0259 void LineEdit::addWidget(QWidget* widget, WidgetPosition position) 0260 { 0261 if (!widget) { 0262 return; 0263 } 0264 if (position == LeftSide) { 0265 m_leftLayout->addWidget(widget); 0266 } 0267 else { 0268 m_rightLayout->addWidget(widget); 0269 } 0270 } 0271 0272 void LineEdit::removeWidget(QWidget* widget) 0273 { 0274 if (!widget) { 0275 return; 0276 } 0277 0278 m_leftLayout->removeWidget(widget); 0279 m_rightLayout->removeWidget(widget); 0280 widget->hide(); 0281 } 0282 0283 void LineEdit::setWidgetSpacing(int spacing) 0284 { 0285 m_leftLayout->setSpacing(spacing); 0286 m_rightLayout->setSpacing(spacing); 0287 updateTextMargins(); 0288 } 0289 0290 int LineEdit::widgetSpacing() const 0291 { 0292 return m_leftLayout->spacing(); 0293 } 0294 0295 int LineEdit::leftMargin() const 0296 { 0297 return m_leftMargin; 0298 } 0299 0300 // http://stackoverflow.com/a/14424003 0301 void LineEdit::setTextFormat(const LineEdit::TextFormat &format) 0302 { 0303 QList<QInputMethodEvent::Attribute> attributes; 0304 0305 for (const QTextLayout::FormatRange &fr : format) { 0306 QInputMethodEvent::AttributeType type = QInputMethodEvent::TextFormat; 0307 int start = fr.start - cursorPosition(); 0308 int length = fr.length; 0309 QVariant value = fr.format; 0310 attributes.append(QInputMethodEvent::Attribute(type, start, length, value)); 0311 } 0312 0313 QInputMethodEvent ev(QString(), attributes); 0314 event(&ev); 0315 } 0316 0317 void LineEdit::clearTextFormat() 0318 { 0319 setTextFormat(TextFormat()); 0320 } 0321 0322 int LineEdit::minHeight() const 0323 { 0324 return m_minHeight; 0325 } 0326 0327 void LineEdit::setMinHeight(int height) 0328 { 0329 m_minHeight = height; 0330 } 0331 0332 QSize LineEdit::sizeHint() const 0333 { 0334 QSize s = QLineEdit::sizeHint(); 0335 0336 if (s.height() < m_minHeight) { 0337 s.setHeight(m_minHeight); 0338 } 0339 0340 return s; 0341 } 0342 0343 QAction* LineEdit::editAction(EditAction action) const 0344 { 0345 return m_editActions[action]; 0346 } 0347 0348 void LineEdit::updateTextMargins() 0349 { 0350 int left = m_leftWidget->sizeHint().width(); 0351 int right = m_rightWidget->sizeHint().width(); 0352 int top = 0; 0353 int bottom = 0; 0354 0355 if (m_leftMargin >= 0) { 0356 left = m_leftMargin; 0357 } 0358 0359 setTextMargins(left, top, right, bottom); 0360 } 0361 0362 void LineEdit::focusInEvent(QFocusEvent* event) 0363 { 0364 if (event->reason() == Qt::MouseFocusReason && qzSettings->selectAllOnClick) { 0365 m_ignoreMousePress = true; 0366 selectAll(); 0367 } 0368 0369 QLineEdit::focusInEvent(event); 0370 } 0371 0372 void LineEdit::mousePressEvent(QMouseEvent* event) 0373 { 0374 if (m_ignoreMousePress) { 0375 m_ignoreMousePress = false; 0376 return; 0377 } 0378 0379 QLineEdit::mousePressEvent(event); 0380 } 0381 0382 void LineEdit::mouseReleaseEvent(QMouseEvent* event) 0383 { 0384 // Workaround issue in QLineEdit::setDragEnabled(true) 0385 // It will incorrectly set cursor position at the end 0386 // of selection when clicking (and not dragging) into selected text 0387 0388 if (!dragEnabled()) { 0389 QLineEdit::mouseReleaseEvent(event); 0390 return; 0391 } 0392 0393 bool wasSelectedText = !selectedText().isEmpty(); 0394 0395 QLineEdit::mouseReleaseEvent(event); 0396 0397 bool isSelectedText = !selectedText().isEmpty(); 0398 0399 if (wasSelectedText && !isSelectedText) { 0400 QMouseEvent ev(QEvent::MouseButtonPress, event->position(), event->globalPosition(), event->button(), 0401 event->buttons(), event->modifiers()); 0402 mousePressEvent(&ev); 0403 } 0404 } 0405 0406 void LineEdit::mouseDoubleClickEvent(QMouseEvent* event) 0407 { 0408 if (event->buttons() == Qt::LeftButton && qzSettings->selectAllOnDoubleClick) { 0409 selectAll(); 0410 return; 0411 } 0412 0413 QLineEdit::mouseDoubleClickEvent(event); 0414 } 0415 0416 void LineEdit::resizeEvent(QResizeEvent *event) 0417 { 0418 QLineEdit::resizeEvent(event); 0419 0420 m_leftWidget->setFixedHeight(height()); 0421 m_rightWidget->setFixedHeight(height()); 0422 }