File indexing completed on 2024-04-14 05:44:25

0001 /*
0002  *  SPDX-FileCopyrightText: 2002-2003 Jesper K. Pedersen <blackie@kde.org>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-only
0005  **/
0006 
0007 #include "regexpeditorwindow.h"
0008 
0009 #include <QApplication>
0010 #include <QClipboard>
0011 #include <QDrag>
0012 #include <QFileInfo>
0013 #include <QHBoxLayout>
0014 #include <QInputDialog>
0015 #include <QMenu>
0016 #include <QMimeData>
0017 #include <QMouseEvent>
0018 #include <QPainter>
0019 #include <QShortcut>
0020 #include <QTextStream>
0021 
0022 #include <KLocalizedString>
0023 #include <KMessageBox>
0024 #include <QIcon>
0025 
0026 #include "concwidget.h"
0027 #include "regexp.h"
0028 #include "regexpconverter.h"
0029 #include "userdefinedregexps.h"
0030 
0031 RegExpEditorWindow::RegExpEditorWindow(QWidget *parent)
0032     : QWidget(parent /*, Qt::WPaintUnclipped*/)
0033 {
0034     _top = new ConcWidget(this, this);
0035     _layout = new QHBoxLayout(this);
0036     _layout->addWidget(_top);
0037     _top->setToplevel();
0038     _menu = nullptr;
0039     _insertInAction = false;
0040     _pasteInAction = false;
0041     _pasteData = nullptr;
0042 
0043     _PosEdit = QPoint(0, 0);
0044 
0045     (void)new QShortcut(Qt::CTRL | Qt::Key_C, this, SLOT(slotCopy()));
0046     (void)new QShortcut(Qt::CTRL | Qt::Key_X, this, SLOT(slotCut()));
0047     (void)new QShortcut(Qt::Key_Delete, this, SLOT(slotCut()));
0048     (void)new QShortcut(Qt::Key_Backspace, this, SLOT(slotCut()));
0049     (void)new QShortcut(Qt::CTRL | Qt::Key_V, this, SLOT(slotStartPasteAction()));
0050     (void)new QShortcut(Qt::Key_Escape, this, SLOT(slotEndActions()));
0051     (void)new QShortcut(Qt::CTRL | Qt::Key_S, this, SLOT(slotSave()));
0052 
0053     connect(this, &RegExpEditorWindow::change, this, &RegExpEditorWindow::emitVerifyRegExp);
0054 }
0055 
0056 RegExpEditorWindow::~RegExpEditorWindow()
0057 {
0058     delete _pasteData;
0059 }
0060 
0061 RegExp *RegExpEditorWindow::regExp() const
0062 {
0063     return _top->regExp();
0064 }
0065 
0066 void RegExpEditorWindow::mousePressEvent(QMouseEvent *event)
0067 {
0068     setFocus();
0069     updateContent(nullptr);
0070 
0071     _start = event->pos();
0072     _lastPoint = QPoint(0, 0);
0073 
0074     if (pointSelected(event->globalPosition())) {
0075         _isDndOperation = true;
0076     } else {
0077         _isDndOperation = false;
0078         _selection = QRect();
0079         _top->updateSelection(false);
0080 
0081         QWidget::mousePressEvent(event);
0082     }
0083     grabMouse();
0084 }
0085 
0086 bool RegExpEditorWindow::pointSelected(const QPointF &p) const
0087 {
0088     QRectF rect = _top->selectionRect();
0089     return rect.contains(p);
0090 }
0091 
0092 void RegExpEditorWindow::mouseMoveEvent(QMouseEvent *event)
0093 {
0094     if (_isDndOperation) {
0095         if ((_start - event->pos()).manhattanLength() > QApplication::startDragDistance()) {
0096             RegExp *regexp = _top->selection();
0097             if (!regexp) {
0098                 return;
0099             }
0100 
0101             QDrag *drag = new QDrag(this);
0102             QMimeData *mimeData = new QMimeData;
0103 
0104             mimeData->setText(RegExpConverter::current()->toStr(regexp, false));
0105             mimeData->setData(QStringLiteral("KRegExpEditor/widgetdrag"), regexp->toXmlString().toLocal8Bit());
0106             delete regexp;
0107 
0108             drag->setMimeData(mimeData);
0109 
0110             Qt::DropAction dropAction = drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction);
0111             if (dropAction == Qt::MoveAction) {
0112                 slotDeleteSelection();
0113             } else {
0114                 clearSelection(true);
0115             }
0116 
0117             releaseMouse();
0118             Q_EMIT change();
0119             Q_EMIT canSave(_top->hasAnyChildren());
0120         }
0121     } else {
0122         _top->updateSelection(false);
0123 
0124         Q_EMIT scrolling(event->pos());
0125 
0126         _lastPoint = event->pos();
0127 
0128         update();
0129 
0130         _selection = QRectF(mapToGlobal(_start), mapToGlobal(_lastPoint)).normalized();
0131     }
0132 }
0133 
0134 void RegExpEditorWindow::mouseReleaseEvent(QMouseEvent *event)
0135 {
0136     releaseMouse();
0137     QWidget::mouseReleaseEvent(event);
0138 
0139     _lastPoint = QPoint(0, 0);
0140 
0141     _top->validateSelection();
0142     _top->updateAll();
0143     Q_EMIT anythingSelected(hasSelection());
0144     if (hasSelection()) {
0145         Q_EMIT verifyRegExp();
0146     }
0147 }
0148 
0149 bool RegExpEditorWindow::selectionOverlap(const QPointF &pos, QSize size) const
0150 {
0151     QRectF child(pos, size);
0152 
0153     return _selection.intersects(child) && !child.contains(_selection);
0154 }
0155 
0156 bool RegExpEditorWindow::hasSelection() const
0157 {
0158     return _top->hasSelection();
0159 }
0160 
0161 void RegExpEditorWindow::clearSelection(bool update)
0162 {
0163     _top->clearSelection();
0164     if (update) {
0165         _top->updateAll();
0166     }
0167     Q_EMIT anythingSelected(false);
0168 }
0169 
0170 void RegExpEditorWindow::slotInsertRegExp(RegExpType type)
0171 {
0172     _insertInAction = true;
0173     _insertTp = type;
0174 
0175     updateCursorUnderPoint();
0176     setFocus();
0177 }
0178 
0179 void RegExpEditorWindow::slotInsertRegExp(RegExp *regexp)
0180 {
0181     delete _pasteData;
0182 
0183     _pasteData = regexp->clone();
0184     _pasteInAction = true;
0185     updateCursorUnderPoint();
0186     setFocus();
0187 }
0188 
0189 void RegExpEditorWindow::slotDoSelect()
0190 {
0191     _pasteInAction = false;
0192     _insertInAction = false;
0193 
0194     // I need to update the cursor recursively, as a repaint may not have been issued yet
0195     // when this method is invoked. This means that when the repaint comes, the cursor may
0196     // move to an other widget.
0197     _top->updateCursorRecursively();
0198 }
0199 
0200 void RegExpEditorWindow::slotDeleteSelection()
0201 {
0202     if (!hasSelection()) {
0203         KMessageBox::information(this, i18n("There is no selection."), i18n("Missing Selection"));
0204     } else {
0205         _top->deleteSelection();
0206     }
0207     updateContent(nullptr);
0208 }
0209 
0210 void RegExpEditorWindow::updateContent(QWidget *focusChild)
0211 {
0212     QPoint p(0, 0);
0213     if (focusChild) {
0214         p = focusChild->mapTo(this, QPoint(0, 0));
0215     }
0216 
0217     _top->update();
0218     Q_EMIT contentChanged(p);
0219 }
0220 
0221 QSize RegExpEditorWindow::sizeHint() const
0222 {
0223     return _top->sizeHint();
0224 }
0225 
0226 void RegExpEditorWindow::paintEvent(QPaintEvent *event)
0227 {
0228     QPainter p(this);
0229 
0230     p.setPen(Qt::DotLine);
0231 
0232     if (!_lastPoint.isNull()) {
0233         p.drawRect(QRectF(_start, _lastPoint));
0234     }
0235 
0236     QWidget::paintEvent(event);
0237 }
0238 
0239 void RegExpEditorWindow::slotCut()
0240 {
0241     cut(QCursor::pos());
0242     Q_EMIT change();
0243     Q_EMIT canSave(_top->hasAnyChildren());
0244 }
0245 
0246 void RegExpEditorWindow::cut(QPointF pos)
0247 {
0248     cutCopyAux(pos);
0249     slotDeleteSelection();
0250 }
0251 
0252 void RegExpEditorWindow::slotCopy()
0253 {
0254     copy(QCursor::pos());
0255 }
0256 
0257 void RegExpEditorWindow::copy(QPointF pos)
0258 {
0259     cutCopyAux(pos);
0260     clearSelection(true);
0261 }
0262 
0263 void RegExpEditorWindow::cutCopyAux(QPointF pos)
0264 {
0265     if (!hasSelection()) {
0266         RegExpWidget *widget = _top->widgetUnderPoint(pos, true);
0267         if (!widget) {
0268             KMessageBox::information(this, i18n("There is no widget under cursor."), i18n("Invalid Operation"));
0269             return;
0270         } else {
0271             widget->updateSelection(true); // HACK!
0272         }
0273     }
0274 
0275     RegExp *regexp = _top->selection();
0276 
0277     QMimeData *mimeData = new QMimeData;
0278     mimeData->setText(RegExpConverter::current()->toStr(regexp, false));
0279     mimeData->setData(QStringLiteral("KRegExpEditor/widgetdrag"), regexp->toXmlString().toLocal8Bit());
0280 
0281     delete regexp;
0282 
0283     QClipboard *clipboard = qApp->clipboard();
0284     clipboard->setMimeData(mimeData);
0285     Q_EMIT anythingOnClipboard(true);
0286     Q_EMIT canSave(_top->hasAnyChildren());
0287 }
0288 
0289 void RegExpEditorWindow::slotStartPasteAction()
0290 {
0291     QString str = QString::fromLatin1(qApp->clipboard()->mimeData()->data(QStringLiteral("KRegExpEditor/widgetdrag")));
0292     if (str.isEmpty()) {
0293         return;
0294     }
0295 
0296     RegExp *regexp = WidgetFactory::createRegExp(str);
0297     if (regexp) {
0298         slotInsertRegExp(regexp);
0299     }
0300 }
0301 
0302 void RegExpEditorWindow::slotEndActions()
0303 {
0304     Q_EMIT doneEditing();
0305     Q_EMIT change();
0306     Q_EMIT canSave(_top->hasAnyChildren());
0307 }
0308 
0309 void RegExpEditorWindow::showRMBMenu(bool enableCutCopy)
0310 {
0311     enum CHOICES { CUT, COPY, PASTE, SAVE, EDIT };
0312 
0313     if (!_menu) {
0314         _menu = new QMenu(nullptr);
0315 
0316         _cutAction = _menu->addAction(getIcon(QStringLiteral("edit-cut")), i18n("C&ut"));
0317         connect(_cutAction, &QAction::triggered, this, &RegExpEditorWindow::slotCut);
0318 
0319         _copyAction = _menu->addAction(getIcon(QStringLiteral("edit-copy")), i18n("&Copy"));
0320         connect(_copyAction, &QAction::triggered, this, &RegExpEditorWindow::slotCopy);
0321 
0322         _pasteAction = _menu->addAction(getIcon(QStringLiteral("edit-paste")), i18n("&Paste"));
0323         connect(_pasteAction, &QAction::triggered, this, &RegExpEditorWindow::slotStartPasteAction);
0324 
0325         _menu->addSeparator();
0326 
0327         _editAction = _menu->addAction(getIcon(QStringLiteral("document-properties")), i18n("&Edit"));
0328         connect(_editAction, &QAction::triggered, this, &RegExpEditorWindow::editWidget);
0329 
0330         _saveAction = _menu->addAction(getIcon(QStringLiteral("document-save")), i18n("&Save Regular Expression..."));
0331         connect(_saveAction, &QAction::triggered, this, &RegExpEditorWindow::slotSave);
0332     }
0333 
0334     _cutAction->setEnabled(enableCutCopy);
0335     _copyAction->setEnabled(enableCutCopy);
0336 
0337     if (!qApp->clipboard()->mimeData()->hasFormat(QStringLiteral("KRegExpEditor/widgetdrag"))) {
0338         _pasteAction->setEnabled(false);
0339     } else {
0340         _pasteAction->setEnabled(true);
0341     }
0342 
0343     _saveAction->setEnabled(_top->hasAnyChildren());
0344 
0345     _PosEdit = QCursor::pos();
0346 
0347     RegExpWidget *editWidget = _top->findWidgetToEdit(_PosEdit);
0348 
0349     _editAction->setEnabled(editWidget);
0350 
0351     _menu->exec(_PosEdit.toPoint());
0352 
0353     _PosEdit = QPoint(0, 0);
0354 
0355     Q_EMIT change();
0356     Q_EMIT canSave(_top->hasAnyChildren());
0357 }
0358 
0359 void RegExpEditorWindow::applyRegExpToSelection(RegExpType tp)
0360 {
0361     _top->applyRegExpToSelection(tp);
0362 }
0363 
0364 void RegExpEditorWindow::slotSave()
0365 {
0366     QString dir = WidgetWinItem::path();
0367     QString txt;
0368 
0369     bool ok = false;
0370     const QString tmp = QInputDialog::getText(this, i18n("Name for Regular Expression"), i18n("Enter name:"), QLineEdit::Normal, QString(), &ok);
0371     if (!ok) {
0372         return;
0373     }
0374     if (tmp.trimmed().isEmpty()) {
0375         KMessageBox::error(this, i18n("Empty name is not supported"), i18n("Save Regular Expression"));
0376         return;
0377     }
0378     txt = tmp;
0379 
0380     QString fileName = dir + QLatin1Char('/') + txt + QStringLiteral(".regexp");
0381     QFileInfo finfo(fileName);
0382     if (finfo.exists()) {
0383         int answer = KMessageBox::warningContinueCancel(this,
0384                                                         i18n("<p>Overwrite named regular expression <b>%1</b></p>", txt),
0385                                                         QString(),
0386                                                         KStandardGuiItem::overwrite());
0387         if (answer != KMessageBox::Continue) {
0388             return;
0389         }
0390     }
0391 
0392     QFile file(fileName);
0393     if (!file.open(QIODevice::WriteOnly)) {
0394         KMessageBox::error(this, i18n("Could not open file for writing: %1", fileName));
0395         return;
0396     }
0397 
0398     // Convert to XML.
0399     RegExp *regexp = _top->regExp();
0400     QString xml = regexp->toXmlString();
0401     delete regexp;
0402 
0403     QTextStream stream(&file);
0404     stream << xml;
0405 
0406     file.close();
0407     Q_EMIT savedRegexp();
0408 }
0409 
0410 void RegExpEditorWindow::slotSetRegExp(RegExp *regexp)
0411 {
0412     // I have no clue why the following line is necesarry, but if it is not here
0413     // then the editor area is messed up when calling slotSetRegExp before starting the eventloop.
0414     qApp->processEvents();
0415 
0416     delete _top;
0417     RegExpWidget *widget = WidgetFactory::createWidget(regexp, this, this);
0418     if (!(_top = dynamic_cast<ConcWidget *>(widget))) {
0419         // It was not a ConcWidget
0420         _top = new ConcWidget(this, widget, this);
0421     }
0422     _top->setToplevel();
0423 
0424     _top->show();
0425     _layout->addWidget(_top);
0426     clearSelection(true); // HACK?
0427     Q_EMIT canSave(_top->hasAnyChildren());
0428 }
0429 
0430 void RegExpEditorWindow::updateCursorUnderPoint()
0431 {
0432     RegExpWidget *widget = _top->widgetUnderPoint(QCursor::pos(), false);
0433     if (widget) {
0434         widget->updateCursorShape();
0435     }
0436 }
0437 
0438 void RegExpEditorWindow::emitVerifyRegExp()
0439 {
0440     Q_EMIT verifyRegExp();
0441 }
0442 
0443 void RegExpEditorWindow::editWidget()
0444 {
0445     auto EditPos = _PosEdit.isNull() ? QCursor::pos() : _PosEdit;
0446     RegExpWidget *editWidget = _top->findWidgetToEdit(EditPos);
0447     if (editWidget) {
0448         editWidget->edit();
0449     }
0450 }
0451 
0452 QIcon RegExpEditorWindow::getIcon(const QString &name)
0453 {
0454     return QIcon::fromTheme(name);
0455 }
0456 
0457 #include "moc_regexpeditorwindow.cpp"