diff --git a/krusader/KViewer/lister.cpp b/krusader/KViewer/lister.cpp index b9bae046..0ec043d7 100644 --- a/krusader/KViewer/lister.cpp +++ b/krusader/KViewer/lister.cpp @@ -1,2243 +1,2259 @@ /*************************************************************************** lister.cpp - description ------------------- copyright : (C) 2009 + by Csaba Karai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net --------------------------------------------------------------------------- Description *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD S o u r c e F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "lister.h" // QtCore #include #include #include #include #include #include // QtGui #include #include #include #include #include // QtWidgets #include #include #include #include #include #include #include #include #include #include #include #include // QtPrintSupport #include #include #include #include #include #include #include #include #include #include #include #include "../krglobal.h" #include "../kractions.h" #include "../GUI/krremoteencodingmenu.h" #define SEARCH_CACHE_CHARS 100000 #define SEARCH_MAX_ROW_LEN 4000 #define CONTROL_CHAR 752 #define CACHE_SIZE 100000 ListerTextArea::ListerTextArea(Lister *lister, QWidget *parent) : KTextEdit(parent), _lister(lister), _sizeX(-1), _sizeY(-1), _cursorAnchorPos(-1), _inSliderOp(false), _inCursorUpdate(false), _hexMode(false) { connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged())); _tabWidth = 4; setWordWrapMode(QTextOption::NoWrap); setLineWrapMode(QTextEdit::NoWrap); // zoom shortcuts connect(new QShortcut(QKeySequence("Ctrl++"), this), SIGNAL(activated()), this, SLOT(zoomIn())); connect(new QShortcut(QKeySequence("Ctrl+-"), this), SIGNAL(activated()), this, SLOT(zoomOut())); } void ListerTextArea::reset() { _screenStartPos = 0; _cursorPos = 0; _cursorAnchorPos = -1; _cursorAtFirstColumn = true; calculateText(); } void ListerTextArea::sizeChanged() { if (_cursorAnchorPos > _lister->fileSize()) _cursorAnchorPos = -1; if (_cursorPos > _lister->fileSize()) _cursorPos = _lister->fileSize(); redrawTextArea(true); } void ListerTextArea::resizeEvent(QResizeEvent * event) { KTextEdit::resizeEvent(event); redrawTextArea(); } void ListerTextArea::calculateText(bool forcedUpdate) { QFontMetrics fm(font()); int fontHeight = fm.height(); if (fontHeight < 1) fontHeight = 1; QRect crect = viewport()->contentsRect(); int windowHeight = crect.height(); int sizeY = windowHeight / fontHeight; _pageSize = sizeY; QList rowStarts; int fontWidth = fm.width("W"); if (fontWidth < 1) fontWidth = 1; int windowWidth = crect.width() - fontWidth / 2; if (windowWidth < 1) windowWidth = 0; setTabStopWidth(fontWidth * _tabWidth); int sizeX = windowWidth / fontWidth; _sizeChanged = (_sizeY != sizeY) || (_sizeX != sizeX) || forcedUpdate; _sizeY = sizeY; _sizeX = sizeX; QStringList list = readLines(_screenStartPos, _screenEndPos, _sizeY, &rowStarts); if (_sizeChanged) { _averagePageSize = _screenEndPos - _screenStartPos; setUpScrollBar(); } rowStarts << _screenEndPos; QStringList listRemn = readLines(_screenEndPos, _screenEndPos, 1); list << listRemn; if (list != _rowContent) { setPlainText(list.join("\n")); _rowContent = list; _rowStarts = rowStarts; } } qint64 ListerTextArea::textToFilePosition(int x, int y, bool &isfirst) { isfirst = (x == 0); if (y >= _rowStarts.count()) return -1; qint64 rowStart = _rowStarts[ y ]; if (x == 0) return rowStart; if (_hexMode) { qint64 pos = rowStart + _lister->hexPositionToIndex(_sizeX, x); if (pos > _lister->fileSize()) pos = _lister->fileSize(); return pos; } // we can't use fromUnicode because of the invalid encoded chars int maxBytes = 2 * _sizeX * MAX_CHAR_LENGTH; char * cache = _lister->cacheRef(rowStart, maxBytes); QByteArray cachedBuffer(cache, maxBytes); QTextStream stream(&cachedBuffer); stream.setCodec(codec()); stream.read(x); return rowStart + stream.pos(); } void ListerTextArea::fileToTextPosition(qint64 p, bool isfirst, int &x, int &y) { if (p < _screenStartPos || p > _screenEndPos || _rowStarts.count() < 1) { x = -1; y = (p > _screenEndPos) ? -2 : -1; } else { y = 0; while (y < _rowStarts.count() && _rowStarts[ y ] <= p) y++; y--; if (y < 0) { x = y = -1; return; } qint64 rowStart = _rowStarts[ y ]; if (_hexMode) { x = _lister->hexIndexToPosition(_sizeX, (int)(p - rowStart)); return; } int maxBytes = 2 * _sizeX * MAX_CHAR_LENGTH; x = 0; if (rowStart >= p) { if ((rowStart == p) && !isfirst && y > 0) { qint64 previousRow = _rowStarts[ y - 1 ]; char * cache = _lister->cacheRef(previousRow, maxBytes); QByteArray cachedBuffer(cache, p - previousRow); QTextStream stream(&cachedBuffer); stream.setCodec(codec()); stream.read(_rowContent[ y - 1].length()); if (previousRow + stream.pos() == p) { y--; x = _rowContent[ y ].length(); } } return; } char * cache = _lister->cacheRef(rowStart, maxBytes); QByteArray cachedBuffer(cache, p - rowStart); QString res = codec()->toUnicode(cachedBuffer); x = res.length(); } } void ListerTextArea::getCursorPosition(int &x, int &y) { getScreenPosition(textCursor().position(), x, y); } void ListerTextArea::getScreenPosition(int position, int &x, int &y) { x = position; y = 0; for (int i = 0; i < _rowContent.count(); i++) { int rowLen = _rowContent[ i ].length() + 1; if (x >= rowLen) { x -= rowLen; y++; } else break; } } void ListerTextArea::setCursorPosition(int x, int y, int anchorX, int anchorY) { _inCursorUpdate = true; if (x == -1 || y < 0) { setCursorWidth(0); if (anchorY == -1) { _inCursorUpdate = false; return; } if (y == -2) { y = _sizeY; x = (_rowContent.count() > _sizeY) ? _rowContent[ _sizeY ].length() : 0; } else x = y = 0; } else setCursorWidth(2); QTextCursor::MoveMode mode = QTextCursor::MoveAnchor; if (anchorX != -1 && anchorY != -1) { if (anchorY >= _sizeY) { moveCursor(QTextCursor::End, mode); moveCursor(QTextCursor::StartOfLine, mode); } else { moveCursor(QTextCursor::Start, mode); for (int i = 0; i < anchorY; i++) moveCursor(QTextCursor::Down, mode); } if (_rowContent.count() > anchorY && anchorX > _rowContent[ anchorY ].length()) anchorX = _rowContent[ anchorY ].length(); for (int j = 0; j < anchorX; j++) moveCursor(QTextCursor::Right, mode); mode = QTextCursor::KeepAnchor; } if (y >= _sizeY) { moveCursor(QTextCursor::End, mode); moveCursor(QTextCursor::StartOfLine, mode); } else { moveCursor(QTextCursor::Start, mode); for (int i = 0; i < y; i++) moveCursor(QTextCursor::Down, mode); } if (_rowContent.count() > y && x > _rowContent[ y ].length()) x = _rowContent[ y ].length(); for (int j = 0; j < x; j++) moveCursor(QTextCursor::Right, mode); _inCursorUpdate = false; } qint64 ListerTextArea::getCursorPosition(bool &isfirst) { if (cursorWidth() == 0) { isfirst = _cursorAtFirstColumn; return _cursorPos; } int x, y; getCursorPosition(x, y); return textToFilePosition(x, y, isfirst); } void ListerTextArea::setCursorPosition(qint64 p, bool isfirst) { _cursorPos = p; int x, y; fileToTextPosition(p, isfirst, x, y); int anchorX = -1, anchorY = -1; if (_cursorAnchorPos != -1 && _cursorAnchorPos != p) { int anchPos = _cursorAnchorPos; bool anchorBelow = false, anchorAbove = false; if (anchPos < _screenStartPos) { anchPos = _screenStartPos; anchorY = -2; anchorAbove = true; } if (anchPos > _screenEndPos) { anchPos = _screenEndPos; anchorY = -3; anchorBelow = true; } fileToTextPosition(anchPos, isfirst, anchorX, anchorY); if (anchorAbove && _hexMode) anchorX = 0; if (anchorBelow && _hexMode && _rowContent.count() > 0) anchorX = _rowContent[ 0 ].length(); } setCursorPosition(x, y, anchorX, anchorY); } void ListerTextArea::slotCursorPositionChanged() { if (_inCursorUpdate) return; int cursorX, cursorY; getCursorPosition(cursorX, cursorY); _cursorAtFirstColumn = (cursorX == 0); _cursorPos = textToFilePosition(cursorX, cursorY, _cursorAtFirstColumn); setCursorWidth(2); //fprintf( stderr, "Cursor pos: %d %d %Ld\n", cursorX, cursorY, _cursorPos ); } QString ListerTextArea::readSection(qint64 p1, qint64 p2) { if (p1 == p2) return QString(); if (p1 > p2) { qint64 tmp = p1; p1 = p2; p2 = tmp; } QString section; qint64 pos = p1; if (_hexMode) { while (p1 != p2) { QStringList list = _lister->readHexLines(p1, p2, _sizeX, 1); if (list.count() == 0) break; if (!section.isEmpty()) section += QChar('\n'); section += list[ 0 ]; } return section; } QTextCodec * textCodec = codec(); QTextDecoder * decoder = textCodec->makeDecoder(); do { int maxBytes = _sizeX * _sizeY * MAX_CHAR_LENGTH; if (maxBytes > (p2 - pos)) maxBytes = (int)(p2 - pos); char * cache = _lister->cacheRef(pos, maxBytes); if (cache == 0 || maxBytes == 0) break; section += decoder->toUnicode(cache, maxBytes); pos += maxBytes; } while (pos < p2); delete decoder; return section; } QStringList ListerTextArea::readLines(qint64 filePos, qint64 &endPos, int lines, QList * locs) { endPos = filePos; QStringList list; if (_hexMode) { endPos = _lister->fileSize(); if (filePos >= endPos) return list; qint64 startPos = filePos; int bytes = _lister->hexBytesPerLine(_sizeX); filePos = startPos = ((startPos) / bytes) * bytes; list = _lister->readHexLines(filePos, endPos, _sizeX, lines); endPos = filePos; if (locs) { for (int i = 0; i != list.count(); i++) { (*locs) << startPos; startPos += bytes; } } return list; } int maxBytes = _sizeX * _sizeY * MAX_CHAR_LENGTH; char * cache = _lister->cacheRef(filePos, maxBytes); if (cache == 0 || maxBytes == 0) return list; QTextCodec * textCodec = codec(); QTextDecoder * decoder = textCodec->makeDecoder(); int cnt = 0; int y = 0; QString row = ""; int effLength = 0; if (locs) (*locs) << filePos; bool isLastLongLine = false; while (cnt < maxBytes && y < lines) { int lastCnt = cnt; QString chr = decoder->toUnicode(cache + (cnt++), 1); if (!chr.isEmpty()) { if ((chr[ 0 ] < 32) && (chr[ 0 ] != '\n') && (chr[ 0 ] != '\t')) chr = QChar(CONTROL_CHAR); if (chr == "\n") { if (!isLastLongLine) { list << row; effLength = 0; row = ""; y++; if (locs) (*locs) << filePos + cnt; } isLastLongLine = false; } else { isLastLongLine = false; if (chr == "\t") { effLength += _tabWidth - (effLength % _tabWidth) - 1; if (effLength > _sizeX) { list << row; effLength = 0; row = ""; y++; if (locs) (*locs) << filePos + lastCnt; } } row += chr; effLength++; if (effLength >= _sizeX) { list << row; effLength = 0; row = ""; y++; if (locs) (*locs) << filePos + cnt; isLastLongLine = true; } } } } if (y < lines) list << row; if (locs) { while (locs->count() > lines) { locs->removeLast(); } } endPos = filePos + cnt; delete decoder; return list; } QTextCodec * ListerTextArea::codec() { QString cs = _lister->characterSet(); if (cs.isEmpty()) return QTextCodec::codecForLocale(); else return KCharsets::charsets()->codecForName(cs); } void ListerTextArea::setUpScrollBar() { if (_averagePageSize == _lister->fileSize()) { _lister->scrollBar()->setPageStep(0); _lister->scrollBar()->setMaximum(0); _lister->scrollBar()->hide(); _lastPageStartPos = 0; } else { int maxPage = MAX_CHAR_LENGTH * _sizeX * _sizeY; qint64 pageStartPos = _lister->fileSize() - maxPage; qint64 endPos; if (pageStartPos < 0) pageStartPos = 0; QStringList list = readLines(pageStartPos, endPos, maxPage); if (list.count() <= _sizeY) { _lastPageStartPos = 0; } else { readLines(pageStartPos, _lastPageStartPos, list.count() - _sizeY); } int maximum = (_lastPageStartPos > SLIDER_MAX) ? SLIDER_MAX : _lastPageStartPos; int pageSize = (_lastPageStartPos > SLIDER_MAX) ? SLIDER_MAX * _averagePageSize / _lastPageStartPos : _averagePageSize; if (pageSize == 0) pageSize++; _lister->scrollBar()->setPageStep(pageSize); _lister->scrollBar()->setMaximum(maximum); _lister->scrollBar()->show(); } } void ListerTextArea::keyPressEvent(QKeyEvent * ke) { if (KrGlobal::copyShortcut == QKeySequence(ke->key() | ke->modifiers())) { copySelectedToClipboard(); ke->accept(); return; } if (ke->modifiers() == Qt::NoModifier || ke->modifiers() & Qt::ShiftModifier) { qint64 newAnchor = -1; if (ke->modifiers() & Qt::ShiftModifier) { newAnchor = _cursorAnchorPos; if (_cursorAnchorPos == -1) newAnchor = _cursorPos; } switch (ke->key()) { case Qt::Key_F3: ke->accept(); if (ke->modifiers() == Qt::ShiftModifier) _lister->searchPrev(); else _lister->searchNext(); return; case Qt::Key_Home: case Qt::Key_End: _cursorAnchorPos = newAnchor; break; case Qt::Key_Left: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); int x, y; getCursorPosition(x, y); if (y == 0 && x == 0) slotActionTriggered(QAbstractSlider::SliderSingleStepSub); } break; case Qt::Key_Right: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); if (textCursor().position() == toPlainText().length()) slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); } break; case Qt::Key_Up: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); int x, y; getCursorPosition(x, y); if (y == 0) slotActionTriggered(QAbstractSlider::SliderSingleStepSub); } break; case Qt::Key_Down: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); int x, y; getCursorPosition(x, y); if (y >= _sizeY) slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); } break; case Qt::Key_PageDown: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); ke->accept(); int x, y; getCursorPosition(x, y); slotActionTriggered(QAbstractSlider::SliderPageStepAdd); y += _sizeY - _skippedLines; if (y > _rowContent.count()) { y = _rowContent.count(); if (y > 0) x = _rowContent[ y - 1 ].length(); else x = 0; } _cursorPos = textToFilePosition(x, y, _cursorAtFirstColumn); setCursorPosition(_cursorPos, _cursorAtFirstColumn); } return; case Qt::Key_PageUp: { _cursorAnchorPos = newAnchor; ensureVisibleCursor(); ke->accept(); int x, y; getCursorPosition(x, y); slotActionTriggered(QAbstractSlider::SliderPageStepSub); y -= _sizeY - _skippedLines; if (y < 0) { y = 0; x = 0; } _cursorPos = textToFilePosition(x, y, _cursorAtFirstColumn); setCursorPosition(_cursorPos, _cursorAtFirstColumn); } return; } } if (ke->modifiers() == Qt::ControlModifier) { switch (ke->key()) { case Qt::Key_G: ke->accept(); _lister->jumpToPosition(); return; case Qt::Key_F: ke->accept(); _lister->enableSearch(true); return; case Qt::Key_Home: _cursorAnchorPos = -1; ke->accept(); slotActionTriggered(QAbstractSlider::SliderToMinimum); setCursorPosition((qint64)0, true); return; case Qt::Key_A: case Qt::Key_End: { _cursorAnchorPos = (ke->key() == Qt::Key_A) ? 0 : -1; ke->accept(); slotActionTriggered(QAbstractSlider::SliderToMaximum); qint64 endPos = _lister->fileSize(); setCursorPosition(endPos, true); return; } case Qt::Key_Down: ke->accept(); slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); return; case Qt::Key_Up: ke->accept(); slotActionTriggered(QAbstractSlider::SliderSingleStepSub); return; case Qt::Key_PageDown: ke->accept(); slotActionTriggered(QAbstractSlider::SliderPageStepAdd); return; case Qt::Key_PageUp: ke->accept(); slotActionTriggered(QAbstractSlider::SliderPageStepSub); return; } } int oldAnchor = textCursor().anchor(); KTextEdit::keyPressEvent(ke); handleAnchorChange(oldAnchor); } void ListerTextArea::mousePressEvent(QMouseEvent * e) { KTextEdit::mousePressEvent(e); // do change anchor only when shift is not pressed if (!(QGuiApplication::keyboardModifiers() & Qt::ShiftModifier)) { performAnchorChange(textCursor().anchor()); } } void ListerTextArea::mouseDoubleClickEvent(QMouseEvent * e) { _cursorAnchorPos = -1; int oldAnchor = textCursor().anchor(); KTextEdit::mouseDoubleClickEvent(e); handleAnchorChange(oldAnchor); } void ListerTextArea::mouseMoveEvent(QMouseEvent * e) { if (e->pos().y() < 0) { slotActionTriggered(QAbstractSlider::SliderSingleStepSub); } else if (e->pos().y() > height()) { slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); } KTextEdit::mouseMoveEvent(e); } void ListerTextArea::wheelEvent(QWheelEvent * e) { int delta = e->delta(); if (delta) { // zooming if (e->modifiers() & Qt::ControlModifier) { e->accept(); if (delta > 0) { zoomIn(); } else { zoomOut(); } return; } if (delta > 0) { e->accept(); while (delta > 0) { slotActionTriggered(QAbstractSlider::SliderSingleStepSub); slotActionTriggered(QAbstractSlider::SliderSingleStepSub); slotActionTriggered(QAbstractSlider::SliderSingleStepSub); delta -= 120; } } else { e->accept(); while (delta < 0) { slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); slotActionTriggered(QAbstractSlider::SliderSingleStepAdd); delta += 120; } } } } void ListerTextArea::slotActionTriggered(int action) { switch (action) { case QAbstractSlider::SliderSingleStepAdd: { qint64 endPos; readLines(_screenStartPos, endPos, 1); if (endPos <= _lastPageStartPos) { _screenStartPos = endPos; } } break; case QAbstractSlider::SliderSingleStepSub: { if (_screenStartPos == 0) break; if (_hexMode) { int bytesPerRow = _lister->hexBytesPerLine(_sizeX); _screenStartPos = (_screenStartPos / bytesPerRow) * bytesPerRow; _screenStartPos -= bytesPerRow; if (_screenStartPos < 0) _screenStartPos = 0; } else { int maxSize = _sizeX * _sizeY * MAX_CHAR_LENGTH; QByteArray encodedEnter = codec()->fromUnicode(QString("\n")); qint64 readPos = _screenStartPos - maxSize; if (readPos < 0) readPos = 0; maxSize = _screenStartPos - readPos; char * cache = _lister->cacheRef(readPos, maxSize); QByteArray backBuffer(cache, maxSize); int from = maxSize; while (from > 0) { from--; from = backBuffer.lastIndexOf(encodedEnter, from); if (from == -1) break; int backRef = from - 20; if (backRef < 0) backRef = 0; int size = from - backRef + encodedEnter.size(); QString decoded = codec()->toUnicode(cache + backRef, size); if (decoded.endsWith(QLatin1String("\n"))) { if (from < (maxSize - encodedEnter.size())) { from += encodedEnter.size(); break; } } } if (from == -1) from = 0; readPos += from; qint64 previousPos = readPos; while (readPos < _screenStartPos) { previousPos = readPos; readLines(readPos, readPos, 1); } _screenStartPos = previousPos; } } break; case QAbstractSlider::SliderPageStepAdd: { _skippedLines = 0; qint64 endPos; for (int i = 0; i < _sizeY; i++) { readLines(_screenStartPos, endPos, 1); if (endPos <= _lastPageStartPos) { _screenStartPos = endPos; _skippedLines++; } else { break; } } } break; case QAbstractSlider::SliderPageStepSub: { _skippedLines = 0; if (_screenStartPos == 0) break; if (_hexMode) { int bytesPerRow = _lister->hexBytesPerLine(_sizeX); _screenStartPos = (_screenStartPos / bytesPerRow) * bytesPerRow; _screenStartPos -= _sizeY * bytesPerRow; if (_screenStartPos < 0) _screenStartPos = 0; } else { int maxSize = 2 * _sizeX * _sizeY * MAX_CHAR_LENGTH; QByteArray encodedEnter = codec()->fromUnicode(QString("\n")); qint64 readPos = _screenStartPos - maxSize; if (readPos < 0) readPos = 0; maxSize = _screenStartPos - readPos; char * cache = _lister->cacheRef(readPos, maxSize); QByteArray backBuffer(cache, maxSize); int sizeY = _sizeY + 1; int origSizeY = sizeY; int from = maxSize; int lastEnter = maxSize; repeat: while (from > 0) { from--; from = backBuffer.lastIndexOf(encodedEnter, from); if (from == -1) break; int backRef = from - 20; if (backRef < 0) backRef = 0; int size = from - backRef + encodedEnter.size(); QString decoded = codec()->toUnicode(cache + backRef, size); if (decoded.endsWith(QLatin1String("\n"))) { if (from < (maxSize - encodedEnter.size())) { int arrayStart = from + encodedEnter.size(); decoded = codec()->toUnicode(cache + arrayStart, lastEnter - arrayStart); sizeY -= ((decoded.length() / (_sizeX + 1)) + 1); if (sizeY < 0) { from = arrayStart; break; } } lastEnter = from; } } if (from == -1) from = 0; qint64 searchPos = readPos + from; QList locs; while (searchPos < _screenStartPos) { locs << searchPos; readLines(searchPos, searchPos, 1); } if (locs.count() >= _sizeY) _screenStartPos = locs[ locs.count() - _sizeY ]; else if (from != 0) { origSizeY += locs.count() + 1; sizeY = origSizeY; goto repeat; } else if (readPos == 0) _screenStartPos = 0; } } break; case QAbstractSlider::SliderToMinimum: _screenStartPos = 0; break; case QAbstractSlider::SliderToMaximum: _screenStartPos = _lastPageStartPos; break; case QAbstractSlider::SliderMove: { if (_inSliderOp) // self created call? return; qint64 pos = _lister->scrollBar()->sliderPosition(); if (pos == SLIDER_MAX) { _screenStartPos = _lastPageStartPos; break; } else if (pos == 0) { _screenStartPos = 0; break; } if (_lastPageStartPos > SLIDER_MAX) pos = _lastPageStartPos * pos / SLIDER_MAX; if (pos != 0) { if (_hexMode) { int bytesPerRow = _lister->hexBytesPerLine(_sizeX); pos = (pos / bytesPerRow) * bytesPerRow; } else { int maxSize = _sizeX * _sizeY * MAX_CHAR_LENGTH; qint64 readPos = pos - maxSize; if (readPos < 0) readPos = 0; qint64 previousPos = readPos; while (readPos <= pos) { previousPos = readPos; readLines(readPos, readPos, 1); } pos = previousPos; } } _screenStartPos = pos; } break; case QAbstractSlider::SliderNoAction: break; }; _inSliderOp = true; int value = (_lastPageStartPos > SLIDER_MAX) ? SLIDER_MAX * _screenStartPos / _lastPageStartPos : _screenStartPos; _lister->scrollBar()->setSliderPosition(value); _inSliderOp = false; redrawTextArea(); } void ListerTextArea::redrawTextArea(bool forcedUpdate) { bool isfirst; qint64 pos = getCursorPosition(isfirst); calculateText(forcedUpdate); setCursorPosition(pos, isfirst); } void ListerTextArea::ensureVisibleCursor() { if (_cursorPos < _screenStartPos || _cursorPos > _screenEndPos) { int delta = _sizeY / 2; if (delta == 0) delta++; qint64 newScreenStart = _cursorPos; while (delta) { int maxSize = _sizeX * MAX_CHAR_LENGTH; qint64 readPos = newScreenStart - maxSize; if (readPos < 0) readPos = 0; qint64 previousPos = readPos; while (readPos < newScreenStart) { previousPos = readPos; readLines(readPos, readPos, 1); if (readPos == previousPos) break; } newScreenStart = previousPos; delta--; } if (newScreenStart > _lastPageStartPos) newScreenStart = _lastPageStartPos; _screenStartPos = newScreenStart; slotActionTriggered(QAbstractSlider::SliderNoAction); } } void ListerTextArea::setAnchorAndCursor(qint64 anchor, qint64 cursor) { _cursorAnchorPos = anchor; setCursorPosition(cursor, false); ensureVisibleCursor(); } QString ListerTextArea::getSelectedText() { if (_cursorAnchorPos != -1 && _cursorAnchorPos != _cursorPos) { return readSection(_cursorAnchorPos, _cursorPos); } return QString(); } void ListerTextArea::copySelectedToClipboard() { QString selection = getSelectedText(); if (!selection.isEmpty()) { QApplication::clipboard()->setText(selection); } } +void ListerTextArea::clearSelection() +{ + QTextCursor cursor = textCursor(); + cursor.clearSelection(); + setTextCursor(cursor); + _cursorAnchorPos = -1; +} + void ListerTextArea::performAnchorChange(int anchor) { int x, y; bool isfirst; getScreenPosition(anchor, x, y); _cursorAnchorPos = textToFilePosition(x, y, isfirst); } void ListerTextArea::handleAnchorChange(int oldAnchor) { int anchor = textCursor().anchor(); if (oldAnchor != anchor) { performAnchorChange(anchor); } } void ListerTextArea::setHexMode(bool hexMode) { bool isfirst; qint64 pos = getCursorPosition(isfirst); _hexMode = hexMode; _screenStartPos = 0; calculateText(true); setCursorPosition(pos, isfirst); ensureVisibleCursor(); } void ListerTextArea::zoomIn(int range) { KTextEdit::zoomIn(range); redrawTextArea(); } void ListerTextArea::zoomOut(int range) { KTextEdit::zoomOut(range); redrawTextArea(); } ListerPane::ListerPane(Lister *lister, QWidget *parent) : QWidget(parent), _lister(lister) { } bool ListerPane::event(QEvent *e) { bool handled = ListerPane::handleCloseEvent(e); if (!handled) { return QWidget::event(e); } return true; } bool ListerPane::handleCloseEvent(QEvent *e) { if (e->type() == QEvent::ShortcutOverride) { QKeyEvent *ke = static_cast(e); - if (ke->key() == Qt::Key_Escape && _lister->isSearchEnabled()) { - _lister->searchDelete(); - _lister->enableSearch(false); - ke->accept(); - return true; + if (ke->key() == Qt::Key_Escape) { + if (_lister->isSearchEnabled()) { + _lister->searchDelete(); + _lister->enableSearch(false); + ke->accept(); + return true; + } + if (!_lister->textArea()->getSelectedText().isEmpty()) { + _lister->textArea()->clearSelection(); + ke->accept(); + return true; + } } } return false; } ListerBrowserExtension::ListerBrowserExtension(Lister * lister) : KParts::BrowserExtension(lister) { _lister = lister; emit enableAction("copy", true); emit enableAction("print", true); } void ListerBrowserExtension::copy() { _lister->textArea()->copySelectedToClipboard(); } void ListerBrowserExtension::print() { _lister->print(); } class ListerEncodingMenu : public KrRemoteEncodingMenu { public: ListerEncodingMenu(Lister *lister, const QString &text, const QString &icon, KActionCollection *parent) : KrRemoteEncodingMenu(text, icon, parent), _lister(lister) { } protected: virtual QString currentCharacterSet() { return _lister->characterSet(); } virtual void chooseDefault() { _lister->setCharacterSet(QString()); } virtual void chooseEncoding(QString encodingName) { QString charset = KCharsets::charsets()->encodingForName(encodingName); _lister->setCharacterSet(charset); } Lister * _lister; }; Lister::Lister(QWidget *parent) : KParts::ReadOnlyPart(parent), _searchInProgress(false), _cache(0), _active(false), _searchLastFailedPosition(-1), _searchProgressCounter(0), _tempFile(0), _downloading(false) { setXMLFile("krusaderlisterui.rc"); _actionSaveSelected = new QAction(QIcon::fromTheme("document-save"), i18n("Save selection..."), this); connect(_actionSaveSelected, SIGNAL(triggered(bool)), SLOT(saveSelected())); actionCollection()->addAction("save_selected", _actionSaveSelected); _actionSaveAs = new QAction(QIcon::fromTheme("document-save-as"), i18n("Save as..."), this); connect(_actionSaveAs, SIGNAL(triggered(bool)), SLOT(saveAs())); actionCollection()->addAction("save_as", _actionSaveAs); _actionPrint = new QAction(QIcon::fromTheme("document-print"), i18n("Print..."), this); connect(_actionPrint, SIGNAL(triggered(bool)), SLOT(print())); actionCollection()->addAction("print", _actionPrint); actionCollection()->setDefaultShortcut(_actionPrint, Qt::CTRL + Qt::Key_P); _actionSearch = new QAction(QIcon::fromTheme("system-search"), i18n("Search"), this); connect(_actionSearch, SIGNAL(triggered(bool)), SLOT(searchAction())); actionCollection()->addAction("search", _actionSearch); actionCollection()->setDefaultShortcut(_actionSearch, Qt::CTRL + Qt::Key_F); _actionSearchNext = new QAction(QIcon::fromTheme("go-down"), i18n("Search next"), this); connect(_actionSearchNext, SIGNAL(triggered(bool)), SLOT(searchNext())); actionCollection()->addAction("search_next", _actionSearchNext); actionCollection()->setDefaultShortcut(_actionSearchNext, Qt::Key_F3); _actionSearchPrev = new QAction(QIcon::fromTheme("go-up"), i18n("Search previous"), this); connect(_actionSearchPrev, SIGNAL(triggered(bool)), SLOT(searchPrev())); actionCollection()->addAction("search_prev", _actionSearchPrev); actionCollection()->setDefaultShortcut(_actionSearchPrev, Qt::SHIFT + Qt::Key_F3); _actionJumpToPosition = new QAction(QIcon::fromTheme("go-jump"), i18n("Jump to position"), this); connect(_actionJumpToPosition, SIGNAL(triggered(bool)), SLOT(jumpToPosition())); actionCollection()->addAction("jump_to_position", _actionJumpToPosition); actionCollection()->setDefaultShortcut(_actionJumpToPosition, Qt::CTRL + Qt::Key_G); _actionHexMode = new QAction(QIcon::fromTheme("document-preview"), i18n("Hex mode"), this); connect(_actionHexMode, SIGNAL(triggered(bool)), SLOT(toggleHexMode())); actionCollection()->addAction("hex_mode", _actionHexMode); actionCollection()->setDefaultShortcut(_actionHexMode, Qt::CTRL + Qt::Key_H); _actionEncoding = new ListerEncodingMenu(this, i18n("Select charset"), "character-set", actionCollection()); QWidget * widget = new ListerPane(this, parent); widget->setFocusPolicy(Qt::StrongFocus); QGridLayout *grid = new QGridLayout(widget); _textArea = new ListerTextArea(this, widget); _textArea->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); _textArea->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); _textArea->setCursorWidth(2); _textArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _textArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); widget->setFocusProxy(_textArea); grid->addWidget(_textArea, 0, 0); _scrollBar = new QScrollBar(Qt::Vertical, widget); grid->addWidget(_scrollBar, 0, 1); _scrollBar->hide(); QWidget * statusWidget = new QWidget(widget); QHBoxLayout *hbox = new QHBoxLayout(statusWidget); _listerLabel = new QLabel(i18n("Lister:"), statusWidget); hbox->addWidget(_listerLabel); _searchProgressBar = new QProgressBar(statusWidget); _searchProgressBar->setMinimum(0); _searchProgressBar->setMaximum(1000); _searchProgressBar->setValue(0); _searchProgressBar->hide(); hbox->addWidget(_searchProgressBar); _searchStopButton = new QToolButton(statusWidget); _searchStopButton->setIcon(QIcon::fromTheme("process-stop")); _searchStopButton->setToolTip(i18n("Stop search")); _searchStopButton->hide(); connect(_searchStopButton, SIGNAL(clicked()), this, SLOT(searchDelete())); hbox->addWidget(_searchStopButton); _searchLineEdit = new KLineEdit(statusWidget); _searchLineEdit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); _originalBackground = _searchLineEdit->palette().color(QPalette::Base); _originalForeground = _searchLineEdit->palette().color(QPalette::Text); connect(_searchLineEdit, SIGNAL(returnPressed()), this, SLOT(searchNext())); connect(_searchLineEdit, SIGNAL(textChanged(const QString &)), this, SLOT(searchTextChanged())); hbox->addWidget(_searchLineEdit); _searchNextButton = new QPushButton(QIcon::fromTheme("go-down"), i18n("Next"), statusWidget); _searchNextButton->setToolTip(i18n("Jump to next match")); connect(_searchNextButton, SIGNAL(clicked()), this, SLOT(searchNext())); hbox->addWidget(_searchNextButton); _searchPrevButton = new QPushButton(QIcon::fromTheme("go-up"), i18n("Previous"), statusWidget); _searchPrevButton->setToolTip(i18n("Jump to previous match")); connect(_searchPrevButton, SIGNAL(clicked()), this, SLOT(searchPrev())); hbox->addWidget(_searchPrevButton); _searchOptions = new QPushButton(i18n("Options"), statusWidget); _searchOptions->setToolTip(i18n("Modify search behavior")); QMenu * menu = new QMenu(); _fromCursorAction = menu->addAction(i18n("From cursor")); _fromCursorAction->setCheckable(true); _fromCursorAction->setChecked(true); _caseSensitiveAction = menu->addAction(i18n("Case sensitive")); _caseSensitiveAction->setCheckable(true); _matchWholeWordsOnlyAction = menu->addAction(i18n("Match whole words only")); _matchWholeWordsOnlyAction->setCheckable(true); _regExpAction = menu->addAction(i18n("RegExp")); _regExpAction->setCheckable(true); _hexAction = menu->addAction(i18n("Hexadecimal")); _hexAction->setCheckable(true); _searchOptions->setMenu(menu); hbox->addWidget(_searchOptions); QSpacerItem* cbSpacer = new QSpacerItem(20, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hbox->addItem(cbSpacer); _statusLabel = new QLabel(statusWidget); hbox->addWidget(_statusLabel); grid->addWidget(statusWidget, 1, 0, 1, 2); setWidget(widget); connect(_scrollBar, SIGNAL(actionTriggered(int)), _textArea, SLOT(slotActionTriggered(int))); connect(&_updateTimer, SIGNAL(timeout()), this, SLOT(slotUpdate())); _updateTimer.setSingleShot(false); new ListerBrowserExtension(this); enableSearch(false); } Lister::~Lister() { if (_cache != 0) { delete []_cache; _cache = 0; } if (_tempFile != 0) { delete _tempFile; _tempFile = 0; } } bool Lister::openUrl(const QUrl &listerUrl) { _downloading = false; setUrl(listerUrl); if (_tempFile) { delete _tempFile; _tempFile = 0; } _fileSize = 0; if (listerUrl.isLocalFile()) { _filePath = listerUrl.path(); if (!QFile::exists(_filePath)) return false; _fileSize = getFileSize(); } else { _tempFile = new QTemporaryFile(QDir::tempPath() + QLatin1String("/krusader_XXXXXX_") + listerUrl.fileName()); _tempFile->open(); _filePath = _tempFile->fileName(); KIO::Job * downloadJob = KIO::get(listerUrl, KIO::NoReload, KIO::HideProgressInfo); connect(downloadJob, SIGNAL(data(KIO::Job *, const QByteArray &)), this, SLOT(slotFileDataReceived(KIO::Job *, const QByteArray &))); connect(downloadJob, SIGNAL(result(KJob*)), this, SLOT(slotFileFinished(KJob *))); _downloading = false; } if (_cache) { delete []_cache; _cache = 0; } _textArea->reset(); emit started(0); emit setWindowCaption(listerUrl.toDisplayString()); emit completed(); return true; } void Lister::slotFileDataReceived(KIO::Job *, const QByteArray &array) { if (array.size() != 0) _tempFile->write(array); } void Lister::slotFileFinished(KJob *job) { _tempFile->flush(); if (job->error()) { /* any error occurred? */ KIO::TransferJob *kioJob = (KIO::TransferJob *)job; KMessageBox::error(_textArea, i18n("Error reading file %1.", kioJob->url().toDisplayString(QUrl::PreferLocalFile))); } _downloading = false; } char * Lister::cacheRef(qint64 filePos, int &size) { if (filePos >= _fileSize) return 0; if (_fileSize - filePos < size) size = _fileSize - filePos; if ((_cache != 0) && (filePos >= _cachePos) && (filePos + size <= _cachePos + _cacheSize)) return _cache + (filePos - _cachePos); int cacheSize = CACHE_SIZE; int negativeOffset = CACHE_SIZE * 2 / 5; qint64 cachePos = filePos - negativeOffset; if (cachePos < 0) cachePos = 0; QFile myfile(_filePath); if (!myfile.open(QIODevice::ReadOnly)) { myfile.close(); return 0; } if (!myfile.seek(cachePos)) { myfile.close(); return 0; } char * newCache = new char [ cacheSize ]; qint64 bytes = myfile.read(newCache, cacheSize); if (bytes == -1) { delete []newCache; myfile.close(); return 0; } if (_cache) delete []_cache; _cache = newCache; _cacheSize = bytes; _cachePos = cachePos; int newSize = _cacheSize - (filePos - _cachePos); if (newSize < size) size = newSize; return _cache + (filePos - _cachePos); } qint64 Lister::getFileSize() { return QFile(_filePath).size(); } void Lister::guiActivateEvent(KParts::GUIActivateEvent * event) { if (event->activated()) { _active = true; _updateTimer.setInterval(150); _updateTimer.start(); slotUpdate(); _textArea->redrawTextArea(true); } else { enableSearch(false); _active = false; _updateTimer.stop(); } KParts::ReadOnlyPart::guiActivateEvent(event); } void Lister::slotUpdate() { qint64 oldSize = _fileSize; _fileSize = getFileSize(); if (oldSize != _fileSize) _textArea->sizeChanged(); int cursorX = 0, cursorY = 0; _textArea->getCursorPosition(cursorX, cursorY); bool isfirst = false; qint64 cursor = _textArea->getCursorPosition(isfirst); int percent = (_fileSize == 0) ? 0 : (int)((201 * cursor) / _fileSize / 2); QString status = i18n("Column: %1, Position: %2 (%3, %4%)", cursorX, cursor, _fileSize, percent); _statusLabel->setText(status); if (_searchProgressCounter) _searchProgressCounter--; } bool Lister::isSearchEnabled() { return !_searchLineEdit->isHidden() || !_searchProgressBar->isHidden(); } void Lister::enableSearch(bool enable) { if (enable) { _listerLabel->setText(i18n("Search:")); _searchLineEdit->show(); _searchNextButton->show(); _searchPrevButton->show(); _searchOptions->show(); if (!_searchLineEdit->hasFocus()) { _searchLineEdit->setFocus(); QString selection = _textArea->getSelectedText(); if (!selection.isEmpty()) { _searchLineEdit->setText(selection); } _searchLineEdit->selectAll(); } } else { _listerLabel->setText(i18n("Lister:")); _searchLineEdit->hide(); _searchNextButton->hide(); _searchPrevButton->hide(); _searchOptions->hide(); + _textArea->setFocus(); } } void Lister::searchNext() { search(true); } void Lister::searchPrev() { search(false); } void Lister::search(bool forward, bool restart) { _restartFromBeginning = restart; if (_searchInProgress || _searchLineEdit->text().isEmpty()) return; if (_searchLineEdit->isHidden()) enableSearch(true); _searchPosition = forward ? 0 : _fileSize; if (_fromCursorAction->isChecked()) { bool isfirst; qint64 cursor = _textArea->getCursorPosition(isfirst); if (cursor != 0 && !forward) cursor--; if (_searchLastFailedPosition == -1 || _searchLastFailedPosition != cursor) _searchPosition = cursor; } bool caseSensitive = _caseSensitiveAction->isChecked(); bool matchWholeWord = _matchWholeWordsOnlyAction->isChecked(); bool regExp = _regExpAction->isChecked(); bool hex = _hexAction->isChecked(); if (hex) { QString hexcontent = _searchLineEdit->text(); hexcontent.remove(QLatin1String("0x")); hexcontent.remove(' '); hexcontent.remove('\t'); hexcontent.remove('\n'); hexcontent.remove('\r'); _searchHexQuery = QByteArray(); if (hexcontent.length() & 1) { setColor(false, false); return; } while (!hexcontent.isEmpty()) { QString hexData = hexcontent.left(2); hexcontent = hexcontent.mid(2); bool ok = true; int c = hexData.toUInt(&ok, 16); if (!ok) { setColor(false, false); return; } _searchHexQuery.push_back((char) c); } } else { _searchQuery.setContent(_searchLineEdit->text(), caseSensitive, matchWholeWord, false, _textArea->codec()->name(), regExp); } _searchIsForward = forward; _searchHexadecimal = hex; QTimer::singleShot(0, this, SLOT(slotSearchMore())); _searchInProgress = true; _searchProgressCounter = 3; enableActions(false); } void Lister::enableActions(bool state) { _actionSearch->setEnabled(state); _actionSearchNext->setEnabled(state); _actionSearchPrev->setEnabled(state); _actionJumpToPosition->setEnabled(state); } void Lister::slotSearchMore() { if (!_searchInProgress) return; updateProgressBar(); if (!_searchIsForward) _searchPosition--; if (_searchPosition < 0 || _searchPosition >= _fileSize) { if (_restartFromBeginning) resetSearchPosition(); else { searchFailed(); return; } } int maxCacheSize = SEARCH_CACHE_CHARS; qint64 searchPos = _searchPosition; bool setPosition = true; if (!_searchIsForward) { qint64 origSearchPos = _searchPosition; searchPos -= maxCacheSize; if (searchPos <= 0) { searchPos = 0; _searchPosition = 0; setPosition = false; } qint64 diff = origSearchPos - searchPos; if (diff < maxCacheSize) maxCacheSize = diff; } char * cache = cacheRef(searchPos, maxCacheSize); if (cache == 0 || maxCacheSize == 0) { searchFailed(); return; } qint64 foundAnchor = -1; qint64 foundCursor = -1; int cnt = 0; if (_searchHexadecimal) { QByteArray cacheItems(cache, maxCacheSize); int ndx = _searchIsForward ? cacheItems.indexOf(_searchHexQuery) : cacheItems.lastIndexOf(_searchHexQuery); if (maxCacheSize > _searchHexQuery.length()) { if (_searchIsForward) { _searchPosition = searchPos + maxCacheSize; if ((_searchPosition < _fileSize) && (maxCacheSize > _searchHexQuery.length())) _searchPosition -= _searchHexQuery.length(); cnt = _searchPosition - searchPos; } else { if (_searchPosition > 0) _searchPosition += _searchHexQuery.length(); } } if (ndx != -1) { foundAnchor = searchPos + ndx; foundCursor = foundAnchor + _searchHexQuery.length(); } } else { QTextCodec * textCodec = _textArea->codec(); QTextDecoder * decoder = textCodec->makeDecoder(); int rowStart = 0; QString row = ""; while (cnt < maxCacheSize) { QString chr = decoder->toUnicode(cache + (cnt++), 1); if (!chr.isEmpty() || cnt >= maxCacheSize) { if (chr != "\n") row += chr; if (chr == "\n" || row.length() >= SEARCH_MAX_ROW_LEN || cnt >= maxCacheSize) { if (setPosition) { _searchPosition = searchPos + cnt; if (!_searchIsForward) { _searchPosition ++; setPosition = false; } } if (_searchQuery.checkLine(row, !_searchIsForward)) { QByteArray cachedBuffer(cache + rowStart, maxCacheSize - rowStart); QTextStream stream(&cachedBuffer); stream.setCodec(textCodec); stream.read(_searchQuery.matchIndex()); foundAnchor = searchPos + rowStart + stream.pos(); stream.read(_searchQuery.matchLength()); foundCursor = searchPos + rowStart + stream.pos(); if (_searchIsForward) break; } row = ""; rowStart = cnt; } } } delete decoder; } if (foundAnchor != -1 && foundCursor != -1) { _textArea->setAnchorAndCursor(foundAnchor, foundCursor); searchSucceeded(); return; } if (_searchIsForward && searchPos + cnt >= _fileSize) { if (_restartFromBeginning) resetSearchPosition(); else { searchFailed(); return; } } else if (_searchPosition <= 0 || _searchPosition >= _fileSize) { if (_restartFromBeginning) resetSearchPosition(); else { searchFailed(); return; } } QTimer::singleShot(0, this, SLOT(slotSearchMore())); } void Lister::resetSearchPosition() { _restartFromBeginning = false; _searchPosition = _searchIsForward ? 0 : _fileSize - 1; } void Lister::searchSucceeded() { _searchInProgress = false; setColor(true, false); hideProgressBar(); _searchLastFailedPosition = -1; enableActions(true); } void Lister::searchFailed() { _searchInProgress = false; setColor(false, false); hideProgressBar(); bool isfirst; _searchLastFailedPosition = _textArea->getCursorPosition(isfirst); if (!_searchIsForward) _searchLastFailedPosition--; enableActions(true); } void Lister::searchDelete() { _searchInProgress = false; setColor(false, true); hideProgressBar(); _searchLastFailedPosition = -1; enableActions(true); } void Lister::searchTextChanged() { searchDelete(); if (_fileSize < 0x10000) { // autosearch files less than 64k if (!_searchLineEdit->text().isEmpty()) { bool isfirst; qint64 anchor = _textArea->getCursorAnchor(); qint64 cursor = _textArea->getCursorPosition(isfirst); if (cursor > anchor && anchor != -1) { _textArea->setCursorPosition(anchor, true); } search(true, true); } } } void Lister::setColor(bool match, bool restore) { QColor fore, back; if (!restore) { KConfigGroup gc(krConfig, "Colors"); QString foreground, background; QPalette p = QGuiApplication::palette(); if (match) { foreground = "Quicksearch Match Foreground"; background = "Quicksearch Match Background"; fore = Qt::black; back = QColor(192, 255, 192); } else { foreground = "Quicksearch Non-match Foreground"; background = "Quicksearch Non-match Background"; fore = Qt::black; back = QColor(255, 192, 192); } if (gc.readEntry(foreground, QString()) == "KDE default") fore = p.color(QPalette::Active, QPalette::Text); else if (!gc.readEntry(foreground, QString()).isEmpty()) fore = gc.readEntry(foreground, fore); if (gc.readEntry(background, QString()) == "KDE default") back = p.color(QPalette::Active, QPalette::Base); else if (!gc.readEntry(background, QString()).isEmpty()) back = gc.readEntry(background, back); } else { back = _originalBackground; fore = _originalForeground; } QPalette pal = _searchLineEdit->palette(); pal.setColor(QPalette::Base, back); pal.setColor(QPalette::Text, fore); _searchLineEdit->setPalette(pal); } void Lister::hideProgressBar() { if (!_searchProgressBar->isHidden()) { _searchProgressBar->hide(); _searchStopButton->hide(); _searchLineEdit->show(); _searchNextButton->show(); _searchPrevButton->show(); _searchOptions->show(); _listerLabel->setText(i18n("Search:")); } } void Lister::updateProgressBar() { if (_searchProgressCounter) return; if (_searchProgressBar->isHidden()) { _searchProgressBar->show(); _searchStopButton->show(); _searchOptions->hide(); _searchLineEdit->hide(); _searchNextButton->hide(); _searchPrevButton->hide(); _listerLabel->setText(i18n("Search position:")); // otherwise focus is set to document tab _textArea->setFocus(); } qint64 pcnt = (_fileSize == 0) ? 1000 : (2001 * _searchPosition) / _fileSize / 2; int pctInt = (int)pcnt; if (_searchProgressBar->value() != pctInt) _searchProgressBar->setValue(pctInt); } void Lister::jumpToPosition() { bool ok = true; QString res = QInputDialog::getText(_textArea, i18n("Jump to position"), i18n("Text position:"), QLineEdit::Normal, "0", &ok); if (!ok) return; res = res.trimmed(); qint64 pos = -1; if (res.startsWith(QLatin1String("0x"))) { res = res.mid(2); bool ok; qulonglong upos = res.toULongLong(&ok, 16); if (!ok) { KMessageBox::error(_textArea, i18n("Invalid number."), i18n("Jump to position")); return; } pos = (qint64)upos; } else { bool ok; qulonglong upos = res.toULongLong(&ok); if (!ok) { KMessageBox::error(_textArea, i18n("Invalid number."), i18n("Jump to position")); return; } pos = (qint64)upos; } if (pos < 0 || pos > _fileSize) { KMessageBox::error(_textArea, i18n("Number out of range."), i18n("Jump to position")); return; } _textArea->deleteAnchor(); _textArea->setCursorPosition(pos, true); _textArea->ensureVisibleCursor(); } void Lister::saveAs() { QUrl url = QFileDialog::getSaveFileUrl(_textArea, i18n("Lister")); if (url.isEmpty()) return; QUrl sourceUrl; if (!_downloading) sourceUrl = QUrl::fromLocalFile(_filePath); else sourceUrl = this->url(); QList urlList; urlList << sourceUrl; KIO::Job *job = KIO::copy(urlList, url); job->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(job); job->ui()->setAutoErrorHandlingEnabled(true); } void Lister::saveSelected() { bool isfirst; qint64 start = _textArea->getCursorAnchor(); qint64 end = _textArea->getCursorPosition(isfirst); if (start == -1 || start == end) { KMessageBox::error(_textArea, i18n("Nothing is selected."), i18n("Save selection...")); return; } if (start > end) { _savePosition = end; _saveEnd = start; } else { _savePosition = start; _saveEnd = end; } QUrl url = QFileDialog::getSaveFileUrl(_textArea, i18n("Lister")); if (url.isEmpty()) return; KIO::Job *saveJob = KIO::put(url, -1, KIO::Overwrite); connect(saveJob, SIGNAL(dataReq(KIO::Job *, QByteArray &)), this, SLOT(slotDataSend(KIO::Job *, QByteArray &))); connect(saveJob, SIGNAL(result(KJob*)), this, SLOT(slotSendFinished(KJob *))); saveJob->setUiDelegate(new KIO::JobUiDelegate()); KIO::getJobTracker()->registerJob(saveJob); saveJob->ui()->setAutoErrorHandlingEnabled(true); _actionSaveSelected->setEnabled(false); } void Lister::slotDataSend(KIO::Job *, QByteArray &array) { if (_savePosition >= _saveEnd) { array = QByteArray(); return; } qint64 max = _saveEnd - _savePosition; if (max > 1000) max = 1000; int maxBytes = (int)max; char * cache = cacheRef(_savePosition, maxBytes); _savePosition += maxBytes; array = QByteArray(cache, maxBytes); } void Lister::slotSendFinished(KJob *) { _actionSaveSelected->setEnabled(true); } void Lister::setCharacterSet(QString set) { _characterSet = set; _textArea->redrawTextArea(true); } void Lister::print() { bool isfirst; qint64 anchor = _textArea->getCursorAnchor(); qint64 cursor = _textArea->getCursorPosition(isfirst); bool hasSelection = (anchor != -1 && anchor != cursor); QString docName = url().fileName(); QPrinter printer; printer.setDocName(docName); QPointer printDialog = new QPrintDialog(&printer, _textArea); if (hasSelection) printDialog->addEnabledOption(QAbstractPrintDialog::PrintSelection); if (printDialog->exec()) { if (printer.pageOrder() == QPrinter::LastPageFirst) { switch (KMessageBox::warningContinueCancel(_textArea, i18n("Reverse printing is not supported. Continue with normal printing?"))) { case KMessageBox::Continue : break; default: return; } } QPainter painter; painter.begin(&printer); QDate date = QDate::currentDate(); QString dateString = date.toString(Qt::SystemLocaleShortDate); QRect pageRect = printer.pageRect(); QRect drawingRect(0, 0, pageRect.width(), pageRect.height()); QFont normalFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont); QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); QFontMetrics fmNormal(normalFont); int normalFontHeight = fmNormal.height(); QFontMetrics fmFixed(fixedFont); int fixedFontHeight = fmFixed.height(); int fixedFontWidth = fmFixed.width("W"); if (fixedFontHeight <= 0) fixedFontHeight = 1; if (fixedFontWidth <= 0) fixedFontWidth = 1; int effPageSize = drawingRect.height() - normalFontHeight - 1; int rowsPerPage = effPageSize / fixedFontHeight; if (rowsPerPage <= 0) rowsPerPage = 1; int columnsPerPage = drawingRect.width() / fixedFontWidth; if (columnsPerPage <= 0) columnsPerPage = 1; bool firstPage = true; qint64 startPos = 0; qint64 endPos = _fileSize; if (printer.printRange() == QPrinter::Selection) { if (anchor > cursor) startPos = cursor, endPos = anchor; else startPos = anchor, endPos = cursor; } for (int page = 1;; ++page) { QStringList rows = readLines(startPos, endPos, columnsPerPage, rowsPerPage); bool visible = true; if (printer.fromPage() && page < printer.fromPage()) visible = false; if (printer.toPage() && printer.toPage() >= printer.fromPage() && page > printer.toPage()) visible = false; if (visible) { if (!firstPage) printer.newPage(); firstPage = false; // Use the painter to draw on the page. painter.setFont(normalFont); painter.drawText(drawingRect, Qt::AlignLeft, dateString); painter.drawText(drawingRect, Qt::AlignHCenter, docName); painter.drawText(drawingRect, Qt::AlignRight, QString("%1").arg(page)); painter.drawLine(0, normalFontHeight, drawingRect.width(), normalFontHeight); painter.setFont(fixedFont); int yoffset = normalFontHeight + 1; foreach(const QString &row, rows) { painter.drawText(0, yoffset + fixedFontHeight, row); yoffset += fixedFontHeight; } } if (startPos >= endPos) break; } } delete printDialog; } QStringList Lister::readLines(qint64 &filePos, qint64 endPos, int columns, int lines) { if (_textArea->hexMode()) return readHexLines(filePos, endPos, columns, lines); QStringList list; int maxBytes = columns * lines * MAX_CHAR_LENGTH; if (maxBytes > (endPos - filePos)) maxBytes = (int)(endPos - filePos); if (maxBytes <= 0) return list; char * cache = cacheRef(filePos, maxBytes); if (cache == 0 || maxBytes == 0) return list; QTextCodec * textCodec = _textArea->codec(); QTextDecoder * decoder = textCodec->makeDecoder(); int cnt = 0; int y = 0; QString row = ""; bool isLastLongLine = false; while (cnt < maxBytes && y < lines) { QString chr = decoder->toUnicode(cache + (cnt++), 1); if (!chr.isEmpty()) { if ((chr[ 0 ] < 32) && (chr[ 0 ] != '\n') && (chr[ 0 ] != '\t')) chr = QChar(' '); if (chr == "\n") { if (!isLastLongLine) { list << row; row = ""; y++; } isLastLongLine = false; } else { isLastLongLine = false; if (chr == "\t") { int tabLength = _textArea->tabWidth() - (row.length() % _textArea->tabWidth()); if (row.length() + tabLength > columns) { list << row; row = ""; y++; } row += QString(tabLength, QChar(' ')); } else row += chr; if (row.length() >= columns) { list << row; row = ""; y++; isLastLongLine = true; } } } } if (y < lines) list << row; filePos += cnt; delete decoder; return list; } int Lister::hexPositionDigits() { int positionDigits = 0; qint64 checker = _fileSize; while (checker) { positionDigits++; checker /= 16; } if (positionDigits < 8) positionDigits = 8; return positionDigits; } int Lister::hexBytesPerLine(int columns) { int positionDigits = hexPositionDigits(); int bytesPerRow = 8; if (columns >= positionDigits + 5 + 64) bytesPerRow = 16; if (columns >= positionDigits + 5 + 128) bytesPerRow = 32; return bytesPerRow; } QStringList Lister::readHexLines(qint64 &filePos, qint64 endPos, int columns, int lines) { int positionDigits = hexPositionDigits(); int bytesPerRow = hexBytesPerLine(columns); QStringList list; qint64 choppedPos = (filePos / bytesPerRow) * bytesPerRow; int maxBytes = bytesPerRow * lines; if (maxBytes > (endPos - choppedPos)) maxBytes = (int)(endPos - choppedPos); if (maxBytes <= 0) return list; char * cache = cacheRef(choppedPos, maxBytes); if (cache == 0 || maxBytes == 0) return list; int cnt = 0; for (int l = 0; l < lines; l++) { if (filePos >= endPos) break; qint64 printPos = (filePos / bytesPerRow) * bytesPerRow; QString pos; pos.setNum(printPos, 16); while (pos.length() < positionDigits) pos = QString("0") + pos; pos = QString("0x") + pos; pos += QString(": "); QString charData; for (int i = 0; i != bytesPerRow; ++i, ++cnt) { qint64 currentPos = printPos + i; if (currentPos < filePos || currentPos >= endPos) { pos += QString(" "); charData += QString(" "); } else { char c = cache[ cnt ]; int charCode = (int)c; if (charCode < 0) charCode += 256; QString hex; hex.setNum(charCode, 16); if (hex.length() < 2) hex = QString("0") + hex; pos += hex + QString(" "); if (c < 32) c = '.'; charData += QChar(c); } } pos += QString(" ") + charData; list << pos; filePos = printPos + bytesPerRow; } if (filePos > endPos) filePos = endPos; return list; } int Lister::hexIndexToPosition(int columns, int index) { int positionDigits = hexPositionDigits(); int bytesPerRow = hexBytesPerLine(columns); if (index >= bytesPerRow) index = bytesPerRow; return positionDigits + 4 + (3*index); } int Lister::hexPositionToIndex(int columns, int position) { int positionDigits = hexPositionDigits(); int bytesPerRow = hexBytesPerLine(columns); position -= 4 + positionDigits; if (position <= 0) return 0; position /= 3; if (position >= bytesPerRow) return bytesPerRow; return position; } void Lister::toggleHexMode() { setHexMode(!_textArea->hexMode()); } void Lister::setHexMode(bool mode) { if (mode) { _textArea->setHexMode(true); _actionHexMode->setText(i18n("Text mode")); } else { _textArea->setHexMode(false); _actionHexMode->setText(i18n("Hex mode")); } } diff --git a/krusader/KViewer/lister.h b/krusader/KViewer/lister.h index 6475498d..e7c1d34c 100644 --- a/krusader/KViewer/lister.h +++ b/krusader/KViewer/lister.h @@ -1,329 +1,330 @@ /*************************************************************************** lister.h - description ------------------- copyright : (C) 2009 + by Csaba Karai e-mail : krusader@users.sourceforge.net web site : http://krusader.sourceforge.net --------------------------------------------------------------------------- Description *************************************************************************** A db dD d8888b. db db .d8888. .d8b. d8888b. d88888b d8888b. 88 ,8P' 88 `8D 88 88 88' YP d8' `8b 88 `8D 88' 88 `8D 88,8P 88oobY' 88 88 `8bo. 88ooo88 88 88 88ooooo 88oobY' 88`8b 88`8b 88 88 `Y8b. 88~~~88 88 88 88~~~~~ 88`8b 88 `88. 88 `88. 88b d88 db 8D 88 88 88 .8D 88. 88 `88. YP YD 88 YD ~Y8888P' `8888Y' YP YP Y8888D' Y88888P 88 YD H e a d e r F i l e *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef LISTER_H #define LISTER_H // QtCore #include #include // QtGui #include // QtWidgets #include #include #include #include #include #include #include "../VFS/krquery.h" #define SLIDER_MAX 10000 #define MAX_CHAR_LENGTH 4 class Lister; class QLabel; class QProgressBar; class QPushButton; class QToolButton; class QAction; class QTemporaryFile; class ListerEncodingMenu; class ListerTextArea : public KTextEdit { Q_OBJECT public: ListerTextArea(Lister *lister, QWidget *parent); void reset(); void calculateText(bool forcedUpdate = false); void redrawTextArea(bool forcedUpdate = false); qint64 textToFilePosition(int x, int y, bool &isfirst); void fileToTextPosition(qint64 p, bool isfirst, int &x, int &y); QTextCodec * codec(); int tabWidth() { return _tabWidth; } bool hexMode() { return _hexMode; } void setHexMode(bool hexMode); void copySelectedToClipboard(); QString getSelectedText(); + void clearSelection(); void getCursorPosition(int &x, int &y); qint64 getCursorPosition(bool &isfirst); qint64 getCursorAnchor() { return _cursorAnchorPos; } void setCursorPosition(qint64 p, bool isfirst); void ensureVisibleCursor(); void deleteAnchor() { _cursorAnchorPos = -1; } void setAnchorAndCursor(qint64 anchor, qint64 cursor); void sizeChanged(); protected: virtual void resizeEvent(QResizeEvent * event) Q_DECL_OVERRIDE; virtual void keyPressEvent(QKeyEvent * e) Q_DECL_OVERRIDE; virtual void mousePressEvent(QMouseEvent * e) Q_DECL_OVERRIDE; virtual void mouseDoubleClickEvent(QMouseEvent * e) Q_DECL_OVERRIDE; virtual void mouseMoveEvent(QMouseEvent * e) Q_DECL_OVERRIDE; virtual void wheelEvent(QWheelEvent * event) Q_DECL_OVERRIDE; QStringList readLines(qint64 filePos, qint64 &endPos, int lines, QList * locs = 0); QString readSection(qint64 p1, qint64 p2); void setUpScrollBar(); void setCursorPosition(int x, int y, int anchorX = -1, int anchorY = -1); void handleAnchorChange(int oldAnchor); void performAnchorChange(int anchor); void getScreenPosition(int position, int &x, int &y); protected slots: void slotActionTriggered(int action); void slotCursorPositionChanged(); void zoomIn(int range = 1); void zoomOut(int range = 1); protected: Lister *_lister; qint64 _screenStartPos; qint64 _screenEndPos; qint64 _averagePageSize; qint64 _lastPageStartPos; int _sizeX; int _sizeY; int _pageSize; int _tabWidth; bool _sizeChanged; QStringList _rowContent; QList _rowStarts; qint64 _cursorPos; bool _cursorAtFirstColumn; qint64 _cursorAnchorPos; int _skippedLines; bool _inSliderOp; bool _inCursorUpdate; bool _hexMode; }; class ListerBrowserExtension : public KParts::BrowserExtension { Q_OBJECT public: ListerBrowserExtension(Lister * lister); public slots: void copy(); void print(); protected: Lister *_lister; }; class ListerPane : public QWidget { Q_OBJECT public: ListerPane(Lister *lister, QWidget *parent); protected: virtual bool event(QEvent *event) Q_DECL_OVERRIDE; protected: bool handleCloseEvent(QEvent *e); Lister *_lister; }; class Lister : public KParts::ReadOnlyPart { Q_OBJECT public: Lister(QWidget *parent); ~Lister(); QScrollBar *scrollBar() { return _scrollBar; } ListerTextArea *textArea() { return _textArea; } inline qint64 fileSize() { return _fileSize; } char * cacheRef(qint64 filePos, int &size); bool isSearchEnabled(); void enableSearch(bool); void enableActions(bool); void searchDelete(); QString characterSet() { return _characterSet; } void setCharacterSet(QString set); void setHexMode(bool); QStringList readHexLines(qint64 &filePos, qint64 endPos, int columns, int lines); int hexBytesPerLine(int columns); int hexPositionDigits(); int hexIndexToPosition(int columns, int index); int hexPositionToIndex(int columns, int position); public slots: void searchAction() { enableSearch(true); } void searchNext(); void searchPrev(); void jumpToPosition(); void saveAs(); void saveSelected(); void print(); void toggleHexMode(); protected slots: void slotUpdate(); void slotSearchMore(); void searchSucceeded(); void searchFailed(); void searchTextChanged(); void slotFileDataReceived(KIO::Job *, const QByteArray &); void slotFileFinished(KJob *); void slotDataSend(KIO::Job *, QByteArray &); void slotSendFinished(KJob *); protected: virtual bool openUrl(const QUrl &url) Q_DECL_OVERRIDE; virtual bool closeUrl() Q_DECL_OVERRIDE { return true; } virtual bool openFile() Q_DECL_OVERRIDE { return true; } virtual void guiActivateEvent(KParts::GUIActivateEvent * event) Q_DECL_OVERRIDE; void setColor(bool match, bool restore); void hideProgressBar(); void updateProgressBar(); void resetSearchPosition(); qint64 getFileSize(); void search(bool forward, bool restart = false); QStringList readLines(qint64 &filePos, qint64 endPos, int columns, int lines); QTimer _updateTimer; ListerTextArea *_textArea; QScrollBar *_scrollBar; QLabel *_listerLabel; KLineEdit *_searchLineEdit; QProgressBar *_searchProgressBar; QToolButton *_searchStopButton; QPushButton *_searchNextButton; QPushButton *_searchPrevButton; bool _searchInProgress; bool _searchHexadecimal; QPushButton *_searchOptions; QLabel *_statusLabel; QAction *_fromCursorAction; QAction *_caseSensitiveAction; QAction *_matchWholeWordsOnlyAction; QAction *_regExpAction; QAction *_hexAction; QAction *_actionSaveSelected; QAction *_actionSaveAs; QAction *_actionPrint; QAction *_actionSearch; QAction *_actionSearchNext; QAction *_actionSearchPrev; QAction *_actionJumpToPosition; QAction *_actionHexMode; ListerEncodingMenu *_actionEncoding; QString _filePath; qint64 _fileSize; char *_cache; int _cacheSize; qint64 _cachePos; bool _active; KRQuery _searchQuery; QByteArray _searchHexQuery; qint64 _searchPosition; bool _searchIsForward; qint64 _searchLastFailedPosition; int _searchProgressCounter; QColor _originalBackground; QColor _originalForeground; QString _characterSet; QTemporaryFile *_tempFile; bool _downloading; bool _restartFromBeginning; qint64 _savePosition; qint64 _saveEnd; }; #endif // __LISTER_H__