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"