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 }