mirror of
https://github.com/KDE/konsole.git
synced 2025-12-23 23:38:08 -05:00
Added autosave functionality
The autosave functionality is contained in a SaveHistoryAutoTask class, which inherits from SessionTask. The autosave mechanism hinges on two pieces of information regarding the autosave file: the number of bytes used to store the contents of dropped lines (represented by SaveHistoryAutoTask::_droppedBytes) and a list of byte offsets corresponding to the start of the contents of lines on the screen (represented by SaveHistoryAutoTask::_bytesLines). Everytime a line is dropped, SaveHistoryAutoTask::_droppedBytes is updated using _bytesLines. Everytime the output is read and saved to file, the autosave file is resized to _droppedBytes and the current screen output is appended. Everytime the output is read or the screen is resized, _bytesLines is updated. The autosave can be started using an "Auto Save Ouput As" button in the "File" tab of the toolbar. Once the autosave is started, said button is replaced by a "Stop Auto Save" button which allows the user to stop the autosave. Internally, any errors encountered by the program would result in an KMessageBox reporting the details of the error and stopping the autosave as well. Clicking on the stop button reveals the start button again. Similar to SaveHistoryTask, there is a file dialog which allows the user to choose which file they would like the autosave contents to be stored in. Apart from errors involving reading session output and writing to file, modifying the file externally, renaming the file and deleting the file will also result in an error. Emulation::_currentScreen being changed will also result in an error. The autosave is conducted at a fixed time interval, apart from an edge case where an autosave is required immediately due to internal constraints. Said fixed time interval can be set by the user in the settings of their current profile, specifically the "Advanced" tab. Details on the internals of the autosave functionality is documented in the source files. GUI FEATURE: 208620
This commit is contained in:
committed by
Kurt Hindenburg
parent
3573cfef06
commit
ae222d8eb5
@@ -5,6 +5,8 @@
|
||||
<MenuBar>
|
||||
<Menu name="file">
|
||||
<Action name="file_save_as" group="session-operations"/>
|
||||
<Action name="file-autosave" group="session-operations"/>
|
||||
<Action name="stop-autosave" group="session-operations"/>
|
||||
<Separator group="session-operations"/>
|
||||
<Action name="file_print" group="session-operations"/>
|
||||
<Separator group="session-operations"/>
|
||||
|
||||
@@ -158,6 +158,7 @@ set(konsoleprivate_SRCS ${windowadaptors_SRCS}
|
||||
RenameTabDialog.cpp
|
||||
SSHProcessInfo.cpp
|
||||
SaveHistoryTask.cpp
|
||||
SaveHistoryAutoTask.cpp
|
||||
Screen.cpp
|
||||
ScreenWindow.cpp
|
||||
ScrollState.cpp
|
||||
|
||||
@@ -129,6 +129,10 @@ void Emulation::setScreenInternal(int index)
|
||||
|
||||
void Emulation::clearHistory()
|
||||
{
|
||||
if (_currentScreen == _screen[0]) {
|
||||
Q_EMIT updateDroppedLines(_screen[0]->getHistLines());
|
||||
}
|
||||
|
||||
_screen[0]->setScroll(_screen[0]->getScroll(), false);
|
||||
}
|
||||
|
||||
@@ -269,6 +273,7 @@ void Emulation::showBulk()
|
||||
_bulkTimer1.stop();
|
||||
_bulkTimer2.stop();
|
||||
|
||||
Q_EMIT updateDroppedLines(_currentScreen->fastDroppedLines() + _currentScreen->droppedLines());
|
||||
Q_EMIT outputChanged();
|
||||
|
||||
_currentScreen->resetScrolledLines();
|
||||
@@ -331,4 +336,9 @@ QSize Emulation::imageSize() const
|
||||
return {_currentScreen->getColumns(), _currentScreen->getLines()};
|
||||
}
|
||||
|
||||
QList<int> Emulation::getCurrentScreenCharacterCounts() const
|
||||
{
|
||||
return _currentScreen->getCharacterCounts();
|
||||
}
|
||||
|
||||
#include "moc_Emulation.cpp"
|
||||
|
||||
@@ -194,6 +194,8 @@ public:
|
||||
|
||||
bool programBracketedPasteMode() const;
|
||||
|
||||
QList<int> getCurrentScreenCharacterCounts() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
|
||||
/** Change the size of the emulation's image */
|
||||
@@ -407,6 +409,13 @@ Q_SIGNALS:
|
||||
|
||||
void toggleUrlExtractionRequest();
|
||||
|
||||
/**
|
||||
* Mainly used to communicate dropped lines to active autosave tasks.
|
||||
* Takes into account lines dropped by Screen::addHistLine and Screen::fastAddHistLine.
|
||||
* Also includes lines dropped by clearing scrollback and resetting the screen.
|
||||
*/
|
||||
void updateDroppedLines(int droppedLines);
|
||||
|
||||
protected:
|
||||
virtual void setMode(int mode) = 0;
|
||||
virtual void resetMode(int mode) = 0;
|
||||
|
||||
259
src/SaveHistoryAutoTask.cpp
Normal file
259
src/SaveHistoryAutoTask.cpp
Normal file
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2024 Theodore Wang <theodorewang12@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "SaveHistoryAutoTask.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileDialog>
|
||||
#include <QLockFile>
|
||||
#include <QTextStream>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <KConfig>
|
||||
#include <KConfigGroup>
|
||||
#include <KLocalizedString>
|
||||
#include <KMessageBox>
|
||||
#include <KSharedConfig>
|
||||
|
||||
#include "Emulation.h"
|
||||
#include "session/SessionManager.h"
|
||||
|
||||
namespace Konsole
|
||||
{
|
||||
QString SaveHistoryAutoTask::_saveDialogRecentURL;
|
||||
|
||||
SaveHistoryAutoTask::SaveHistoryAutoTask(QObject *parent)
|
||||
: SessionTask(parent)
|
||||
, _droppedBytes(0)
|
||||
, _bytesLines(0)
|
||||
, _pendingChanges(false)
|
||||
{
|
||||
}
|
||||
|
||||
SaveHistoryAutoTask::~SaveHistoryAutoTask() = default;
|
||||
|
||||
void SaveHistoryAutoTask::execute()
|
||||
{
|
||||
QFileDialog *dialog = new QFileDialog(QApplication::activeWindow());
|
||||
dialog->setAcceptMode(QFileDialog::AcceptSave);
|
||||
|
||||
QStringList mimeTypes{QStringLiteral("text/plain")};
|
||||
dialog->setMimeTypeFilters(mimeTypes);
|
||||
|
||||
KSharedConfigPtr konsoleConfig = KSharedConfig::openConfig();
|
||||
auto group = konsoleConfig->group(QStringLiteral("SaveHistory Settings"));
|
||||
|
||||
if (_saveDialogRecentURL.isEmpty()) {
|
||||
const auto list = group.readPathEntry("Recent URLs", QStringList());
|
||||
if (list.isEmpty()) {
|
||||
dialog->setDirectory(QDir::homePath());
|
||||
} else {
|
||||
dialog->setDirectoryUrl(QUrl(list.at(0)));
|
||||
}
|
||||
} else {
|
||||
dialog->setDirectoryUrl(QUrl(_saveDialogRecentURL));
|
||||
}
|
||||
|
||||
Q_ASSERT(sessions().size() == 1);
|
||||
|
||||
dialog->setWindowTitle(i18n("Save Output From %1", session()->title(Session::NameRole)));
|
||||
|
||||
int result = dialog->exec();
|
||||
|
||||
if (result != QDialog::Accepted) {
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl url = (dialog->selectedUrls()).at(0);
|
||||
|
||||
if (!url.isValid()) {
|
||||
// UI: Can we make this friendlier?
|
||||
KMessageBox::error(nullptr, i18n("%1 is an invalid URL, the output could not be saved.", url.url()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Save selected URL for next time
|
||||
_saveDialogRecentURL = url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toString();
|
||||
group.writePathEntry("Recent URLs", _saveDialogRecentURL);
|
||||
|
||||
const QString path = url.path();
|
||||
|
||||
_destinationFile.setFileName(path);
|
||||
|
||||
if (!_destinationFile.open(QFile::ReadWrite)) {
|
||||
KMessageBox::error(nullptr, i18n("Failed to create autosave file at %1.", url.url()));
|
||||
return;
|
||||
}
|
||||
|
||||
_watcher.addPath(path);
|
||||
|
||||
connect(&_watcher, &QFileSystemWatcher::fileChanged, this, &SaveHistoryAutoTask::fileModified);
|
||||
connect(&_timer, &QTimer::timeout, this, &SaveHistoryAutoTask::linesChanged);
|
||||
connect(session()->emulation(), &Emulation::outputChanged, this, [&]() {
|
||||
_pendingChanges = true;
|
||||
});
|
||||
connect(session()->emulation(), &Emulation::imageSizeChanged, this, &SaveHistoryAutoTask::imageResized);
|
||||
connect(session()->emulation(), &Emulation::updateDroppedLines, this, &SaveHistoryAutoTask::linesDropped);
|
||||
connect(session()->emulation(), &Emulation::primaryScreenInUse, this, [&]() {
|
||||
KMessageBox::error(nullptr, i18n("Stopping autosave due to switching of screens."));
|
||||
stop();
|
||||
});
|
||||
|
||||
_timer.setSingleShot(true);
|
||||
|
||||
readLines();
|
||||
dialog->deleteLater();
|
||||
}
|
||||
|
||||
void SaveHistoryAutoTask::fileModified()
|
||||
{
|
||||
stop();
|
||||
KMessageBox::error(nullptr, i18n("Autosave file has been modified externally, preventing further autosaves."));
|
||||
}
|
||||
|
||||
void SaveHistoryAutoTask::linesDropped(int linesDropped)
|
||||
{
|
||||
if (linesDropped > 0) {
|
||||
if (linesDropped > _bytesLines.count()) {
|
||||
readLines();
|
||||
}
|
||||
|
||||
if (linesDropped == _bytesLines.size()) {
|
||||
_droppedBytes = _destinationFile.size();
|
||||
} else {
|
||||
_droppedBytes = _bytesLines[linesDropped];
|
||||
}
|
||||
|
||||
for (int i = 0; i < linesDropped; ++i) {
|
||||
_bytesLines.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SaveHistoryAutoTask::stop()
|
||||
{
|
||||
Q_EMIT completed(true);
|
||||
disconnect();
|
||||
|
||||
if (autoDelete()) {
|
||||
deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void SaveHistoryAutoTask::imageResized(int /*rows*/, int /*columns*/)
|
||||
{
|
||||
updateByteLineAnchors();
|
||||
}
|
||||
|
||||
void SaveHistoryAutoTask::linesChanged()
|
||||
{
|
||||
if (_pendingChanges) {
|
||||
readLines();
|
||||
} else {
|
||||
_timer.start(timerInterval());
|
||||
}
|
||||
}
|
||||
|
||||
void SaveHistoryAutoTask::readLines()
|
||||
{
|
||||
if (session().isNull()) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
_timer.stop();
|
||||
|
||||
if (!updateArchive()) {
|
||||
stop();
|
||||
KMessageBox::error(nullptr, i18n("Failed to update autosave state on output changes."));
|
||||
return;
|
||||
}
|
||||
|
||||
updateByteLineAnchors();
|
||||
|
||||
_pendingChanges = false;
|
||||
_timer.start(timerInterval());
|
||||
}
|
||||
|
||||
bool SaveHistoryAutoTask::updateArchive()
|
||||
{
|
||||
_watcher.removePath(_destinationFile.fileName());
|
||||
|
||||
QTextStream stream(&_destinationFile);
|
||||
|
||||
if (!_destinationFile.resize(_droppedBytes) || !stream.seek(_destinationFile.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_decoder.begin(&stream);
|
||||
session()->emulation()->writeToStream(&_decoder, 0, session()->emulation()->lineCount() - 1);
|
||||
_decoder.end();
|
||||
stream.flush();
|
||||
|
||||
if (stream.status() != QTextStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_watcher.addPath(_destinationFile.fileName());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SaveHistoryAutoTask::updateByteLineAnchors()
|
||||
{
|
||||
if (session().isNull()) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
qint64 currentByte = _droppedBytes;
|
||||
QList<int> lineLengths = session()->emulation()->getCurrentScreenCharacterCounts();
|
||||
|
||||
_bytesLines.resize(lineLengths.size());
|
||||
_destinationFile.seek(currentByte);
|
||||
|
||||
for (int i = 0; i < lineLengths.size(); ++i) {
|
||||
_bytesLines[i] = currentByte;
|
||||
QString line;
|
||||
|
||||
int lineNumChars = lineLengths[i];
|
||||
|
||||
for (int j = 0; j < lineNumChars; ++j) {
|
||||
char c;
|
||||
|
||||
if (!_destinationFile.getChar(&c)) {
|
||||
qDebug() << "LINE " << (i + 1) << " (" << lineNumChars << ") : " << line;
|
||||
qDebug() << "INVALID";
|
||||
return;
|
||||
}
|
||||
|
||||
line.append(QLatin1Char(c));
|
||||
}
|
||||
|
||||
currentByte = _destinationFile.pos();
|
||||
qDebug() << "LINE " << (i + 1) << " (" << lineNumChars << ") : " << line;
|
||||
}
|
||||
|
||||
if (_destinationFile.atEnd())
|
||||
qDebug() << "VALID";
|
||||
else
|
||||
qDebug() << "INVALID";
|
||||
}
|
||||
|
||||
const QPointer<Session> &SaveHistoryAutoTask::session() const
|
||||
{
|
||||
return sessions()[0];
|
||||
}
|
||||
|
||||
int SaveHistoryAutoTask::timerInterval() const
|
||||
{
|
||||
return SessionManager::instance()->sessionProfile(session().data())->property<int>(Profile::AutoSaveInterval);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#include "moc_SaveHistoryAutoTask.cpp"
|
||||
119
src/SaveHistoryAutoTask.h
Normal file
119
src/SaveHistoryAutoTask.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2024 Theodore Wang <theodorewang12@gmail.com>
|
||||
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef SAVEHISTORYAUTOTASK_H
|
||||
#define SAVEHISTORYAUTOTASK_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileSystemWatcher>
|
||||
#include <QTimer>
|
||||
|
||||
#include "../decoders/PlainTextDecoder.h"
|
||||
#include "konsoleprivate_export.h"
|
||||
#include "session/SessionTask.h"
|
||||
|
||||
namespace Konsole
|
||||
{
|
||||
/**
|
||||
* A task which prompts for a URL for each session and saves that session's output
|
||||
* to the given URL
|
||||
*/
|
||||
class KONSOLEPRIVATE_EXPORT SaveHistoryAutoTask : public SessionTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/** Constructs a new task to save session output to URLs */
|
||||
explicit SaveHistoryAutoTask(QObject *parent = nullptr);
|
||||
~SaveHistoryAutoTask() override;
|
||||
|
||||
/**
|
||||
* Opens a save file dialog for each session in the group and begins saving
|
||||
* each session's history to the given URL.
|
||||
*
|
||||
* The data transfer is performed asynchronously and will continue after execute() returns.
|
||||
*/
|
||||
void execute() override;
|
||||
|
||||
public Q_SLOTS:
|
||||
// Stops the autosave process.
|
||||
void stop();
|
||||
|
||||
private Q_SLOTS:
|
||||
/**
|
||||
* Increases _droppedBytes using info in _bytesLines when lines
|
||||
* have been dropped from the screen and history.
|
||||
*/
|
||||
void linesDropped(int linesDropped);
|
||||
|
||||
/**
|
||||
* Resets _bytesLines since the lines on the screen changes when
|
||||
* the screen is resized.
|
||||
*/
|
||||
void imageResized(int rows, int columns);
|
||||
|
||||
// Called when Emulation::outputChanged() is emitted.
|
||||
void linesChanged();
|
||||
|
||||
/**
|
||||
* If the QFileSystemWatcher::fileChanged() signal is emitted,
|
||||
* this is called which terminates the autosave process with an error.
|
||||
*/
|
||||
void fileModified();
|
||||
|
||||
private:
|
||||
// Reads the session output.
|
||||
void readLines();
|
||||
|
||||
bool updateArchive();
|
||||
|
||||
// Updates _bytesLines.
|
||||
void updateByteLineAnchors();
|
||||
|
||||
const QPointer<Session> &session() const;
|
||||
|
||||
/**
|
||||
* The number of milliseconds to wait after an autosave before
|
||||
* checking if another is needed.
|
||||
*/
|
||||
int timerInterval() const;
|
||||
|
||||
// File object used to store the autosaved contents.
|
||||
QFile _destinationFile;
|
||||
|
||||
/**
|
||||
* Since the autosave process relies on the files not being modified
|
||||
* externally, this keeps watch on the autosave destination file.
|
||||
*/
|
||||
QFileSystemWatcher _watcher;
|
||||
|
||||
// Number of bytes used to accomodate dropped content in the autosave file.
|
||||
qint64 _droppedBytes;
|
||||
|
||||
/**
|
||||
* A list of byte offsets in _destinationFile.
|
||||
* Each offset corresponds to the first of a series of bytes
|
||||
* containing content of a line on the emulation's current screen and history.
|
||||
*/
|
||||
QList<qint64> _bytesLines;
|
||||
|
||||
PlainTextDecoder _decoder;
|
||||
|
||||
// Used to time how often the output should be re-read.
|
||||
QTimer _timer;
|
||||
|
||||
/**
|
||||
* Determines whether the output is re-read at the end
|
||||
* of the current timer internal.
|
||||
*/
|
||||
bool _pendingChanges;
|
||||
|
||||
static QString _saveDialogRecentURL;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -62,6 +62,7 @@ Screen::Screen(int lines, int columns)
|
||||
, _scrolledLines(0)
|
||||
, _lastScrolledRegion(QRect())
|
||||
, _droppedLines(0)
|
||||
, _fastDroppedLines(0)
|
||||
, _oldTotalLines(0)
|
||||
, _isResize(false)
|
||||
, _enableReflowLines(false)
|
||||
@@ -1338,9 +1339,14 @@ int Screen::droppedLines() const
|
||||
{
|
||||
return _droppedLines;
|
||||
}
|
||||
int Screen::fastDroppedLines() const
|
||||
{
|
||||
return _fastDroppedLines;
|
||||
}
|
||||
void Screen::resetDroppedLines()
|
||||
{
|
||||
_droppedLines = 0;
|
||||
_fastDroppedLines = 0;
|
||||
}
|
||||
void Screen::resetScrolledLines()
|
||||
{
|
||||
@@ -2090,6 +2096,78 @@ Character *Screen::getCharacterBuffer(const int size)
|
||||
return characterBuffer.data();
|
||||
}
|
||||
|
||||
QList<int> Screen::getCharacterCounts() const
|
||||
{
|
||||
QList<int> counts;
|
||||
int totalLines = _history->getLines() + getLines();
|
||||
|
||||
for (int line = 0; line < totalLines; ++line) {
|
||||
int count = getLineLength(line);
|
||||
bool lineIsWrapped = false;
|
||||
Character *characterBuffer = getCharacterBuffer(count - 1);
|
||||
|
||||
Q_ASSERT(count >= 0);
|
||||
|
||||
if (line < _history->getLines()) {
|
||||
// safety checks
|
||||
Q_ASSERT(count <= _history->getLineLen(line));
|
||||
|
||||
_history->getCells(line, 0, count, characterBuffer);
|
||||
|
||||
// Exclude trailing empty cells from count and don't bother processing them further.
|
||||
// See the comment on the similar case for screen lines for an explanation.
|
||||
while (count > 0 && (characterBuffer[count - 1].flags & EF_REAL) == 0) {
|
||||
count--;
|
||||
}
|
||||
|
||||
if (_history->isWrappedLine(line)) {
|
||||
lineIsWrapped = true;
|
||||
}
|
||||
} else {
|
||||
int screenLine = line - _history->getLines();
|
||||
|
||||
Q_ASSERT(screenLine <= _screenLinesSize);
|
||||
|
||||
screenLine = qMin(screenLine, _screenLinesSize);
|
||||
|
||||
auto *data = _screenLines[screenLine].data();
|
||||
int length = _screenLines.at(screenLine).count();
|
||||
|
||||
// Exclude trailing empty cells from count and don't bother processing them further.
|
||||
// This is necessary because a newline gets added to the last line when
|
||||
// the selection extends beyond the last character (last non-whitespace
|
||||
// character when TrimTrailingWhitespace is true), so the returned
|
||||
// count from this function must not include empty cells beyond that
|
||||
// last character.
|
||||
while (length > 0 && (data[length - 1].flags & EF_REAL) == 0) {
|
||||
length--;
|
||||
}
|
||||
|
||||
if (_lineProperties.at(screenLine).flags.f.wrapped == 1) {
|
||||
lineIsWrapped = true;
|
||||
}
|
||||
|
||||
// count cannot be any greater than length
|
||||
count = qBound(0, length, count);
|
||||
}
|
||||
|
||||
// If the last character is wide, account for it
|
||||
if (Character::width(characterBuffer[count - 1].character, _ignoreWcWidth) == 2)
|
||||
count++;
|
||||
|
||||
// When users ask not to preserve the linebreaks, they usually mean:
|
||||
// `treat LINEBREAK as SPACE, thus joining multiple _lines into
|
||||
// single line in the same way as 'J' does in VIM.`
|
||||
if (_blockSelectionMode || !lineIsWrapped) {
|
||||
++count;
|
||||
}
|
||||
|
||||
counts.append(count);
|
||||
}
|
||||
|
||||
return counts;
|
||||
}
|
||||
|
||||
int Screen::copyLineToStream(int line,
|
||||
int start,
|
||||
int count,
|
||||
@@ -2261,8 +2339,12 @@ void Screen::fastAddHistLine()
|
||||
|
||||
// If _history size > max history size it will drop a line from _history.
|
||||
// We need to verify if we need to remove a URL.
|
||||
if (removeLine && _escapeSequenceUrlExtractor) {
|
||||
_escapeSequenceUrlExtractor->historyLinesRemoved(1);
|
||||
if (removeLine) {
|
||||
if (_escapeSequenceUrlExtractor) {
|
||||
_escapeSequenceUrlExtractor->historyLinesRemoved(1);
|
||||
}
|
||||
|
||||
_fastDroppedLines++;
|
||||
}
|
||||
// Rotate left + clear the last line
|
||||
std::rotate(_screenLines.begin(), _screenLines.begin() + 1, _screenLines.end());
|
||||
|
||||
@@ -634,6 +634,8 @@ public:
|
||||
*/
|
||||
int droppedLines() const;
|
||||
|
||||
int fastDroppedLines() const;
|
||||
|
||||
/**
|
||||
* Resets the count of the number of lines dropped from
|
||||
* the history.
|
||||
@@ -705,6 +707,8 @@ public:
|
||||
}
|
||||
void setIgnoreWcWidth(bool ignore);
|
||||
|
||||
QList<int> getCharacterCounts() const;
|
||||
|
||||
private:
|
||||
// copies a line of text from the screen or history into a stream using a
|
||||
// specified character decoder. Returns the number of lines actually copied,
|
||||
@@ -804,6 +808,7 @@ private:
|
||||
QRect _lastScrolledRegion;
|
||||
|
||||
int _droppedLines;
|
||||
int _fastDroppedLines;
|
||||
|
||||
int _oldTotalLines;
|
||||
bool _isResize;
|
||||
|
||||
@@ -105,6 +105,8 @@ void Vt102Emulation::clearHistory()
|
||||
|
||||
void Vt102Emulation::reset(bool softReset, bool preservePrompt)
|
||||
{
|
||||
Q_EMIT updateDroppedLines(_currentScreen->getLines());
|
||||
|
||||
// Save the current codec so we can set it later.
|
||||
// Ideally we would want to use the profile setting
|
||||
const QTextCodec *currentCodec = codec();
|
||||
|
||||
@@ -141,6 +141,7 @@ const std::vector<Profile::PropertyInfo> Profile::DefaultProperties = {
|
||||
{VerticalLineAtChar, "VerticalLineAtChar", TERMINAL_GROUP, 80},
|
||||
{PeekPrimaryKeySequence, "PeekPrimaryKeySequence", TERMINAL_GROUP, QString()},
|
||||
{LineNumbers, "LineNumbers", TERMINAL_GROUP, 0},
|
||||
{AutoSaveInterval, "AutoSaveInterval", TERMINAL_GROUP, 10000},
|
||||
|
||||
// Cursor
|
||||
{UseCustomCursorColor, "UseCustomCursorColor", CURSOR_GROUP, false},
|
||||
|
||||
@@ -423,6 +423,9 @@ public:
|
||||
* soft hyphen (\u00ad) has wcwidth=1, but should not be displayed per Unicode.
|
||||
*/
|
||||
IgnoreWcWidth,
|
||||
/** (int) Milliseconds interval between autosave activations
|
||||
*/
|
||||
AutoSaveInterval,
|
||||
};
|
||||
|
||||
Q_ENUM(Property)
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#include "Emulation.h"
|
||||
#include "HistorySizeDialog.h"
|
||||
#include "RenameTabDialog.h"
|
||||
#include "SaveHistoryAutoTask.h"
|
||||
#include "SaveHistoryTask.h"
|
||||
#include "ScreenWindow.h"
|
||||
#include "SearchHistoryTask.h"
|
||||
@@ -331,6 +332,10 @@ void SessionController::snapshot()
|
||||
title = session()->title(Session::NameRole);
|
||||
}
|
||||
|
||||
if (!_autoSaveTask.isNull()) {
|
||||
title.append(QStringLiteral(" (autosaving)"));
|
||||
}
|
||||
|
||||
QColor color = session()->color();
|
||||
// use the fallback color if needed
|
||||
if (!color.isValid()) {
|
||||
@@ -730,6 +735,14 @@ void SessionController::setupCommonActions()
|
||||
action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_S));
|
||||
#endif
|
||||
|
||||
_startAutoSaveAction = collection->addAction(QStringLiteral("file-autosave"), this, &SessionController::autoSaveHistory);
|
||||
_startAutoSaveAction->setText(i18n("Auto Save Output As..."));
|
||||
_startAutoSaveAction->setVisible(true);
|
||||
|
||||
_stopAutoSaveAction = collection->addAction(QStringLiteral("stop-autosave"), this, &SessionController::stopAutoSaveHistory);
|
||||
_stopAutoSaveAction->setText(i18n("Stop Auto Save"));
|
||||
_stopAutoSaveAction->setVisible(false);
|
||||
|
||||
action = KStandardAction::print(this, &SessionController::requestPrint, collection);
|
||||
action->setText(i18n("&Print Screen..."));
|
||||
collection->setDefaultShortcut(action, Konsole::ACCEL | Qt::Key_P);
|
||||
@@ -1793,6 +1806,29 @@ void SessionController::scrollBackOptionsChanged(int mode, int lines)
|
||||
}
|
||||
}
|
||||
|
||||
void SessionController::autoSaveHistory()
|
||||
{
|
||||
_autoSaveTask = new SaveHistoryAutoTask(this);
|
||||
_autoSaveTask->setAutoDelete(true);
|
||||
_autoSaveTask->addSession(session());
|
||||
_autoSaveTask->execute();
|
||||
|
||||
// Only show the button to start autosave when autosave is not ongoing.
|
||||
// Only show the button to stop autosave when auytosave is ongoing.
|
||||
connect(_autoSaveTask, &SaveHistoryAutoTask::completed, this, [&]() {
|
||||
_startAutoSaveAction->setVisible(true);
|
||||
_stopAutoSaveAction->setVisible(false);
|
||||
});
|
||||
|
||||
_startAutoSaveAction->setVisible(false);
|
||||
_stopAutoSaveAction->setVisible(true);
|
||||
}
|
||||
|
||||
void SessionController::stopAutoSaveHistory()
|
||||
{
|
||||
_autoSaveTask->stop();
|
||||
}
|
||||
|
||||
void SessionController::saveHistory()
|
||||
{
|
||||
SessionTask *task = new SaveHistoryTask(this);
|
||||
|
||||
@@ -53,6 +53,7 @@ class TerminalDisplay;
|
||||
class UrlFilter;
|
||||
class ColorFilter;
|
||||
class HotSpot;
|
||||
class SaveHistoryAutoTask;
|
||||
|
||||
/**
|
||||
* Provides the menu actions to manipulate a single terminal session and view pair.
|
||||
@@ -277,6 +278,8 @@ private Q_SLOTS:
|
||||
void findPreviousInHistory();
|
||||
void updateMenuIconsAccordingToReverseSearchSetting();
|
||||
void changeSearchMatch();
|
||||
void autoSaveHistory();
|
||||
void stopAutoSaveHistory();
|
||||
void saveHistory();
|
||||
void showHistoryOptions();
|
||||
void clearHistory();
|
||||
@@ -415,6 +418,10 @@ private:
|
||||
std::unique_ptr<KXMLGUIBuilder> _clientBuilder;
|
||||
|
||||
QSharedPointer<HotSpot> _currentHotSpot;
|
||||
|
||||
QAction *_startAutoSaveAction;
|
||||
QAction *_stopAutoSaveAction;
|
||||
QPointer<SaveHistoryAutoTask> _autoSaveTask;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -249,6 +249,26 @@
|
||||
<item row="8" column="1" alignment="Qt::AlignmentFlag::AlignRight">
|
||||
<widget class="QKeySequenceEdit" name="peekPrimaryWidget"/>
|
||||
</item>
|
||||
<item row="9" column="0" alignment="Qt::AlignmentFlag::AlignRight">
|
||||
<widget class="QLabel" name="autoSaveIntervalLabel">
|
||||
<property name="text">
|
||||
<string>Set interval between autosaves:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1" alignment="Qt::AlignmentFlag::AlignRight">
|
||||
<widget class="QSpinBox" name="autoSaveIntervalWidget">
|
||||
<property name="maximum">
|
||||
<number>3600000</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> ms</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item alignment="Qt::AlignmentFlag::AlignRight">
|
||||
|
||||
@@ -1987,6 +1987,9 @@ void EditProfileDialog::setupAdvancedPage(const Profile::Ptr &profile)
|
||||
connect(_advancedUi->urlHintsModifierMeta, &QCheckBox::toggled, this, &EditProfileDialog::updateUrlHintsModifier);
|
||||
}
|
||||
|
||||
_advancedUi->autoSaveIntervalWidget->setValue(profile->property<int>(Profile::AutoSaveInterval));
|
||||
connect(_advancedUi->autoSaveIntervalWidget, &QSpinBox::valueChanged, this, &EditProfileDialog::setAutoSaveInterval);
|
||||
|
||||
// encoding options
|
||||
auto codecAction = new KCodecAction(this);
|
||||
codecAction->setCurrentCodec(profile->defaultEncoding());
|
||||
@@ -2182,6 +2185,11 @@ void EditProfileDialog::toggleFlowControl(bool enable)
|
||||
updateTempProfileProperty(Profile::FlowControlEnabled, enable);
|
||||
}
|
||||
|
||||
void EditProfileDialog::setAutoSaveInterval(int newVal)
|
||||
{
|
||||
updateTempProfileProperty(Profile::AutoSaveInterval, newVal);
|
||||
}
|
||||
|
||||
void EditProfileDialog::peekPrimaryKeySequenceChanged()
|
||||
{
|
||||
updateTempProfileProperty(Profile::PeekPrimaryKeySequence, _advancedUi->peekPrimaryWidget->keySequence().toString());
|
||||
|
||||
@@ -220,6 +220,7 @@ private Q_SLOTS:
|
||||
void toggleFlowControl(bool);
|
||||
void updateUrlHintsModifier(bool);
|
||||
void toggleReverseUrlHints(bool);
|
||||
void setAutoSaveInterval(int);
|
||||
|
||||
void setDefaultCodec(QTextCodec *);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user