diff --git a/CMakeLists.txt b/CMakeLists.txt index c15093ad..a8c1e347 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,7 @@ set(SRC_FILES src/RoomMessages.cc src/RoomState.cc src/SideBarActions.cc + src/UserSettingsPage.cc src/Splitter.cc src/Sync.cc src/TextInputWidget.cc @@ -181,6 +182,7 @@ set(SRC_FILES src/ui/RippleOverlay.cc src/ui/OverlayWidget.cc src/ui/TextField.cc + src/ui/ToggleButton.cc src/ui/Theme.cc src/ui/ThemeManager.cc ) @@ -239,6 +241,7 @@ qt5_wrap_cpp(MOC_HEADERS include/RoomInfoListItem.h include/RoomList.h include/SideBarActions.h + include/UserSettingsPage.h include/Splitter.h include/TextInputWidget.h include/TimelineItem.h @@ -262,6 +265,7 @@ qt5_wrap_cpp(MOC_HEADERS include/ui/Ripple.h include/ui/RippleOverlay.h include/ui/TextField.h + include/ui/ToggleButton.h include/ui/Theme.h include/ui/ThemeManager.h ) diff --git a/include/ChatPage.h b/include/ChatPage.h index 849d60e7..f7d2e1a5 100644 --- a/include/ChatPage.h +++ b/include/ChatPage.h @@ -68,6 +68,7 @@ signals: void unreadMessages(int count); void showNotification(const QString &msg); void showLoginPage(const QString &msg); + void showUserSettingsPage(); private slots: void showUnreadMessageNotification(int count); diff --git a/include/MainWindow.h b/include/MainWindow.h index f56592c2..21530d07 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -30,6 +30,8 @@ class OverlayModal; class RegisterPage; class SnackBar; class TrayIcon; +class UserSettingsPage; +class UserSettings; class WelcomePage; class MainWindow : public QMainWindow @@ -59,6 +61,7 @@ private slots: // Show the register page in the main window. void showRegisterPage(); + void showUserSettingsPage(); // Show the chat page and start communicating with the given access token. void showChatPage(QString user_id, QString home_server, QString token); @@ -85,6 +88,8 @@ private: // The main chat area. ChatPage *chat_page_; + UserSettingsPage *userSettingsPage_; + QSharedPointer userSettings_; // Used to hide undefined states between page transitions. QSharedPointer progressModal_; diff --git a/include/SideBarActions.h b/include/SideBarActions.h index 7b550578..bf48c4da 100644 --- a/include/SideBarActions.h +++ b/include/SideBarActions.h @@ -14,6 +14,9 @@ public: SideBarActions(QWidget *parent = nullptr); ~SideBarActions(); +signals: + void showSettings(); + protected: void resizeEvent(QResizeEvent *event) override; diff --git a/include/UserSettingsPage.h b/include/UserSettingsPage.h new file mode 100644 index 00000000..9a4c01dd --- /dev/null +++ b/include/UserSettingsPage.h @@ -0,0 +1,80 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include + +class Toggle; + +constexpr int OptionMargin = 6; +constexpr int LayoutSideMargin = 300; + +class UserSettings +{ +public: + UserSettings(); + + void save(); + void load(); + void setTheme(QString theme) { theme_ = theme; } + void setTray(bool state) { isTrayEnabled_ = state; } + + QString theme() const { return !theme_.isEmpty() ? theme_ : "default"; } + bool isTrayEnabled() const { return isTrayEnabled_; } + +private: + QString theme_; + bool isTrayEnabled_; +}; + +class HorizontalLine : public QFrame +{ + Q_OBJECT + +public: + HorizontalLine(QWidget *parent = nullptr); +}; + +class UserSettingsPage : public QWidget +{ + Q_OBJECT + +public: + UserSettingsPage(QSharedPointer settings, QWidget *parent = 0); + +protected: + void showEvent(QShowEvent *event) override; + +signals: + void moveBack(); + +private: + // Layouts + QVBoxLayout *topLayout_; + QHBoxLayout *topBarLayout_; + + // Shared settings object. + QSharedPointer settings_; + + Toggle *trayToggle_; + QComboBox *themeCombo_; +}; diff --git a/include/ui/ToggleButton.h b/include/ui/ToggleButton.h new file mode 100644 index 00000000..14c3450b --- /dev/null +++ b/include/ui/ToggleButton.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include + +class ToggleTrack; +class ToggleThumb; + +enum class Position +{ + Left, + Right +}; + +class Toggle : public QAbstractButton +{ + Q_OBJECT + + Q_PROPERTY(QColor activeColor WRITE setActiveColor READ activeColor) + Q_PROPERTY(QColor disabledColor WRITE setDisabledColor READ disabledColor) + Q_PROPERTY(QColor inactiveColor WRITE setInactiveColor READ inactiveColor) + Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor) + +public: + Toggle(QWidget *parent = nullptr); + + void setState(bool isEnabled); + + void setActiveColor(const QColor &color); + void setDisabledColor(const QColor &color); + void setInactiveColor(const QColor &color); + void setTrackColor(const QColor &color); + + QColor activeColor() const { return activeColor_; }; + QColor disabledColor() const { return disabledColor_; }; + QColor inactiveColor() const { return inactiveColor_; }; + QColor trackColor() const { return trackColor_.isValid() ? trackColor_ : QColor("#eee"); }; + + QSize sizeHint() const override { return QSize(64, 48); }; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + void init(); + void setupProperties(); + + ToggleTrack *track_; + ToggleThumb *thumb_; + + QColor disabledColor_; + QColor activeColor_; + QColor inactiveColor_; + QColor trackColor_; +}; + +class ToggleThumb : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor thumbColor WRITE setThumbColor READ thumbColor) + +public: + ToggleThumb(Toggle *parent); + + Position shift() const { return position_; }; + qreal offset() const { return offset_; }; + QColor thumbColor() const { return thumbColor_; }; + + void setShift(Position position); + void setThumbColor(const QColor &color) + { + thumbColor_ = color; + update(); + }; + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + void paintEvent(QPaintEvent *event) override; + +private: + void updateOffset(); + + Toggle *const toggle_; + QColor thumbColor_; + + Position position_; + qreal offset_; +}; + +class ToggleTrack : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor trackColor WRITE setTrackColor READ trackColor) + +public: + ToggleTrack(Toggle *parent); + + void setTrackColor(const QColor &color); + QColor trackColor() const { return trackColor_; }; + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + void paintEvent(QPaintEvent *event) override; + +private: + Toggle *const toggle_; + QColor trackColor_; +}; diff --git a/src/ChatPage.cc b/src/ChatPage.cc index d81b64fb..e32b288a 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -75,6 +75,8 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) sideBarMainLayout_->setMargin(0); sidebarActions_ = new SideBarActions(this); + connect( + sidebarActions_, &SideBarActions::showSettings, this, &ChatPage::showUserSettingsPage); sideBarLayout_->addLayout(sideBarTopLayout_); sideBarLayout_->addLayout(sideBarMainLayout_); diff --git a/src/MainWindow.cc b/src/MainWindow.cc index 92388ae4..5c188903 100644 --- a/src/MainWindow.cc +++ b/src/MainWindow.cc @@ -31,6 +31,7 @@ #include "RegisterPage.h" #include "SnackBar.h" #include "TrayIcon.h" +#include "UserSettingsPage.h" #include "WelcomePage.h" MainWindow *MainWindow::instance_ = nullptr; @@ -54,13 +55,15 @@ MainWindow::MainWindow(QWidget *parent) font.setStyleStrategy(QFont::PreferAntialias); setFont(font); - client_ = QSharedPointer(new MatrixClient("matrix.org")); - trayIcon_ = new TrayIcon(":/logos/nheko-32.png", this); + client_ = QSharedPointer(new MatrixClient("matrix.org")); + userSettings_ = QSharedPointer(new UserSettings); + trayIcon_ = new TrayIcon(":/logos/nheko-32.png", this); - welcome_page_ = new WelcomePage(this); - login_page_ = new LoginPage(client_, this); - register_page_ = new RegisterPage(client_, this); - chat_page_ = new ChatPage(client_, this); + welcome_page_ = new WelcomePage(this); + login_page_ = new LoginPage(client_, this); + register_page_ = new RegisterPage(client_, this); + chat_page_ = new ChatPage(client_, this); + userSettingsPage_ = new UserSettingsPage(userSettings_, this); // Initialize sliding widget manager. pageStack_ = new QStackedWidget(this); @@ -68,6 +71,7 @@ MainWindow::MainWindow(QWidget *parent) pageStack_->addWidget(login_page_); pageStack_->addWidget(register_page_); pageStack_->addWidget(chat_page_); + pageStack_->addWidget(userSettingsPage_); setCentralWidget(pageStack_); @@ -86,12 +90,18 @@ MainWindow::MainWindow(QWidget *parent) showLoginPage(); }); + connect(userSettingsPage_, &UserSettingsPage::moveBack, this, [=]() { + pageStack_->setCurrentWidget(chat_page_); + }); + connect(trayIcon_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(iconActivated(QSystemTrayIcon::ActivationReason))); connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar())); + connect( + chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage); connect(client_.data(), SIGNAL(loginSuccess(QString, QString, QString)), @@ -234,6 +244,12 @@ MainWindow::showRegisterPage() pageStack_->setCurrentWidget(register_page_); } +void +MainWindow::showUserSettingsPage() +{ + pageStack_->setCurrentWidget(userSettingsPage_); +} + void MainWindow::closeEvent(QCloseEvent *event) { diff --git a/src/SideBarActions.cc b/src/SideBarActions.cc index 1484bd00..3898eb22 100644 --- a/src/SideBarActions.cc +++ b/src/SideBarActions.cc @@ -45,6 +45,8 @@ SideBarActions::SideBarActions(QWidget *parent) layout_->addWidget(createRoomBtn_); layout_->addWidget(joinRoomBtn_); layout_->addWidget(settingsBtn_); + + connect(settingsBtn_, &QPushButton::clicked, this, &SideBarActions::showSettings); } SideBarActions::~SideBarActions() {} diff --git a/src/UserSettingsPage.cc b/src/UserSettingsPage.cc new file mode 100644 index 00000000..ff4714f5 --- /dev/null +++ b/src/UserSettingsPage.cc @@ -0,0 +1,140 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include + +#include "Config.h" +#include "FlatButton.h" +#include "UserSettingsPage.h" +#include + +UserSettings::UserSettings() { load(); } + +void +UserSettings::load() +{ + QSettings settings; + isTrayEnabled_ = settings.value("user/tray", true).toBool(); + theme_ = settings.value("user/theme", "default").toString(); +} + +void +UserSettings::save() +{ + QSettings settings; + settings.beginGroup("user"); + settings.setValue("tray", isTrayEnabled_); + settings.setValue("theme", theme()); + settings.endGroup(); +} + +HorizontalLine::HorizontalLine(QWidget *parent) + : QFrame{ parent } +{ + setFrameShape(QFrame::HLine); + setFrameShadow(QFrame::Sunken); +} + +UserSettingsPage::UserSettingsPage(QSharedPointer settings, QWidget *parent) + : QWidget{ parent } + , settings_{ settings } +{ + topLayout_ = new QVBoxLayout(this); + + QIcon icon; + icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png"); + + auto backBtn_ = new FlatButton(this); + backBtn_->setMinimumSize(QSize(24, 24)); + backBtn_->setIcon(icon); + backBtn_->setIconSize(QSize(24, 24)); + + auto heading_ = new QLabel(tr("User Settings")); + heading_->setFont(QFont("Open Sans Bold", 22)); + + topBarLayout_ = new QHBoxLayout; + topBarLayout_->setSpacing(0); + topBarLayout_->setMargin(0); + topBarLayout_->addWidget(backBtn_, 1, Qt::AlignLeft | Qt::AlignVCenter); + topBarLayout_->addWidget(heading_, 0, Qt::AlignBottom); + topBarLayout_->addStretch(1); + + auto trayOptionLayout_ = new QHBoxLayout; + trayOptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin); + auto trayLabel = new QLabel(tr("Minimize to tray"), this); + trayToggle_ = new Toggle(this); + trayToggle_->setActiveColor(QColor("#38A3D8")); + trayToggle_->setInactiveColor(QColor("gray")); + trayLabel->setFont(QFont("Open Sans", 15)); + + trayOptionLayout_->addWidget(trayLabel); + trayOptionLayout_->addWidget(trayToggle_, 0, Qt::AlignBottom | Qt::AlignRight); + + auto themeOptionLayout_ = new QHBoxLayout; + themeOptionLayout_->setContentsMargins(0, OptionMargin, 0, OptionMargin); + auto themeLabel_ = new QLabel(tr("App theme"), this); + themeCombo_ = new QComboBox(this); + themeCombo_->addItem("Default"); + themeCombo_->addItem("System"); + themeLabel_->setFont(QFont("Open Sans", 15)); + + themeOptionLayout_->addWidget(themeLabel_); + themeOptionLayout_->addWidget(themeCombo_, 0, Qt::AlignBottom | Qt::AlignRight); + + auto general_ = new QLabel(tr("GENERAL"), this); + general_->setFont(QFont("Open Sans Bold", 17)); + general_->setStyleSheet("color: #5d6565"); + + auto mainLayout_ = new QVBoxLayout; + mainLayout_->setSpacing(7); + mainLayout_->setContentsMargins( + LayoutSideMargin, LayoutSideMargin / 6, LayoutSideMargin, LayoutSideMargin / 6); + mainLayout_->addWidget(general_, 1, Qt::AlignLeft | Qt::AlignVCenter); + mainLayout_->addWidget(new HorizontalLine(this)); + mainLayout_->addLayout(trayOptionLayout_); + mainLayout_->addWidget(new HorizontalLine(this)); + mainLayout_->addLayout(themeOptionLayout_); + mainLayout_->addWidget(new HorizontalLine(this)); + + topLayout_->addLayout(topBarLayout_); + topLayout_->addLayout(mainLayout_); + topLayout_->addStretch(1); + + connect(themeCombo_, + static_cast(&QComboBox::activated), + [=](const QString &text) { settings_->setTheme(text.toLower()); }); + + connect(trayToggle_, &Toggle::toggled, this, [=](bool isEnabled) { + settings_->setTray(isEnabled); + }); + + connect(backBtn_, &QPushButton::clicked, this, [=]() { + settings_->save(); + emit moveBack(); + }); +} + +void +UserSettingsPage::showEvent(QShowEvent *) +{ + themeCombo_->setCurrentIndex((settings_->theme() == "default" ? 0 : 1)); + trayToggle_->setState(settings_->isTrayEnabled()); +} diff --git a/src/ui/ToggleButton.cc b/src/ui/ToggleButton.cc new file mode 100644 index 00000000..8054bfe7 --- /dev/null +++ b/src/ui/ToggleButton.cc @@ -0,0 +1,212 @@ +#include +#include +#include +#include + +#include "ToggleButton.h" + +void +Toggle::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); +} + +Toggle::Toggle(QWidget *parent) + : QAbstractButton{ parent } +{ + init(); + + connect(this, &QAbstractButton::toggled, this, &Toggle::setState); +} + +void +Toggle::setState(bool isEnabled) +{ + thumb_->setShift(isEnabled ? Position::Right : Position::Left); + setupProperties(); +} + +void +Toggle::init() +{ + track_ = new ToggleTrack(this); + thumb_ = new ToggleThumb(this); + + setCursor(QCursor(Qt::PointingHandCursor)); + setCheckable(true); + setChecked(false); + setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + + setState(false); + setupProperties(); + + QCoreApplication::processEvents(); +} + +void +Toggle::setupProperties() +{ + if (isEnabled()) { + Position position = thumb_->shift(); + + thumb_->setThumbColor(trackColor()); + + if (position == Position::Left) + track_->setTrackColor(activeColor()); + else if (position == Position::Right) + track_->setTrackColor(inactiveColor()); + } + + update(); +} + +void +Toggle::setDisabledColor(const QColor &color) +{ + disabledColor_ = color; + setupProperties(); +} + +void +Toggle::setActiveColor(const QColor &color) +{ + activeColor_ = color; + setupProperties(); +} + +void +Toggle::setInactiveColor(const QColor &color) +{ + inactiveColor_ = color; + setupProperties(); +} + +void +Toggle::setTrackColor(const QColor &color) +{ + trackColor_ = color; + setupProperties(); +} + +ToggleThumb::ToggleThumb(Toggle *parent) + : QWidget{ parent } + , toggle_{ parent } + , position_{ Position::Right } + , offset_{ 0 } +{ + parent->installEventFilter(this); +} + +void +ToggleThumb::setShift(Position position) +{ + if (position_ != position) { + position_ = position; + updateOffset(); + } +} + +bool +ToggleThumb::eventFilter(QObject *obj, QEvent *event) +{ + const QEvent::Type type = event->type(); + + if (QEvent::Resize == type || QEvent::Move == type) { + setGeometry(toggle_->rect().adjusted(8, 8, -8, -8)); + updateOffset(); + } + + return QWidget::eventFilter(obj, event); +} + +void +ToggleThumb::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QBrush brush; + brush.setStyle(Qt::SolidPattern); + brush.setColor(toggle_->isEnabled() ? thumbColor_ : Qt::white); + + painter.setBrush(brush); + painter.setPen(Qt::NoPen); + + int s; + QRectF r; + + s = height() - 10; + r = QRectF(5 + offset_, 5, s, s); + + painter.drawEllipse(r); + + if (!toggle_->isEnabled()) { + brush.setColor(toggle_->disabledColor()); + painter.setBrush(brush); + painter.drawEllipse(r); + } +} + +void +ToggleThumb::updateOffset() +{ + const QSize s(size()); + offset_ = position_ == Position::Left ? static_cast(s.width() - s.height()) : 0; + update(); +} + +ToggleTrack::ToggleTrack(Toggle *parent) + : QWidget{ parent } + , toggle_{ parent } +{ + Q_ASSERT(parent); + + parent->installEventFilter(this); +} + +void +ToggleTrack::setTrackColor(const QColor &color) +{ + trackColor_ = color; + update(); +} + +bool +ToggleTrack::eventFilter(QObject *obj, QEvent *event) +{ + const QEvent::Type type = event->type(); + + if (QEvent::Resize == type || QEvent::Move == type) { + setGeometry(toggle_->rect()); + } + + return QWidget::eventFilter(obj, event); +} + +void +ToggleTrack::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QBrush brush; + if (toggle_->isEnabled()) { + brush.setColor(trackColor_); + painter.setOpacity(0.8); + } else { + brush.setColor(toggle_->disabledColor()); + painter.setOpacity(0.6); + } + + brush.setStyle(Qt::SolidPattern); + painter.setBrush(brush); + painter.setPen(Qt::NoPen); + + const int h = height() / 2; + const QRect r(0, h / 2, width(), h); + painter.drawRoundedRect(r.adjusted(14, 4, -14, -4), h / 2 - 4, h / 2 - 4); +}