File indexing completed on 2025-03-09 03:57:07

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2009-09-03
0007  * Description : an input widget for the AdvancedRename utility
0008  *
0009  * SPDX-FileCopyrightText: 2009-2012 by Andi Clemens <andi dot clemens at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "advancedrenameinput.h"
0016 
0017 // Qt includes
0018 
0019 #include <QFontMetrics>
0020 #include <QLayout>
0021 #include <QScrollBar>
0022 #include <QTimer>
0023 #include <QApplication>
0024 #include <QStyle>
0025 
0026 // KDE includes
0027 
0028 #include <kconfiggroup.h>
0029 #include <ksharedconfig.h>
0030 
0031 // Local includes
0032 
0033 #include "highlighter.h"
0034 
0035 // Constants
0036 
0037 namespace
0038 {
0039 static const quint8 INVALID = -1;
0040 static const QString DUMMY_TEXT(QLatin1String("DUMMY_TEXT_y_fjqp|"));
0041 }
0042 
0043 namespace Digikam
0044 {
0045 
0046 class Q_DECL_HIDDEN AdvancedRenameLineEdit::Private
0047 {
0048 public:
0049 
0050     explicit Private()
0051       : allowDirectoryCreation(false),
0052         verticalSliderPosition(INVALID),
0053         parseTimer            (nullptr),
0054         parser                (nullptr)
0055     {
0056     }
0057 
0058     bool    allowDirectoryCreation;
0059     int     verticalSliderPosition;
0060 
0061     QString lastPlainText;
0062 
0063     QTimer* parseTimer;
0064     Parser* parser;
0065 };
0066 
0067 AdvancedRenameLineEdit::AdvancedRenameLineEdit(QWidget* const parent)
0068     : QPlainTextEdit(parent),
0069       d             (new Private)
0070 {
0071     setupWidgets();
0072     setupConnections();
0073 }
0074 
0075 AdvancedRenameLineEdit::~AdvancedRenameLineEdit()
0076 {
0077     delete d;
0078 }
0079 
0080 void AdvancedRenameLineEdit::setupWidgets()
0081 {
0082     setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0083     setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0084     setLineWrapMode(QPlainTextEdit::NoWrap);
0085     setWordWrapMode(QTextOption::NoWrap);
0086     setFocusPolicy(Qt::StrongFocus);
0087     setFrameStyle(QFrame::NoFrame);
0088     setPalette(qApp->palette());
0089 
0090     QFontMetrics fm = fontMetrics();
0091     int textHeight  = fm.boundingRect(DUMMY_TEXT).height();
0092     document()->setDocumentMargin(1);
0093     setFixedHeight(textHeight + 8);
0094 
0095     // --------------------------------------------------------
0096 
0097     d->parseTimer = new QTimer(this);
0098     d->parseTimer->setInterval(500);
0099     d->parseTimer->setSingleShot(true);
0100 
0101     // --------------------------------------------------------
0102 
0103     // layout widget correctly by setting a dummy text and calling ensureCursorVisible().
0104     // Save the scrollbar position now, to avoid scrolling of the text when selecting with the mouse
0105 
0106     setPlainText(DUMMY_TEXT);
0107     ensureCursorVisible();
0108     d->verticalSliderPosition = verticalScrollBar()->value();
0109     clear();
0110 }
0111 
0112 void AdvancedRenameLineEdit::setupConnections()
0113 {
0114     connect(d->parseTimer, SIGNAL(timeout()),
0115             this, SLOT(slotParseTimer()));
0116 
0117     connect(this, SIGNAL(textChanged()),
0118             this, SLOT(slotTextChanged()));
0119 
0120     connect(this, SIGNAL(cursorPositionChanged()),
0121             this, SLOT(slotCursorPositionChanged()));
0122 }
0123 
0124 void AdvancedRenameLineEdit::setParseTimerDuration(int milliseconds)
0125 {
0126     d->parseTimer->setInterval(milliseconds);
0127 }
0128 
0129 void AdvancedRenameLineEdit::setParser(Parser* parser)
0130 {
0131     if (parser)
0132     {
0133         d->parser = parser;
0134     }
0135 }
0136 
0137 Parser* AdvancedRenameLineEdit::parser() const
0138 {
0139     return d->parser;
0140 }
0141 
0142 void AdvancedRenameLineEdit::keyPressEvent(QKeyEvent* e)
0143 {
0144     switch (e->key())
0145     {
0146         // avoid newlines in the new name
0147 
0148         case Qt::Key_Enter:
0149         case Qt::Key_Return:
0150         {
0151             slotParseTimer();
0152             Q_EMIT signalReturnPressed();
0153             break;
0154         }
0155 
0156         // the keys "Up, Down, PageUp, PageDown" should be send to the QComboBox
0157 
0158         case Qt::Key_Up:
0159         case Qt::Key_PageUp:
0160         case Qt::Key_Down:
0161         case Qt::Key_PageDown:
0162         {
0163             e->setAccepted(false);
0164             break;
0165         }
0166 
0167         // the key "/" should not be allowed (QTextEdit is not able to use a QValidator, so we must do it in here)
0168 
0169         case Qt::Key_Slash:
0170         {
0171             if (!d->allowDirectoryCreation)
0172             {
0173                 // do nothing
0174             }
0175 
0176             break;
0177         }
0178 
0179         default:
0180         {
0181             QPlainTextEdit::keyPressEvent(e);
0182             break;
0183         }
0184     }
0185 }
0186 
0187 void AdvancedRenameLineEdit::wheelEvent(QWheelEvent* e)
0188 {
0189     e->setAccepted(false);
0190 }
0191 
0192 void AdvancedRenameLineEdit::slotTextChanged()
0193 {
0194     d->parseTimer->start();
0195 }
0196 
0197 void AdvancedRenameLineEdit::slotParseTimer()
0198 {
0199     if (d->lastPlainText != toPlainText())
0200     {
0201         d->lastPlainText = toPlainText();
0202 
0203         Q_EMIT signalTextChanged(d->lastPlainText);
0204     }
0205 }
0206 
0207 void AdvancedRenameLineEdit::scrollContentsBy(int dx, int dy)
0208 {
0209     Q_UNUSED(dx)
0210     Q_UNUSED(dy)
0211 
0212     if (d->verticalSliderPosition != INVALID)
0213     {
0214         verticalScrollBar()->setValue(d->verticalSliderPosition);
0215     }
0216 
0217     viewport()->update();
0218 }
0219 
0220 void AdvancedRenameLineEdit::slotCursorPositionChanged()
0221 {
0222     bool found = false;
0223 
0224     if (d->parser)
0225     {
0226         ParseSettings settings;
0227         settings.parseString = toPlainText();
0228         int start            = INVALID;
0229         int length           = INVALID;
0230         int pos              = textCursor().position();
0231         found                = d->parser->tokenAtPosition(settings, pos, start, length);
0232         found                = found && ((start + length) == pos);
0233     }
0234 
0235     Q_EMIT signalTokenMarked(found);
0236 }
0237 
0238 void AdvancedRenameLineEdit::slotSetText(const QString& text)
0239 {
0240     clear();
0241     setPlainText(text);
0242     QTextCursor cursor = textCursor();
0243     cursor.movePosition(QTextCursor::EndOfLine);
0244     setTextCursor(cursor);
0245     setFocus();
0246 }
0247 
0248 // --------------------------------------------------------
0249 
0250 class Q_DECL_HIDDEN AdvancedRenameInput::Private
0251 {
0252 public:
0253 
0254     explicit Private()
0255       : maxVisibleItems(10),
0256         maxHistoryItems(30),
0257         lineEdit       (nullptr),
0258         proxy          (nullptr),
0259         highlighter    (nullptr)
0260     {
0261     }
0262 
0263     static const QString    configGroupName;
0264     static const QString    configPatternHistoryListEntry;
0265 
0266     const int               maxVisibleItems;
0267     const int               maxHistoryItems;
0268 
0269     AdvancedRenameLineEdit* lineEdit;
0270     ProxyLineEdit*          proxy;
0271     Highlighter*            highlighter;
0272 };
0273 
0274 const QString AdvancedRenameInput::Private::configGroupName(QLatin1String("AdvancedRename Input"));
0275 const QString AdvancedRenameInput::Private::configPatternHistoryListEntry(QLatin1String("Pattern History List"));
0276 
0277 // --------------------------------------------------------
0278 
0279 AdvancedRenameInput::AdvancedRenameInput(QWidget* const parent)
0280     : QComboBox(parent),
0281       d        (new Private)
0282 {
0283     setupWidgets();
0284     setupConnections();
0285 
0286     readSettings();
0287 }
0288 
0289 AdvancedRenameInput::~AdvancedRenameInput()
0290 {
0291     writeSettings();
0292     delete d;
0293 }
0294 
0295 void AdvancedRenameInput::setParser(Parser* parser)
0296 {
0297     d->lineEdit->setParser(parser);
0298     enableHighlighter(true);
0299 }
0300 
0301 void AdvancedRenameInput::setParseTimerDuration(int milliseconds)
0302 {
0303     d->lineEdit->setParseTimerDuration(milliseconds);
0304 }
0305 
0306 void AdvancedRenameInput::setText(const QString& text)
0307 {
0308     d->lineEdit->slotSetText(text);
0309 }
0310 
0311 void AdvancedRenameInput::slotClearText()
0312 {
0313     d->lineEdit->clear();
0314 }
0315 
0316 void AdvancedRenameInput::slotClearTextAndHistory()
0317 {
0318     d->lineEdit->clear();
0319     clear();
0320 }
0321 
0322 void AdvancedRenameInput::slotSetFocus()
0323 {
0324     d->lineEdit->setFocus();
0325     d->lineEdit->ensureCursorVisible();
0326 }
0327 
0328 void AdvancedRenameInput::slotHighlightLineEdit()
0329 {
0330     d->lineEdit->selectAll();
0331 }
0332 
0333 void AdvancedRenameInput::slotHighlightLineEdit(const QString& word)
0334 {
0335     QTextCursor cursor = d->lineEdit->textCursor();
0336     cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
0337     d->lineEdit->setTextCursor(cursor);
0338     d->lineEdit->find(word, QTextDocument::FindCaseSensitively);
0339 }
0340 
0341 void AdvancedRenameInput::enableHighlighter(bool enable)
0342 {
0343     delete d->highlighter;
0344     d->highlighter = enable ? new Highlighter(d->lineEdit->document(), d->lineEdit->parser())
0345                             : nullptr;
0346 }
0347 
0348 void AdvancedRenameInput::setupWidgets()
0349 {
0350     setEditable(true);
0351 
0352     setMaxVisibleItems(d->maxVisibleItems);
0353     setMaxCount(d->maxHistoryItems);
0354 
0355     d->lineEdit = new AdvancedRenameLineEdit(this);
0356     d->proxy    = new ProxyLineEdit(this);
0357     d->proxy->setWidget(d->lineEdit);
0358     d->proxy->setClearButtonShown(true);
0359     d->proxy->setContentsMargins(0, -2, 0, 2);
0360 
0361     setMinimumHeight(d->lineEdit->height() + 2);
0362     setLineEdit(d->proxy);
0363 }
0364 
0365 void AdvancedRenameInput::setupConnections()
0366 {
0367     connect(d->proxy, SIGNAL(signalClearButtonPressed()),
0368             this, SLOT(slotClearButtonPressed()));
0369 
0370     connect(d->lineEdit, SIGNAL(signalTextChanged(QString)),
0371             this, SLOT(slotTextChanged(QString)));
0372 
0373     connect(d->lineEdit, SIGNAL(signalTokenMarked(bool)),
0374             this, SIGNAL(signalTokenMarked(bool)));
0375 
0376     connect(d->lineEdit, SIGNAL(signalReturnPressed()),
0377             this, SIGNAL(signalReturnPressed()));
0378 
0379     connect(this, SIGNAL(currentIndexChanged(int)),
0380             this, SLOT(slotIndexChanged(int)));
0381 }
0382 
0383 void AdvancedRenameInput::slotIndexChanged(int index)
0384 {
0385     setText(itemText(index));
0386 }
0387 
0388 void AdvancedRenameInput::changeEvent(QEvent* e)
0389 {
0390     QComboBox::changeEvent(e);
0391 
0392     if (e->type() == QEvent::EnabledChange)
0393     {
0394         enableHighlighter(isEnabled());
0395     }
0396 }
0397 
0398 void AdvancedRenameInput::slotClearButtonPressed()
0399 {
0400     slotClearText();
0401     slotSetFocus();
0402 }
0403 
0404 void AdvancedRenameInput::slotTextChanged(const QString& text)
0405 {
0406     d->proxy->setText(text);
0407 
0408     Q_EMIT signalTextChanged(text);
0409 }
0410 
0411 QString AdvancedRenameInput::text() const
0412 {
0413     return d->lineEdit->toPlainText();
0414 }
0415 
0416 void AdvancedRenameInput::slotAddToken(const QString& token)
0417 {
0418     d->lineEdit->insertPlainText(token);
0419     slotSetFocus();
0420 }
0421 
0422 void AdvancedRenameInput::readSettings()
0423 {
0424     KSharedConfig::Ptr config  = KSharedConfig::openConfig();
0425     KConfigGroup group         = config->group(d->configGroupName);
0426     QStringList patternHistory = group.readEntry(d->configPatternHistoryListEntry, QStringList());
0427     patternHistory.removeAll(QLatin1String(""));
0428     addItems(patternHistory);
0429     d->lineEdit->clear();
0430     setCurrentIndex(-1);
0431 }
0432 
0433 void AdvancedRenameInput::writeSettings()
0434 {
0435     KSharedConfig::Ptr config  = KSharedConfig::openConfig();
0436     KConfigGroup group         = config->group(d->configGroupName);
0437     QStringList patternHistory = group.readEntry(d->configPatternHistoryListEntry, QStringList());
0438 
0439     // remove duplicate entries and save pattern history, omit empty strings
0440 
0441     QString pattern = d->lineEdit->toPlainText();
0442     patternHistory.removeAll(pattern);
0443     patternHistory.removeAll(QLatin1String(""));
0444     patternHistory.prepend(pattern);
0445     group.writeEntry(d->configPatternHistoryListEntry, patternHistory);
0446 }
0447 
0448 } // namespace Digikam
0449 
0450 #include "moc_advancedrenameinput.cpp"