Terminal: Use PageSettings for settings

The DropSupport is disabled for now, plan is to have a DropArea
LayoutItem later and use that.

Change-Id: I7fd1e55ad0c053f0357bb53a7cc20e9da8a933a7
Reviewed-by: Marcus Tillmanns <marcus.tillmanns@qt.io>
This commit is contained in:
hjk
2023-05-25 14:49:11 +02:00
parent 7aec8c0b14
commit 8b29c06372
9 changed files with 503 additions and 595 deletions

View File

@@ -12,11 +12,10 @@ add_qtc_plugin(Terminal
terminal.qrc terminal.qrc
terminalcommands.cpp terminalcommands.h terminalcommands.cpp terminalcommands.h
terminalpane.cpp terminalpane.h terminalpane.cpp terminalpane.h
terminalplugin.cpp terminalplugin.h terminalplugin.cpp
terminalprocessimpl.cpp terminalprocessimpl.h terminalprocessimpl.cpp terminalprocessimpl.h
terminalsearch.cpp terminalsearch.h terminalsearch.cpp terminalsearch.h
terminalsettings.cpp terminalsettings.h terminalsettings.cpp terminalsettings.h
terminalsettingspage.cpp terminalsettingspage.h
terminalsurface.cpp terminalsurface.h terminalsurface.cpp terminalsurface.h
terminaltr.h terminaltr.h
terminalwidget.cpp terminalwidget.h terminalwidget.cpp terminalwidget.h

View File

@@ -27,15 +27,12 @@ QtcPlugin {
"terminalpane.cpp", "terminalpane.cpp",
"terminalpane.h", "terminalpane.h",
"terminalplugin.cpp", "terminalplugin.cpp",
"terminalplugin.h",
"terminalprocessimpl.cpp", "terminalprocessimpl.cpp",
"terminalprocessimpl.h", "terminalprocessimpl.h",
"terminalsearch.cpp", "terminalsearch.cpp",
"terminalsearch.h", "terminalsearch.h",
"terminalsettings.cpp", "terminalsettings.cpp",
"terminalsettings.h", "terminalsettings.h",
"terminalsettingspage.cpp",
"terminalsettingspage.h",
"terminalsurface.cpp", "terminalsurface.cpp",
"terminalsurface.h", "terminalsurface.h",
"terminaltr.h", "terminaltr.h",

View File

@@ -151,7 +151,6 @@ TerminalPane::TerminalPane(QObject *parent)
connect(m_escSettingButton, &QToolButton::toggled, this, [this] { connect(m_escSettingButton, &QToolButton::toggled, this, [this] {
TerminalSettings::instance().sendEscapeToTerminal.setValue(m_escSettingButton->isChecked()); TerminalSettings::instance().sendEscapeToTerminal.setValue(m_escSettingButton->isChecked());
TerminalSettings::instance().apply();
TerminalSettings::instance().writeSettings(Core::ICore::settings()); TerminalSettings::instance().writeSettings(Core::ICore::settings());
}); });

View File

@@ -1,12 +1,9 @@
// Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "terminalplugin.h"
#include "terminalpane.h" #include "terminalpane.h"
#include "terminalprocessimpl.h" #include "terminalprocessimpl.h"
#include "terminalsettings.h" #include "terminalsettings.h"
#include "terminalsettingspage.h"
#include "terminalcommands.h" #include "terminalcommands.h"
#include <coreplugin/actionmanager/actioncontainer.h> #include <coreplugin/actionmanager/actioncontainer.h>
@@ -16,6 +13,7 @@
#include <coreplugin/imode.h> #include <coreplugin/imode.h>
#include <coreplugin/modemanager.h> #include <coreplugin/modemanager.h>
#include <extensionsystem/iplugin.h>
#include <extensionsystem/pluginmanager.h> #include <extensionsystem/pluginmanager.h>
#include <QAction> #include <QAction>
@@ -24,31 +22,32 @@
#include <QMessageBox> #include <QMessageBox>
#include <QPushButton> #include <QPushButton>
namespace Terminal { namespace Terminal::Internal {
namespace Internal {
TerminalPlugin::TerminalPlugin() {} class TerminalPlugin final : public ExtensionSystem::IPlugin
TerminalPlugin::~TerminalPlugin()
{ {
ExtensionSystem::PluginManager::instance()->removeObject(m_terminalPane); Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Terminal.json")
public:
TerminalPlugin() = default;
~TerminalPlugin() final
{
ExtensionSystem::PluginManager::removeObject(m_terminalPane);
delete m_terminalPane; delete m_terminalPane;
m_terminalPane = nullptr; m_terminalPane = nullptr;
} }
bool TerminalPlugin::delayedInitialize() void initialize() final
{ {
TerminalCommands::instance().lazyInitCommands(); addManaged<TerminalSettings>();
return true;
} }
void TerminalPlugin::extensionsInitialized() void extensionsInitialized() final
{ {
(void) TerminalSettingsPage::instance(); m_terminalPane = new TerminalPane;
TerminalSettings::instance().readSettings(Core::ICore::settings()); ExtensionSystem::PluginManager::addObject(m_terminalPane);
m_terminalPane = new TerminalPane();
ExtensionSystem::PluginManager::instance()->addObject(m_terminalPane);
auto enable = [this] { auto enable = [this] {
Utils::Terminal::Hooks::instance() Utils::Terminal::Hooks::instance()
@@ -63,8 +62,8 @@ void TerminalPlugin::extensionsInitialized()
static bool isEnabled = false; static bool isEnabled = false;
auto settingsChanged = [enable, disable] { auto settingsChanged = [enable, disable] {
if (isEnabled != TerminalSettings::instance().enableTerminal.value()) { if (isEnabled != TerminalSettings::instance().enableTerminal()) {
isEnabled = TerminalSettings::instance().enableTerminal.value(); isEnabled = TerminalSettings::instance().enableTerminal();
if (isEnabled) if (isEnabled)
enable(); enable();
else else
@@ -72,13 +71,21 @@ void TerminalPlugin::extensionsInitialized()
} }
}; };
QObject::connect(&TerminalSettings::instance(), QObject::connect(&TerminalSettings::instance(), &Utils::AspectContainer::applied, this, settingsChanged);
&Utils::AspectContainer::applied,
this,
settingsChanged);
settingsChanged(); settingsChanged();
} }
} // namespace Internal bool delayedInitialize() final
} // namespace Terminal {
TerminalCommands::instance().lazyInitCommands();
return true;
}
private:
TerminalPane *m_terminalPane{nullptr};
};
} // Terminal::Internal
#include "terminalplugin.moc"

View File

@@ -1,30 +0,0 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <extensionsystem/iplugin.h>
namespace Terminal {
class TerminalPane;
namespace Internal {
class TerminalPlugin : public ExtensionSystem::IPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Terminal.json")
public:
TerminalPlugin();
~TerminalPlugin() override;
void extensionsInitialized() override;
bool delayedInitialize() override;
private:
TerminalPane *m_terminalPane{nullptr};
};
} // namespace Internal
} // namespace Terminal

View File

@@ -5,10 +5,26 @@
#include "terminaltr.h" #include "terminaltr.h"
#include <coreplugin/icore.h>
#include <utils/dropsupport.h>
#include <utils/environment.h> #include <utils/environment.h>
#include <utils/expected.h>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h> #include <utils/hostosinfo.h>
#include <utils/layoutbuilder.h>
#include <utils/pathchooser.h>
#include <utils/theme/theme.h> #include <utils/theme/theme.h>
#include <QFontComboBox>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMessageBox>
#include <QPushButton>
#include <QRegularExpression>
#include <QTemporaryFile>
#include <QXmlStreamReader>
using namespace Utils; using namespace Utils;
namespace Terminal { namespace Terminal {
@@ -46,12 +62,6 @@ static QString defaultShell()
return shPath.nativePath(); return shPath.nativePath();
} }
TerminalSettings &TerminalSettings::instance()
{
static TerminalSettings settings;
return settings;
}
void setupColor(TerminalSettings *settings, void setupColor(TerminalSettings *settings,
ColorAspect &color, ColorAspect &color,
const QString &label, const QString &label,
@@ -64,10 +74,305 @@ void setupColor(TerminalSettings *settings,
settings->registerAspect(&color); settings->registerAspect(&color);
} }
static expected_str<void> loadXdefaults(const FilePath &path)
{
const expected_str<QByteArray> readResult = path.fileContents();
if (!readResult)
return make_unexpected(readResult.error());
QRegularExpression re(R"(.*\*(color[0-9]{1,2}|foreground|background):\s*(#[0-9a-f]{6}))");
for (const QByteArray &line : readResult->split('\n')) {
if (line.trimmed().startsWith('!'))
continue;
const auto match = re.match(QString::fromUtf8(line));
if (match.hasMatch()) {
const QString colorName = match.captured(1);
const QColor color(match.captured(2));
if (colorName == "foreground") {
TerminalSettings::instance().foregroundColor.setVolatileValue(color);
} else if (colorName == "background") {
TerminalSettings::instance().backgroundColor.setVolatileValue(color);
} else {
const int colorIndex = colorName.mid(5).toInt();
if (colorIndex >= 0 && colorIndex < 16)
TerminalSettings::instance().colors[colorIndex].setVolatileValue(color);
}
}
}
return {};
}
static expected_str<void> loadItermColors(const FilePath &path)
{
QFile f(path.toFSPathString());
const bool opened = f.open(QIODevice::ReadOnly);
if (!opened)
return make_unexpected(Tr::tr("Failed to open file"));
QXmlStreamReader reader(&f);
while (!reader.atEnd() && reader.readNextStartElement()) {
if (reader.name() == u"plist") {
while (!reader.atEnd() && reader.readNextStartElement()) {
if (reader.name() == u"dict") {
QString colorName;
while (!reader.atEnd() && reader.readNextStartElement()) {
if (reader.name() == u"key") {
colorName = reader.readElementText();
} else if (reader.name() == u"dict") {
QColor color;
int component = 0;
while (!reader.atEnd() && reader.readNextStartElement()) {
if (reader.name() == u"key") {
const auto &text = reader.readElementText();
if (text == u"Red Component")
component = 0;
else if (text == u"Green Component")
component = 1;
else if (text == u"Blue Component")
component = 2;
else if (text == u"Alpha Component")
component = 3;
} else if (reader.name() == u"real") {
// clang-format off
switch (component) {
case 0: color.setRedF(reader.readElementText().toDouble()); break;
case 1: color.setGreenF(reader.readElementText().toDouble()); break;
case 2: color.setBlueF(reader.readElementText().toDouble()); break;
case 3: color.setAlphaF(reader.readElementText().toDouble()); break;
}
// clang-format on
} else {
reader.skipCurrentElement();
}
}
if (colorName.startsWith("Ansi")) {
const auto c = colorName.mid(5, 2);
const int colorIndex = c.toInt();
if (colorIndex >= 0 && colorIndex < 16)
TerminalSettings::instance().colors[colorIndex].setVolatileValue(
color);
} else if (colorName == "Foreground Color") {
TerminalSettings::instance().foregroundColor.setVolatileValue(color);
} else if (colorName == "Background Color") {
TerminalSettings::instance().backgroundColor.setVolatileValue(color);
} else if (colorName == "Selection Color") {
TerminalSettings::instance().selectionColor.setVolatileValue(color);
}
}
}
}
}
break;
}
}
if (reader.hasError())
return make_unexpected(reader.errorString());
return {};
}
static expected_str<void> loadVsCodeColors(const FilePath &path)
{
const expected_str<QByteArray> readResult = path.fileContents();
if (!readResult)
return make_unexpected(readResult.error());
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(*readResult, &error);
if (error.error != QJsonParseError::NoError)
return make_unexpected(Tr::tr("JSON parsing error: \"%1\", at offset: %2")
.arg(error.errorString())
.arg(error.offset));
const QJsonObject root = doc.object();
const auto itColors = root.find("colors");
if (itColors == root.end())
return make_unexpected(Tr::tr("No colors found"));
const QJsonObject colors = itColors->toObject();
// clang-format off
const QList<QPair<QStringView, ColorAspect *>> colorKeys = {
qMakePair(u"editor.background", &TerminalSettings::instance().backgroundColor),
qMakePair(u"terminal.foreground", &TerminalSettings::instance().foregroundColor),
qMakePair(u"terminal.selectionBackground", &TerminalSettings::instance().selectionColor),
qMakePair(u"terminal.ansiBlack", &TerminalSettings::instance().colors[0]),
qMakePair(u"terminal.ansiBrightBlack", &TerminalSettings::instance().colors[8]),
qMakePair(u"terminal.ansiRed", &TerminalSettings::instance().colors[1]),
qMakePair(u"terminal.ansiBrightRed", &TerminalSettings::instance().colors[9]),
qMakePair(u"terminal.ansiGreen", &TerminalSettings::instance().colors[2]),
qMakePair(u"terminal.ansiBrightGreen", &TerminalSettings::instance().colors[10]),
qMakePair(u"terminal.ansiYellow", &TerminalSettings::instance().colors[3]),
qMakePair(u"terminal.ansiBrightYellow", &TerminalSettings::instance().colors[11]),
qMakePair(u"terminal.ansiBlue", &TerminalSettings::instance().colors[4]),
qMakePair(u"terminal.ansiBrightBlue", &TerminalSettings::instance().colors[12]),
qMakePair(u"terminal.ansiMagenta", &TerminalSettings::instance().colors[5]),
qMakePair(u"terminal.ansiBrightMagenta", &TerminalSettings::instance().colors[13]),
qMakePair(u"terminal.ansiCyan", &TerminalSettings::instance().colors[6]),
qMakePair(u"terminal.ansiBrightCyan", &TerminalSettings::instance().colors[14]),
qMakePair(u"terminal.ansiWhite", &TerminalSettings::instance().colors[7]),
qMakePair(u"terminal.ansiBrightWhite", &TerminalSettings::instance().colors[15])
};
// clang-format on
for (const auto &pair : colorKeys) {
const auto it = colors.find(pair.first);
if (it != colors.end()) {
const QString colorString = it->toString();
if (colorString.startsWith("#")) {
QColor color(colorString.mid(0, 7));
if (colorString.size() > 7) {
int alpha = colorString.mid(7).toInt(nullptr, 16);
color.setAlpha(alpha);
}
if (color.isValid())
pair.second->setVolatileValue(color);
}
}
}
return {};
}
static expected_str<void> loadKonsoleColorScheme(const FilePath &path)
{
QSettings settings(path.toFSPathString(), QSettings::IniFormat);
auto parseColor = [](const QStringList &parts) -> expected_str<QColor> {
if (parts.size() != 3 && parts.size() != 4)
return make_unexpected(Tr::tr("Invalid color format"));
int alpha = parts.size() == 4 ? parts[3].toInt() : 255;
return QColor(parts[0].toInt(), parts[1].toInt(), parts[2].toInt(), alpha);
};
// clang-format off
const QList<QPair<QString, ColorAspect *>> colorKeys = {
qMakePair(QLatin1String("Background/Color"), &TerminalSettings::instance().backgroundColor),
qMakePair(QLatin1String("Foreground/Color"), &TerminalSettings::instance().foregroundColor),
qMakePair(QLatin1String("Color0/Color"), &TerminalSettings::instance().colors[0]),
qMakePair(QLatin1String("Color0Intense/Color"), &TerminalSettings::instance().colors[8]),
qMakePair(QLatin1String("Color1/Color"), &TerminalSettings::instance().colors[1]),
qMakePair(QLatin1String("Color1Intense/Color"), &TerminalSettings::instance().colors[9]),
qMakePair(QLatin1String("Color2/Color"), &TerminalSettings::instance().colors[2]),
qMakePair(QLatin1String("Color2Intense/Color"), &TerminalSettings::instance().colors[10]),
qMakePair(QLatin1String("Color3/Color"), &TerminalSettings::instance().colors[3]),
qMakePair(QLatin1String("Color3Intense/Color"), &TerminalSettings::instance().colors[11]),
qMakePair(QLatin1String("Color4/Color"), &TerminalSettings::instance().colors[4]),
qMakePair(QLatin1String("Color4Intense/Color"), &TerminalSettings::instance().colors[12]),
qMakePair(QLatin1String("Color5/Color"), &TerminalSettings::instance().colors[5]),
qMakePair(QLatin1String("Color5Intense/Color"), &TerminalSettings::instance().colors[13]),
qMakePair(QLatin1String("Color6/Color"), &TerminalSettings::instance().colors[6]),
qMakePair(QLatin1String("Color6Intense/Color"), &TerminalSettings::instance().colors[14]),
qMakePair(QLatin1String("Color7/Color"), &TerminalSettings::instance().colors[7]),
qMakePair(QLatin1String("Color7Intense/Color"), &TerminalSettings::instance().colors[15])
};
// clang-format on
for (const auto &colorKey : colorKeys) {
if (settings.contains(colorKey.first)) {
const auto color = parseColor(settings.value(colorKey.first).toStringList());
if (!color)
return make_unexpected(color.error());
colorKey.second->setVolatileValue(*color);
}
}
return {};
}
static expected_str<void> loadXFCE4ColorScheme(const FilePath &path)
{
expected_str<QByteArray> arr = path.fileContents();
if (!arr)
return make_unexpected(arr.error());
arr->replace(';', ',');
QTemporaryFile f;
f.open();
f.write(*arr);
f.close();
QSettings settings(f.fileName(), QSettings::IniFormat);
// clang-format off
const QList<QPair<QString, ColorAspect *>> colorKeys = {
qMakePair(QLatin1String("Scheme/ColorBackground"), &TerminalSettings::instance().backgroundColor),
qMakePair(QLatin1String("Scheme/ColorForeground"), &TerminalSettings::instance().foregroundColor),
};
// clang-format on
for (const auto &colorKey : colorKeys) {
if (settings.contains(colorKey.first)) {
colorKey.second->setVolatileValue(QColor(settings.value(colorKey.first).toString()));
}
}
QStringList colors = settings.value(QLatin1String("Scheme/ColorPalette")).toStringList();
int i = 0;
for (const auto &color : colors) {
TerminalSettings::instance().colors[i++].setVolatileValue(QColor(color));
}
return {};
}
static expected_str<void> loadColorScheme(const FilePath &path)
{
if (path.endsWith("Xdefaults"))
return loadXdefaults(path);
else if (path.suffix() == "itermcolors")
return loadItermColors(path);
else if (path.suffix() == "json")
return loadVsCodeColors(path);
else if (path.suffix() == "colorscheme")
return loadKonsoleColorScheme(path);
else if (path.suffix() == "theme" || path.completeSuffix() == "theme.txt")
return loadXFCE4ColorScheme(path);
return make_unexpected(Tr::tr("Unknown color scheme format"));
}
static TerminalSettings *s_instance;
TerminalSettings &TerminalSettings::instance()
{
return *s_instance;
}
TerminalSettings::TerminalSettings() TerminalSettings::TerminalSettings()
{ {
s_instance = this;
setAutoApply(false); setAutoApply(false);
setSettingsGroup("Terminal"); setSettingsGroup("Terminal");
setId("Terminal.General");
setDisplayName("Terminal");
setCategory("ZY.Terminal");
setDisplayCategory("Terminal");
setSettings(&TerminalSettings::instance());
setCategoryIconPath(":/terminal/images/settingscategory_terminal.png");
enableTerminal.setSettingsKey("EnableTerminal"); enableTerminal.setSettingsKey("EnableTerminal");
enableTerminal.setLabelText(Tr::tr("Use internal terminal")); enableTerminal.setLabelText(Tr::tr("Use internal terminal"));
@@ -122,15 +427,6 @@ TerminalSettings::TerminalSettings()
"character is received.")); "character is received."));
audibleBell.setDefaultValue(true); audibleBell.setDefaultValue(true);
registerAspect(&font);
registerAspect(&fontSize);
registerAspect(&shell);
registerAspect(&allowBlinkingCursor);
registerAspect(&enableTerminal);
registerAspect(&sendEscapeToTerminal);
registerAspect(&audibleBell);
registerAspect(&shellArguments);
setupColor(this, setupColor(this,
foregroundColor, foregroundColor,
"Foreground", "Foreground",
@@ -172,6 +468,127 @@ TerminalSettings::TerminalSettings()
setupColor(this, colors[7], "7", Utils::creatorTheme()->color(Theme::TerminalAnsi7)); setupColor(this, colors[7], "7", Utils::creatorTheme()->color(Theme::TerminalAnsi7));
setupColor(this, colors[15], "15", Utils::creatorTheme()->color(Theme::TerminalAnsi15)); setupColor(this, colors[15], "15", Utils::creatorTheme()->color(Theme::TerminalAnsi15));
setLayouter([this] {
using namespace Layouting;
QFontComboBox *fontComboBox = new QFontComboBox;
fontComboBox->setFontFilters(QFontComboBox::MonospacedFonts);
fontComboBox->setCurrentFont(font());
connect(fontComboBox, &QFontComboBox::currentFontChanged, this, [this](const QFont &f) {
font.setValue(f.family());
});
auto loadThemeButton = new QPushButton(Tr::tr("Load Theme..."));
auto resetTheme = new QPushButton(Tr::tr("Reset Theme"));
connect(loadThemeButton, &QPushButton::clicked, this, [this] {
const FilePath path = FileUtils::getOpenFilePath(
Core::ICore::dialogParent(),
"Open Theme",
{},
"All Scheme formats (*.itermcolors *.json *.colorscheme *.theme *.theme.txt);;"
"Xdefaults (.Xdefaults Xdefaults);;"
"iTerm Color Schemes(*.itermcolors);;"
"VS Code Color Schemes(*.json);;"
"Konsole Color Schemes(*.colorscheme);;"
"XFCE4 Terminal Color Schemes(*.theme *.theme.txt);;"
"All files (*)",
nullptr,
{},
true,
false);
if (path.isEmpty())
return;
const expected_str<void> result = loadColorScheme(path);
if (!result)
QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Error"), result.error());
});
connect(resetTheme, &QPushButton::clicked, this, [this] {
foregroundColor.setVolatileValue(foregroundColor.defaultValue());
backgroundColor.setVolatileValue(backgroundColor.defaultValue());
selectionColor.setVolatileValue(selectionColor.defaultValue());
for (ColorAspect &color : colors)
color.setVolatileValue(color.defaultValue());
});
// FIXME: Implement and use a Layouting::DropArea item
// DropSupport *dropSupport = new DropSupport;
// connect(dropSupport,
// &DropSupport::filesDropped,
// this,
// [this](const QList<DropSupport::FileSpec> &files) {
// if (files.size() != 1)
// return;
// const expected_str<void> result = loadColorScheme(files.at(0).filePath);
// if (!result)
// QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("Error"), result.error());
// });
// clang-format off
return Column {
Group {
title(Tr::tr("General")),
Column {
enableTerminal, st,
sendEscapeToTerminal, st,
audibleBell, st,
allowBlinkingCursor, st,
},
},
Group {
title(Tr::tr("Font")),
Row {
font.labelText(), fontComboBox, Space(20),
fontSize, st,
},
},
Group {
title(Tr::tr("Colors")),
Column {
Row {
Tr::tr("Foreground"), foregroundColor, st,
Tr::tr("Background"), backgroundColor, st,
Tr::tr("Selection"), selectionColor, st,
Tr::tr("Find match"), findMatchColor, st,
},
Row {
colors[0], colors[1],
colors[2], colors[3],
colors[4], colors[5],
colors[6], colors[7]
},
Row {
colors[8], colors[9],
colors[10], colors[11],
colors[12], colors[13],
colors[14], colors[15]
},
Row {
loadThemeButton, resetTheme, st,
}
},
},
Group {
title(Tr::tr("Default Shell")),
Column {
shell,
shellArguments,
},
},
st,
};
// clang-format on
});
readSettings();
} }
} // namespace Terminal } // Terminal

View File

@@ -3,22 +3,23 @@
#pragma once #pragma once
#include <utils/aspects.h> #include <coreplugin/dialogs/ioptionspage.h>
namespace Terminal { namespace Terminal {
class TerminalSettings : public Utils::AspectContainer
class TerminalSettings : public Core::PagedSettings
{ {
public: public:
TerminalSettings(); TerminalSettings();
static TerminalSettings &instance(); static TerminalSettings &instance();
Utils::BoolAspect enableTerminal; Utils::BoolAspect enableTerminal{this};
Utils::StringAspect font; Utils::StringAspect font{this};
Utils::IntegerAspect fontSize; Utils::IntegerAspect fontSize{this};
Utils::StringAspect shell; Utils::StringAspect shell{this};
Utils::StringAspect shellArguments; Utils::StringAspect shellArguments{this};
Utils::ColorAspect foregroundColor; Utils::ColorAspect foregroundColor;
Utils::ColorAspect backgroundColor; Utils::ColorAspect backgroundColor;
@@ -27,10 +28,10 @@ public:
Utils::ColorAspect colors[16]; Utils::ColorAspect colors[16];
Utils::BoolAspect allowBlinkingCursor; Utils::BoolAspect allowBlinkingCursor{this};
Utils::BoolAspect sendEscapeToTerminal; Utils::BoolAspect sendEscapeToTerminal{this};
Utils::BoolAspect audibleBell; Utils::BoolAspect audibleBell{this};
}; };
} // namespace Terminal } // Terminal

View File

@@ -1,464 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "terminalsettingspage.h"
#include "terminalsettings.h"
#include "terminaltr.h"
#include <coreplugin/icore.h>
#include <utils/aspects.h>
#include <utils/dropsupport.h>
#include <utils/expected.h>
#include <utils/fileutils.h>
#include <utils/layoutbuilder.h>
#include <utils/pathchooser.h>
#include <QFontComboBox>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMessageBox>
#include <QPushButton>
#include <QRegularExpression>
#include <QTemporaryFile>
#include <QXmlStreamReader>
using namespace Utils;
namespace Terminal {
static expected_str<void> loadXdefaults(const FilePath &path)
{
const expected_str<QByteArray> readResult = path.fileContents();
if (!readResult)
return make_unexpected(readResult.error());
QRegularExpression re(R"(.*\*(color[0-9]{1,2}|foreground|background):\s*(#[0-9a-f]{6}))");
for (const QByteArray &line : readResult->split('\n')) {
if (line.trimmed().startsWith('!'))
continue;
const auto match = re.match(QString::fromUtf8(line));
if (match.hasMatch()) {
const QString colorName = match.captured(1);
const QColor color(match.captured(2));
if (colorName == "foreground") {
TerminalSettings::instance().foregroundColor.setVolatileValue(color);
} else if (colorName == "background") {
TerminalSettings::instance().backgroundColor.setVolatileValue(color);
} else {
const int colorIndex = colorName.mid(5).toInt();
if (colorIndex >= 0 && colorIndex < 16)
TerminalSettings::instance().colors[colorIndex].setVolatileValue(color);
}
}
}
return {};
}
static expected_str<void> loadItermColors(const FilePath &path)
{
QFile f(path.toFSPathString());
const bool opened = f.open(QIODevice::ReadOnly);
if (!opened)
return make_unexpected(Tr::tr("Failed to open file"));
QXmlStreamReader reader(&f);
while (!reader.atEnd() && reader.readNextStartElement()) {
if (reader.name() == u"plist") {
while (!reader.atEnd() && reader.readNextStartElement()) {
if (reader.name() == u"dict") {
QString colorName;
while (!reader.atEnd() && reader.readNextStartElement()) {
if (reader.name() == u"key") {
colorName = reader.readElementText();
} else if (reader.name() == u"dict") {
QColor color;
int component = 0;
while (!reader.atEnd() && reader.readNextStartElement()) {
if (reader.name() == u"key") {
const auto &text = reader.readElementText();
if (text == u"Red Component")
component = 0;
else if (text == u"Green Component")
component = 1;
else if (text == u"Blue Component")
component = 2;
else if (text == u"Alpha Component")
component = 3;
} else if (reader.name() == u"real") {
// clang-format off
switch (component) {
case 0: color.setRedF(reader.readElementText().toDouble()); break;
case 1: color.setGreenF(reader.readElementText().toDouble()); break;
case 2: color.setBlueF(reader.readElementText().toDouble()); break;
case 3: color.setAlphaF(reader.readElementText().toDouble()); break;
}
// clang-format on
} else {
reader.skipCurrentElement();
}
}
if (colorName.startsWith("Ansi")) {
const auto c = colorName.mid(5, 2);
const int colorIndex = c.toInt();
if (colorIndex >= 0 && colorIndex < 16)
TerminalSettings::instance().colors[colorIndex].setVolatileValue(
color);
} else if (colorName == "Foreground Color") {
TerminalSettings::instance().foregroundColor.setVolatileValue(color);
} else if (colorName == "Background Color") {
TerminalSettings::instance().backgroundColor.setVolatileValue(color);
} else if (colorName == "Selection Color") {
TerminalSettings::instance().selectionColor.setVolatileValue(color);
}
}
}
}
}
break;
}
}
if (reader.hasError())
return make_unexpected(reader.errorString());
return {};
}
static expected_str<void> loadVsCodeColors(const FilePath &path)
{
const expected_str<QByteArray> readResult = path.fileContents();
if (!readResult)
return make_unexpected(readResult.error());
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(*readResult, &error);
if (error.error != QJsonParseError::NoError)
return make_unexpected(Tr::tr("JSON parsing error: \"%1\", at offset: %2")
.arg(error.errorString())
.arg(error.offset));
const QJsonObject root = doc.object();
const auto itColors = root.find("colors");
if (itColors == root.end())
return make_unexpected(Tr::tr("No colors found"));
const QJsonObject colors = itColors->toObject();
// clang-format off
const QList<QPair<QStringView, ColorAspect *>> colorKeys = {
qMakePair(u"editor.background", &TerminalSettings::instance().backgroundColor),
qMakePair(u"terminal.foreground", &TerminalSettings::instance().foregroundColor),
qMakePair(u"terminal.selectionBackground", &TerminalSettings::instance().selectionColor),
qMakePair(u"terminal.ansiBlack", &TerminalSettings::instance().colors[0]),
qMakePair(u"terminal.ansiBrightBlack", &TerminalSettings::instance().colors[8]),
qMakePair(u"terminal.ansiRed", &TerminalSettings::instance().colors[1]),
qMakePair(u"terminal.ansiBrightRed", &TerminalSettings::instance().colors[9]),
qMakePair(u"terminal.ansiGreen", &TerminalSettings::instance().colors[2]),
qMakePair(u"terminal.ansiBrightGreen", &TerminalSettings::instance().colors[10]),
qMakePair(u"terminal.ansiYellow", &TerminalSettings::instance().colors[3]),
qMakePair(u"terminal.ansiBrightYellow", &TerminalSettings::instance().colors[11]),
qMakePair(u"terminal.ansiBlue", &TerminalSettings::instance().colors[4]),
qMakePair(u"terminal.ansiBrightBlue", &TerminalSettings::instance().colors[12]),
qMakePair(u"terminal.ansiMagenta", &TerminalSettings::instance().colors[5]),
qMakePair(u"terminal.ansiBrightMagenta", &TerminalSettings::instance().colors[13]),
qMakePair(u"terminal.ansiCyan", &TerminalSettings::instance().colors[6]),
qMakePair(u"terminal.ansiBrightCyan", &TerminalSettings::instance().colors[14]),
qMakePair(u"terminal.ansiWhite", &TerminalSettings::instance().colors[7]),
qMakePair(u"terminal.ansiBrightWhite", &TerminalSettings::instance().colors[15])
};
// clang-format on
for (const auto &pair : colorKeys) {
const auto it = colors.find(pair.first);
if (it != colors.end()) {
const QString colorString = it->toString();
if (colorString.startsWith("#")) {
QColor color(colorString.mid(0, 7));
if (colorString.size() > 7) {
int alpha = colorString.mid(7).toInt(nullptr, 16);
color.setAlpha(alpha);
}
if (color.isValid())
pair.second->setVolatileValue(color);
}
}
}
return {};
}
static expected_str<void> loadKonsoleColorScheme(const FilePath &path)
{
QSettings settings(path.toFSPathString(), QSettings::IniFormat);
auto parseColor = [](const QStringList &parts) -> expected_str<QColor> {
if (parts.size() != 3 && parts.size() != 4)
return make_unexpected(Tr::tr("Invalid color format"));
int alpha = parts.size() == 4 ? parts[3].toInt() : 255;
return QColor(parts[0].toInt(), parts[1].toInt(), parts[2].toInt(), alpha);
};
// clang-format off
const QList<QPair<QString, ColorAspect *>> colorKeys = {
qMakePair(QLatin1String("Background/Color"), &TerminalSettings::instance().backgroundColor),
qMakePair(QLatin1String("Foreground/Color"), &TerminalSettings::instance().foregroundColor),
qMakePair(QLatin1String("Color0/Color"), &TerminalSettings::instance().colors[0]),
qMakePair(QLatin1String("Color0Intense/Color"), &TerminalSettings::instance().colors[8]),
qMakePair(QLatin1String("Color1/Color"), &TerminalSettings::instance().colors[1]),
qMakePair(QLatin1String("Color1Intense/Color"), &TerminalSettings::instance().colors[9]),
qMakePair(QLatin1String("Color2/Color"), &TerminalSettings::instance().colors[2]),
qMakePair(QLatin1String("Color2Intense/Color"), &TerminalSettings::instance().colors[10]),
qMakePair(QLatin1String("Color3/Color"), &TerminalSettings::instance().colors[3]),
qMakePair(QLatin1String("Color3Intense/Color"), &TerminalSettings::instance().colors[11]),
qMakePair(QLatin1String("Color4/Color"), &TerminalSettings::instance().colors[4]),
qMakePair(QLatin1String("Color4Intense/Color"), &TerminalSettings::instance().colors[12]),
qMakePair(QLatin1String("Color5/Color"), &TerminalSettings::instance().colors[5]),
qMakePair(QLatin1String("Color5Intense/Color"), &TerminalSettings::instance().colors[13]),
qMakePair(QLatin1String("Color6/Color"), &TerminalSettings::instance().colors[6]),
qMakePair(QLatin1String("Color6Intense/Color"), &TerminalSettings::instance().colors[14]),
qMakePair(QLatin1String("Color7/Color"), &TerminalSettings::instance().colors[7]),
qMakePair(QLatin1String("Color7Intense/Color"), &TerminalSettings::instance().colors[15])
};
// clang-format on
for (const auto &colorKey : colorKeys) {
if (settings.contains(colorKey.first)) {
const auto color = parseColor(settings.value(colorKey.first).toStringList());
if (!color)
return make_unexpected(color.error());
colorKey.second->setVolatileValue(*color);
}
}
return {};
}
static expected_str<void> loadXFCE4ColorScheme(const FilePath &path)
{
expected_str<QByteArray> arr = path.fileContents();
if (!arr)
return make_unexpected(arr.error());
arr->replace(';', ',');
QTemporaryFile f;
f.open();
f.write(*arr);
f.close();
QSettings settings(f.fileName(), QSettings::IniFormat);
// clang-format off
const QList<QPair<QString, ColorAspect *>> colorKeys = {
qMakePair(QLatin1String("Scheme/ColorBackground"), &TerminalSettings::instance().backgroundColor),
qMakePair(QLatin1String("Scheme/ColorForeground"), &TerminalSettings::instance().foregroundColor),
};
// clang-format on
for (const auto &colorKey : colorKeys) {
if (settings.contains(colorKey.first)) {
colorKey.second->setVolatileValue(QColor(settings.value(colorKey.first).toString()));
}
}
QStringList colors = settings.value(QLatin1String("Scheme/ColorPalette")).toStringList();
int i = 0;
for (const auto &color : colors) {
TerminalSettings::instance().colors[i++].setVolatileValue(QColor(color));
}
return {};
}
static expected_str<void> loadColorScheme(const FilePath &path)
{
if (path.endsWith("Xdefaults"))
return loadXdefaults(path);
else if (path.suffix() == "itermcolors")
return loadItermColors(path);
else if (path.suffix() == "json")
return loadVsCodeColors(path);
else if (path.suffix() == "colorscheme")
return loadKonsoleColorScheme(path);
else if (path.suffix() == "theme" || path.completeSuffix() == "theme.txt")
return loadXFCE4ColorScheme(path);
return make_unexpected(Tr::tr("Unknown color scheme format"));
}
class TerminalSettingsPageWidget : public Core::IOptionsPageWidget
{
public:
TerminalSettingsPageWidget()
{
using namespace Layouting;
QFontComboBox *fontComboBox = new QFontComboBox(this);
fontComboBox->setFontFilters(QFontComboBox::MonospacedFonts);
fontComboBox->setCurrentFont(TerminalSettings::instance().font.value());
connect(fontComboBox, &QFontComboBox::currentFontChanged, this, [](const QFont &f) {
TerminalSettings::instance().font.setValue(f.family());
});
TerminalSettings &settings = TerminalSettings::instance();
QPushButton *loadThemeButton = new QPushButton(Tr::tr("Load Theme..."));
QPushButton *resetTheme = new QPushButton(Tr::tr("Reset Theme"));
connect(loadThemeButton, &QPushButton::clicked, this, [this] {
const FilePath path = FileUtils::getOpenFilePath(
this,
"Open Theme",
{},
"All Scheme formats (*.itermcolors *.json *.colorscheme *.theme *.theme.txt);;"
"Xdefaults (.Xdefaults Xdefaults);;"
"iTerm Color Schemes(*.itermcolors);;"
"VS Code Color Schemes(*.json);;"
"Konsole Color Schemes(*.colorscheme);;"
"XFCE4 Terminal Color Schemes(*.theme *.theme.txt);;"
"All files (*)",
nullptr,
{},
true,
false);
if (path.isEmpty())
return;
const expected_str<void> result = loadColorScheme(path);
if (!result)
QMessageBox::warning(this, Tr::tr("Error"), result.error());
});
connect(resetTheme, &QPushButton::clicked, this, [] {
TerminalSettings &settings = TerminalSettings::instance();
settings.foregroundColor.setVolatileValue(settings.foregroundColor.defaultValue());
settings.backgroundColor.setVolatileValue(settings.backgroundColor.defaultValue());
settings.selectionColor.setVolatileValue(settings.selectionColor.defaultValue());
for (auto &color : settings.colors)
color.setVolatileValue(color.defaultValue());
});
// clang-format off
Column {
Group {
title(Tr::tr("General")),
Column {
settings.enableTerminal, st,
settings.sendEscapeToTerminal, st,
settings.audibleBell, st,
settings.allowBlinkingCursor, st,
},
},
Group {
title(Tr::tr("Font")),
Row {
settings.font.labelText(), fontComboBox, Space(20),
settings.fontSize, st,
},
},
Group {
title(Tr::tr("Colors")),
Column {
Row {
Tr::tr("Foreground"), settings.foregroundColor, st,
Tr::tr("Background"), settings.backgroundColor, st,
Tr::tr("Selection"), settings.selectionColor, st,
Tr::tr("Find match"), settings.findMatchColor, st,
},
Row {
settings.colors[0], settings.colors[1],
settings.colors[2], settings.colors[3],
settings.colors[4], settings.colors[5],
settings.colors[6], settings.colors[7]
},
Row {
settings.colors[8], settings.colors[9],
settings.colors[10], settings.colors[11],
settings.colors[12], settings.colors[13],
settings.colors[14], settings.colors[15]
},
Row {
loadThemeButton, resetTheme, st,
}
},
},
Group {
title(Tr::tr("Default Shell")),
Column {
settings.shell,
settings.shellArguments,
},
},
st,
}.attachTo(this);
// clang-format on
DropSupport *dropSupport = new DropSupport(this);
connect(dropSupport,
&DropSupport::filesDropped,
this,
[this](const QList<DropSupport::FileSpec> &files) {
if (files.size() != 1)
return;
const expected_str<void> result = loadColorScheme(files.at(0).filePath);
if (!result)
QMessageBox::warning(this, Tr::tr("Error"), result.error());
});
}
void apply() final
{
TerminalSettings &settings = TerminalSettings::instance();
if (settings.isDirty()) {
settings.apply();
settings.writeSettings(Core::ICore::settings());
}
}
};
// TerminalSettingsPage
TerminalSettingsPage::TerminalSettingsPage()
{
setId("Terminal.General");
setDisplayName("Terminal");
setCategory("ZY.Terminal");
setDisplayCategory("Terminal");
setSettings(&TerminalSettings::instance());
setCategoryIconPath(":/terminal/images/settingscategory_terminal.png");
setWidgetCreator([] { return new TerminalSettingsPageWidget; });
}
TerminalSettingsPage &TerminalSettingsPage::instance()
{
static TerminalSettingsPage settingsPage;
return settingsPage;
}
} // namespace Terminal

View File

@@ -1,18 +0,0 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
#include <coreplugin/dialogs/ioptionspage.h>
namespace Terminal {
class TerminalSettingsPage : public Core::IOptionsPage
{
public:
TerminalSettingsPage();
static TerminalSettingsPage &instance();
};
} // namespace Terminal