diff --git a/src/plugins/terminal/CMakeLists.txt b/src/plugins/terminal/CMakeLists.txt index 005a3aaca56..083ffa844e6 100644 --- a/src/plugins/terminal/CMakeLists.txt +++ b/src/plugins/terminal/CMakeLists.txt @@ -12,11 +12,10 @@ add_qtc_plugin(Terminal terminal.qrc terminalcommands.cpp terminalcommands.h terminalpane.cpp terminalpane.h - terminalplugin.cpp terminalplugin.h + terminalplugin.cpp terminalprocessimpl.cpp terminalprocessimpl.h terminalsearch.cpp terminalsearch.h terminalsettings.cpp terminalsettings.h - terminalsettingspage.cpp terminalsettingspage.h terminalsurface.cpp terminalsurface.h terminaltr.h terminalwidget.cpp terminalwidget.h diff --git a/src/plugins/terminal/terminal.qbs b/src/plugins/terminal/terminal.qbs index 231d3630b7b..0fa1ba46316 100644 --- a/src/plugins/terminal/terminal.qbs +++ b/src/plugins/terminal/terminal.qbs @@ -27,15 +27,12 @@ QtcPlugin { "terminalpane.cpp", "terminalpane.h", "terminalplugin.cpp", - "terminalplugin.h", "terminalprocessimpl.cpp", "terminalprocessimpl.h", "terminalsearch.cpp", "terminalsearch.h", "terminalsettings.cpp", "terminalsettings.h", - "terminalsettingspage.cpp", - "terminalsettingspage.h", "terminalsurface.cpp", "terminalsurface.h", "terminaltr.h", diff --git a/src/plugins/terminal/terminalpane.cpp b/src/plugins/terminal/terminalpane.cpp index 9b1f9898e81..34370f22bb2 100644 --- a/src/plugins/terminal/terminalpane.cpp +++ b/src/plugins/terminal/terminalpane.cpp @@ -151,7 +151,6 @@ TerminalPane::TerminalPane(QObject *parent) connect(m_escSettingButton, &QToolButton::toggled, this, [this] { TerminalSettings::instance().sendEscapeToTerminal.setValue(m_escSettingButton->isChecked()); - TerminalSettings::instance().apply(); TerminalSettings::instance().writeSettings(Core::ICore::settings()); }); diff --git a/src/plugins/terminal/terminalplugin.cpp b/src/plugins/terminal/terminalplugin.cpp index 831981e823a..4738bf9b307 100644 --- a/src/plugins/terminal/terminalplugin.cpp +++ b/src/plugins/terminal/terminalplugin.cpp @@ -1,12 +1,9 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -#include "terminalplugin.h" - #include "terminalpane.h" #include "terminalprocessimpl.h" #include "terminalsettings.h" -#include "terminalsettingspage.h" #include "terminalcommands.h" #include @@ -16,6 +13,7 @@ #include #include +#include #include #include @@ -24,61 +22,70 @@ #include #include -namespace Terminal { -namespace Internal { +namespace Terminal::Internal { -TerminalPlugin::TerminalPlugin() {} - -TerminalPlugin::~TerminalPlugin() +class TerminalPlugin final : public ExtensionSystem::IPlugin { - ExtensionSystem::PluginManager::instance()->removeObject(m_terminalPane); - delete m_terminalPane; - m_terminalPane = nullptr; -} + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Terminal.json") -bool TerminalPlugin::delayedInitialize() -{ - TerminalCommands::instance().lazyInitCommands(); - return true; -} +public: + TerminalPlugin() = default; -void TerminalPlugin::extensionsInitialized() -{ - (void) TerminalSettingsPage::instance(); - TerminalSettings::instance().readSettings(Core::ICore::settings()); + ~TerminalPlugin() final + { + ExtensionSystem::PluginManager::removeObject(m_terminalPane); + delete m_terminalPane; + m_terminalPane = nullptr; + } - m_terminalPane = new TerminalPane(); - ExtensionSystem::PluginManager::instance()->addObject(m_terminalPane); + void initialize() final + { + addManaged(); + } - auto enable = [this] { - Utils::Terminal::Hooks::instance() - .addCallbackSet("Internal", - {[this](const Utils::Terminal::OpenTerminalParameters &p) { - m_terminalPane->openTerminal(p); - }, - [this] { return new TerminalProcessImpl(m_terminalPane); }}); - }; + void extensionsInitialized() final + { + m_terminalPane = new TerminalPane; + ExtensionSystem::PluginManager::addObject(m_terminalPane); - auto disable = [] { Utils::Terminal::Hooks::instance().removeCallbackSet("Internal"); }; + auto enable = [this] { + Utils::Terminal::Hooks::instance() + .addCallbackSet("Internal", + {[this](const Utils::Terminal::OpenTerminalParameters &p) { + m_terminalPane->openTerminal(p); + }, + [this] { return new TerminalProcessImpl(m_terminalPane); }}); + }; - static bool isEnabled = false; - auto settingsChanged = [enable, disable] { - if (isEnabled != TerminalSettings::instance().enableTerminal.value()) { - isEnabled = TerminalSettings::instance().enableTerminal.value(); - if (isEnabled) - enable(); - else - disable(); - } - }; + auto disable = [] { Utils::Terminal::Hooks::instance().removeCallbackSet("Internal"); }; - QObject::connect(&TerminalSettings::instance(), - &Utils::AspectContainer::applied, - this, - settingsChanged); + static bool isEnabled = false; + auto settingsChanged = [enable, disable] { + if (isEnabled != TerminalSettings::instance().enableTerminal()) { + isEnabled = TerminalSettings::instance().enableTerminal(); + if (isEnabled) + enable(); + else + disable(); + } + }; - settingsChanged(); -} + QObject::connect(&TerminalSettings::instance(), &Utils::AspectContainer::applied, this, settingsChanged); -} // namespace Internal -} // namespace Terminal + settingsChanged(); + } + + bool delayedInitialize() final + { + TerminalCommands::instance().lazyInitCommands(); + return true; + } + +private: + TerminalPane *m_terminalPane{nullptr}; +}; + +} // Terminal::Internal + +#include "terminalplugin.moc" diff --git a/src/plugins/terminal/terminalplugin.h b/src/plugins/terminal/terminalplugin.h deleted file mode 100644 index 2bfbd9f22e0..00000000000 --- a/src/plugins/terminal/terminalplugin.h +++ /dev/null @@ -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 - -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 diff --git a/src/plugins/terminal/terminalsettings.cpp b/src/plugins/terminal/terminalsettings.cpp index e2d2150cd72..577367b6641 100644 --- a/src/plugins/terminal/terminalsettings.cpp +++ b/src/plugins/terminal/terminalsettings.cpp @@ -5,10 +5,26 @@ #include "terminaltr.h" +#include + +#include #include +#include +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include + using namespace Utils; namespace Terminal { @@ -46,12 +62,6 @@ static QString defaultShell() return shPath.nativePath(); } -TerminalSettings &TerminalSettings::instance() -{ - static TerminalSettings settings; - return settings; -} - void setupColor(TerminalSettings *settings, ColorAspect &color, const QString &label, @@ -64,10 +74,305 @@ void setupColor(TerminalSettings *settings, settings->registerAspect(&color); } +static expected_str loadXdefaults(const FilePath &path) +{ + const expected_str 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 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 loadVsCodeColors(const FilePath &path) +{ + const expected_str 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> 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 loadKonsoleColorScheme(const FilePath &path) +{ + QSettings settings(path.toFSPathString(), QSettings::IniFormat); + + auto parseColor = [](const QStringList &parts) -> expected_str { + 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> 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 loadXFCE4ColorScheme(const FilePath &path) +{ + expected_str 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> 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 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() { + s_instance = this; + setAutoApply(false); 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.setLabelText(Tr::tr("Use internal terminal")); @@ -122,15 +427,6 @@ TerminalSettings::TerminalSettings() "character is received.")); audibleBell.setDefaultValue(true); - registerAspect(&font); - registerAspect(&fontSize); - registerAspect(&shell); - registerAspect(&allowBlinkingCursor); - registerAspect(&enableTerminal); - registerAspect(&sendEscapeToTerminal); - registerAspect(&audibleBell); - registerAspect(&shellArguments); - setupColor(this, foregroundColor, "Foreground", @@ -172,6 +468,127 @@ TerminalSettings::TerminalSettings() setupColor(this, colors[7], "7", Utils::creatorTheme()->color(Theme::TerminalAnsi7)); 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 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 &files) { +// if (files.size() != 1) +// return; + +// const expected_str 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 diff --git a/src/plugins/terminal/terminalsettings.h b/src/plugins/terminal/terminalsettings.h index 137f3abe3aa..30212b0c3de 100644 --- a/src/plugins/terminal/terminalsettings.h +++ b/src/plugins/terminal/terminalsettings.h @@ -3,22 +3,23 @@ #pragma once -#include +#include namespace Terminal { -class TerminalSettings : public Utils::AspectContainer + +class TerminalSettings : public Core::PagedSettings { public: TerminalSettings(); static TerminalSettings &instance(); - Utils::BoolAspect enableTerminal; + Utils::BoolAspect enableTerminal{this}; - Utils::StringAspect font; - Utils::IntegerAspect fontSize; - Utils::StringAspect shell; - Utils::StringAspect shellArguments; + Utils::StringAspect font{this}; + Utils::IntegerAspect fontSize{this}; + Utils::StringAspect shell{this}; + Utils::StringAspect shellArguments{this}; Utils::ColorAspect foregroundColor; Utils::ColorAspect backgroundColor; @@ -27,10 +28,10 @@ public: Utils::ColorAspect colors[16]; - Utils::BoolAspect allowBlinkingCursor; + Utils::BoolAspect allowBlinkingCursor{this}; - Utils::BoolAspect sendEscapeToTerminal; - Utils::BoolAspect audibleBell; + Utils::BoolAspect sendEscapeToTerminal{this}; + Utils::BoolAspect audibleBell{this}; }; -} // namespace Terminal +} // Terminal diff --git a/src/plugins/terminal/terminalsettingspage.cpp b/src/plugins/terminal/terminalsettingspage.cpp deleted file mode 100644 index 25a7250a1eb..00000000000 --- a/src/plugins/terminal/terminalsettingspage.cpp +++ /dev/null @@ -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 - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace Utils; - -namespace Terminal { - -static expected_str loadXdefaults(const FilePath &path) -{ - const expected_str 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 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 loadVsCodeColors(const FilePath &path) -{ - const expected_str 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> 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 loadKonsoleColorScheme(const FilePath &path) -{ - QSettings settings(path.toFSPathString(), QSettings::IniFormat); - - auto parseColor = [](const QStringList &parts) -> expected_str { - 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> 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 loadXFCE4ColorScheme(const FilePath &path) -{ - expected_str 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> 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 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 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 &files) { - if (files.size() != 1) - return; - - const expected_str 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 diff --git a/src/plugins/terminal/terminalsettingspage.h b/src/plugins/terminal/terminalsettingspage.h deleted file mode 100644 index 12d3762844e..00000000000 --- a/src/plugins/terminal/terminalsettingspage.h +++ /dev/null @@ -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 - -namespace Terminal { - -class TerminalSettingsPage : public Core::IOptionsPage -{ -public: - TerminalSettingsPage(); - - static TerminalSettingsPage &instance(); -}; - -} // namespace Terminal