File indexing completed on 2024-05-12 17:21:58
0001 /* 0002 SPDX-FileCopyrightText: 2009 Csaba Karai <krusader@users.sourceforge.net> 0003 SPDX-FileCopyrightText: 2009-2022 Krusader Krew <https://krusader.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "lister.h" 0009 0010 // QtCore 0011 #include <QDate> 0012 #include <QFile> 0013 #include <QRect> 0014 #include <QTemporaryFile> 0015 #include <QTextStream> 0016 // QtGui 0017 #include <QClipboard> 0018 #include <QFontDatabase> 0019 #include <QFontMetrics> 0020 #include <QKeyEvent> 0021 #include <QPainter> 0022 // QtWidgets 0023 #include <QApplication> 0024 #include <QFileDialog> 0025 #include <QHBoxLayout> 0026 #include <QInputDialog> 0027 #include <QLabel> 0028 #include <QLineEdit> 0029 #include <QMenu> 0030 #include <QProgressBar> 0031 #include <QPushButton> 0032 #include <QScrollBar> 0033 #include <QSpacerItem> 0034 #include <QToolButton> 0035 // QtPrintSupport 0036 #include <QPrintDialog> 0037 #include <QPrinter> 0038 0039 #include <KCodecs/KCharsets> 0040 #include <KConfigCore/KSharedConfig> 0041 #include <KCoreAddons/KJobTrackerInterface> 0042 #include <KI18n/KLocalizedString> 0043 #include <KIO/CopyJob> 0044 #include <KIO/JobUiDelegate> 0045 #include <KIO/TransferJob> 0046 #include <KParts/GUIActivateEvent> 0047 #include <KWidgetsAddons/KMessageBox> 0048 #include <KXmlGui/KActionCollection> 0049 0050 #include "../GUI/krremoteencodingmenu.h" 0051 #include "../compat.h" 0052 #include "../icon.h" 0053 #include "../kractions.h" 0054 #include "../krglobal.h" 0055 0056 #define SEARCH_CACHE_CHARS 100000 0057 #define SEARCH_MAX_ROW_LEN 4000 0058 #define CONTROL_CHAR 752 0059 #define CACHE_SIZE 1048576 // cache size set to 1MiB 0060 0061 ListerTextArea::ListerTextArea(Lister *lister, QWidget *parent) 0062 : KTextEdit(parent) 0063 , _lister(lister) 0064 { 0065 connect(this, &QTextEdit::cursorPositionChanged, this, &ListerTextArea::slotCursorPositionChanged); 0066 _tabWidth = 4; 0067 setWordWrapMode(QTextOption::NoWrap); 0068 setLineWrapMode(QTextEdit::NoWrap); 0069 0070 // zoom shortcuts 0071 connect(new QShortcut(QKeySequence("Ctrl++"), this), &QShortcut::activated, this, [=]() { 0072 zoomIn(); 0073 }); 0074 connect(new QShortcut(QKeySequence("Ctrl+-"), this), &QShortcut::activated, this, [=]() { 0075 zoomOut(); 0076 }); 0077 0078 // start cursor blinking 0079 connect(&_blinkTimer, &QTimer::timeout, this, [=] { 0080 if (!_cursorBlinkMutex.tryLock()) { 0081 return; 0082 } 0083 setCursorWidth(cursorWidth() == 0 ? 2 : 0); 0084 _cursorBlinkMutex.unlock(); 0085 }); 0086 _blinkTimer.start(500); 0087 } 0088 0089 void ListerTextArea::reset() 0090 { 0091 _screenStartPos = 0; 0092 _cursorPos = 0; 0093 _cursorAnchorPos = -1; 0094 _cursorAtFirstColumn = true; 0095 calculateText(); 0096 } 0097 0098 void ListerTextArea::sizeChanged() 0099 { 0100 if (_cursorAnchorPos > _lister->fileSize()) 0101 _cursorAnchorPos = -1; 0102 if (_cursorPos > _lister->fileSize()) 0103 _cursorPos = _lister->fileSize(); 0104 0105 redrawTextArea(true); 0106 } 0107 0108 void ListerTextArea::resizeEvent(QResizeEvent *event) 0109 { 0110 KTextEdit::resizeEvent(event); 0111 redrawTextArea(); 0112 } 0113 0114 void ListerTextArea::calculateText(const bool forcedUpdate) 0115 { 0116 const QRect contentRect = viewport()->contentsRect(); 0117 const QFontMetrics fm(font()); 0118 0119 const int fontHeight = std::max(fm.height(), 1); 0120 0121 // This is quite accurate (although not perfect) way of getting 0122 // a single character width along with its surrounding space. 0123 const double fontWidth = (fm.horizontalAdvance("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW") 0124 - fm.horizontalAdvance("W")) 0125 / 99.0; 0126 0127 const int sizeY = contentRect.height() / fontHeight; 0128 _pageSize = sizeY; 0129 0130 const int textViewportWidth = std::max(contentRect.width() - (int)fontWidth, 0); 0131 0132 setTabStopDistance(fontWidth * _tabWidth); 0133 0134 const int sizeX = static_cast<int>(textViewportWidth / fontWidth); 0135 0136 _sizeChanged = (_sizeY != sizeY) || (_sizeX != sizeX) || forcedUpdate; 0137 _sizeY = sizeY; 0138 _sizeX = sizeX; 0139 0140 QList<qint64> rowStarts; 0141 0142 QStringList list = readLines(_screenStartPos, _screenEndPos, _sizeY, &rowStarts); 0143 0144 if (_sizeChanged) { 0145 _averagePageSize = _screenEndPos - _screenStartPos; 0146 setUpScrollBar(); 0147 } 0148 0149 const QStringList listRemn = readLines(_screenEndPos, _screenEndPos, 1); 0150 list << listRemn; 0151 0152 if (list != _rowContent) { 0153 _cursorBlinkMutex.lock(); 0154 _blinkTimer.stop(); 0155 setCursorWidth(0); 0156 0157 setPlainText(list.join("\n")); 0158 0159 if (_cursorAnchorPos == -1 || _cursorAnchorPos == _cursorPos) { 0160 clearSelection(); 0161 _blinkTimer.start(500); 0162 } 0163 0164 _cursorBlinkMutex.unlock(); 0165 0166 _rowContent = list; 0167 _rowStarts = rowStarts; 0168 if (_rowStarts.size() < _sizeY) { 0169 _rowStarts << _screenEndPos; 0170 } 0171 } 0172 } 0173 0174 qint64 ListerTextArea::textToFilePositionOnScreen(const int x, const int y, bool &isfirst) 0175 { 0176 isfirst = (x == 0); 0177 if (y >= _rowStarts.count()) { 0178 return 0; 0179 } 0180 const qint64 rowStart = _rowStarts[y]; 0181 if (x == 0) { 0182 return rowStart; 0183 } 0184 0185 if (_hexMode) { 0186 const qint64 pos = rowStart + _lister->hexPositionToIndex(_sizeX, x); 0187 if (pos > _lister->fileSize()) { 0188 return _lister->fileSize(); 0189 } 0190 return pos; 0191 } 0192 0193 // we can't use fromUnicode because of the invalid encoded chars 0194 const int maxBytes = 2 * _sizeX * MAX_CHAR_LENGTH; 0195 QByteArray chunk = _lister->cacheChunk(rowStart, maxBytes); 0196 0197 QTextStream stream(&chunk); 0198 stream.setCodec(_lister->codec()); 0199 stream.read(x); 0200 return rowStart + stream.pos(); 0201 } 0202 0203 void ListerTextArea::fileToTextPositionOnScreen(const qint64 p, const bool isfirst, int &x, int &y) 0204 { 0205 // check if cursor is outside of visible area 0206 if (p < _screenStartPos || p > _screenEndPos || _rowStarts.count() < 1) { 0207 x = -1; 0208 y = (p > _screenEndPos) ? -2 : -1; 0209 return; 0210 } 0211 0212 // find row 0213 y = 0; 0214 while (y < _rowStarts.count() && _rowStarts[y] <= p) { 0215 y++; 0216 } 0217 y--; 0218 if (y < 0) { 0219 x = y = -1; 0220 return; 0221 } 0222 0223 const qint64 rowStart = _rowStarts[y]; 0224 if (_hexMode) { 0225 x = _lister->hexIndexToPosition(_sizeX, (int)(p - rowStart)); 0226 return; 0227 } 0228 0229 // find column 0230 const int maxBytes = 2 * _sizeX * MAX_CHAR_LENGTH; 0231 x = 0; 0232 if (rowStart >= p) { 0233 if ((rowStart == p) && !isfirst && y > 0) { 0234 const qint64 previousRow = _rowStarts[y - 1]; 0235 const QByteArray chunk = _lister->cacheChunk(previousRow, maxBytes); 0236 QByteArray cachedBuffer = chunk.left(static_cast<int>(p - previousRow)); 0237 0238 QTextStream stream(&cachedBuffer); 0239 stream.setCodec(_lister->codec()); 0240 stream.read(_rowContent[y - 1].length()); 0241 if (previousRow + stream.pos() == p) { 0242 y--; 0243 x = _rowContent[y].length(); 0244 } 0245 } 0246 return; 0247 } 0248 0249 const QByteArray chunk = _lister->cacheChunk(rowStart, maxBytes); 0250 const QByteArray cachedBuffer = chunk.left(static_cast<int>(p - rowStart)); 0251 0252 x = _lister->codec()->toUnicode(cachedBuffer).length(); 0253 } 0254 0255 void ListerTextArea::getCursorPosition(int &x, int &y) 0256 { 0257 getScreenPosition(textCursor().position(), x, y); 0258 } 0259 0260 void ListerTextArea::getScreenPosition(const int position, int &x, int &y) 0261 { 0262 x = position; 0263 y = 0; 0264 foreach (const QString &row, _rowContent) { 0265 const int rowLen = row.length() + 1; 0266 if (x < rowLen) { 0267 return; 0268 } 0269 x -= rowLen; 0270 y++; 0271 } 0272 } 0273 0274 void ListerTextArea::setCursorPositionOnScreen(const int x, const int y, const int anchorX, const int anchorY) 0275 { 0276 setCursorWidth(0); 0277 0278 int finalX = x; 0279 int finalY = y; 0280 0281 if (finalX == -1 || finalY < 0) { 0282 if (anchorY == -1) { 0283 return; 0284 } 0285 0286 if (finalY == -2) { 0287 finalY = _sizeY; 0288 finalX = (_rowContent.count() > _sizeY) ? _rowContent[_sizeY].length() : 0; 0289 } else 0290 finalX = finalY = 0; 0291 } 0292 0293 const int realSizeY = std::min(_sizeY + 1, _rowContent.count()); 0294 0295 const auto setUpCursor = [&](const int cursorX, const int cursorY, const QTextCursor::MoveMode mode) -> bool { 0296 if (cursorY > realSizeY) { 0297 return false; 0298 } 0299 0300 _skipCursorChangedListener = true; 0301 0302 moveCursor(QTextCursor::Start, mode); 0303 for (int i = 0; i < cursorY; i++) { 0304 moveCursor(QTextCursor::Down, mode); 0305 } 0306 0307 int finalCursorX = cursorX; 0308 if (_rowContent.count() > cursorY && finalCursorX > _rowContent[cursorY].length()) { 0309 finalCursorX = _rowContent[cursorY].length(); 0310 } 0311 0312 for (int i = 0; i < finalCursorX; i++) { 0313 moveCursor(QTextCursor::Right, mode); 0314 } 0315 0316 _skipCursorChangedListener = false; 0317 0318 return true; 0319 }; 0320 0321 QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; 0322 0323 // set cursor anchor 0324 if (anchorX != -1 && anchorY != -1) { 0325 const bool canContinue = setUpCursor(anchorX, anchorY, mode); 0326 if (!canContinue) { 0327 return; 0328 } 0329 0330 mode = QTextCursor::KeepAnchor; 0331 } 0332 0333 // set cursor position 0334 setUpCursor(finalX, finalY, mode); 0335 } 0336 0337 qint64 ListerTextArea::getCursorPosition(bool &isfirst) 0338 { 0339 if (cursorWidth() == 0) { 0340 isfirst = _cursorAtFirstColumn; 0341 return _cursorPos; 0342 } 0343 0344 int x, y; 0345 getCursorPosition(x, y); 0346 return textToFilePositionOnScreen(x, y, isfirst); 0347 } 0348 0349 void ListerTextArea::setCursorPositionInDocument(const qint64 p, const bool isfirst) 0350 { 0351 _cursorPos = p; 0352 int x, y; 0353 fileToTextPositionOnScreen(p, isfirst, x, y); 0354 0355 bool startBlinkTimer = _screenStartPos <= _cursorPos && _cursorPos <= _screenEndPos; 0356 int anchorX = -1, anchorY = -1; 0357 if (_cursorAnchorPos != -1 && _cursorAnchorPos != p) { 0358 qint64 anchPos = _cursorAnchorPos; 0359 bool anchorBelow = false, anchorAbove = false; 0360 if (anchPos < _screenStartPos) { 0361 anchPos = _screenStartPos; 0362 anchorY = -2; 0363 anchorAbove = true; 0364 } 0365 if (anchPos > _screenEndPos) { 0366 anchPos = _screenEndPos; 0367 anchorY = -3; 0368 anchorBelow = true; 0369 } 0370 0371 fileToTextPositionOnScreen(anchPos, isfirst, anchorX, anchorY); 0372 0373 if (_hexMode) { 0374 if (anchorAbove) { 0375 anchorX = 0; 0376 } 0377 if (anchorBelow && _rowContent.count() > 0) { 0378 anchorX = _rowContent[0].length(); 0379 } 0380 } 0381 0382 startBlinkTimer = startBlinkTimer && !anchorAbove && !anchorBelow; 0383 } 0384 if (startBlinkTimer) { 0385 _blinkTimer.start(500); 0386 } 0387 setCursorPositionOnScreen(x, y, anchorX, anchorY); 0388 _lister->slotUpdate(); 0389 } 0390 0391 void ListerTextArea::slotCursorPositionChanged() 0392 { 0393 if (_skipCursorChangedListener) { 0394 return; 0395 } 0396 int cursorX, cursorY; 0397 getCursorPosition(cursorX, cursorY); 0398 _cursorAtFirstColumn = (cursorX == 0); 0399 _cursorPos = textToFilePositionOnScreen(cursorX, cursorY, _cursorAtFirstColumn); 0400 _lister->slotUpdate(); 0401 } 0402 0403 QString ListerTextArea::readSection(const qint64 p1, const qint64 p2) 0404 { 0405 if (p1 == p2) 0406 return QString(); 0407 0408 qint64 sel1 = p1; 0409 qint64 sel2 = p2; 0410 if (sel1 > sel2) { 0411 std::swap(sel1, sel2); 0412 } 0413 0414 QString section; 0415 0416 if (_hexMode) { 0417 while (sel1 != sel2) { 0418 const QStringList list = _lister->readHexLines(sel1, sel2, _sizeX, 1); 0419 if (list.isEmpty()) { 0420 break; 0421 } 0422 if (!section.isEmpty()) { 0423 section += QChar('\n'); 0424 } 0425 section += list.at(0); 0426 } 0427 return section; 0428 } 0429 0430 qint64 pos = sel1; 0431 0432 QScopedPointer<QTextDecoder> decoder(_lister->codec()->makeDecoder()); 0433 0434 do { 0435 const int maxBytes = std::min(_sizeX * _sizeY * MAX_CHAR_LENGTH, (int)(sel2 - pos)); 0436 const QByteArray chunk = _lister->cacheChunk(pos, maxBytes); 0437 if (chunk.isEmpty()) 0438 break; 0439 section += decoder->toUnicode(chunk); 0440 pos += chunk.size(); 0441 } while (pos < sel2); 0442 0443 return section; 0444 } 0445 0446 QStringList ListerTextArea::readLines(qint64 filePos, qint64 &endPos, const int lines, QList<qint64> *locs) 0447 { 0448 QStringList list; 0449 0450 if (_hexMode) { 0451 endPos = _lister->fileSize(); 0452 if (filePos >= endPos) { 0453 return list; 0454 } 0455 const int bytes = _lister->hexBytesPerLine(_sizeX); 0456 qint64 startPos = (filePos / bytes) * bytes; 0457 qint64 shiftPos = startPos; 0458 list = _lister->readHexLines(shiftPos, endPos, _sizeX, lines); 0459 endPos = shiftPos; 0460 if (locs) { 0461 for (int i = 0; i < list.count(); i++) { 0462 (*locs) << startPos; 0463 startPos += bytes; 0464 } 0465 } 0466 return list; 0467 } 0468 0469 endPos = filePos; 0470 const int maxBytes = _sizeX * _sizeY * MAX_CHAR_LENGTH; 0471 const QByteArray chunk = _lister->cacheChunk(filePos, maxBytes); 0472 if (chunk.isEmpty()) 0473 return list; 0474 0475 int byteCounter = 0; 0476 QString row = ""; 0477 int effLength = 0; 0478 if (locs) 0479 (*locs) << filePos; 0480 bool skipImmediateNewline = false; 0481 0482 const auto performNewline = [&](qint64 nextRowStartOffset) { 0483 list << row; 0484 effLength = 0; 0485 row = ""; 0486 if (locs) { 0487 (*locs) << (filePos + nextRowStartOffset); 0488 } 0489 }; 0490 0491 QScopedPointer<QTextDecoder> decoder(_lister->codec()->makeDecoder()); 0492 0493 while (byteCounter < chunk.size() && list.size() < lines) { 0494 const int lastCnt = byteCounter; 0495 QString chr = decoder->toUnicode(chunk.mid(byteCounter++, 1)); 0496 if (chr.isEmpty()) { 0497 continue; 0498 } 0499 0500 if ((chr[0] < 32) && (chr[0] != '\n') && (chr[0] != '\t')) { 0501 chr = QChar(CONTROL_CHAR); 0502 } 0503 0504 if (chr == "\n") { 0505 if (!skipImmediateNewline) { 0506 performNewline(byteCounter); 0507 } 0508 skipImmediateNewline = false; 0509 continue; 0510 } 0511 0512 skipImmediateNewline = false; 0513 0514 if (chr == "\t") { 0515 effLength += _tabWidth - (effLength % _tabWidth) - 1; 0516 if (effLength > _sizeX) { 0517 performNewline(lastCnt); 0518 } 0519 } 0520 row += chr; 0521 effLength++; 0522 if (effLength >= _sizeX) { 0523 performNewline(byteCounter); 0524 skipImmediateNewline = true; 0525 } 0526 } 0527 0528 if (list.size() < lines) 0529 list << row; 0530 0531 endPos = filePos + byteCounter; 0532 0533 return list; 0534 } 0535 0536 void ListerTextArea::setUpScrollBar() 0537 { 0538 if (_averagePageSize == _lister->fileSize()) { 0539 _lister->scrollBar()->setPageStep(0); 0540 _lister->scrollBar()->setMaximum(0); 0541 _lister->scrollBar()->hide(); 0542 _lastPageStartPos = 0; 0543 } else { 0544 const int maxPage = MAX_CHAR_LENGTH * _sizeX * _sizeY; 0545 qint64 pageStartPos = _lister->fileSize() - maxPage; 0546 qint64 endPos; 0547 if (pageStartPos < 0) 0548 pageStartPos = 0; 0549 QStringList list = readLines(pageStartPos, endPos, maxPage); 0550 if (list.count() <= _sizeY) { 0551 _lastPageStartPos = 0; 0552 } else { 0553 readLines(pageStartPos, _lastPageStartPos, list.count() - _sizeY); 0554 } 0555 0556 const qint64 maximum = (_lastPageStartPos > SLIDER_MAX) ? SLIDER_MAX : _lastPageStartPos; 0557 qint64 pageSize = (_lastPageStartPos > SLIDER_MAX) ? SLIDER_MAX * _averagePageSize / _lastPageStartPos : _averagePageSize; 0558 if (pageSize == 0) 0559 pageSize++; 0560 0561 _lister->scrollBar()->setPageStep(static_cast<int>(pageSize)); 0562 _lister->scrollBar()->setMaximum(static_cast<int>(maximum)); 0563 _lister->scrollBar()->show(); 0564 } 0565 } 0566 0567 void ListerTextArea::keyPressEvent(QKeyEvent *ke) 0568 { 0569 if (KrGlobal::copyShortcut == QKeySequence(ke->key() | ke->modifiers())) { 0570 copySelectedToClipboard(); 0571 ke->accept(); 0572 return; 0573 } 0574 0575 if (ke->modifiers() == Qt::NoModifier || ke->modifiers() & Qt::ShiftModifier) { 0576 qint64 newAnchor = -1; 0577 if (ke->modifiers() & Qt::ShiftModifier) { 0578 newAnchor = _cursorAnchorPos; 0579 if (_cursorAnchorPos == -1) 0580 newAnchor = _cursorPos; 0581 } 0582 0583 switch (ke->key()) { 0584 case Qt::Key_F3: 0585 ke->accept(); 0586 if (ke->modifiers() == Qt::ShiftModifier) 0587 _lister->searchPrev(); 0588 else 0589 _lister->searchNext(); 0590 return; 0591 case Qt::Key_Home: 0592 case Qt::Key_End: 0593 _cursorAnchorPos = newAnchor; 0594 break; 0595 case Qt::Key_Left: { 0596 _cursorAnchorPos = newAnchor; 0597 ensureVisibleCursor(); 0598 int x, y; 0599 getCursorPosition(x, y); 0600 if (y == 0 && x == 0) 0601 slotActionTriggered(QAbstractSlider::SliderSingleStepSub); 0602 } break; 0603 case Qt::Key_Right: { 0604 _cursorAnchorPos = newAnchor; 0605 ensureVisibleCursor(); 0606 if (textCursor().position() == toPlainText().length()) 0607 slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); 0608 } break; 0609 case Qt::Key_Up: { 0610 _cursorAnchorPos = newAnchor; 0611 ensureVisibleCursor(); 0612 int x, y; 0613 getCursorPosition(x, y); 0614 if (y == 0) 0615 slotActionTriggered(QAbstractSlider::SliderSingleStepSub); 0616 } break; 0617 case Qt::Key_Down: { 0618 _cursorAnchorPos = newAnchor; 0619 ensureVisibleCursor(); 0620 int x, y; 0621 getCursorPosition(x, y); 0622 if (y >= _sizeY - 1) 0623 slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); 0624 } break; 0625 case Qt::Key_PageDown: { 0626 _cursorAnchorPos = newAnchor; 0627 ensureVisibleCursor(); 0628 ke->accept(); 0629 int x, y; 0630 getCursorPosition(x, y); 0631 slotActionTriggered(QAbstractSlider::SliderPageStepAdd); 0632 y += _sizeY - _skippedLines; 0633 if (y > _rowContent.count()) { 0634 y = _rowContent.count() - 1; 0635 if (y > 0) 0636 x = _rowContent[y - 1].length(); 0637 else 0638 x = 0; 0639 } 0640 _cursorPos = textToFilePositionOnScreen(x, y, _cursorAtFirstColumn); 0641 setCursorPositionInDocument(_cursorPos, _cursorAtFirstColumn); 0642 } 0643 return; 0644 case Qt::Key_PageUp: { 0645 _cursorAnchorPos = newAnchor; 0646 ensureVisibleCursor(); 0647 ke->accept(); 0648 int x, y; 0649 getCursorPosition(x, y); 0650 slotActionTriggered(QAbstractSlider::SliderPageStepSub); 0651 y -= _sizeY - _skippedLines; 0652 if (y < 0) { 0653 y = 0; 0654 x = 0; 0655 } 0656 _cursorPos = textToFilePositionOnScreen(x, y, _cursorAtFirstColumn); 0657 setCursorPositionInDocument(_cursorPos, _cursorAtFirstColumn); 0658 } 0659 return; 0660 } 0661 } 0662 if (ke->modifiers() == Qt::ControlModifier) { 0663 switch (ke->key()) { 0664 case Qt::Key_G: 0665 ke->accept(); 0666 _lister->jumpToPosition(); 0667 return; 0668 case Qt::Key_F: 0669 ke->accept(); 0670 _lister->enableSearch(true); 0671 return; 0672 case Qt::Key_Home: 0673 _cursorAnchorPos = -1; 0674 ke->accept(); 0675 slotActionTriggered(QAbstractSlider::SliderToMinimum); 0676 setCursorPositionInDocument((qint64)0, true); 0677 return; 0678 case Qt::Key_A: 0679 case Qt::Key_End: { 0680 _cursorAnchorPos = (ke->key() == Qt::Key_A) ? 0 : -1; 0681 ke->accept(); 0682 slotActionTriggered(QAbstractSlider::SliderToMaximum); 0683 const qint64 endPos = _lister->fileSize(); 0684 setCursorPositionInDocument(endPos, false); 0685 return; 0686 } 0687 case Qt::Key_Down: 0688 ke->accept(); 0689 slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); 0690 return; 0691 case Qt::Key_Up: 0692 ke->accept(); 0693 slotActionTriggered(QAbstractSlider::SliderSingleStepSub); 0694 return; 0695 case Qt::Key_PageDown: 0696 ke->accept(); 0697 slotActionTriggered(QAbstractSlider::SliderPageStepAdd); 0698 return; 0699 case Qt::Key_PageUp: 0700 ke->accept(); 0701 slotActionTriggered(QAbstractSlider::SliderPageStepSub); 0702 return; 0703 } 0704 } 0705 const int oldAnchor = textCursor().anchor(); 0706 KTextEdit::keyPressEvent(ke); 0707 handleAnchorChange(oldAnchor); 0708 } 0709 0710 void ListerTextArea::mousePressEvent(QMouseEvent *e) 0711 { 0712 KTextEdit::mousePressEvent(e); 0713 // do change anchor only when shift is not pressed 0714 if (!(QGuiApplication::keyboardModifiers() & Qt::ShiftModifier)) { 0715 performAnchorChange(textCursor().anchor()); 0716 } 0717 } 0718 0719 void ListerTextArea::mouseDoubleClickEvent(QMouseEvent *e) 0720 { 0721 _cursorAnchorPos = -1; 0722 const int oldAnchor = textCursor().anchor(); 0723 KTextEdit::mouseDoubleClickEvent(e); 0724 handleAnchorChange(oldAnchor); 0725 } 0726 0727 void ListerTextArea::mouseMoveEvent(QMouseEvent *e) 0728 { 0729 if (e->pos().y() < 0) { 0730 slotActionTriggered(QAbstractSlider::SliderSingleStepSub); 0731 } else if (e->pos().y() > height()) { 0732 slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); 0733 } 0734 KTextEdit::mouseMoveEvent(e); 0735 } 0736 0737 void ListerTextArea::wheelEvent(QWheelEvent *e) 0738 { 0739 int delta = e->angleDelta().y(); 0740 if (delta) { 0741 // zooming 0742 if (e->modifiers() & Qt::ControlModifier) { 0743 e->accept(); 0744 if (delta > 0) { 0745 zoomIn(); 0746 } else { 0747 zoomOut(); 0748 } 0749 return; 0750 } 0751 0752 if (delta > 0) { 0753 e->accept(); 0754 while (delta > 0) { 0755 slotActionTriggered(QAbstractSlider::SliderSingleStepSub); 0756 slotActionTriggered(QAbstractSlider::SliderSingleStepSub); 0757 slotActionTriggered(QAbstractSlider::SliderSingleStepSub); 0758 delta -= 120; 0759 } 0760 } else { 0761 e->accept(); 0762 while (delta < 0) { 0763 slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); 0764 slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); 0765 slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); 0766 delta += 120; 0767 } 0768 } 0769 0770 setCursorPositionInDocument(_cursorPos, false); 0771 } 0772 } 0773 0774 void ListerTextArea::slotActionTriggered(int action) 0775 { 0776 switch (action) { 0777 case QAbstractSlider::SliderSingleStepAdd: { 0778 qint64 endPos; 0779 readLines(_screenStartPos, endPos, 1); 0780 if (endPos <= _lastPageStartPos) { 0781 _screenStartPos = endPos; 0782 } 0783 } break; 0784 case QAbstractSlider::SliderSingleStepSub: { 0785 if (_screenStartPos == 0) { 0786 break; 0787 } 0788 0789 if (_hexMode) { 0790 int bytesPerRow = _lister->hexBytesPerLine(_sizeX); 0791 _screenStartPos = (_screenStartPos / bytesPerRow) * bytesPerRow; 0792 _screenStartPos -= bytesPerRow; 0793 if (_screenStartPos < 0) { 0794 _screenStartPos = 0; 0795 } 0796 break; 0797 } 0798 0799 qint64 maxSize = _sizeX * _sizeY * MAX_CHAR_LENGTH; 0800 const QByteArray encodedEnter = _lister->codec()->fromUnicode(QString("\n")); 0801 0802 qint64 readPos = _screenStartPos - maxSize; 0803 if (readPos < 0) { 0804 readPos = 0; 0805 } 0806 maxSize = _screenStartPos - readPos; 0807 0808 const QByteArray chunk = _lister->cacheChunk(readPos, maxSize); 0809 0810 int from = chunk.size(); 0811 while (from > 0) { 0812 from--; 0813 from = chunk.lastIndexOf(encodedEnter, from); 0814 if (from == -1) { 0815 from = 0; 0816 break; 0817 } 0818 const int backRef = std::max(from - 20, 0); 0819 const int size = from - backRef + encodedEnter.size(); 0820 const QString decoded = _lister->codec()->toUnicode(chunk.mid(backRef, size)); 0821 if (decoded.endsWith(QLatin1String("\n"))) { 0822 if (from < (chunk.size() - encodedEnter.size())) { 0823 from += encodedEnter.size(); 0824 break; 0825 } 0826 } 0827 } 0828 0829 readPos += from; 0830 qint64 previousPos = readPos; 0831 while (readPos < _screenStartPos) { 0832 previousPos = readPos; 0833 readLines(readPos, readPos, 1); 0834 } 0835 _screenStartPos = previousPos; 0836 } break; 0837 case QAbstractSlider::SliderPageStepAdd: { 0838 _skippedLines = 0; 0839 0840 qint64 endPos; 0841 for (int i = 0; i < _sizeY; i++) { 0842 readLines(_screenStartPos, endPos, 1); 0843 if (endPos <= _lastPageStartPos) { 0844 _screenStartPos = endPos; 0845 _skippedLines++; 0846 } else { 0847 break; 0848 } 0849 } 0850 } break; 0851 case QAbstractSlider::SliderPageStepSub: { 0852 _skippedLines = 0; 0853 0854 if (_screenStartPos == 0) { 0855 break; 0856 } 0857 0858 if (_hexMode) { 0859 const int bytesPerRow = _lister->hexBytesPerLine(_sizeX); 0860 _screenStartPos = (_screenStartPos / bytesPerRow) * bytesPerRow; 0861 _screenStartPos -= _sizeY * bytesPerRow; 0862 if (_screenStartPos < 0) { 0863 _screenStartPos = 0; 0864 } 0865 break; 0866 } 0867 0868 // text lister mode 0869 qint64 maxSize = 2 * _sizeX * _sizeY * MAX_CHAR_LENGTH; 0870 const QByteArray encodedEnter = _lister->codec()->fromUnicode(QString("\n")); 0871 0872 qint64 readPos = _screenStartPos - maxSize; 0873 if (readPos < 0) 0874 readPos = 0; 0875 maxSize = _screenStartPos - readPos; 0876 0877 const QByteArray chunk = _lister->cacheChunk(readPos, maxSize); 0878 maxSize = chunk.size(); 0879 0880 int sizeY = _sizeY + 1; 0881 int origSizeY = sizeY; 0882 qint64 from = maxSize; 0883 qint64 lastEnter = maxSize; 0884 0885 bool readNext = true; 0886 while (readNext) { 0887 readNext = false; 0888 while (from > 0) { 0889 from--; 0890 from = chunk.lastIndexOf(encodedEnter, static_cast<int>(from)); 0891 if (from == -1) { 0892 from = 0; 0893 break; 0894 } 0895 const qint64 backRef = std::max(from - 20, 0LL); 0896 const qint64 size = from - backRef + static_cast<qint64>(encodedEnter.size()); 0897 QString decoded = _lister->codec()->toUnicode(chunk.mid(static_cast<int>(backRef), static_cast<int>(size))); 0898 if (decoded.endsWith(QLatin1String("\n"))) { 0899 if (from < (maxSize - encodedEnter.size())) { 0900 qint64 arrayStart = from + encodedEnter.size(); 0901 decoded = _lister->codec()->toUnicode(chunk.mid(static_cast<int>(arrayStart), static_cast<int>(lastEnter - arrayStart))); 0902 sizeY -= ((decoded.length() / (_sizeX + 1)) + 1); 0903 if (sizeY < 0) { 0904 from = arrayStart; 0905 break; 0906 } 0907 } 0908 lastEnter = from; 0909 } 0910 } 0911 0912 qint64 searchPos = readPos + from; 0913 QList<qint64> locs; 0914 while (searchPos < _screenStartPos) { 0915 locs << searchPos; 0916 readLines(searchPos, searchPos, 1); 0917 } 0918 0919 if (locs.count() >= _sizeY) { 0920 _screenStartPos = locs[locs.count() - _sizeY]; 0921 } else if (from != 0) { 0922 origSizeY += locs.count() + 1; 0923 sizeY = origSizeY; 0924 readNext = true; 0925 } else if (readPos == 0) { 0926 _screenStartPos = 0; 0927 } 0928 } 0929 0930 } break; 0931 case QAbstractSlider::SliderToMinimum: 0932 _screenStartPos = 0; 0933 break; 0934 case QAbstractSlider::SliderToMaximum: 0935 _screenStartPos = _lastPageStartPos; 0936 break; 0937 case QAbstractSlider::SliderMove: { 0938 if (_inSliderOp) // self created call? 0939 return; 0940 qint64 pos = _lister->scrollBar()->sliderPosition(); 0941 0942 if (pos == SLIDER_MAX) { 0943 _screenStartPos = _lastPageStartPos; 0944 break; 0945 } else if (pos == 0) { 0946 _screenStartPos = 0; 0947 break; 0948 } 0949 0950 if (_lastPageStartPos > SLIDER_MAX) 0951 pos = _lastPageStartPos * pos / SLIDER_MAX; 0952 0953 if (pos != 0) { 0954 if (_hexMode) { 0955 const int bytesPerRow = _lister->hexBytesPerLine(_sizeX); 0956 pos = (pos / bytesPerRow) * bytesPerRow; 0957 } else { 0958 const int maxSize = _sizeX * _sizeY * MAX_CHAR_LENGTH; 0959 qint64 readPos = pos - maxSize; 0960 if (readPos < 0) 0961 readPos = 0; 0962 qint64 previousPos = readPos; 0963 0964 while (readPos <= pos) { 0965 previousPos = readPos; 0966 readLines(readPos, readPos, 1); 0967 } 0968 0969 pos = previousPos; 0970 } 0971 } 0972 0973 _screenStartPos = pos; 0974 } break; 0975 case QAbstractSlider::SliderNoAction: 0976 break; 0977 }; 0978 0979 _inSliderOp = true; 0980 const int value = static_cast<int>((_lastPageStartPos > SLIDER_MAX) ? SLIDER_MAX * _screenStartPos / _lastPageStartPos : _screenStartPos); 0981 _lister->scrollBar()->setSliderPosition(value); 0982 _inSliderOp = false; 0983 0984 redrawTextArea(); 0985 } 0986 0987 void ListerTextArea::redrawTextArea(bool forcedUpdate) 0988 { 0989 if (_redrawing) { 0990 return; 0991 } 0992 _redrawing = true; 0993 bool isfirst; 0994 const qint64 pos = getCursorPosition(isfirst); 0995 calculateText(forcedUpdate); 0996 setCursorPositionInDocument(pos, isfirst); 0997 _redrawing = false; 0998 } 0999 1000 void ListerTextArea::ensureVisibleCursor() 1001 { 1002 if (_screenStartPos <= _cursorPos && _cursorPos <= _screenEndPos) { 1003 return; 1004 } 1005 1006 int delta = _sizeY / 2; 1007 if (delta == 0) 1008 delta++; 1009 1010 qint64 newScreenStart = _cursorPos; 1011 while (delta) { 1012 const int maxSize = _sizeX * MAX_CHAR_LENGTH; 1013 qint64 readPos = newScreenStart - maxSize; 1014 if (readPos < 0) 1015 readPos = 0; 1016 1017 qint64 previousPos = readPos; 1018 1019 while (readPos < newScreenStart) { 1020 previousPos = readPos; 1021 readLines(readPos, readPos, 1); 1022 if (readPos == previousPos) 1023 break; 1024 } 1025 1026 newScreenStart = previousPos; 1027 delta--; 1028 } 1029 if (newScreenStart > _lastPageStartPos) { 1030 newScreenStart = _lastPageStartPos; 1031 } 1032 1033 _screenStartPos = newScreenStart; 1034 slotActionTriggered(QAbstractSlider::SliderNoAction); 1035 } 1036 1037 void ListerTextArea::setAnchorAndCursor(qint64 anchor, qint64 cursor) 1038 { 1039 _cursorPos = cursor; 1040 _cursorAnchorPos = anchor; 1041 ensureVisibleCursor(); 1042 setCursorPositionInDocument(cursor, false); 1043 } 1044 1045 QString ListerTextArea::getSelectedText() 1046 { 1047 if (_cursorAnchorPos != -1 && _cursorAnchorPos != _cursorPos) { 1048 return readSection(_cursorAnchorPos, _cursorPos); 1049 } 1050 return QString(); 1051 } 1052 1053 void ListerTextArea::copySelectedToClipboard() 1054 { 1055 const QString selection = getSelectedText(); 1056 if (!selection.isEmpty()) { 1057 QApplication::clipboard()->setText(selection); 1058 } 1059 } 1060 1061 void ListerTextArea::clearSelection() 1062 { 1063 QTextCursor cursor = textCursor(); 1064 cursor.clearSelection(); 1065 setTextCursor(cursor); 1066 _cursorAnchorPos = -1; 1067 } 1068 1069 void ListerTextArea::performAnchorChange(int anchor) 1070 { 1071 int x, y; 1072 bool isfirst; 1073 getScreenPosition(anchor, x, y); 1074 _cursorAnchorPos = textToFilePositionOnScreen(x, y, isfirst); 1075 } 1076 1077 void ListerTextArea::handleAnchorChange(int oldAnchor) 1078 { 1079 const int anchor = textCursor().anchor(); 1080 1081 if (oldAnchor != anchor) { 1082 performAnchorChange(anchor); 1083 } 1084 } 1085 1086 void ListerTextArea::setHexMode(bool hexMode) 1087 { 1088 bool isfirst; 1089 const qint64 pos = getCursorPosition(isfirst); 1090 _hexMode = hexMode; 1091 _screenStartPos = 0; 1092 calculateText(true); 1093 setCursorPositionInDocument(pos, isfirst); 1094 ensureVisibleCursor(); 1095 } 1096 1097 void ListerTextArea::zoomIn(int range) 1098 { 1099 KTextEdit::zoomIn(range); 1100 redrawTextArea(); 1101 } 1102 1103 void ListerTextArea::zoomOut(int range) 1104 { 1105 KTextEdit::zoomOut(range); 1106 redrawTextArea(); 1107 } 1108 1109 ListerPane::ListerPane(Lister *lister, QWidget *parent) 1110 : QWidget(parent) 1111 , _lister(lister) 1112 { 1113 } 1114 1115 bool ListerPane::event(QEvent *e) 1116 { 1117 const bool handled = ListerPane::handleCloseEvent(e); 1118 if (!handled) { 1119 return QWidget::event(e); 1120 } 1121 return true; 1122 } 1123 1124 bool ListerPane::handleCloseEvent(QEvent *e) 1125 { 1126 if (e->type() == QEvent::ShortcutOverride) { 1127 auto *ke = dynamic_cast<QKeyEvent *>(e); 1128 if (ke->key() == Qt::Key_Escape) { 1129 if (_lister->isSearchEnabled()) { 1130 _lister->searchDelete(); 1131 _lister->enableSearch(false); 1132 ke->accept(); 1133 return true; 1134 } 1135 if (!_lister->textArea()->getSelectedText().isEmpty()) { 1136 _lister->textArea()->clearSelection(); 1137 ke->accept(); 1138 return true; 1139 } 1140 } 1141 } 1142 return false; 1143 } 1144 1145 ListerBrowserExtension::ListerBrowserExtension(Lister *lister) 1146 : KParts::BrowserExtension(lister) 1147 { 1148 _lister = lister; 1149 1150 emit enableAction("copy", true); 1151 emit enableAction("print", true); 1152 } 1153 1154 void ListerBrowserExtension::copy() 1155 { 1156 _lister->textArea()->copySelectedToClipboard(); 1157 } 1158 1159 void ListerBrowserExtension::print() 1160 { 1161 _lister->print(); 1162 } 1163 1164 class ListerEncodingMenu : public KrRemoteEncodingMenu 1165 { 1166 public: 1167 ListerEncodingMenu(Lister *lister, const QString &text, const QString &icon, KActionCollection *parent) 1168 : KrRemoteEncodingMenu(text, icon, parent) 1169 , _lister(lister) 1170 { 1171 } 1172 1173 protected: 1174 QString currentCharacterSet() override 1175 { 1176 return _lister->characterSet(); 1177 } 1178 1179 void chooseDefault() override 1180 { 1181 _lister->setCharacterSet(QString()); 1182 } 1183 1184 void chooseEncoding(QString encodingName) override 1185 { 1186 QString charset = KCharsets::charsets()->encodingForName(encodingName); 1187 _lister->setCharacterSet(charset); 1188 } 1189 1190 Lister *_lister; 1191 }; 1192 1193 Lister::Lister(QWidget *parent) 1194 : KParts::ReadOnlyPart(parent) 1195 { 1196 setXMLFile("krusaderlisterui.rc"); 1197 1198 _actionSaveSelected = new QAction(Icon("document-save"), i18n("Save selection..."), this); 1199 connect(_actionSaveSelected, &QAction::triggered, this, &Lister::saveSelected); 1200 actionCollection()->addAction("save_selected", _actionSaveSelected); 1201 1202 _actionSaveAs = new QAction(Icon("document-save-as"), i18n("Save as..."), this); 1203 connect(_actionSaveAs, &QAction::triggered, this, &Lister::saveAs); 1204 actionCollection()->addAction("save_as", _actionSaveAs); 1205 1206 _actionPrint = new QAction(Icon("document-print"), i18n("Print..."), this); 1207 connect(_actionPrint, &QAction::triggered, this, &Lister::print); 1208 actionCollection()->addAction("print", _actionPrint); 1209 actionCollection()->setDefaultShortcut(_actionPrint, Qt::CTRL + Qt::Key_P); 1210 1211 _actionSearch = new QAction(Icon("system-search"), i18n("Search"), this); 1212 connect(_actionSearch, &QAction::triggered, this, &Lister::searchAction); 1213 actionCollection()->addAction("search", _actionSearch); 1214 actionCollection()->setDefaultShortcut(_actionSearch, Qt::CTRL + Qt::Key_F); 1215 1216 _actionSearchNext = new QAction(Icon("go-down"), i18n("Search next"), this); 1217 connect(_actionSearchNext, &QAction::triggered, this, &Lister::searchNext); 1218 actionCollection()->addAction("search_next", _actionSearchNext); 1219 actionCollection()->setDefaultShortcut(_actionSearchNext, Qt::Key_F3); 1220 1221 _actionSearchPrev = new QAction(Icon("go-up"), i18n("Search previous"), this); 1222 connect(_actionSearchPrev, &QAction::triggered, this, &Lister::searchPrev); 1223 actionCollection()->addAction("search_prev", _actionSearchPrev); 1224 actionCollection()->setDefaultShortcut(_actionSearchPrev, Qt::SHIFT + Qt::Key_F3); 1225 1226 _actionJumpToPosition = new QAction(Icon("go-jump"), i18n("Jump to position"), this); 1227 connect(_actionJumpToPosition, &QAction::triggered, this, &Lister::jumpToPosition); 1228 actionCollection()->addAction("jump_to_position", _actionJumpToPosition); 1229 actionCollection()->setDefaultShortcut(_actionJumpToPosition, Qt::CTRL + Qt::Key_G); 1230 1231 _actionHexMode = new QAction(Icon("document-preview"), i18n("Hex mode"), this); 1232 connect(_actionHexMode, &QAction::triggered, this, &Lister::toggleHexMode); 1233 actionCollection()->addAction("hex_mode", _actionHexMode); 1234 actionCollection()->setDefaultShortcut(_actionHexMode, Qt::CTRL + Qt::Key_H); 1235 1236 new ListerEncodingMenu(this, i18n("Select charset"), "character-set", actionCollection()); 1237 1238 QWidget *widget = new ListerPane(this, parent); 1239 widget->setFocusPolicy(Qt::StrongFocus); 1240 auto *grid = new QGridLayout(widget); 1241 _textArea = new ListerTextArea(this, widget); 1242 _textArea->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); 1243 _textArea->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); 1244 _textArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 1245 _textArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 1246 widget->setFocusProxy(_textArea); 1247 grid->addWidget(_textArea, 0, 0); 1248 _scrollBar = new QScrollBar(Qt::Vertical, widget); 1249 grid->addWidget(_scrollBar, 0, 1); 1250 _scrollBar->hide(); 1251 1252 QWidget *statusWidget = new QWidget(widget); 1253 auto *hbox = new QHBoxLayout(statusWidget); 1254 1255 _listerLabel = new QLabel(i18n("Lister:"), statusWidget); 1256 hbox->addWidget(_listerLabel); 1257 _searchProgressBar = new QProgressBar(statusWidget); 1258 _searchProgressBar->setMinimum(0); 1259 _searchProgressBar->setMaximum(1000); 1260 _searchProgressBar->setValue(0); 1261 _searchProgressBar->hide(); 1262 hbox->addWidget(_searchProgressBar); 1263 1264 _searchStopButton = new QToolButton(statusWidget); 1265 _searchStopButton->setIcon(Icon("process-stop")); 1266 _searchStopButton->setToolTip(i18n("Stop search")); 1267 _searchStopButton->hide(); 1268 connect(_searchStopButton, &QToolButton::clicked, this, &Lister::searchDelete); 1269 hbox->addWidget(_searchStopButton); 1270 1271 _searchLineEdit = new KLineEdit(statusWidget); 1272 _searchLineEdit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); 1273 1274 _originalBackground = _searchLineEdit->palette().color(QPalette::Base); 1275 _originalForeground = _searchLineEdit->palette().color(QPalette::Text); 1276 1277 connect(_searchLineEdit, &KLineEdit::KLINEEDIT_RETURNKEYPRESSED, this, &Lister::searchNext); 1278 connect(_searchLineEdit, &KLineEdit::textChanged, this, &Lister::searchTextChanged); 1279 1280 hbox->addWidget(_searchLineEdit); 1281 _searchNextButton = new QPushButton(Icon("go-down"), i18n("Next"), statusWidget); 1282 _searchNextButton->setToolTip(i18n("Jump to next match")); 1283 connect(_searchNextButton, &QPushButton::clicked, this, &Lister::searchNext); 1284 hbox->addWidget(_searchNextButton); 1285 _searchPrevButton = new QPushButton(Icon("go-up"), i18n("Previous"), statusWidget); 1286 _searchPrevButton->setToolTip(i18n("Jump to previous match")); 1287 connect(_searchPrevButton, &QPushButton::clicked, this, &Lister::searchPrev); 1288 hbox->addWidget(_searchPrevButton); 1289 _searchOptions = new QPushButton(i18n("Options"), statusWidget); 1290 _searchOptions->setToolTip(i18n("Modify search behavior")); 1291 auto *menu = new QMenu(); 1292 _fromCursorAction = menu->addAction(i18n("From cursor")); 1293 _fromCursorAction->setCheckable(true); 1294 _fromCursorAction->setChecked(true); 1295 _caseSensitiveAction = menu->addAction(i18n("Case sensitive")); 1296 _caseSensitiveAction->setCheckable(true); 1297 _matchWholeWordsOnlyAction = menu->addAction(i18n("Match whole words only")); 1298 _matchWholeWordsOnlyAction->setCheckable(true); 1299 _regExpAction = menu->addAction(i18n("RegExp")); 1300 _regExpAction->setCheckable(true); 1301 _hexAction = menu->addAction(i18n("Hexadecimal")); 1302 _hexAction->setCheckable(true); 1303 _searchOptions->setMenu(menu); 1304 1305 hbox->addWidget(_searchOptions); 1306 1307 hbox->addItem(new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum)); 1308 1309 _statusLabel = new QLabel(statusWidget); 1310 hbox->addWidget(_statusLabel); 1311 1312 grid->addWidget(statusWidget, 1, 0, 1, 2); 1313 setWidget(widget); 1314 1315 connect(_scrollBar, &QScrollBar::actionTriggered, _textArea, &ListerTextArea::slotActionTriggered); 1316 connect(&_searchUpdateTimer, &QTimer::timeout, this, &Lister::slotUpdate); 1317 1318 new ListerBrowserExtension(this); 1319 enableSearch(false); 1320 1321 _tempFile = new QTemporaryFile(this); 1322 _tempFile->setFileTemplate(QDir::tempPath() + QLatin1String("/krusader_lister.XXXXXX")); 1323 } 1324 1325 bool Lister::openUrl(const QUrl &listerUrl) 1326 { 1327 _downloading = false; 1328 setUrl(listerUrl); 1329 1330 _fileSize = 0; 1331 1332 if (listerUrl.isLocalFile()) { 1333 _filePath = listerUrl.path(); 1334 if (!QFile::exists(_filePath)) 1335 return false; 1336 _fileSize = getFileSize(); 1337 } else { 1338 if (_tempFile->isOpen()) { 1339 _tempFile->close(); 1340 } 1341 _tempFile->open(); 1342 1343 _filePath = _tempFile->fileName(); 1344 1345 KIO::TransferJob *downloadJob = KIO::get(listerUrl, KIO::NoReload, KIO::HideProgressInfo); 1346 1347 connect(downloadJob, &KIO::TransferJob::data, this, [=](KIO::Job *, QByteArray array) { 1348 if (array.size() != 0) { 1349 _tempFile->write(array); 1350 } 1351 }); 1352 connect(downloadJob, &KIO::TransferJob::result, this, [=](KJob *job) { 1353 _tempFile->flush(); 1354 if (job->error()) { /* any error occurred? */ 1355 auto *kioJob = qobject_cast<KIO::TransferJob *>(job); 1356 KMessageBox::error(_textArea, i18n("Error reading file %1.", kioJob->url().toDisplayString(QUrl::PreferLocalFile))); 1357 } 1358 _downloading = false; 1359 _downloadUpdateTimer.stop(); 1360 slotUpdate(); 1361 }); 1362 connect(&_downloadUpdateTimer, &QTimer::timeout, this, [&]() { 1363 slotUpdate(); 1364 }); 1365 _downloadUpdateTimer.start(500); 1366 _downloading = true; 1367 } 1368 1369 // invalidate cache 1370 _cache.clear(); 1371 1372 _textArea->reset(); 1373 emit started(nullptr); 1374 emit setWindowCaption(listerUrl.toDisplayString()); 1375 emit completed(); 1376 return true; 1377 } 1378 1379 QByteArray Lister::cacheChunk(const qint64 filePos, const qint64 maxSize) 1380 { 1381 if (filePos >= _fileSize) { 1382 return QByteArray(); 1383 } 1384 1385 qint64 size = maxSize; 1386 if (_fileSize - filePos < size) { 1387 size = _fileSize - filePos; 1388 } 1389 1390 if (!_cache.isEmpty() && (filePos >= _cachePos) && (filePos + size <= _cachePos + _cache.size())) { 1391 return _cache.mid(static_cast<int>(filePos - _cachePos), static_cast<int>(size)); 1392 } 1393 1394 const int negativeOffset = CACHE_SIZE * 2 / 5; 1395 qint64 cachePos = filePos - negativeOffset; 1396 if (cachePos < 0) 1397 cachePos = 0; 1398 1399 QFile sourceFile(_filePath); 1400 if (!sourceFile.open(QIODevice::ReadOnly)) { 1401 return QByteArray(); 1402 } 1403 1404 if (!sourceFile.seek(cachePos)) { 1405 return QByteArray(); 1406 } 1407 1408 const QByteArray bytes = sourceFile.read(CACHE_SIZE); 1409 if (bytes.isEmpty()) { 1410 return bytes; 1411 } 1412 1413 _cache = bytes; 1414 _cachePos = cachePos; 1415 const qint64 cacheRefIndex = filePos - _cachePos; 1416 qint64 newSize = bytes.size() - cacheRefIndex; 1417 if (newSize < size) 1418 size = newSize; 1419 1420 return _cache.mid(static_cast<int>(cacheRefIndex), static_cast<int>(size)); 1421 } 1422 1423 qint64 Lister::getFileSize() 1424 { 1425 return QFile(_filePath).size(); 1426 } 1427 1428 void Lister::guiActivateEvent(KParts::GUIActivateEvent *event) 1429 { 1430 if (event->activated()) { 1431 slotUpdate(); 1432 _textArea->redrawTextArea(true); 1433 } else { 1434 enableSearch(false); 1435 } 1436 KParts::ReadOnlyPart::guiActivateEvent(event); 1437 } 1438 1439 void Lister::slotUpdate() 1440 { 1441 const qint64 oldSize = _fileSize; 1442 _fileSize = getFileSize(); 1443 if (oldSize != _fileSize) 1444 _textArea->sizeChanged(); 1445 1446 int cursorX = 0, cursorY = 0; 1447 _textArea->getCursorPosition(cursorX, cursorY); 1448 bool isfirst = false; 1449 const qint64 cursor = _textArea->getCursorPosition(isfirst); 1450 1451 const int percent = (_fileSize == 0) ? 0 : (int)((201 * cursor) / _fileSize / 2); 1452 1453 const QString status = i18n("Column: %1, Position: %2 (%3, %4%)", cursorX, cursor, _fileSize, percent); 1454 _statusLabel->setText(status); 1455 1456 if (_searchProgressCounter) 1457 _searchProgressCounter--; 1458 } 1459 1460 bool Lister::isSearchEnabled() 1461 { 1462 return !_searchLineEdit->isHidden() || !_searchProgressBar->isHidden(); 1463 } 1464 1465 void Lister::enableSearch(const bool enable) 1466 { 1467 if (enable) { 1468 _listerLabel->setText(i18n("Search:")); 1469 _searchLineEdit->show(); 1470 _searchNextButton->show(); 1471 _searchPrevButton->show(); 1472 _searchOptions->show(); 1473 if (!_searchLineEdit->hasFocus()) { 1474 _searchLineEdit->setFocus(); 1475 const QString selection = _textArea->getSelectedText(); 1476 if (!selection.isEmpty()) { 1477 _searchLineEdit->setText(selection); 1478 } 1479 _searchLineEdit->selectAll(); 1480 } 1481 } else { 1482 _listerLabel->setText(i18n("Lister:")); 1483 _searchLineEdit->hide(); 1484 _searchNextButton->hide(); 1485 _searchPrevButton->hide(); 1486 _searchOptions->hide(); 1487 _textArea->setFocus(); 1488 } 1489 } 1490 1491 void Lister::searchNext() 1492 { 1493 search(true); 1494 } 1495 1496 void Lister::searchPrev() 1497 { 1498 search(false); 1499 } 1500 1501 void Lister::search(const bool forward, const bool restart) 1502 { 1503 _restartFromBeginning = restart; 1504 if (_searchInProgress || _searchLineEdit->text().isEmpty()) 1505 return; 1506 if (_searchLineEdit->isHidden()) 1507 enableSearch(true); 1508 1509 _searchPosition = forward ? 0 : _fileSize; 1510 if (_fromCursorAction->isChecked()) { 1511 bool isfirst; 1512 qint64 cursor = _textArea->getCursorPosition(isfirst); 1513 if (cursor != 0 && !forward) 1514 cursor--; 1515 if (_searchLastFailedPosition == -1 || _searchLastFailedPosition != cursor) 1516 _searchPosition = cursor; 1517 } 1518 const bool caseSensitive = _caseSensitiveAction->isChecked(); 1519 const bool matchWholeWord = _matchWholeWordsOnlyAction->isChecked(); 1520 const bool regExp = _regExpAction->isChecked(); 1521 const bool hex = _hexAction->isChecked(); 1522 1523 if (hex) { 1524 QString hexcontent = _searchLineEdit->text(); 1525 hexcontent.remove(QLatin1String("0x")); 1526 hexcontent.remove(' '); 1527 hexcontent.remove('\t'); 1528 hexcontent.remove('\n'); 1529 hexcontent.remove('\r'); 1530 1531 _searchHexQuery = QByteArray(); 1532 1533 if (hexcontent.length() & 1) { 1534 setColor(false, false); 1535 return; 1536 } 1537 1538 while (!hexcontent.isEmpty()) { 1539 const QString hexData = hexcontent.left(2); 1540 hexcontent = hexcontent.mid(2); 1541 bool ok = true; 1542 const int c = hexData.toUInt(&ok, 16); 1543 if (!ok) { 1544 setColor(false, false); 1545 return; 1546 } 1547 _searchHexQuery.push_back((char)c); 1548 } 1549 } else { 1550 _searchQuery.setContent(_searchLineEdit->text(), caseSensitive, matchWholeWord, codec()->name(), regExp); 1551 } 1552 _searchIsForward = forward; 1553 _searchHexadecimal = hex; 1554 1555 QTimer::singleShot(0, this, &Lister::slotSearchMore); 1556 _searchInProgress = true; 1557 _searchProgressCounter = 3; 1558 1559 enableActions(false); 1560 } 1561 1562 void Lister::enableActions(const bool state) 1563 { 1564 _actionSearch->setEnabled(state); 1565 _actionSearchNext->setEnabled(state); 1566 _actionSearchPrev->setEnabled(state); 1567 _actionJumpToPosition->setEnabled(state); 1568 if (state) { 1569 _searchUpdateTimer.stop(); 1570 } else { 1571 slotUpdate(); 1572 } 1573 } 1574 1575 void Lister::slotSearchMore() 1576 { 1577 if (!_searchInProgress) 1578 return; 1579 1580 if (!_searchUpdateTimer.isActive()) { 1581 _searchUpdateTimer.start(200); 1582 } 1583 1584 updateProgressBar(); 1585 if (!_searchIsForward) 1586 _searchPosition--; 1587 1588 if (_searchPosition < 0 || _searchPosition >= _fileSize) { 1589 if (_restartFromBeginning) 1590 resetSearchPosition(); 1591 else { 1592 searchFailed(); 1593 return; 1594 } 1595 } 1596 1597 qint64 maxCacheSize = SEARCH_CACHE_CHARS; 1598 qint64 searchPos = _searchPosition; 1599 bool setPosition = true; 1600 if (!_searchIsForward) { 1601 qint64 origSearchPos = _searchPosition; 1602 searchPos -= maxCacheSize; 1603 if (searchPos <= 0) { 1604 searchPos = 0; 1605 _searchPosition = 0; 1606 setPosition = false; 1607 } 1608 qint64 diff = origSearchPos - searchPos; 1609 if (diff < maxCacheSize) 1610 maxCacheSize = diff; 1611 } 1612 1613 const QByteArray chunk = cacheChunk(searchPos, maxCacheSize); 1614 if (chunk.isEmpty()) { 1615 searchFailed(); 1616 return; 1617 } 1618 1619 const qint64 chunkSize = chunk.size(); 1620 1621 qint64 foundAnchor = -1; 1622 qint64 foundCursor = -1; 1623 qint64 byteCounter = 0; 1624 1625 if (_searchHexadecimal) { 1626 const int ndx = _searchIsForward ? chunk.indexOf(_searchHexQuery) : chunk.lastIndexOf(_searchHexQuery); 1627 if (chunkSize > _searchHexQuery.length()) { 1628 if (_searchIsForward) { 1629 _searchPosition = searchPos + chunkSize; 1630 if ((_searchPosition < _fileSize) && (chunkSize > _searchHexQuery.length())) 1631 _searchPosition -= _searchHexQuery.length(); 1632 byteCounter = _searchPosition - searchPos; 1633 } else { 1634 if (_searchPosition > 0) 1635 _searchPosition += _searchHexQuery.length(); 1636 } 1637 } 1638 if (ndx != -1) { 1639 foundAnchor = searchPos + ndx; 1640 foundCursor = foundAnchor + _searchHexQuery.length(); 1641 } 1642 } else { 1643 qint64 rowStart = 0; 1644 QString row = ""; 1645 1646 QScopedPointer<QTextDecoder> decoder(_codec->makeDecoder()); 1647 1648 while (byteCounter < chunkSize) { 1649 const QString chr = decoder->toUnicode(chunk.mid(static_cast<int>(byteCounter++), 1)); 1650 if (chr.isEmpty() && byteCounter < chunkSize) { 1651 continue; 1652 } 1653 1654 if (chr != "\n") 1655 row += chr; 1656 1657 if (chr == "\n" || row.length() >= SEARCH_MAX_ROW_LEN || byteCounter >= chunkSize) { 1658 if (setPosition) { 1659 _searchPosition = searchPos + byteCounter; 1660 if (!_searchIsForward) { 1661 _searchPosition++; 1662 setPosition = false; 1663 } 1664 } 1665 1666 if (_searchQuery.checkLine(row, !_searchIsForward)) { 1667 QByteArray cachedBuffer = chunk.mid(static_cast<int>(rowStart), static_cast<int>(chunkSize - rowStart)); 1668 1669 QTextStream stream(&cachedBuffer); 1670 stream.setCodec(_codec); 1671 1672 stream.read(_searchQuery.matchIndex()); 1673 foundAnchor = searchPos + rowStart + stream.pos(); 1674 1675 stream.read(_searchQuery.matchLength()); 1676 foundCursor = searchPos + rowStart + stream.pos(); 1677 1678 if (_searchIsForward) 1679 break; 1680 } 1681 1682 row = ""; 1683 rowStart = byteCounter; 1684 } 1685 } 1686 } 1687 1688 if (foundAnchor != -1 && foundCursor != -1) { 1689 _textArea->setAnchorAndCursor(foundAnchor, foundCursor); 1690 searchSucceeded(); 1691 return; 1692 } 1693 1694 if (_searchIsForward && searchPos + byteCounter >= _fileSize) { 1695 if (_restartFromBeginning) 1696 resetSearchPosition(); 1697 else { 1698 searchFailed(); 1699 return; 1700 } 1701 } else if (_searchPosition <= 0 || _searchPosition >= _fileSize) { 1702 if (_restartFromBeginning) 1703 resetSearchPosition(); 1704 else { 1705 searchFailed(); 1706 return; 1707 } 1708 } 1709 1710 QTimer::singleShot(0, this, &Lister::slotSearchMore); 1711 } 1712 1713 void Lister::resetSearchPosition() 1714 { 1715 _restartFromBeginning = false; 1716 _searchPosition = _searchIsForward ? 0 : _fileSize - 1; 1717 } 1718 1719 void Lister::searchSucceeded() 1720 { 1721 _searchInProgress = false; 1722 setColor(true, false); 1723 hideProgressBar(); 1724 _searchLastFailedPosition = -1; 1725 1726 enableActions(true); 1727 } 1728 1729 void Lister::searchFailed() 1730 { 1731 _searchInProgress = false; 1732 setColor(false, false); 1733 hideProgressBar(); 1734 bool isfirst; 1735 _searchLastFailedPosition = _textArea->getCursorPosition(isfirst); 1736 if (!_searchIsForward) 1737 _searchLastFailedPosition--; 1738 1739 enableActions(true); 1740 } 1741 1742 void Lister::searchDelete() 1743 { 1744 _searchInProgress = false; 1745 setColor(false, true); 1746 hideProgressBar(); 1747 _searchLastFailedPosition = -1; 1748 1749 enableActions(true); 1750 } 1751 1752 void Lister::searchTextChanged() 1753 { 1754 searchDelete(); 1755 if (_fileSize < 0x10000) { // autosearch files less than 64k 1756 if (!_searchLineEdit->text().isEmpty()) { 1757 bool isfirst; 1758 const qint64 anchor = _textArea->getCursorAnchor(); 1759 const qint64 cursor = _textArea->getCursorPosition(isfirst); 1760 if (cursor > anchor && anchor != -1) { 1761 _textArea->setCursorPositionInDocument(anchor, true); 1762 } 1763 search(true, true); 1764 } 1765 } 1766 } 1767 1768 void Lister::setColor(const bool match, const bool restore) 1769 { 1770 QColor fore, back; 1771 1772 if (!restore) { 1773 const KConfigGroup gc(krConfig, "Colors"); 1774 1775 QString foreground, background; 1776 const QPalette p = QGuiApplication::palette(); 1777 1778 if (match) { 1779 foreground = "Quicksearch Match Foreground"; 1780 background = "Quicksearch Match Background"; 1781 fore = Qt::black; 1782 back = QColor(192, 255, 192); 1783 } else { 1784 foreground = "Quicksearch Non-match Foreground"; 1785 background = "Quicksearch Non-match Background"; 1786 fore = Qt::black; 1787 back = QColor(255, 192, 192); 1788 } 1789 1790 if (gc.readEntry(foreground, QString()) == "KDE default") 1791 fore = p.color(QPalette::Active, QPalette::Text); 1792 else if (!gc.readEntry(foreground, QString()).isEmpty()) 1793 fore = gc.readEntry(foreground, fore); 1794 1795 if (gc.readEntry(background, QString()) == "KDE default") 1796 back = p.color(QPalette::Active, QPalette::Base); 1797 else if (!gc.readEntry(background, QString()).isEmpty()) 1798 back = gc.readEntry(background, back); 1799 } else { 1800 back = _originalBackground; 1801 fore = _originalForeground; 1802 } 1803 1804 QPalette pal = _searchLineEdit->palette(); 1805 pal.setColor(QPalette::Base, back); 1806 pal.setColor(QPalette::Text, fore); 1807 _searchLineEdit->setPalette(pal); 1808 } 1809 1810 void Lister::hideProgressBar() 1811 { 1812 if (!_searchProgressBar->isHidden()) { 1813 _searchProgressBar->hide(); 1814 _searchStopButton->hide(); 1815 _searchLineEdit->show(); 1816 _searchNextButton->show(); 1817 _searchPrevButton->show(); 1818 _searchOptions->show(); 1819 _listerLabel->setText(i18n("Search:")); 1820 } 1821 } 1822 1823 void Lister::updateProgressBar() 1824 { 1825 if (_searchProgressCounter) 1826 return; 1827 1828 if (_searchProgressBar->isHidden()) { 1829 _searchProgressBar->show(); 1830 _searchStopButton->show(); 1831 _searchOptions->hide(); 1832 _searchLineEdit->hide(); 1833 _searchNextButton->hide(); 1834 _searchPrevButton->hide(); 1835 _listerLabel->setText(i18n("Search position:")); 1836 1837 // otherwise focus is set to document tab 1838 _textArea->setFocus(); 1839 } 1840 1841 const qint64 pcnt = (_fileSize == 0) ? 1000 : (2001 * _searchPosition) / _fileSize / 2; 1842 const auto pctInt = (int)pcnt; 1843 if (_searchProgressBar->value() != pctInt) 1844 _searchProgressBar->setValue(pctInt); 1845 } 1846 1847 void Lister::jumpToPosition() 1848 { 1849 bool ok = true; 1850 QString res = QInputDialog::getText(_textArea, i18n("Jump to position"), i18n("Text position:"), QLineEdit::Normal, "0", &ok); 1851 if (!ok) 1852 return; 1853 1854 res = res.trimmed(); 1855 qint64 pos = -1; 1856 if (res.startsWith(QLatin1String("0x"))) { 1857 res = res.mid(2); 1858 bool ok; 1859 const qulonglong upos = res.toULongLong(&ok, 16); 1860 if (!ok) { 1861 KMessageBox::error(_textArea, i18n("Invalid number."), i18n("Jump to position")); 1862 return; 1863 } 1864 pos = (qint64)upos; 1865 } else { 1866 bool ok; 1867 const qulonglong upos = res.toULongLong(&ok); 1868 if (!ok) { 1869 KMessageBox::error(_textArea, i18n("Invalid number."), i18n("Jump to position")); 1870 return; 1871 } 1872 pos = (qint64)upos; 1873 } 1874 1875 if (pos < 0 || pos > _fileSize) { 1876 KMessageBox::error(_textArea, i18n("Number out of range."), i18n("Jump to position")); 1877 return; 1878 } 1879 1880 _textArea->deleteAnchor(); 1881 _textArea->setCursorPositionInDocument(pos, true); 1882 _textArea->ensureVisibleCursor(); 1883 } 1884 1885 void Lister::saveAs() 1886 { 1887 const QUrl url = QFileDialog::getSaveFileUrl(_textArea, i18n("Lister")); 1888 if (url.isEmpty()) 1889 return; 1890 QUrl sourceUrl; 1891 if (!_downloading) 1892 sourceUrl = QUrl::fromLocalFile(_filePath); 1893 else 1894 sourceUrl = this->url(); 1895 1896 QList<QUrl> urlList; 1897 urlList << sourceUrl; 1898 1899 KIO::Job *job = KIO::copy(urlList, url); 1900 job->setUiDelegate(new KIO::JobUiDelegate()); 1901 KIO::getJobTracker()->registerJob(job); 1902 job->uiDelegate()->setAutoErrorHandlingEnabled(true); 1903 } 1904 1905 void Lister::saveSelected() 1906 { 1907 bool isfirst; 1908 const qint64 start = _textArea->getCursorAnchor(); 1909 const qint64 end = _textArea->getCursorPosition(isfirst); 1910 if (start == -1 || start == end) { 1911 KMessageBox::error(_textArea, i18n("Nothing is selected."), i18n("Save selection...")); 1912 return; 1913 } 1914 if (start > end) { 1915 _savePosition = end; 1916 _saveEnd = start; 1917 } else { 1918 _savePosition = start; 1919 _saveEnd = end; 1920 } 1921 1922 const QUrl url = QFileDialog::getSaveFileUrl(_textArea, i18n("Lister")); 1923 if (url.isEmpty()) 1924 return; 1925 1926 KIO::TransferJob *saveJob = KIO::put(url, -1, KIO::Overwrite); 1927 connect(saveJob, &KIO::TransferJob::dataReq, this, &Lister::slotDataSend); 1928 connect(saveJob, &KIO::TransferJob::result, this, &Lister::slotSendFinished); 1929 1930 saveJob->setUiDelegate(new KIO::JobUiDelegate()); 1931 KIO::getJobTracker()->registerJob(saveJob); 1932 saveJob->uiDelegate()->setAutoErrorHandlingEnabled(true); 1933 1934 _actionSaveSelected->setEnabled(false); 1935 } 1936 1937 void Lister::slotDataSend(KIO::Job *, QByteArray &array) 1938 { 1939 if (_savePosition >= _saveEnd) { 1940 array = QByteArray(); 1941 return; 1942 } 1943 qint64 max = _saveEnd - _savePosition; 1944 if (max > 1000) 1945 max = 1000; 1946 array = cacheChunk(_savePosition, (int)max); 1947 _savePosition += array.size(); 1948 } 1949 1950 void Lister::slotSendFinished(KJob *) 1951 { 1952 _actionSaveSelected->setEnabled(true); 1953 } 1954 1955 void Lister::setCharacterSet(const QString &set) 1956 { 1957 _characterSet = set; 1958 if (_characterSet.isEmpty()) { 1959 _codec = QTextCodec::codecForLocale(); 1960 } else { 1961 _codec = KCharsets::charsets()->codecForName(_characterSet); 1962 } 1963 _textArea->redrawTextArea(true); 1964 } 1965 1966 void Lister::print() 1967 { 1968 bool isfirst; 1969 const qint64 anchor = _textArea->getCursorAnchor(); 1970 const qint64 cursor = _textArea->getCursorPosition(isfirst); 1971 const bool hasSelection = (anchor != -1 && anchor != cursor); 1972 1973 const QString docName = url().fileName(); 1974 QPrinter printer; 1975 printer.setDocName(docName); 1976 1977 QScopedPointer<QPrintDialog> printDialog(new QPrintDialog(&printer, _textArea)); 1978 1979 if (hasSelection) { 1980 printDialog->addEnabledOption(QAbstractPrintDialog::PrintSelection); 1981 } 1982 1983 if (!printDialog->exec()) { 1984 return; 1985 } 1986 1987 if (printer.pageOrder() == QPrinter::LastPageFirst) { 1988 switch (KMessageBox::warningContinueCancel(_textArea, i18n("Reverse printing is not supported. Continue with normal printing?"))) { 1989 case KMessageBox::Continue: 1990 break; 1991 default: 1992 return; 1993 } 1994 } 1995 QPainter painter; 1996 painter.begin(&printer); 1997 1998 const QString dateString = QDate::currentDate().toString(Qt::SystemLocaleShortDate); 1999 2000 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 2001 const QRect pageRect = printer.pageLayout().paintRectPixels(printer.resolution()); 2002 #else 2003 const QRect pageRect = printer.pageRect(); 2004 #endif 2005 const QRect drawingRect(0, 0, pageRect.width(), pageRect.height()); 2006 2007 const QFont normalFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); 2008 const QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); 2009 2010 const QFontMetrics fmNormal(normalFont); 2011 const int normalFontHeight = fmNormal.height(); 2012 2013 const QFontMetrics fmFixed(fixedFont); 2014 const int fixedFontHeight = std::max(fmFixed.height(), 1); 2015 const int fixedFontWidth = std::max(fmFixed.horizontalAdvance("W"), 1); 2016 2017 const int effPageSize = drawingRect.height() - normalFontHeight - 1; 2018 const int rowsPerPage = std::max(effPageSize / fixedFontHeight, 1); 2019 const int columnsPerPage = std::max(drawingRect.width() / fixedFontWidth, 1); 2020 2021 bool firstPage = true; 2022 2023 qint64 startPos = 0; 2024 qint64 endPos = _fileSize; 2025 if (printer.printRange() == QPrinter::Selection) { 2026 if (anchor > cursor) 2027 startPos = cursor, endPos = anchor; 2028 else 2029 startPos = anchor, endPos = cursor; 2030 } 2031 2032 int page = 0; 2033 while (startPos < endPos) { 2034 page++; 2035 2036 QStringList rows = readLines(startPos, endPos, columnsPerPage, rowsPerPage); 2037 2038 // print since set-up fromPage number 2039 if (printer.fromPage() && page < printer.fromPage()) { 2040 continue; 2041 } 2042 2043 // print until set-up toPage number 2044 if (printer.toPage() && printer.toPage() >= printer.fromPage() && page > printer.toPage()) 2045 break; 2046 2047 if (!firstPage) { 2048 printer.newPage(); 2049 } 2050 firstPage = false; 2051 // Use the painter to draw on the page. 2052 painter.setFont(normalFont); 2053 2054 painter.drawText(drawingRect, Qt::AlignLeft, dateString); 2055 painter.drawText(drawingRect, Qt::AlignHCenter, docName); 2056 painter.drawText(drawingRect, Qt::AlignRight, QString("%1").arg(page)); 2057 2058 painter.drawLine(0, normalFontHeight, drawingRect.width(), normalFontHeight); 2059 2060 painter.setFont(fixedFont); 2061 int yOffset = normalFontHeight + 1; 2062 foreach (const QString &row, rows) { 2063 painter.drawText(0, yOffset + fixedFontHeight, row); 2064 yOffset += fixedFontHeight; 2065 } 2066 } 2067 } 2068 2069 QStringList Lister::readLines(qint64 &filePos, const qint64 endPos, const int columns, const int lines) 2070 { 2071 if (_textArea->hexMode()) { 2072 return readHexLines(filePos, endPos, columns, lines); 2073 } 2074 QStringList list; 2075 const int maxBytes = std::min(columns * lines * MAX_CHAR_LENGTH, (int)(endPos - filePos)); 2076 if (maxBytes <= 0) { 2077 return list; 2078 } 2079 const QByteArray chunk = cacheChunk(filePos, maxBytes); 2080 if (chunk.isEmpty()) { 2081 return list; 2082 } 2083 2084 QScopedPointer<QTextDecoder> decoder(_codec->makeDecoder()); 2085 2086 int byteCounter = 0; 2087 QString row = ""; 2088 bool skipImmediateNewline = false; 2089 while (byteCounter < chunk.size() && list.size() < lines) { 2090 QString chr = decoder->toUnicode(chunk.mid(byteCounter++, 1)); 2091 if (chr.isEmpty()) { 2092 continue; 2093 } 2094 2095 // replace unreadable characters 2096 if ((chr[0] < 32) && (chr[0] != '\n') && (chr[0] != '\t')) { 2097 chr = QChar(' '); 2098 } 2099 2100 // handle newline 2101 if (chr == "\n") { 2102 if (!skipImmediateNewline) { 2103 list << row; 2104 row = ""; 2105 } 2106 skipImmediateNewline = false; 2107 continue; 2108 } 2109 2110 skipImmediateNewline = false; 2111 2112 // handle tab 2113 if (chr == "\t") { 2114 const int tabLength = _textArea->tabWidth() - (row.length() % _textArea->tabWidth()); 2115 if (row.length() + tabLength > columns) { 2116 list << row; 2117 row = ""; 2118 } 2119 row += QString(tabLength, QChar(' ')); 2120 } else { 2121 // normal printable character 2122 row += chr; 2123 } 2124 2125 if (row.length() >= columns) { 2126 list << row; 2127 row = ""; 2128 skipImmediateNewline = true; 2129 } 2130 } 2131 2132 if (list.size() < lines) { 2133 list << row; 2134 } 2135 2136 filePos += byteCounter; 2137 2138 return list; 2139 } 2140 2141 int Lister::hexPositionDigits() 2142 { 2143 int positionDigits = 0; 2144 qint64 checker = _fileSize; 2145 while (checker) { 2146 positionDigits++; 2147 checker /= 16; 2148 } 2149 if (positionDigits < 8) { 2150 return 8; 2151 } 2152 return positionDigits; 2153 } 2154 2155 int Lister::hexBytesPerLine(const int columns) 2156 { 2157 const int positionDigits = hexPositionDigits(); 2158 if (columns >= positionDigits + 5 + 128) { 2159 return 32; 2160 } 2161 if (columns >= positionDigits + 5 + 64) { 2162 return 16; 2163 } 2164 return 8; 2165 } 2166 2167 QStringList Lister::readHexLines(qint64 &filePos, const qint64 endPos, const int columns, const int lines) 2168 { 2169 const int positionDigits = hexPositionDigits(); 2170 const int bytesPerRow = hexBytesPerLine(columns); 2171 2172 QStringList list; 2173 2174 const qint64 choppedPos = (filePos / bytesPerRow) * bytesPerRow; 2175 const int maxBytes = std::min(bytesPerRow * lines, (int)(endPos - choppedPos)); 2176 if (maxBytes <= 0) 2177 return list; 2178 2179 const QByteArray chunk = cacheChunk(choppedPos, maxBytes); 2180 if (chunk.isEmpty()) 2181 return list; 2182 2183 int cnt = 0; 2184 for (int l = 0; l < lines; l++) { 2185 if (filePos >= endPos) { 2186 break; 2187 } 2188 2189 const qint64 printPos = (filePos / bytesPerRow) * bytesPerRow; 2190 QString pos; 2191 pos.setNum(printPos, 16); 2192 while (pos.length() < positionDigits) 2193 pos = QString("0") + pos; 2194 pos = QString("0x") + pos; 2195 pos += QString(": "); 2196 2197 QString charData; 2198 2199 for (int i = 0; i != bytesPerRow; ++i, ++cnt) { 2200 const qint64 currentPos = printPos + i; 2201 if (currentPos < filePos || currentPos >= endPos) { 2202 pos += QString(" "); 2203 charData += QString(" "); 2204 } else { 2205 char c = chunk.at(cnt); 2206 auto charCode = (int)c; 2207 if (charCode < 0) 2208 charCode += 256; 2209 QString hex; 2210 hex.setNum(charCode, 16); 2211 if (hex.length() < 2) 2212 hex = QString("0") + hex; 2213 pos += hex + QString(" "); 2214 if (c < 32) 2215 c = '.'; 2216 charData += QChar(c); 2217 } 2218 } 2219 2220 pos += QString(" ") + charData; 2221 list << pos; 2222 filePos = printPos + bytesPerRow; 2223 } 2224 2225 if (filePos > endPos) { 2226 filePos = endPos; 2227 } 2228 2229 return list; 2230 } 2231 2232 int Lister::hexIndexToPosition(const int columns, const int index) 2233 { 2234 const int positionDigits = hexPositionDigits(); 2235 const int bytesPerRow = hexBytesPerLine(columns); 2236 const int finalIndex = std::min(index, bytesPerRow); 2237 2238 return positionDigits + 4 + (3 * finalIndex); 2239 } 2240 2241 int Lister::hexPositionToIndex(const int columns, const int position) 2242 { 2243 const int positionDigits = hexPositionDigits(); 2244 const int bytesPerRow = hexBytesPerLine(columns); 2245 2246 int finalPosition = position; 2247 finalPosition -= 4 + positionDigits; 2248 if (finalPosition <= 0) 2249 return 0; 2250 2251 finalPosition /= 3; 2252 if (finalPosition >= bytesPerRow) 2253 return bytesPerRow; 2254 return finalPosition; 2255 } 2256 2257 void Lister::toggleHexMode() 2258 { 2259 setHexMode(!_textArea->hexMode()); 2260 } 2261 2262 void Lister::setHexMode(const bool mode) 2263 { 2264 if (mode) { 2265 _textArea->setHexMode(true); 2266 _actionHexMode->setText(i18n("Text mode")); 2267 } else { 2268 _textArea->setHexMode(false); 2269 _actionHexMode->setText(i18n("Hex mode")); 2270 } 2271 }