diff --git a/CMakeLists.txt b/CMakeLists.txt index ae6fbb85d..12c44f7ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,8 +68,7 @@ if(ANDROID) ) else() find_package(Qt5 ${QT_MIN_VERSION} COMPONENTS Widgets) - find_package(KF5 ${KF5_MIN_VERSION} COMPONENTS REQUIRED KIO) - find_package(KF5QQC2DesktopStyle ${KF5_MIN_VERSION} REQUIRED) + find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO) set_package_properties(KF5QQC2DesktopStyle PROPERTIES TYPE RUNTIME ) diff --git a/imports/NeoChat/Page/SettingsPage.qml b/imports/NeoChat/Page/SettingsPage.qml index 386b60cf1..2af2f9d03 100644 --- a/imports/NeoChat/Page/SettingsPage.qml +++ b/imports/NeoChat/Page/SettingsPage.qml @@ -8,80 +8,302 @@ import QtQuick.Layouts 1.15 import org.kde.kirigami 2.15 as Kirigami import org.kde.neochat 1.0 +import NeoChat.Settings 1.0 Kirigami.ScrollablePage { title: i18n("Settings") + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + topPadding: 0 - Kirigami.FormLayout { - QQC2.CheckBox { - Kirigami.FormData.label: i18n("General settings:") - text: i18n("Close to system tray") - checked: Config.systemTray - visible: Controller.supportSystemTray - onToggled: { - Config.systemTray = checked - Config.save() + onBackRequested: { + if (pageSettingStack.depth > 1 && !pageSettingStack.wideMode && pageSettingStack.currentIndex !== 0) { + event.accepted = true; + pageSettingStack.pop(); + } + } + + Kirigami.PageRow { + id: pageSettingStack + anchors.fill: parent + columnView.columnWidth: Kirigami.Units.gridUnit * 12 + initialPage: Kirigami.ScrollablePage { + bottomPadding: 0 + leftPadding: 0 + rightPadding: 0 + topPadding: 0 + Kirigami.Theme.colorSet: Kirigami.Theme.View + ListView { + Component.onCompleted: actions[0].trigger(); + property list actions: [ + Kirigami.Action { + text: i18n("General") + icon.name: "org.kde.neochat" + onTriggered: pageSettingStack.push(generalSettings) + }, + Kirigami.Action { + text: i18n("Appearance") + icon.name: "preferences-desktop-theme-global" + onTriggered: pageSettingStack.push(appearanceSettings) + } + ] + model: actions + delegate: Kirigami.BasicListItem { + action: modelData + } } } - QQC2.CheckBox { - // TODO: When there are enough notification and timeline event - // settings, make 2 separate groups with FormData labels. - Kirigami.FormData.label: i18n("Notifications and events:") - text: i18n("Show notifications") - checked: Config.showNotifications - onToggled: { - Config.showNotifications = checked - Config.save() + } + + Component { + id: generalSettings + Kirigami.ScrollablePage { + Kirigami.FormLayout { + QQC2.CheckBox { + Kirigami.FormData.label: i18n("General settings:") + text: i18n("Close to system tray") + checked: Config.systemTray + visible: Controller.supportSystemTray + onToggled: { + Config.systemTray = checked + Config.save() + } + } + QQC2.CheckBox { + // TODO: When there are enough notification and timeline event + // settings, make 2 separate groups with FormData labels. + Kirigami.FormData.label: i18n("Notifications and events:") + text: i18n("Show notifications") + checked: Config.showNotifications + onToggled: { + Config.showNotifications = checked + Config.save() + } + } + QQC2.CheckBox { + text: i18n("Show leave and join events") + checked: Config.showLeaveJoinEvent + onToggled: { + Config.showLeaveJoinEvent = checked + Config.save() + } + } + QQC2.RadioButton { + Kirigami.FormData.label: i18n("Rooms and private chats:") + text: i18n("Separated") + checked: !Config.mergeRoomList + onToggled: { + Config.mergeRoomList = false + Config.save() + } + } + QQC2.RadioButton { + text: i18n("Intermixed") + checked: Config.mergeRoomList + onToggled: { + Config.mergeRoomList = true + Config.save() + } + } + QQC2.CheckBox { + text: i18n("Use s/text/replacement syntax to edit your last message") + checked: Config.allowQuickEdit + onToggled: { + Config.allowQuickEdit = checked + Config.save() + } + } } } - QQC2.CheckBox { - text: i18n("Show leave and join events") - checked: Config.showLeaveJoinEvent - onToggled: { - Config.showLeaveJoinEvent = checked - Config.save() - } - } - QQC2.RadioButton { - Kirigami.FormData.label: i18n("Rooms and private chats:") - text: i18n("Separated") - checked: !Config.mergeRoomList - onToggled: { - Config.mergeRoomList = false - Config.save() - } - } - QQC2.RadioButton { - text: i18n("Intermixed") - checked: Config.mergeRoomList - onToggled: { - Config.mergeRoomList = true - Config.save() - } - } - QQC2.CheckBox { - Kirigami.FormData.label: i18n("Timeline:") - text: i18n("Show User Avatar") - checked: Config.showAvatarInTimeline - onToggled: { - Config.showAvatarInTimeline = checked - Config.save() - } - } - QQC2.CheckBox { - text: i18n("Show Fancy Effects") - checked: Config.showFancyEffects - onToggled: { - Config.showFancyEffects = checked - Config.save() - } - } - QQC2.CheckBox { - text: i18n("Use s/text/replacement syntax to edit your last message") - checked: Config.allowQuickEdit - onToggled: { - Config.allowQuickEdit = checked - Config.save() + } + + Component { + id: appearanceSettings + Kirigami.ScrollablePage { + ColumnLayout { + RowLayout { + Layout.alignment: Qt.AlignCenter + spacing: Kirigami.Units.gridUnit * 2 + ThemeRadioButton { + innerObject: [ + RowLayout { + Layout.fillWidth: true + Kirigami.Avatar { + color: "blue" + Layout.alignment: Qt.AlignTop + visible: Config.showAvatarInTimeline + Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0 + Layout.preferredHeight: Kirigami.Units.largeSpacing * 2 + } + QQC2.Control { + Layout.fillWidth: true + contentItem: ColumnLayout { + QQC2.Label { + Layout.fillWidth: true + font.weight: Font.Bold + font.pixelSize: 7 + text: "Paul Müller" + color: "blue" + wrapMode: Text.Wrap + } + QQC2.Label { + Layout.fillWidth: true + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta mauris, quis finibus sem suscipit tincidunt." + wrapMode: Text.Wrap + font.pixelSize: 7 + } + } + background: Kirigami.ShadowedRectangle { + color: Kirigami.Theme.backgroundColor + radius: Kirigami.Units.smallSpacing + shadow.size: Kirigami.Units.smallSpacing + shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10) + border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15) + border.width: Kirigami.Units.devicePixelRatio + } + } + }, + RowLayout { + Layout.fillWidth: true + Kirigami.Avatar { + color: "red" + Layout.alignment: Qt.AlignTop + visible: Config.showAvatarInTimeline + Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0 + Layout.preferredHeight: Kirigami.Units.largeSpacing * 2 + } + QQC2.Control { + Layout.fillWidth: true + contentItem: ColumnLayout { + QQC2.Label { + Layout.fillWidth: true + font.weight: Font.Bold + font.pixelSize: 7 + text: "Jean Paul" + color: "red" + wrapMode: Text.Wrap + } + QQC2.Label { + Layout.fillWidth: true + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta , quis sem suscipit tincidunt." + wrapMode: Text.Wrap + font.pixelSize: 7 + } + } + background: Kirigami.ShadowedRectangle { + color: Kirigami.Theme.backgroundColor + radius: Kirigami.Units.smallSpacing + shadow.size: Kirigami.Units.smallSpacing + shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10) + border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15) + border.width: Kirigami.Units.devicePixelRatio + } + } + } + ] + + text: i18n("Bubbles") + checked: !Config.compactLayout + QQC2.ButtonGroup.group: themeGroup + + onToggled: { + Config.compactLayout = !checked; + Config.save(); + } + } + ThemeRadioButton { + innerObject: [ + RowLayout { + Layout.fillWidth: true + Kirigami.Avatar { + color: "blue" + Layout.alignment: Qt.AlignTop + visible: Config.showAvatarInTimeline + Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0 + Layout.preferredHeight: Kirigami.Units.largeSpacing * 2 + } + ColumnLayout { + Layout.fillWidth: true + QQC2.Label { + Layout.fillWidth: true + font.weight: Font.Bold + font.pixelSize: 7 + text: "Paul Müller" + color: "blue" + wrapMode: Text.Wrap + } + QQC2.Label { + Layout.fillWidth: true + text: "Lorem ipsum dolor sit amet, consectetur elit. Vivamus facilisis porta mauris, finibus sem suscipit tincidunt." + wrapMode: Text.Wrap + font.pixelSize: 7 + } + } + }, + RowLayout { + Layout.fillWidth: true + Kirigami.Avatar { + color: "red" + Layout.alignment: Qt.AlignTop + visible: Config.showAvatarInTimeline + Layout.preferredWidth: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing * 2 : 0 + Layout.preferredHeight: Kirigami.Units.largeSpacing * 2 + } + ColumnLayout { + Layout.fillWidth: true + QQC2.Label { + Layout.fillWidth: true + font.weight: Font.Bold + font.pixelSize: 7 + text: "Jean Paul" + color: "red" + wrapMode: Text.Wrap + } + QQC2.Label { + Layout.fillWidth: true + text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus facilisis porta mauris, quis finibus sem suscipit tincidunt." + wrapMode: Text.Wrap + font.pixelSize: 7 + } + } + } + ] + text: i18n("Compact") + checked: Config.compactLayout + QQC2.ButtonGroup.group: themeGroup + + onToggled: { + Config.compactLayout = checked; + Config.save(); + } + } + } + Kirigami.FormLayout { + QQC2.CheckBox { + text: i18n("Show User Avatar") + checked: Config.showAvatarInTimeline + onToggled: { + Config.showAvatarInTimeline = checked; + Config.save(); + } + } + + QQC2.CheckBox { + text: i18n("Show Fancy Effects") + checked: Config.showFancyEffects + onToggled: { + Config.showFancyEffects = checked; + Config.save(); + } + } + Loader { + visible: item !== null + Kirigami.FormData.label: item ? i18n("Theme:") : "" + Kirigami.FormData.buddyFor: item ? item.slider : null + source: "qrc:/imports/NeoChat/Settings/ColorScheme.qml" + } + } } } } diff --git a/imports/NeoChat/Settings/ColorScheme.qml b/imports/NeoChat/Settings/ColorScheme.qml new file mode 100644 index 000000000..e71e84dee --- /dev/null +++ b/imports/NeoChat/Settings/ColorScheme.qml @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2020 Carl Schwan +// SPDX-License-Identifier: LGPL-2.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 + +import org.kde.kirigami 2.15 as Kirigami + +import org.kde.neochat 1.0 +import NeoChat.Settings 1.0 + +QQC2.ComboBox { + textRole: "display" + model: ColorSchemer.model + Component.onCompleted: currentIndex = ColorSchemer.indexForScheme(Config.colorScheme); + onActivated: { + ColorSchemer.apply(currentIndex); + Config.colorScheme = ColorSchemer.nameForIndex(currentIndex); + Config.save(); + } +} diff --git a/imports/NeoChat/Settings/ThemeRadioButton.qml b/imports/NeoChat/Settings/ThemeRadioButton.qml new file mode 100644 index 000000000..cb49e4284 --- /dev/null +++ b/imports/NeoChat/Settings/ThemeRadioButton.qml @@ -0,0 +1,65 @@ +// Copyright 2021 Marco Martin +// Copyright 2018 Furkan Tokac +// Copyright 2019 Nate Graham +// SPDX-License-Identifier: GPL-2.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls 2.15 as QQC2 +import QtQuick.Layouts 1.15 +import org.kde.kirigami 2.15 as Kirigami + +QQC2.RadioButton { + id: delegate + + implicitWidth: contentItem.implicitWidth + implicitHeight: contentItem.implicitHeight + + property alias innerObject: contentLayout.children + + contentItem: ColumnLayout { + Kirigami.ShadowedRectangle { + implicitWidth: implicitHeight * 1.6 + implicitHeight: Kirigami.Units.gridUnit * 6 + radius: Kirigami.Units.smallSpacing + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.View + + shadow.xOffset: 0 + shadow.yOffset: 2 + shadow.size: 10 + shadow.color: Qt.rgba(0, 0, 0, 0.3) + + color: { + if (delegate.checked) { + return Kirigami.Theme.highlightColor; + } else if (delegate.hovered) { + // Match appearance of hovered list items + return Qt.rgba(Kirigami.Theme.highlightColor.r, + Kirigami.Theme.highlightColor.g, + Kirigami.Theme.highlightColor.b, + 0.5); + } else { + return Kirigami.Theme.backgroundColor; + } + } + ColumnLayout { + id: contentLayout + anchors.fill: parent + anchors.margins: Kirigami.Units.smallSpacing + clip: true + } + } + + QQC2.Label { + id: label + Layout.fillWidth: true + text: delegate.text + horizontalAlignment: Text.AlignHCenter + } + } + + indicator: Item {} + background: Item {} +} + + diff --git a/imports/NeoChat/Settings/qmldir b/imports/NeoChat/Settings/qmldir new file mode 100644 index 000000000..0c35abcfc --- /dev/null +++ b/imports/NeoChat/Settings/qmldir @@ -0,0 +1,2 @@ +module NeoChat.Settings +ThemeRadioButton 1.0 ThemeRadioButton.qml diff --git a/res.qrc b/res.qrc index bcd752443..9043202b2 100644 --- a/res.qrc +++ b/res.qrc @@ -65,5 +65,8 @@ qtquickcontrols2.conf imports/NeoChat/Component/glowdot.png imports/NeoChat/Component/confetti.png + imports/NeoChat/Settings/ThemeRadioButton.qml + imports/NeoChat/Settings/ColorScheme.qml + imports/NeoChat/Settings/qmldir diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0493532c3..677419841 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,7 +43,9 @@ ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png) target_sources(neochat PRIVATE ${NEOCHAT_ICON}) if(NOT ANDROID) - target_sources(neochat PRIVATE trayicon.cpp) + target_sources(neochat PRIVATE trayicon.cpp colorschemer.cpp) + target_link_libraries(neochat PRIVATE KF5::ConfigWidgets) + target_compile_definitions(neochat PRIVATE -DHAVE_COLORSCHEME) endif() target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR}) diff --git a/src/colorschemer.cpp b/src/colorschemer.cpp new file mode 100644 index 000000000..0f8ddecdc --- /dev/null +++ b/src/colorschemer.cpp @@ -0,0 +1,39 @@ +#include +#include + +#include "colorschemer.h" + +ColorSchemer::ColorSchemer(QObject* parent) + : QObject(parent) + , c(new KColorSchemeManager(this)) +{ +} + +ColorSchemer::~ColorSchemer() +{ +} + +QAbstractItemModel *ColorSchemer::model() const +{ + return c->model(); +} + +void ColorSchemer::apply(int idx) +{ + c->activateScheme(c->model()->index(idx, 0)); +} + +void ColorSchemer::apply(const QString &name) +{ + c->activateScheme(c->indexForScheme(name)); +} + +int ColorSchemer::indexForScheme(const QString &name) const +{ + return c->indexForScheme(name).row(); +} + +QString ColorSchemer::nameForIndex(int index) const +{ + return c->model()->data(c->model()->index(index, 0), Qt::DisplayRole).toString(); +} diff --git a/src/colorschemer.h b/src/colorschemer.h new file mode 100644 index 000000000..e6f706969 --- /dev/null +++ b/src/colorschemer.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +class QAbstractItemModel; +class KColorSchemeManager; + +class ColorSchemer : public QObject +{ + Q_OBJECT + Q_PROPERTY(QAbstractItemModel *model READ model CONSTANT) +public: + ColorSchemer(QObject* parent = nullptr); + ~ColorSchemer(); + + QAbstractItemModel* model() const; + Q_INVOKABLE void apply(int idx); + Q_INVOKABLE void apply(const QString &name); + Q_INVOKABLE int indexForScheme(const QString &name) const; + Q_INVOKABLE QString nameForIndex(int index) const; + +private: + KColorSchemeManager* c; +}; + diff --git a/src/main.cpp b/src/main.cpp index 214094e87..45108d75e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,6 +57,9 @@ #include "userdirectorylistmodel.h" #include "userlistmodel.h" #include "webshortcutmodel.h" +#ifdef HAVE_COLORSCHEME +#include "colorschemer.h" +#endif using namespace Quotient; @@ -137,6 +140,14 @@ int main(int argc, char *argv[]) Login *login = new Login(); ChatBoxHelper chatBoxHelper; +#ifdef HAVE_COLORSCHEME + ColorSchemer colorScheme; + qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "ColorSchemer", &colorScheme); + if (!config->colorScheme().isEmpty()) { + colorScheme.apply(config->colorScheme()); + } +#endif + qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Controller", &Controller::instance()); qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Clipboard", &clipboard); qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Config", config); diff --git a/src/neochatconfig.kcfg b/src/neochatconfig.kcfg index 75feeeb61..b3a215221 100644 --- a/src/neochatconfig.kcfg +++ b/src/neochatconfig.kcfg @@ -14,6 +14,9 @@ + + + true @@ -36,6 +39,10 @@ true + + + false + true