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

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 "kregexpeditorprivate.h"
0008 
0009 #include <KIconLoader>
0010 #include <KLocalizedString>
0011 #include <QIcon>
0012 
0013 #include <KMessageBox>
0014 #include <QLineEdit>
0015 
0016 #include <QApplication>
0017 #include <QFile>
0018 #include <QHBoxLayout>
0019 #include <QLabel>
0020 #include <QShortcut>
0021 #include <QSplitter>
0022 #include <QStandardPaths>
0023 #include <QTextStream>
0024 #include <QTimer>
0025 #include <QToolButton>
0026 
0027 #include "infopage.h"
0028 #include "regexp.h"
0029 #include "regexpbuttons.h"
0030 #include "regexpconverter.h"
0031 #include "scrollededitorwindow.h"
0032 #include "userdefinedregexps.h"
0033 #include "verifier.h"
0034 
0035 KRegExpEditorPrivate::KRegExpEditorPrivate(QWidget *parent)
0036     : QMainWindow(parent)
0037     , _updating(false)
0038     , _autoVerify(true)
0039     , _matchGreedy(false)
0040 {
0041     setMinimumSize(730, 300);
0042     setWindowFlags(Qt::Widget);
0043 
0044     // The DockWindows.
0045     _regExpButtons = new RegExpButtons(this, QStringLiteral("RegExpButton"));
0046     addToolBar(Qt::LeftToolBarArea, _regExpButtons);
0047 
0048     _userRegExps = new UserDefinedRegExps(/*verArea1*/ this, /*"KRegExpEditorPrivate::userRegExps"*/ i18n("Compound regular expression:"));
0049     _userRegExps->setWhatsThis(
0050         i18n("In this window you will find predefined regular expressions. Both regular expressions "
0051              "you have developed and saved, and regular expressions shipped with the system."));
0052     addDockWidget(Qt::LeftDockWidgetArea, _userRegExps);
0053 
0054     // Editor window
0055     _editor = new QSplitter(Qt::Vertical, this);
0056     _editor->setObjectName(QStringLiteral("KRegExpEditorPrivate::_editor"));
0057 
0058     _scrolledEditorWindow = new RegExpScrolledEditorWindow(_editor);
0059     _scrolledEditorWindow->setWhatsThis(
0060         i18n("In this window you will develop your regular expressions. "
0061              "Select one of the actions from the action buttons above, and click the mouse in this "
0062              "window to insert the given action."));
0063 
0064     _info = new InfoPage(this);
0065     _info->setObjectName(QStringLiteral("_info"));
0066     _verifier = new Verifier(_editor);
0067     connect(_verifier, &QTextEdit::textChanged, this, &KRegExpEditorPrivate::maybeVerify);
0068     _verifier->setWhatsThis(
0069         i18n("<p>Type in some text in this window, and see what the regular expression you have developed matches.</p>"
0070              "<p>Each second match will be colored in red and each other match will be colored blue, simply so you "
0071              "can distinguish them from each other.</p>"
0072              "<p>If you select part of the regular expression in the editor window, then this part will be "
0073              "highlighted - This allows you to <i>debug</i> your regular expressions</p>"));
0074 
0075     _editor->hide();
0076     _editor->setSizes(QList<int>() << _editor->height() / 2 << _editor->height() / 2);
0077 
0078     QWidget *centralWidget = new QWidget(this);
0079     QHBoxLayout *layout = new QHBoxLayout(centralWidget);
0080     layout->setContentsMargins(0, 0, 0, 0);
0081     layout->addWidget(_editor);
0082     layout->addWidget(_info);
0083     setCentralWidget(centralWidget);
0084 
0085     // Connect the buttons
0086     connect(_regExpButtons, SIGNAL(clicked(int)), _scrolledEditorWindow, SLOT(slotInsertRegExp(int)));
0087     connect(_regExpButtons, &RegExpButtons::doSelect, _scrolledEditorWindow, &RegExpScrolledEditorWindow::slotDoSelect);
0088     connect(_userRegExps, SIGNAL(load(RegExp *)), _scrolledEditorWindow, SLOT(slotInsertRegExp(RegExp *)));
0089 
0090     connect(_regExpButtons, SIGNAL(clicked(int)), _userRegExps, SLOT(slotUnSelect()));
0091     connect(_regExpButtons, SIGNAL(doSelect()), _userRegExps, SLOT(slotUnSelect()));
0092     connect(_userRegExps, &UserDefinedRegExps::load, _regExpButtons, &RegExpButtons::slotUnSelect);
0093 
0094     connect(_scrolledEditorWindow, &RegExpScrolledEditorWindow::doneEditing, _regExpButtons, &RegExpButtons::slotSelectNewAction);
0095     connect(_scrolledEditorWindow, &RegExpScrolledEditorWindow::doneEditing, _userRegExps, &UserDefinedRegExps::slotSelectNewAction);
0096 
0097     connect(_regExpButtons, &RegExpButtons::clicked, this, &KRegExpEditorPrivate::slotShowEditor);
0098     connect(_userRegExps, &UserDefinedRegExps::load, this, &KRegExpEditorPrivate::slotShowEditor);
0099     connect(_regExpButtons, &RegExpButtons::doSelect, this, &KRegExpEditorPrivate::slotShowEditor);
0100 
0101     connect(_scrolledEditorWindow, SIGNAL(savedRegexp()), _userRegExps, SLOT(slotPopulateUserRegexps()));
0102 
0103     connect(_scrolledEditorWindow, &RegExpScrolledEditorWindow::anythingSelected, this, &KRegExpEditorPrivate::anythingSelected);
0104     connect(_scrolledEditorWindow, &RegExpScrolledEditorWindow::anythingOnClipboard, this, &KRegExpEditorPrivate::anythingOnClipboard);
0105     connect(_scrolledEditorWindow, &RegExpScrolledEditorWindow::canSave, this, &KRegExpEditorPrivate::canSave);
0106 
0107     connect(_scrolledEditorWindow, &RegExpScrolledEditorWindow::verifyRegExp, this, &KRegExpEditorPrivate::maybeVerify);
0108 
0109     // Line Edit
0110     QDockWidget *editDock = new QDockWidget(i18n("ASCII syntax:"), this);
0111     editDock->setFeatures(QDockWidget::NoDockWidgetFeatures);
0112     addDockWidget(Qt::BottomDockWidgetArea, editDock);
0113 
0114     QWidget *editDockWidget = new QWidget(editDock);
0115     editDock->setWidget(editDockWidget);
0116     QHBoxLayout *dockLayout = new QHBoxLayout(editDockWidget);
0117     dockLayout->setContentsMargins(0, 0, 0, 0);
0118 
0119     _regexpEdit = new QLineEdit(editDockWidget);
0120     dockLayout->addWidget(_regexpEdit);
0121     _regexpEdit->setFocus(Qt::OtherFocusReason);
0122     _regexpEdit->setClearButtonEnabled(true);
0123     _regexpEdit->setWhatsThis(
0124         i18n("<p>This is the regular expression in ASCII syntax. You are likely only "
0125              "to be interested in this if you are a programmer, and need to "
0126              "develop a regular expression using QRegExp.</p>"
0127              "<p>You may develop your regular expression both by using the graphical "
0128              "editor, and by typing the regular expression in this line edit.</p>"));
0129 
0130     QPixmap pix = KIconLoader::global()->loadIcon(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kregexpeditor/pics/error.png")),
0131                                                   KIconLoader::Toolbar);
0132     _error = new QLabel(editDockWidget);
0133     _error->setPixmap(pix);
0134     dockLayout->addWidget(_error);
0135     _error->hide();
0136 
0137     _timer = new QTimer(this);
0138     _timer->setSingleShot(true);
0139 
0140     connect(_scrolledEditorWindow, &RegExpScrolledEditorWindow::change, this, &KRegExpEditorPrivate::slotUpdateLineEdit);
0141     connect(_regexpEdit, &QLineEdit::textChanged, this, &KRegExpEditorPrivate::slotTriggerUpdate);
0142     connect(_timer, &QTimer::timeout, this, &KRegExpEditorPrivate::slotTimeout);
0143 
0144     // Push an initial empty element on the stack.
0145     _undoStack.push(_scrolledEditorWindow->regExp());
0146 }
0147 
0148 KRegExpEditorPrivate::~KRegExpEditorPrivate()
0149 {
0150     qDeleteAll(_undoStack);
0151     qDeleteAll(_redoStack);
0152 }
0153 
0154 QString KRegExpEditorPrivate::regexp()
0155 {
0156     RegExp *regexp = _scrolledEditorWindow->regExp();
0157     QString res = RegExpConverter::current()->toStr(regexp, false);
0158     delete regexp;
0159     return res;
0160 }
0161 
0162 void KRegExpEditorPrivate::slotUpdateEditor(const QString &txt)
0163 {
0164     _updating = true;
0165     bool ok;
0166     if (!RegExpConverter::current()->canParse()) {
0167         // This can happend if the application set a text through the API.
0168         // qDebug("cannot parse");
0169     } else {
0170         RegExp *result = RegExpConverter::current()->parse(txt, &ok);
0171         if (ok) {
0172             const QList<CompoundRegExp *> list = _userRegExps->regExps();
0173             for (CompoundRegExp *regExp : list) {
0174                 result->replacePart(regExp);
0175             }
0176 
0177             _scrolledEditorWindow->slotSetRegExp(result);
0178             _error->hide();
0179             maybeVerify();
0180             recordUndoInfo();
0181             result->check(_errorMap);
0182         } else {
0183             _error->show();
0184             if (_autoVerify) {
0185                 _verifier->clearRegexp();
0186             }
0187         }
0188         delete result;
0189     }
0190     _updating = false;
0191 }
0192 
0193 void KRegExpEditorPrivate::slotUpdateLineEdit()
0194 {
0195     if (_updating) {
0196         return;
0197     }
0198     _updating = true;
0199 
0200     RegExp *regexp = _scrolledEditorWindow->regExp();
0201     regexp->check(_errorMap);
0202 
0203     QString str = RegExpConverter::current()->toStr(regexp, false);
0204     _regexpEdit->setText(str);
0205     delete regexp;
0206 
0207     recordUndoInfo();
0208 
0209     _updating = false;
0210 }
0211 
0212 void KRegExpEditorPrivate::recordUndoInfo()
0213 {
0214     Q_ASSERT(_updating);
0215 
0216     // Update undo/redo stacks
0217     RegExp *regexp = _scrolledEditorWindow->regExp();
0218     if (regexp->toXmlString() != _undoStack.top()->toXmlString()) {
0219         _undoStack.push(regexp);
0220         qDeleteAll(_redoStack);
0221         _redoStack = QStack<RegExp *>();
0222         emitUndoRedoSignals();
0223     }
0224 }
0225 
0226 void KRegExpEditorPrivate::slotRedo()
0227 {
0228     if (!_redoStack.isEmpty()) {
0229         _undoStack.push(_redoStack.pop());
0230         _scrolledEditorWindow->slotSetRegExp(_undoStack.top());
0231         slotUpdateLineEdit();
0232         emitUndoRedoSignals();
0233         maybeVerify();
0234     }
0235 }
0236 
0237 void KRegExpEditorPrivate::slotUndo()
0238 {
0239     if (_undoStack.count() > 1) {
0240         _redoStack.push(_undoStack.pop());
0241         _scrolledEditorWindow->slotSetRegExp(_undoStack.top());
0242         slotUpdateLineEdit();
0243         emitUndoRedoSignals();
0244         maybeVerify();
0245     }
0246 }
0247 
0248 void KRegExpEditorPrivate::slotCut()
0249 {
0250     _scrolledEditorWindow->slotCut();
0251 }
0252 
0253 void KRegExpEditorPrivate::slotCopy()
0254 {
0255     _scrolledEditorWindow->slotCopy();
0256 }
0257 
0258 void KRegExpEditorPrivate::slotPaste()
0259 {
0260     _scrolledEditorWindow->slotPaste();
0261 }
0262 
0263 void KRegExpEditorPrivate::slotSave()
0264 {
0265     _scrolledEditorWindow->slotSave();
0266 }
0267 
0268 void KRegExpEditorPrivate::slotShowEditor()
0269 {
0270     _info->hide();
0271     _editor->show();
0272 }
0273 
0274 void KRegExpEditorPrivate::emitUndoRedoSignals()
0275 {
0276     Q_EMIT canUndo(_undoStack.count() > 1);
0277     Q_EMIT changes(_undoStack.count() > 1);
0278     Q_EMIT canRedo(_redoStack.count() > 0);
0279 }
0280 
0281 void KRegExpEditorPrivate::slotSetRegexp(const QString &regexp)
0282 {
0283     _regexpEdit->setText(regexp);
0284 }
0285 
0286 void KRegExpEditorPrivate::slotTriggerUpdate()
0287 {
0288     /* ### Guess this timeout value should be configurable somewhere, or (even
0289      * better: do some kind of benchmark each time the editor view gets updated
0290      * to measure how long it takes on the client system to render the editor
0291      * with the current complexity. That way we'd get good response times for
0292      * simple regexps, and flicker-free display for complex regexps.
0293      * - Frerich
0294      */
0295     if (!_updating) {
0296         _timer->start(300);
0297         slotShowEditor();
0298     }
0299 }
0300 
0301 void KRegExpEditorPrivate::slotTimeout()
0302 {
0303     slotUpdateEditor(_regexpEdit->text());
0304 }
0305 
0306 void KRegExpEditorPrivate::setMatchText(const QString &text)
0307 {
0308     bool autoVerify = _autoVerify;
0309     _autoVerify = false;
0310     _verifier->setText(text);
0311     _autoVerify = autoVerify;
0312 }
0313 
0314 void KRegExpEditorPrivate::maybeVerify()
0315 {
0316     if (_autoVerify) {
0317         doVerify();
0318     }
0319 }
0320 
0321 void KRegExpEditorPrivate::doVerify()
0322 {
0323     bool autoVerify = _autoVerify; // prevent loop due to verify emit changed, which calls maybeVerify
0324     _autoVerify = false;
0325     RegExp *regexp = _scrolledEditorWindow->regExp();
0326 
0327     _verifier->verify(RegExpConverter::current()->toStr(regexp, true));
0328     delete regexp;
0329     _autoVerify = autoVerify;
0330 }
0331 
0332 void KRegExpEditorPrivate::setAutoVerify(bool on)
0333 {
0334     _autoVerify = on;
0335     if (!_autoVerify) {
0336         _verifier->clearRegexp();
0337     } else {
0338         doVerify();
0339     }
0340 }
0341 
0342 void KRegExpEditorPrivate::setVerifyText(const QString &fileName)
0343 {
0344     bool autoVerify = _autoVerify;
0345     _autoVerify = false;
0346     QFile file(fileName);
0347     if (!file.open(QIODevice::ReadOnly)) {
0348         KMessageBox::error(nullptr, i18n("Could not open file '%1' for reading", fileName));
0349     } else {
0350         QTextStream s(&file);
0351         QString txt = s.readAll();
0352         file.close();
0353         RegExp *regexp = _scrolledEditorWindow->regExp();
0354         _verifier->setText(txt);
0355         _verifier->verify(RegExpConverter::current()->toStr(regexp, true));
0356         delete regexp;
0357     }
0358     _autoVerify = autoVerify;
0359 }
0360 
0361 void KRegExpEditorPrivate::setCaseSensitive(bool b)
0362 {
0363     _verifier->setCaseSensitive(b);
0364 }
0365 
0366 void KRegExpEditorPrivate::setMinimal(bool b)
0367 {
0368     _verifier->setMinimal(b);
0369 }
0370 
0371 void KRegExpEditorPrivate::setSyntax(RegExpConverter *converter)
0372 {
0373     RegExpConverter::setCurrent(converter);
0374     if (converter->canParse()) {
0375         _regexpEdit->setReadOnly(false);
0376         _regexpEdit->setBackgroundRole(QPalette::Base);
0377     } else {
0378         _regexpEdit->setReadOnly(true);
0379         _regexpEdit->setBackgroundRole(QPalette::Window);
0380     }
0381     _regExpButtons->setFeatures(converter->features());
0382     _verifier->setHighlighter(converter->highlighter(_verifier));
0383     slotUpdateLineEdit();
0384 }
0385 
0386 void KRegExpEditorPrivate::showHelp()
0387 {
0388     _info->show();
0389     _editor->hide();
0390 }
0391 
0392 void KRegExpEditorPrivate::setMatchGreedy(bool b)
0393 {
0394     _matchGreedy = b;
0395     _verifier->setMinimal(!b);
0396     doVerify();
0397 }
0398 
0399 #include "moc_kregexpeditorprivate.cpp"