diff --git a/qtox.pro b/qtox.pro index 3eec7904d..923317fd0 100644 --- a/qtox.pro +++ b/qtox.pro @@ -495,7 +495,10 @@ SOURCES += \ src/widget/friendlistlayout.cpp \ src/widget/genericchatitemlayout.cpp \ src/widget/categorywidget.cpp \ - src/widget/tool/removefrienddialog.cpp + src/widget/tool/removefrienddialog.cpp \ + src/widget/contentlayout.cpp \ + src/widget/contentdialog.cpp \ + src/widget/tool/activatedialog.cpp HEADERS += \ src/audio/audio.h \ @@ -539,4 +542,7 @@ HEADERS += \ src/widget/friendlistlayout.h \ src/widget/genericchatitemlayout.h \ src/widget/categorywidget.h \ + src/widget/contentlayout.h \ + src/widget/contentdialog.h \ + src/widget/tool/activatedialog.h \ src/widget/tool/removefrienddialog.h diff --git a/src/group.cpp b/src/group.cpp index a9a9f68a1..893d08b32 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -84,6 +84,7 @@ void Group::updatePeer(int peerId, QString name) widget->onUserListChanged(); chatForm->onUserListChanged(); + emit userListChanged(getGroupWidget()); } void Group::setName(const QString& name) @@ -91,7 +92,14 @@ void Group::setName(const QString& name) chatForm->setName(name); if (widget->isActive()) - GUI::setWindowTitle(name); + GUI::setWindowTitle(name); + + emit titleChanged(this->getGroupWidget()); +} + +QString Group::getName() const +{ + return widget->getName(); } void Group::regeneratePeerList() @@ -117,6 +125,7 @@ void Group::regeneratePeerList() widget->onUserListChanged(); chatForm->onUserListChanged(); + emit userListChanged(getGroupWidget()); } bool Group::isAvGroupchat() const diff --git a/src/group.h b/src/group.h index 8ccde2497..8d029bf71 100644 --- a/src/group.h +++ b/src/group.h @@ -61,9 +61,14 @@ public: void updatePeer(int peerId, QString newName); void setName(const QString& name); + QString getName() const; QString resolveToxId(const ToxId &id) const; +signals: + void titleChanged(GroupWidget* widget); + void userListChanged(GroupWidget* widget); + private: GroupWidget* widget; GroupChatForm* chatForm; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index b419431c6..32e6dc777 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -7,7 +7,7 @@ 0 0 775 - 537 + 420 @@ -1288,555 +1288,6 @@ QSplitter:handle{ - - - - 0 - 0 - - - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 127 - 127 - 127 - - - - - - - 170 - 170 - 170 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 127 - 127 - 127 - - - - - - - 170 - 170 - 170 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - - - 127 - 127 - 127 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 127 - 127 - 127 - - - - - - - 170 - 170 - 170 - - - - - - - 127 - 127 - 127 - - - - - - - 255 - 255 - 255 - - - - - - - 127 - 127 - 127 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 255 - - - - - - - 0 - 0 - 0 - - - - - - - 255 - 255 - 255 - - - - - - - 255 - 255 - 220 - - - - - - - 0 - 0 - 0 - - - - - - - - true - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - - 0 - 57 - - - - true - - - - - - - - 0 - 1 - - - - - 16777215 - 1 - - - - - - - - - 193 - 193 - 193 - - - - - - - - - 193 - 193 - 193 - - - - - - - - - 127 - 127 - 127 - - - - - - - - QFrame::HLine - - - QFrame::Plain - - - - - - - - 0 - 0 - - - - - 375 - 0 - - - - - - diff --git a/src/nexus.cpp b/src/nexus.cpp index 9398f7c45..93f7ebda3 100644 --- a/src/nexus.cpp +++ b/src/nexus.cpp @@ -42,6 +42,8 @@ #ifdef Q_OS_MAC #include #include +#include +#include #endif static Nexus* nexus{nullptr}; @@ -89,26 +91,44 @@ void Nexus::start() qRegisterMetaType("ToxFile::FileDirection"); qRegisterMetaType>("std::shared_ptr"); + loginScreen = new LoginScreen(); + #ifdef Q_OS_MAC globalMenuBar = new QMenuBar(0); + dockMenu = new QMenu(globalMenuBar); - windowMenu = globalMenuBar->addMenu(tr("Window")); + viewMenu = globalMenuBar->addMenu(QString()); + + windowMenu = globalMenuBar->addMenu(QString()); globalMenuBar->addAction(windowMenu->menuAction()); - QAction* minimizeAction = Nexus::getInstance().windowMenu->addAction(tr("Minimize")); + fullscreenAction = viewMenu->addAction(QString()); + fullscreenAction->setShortcut(QKeySequence::FullScreen); + connect(fullscreenAction, &QAction::triggered, this, &Nexus::toggleFullscreen); + + minimizeAction = windowMenu->addAction(QString()); minimizeAction->setShortcut(Qt::CTRL + Qt::Key_M); - connect(minimizeAction, &QAction::triggered, [minimizeAction]() + connect(minimizeAction, &QAction::triggered, [this]() { + minimizeAction->setEnabled(false); QApplication::focusWindow()->showMinimized(); }); - Nexus::getInstance().windowMenu->addSeparator(); + + windowMenu->addSeparator(); + frontAction = windowMenu->addAction(QString()); + connect(frontAction, &QAction::triggered, this, &Nexus::bringAllToFront); QAction* quitAction = new QAction(globalMenuBar); quitAction->setMenuRole(QAction::QuitRole); connect(quitAction, &QAction::triggered, qApp, &QApplication::quit); -#endif - loginScreen = new LoginScreen(); + windowMapper = new QSignalMapper(this); + connect(windowMapper, SIGNAL(mapped(QObject*)), this, SLOT(onOpenWindow(QObject*))); + + connect(loginScreen, &LoginScreen::windowStateChanged, this, &Nexus::onWindowStateChanged); + + retranslateUi(); +#endif if (profile) showMainGUI(); @@ -278,3 +298,131 @@ bool Nexus::tryRemoveFile(const QString& filepath) tmp.remove(); return writable; } + +#ifdef Q_OS_MAC +void Nexus::retranslateUi() +{ + viewMenu->menuAction()->setText(tr("View", "OS X Menu bar")); + windowMenu->menuAction()->setText(tr("Window", "OS X Menu bar")); + minimizeAction->setText(tr("Minimize", "OS X Menu bar")); + frontAction->setText((tr("Bring All to Front", "OS X Menu bar"))); +} + +void Nexus::onWindowStateChanged(Qt::WindowStates state) +{ + minimizeAction->setEnabled(QApplication::activeWindow() != nullptr); + + if (QApplication::activeWindow() != nullptr && sender() == QApplication::activeWindow()) + { + if (state & Qt::WindowFullScreen) + minimizeAction->setEnabled(false); + + if (state & Qt::WindowFullScreen) + fullscreenAction->setText(tr("Exit Fullscreen")); + else + fullscreenAction->setText(tr("Enter Fullscreen")); + + updateWindows(); + } + + updateWindowsStates(); +} + +void Nexus::updateWindows() +{ + updateWindowsArg(nullptr); +} + +void Nexus::updateWindowsArg(QWindow* closedWindow) +{ + QWindowList windowList = QApplication::topLevelWindows(); + delete windowActions; + windowActions = new QActionGroup(this); + + windowMenu->addSeparator(); + + QAction* dockLast; + if (dockMenu->actions().count() != 0) + dockLast = dockMenu->actions().first(); + else + dockLast = nullptr; + + QWindow* activeWindow; + + if (QApplication::activeWindow()) + activeWindow = QApplication::activeWindow()->windowHandle(); + else + activeWindow = nullptr; + + for (int i = 0; i < windowList.size(); ++i) + { + if (closedWindow == windowList[i]) + continue; + + QAction* action = windowActions->addAction(windowList[i]->title()); + action->setCheckable(true); + action->setChecked(windowList[i] == activeWindow); + connect(action, SIGNAL(triggered()), windowMapper, SLOT(map())); + windowMapper->setMapping(action, windowList[i]); + windowMenu->addAction(action); + dockMenu->insertAction(dockLast, action); + } + + if (!dockLast->isSeparator()) + dockMenu->insertSeparator(dockLast); +} + +void Nexus::updateWindowsClosed() +{ + updateWindowsArg(static_cast(sender())->windowHandle()); +} + +void Nexus::updateWindowsStates() +{ + bool exists = false; + QWindowList windowList = QApplication::topLevelWindows(); + + for (QWindow* window : windowList) + { + if (!(window->windowState() & Qt::WindowMinimized)) + { + exists = true; + break; + } + } + + frontAction->setEnabled(exists); +} + +void Nexus::onOpenWindow(QObject* object) +{ + QWindow* window = static_cast(object); + + if (window->windowState() & QWindow::Minimized) + window->showNormal(); + + window->raise(); + window->requestActivate(); +} + +void Nexus::toggleFullscreen() +{ + QWidget* window = QApplication::activeWindow(); + + if (window->isFullScreen()) + window->showNormal(); + else + window->showFullScreen(); +} + +void Nexus::bringAllToFront() +{ + QWindowList windowList = QApplication::topLevelWindows(); + QWindow* focused = QApplication::focusWindow(); + + for (QWindow* window : windowList) + window->raise(); + + focused->raise(); +} +#endif diff --git a/src/nexus.h b/src/nexus.h index 53b2a0df2..95f132223 100644 --- a/src/nexus.h +++ b/src/nexus.h @@ -32,6 +32,10 @@ class Core; #ifdef Q_OS_MAC class QMenuBar; class QMenu; +class QAction; +class QWindow; +class QActionGroup; +class QSignalMapper; #endif /// This class is in charge of connecting various systems together @@ -59,7 +63,28 @@ public: #ifdef Q_OS_MAC QMenuBar* globalMenuBar; + QMenu* viewMenu; QMenu* windowMenu; + QAction* minimizeAction; + QAction* fullscreenAction; + QAction* frontAction; + QMenu* dockMenu; + +public slots: + void retranslateUi(); + void onWindowStateChanged(Qt::WindowStates state); + void updateWindows(); + void updateWindowsClosed(); + void updateWindowsStates(); + void onOpenWindow(QObject* object); + void toggleFullscreen(); + void bringAllToFront(); + +private: + void updateWindowsArg(QWindow *closedWindow); + + QSignalMapper* windowMapper; + QActionGroup* windowActions = nullptr; #endif private: diff --git a/src/persistence/settings.cpp b/src/persistence/settings.cpp index d480d92d4..be4d7c41f 100644 --- a/src/persistence/settings.cpp +++ b/src/persistence/settings.cpp @@ -174,6 +174,8 @@ void Settings::loadGlobal() globalAutoAcceptDir = s.value("globalAutoAcceptDir", QStandardPaths::locate(QStandardPaths::HomeLocation, QString(), QStandardPaths::LocateDirectory) ).toString(); + separateWindow = s.value("separateWindow", false).toBool(); + dontGroupWindows = s.value("dontGroupWindows", true).toBool(); groupchatPosition = s.value("groupchatPosition", true).toBool(); s.endGroup(); @@ -216,6 +218,9 @@ void Settings::loadGlobal() windowGeometry = s.value("windowGeometry", QByteArray()).toByteArray(); windowState = s.value("windowState", QByteArray()).toByteArray(); splitterState = s.value("splitterState", QByteArray()).toByteArray(); + dialogGeometry = s.value("dialogGeometry", QByteArray()).toByteArray(); + dialogSplitterState = s.value("dialogSplitterState", QByteArray()).toByteArray(); + dialogSettingsGeometry = s.value("dialogSettingsGeometry", QByteArray()).toByteArray(); s.endGroup(); s.beginGroup("Audio"); @@ -375,6 +380,8 @@ void Settings::saveGlobal() s.setValue("notifySound", notifySound); s.setValue("groupAlwaysNotify", groupAlwaysNotify); s.setValue("fauxOfflineMessaging", fauxOfflineMessaging); + s.setValue("separateWindow", separateWindow); + s.setValue("dontGroupWindows", dontGroupWindows); s.setValue("groupchatPosition", groupchatPosition); s.setValue("autoSaveEnabled", autoSaveEnabled); s.setValue("globalAutoAcceptDir", globalAutoAcceptDir); @@ -410,6 +417,9 @@ void Settings::saveGlobal() s.setValue("windowGeometry", windowGeometry); s.setValue("windowState", windowState); s.setValue("splitterState", splitterState); + s.setValue("dialogGeometry", dialogGeometry); + s.setValue("dialogSplitterState", dialogSplitterState); + s.setValue("dialogSettingsGeometry", dialogSettingsGeometry); s.endGroup(); s.beginGroup("Audio"); @@ -746,7 +756,7 @@ void Settings::setStatusChangeNotificationEnabled(bool newValue) bool Settings::getShowInFront() const { QMutexLocker locker{&bigLock}; - return showInFront; + return showInFront; } void Settings::setShowInFront(bool newValue) @@ -1090,6 +1100,42 @@ void Settings::setSplitterState(const QByteArray &value) splitterState = value; } +QByteArray Settings::getDialogGeometry() const +{ + QMutexLocker locker{&bigLock}; + return dialogGeometry; +} + +void Settings::setDialogGeometry(const QByteArray &value) +{ + QMutexLocker locker{&bigLock}; + dialogGeometry = value; +} + +QByteArray Settings::getDialogSplitterState() const +{ + QMutexLocker locker{&bigLock}; + return dialogSplitterState; +} + +void Settings::setDialogSplitterState(const QByteArray &value) +{ + QMutexLocker locker{&bigLock}; + dialogSplitterState = value; +} + +QByteArray Settings::getDialogSettingsGeometry() const +{ + QMutexLocker locker{&bigLock}; + return dialogSettingsGeometry; +} + +void Settings::setDialogSettingsGeometry(const QByteArray &value) +{ + QMutexLocker locker{&bigLock}; + dialogSettingsGeometry = value; +} + bool Settings::isMinimizeOnCloseEnabled() const { QMutexLocker locker{&bigLock}; @@ -1324,6 +1370,30 @@ void Settings::setCompactLayout(bool value) compactLayout = value; } +bool Settings::getSeparateWindow() const +{ + QMutexLocker locker{&bigLock}; + return separateWindow; +} + +void Settings::setSeparateWindow(bool value) +{ + QMutexLocker locker{&bigLock}; + separateWindow = value; +} + +bool Settings::getDontGroupWindows() const +{ + QMutexLocker locker{&bigLock}; + return dontGroupWindows; +} + +void Settings::setDontGroupWindows(bool value) +{ + QMutexLocker locker{&bigLock}; + dontGroupWindows = value; +} + bool Settings::getGroupchatPosition() const { QMutexLocker locker{&bigLock}; diff --git a/src/persistence/settings.h b/src/persistence/settings.h index cd9ca5715..16be7f728 100644 --- a/src/persistence/settings.h +++ b/src/persistence/settings.h @@ -217,6 +217,15 @@ public: QByteArray getSplitterState() const; void setSplitterState(const QByteArray &value); + QByteArray getDialogGeometry() const; + void setDialogGeometry(const QByteArray& value); + + QByteArray getDialogSplitterState() const; + void setDialogSplitterState(const QByteArray &value); + + QByteArray getDialogSettingsGeometry() const; + void setDialogSettingsGeometry(const QByteArray& value); + QString getFriendAdress(const QString &publicKey) const; void updateFriendAdress(const QString &newAddr); @@ -237,6 +246,12 @@ public: bool getCompactLayout() const; void setCompactLayout(bool compact); + bool getSeparateWindow() const; + void setSeparateWindow(bool value); + + bool getDontGroupWindows() const; + void setDontGroupWindows(bool value); + bool getGroupchatPosition() const; void setGroupchatPosition(bool value); @@ -295,6 +310,8 @@ private: bool fauxOfflineMessaging; bool compactLayout; bool groupchatPosition; + bool separateWindow; + bool dontGroupWindows; bool enableIPv6; QString translation; bool makeToxPortable; @@ -334,6 +351,9 @@ private: QByteArray windowGeometry; QByteArray windowState; QByteArray splitterState; + QByteArray dialogGeometry; + QByteArray dialogSplitterState; + QByteArray dialogSettingsGeometry; QString style; bool showSystemTray; diff --git a/src/widget/circlewidget.cpp b/src/widget/circlewidget.cpp index 90225a194..a0c83701b 100644 --- a/src/widget/circlewidget.cpp +++ b/src/widget/circlewidget.cpp @@ -24,6 +24,7 @@ #include "src/persistence/settings.h" #include "src/friendlist.h" #include "src/friend.h" +#include "src/widget/contentdialog.h" #include "widget.h" #include #include @@ -84,29 +85,66 @@ void CircleWidget::contextMenuEvent(QContextMenuEvent* event) QMenu menu; QAction* renameAction = menu.addAction(tr("Rename circle", "Menu for renaming a circle")); QAction* removeAction = menu.addAction(tr("Remove circle", "Menu for removing a circle")); + QAction* openAction = nullptr; + + if (friendOfflineLayout()->count() + friendOnlineLayout()->count() > 0) + openAction = menu.addAction(tr("Open all in new window")); QAction* selectedItem = menu.exec(mapToGlobal(event->pos())); - if (selectedItem == renameAction) + + if (selectedItem) { - editName(); + if (selectedItem == renameAction) + { + editName(); + } + else if (selectedItem == removeAction) + { + FriendListWidget* friendList = static_cast(parentWidget()); + moveFriendWidgets(friendList); + + friendList->removeCircleWidget(this); + + int replacedCircle = Settings::getInstance().removeCircle(id); + + auto circleReplace = circleList.find(replacedCircle); + if (circleReplace != circleList.end()) + circleReplace.value()->updateID(id); + else + assert(true); // This should never happen. + + circleList.remove(replacedCircle); + } + else if (selectedItem == openAction) + { + ContentDialog* dialog = Widget::getInstance()->createContentDialog(); + + for (int i = 0; i < friendOnlineLayout()->count(); ++i) + { + FriendWidget* friendWidget = dynamic_cast(friendOnlineLayout()->itemAt(i)->widget()); + + if (friendWidget != nullptr) + { + Friend* f = friendWidget->getFriend(); + dialog->addFriend(friendWidget->friendId, f->getDisplayedName()); + } + } + for (int i = 0; i < friendOfflineLayout()->count(); ++i) + { + FriendWidget* friendWidget = dynamic_cast(friendOfflineLayout()->itemAt(i)->widget()); + + if (friendWidget != nullptr) + { + Friend* f = friendWidget->getFriend(); + dialog->addFriend(friendWidget->friendId, f->getDisplayedName()); + } + } + + dialog->show(); + dialog->ensureSplitterVisible(); + } } - else if (selectedItem == removeAction) - { - FriendListWidget* friendList = static_cast(parentWidget()); - moveFriendWidgets(friendList); - friendList->removeCircleWidget(this); - - int replacedCircle = Settings::getInstance().removeCircle(id); - - auto circleReplace = circleList.find(replacedCircle); - if (circleReplace != circleList.end()) - circleReplace.value()->updateID(id); - else - assert(true); // This should never happen. - - circleList.remove(replacedCircle); - } setContainerAttribute(Qt::WA_UnderMouse, false); } @@ -181,14 +219,14 @@ void CircleWidget::updateID(int index) for (int i = 0; i < friendOnlineLayout()->count(); ++i) { - FriendWidget* friendWidget = dynamic_cast(friendOnlineLayout()->itemAt(i)); + FriendWidget* friendWidget = dynamic_cast(friendOnlineLayout()->itemAt(i)->widget()); if (friendWidget != nullptr) Settings::getInstance().setFriendCircleID(FriendList::findFriend(friendWidget->friendId)->getToxId(), id); } for (int i = 0; i < friendOfflineLayout()->count(); ++i) { - FriendWidget* friendWidget = dynamic_cast(friendOfflineLayout()->itemAt(i)); + FriendWidget* friendWidget = dynamic_cast(friendOfflineLayout()->itemAt(i)->widget()); if (friendWidget != nullptr) Settings::getInstance().setFriendCircleID(FriendList::findFriend(friendWidget->friendId)->getToxId(), id); diff --git a/src/widget/contentdialog.cpp b/src/widget/contentdialog.cpp new file mode 100644 index 000000000..86e2e3cdc --- /dev/null +++ b/src/widget/contentdialog.cpp @@ -0,0 +1,769 @@ +/* + Copyright © 2015 by The qTox Project + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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. + + qTox 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 qTox. If not, see . +*/ + +#include "contentdialog.h" +#include "contentlayout.h" +#include "friendwidget.h" +#include "groupwidget.h" +#include "style.h" +#include "widget.h" +#include "tool/adjustingscrollarea.h" +#include "src/persistence/settings.h" +#include "src/friend.h" +#include "src/friendlist.h" +#include "src/group.h" +#include "src/grouplist.h" +#include "src/widget/form/chatform.h" +#include "src/core/core.h" +#include "src/widget/friendlistlayout.h" +#include "src/widget/form/settingswidget.h" +#include "src/widget/translator.h" +#include +#include +#include +#include +#include +#include + +ContentDialog* ContentDialog::currentDialog = nullptr; +QHash> ContentDialog::friendList; +QHash> ContentDialog::groupList; + +ContentDialog::ContentDialog(SettingsWidget* settingsWidget, QWidget* parent) + : ActivateDialog(parent, Qt::Window) + , activeChatroomWidget(nullptr) + , settingsWidget(settingsWidget) +{ + QVBoxLayout* boxLayout = new QVBoxLayout(this); + boxLayout->setMargin(0); + boxLayout->setSpacing(0); + + splitter = new QSplitter(this); + setStyleSheet("QSplitter{color: rgb(255, 255, 255);background-color: rgb(255, 255, 255);alternate-background-color: rgb(255, 255, 255);border-color: rgb(255, 255, 255);gridline-color: rgb(255, 255, 255);selection-color: rgb(255, 255, 255);selection-background-color: rgb(255, 255, 255);}QSplitter:handle{color: rgb(255, 255, 255);background-color: rgb(255, 255, 255);}"); + splitter->setHandleWidth(6); + + QWidget *friendWidget = new QWidget(); + friendWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + friendWidget->setAutoFillBackground(true); + + friendLayout = new FriendListLayout(); + friendLayout->setMargin(0); + friendLayout->setSpacing(0); + friendWidget->setLayout(friendLayout); + + onGroupchatPositionChanged(Settings::getInstance().getGroupchatPosition()); + + QScrollArea *friendScroll = new QScrollArea(this); + friendScroll->setMinimumWidth(220); + friendScroll->setFrameStyle(QFrame::NoFrame); + friendScroll->setLayoutDirection(Qt::RightToLeft); + friendScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + friendScroll->setStyleSheet(Style::getStylesheet(":/ui/friendList/friendList.css")); + friendScroll->setWidgetResizable(true); + friendScroll->setWidget(friendWidget); + + QWidget* contentWidget = new QWidget(this); + contentWidget->setAutoFillBackground(true); + contentLayout = new ContentLayout(contentWidget); + contentLayout->setMargin(0); + contentLayout->setSpacing(0); + + splitter->addWidget(friendScroll); + splitter->addWidget(contentWidget); + splitter->setStretchFactor(1, 1); + splitter->setCollapsible(1, false); + boxLayout->addWidget(splitter); + + connect(splitter, &QSplitter::splitterMoved, this, &ContentDialog::saveSplitterState); + + connect(settingsWidget, &SettingsWidget::groupchatPositionToggled, this, &ContentDialog::onGroupchatPositionChanged); + + setMinimumSize(500, 220); + setAttribute(Qt::WA_DeleteOnClose); + + QByteArray geometry = Settings::getInstance().getDialogGeometry(); + + if (!geometry.isNull()) + restoreGeometry(geometry); + else + resize(720, 400); + + + QByteArray splitterState = Settings::getInstance().getDialogSplitterState(); + + if (!splitterState.isNull()) + splitter->restoreState(splitterState); + + currentDialog = this; + + setAcceptDrops(true); + + new QShortcut(Qt::CTRL + Qt::Key_Q, this, SLOT(close())); + new QShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Tab, this, SLOT(previousContact())); + new QShortcut(Qt::CTRL + Qt::Key_Tab, this, SLOT(nextContact())); + new QShortcut(Qt::CTRL + Qt::Key_PageUp, this, SLOT(previousContact())); + new QShortcut(Qt::CTRL + Qt::Key_PageDown, this, SLOT(nextContact())); + + connect(Core::getInstance(), &Core::usernameSet, this, &ContentDialog::updateTitleUsername); + + Translator::registerHandler(std::bind(&ContentDialog::retranslateUi, this), this); +} + +ContentDialog::~ContentDialog() +{ + if (currentDialog == this) + currentDialog = nullptr; + + auto friendIt = friendList.begin(); + + while (friendIt != friendList.end()) + { + if (std::get<0>(friendIt.value()) == this) + { + friendIt = friendList.erase(friendIt); + continue; + } + ++friendIt; + } + + auto groupIt = groupList.begin(); + + while (groupIt != groupList.end()) + { + if (std::get<0>(groupIt.value()) == this) + { + groupIt = groupList.erase(groupIt); + continue; + } + ++groupIt; + } + + Translator::unregister(this); +} + +FriendWidget* ContentDialog::addFriend(int friendId, QString id) +{ + FriendWidget* friendWidget = new FriendWidget(friendId, id); + friendLayout->addFriendWidget(friendWidget, FriendList::findFriend(friendId)->getStatus()); + + Friend* frnd = friendWidget->getFriend(); + + connect(frnd, &Friend::displayedNameChanged, this, &ContentDialog::updateFriendWidget); + connect(settingsWidget, &SettingsWidget::compactToggled, friendWidget, &FriendWidget::compactChange); + connect(friendWidget, &FriendWidget::chatroomWidgetClicked, this, &ContentDialog::onChatroomWidgetClicked); + connect(friendWidget, SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), frnd->getChatForm(), SLOT(focusInput())); + connect(Core::getInstance(), &Core::friendAvatarChanged, friendWidget, &FriendWidget::onAvatarChange); + connect(Core::getInstance(), &Core::friendAvatarRemoved, friendWidget, &FriendWidget::onAvatarRemoved); + + ContentDialog* lastDialog = getFriendDialog(friendId); + + if (lastDialog != nullptr) + lastDialog->removeFriend(friendId); + + friendList.insert(friendId, std::make_tuple(this, friendWidget)); + onChatroomWidgetClicked(friendWidget, false); + + return friendWidget; +} + +GroupWidget* ContentDialog::addGroup(int groupId, const QString& name) +{ + GroupWidget* groupWidget = new GroupWidget(groupId, name); + groupLayout.addSortedWidget(groupWidget); + + Group* group = groupWidget->getGroup(); + connect(group, &Group::titleChanged, this, &ContentDialog::updateGroupWidget); + connect(group, &Group::userListChanged, this, &ContentDialog::updateGroupWidget); + connect(settingsWidget, &SettingsWidget::compactToggled, groupWidget, &GroupWidget::compactChange); + connect(groupWidget, &GroupWidget::chatroomWidgetClicked, this, &ContentDialog::onChatroomWidgetClicked); + + ContentDialog* lastDialog = getGroupDialog(groupId); + + if (lastDialog != nullptr) + lastDialog->removeGroup(groupId); + + groupList.insert(groupId, std::make_tuple(this, groupWidget)); + onChatroomWidgetClicked(groupWidget, false); + + return groupWidget; +} + +void ContentDialog::removeFriend(int friendId) +{ + auto iter = friendList.find(friendId); + + if (iter == friendList.end()) + return; + + FriendWidget* chatroomWidget = static_cast(std::get<1>(iter.value())); + disconnect(chatroomWidget->getFriend(), &Friend::displayedNameChanged, this, &ContentDialog::updateFriendWidget); + + if (activeChatroomWidget == chatroomWidget) + { + // Need to find replacement to show here instead. + cycleContacts(true, false); + } + + friendLayout->removeFriendWidget(chatroomWidget, Status::Offline); + friendLayout->removeFriendWidget(chatroomWidget, Status::Online); + + chatroomWidget->deleteLater(); + friendList.remove(friendId); + + if (chatroomWidgetCount() == 0) + { + contentLayout->clear(); + activeChatroomWidget = nullptr; + deleteLater(); + } + else + { + update(); + } +} + +void ContentDialog::removeGroup(int groupId) +{ + Group* group = GroupList::findGroup(groupId); + + if (group) + { + disconnect(group, &Group::titleChanged, this, &ContentDialog::updateGroupWidget); + disconnect(group, &Group::userListChanged, this, &ContentDialog::updateGroupWidget); + } + + auto iter = groupList.find(groupId); + + if (iter == groupList.end()) + return; + + GenericChatroomWidget* chatroomWidget = std::get<1>(iter.value()); + + if (activeChatroomWidget == chatroomWidget) + { + // Need to find replacement to show here instead. + cycleContacts(true, false); + } + + groupLayout.removeSortedWidget(chatroomWidget); + chatroomWidget->deleteLater(); + groupList.remove(groupId); + + if (chatroomWidgetCount() == 0) + { + contentLayout->clear(); + activeChatroomWidget = nullptr; + deleteLater(); + } + else + { + update(); + } +} + +bool ContentDialog::hasFriendWidget(int friendId, GenericChatroomWidget* chatroomWidget) +{ + return hasWidget(friendId, chatroomWidget, friendList); +} + +bool ContentDialog::hasGroupWidget(int groupId, GenericChatroomWidget *chatroomWidget) +{ + return hasWidget(groupId, chatroomWidget, groupList); +} + +int ContentDialog::chatroomWidgetCount() const +{ + return friendLayout->friendTotalCount() + groupLayout.getLayout()->count(); +} + +void ContentDialog::ensureSplitterVisible() +{ + if (splitter->sizes().at(0) == 0) + splitter->setSizes({1, 1}); + + update(); +} + +void ContentDialog::cycleContacts(bool forward, bool loop) +{ + Settings::getInstance().getGroupchatPosition(); + + int index; + QLayout* currentLayout; + if (activeChatroomWidget->getFriend()) + { + currentLayout = friendLayout->getLayoutOnline(); + index = friendLayout->indexOfFriendWidget(activeChatroomWidget, true); + if (index == -1) + { + currentLayout = friendLayout->getLayoutOffline(); + index = friendLayout->indexOfFriendWidget(activeChatroomWidget, false); + } + } + else + { + currentLayout = groupLayout.getLayout(); + index = groupLayout.indexOfSortedWidget(activeChatroomWidget); + } + + if (!loop && index == currentLayout->count() - 1) + { + bool groupsOnTop = Settings::getInstance().getGroupchatPosition(); + bool offlineEmpty = friendLayout->getLayoutOffline()->count() == 0; + bool onlineEmpty = offlineEmpty && ((friendLayout->getLayoutOnline()->count() == 0 && groupsOnTop) || !groupsOnTop); + bool groupsEmpty = offlineEmpty && ((groupLayout.getLayout()->count() == 0 && !groupsOnTop) || groupsOnTop); + + if ((currentLayout == friendLayout->getLayoutOffline()) + || (currentLayout == friendLayout->getLayoutOnline() && groupsEmpty) + || (currentLayout == groupLayout.getLayout() && onlineEmpty)) + { + forward = !forward; + } + } + + index += forward ? 1 : -1; + + for (;;) + { + // Bounds checking. + if (index < 0) + { + currentLayout = nextLayout(currentLayout, forward); + index = currentLayout->count() - 1; + continue; + } + else if (index >= currentLayout->count()) + { + currentLayout = nextLayout(currentLayout, forward); + index = 0; + continue; + } + + GenericChatroomWidget* chatWidget = dynamic_cast(currentLayout->itemAt(index)->widget()); + + if (chatWidget != nullptr && chatWidget != activeChatroomWidget) + onChatroomWidgetClicked(chatWidget, false); + + return; + } +} + +ContentDialog* ContentDialog::current() +{ + return currentDialog; +} + +bool ContentDialog::existsFriendWidget(int friendId, bool focus) +{ + return existsWidget(friendId, focus, friendList); +} + +bool ContentDialog::existsGroupWidget(int groupId, bool focus) +{ + return existsWidget(groupId, focus, groupList); +} + +void ContentDialog::updateFriendStatus(int friendId) +{ + updateStatus(friendId, friendList); + ContentDialog* contentDialog = getFriendDialog(friendId); + if (contentDialog != nullptr) + { + FriendWidget* friendWidget = static_cast(std::get<1>(friendList.find(friendId).value())); + contentDialog->friendLayout->addFriendWidget(friendWidget, FriendList::findFriend(friendId)->getStatus()); + } +} + +void ContentDialog::updateFriendStatusMessage(int friendId, const QString &message) +{ + auto iter = friendList.find(friendId); + + if (iter == friendList.end()) + return; + + std::get<1>(iter.value())->setStatusMsg(message); +} + +void ContentDialog::updateGroupStatus(int groupId) +{ + updateStatus(groupId, groupList); +} + +bool ContentDialog::isFriendWidgetActive(int friendId) +{ + return isWidgetActive(friendId, friendList); +} + +bool ContentDialog::isGroupWidgetActive(int groupId) +{ + return isWidgetActive(groupId, groupList); +} + +ContentDialog* ContentDialog::getFriendDialog(int friendId) +{ + return getDialog(friendId, friendList); +} + +ContentDialog* ContentDialog::getGroupDialog(int groupId) +{ + return getDialog(groupId, groupList); +} + +void ContentDialog::updateTitleUsername(const QString& username) +{ + if (displayWidget != nullptr) + setWindowTitle(displayWidget->getTitle() + QStringLiteral(" - ") + username); + else + setWindowTitle(username); +} + +void ContentDialog::updateTitle(GenericChatroomWidget* chatroomWidget) +{ + displayWidget = chatroomWidget; + updateTitleUsername(Core::getInstance()->getUsername()); +} + +void ContentDialog::previousContact() +{ + cycleContacts(false); +} + +void ContentDialog::nextContact() +{ + cycleContacts(true); +} + +bool ContentDialog::event(QEvent* event) +{ + switch (event->type()) + { + case QEvent::WindowActivate: + if (activeChatroomWidget != nullptr) + { + activeChatroomWidget->resetEventFlags(); + activeChatroomWidget->updateStatusLight(); + updateTitle(activeChatroomWidget); + + Friend* frnd = activeChatroomWidget->getFriend(); + + if (frnd) + { + frnd->getFriendWidget()->resetEventFlags(); + frnd->getFriendWidget()->updateStatusLight(); + } + else + { + Group* g = activeChatroomWidget->getGroup(); + g->getGroupWidget()->resetEventFlags(); + g->getGroupWidget()->updateStatusLight(); + } + } + + currentDialog = this; + +#ifdef Q_OS_MAC + emit activated(); +#endif + default: + break; + } + + return ActivateDialog::event(event); +} + +void ContentDialog::dragEnterEvent(QDragEnterEvent *event) +{ + if (event->mimeData()->hasFormat("friend")) + { + int friendId = event->mimeData()->data("friend").toInt(); + auto iter = friendList.find(friendId); + + // If friend is already in a dialog then you can't drop friend where it already is. + if (iter == friendList.end() || (iter != friendList.end() && std::get<0>(iter.value()) != this)) + event->acceptProposedAction(); + } + else if (event->mimeData()->hasFormat("group")) + { + int groupId = event->mimeData()->data("group").toInt(); + auto iter = groupList.find(groupId); + + if (iter == groupList.end() || (iter != groupList.end() && std::get<0>(iter.value()) != this)) + event->acceptProposedAction(); + } +} + +void ContentDialog::dropEvent(QDropEvent *event) +{ + if (event->mimeData()->hasFormat("friend")) + { + int friendId = event->mimeData()->data("friend").toInt(); + auto iter = friendList.find(friendId); + + if (iter != friendList.end()) + std::get<0>(iter.value())->removeFriend(friendId); + + Friend* contact = FriendList::findFriend(friendId); + Widget::getInstance()->addFriendDialog(contact, this); + ensureSplitterVisible(); + } + else if (event->mimeData()->hasFormat("group")) + { + int groupId = event->mimeData()->data("group").toInt(); + auto iter = friendList.find(groupId); + + if (iter != friendList.end()) + std::get<0>(iter.value())->removeGroup(groupId); + + Group* contact = GroupList::findGroup(groupId); + Widget::getInstance()->addGroupDialog(contact, this); + ensureSplitterVisible(); + } +} + +void ContentDialog::changeEvent(QEvent *event) +{ + QWidget::changeEvent(event); + if (event->type() == QEvent::ActivationChange) + { + if (isActiveWindow()) + currentDialog = this; + } +} + +void ContentDialog::resizeEvent(QResizeEvent* event) +{ + saveDialogGeometry(); + QDialog::resizeEvent(event); +} + +void ContentDialog::moveEvent(QMoveEvent* event) +{ + saveDialogGeometry(); + QDialog::moveEvent(event); +} + +void ContentDialog::keyPressEvent(QKeyEvent* event) +{ + if(event->key() != Qt::Key_Escape) + QDialog::keyPressEvent(event); // Ignore escape keyboard shortcut. +} + +void ContentDialog::onChatroomWidgetClicked(GenericChatroomWidget *widget, bool group) +{ + if (group) + { + ContentDialog* contentDialog = new ContentDialog(settingsWidget); + contentDialog->show(); + + if (widget->getFriend() != nullptr) + { + removeFriend(widget->getFriend()->getFriendID()); + Widget::getInstance()->addFriendDialog(widget->getFriend(), contentDialog); + } + else + { + removeGroup(widget->getGroup()->getGroupId()); + Widget::getInstance()->addGroupDialog(widget->getGroup(), contentDialog); + } + + contentDialog->raise(); + contentDialog->activateWindow(); + + return; + } + + // If we clicked on the currently active widget, don't reload and relayout everything + if (activeChatroomWidget == widget) + return; + + contentLayout->clear(); + + if (activeChatroomWidget != nullptr) + activeChatroomWidget->setAsInactiveChatroom(); + + activeChatroomWidget = widget; + + widget->setChatForm(contentLayout); + widget->setAsActiveChatroom(); + widget->resetEventFlags(); + widget->updateStatusLight(); + updateTitle(widget); + + if (widget->getFriend()) + widget->getFriend()->getFriendWidget()->updateStatusLight(); + else + widget->getGroup()->getGroupWidget()->updateStatusLight(); +} + +void ContentDialog::updateFriendWidget(FriendWidget *w, Status s) +{ + FriendWidget* friendWidget = static_cast(std::get<1>(friendList.find(w->friendId).value())); + friendWidget->setName(w->getName()); + friendLayout->addFriendWidget(friendWidget, s); +} + +void ContentDialog::updateGroupWidget(GroupWidget *w) +{ + std::get<1>(groupList.find(w->groupId).value())->setName(w->getName()); + static_cast(std::get<1>(groupList.find(w->groupId).value()))->onUserListChanged(); +} + +void ContentDialog::onGroupchatPositionChanged(bool top) +{ + friendLayout->removeItem(groupLayout.getLayout()); + + if (top) + friendLayout->insertLayout(0, groupLayout.getLayout()); + else + friendLayout->insertLayout(1, groupLayout.getLayout()); +} + +void ContentDialog::retranslateUi() +{ + updateTitleUsername(Core::getInstance()->getUsername()); +} + +void ContentDialog::saveDialogGeometry() +{ + Settings::getInstance().setDialogGeometry(saveGeometry()); +} + +void ContentDialog::saveSplitterState() +{ + Settings::getInstance().setDialogSplitterState(splitter->saveState()); +} + +bool ContentDialog::hasWidget(int id, GenericChatroomWidget* chatroomWidget, const QHash>& list) +{ + auto iter = list.find(id); + + if (iter == list.end() || std::get<0>(iter.value()) != this) + return false; + + return chatroomWidget == std::get<1>(iter.value()); +} + +bool ContentDialog::existsWidget(int id, bool focus, const QHash>& list) +{ + auto iter = list.find(id); + if (iter == list.end()) + return false; + + if (focus) + { + if (std::get<0>(iter.value())->windowState() & Qt::WindowMinimized) + std::get<0>(iter.value())->showNormal(); + + std::get<0>(iter.value())->raise(); + std::get<0>(iter.value())->activateWindow(); + std::get<0>(iter.value())->onChatroomWidgetClicked(std::get<1>(iter.value()), false); + } + + return true; +} + +void ContentDialog::updateStatus(int id, const QHash > &list) +{ + auto iter = list.find(id); + + if (iter == list.end()) + return; + + GenericChatroomWidget* chatroomWidget = std::get<1>(iter.value()); + chatroomWidget->updateStatusLight(); + + if (chatroomWidget->isActive()) + std::get<0>(iter.value())->updateTitle(chatroomWidget); +} + +bool ContentDialog::isWidgetActive(int id, const QHash > &list) +{ + auto iter = list.find(id); + + if (iter == list.end()) + return false; + + return std::get<0>(iter.value())->activeChatroomWidget == std::get<1>(iter.value()); +} + +ContentDialog* ContentDialog::getDialog(int id, const QHash>& list) +{ + auto iter = list.find(id); + + if (iter == list.end()) + return nullptr; + + return std::get<0>(iter.value()); +} + +QLayout* ContentDialog::nextLayout(QLayout* layout, bool forward) const +{ + if (layout == groupLayout.getLayout()) + { + if (forward) + { + if (Settings::getInstance().getGroupchatPosition()) + return friendLayout->getLayoutOnline(); + + return friendLayout->getLayoutOffline(); + } + else + { + if (Settings::getInstance().getGroupchatPosition()) + return friendLayout->getLayoutOffline(); + + return friendLayout->getLayoutOnline(); + } + } + else if (layout == friendLayout->getLayoutOnline()) + { + if (forward) + { + if (Settings::getInstance().getGroupchatPosition()) + return friendLayout->getLayoutOffline(); + + return groupLayout.getLayout(); + } + else + { + if (Settings::getInstance().getGroupchatPosition()) + return groupLayout.getLayout(); + + return friendLayout->getLayoutOffline(); + } + } + else if (layout == friendLayout->getLayoutOffline()) + { + if (forward) + { + if (Settings::getInstance().getGroupchatPosition()) + return groupLayout.getLayout(); + + return friendLayout->getLayoutOnline(); + } + else + { + if (Settings::getInstance().getGroupchatPosition()) + return friendLayout->getLayoutOnline(); + + return groupLayout.getLayout(); + } + } + return nullptr; +} diff --git a/src/widget/contentdialog.h b/src/widget/contentdialog.h new file mode 100644 index 000000000..51904f087 --- /dev/null +++ b/src/widget/contentdialog.h @@ -0,0 +1,119 @@ +/* + Copyright © 2015 by The qTox Project + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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. + + qTox 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 qTox. If not, see . +*/ + +#ifndef CONTENTDIALOG_H +#define CONTENTDIALOG_H + +#include "src/widget/tool/activatedialog.h" +#include +#include "src/core/corestructs.h" +#include "src/widget/genericchatitemlayout.h" + +template class QHash; +template class QSet; + +class QSplitter; +class QVBoxLayout; +class ContentLayout; +class GenericChatroomWidget; +class FriendWidget; +class GroupWidget; +class FriendListLayout; +class SettingsWidget; + +class ContentDialog : public ActivateDialog +{ + Q_OBJECT +public: + ContentDialog(SettingsWidget* settingsWidget, QWidget* parent = 0); + ~ContentDialog(); + + FriendWidget* addFriend(int friendId, QString id); + GroupWidget* addGroup(int groupId, const QString& name); + void removeFriend(int friendId); + void removeGroup(int groupId); + bool hasFriendWidget(int friendId, GenericChatroomWidget* chatroomWidget); + bool hasGroupWidget(int groupId, GenericChatroomWidget* chatroomWidget); + int chatroomWidgetCount() const; + void ensureSplitterVisible(); + + void cycleContacts(bool forward, bool loop = true); + + static ContentDialog* current(); + static bool existsFriendWidget(int friendId, bool focus); + static bool existsGroupWidget(int groupId, bool focus); + static void updateFriendStatus(int friendId); + static void updateFriendStatusMessage(int friendId, const QString &message); + static void updateGroupStatus(int groupId); + static bool isFriendWidgetActive(int friendId); + static bool isGroupWidgetActive(int groupId); + static ContentDialog* getFriendDialog(int friendId); + static ContentDialog* getGroupDialog(int groupId); + +#ifdef Q_OS_MAC +signals: + void activated(); +#endif + +public slots: + void updateTitleUsername(const QString& username); + void updateTitle(GenericChatroomWidget* chatroomWidget); + void previousContact(); + void nextContact(); + +protected: + bool event(QEvent* event) final override; + void dragEnterEvent(QDragEnterEvent* event) final override; + void dropEvent(QDropEvent* event) final override; + void changeEvent(QEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + void moveEvent(QMoveEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + +private slots: + void onChatroomWidgetClicked(GenericChatroomWidget* widget, bool group); + void updateFriendWidget(FriendWidget* w, Status s); + void updateGroupWidget(GroupWidget* w); + void onGroupchatPositionChanged(bool top); + +private: + void retranslateUi(); + void saveDialogGeometry(); + void saveSplitterState(); + QLayout* nextLayout(QLayout* layout, bool forward) const; + + bool hasWidget(int id, GenericChatroomWidget* chatroomWidget, const QHash>& list); + static bool existsWidget(int id, bool focus, const QHash>& list); + static void updateStatus(int id, const QHash>& list); + static bool isWidgetActive(int id, const QHash>& list); + static ContentDialog* getDialog(int id, const QHash>& list); + + QSplitter* splitter; + FriendListLayout* friendLayout; + GenericChatItemLayout groupLayout; + ContentLayout* contentLayout; + GenericChatroomWidget* activeChatroomWidget; + GenericChatroomWidget* displayWidget = nullptr; + SettingsWidget* settingsWidget; + static ContentDialog* currentDialog; + static QHash> friendList; + static QHash> groupList; +}; + +#endif // CONTENTDIALOG_H diff --git a/src/widget/contentlayout.cpp b/src/widget/contentlayout.cpp new file mode 100644 index 000000000..9012897b7 --- /dev/null +++ b/src/widget/contentlayout.cpp @@ -0,0 +1,124 @@ +/* + Copyright © 2015 by The qTox Project + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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. + + qTox 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 qTox. If not, see . +*/ + +#include "contentlayout.h" +#include "src/persistence/settings.h" +#include "style.h" +#include +#include + +ContentLayout::ContentLayout() + : QVBoxLayout() +{ + init(); +} + +ContentLayout::ContentLayout(QWidget *parent) + : QVBoxLayout(parent) +{ + init(); + + QPalette palette = parent->palette(); + palette.setBrush(QPalette::WindowText, QColor(0, 0, 0)); + palette.setBrush(QPalette::Button, QColor(255, 255, 255)); + palette.setBrush(QPalette::Light, QColor(255, 255, 255)); + palette.setBrush(QPalette::Midlight, QColor(255, 255, 255)); + palette.setBrush(QPalette::Dark, QColor(127, 127, 127)); + palette.setBrush(QPalette::Mid, QColor(170, 170, 170)); + palette.setBrush(QPalette::Text, QColor(0, 0, 0)); + palette.setBrush(QPalette::BrightText, QColor(255, 255, 255)); + palette.setBrush(QPalette::ButtonText, QColor(0, 0, 0)); + palette.setBrush(QPalette::Base, QColor(255, 255, 255)); + palette.setBrush(QPalette::Window, QColor(255, 255, 255)); + palette.setBrush(QPalette::Shadow, QColor(0, 0, 0)); + palette.setBrush(QPalette::AlternateBase, QColor(255, 255, 255)); + palette.setBrush(QPalette::ToolTipBase, QColor(255, 255, 220)); + palette.setBrush(QPalette::ToolTipText, QColor(0, 0, 0)); + + palette.setBrush(QPalette::Disabled, QPalette::WindowText, QColor(127, 127, 127)); + palette.setBrush(QPalette::Disabled, QPalette::Text, QColor(127, 127, 127)); + palette.setBrush(QPalette::Disabled, QPalette::ButtonText, QColor(127, 127, 127)); + + parent->setPalette(palette); +} + +ContentLayout::~ContentLayout() +{ + clear(); + + mainHead->deleteLater(); + mainContent->deleteLater(); +} + +void ContentLayout::clear() +{ + QLayoutItem* item; + while ((item = mainHead->layout()->takeAt(0)) != 0) + { + item->widget()->hide(); + item->widget()->setParent(nullptr); + } + + while ((item = mainContent->layout()->takeAt(0)) != 0) + { + item->widget()->hide(); + item->widget()->setParent(nullptr); + } +} + +void ContentLayout::init() +{ + setMargin(0); + setSpacing(0); + + mainHead = new QWidget(); + mainHead->setLayout(new QVBoxLayout); + mainHead->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + mainHead->layout()->setMargin(0); + mainHead->layout()->setSpacing(0); + mainHead->setMouseTracking(true); + + mainHLine = new QFrame(); + mainHLine->setFrameShape(QFrame::HLine); + mainHLine->setFrameShadow(QFrame::Plain); + QPalette palette = mainHLine->palette(); + palette.setBrush(QPalette::WindowText, QBrush(QColor(193, 193, 193))); + palette.setBrush(QPalette::WindowText, QBrush(QColor(193, 193, 193))); + mainHLine->setPalette(palette); + + mainContent = new QWidget(); + mainContent->setLayout(new QVBoxLayout); + mainContent->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + + if (QStyleFactory::keys().contains(Settings::getInstance().getStyle()) + && Settings::getInstance().getStyle() != "None") + { + mainHead->setStyle(QStyleFactory::create(Settings::getInstance().getStyle())); + mainContent->setStyle(QStyleFactory::create(Settings::getInstance().getStyle())); + } + +#ifndef Q_OS_MAC + mainHead->setStyleSheet(Style::getStylesheet(":ui/settings/mainHead.css")); + mainContent->setStyleSheet(Style::getStylesheet(":ui/settings/mainContent.css")); +#endif + + addWidget(mainHead); + addWidget(mainHLine); + addWidget(mainContent); +} diff --git a/src/widget/contentlayout.h b/src/widget/contentlayout.h new file mode 100644 index 000000000..6955ca63e --- /dev/null +++ b/src/widget/contentlayout.h @@ -0,0 +1,44 @@ +/* + Copyright © 2015 by The qTox Project + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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. + + qTox 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 qTox. If not, see . +*/ + +#ifndef CONTENTLAYOUT_H +#define CONTENTLAYOUT_H + +#include + +class QFrame; + +class ContentLayout : public QVBoxLayout +{ +public: + ContentLayout(); + ContentLayout(QWidget* parent); + ~ContentLayout(); + + void clear(); + + QWidget* mainContent; + QFrame* mainHLine; + QWidget* mainHead; + +private: + void init(); +}; + +#endif // CONTENTLAYOUT_H diff --git a/src/widget/form/addfriendform.cpp b/src/widget/form/addfriendform.cpp index 347903813..4c28bb4f1 100644 --- a/src/widget/form/addfriendform.cpp +++ b/src/widget/form/addfriendform.cpp @@ -22,9 +22,9 @@ #include #include #include +#include #include #include -#include "ui_mainwindow.h" #include "src/nexus.h" #include "src/core/core.h" #include "src/core/cdata.h" @@ -32,6 +32,8 @@ #include "src/persistence/settings.h" #include "src/widget/gui.h" #include "src/widget/translator.h" +#include "src/widget/contentlayout.h" +#include AddFriendForm::AddFriendForm() { @@ -66,10 +68,21 @@ AddFriendForm::~AddFriendForm() main->deleteLater(); } -void AddFriendForm::show(Ui::MainWindow &ui) +bool AddFriendForm::isShown() const { - ui.mainContent->layout()->addWidget(main); - ui.mainHead->layout()->addWidget(head); + if (main->isVisible()) + { + head->window()->windowHandle()->alert(0); + return true; + } + + return false; +} + +void AddFriendForm::show(ContentLayout* contentLayout) +{ + contentLayout->mainContent->layout()->addWidget(main); + contentLayout->mainHead->layout()->addWidget(head); main->show(); head->show(); setIdFromClipboard(); diff --git a/src/widget/form/addfriendform.h b/src/widget/form/addfriendform.h index fb1441979..b45d408af 100644 --- a/src/widget/form/addfriendform.h +++ b/src/widget/form/addfriendform.h @@ -26,7 +26,7 @@ #include #include -namespace Ui {class MainWindow;} +class ContentLayout; class AddFriendForm : public QObject { @@ -37,7 +37,8 @@ public: AddFriendForm& operator=(const AddFriendForm&) = delete; ~AddFriendForm(); - void show(Ui::MainWindow &ui); + bool isShown() const; + void show(ContentLayout* contentLayout); QString getMessage() const; signals: diff --git a/src/widget/form/chatform.cpp b/src/widget/form/chatform.cpp index bc43b1674..2a11fb63c 100644 --- a/src/widget/form/chatform.cpp +++ b/src/widget/form/chatform.cpp @@ -216,13 +216,7 @@ void ChatForm::onFileRecvRequest(ToxFile file) if (file.friendId != f->getFriendID()) return; - Widget* w = Widget::getInstance(); - if (!w->isFriendWidgetCurActiveWidget(f)|| w->isMinimized() || !w->isActiveWindow()) - { - w->newMessageAlert(f->getFriendWidget()); - f->setEventFlag(true); - f->getFriendWidget()->updateStatusLight(); - } + Widget::getInstance()->newFriendMessageAlert(file.friendId); QString name; ToxId friendId = f->getToxId(); @@ -265,7 +259,7 @@ void ChatForm::onAvInvite(uint32_t FriendId, int CallId, bool video) if (video) { callConfirm = new CallConfirmWidget(videoButton, *f); - if (Widget::getInstance()->isFriendWidgetCurActiveWidget(f)) + if (f->getFriendWidget()->chatFormIsSet(false)) callConfirm->show(); connect(callConfirm, &CallConfirmWidget::accepted, this, &ChatForm::onAnswerCallTriggered); @@ -280,7 +274,7 @@ void ChatForm::onAvInvite(uint32_t FriendId, int CallId, bool video) else { callConfirm = new CallConfirmWidget(callButton, *f); - if (Widget::getInstance()->isFriendWidgetCurActiveWidget(f)) + if (f->getFriendWidget()->chatFormIsSet(false)) callConfirm->show(); connect(callConfirm, &CallConfirmWidget::accepted, this, &ChatForm::onAnswerCallTriggered); @@ -292,6 +286,7 @@ void ChatForm::onAvInvite(uint32_t FriendId, int CallId, bool video) videoButton->setToolTip(""); connect(callButton, &QPushButton::clicked, this, &ChatForm::onAnswerCallTriggered); } + callButton->style()->polish(callButton); videoButton->style()->polish(videoButton); @@ -299,13 +294,7 @@ void ChatForm::onAvInvite(uint32_t FriendId, int CallId, bool video) ChatMessage::INFO, QDateTime::currentDateTime())); - Widget* w = Widget::getInstance(); - if (!w->isFriendWidgetCurActiveWidget(f)|| w->isMinimized() || !w->isActiveWindow()) - { - w->newMessageAlert(f->getFriendWidget()); - f->setEventFlag(true); - f->getFriendWidget()->updateStatusLight(); - } + Widget::getInstance()->newFriendMessageAlert(FriendId); } void ChatForm::onAvStart(uint32_t FriendId, int CallId, bool video) @@ -1009,9 +998,9 @@ void ChatForm::setFriendTyping(bool isTyping) text->setText("
" + QString("%1 is typing").arg(f->getDisplayedName()) + "
"); } -void ChatForm::show(Ui::MainWindow &ui) +void ChatForm::show(ContentLayout* contentLayout) { - GenericChatForm::show(ui); + GenericChatForm::show(contentLayout); if (callConfirm) callConfirm->show(); diff --git a/src/widget/form/chatform.h b/src/widget/form/chatform.h index 2aaf72f04..e2bfd2c49 100644 --- a/src/widget/form/chatform.h +++ b/src/widget/form/chatform.h @@ -27,7 +27,6 @@ #include #include - class Friend; class FileTransferInstance; class NetCamView; @@ -50,7 +49,7 @@ public: void setFriendTyping(bool isTyping); OfflineMsgEngine* getOfflineMsgEngine(); - virtual void show(Ui::MainWindow &ui) final override; + virtual void show(ContentLayout* contentLayout) final override; signals: void sendFile(uint32_t friendId, QString, QString, long long); diff --git a/src/widget/form/filesform.cpp b/src/widget/form/filesform.cpp index 623b330e9..61c1f949f 100644 --- a/src/widget/form/filesform.cpp +++ b/src/widget/form/filesform.cpp @@ -18,10 +18,11 @@ */ #include "filesform.h" -#include "ui_mainwindow.h" #include "src/widget/widget.h" #include "src/widget/translator.h" +#include "src/widget/contentlayout.h" #include +#include FilesForm::FilesForm() : QObject(), doneIcon(":/ui/fileTransferWidget/fileDone.svg") @@ -54,10 +55,21 @@ FilesForm::~FilesForm() head->deleteLater(); } -void FilesForm::show(Ui::MainWindow& ui) +bool FilesForm::isShown() const { - ui.mainContent->layout()->addWidget(&main); - ui.mainHead->layout()->addWidget(head); + if (main.isVisible()) + { + head->window()->windowHandle()->alert(0); + return true; + } + + return false; +} + +void FilesForm::show(ContentLayout* contentLayout) +{ + contentLayout->mainContent->layout()->addWidget(&main); + contentLayout->mainHead->layout()->addWidget(head); main.show(); head->show(); } diff --git a/src/widget/form/filesform.h b/src/widget/form/filesform.h index cc523def2..bb18dc41b 100644 --- a/src/widget/form/filesform.h +++ b/src/widget/form/filesform.h @@ -26,7 +26,7 @@ #include #include -namespace Ui {class MainWindow;} +class ContentLayout; class QListWidget; class FilesForm : public QObject @@ -37,7 +37,8 @@ public: FilesForm(); ~FilesForm(); - void show(Ui::MainWindow &ui); + bool isShown() const; + void show(ContentLayout* contentLayout); public slots: void onFileDownloadComplete(const QString& path); diff --git a/src/widget/form/genericchatform.cpp b/src/widget/form/genericchatform.cpp index 4b2a4f47e..21c9f0c51 100644 --- a/src/widget/form/genericchatform.cpp +++ b/src/widget/form/genericchatform.cpp @@ -18,7 +18,6 @@ */ #include "genericchatform.h" -#include "ui_mainwindow.h" #include #include @@ -42,6 +41,9 @@ #include "src/chatlog/content/timestamp.h" #include "src/widget/tool/flyoutoverlaywidget.h" #include "src/widget/translator.h" +#include "src/widget/contentlayout.h" +#include "src/widget/tool/croppinglabel.h" +#include GenericChatForm::GenericChatForm(QWidget *parent) : QWidget(parent) @@ -255,10 +257,10 @@ void GenericChatForm::setName(const QString &newName) nameLabel->setToolTip(newName); // for overlength names } -void GenericChatForm::show(Ui::MainWindow &ui) +void GenericChatForm::show(ContentLayout* contentLayout) { - ui.mainContent->layout()->addWidget(this); - ui.mainHead->layout()->addWidget(headWidget); + contentLayout->mainContent->layout()->addWidget(this); + contentLayout->mainHead->layout()->addWidget(headWidget); headWidget->show(); QWidget::show(); } diff --git a/src/widget/form/genericchatform.h b/src/widget/form/genericchatform.h index 573d6757a..237afdc03 100644 --- a/src/widget/form/genericchatform.h +++ b/src/widget/form/genericchatform.h @@ -40,10 +40,7 @@ class ChatLog; class MaskablePixmapWidget; class Widget; class FlyoutOverlayWidget; - -namespace Ui { - class MainWindow; -} +class ContentLayout; class GenericChatForm : public QWidget { @@ -54,7 +51,7 @@ public: void setName(const QString &newName); virtual void show() final{} - virtual void show(Ui::MainWindow &ui); + virtual void show(ContentLayout* contentLayout); ChatMessage::Ptr addMessage(const ToxId& author, const QString &message, bool isAction, const QDateTime &datetime, bool isSent); ChatMessage::Ptr addSelfMessage(const QString &message, bool isAction, const QDateTime &datetime, bool isSent); diff --git a/src/widget/form/profileform.cpp b/src/widget/form/profileform.cpp index 051a1b8e7..4954760cb 100644 --- a/src/widget/form/profileform.cpp +++ b/src/widget/form/profileform.cpp @@ -21,11 +21,11 @@ #include "src/nexus.h" #include "ui_profileform.h" #include "profileform.h" -#include "ui_mainwindow.h" #include "src/widget/form/settingswidget.h" #include "src/widget/maskablepixmapwidget.h" #include "src/widget/form/setpassworddialog.h" #include "src/persistence/settings.h" +#include "src/widget/contentlayout.h" #include "src/widget/tool/croppinglabel.h" #include "src/widget/widget.h" #include "src/widget/gui.h" @@ -44,6 +44,7 @@ #include #include #include +#include ProfileForm::ProfileForm(QWidget *parent) : QWidget{parent}, qr{nullptr} @@ -134,10 +135,21 @@ ProfileForm::~ProfileForm() head->deleteLater(); } -void ProfileForm::show(Ui::MainWindow &ui) +bool ProfileForm::isShown() const { - ui.mainHead->layout()->addWidget(head); - ui.mainContent->layout()->addWidget(this); + if (head->isVisible()) + { + window()->windowHandle()->alert(0); + return true; + } + + return false; +} + +void ProfileForm::show(ContentLayout* contentLayout) +{ + contentLayout->mainHead->layout()->addWidget(head); + contentLayout->mainContent->layout()->addWidget(this); head->show(); QWidget::show(); prFileLabelUpdate(); diff --git a/src/widget/form/profileform.h b/src/widget/form/profileform.h index db3c87068..cc995e154 100644 --- a/src/widget/form/profileform.h +++ b/src/widget/form/profileform.h @@ -30,10 +30,10 @@ class CroppingLabel; class Core; class MaskablePixmapWidget; +class ContentLayout; namespace Ui { class IdentitySettings; -class MainWindow; } class ClickableTE : public QLineEdit @@ -54,7 +54,8 @@ public: ProfileForm(QWidget *parent = nullptr); ~ProfileForm(); virtual void show() final{} - void show(Ui::MainWindow &ui); + void show(ContentLayout* contentLayout); + bool isShown() const; signals: void userNameChanged(QString); diff --git a/src/widget/form/settings/generalform.cpp b/src/widget/form/settings/generalform.cpp index 27dcf2b2e..7e552fd85 100644 --- a/src/widget/form/settings/generalform.cpp +++ b/src/widget/form/settings/generalform.cpp @@ -87,6 +87,9 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : bodyUI->groupAlwaysNotify->setChecked(Settings::getInstance().getGroupAlwaysNotify()); bodyUI->cbFauxOfflineMessaging->setChecked(Settings::getInstance().getFauxOfflineMessaging()); bodyUI->cbCompactLayout->setChecked(Settings::getInstance().getCompactLayout()); + bodyUI->cbSeparateWindow->setChecked(Settings::getInstance().getSeparateWindow()); + bodyUI->cbDontGroupWindows->setChecked(Settings::getInstance().getDontGroupWindows()); + bodyUI->cbDontGroupWindows->setEnabled(bodyUI->cbSeparateWindow->isChecked()); bodyUI->cbGroupchatPosition->setChecked(Settings::getInstance().getGroupchatPosition()); for (auto entry : SmileyPack::listSmileyPacks()) @@ -179,6 +182,8 @@ GeneralForm::GeneralForm(SettingsWidget *myParent) : connect(bodyUI->reconnectButton, &QPushButton::clicked, this, &GeneralForm::onReconnectClicked); connect(bodyUI->cbFauxOfflineMessaging, &QCheckBox::stateChanged, this, &GeneralForm::onFauxOfflineMessaging); connect(bodyUI->cbCompactLayout, &QCheckBox::stateChanged, this, &GeneralForm::onCompactLayout); + connect(bodyUI->cbSeparateWindow, &QCheckBox::stateChanged, this, &GeneralForm::onSeparateWindowChanged); + connect(bodyUI->cbDontGroupWindows, &QCheckBox::stateChanged, this, &GeneralForm::onDontGroupWindowsChanged); connect(bodyUI->cbGroupchatPosition, &QCheckBox::stateChanged, this, &GeneralForm::onGroupchatPositionChanged); // prevent stealing mouse whell scroll @@ -430,6 +435,18 @@ void GeneralForm::onCompactLayout() emit parent->compactToggled(bodyUI->cbCompactLayout->isChecked()); } +void GeneralForm::onSeparateWindowChanged() +{ + bodyUI->cbDontGroupWindows->setEnabled(bodyUI->cbSeparateWindow->isChecked()); + Settings::getInstance().setSeparateWindow(bodyUI->cbSeparateWindow->isChecked()); + emit parent->separateWindowToggled(bodyUI->cbSeparateWindow->isChecked()); +} + +void GeneralForm::onDontGroupWindowsChanged() +{ + Settings::getInstance().setDontGroupWindows(bodyUI->cbDontGroupWindows->isChecked()); +} + void GeneralForm::onGroupchatPositionChanged() { Settings::getInstance().setGroupchatPosition(bodyUI->cbGroupchatPosition->isChecked()); diff --git a/src/widget/form/settings/generalform.h b/src/widget/form/settings/generalform.h index 4f53c70c1..74fc7e19f 100644 --- a/src/widget/form/settings/generalform.h +++ b/src/widget/form/settings/generalform.h @@ -67,6 +67,8 @@ private slots: void onSetGroupAlwaysNotify(); void onFauxOfflineMessaging(); void onCompactLayout(); + void onSeparateWindowChanged(); + void onDontGroupWindowsChanged(); void onGroupchatPositionChanged(); void onThemeColorChanged(int); diff --git a/src/widget/form/settings/generalsettings.ui b/src/widget/form/settings/generalsettings.ui index 81884fe90..8cc7b04f0 100644 --- a/src/widget/form/settings/generalsettings.ui +++ b/src/widget/form/settings/generalsettings.ui @@ -40,7 +40,7 @@ 0 0 639 - 1221 + 1388
@@ -374,6 +374,23 @@ instead of system taskbar. + + + + Open chats in separate window. + + + + + + + false + + + Don't group chat windows. + + + diff --git a/src/widget/form/settingswidget.cpp b/src/widget/form/settingswidget.cpp index ea9ce8b45..c44b94806 100644 --- a/src/widget/form/settingswidget.cpp +++ b/src/widget/form/settingswidget.cpp @@ -19,7 +19,6 @@ #include "settingswidget.h" #include "src/widget/widget.h" -#include "ui_mainwindow.h" #include "src/video/camerasource.h" #include "src/widget/form/settings/generalform.h" #include "src/widget/form/settings/privacyform.h" @@ -27,12 +26,15 @@ #include "src/widget/form/settings/advancedform.h" #include "src/widget/form/settings/aboutform.h" #include "src/widget/translator.h" +#include "src/widget/contentlayout.h" #include +#include +#include SettingsWidget::SettingsWidget(QWidget* parent) : QWidget(parent) { - body = new QWidget(this); + body = new QWidget(); QVBoxLayout* bodyLayout = new QVBoxLayout(); body->setLayout(bodyLayout); @@ -86,10 +88,21 @@ void SettingsWidget::showAbout() onTabChanged(settingsWidgets->count() - 1); } -void SettingsWidget::show(Ui::MainWindow& ui) +bool SettingsWidget::isShown() const { - ui.mainContent->layout()->addWidget(body); - ui.mainHead->layout()->addWidget(head); + if (body->isVisible()) + { + body->window()->windowHandle()->alert(0); + return true; + } + + return false; +} + +void SettingsWidget::show(ContentLayout* contentLayout) +{ + contentLayout->mainContent->layout()->addWidget(body); + contentLayout->mainHead->layout()->addWidget(head); body->show(); head->show(); onTabChanged(settingsWidgets->currentIndex()); diff --git a/src/widget/form/settingswidget.h b/src/widget/form/settingswidget.h index bdcba3a2e..8d696b069 100644 --- a/src/widget/form/settingswidget.h +++ b/src/widget/form/settingswidget.h @@ -32,8 +32,7 @@ class PrivacyForm; class AVForm; class QLabel; class QTabWidget; - -namespace Ui {class MainWindow;} +class ContentLayout; class SettingsWidget : public QWidget { @@ -42,7 +41,8 @@ public: SettingsWidget(QWidget* parent = nullptr); ~SettingsWidget(); - void show(Ui::MainWindow &ui); + bool isShown() const; + void show(ContentLayout* contentLayout); void setBodyHeadStyle(QString style); void showAbout(); @@ -50,6 +50,7 @@ public: signals: void setShowSystemTray(bool newValue); void compactToggled(bool compact); + void separateWindowToggled(bool separateWindow); void groupchatPositionToggled(bool groupchatPosition); private slots: diff --git a/src/widget/friendlistlayout.cpp b/src/widget/friendlistlayout.cpp index cbe099e25..5b00d71c1 100644 --- a/src/widget/friendlistlayout.cpp +++ b/src/widget/friendlistlayout.cpp @@ -62,7 +62,7 @@ void FriendListLayout::removeFriendWidget(FriendWidget *widget, Status s) friendOnlineLayout.removeSortedWidget(widget); } -int FriendListLayout::indexOfFriendWidget(FriendWidget* widget, bool online) const +int FriendListLayout::indexOfFriendWidget(GenericChatItemWidget* widget, bool online) const { if (online) return friendOnlineLayout.indexOfSortedWidget(widget); diff --git a/src/widget/friendlistlayout.h b/src/widget/friendlistlayout.h index aef147b11..a0e9d6025 100644 --- a/src/widget/friendlistlayout.h +++ b/src/widget/friendlistlayout.h @@ -31,7 +31,7 @@ public: void addFriendWidget(FriendWidget* widget, Status s); void removeFriendWidget(FriendWidget* widget, Status s); - int indexOfFriendWidget(FriendWidget* widget, bool online) const; + int indexOfFriendWidget(GenericChatItemWidget* widget, bool online) const; void moveFriendWidgets(FriendListWidget* listWidget); int friendOnlineCount() const; int friendTotalCount() const; diff --git a/src/widget/friendlistwidget.cpp b/src/widget/friendlistwidget.cpp index 1f0f50591..f74fbda18 100644 --- a/src/widget/friendlistwidget.cpp +++ b/src/widget/friendlistwidget.cpp @@ -389,8 +389,11 @@ void FriendListWidget::addCircleWidget(FriendWidget* friendWidget) } Widget::getInstance()->searchCircle(circleWidget); - circleWidget->editName(); + + if (window()->isActiveWindow()) + circleWidget->editName(); } + reDraw(); } void FriendListWidget::removeCircleWidget(CircleWidget* widget) @@ -596,17 +599,6 @@ void FriendListWidget::cycleContacts(GenericChatroomWidget* activeChatroomWidget } } -QVector FriendListWidget::getAllCircles() -{ - QVector vec; - vec.reserve(circleLayout->getLayout()->count()); - for (int i = 0; i < circleLayout->getLayout()->count(); ++i) - { - vec.push_back(dynamic_cast(circleLayout->getLayout()->itemAt(i)->widget())); - } - return vec; -} - void FriendListWidget::dragEnterEvent(QDragEnterEvent* event) { if (event->mimeData()->hasFormat("friend")) diff --git a/src/widget/friendlistwidget.h b/src/widget/friendlistwidget.h index 5c1bc0335..63c4ac20f 100644 --- a/src/widget/friendlistwidget.h +++ b/src/widget/friendlistwidget.h @@ -58,7 +58,6 @@ public: void searchChatrooms(const QString &searchString, bool hideOnline = false, bool hideOffline = false, bool hideGroups = false); void cycleContacts(GenericChatroomWidget* activeChatroomWidget, bool forward); - QVector getAllCircles(); void updateActivityDate(const QDate& date); void reDraw(); diff --git a/src/widget/friendwidget.cpp b/src/widget/friendwidget.cpp index cf4a7ce8a..9459b3ece 100644 --- a/src/widget/friendwidget.cpp +++ b/src/widget/friendwidget.cpp @@ -26,6 +26,7 @@ #include "src/core/core.h" #include "form/chatform.h" #include "maskablepixmapwidget.h" +#include "contentdialog.h" #include "src/widget/tool/croppinglabel.h" #include "src/widget/style.h" #include "src/persistence/settings.h" @@ -67,6 +68,20 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event) ToxId id = FriendList::findFriend(friendId)->getToxId(); QString dir = Settings::getInstance().getAutoAcceptDir(id); QMenu menu; + QAction* openChat = menu.addAction(tr("Open chat")); + QAction* openChatWindow = nullptr; + QAction* removeChatWindow = nullptr; + + ContentDialog* contentDialog = ContentDialog::getFriendDialog(friendId); + bool notAlone = contentDialog != nullptr && contentDialog->chatroomWidgetCount() > 1; + + if (contentDialog == nullptr || notAlone) + openChatWindow = menu.addAction(tr("Open chat in new window")); + + if (contentDialog->hasFriendWidget(friendId, this)) + removeChatWindow = menu.addAction(tr("Remove chat from this window")); + + menu.addSeparator(); QMenu* inviteMenu = menu.addMenu(tr("Invite to group","Menu to invite a friend to a groupchat")); QMap groupActions; @@ -90,7 +105,7 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event) FriendListWidget *friendList; if (circleWidget == nullptr) - friendList = dynamic_cast(parentWidget()); + friendList = dynamic_cast(FriendList::findFriend(friendId)->getFriendWidget()->parentWidget()); else friendList = dynamic_cast(circleWidget->parentWidget()); @@ -132,7 +147,10 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event) autoAccept->setChecked(!dir.isEmpty()); menu.addSeparator(); - QAction* removeFriendAction = menu.addAction(tr("Remove friend", "Menu to remove the friend from our friendlist")); + QAction* removeFriendAction = nullptr; + + if (contentDialog == nullptr || !contentDialog->hasFriendWidget(friendId, this)) + removeFriendAction = menu.addAction(tr("Remove friend", "Menu to remove the friend from our friendlist")); QAction* selectedItem = menu.exec(pos); @@ -156,6 +174,22 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event) emit removeFriend(friendId); return; } + else if (selectedItem == openChat) + { + emit chatroomWidgetClicked(this); + return; + } + else if (selectedItem == openChatWindow) + { + emit chatroomWidgetClicked(this, true); + return; + } + else if (selectedItem == removeChatWindow) + { + ContentDialog* contentDialog = ContentDialog::getFriendDialog(friendId); + contentDialog->removeFriend(friendId); + return; + } else if (selectedItem == autoAccept) { if (!autoAccept->isChecked()) @@ -180,7 +214,7 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event) circleWidget->updateStatus(); if (friendList != nullptr) - friendList->addCircleWidget(this); + friendList->addCircleWidget(FriendList::findFriend(friendId)->getFriendWidget()); else Settings::getInstance().setFriendCircleID(id, Settings::getInstance().addCircle()); } @@ -192,7 +226,7 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event) else if (removeCircleAction != nullptr && selectedItem == removeCircleAction) { if (friendList != nullptr) - friendList->moveWidget(this, FriendList::findFriend(friendId)->getStatus(), true); + friendList->moveWidget(FriendList::findFriend(friendId)->getFriendWidget(), FriendList::findFriend(friendId)->getStatus(), true); else Settings::getInstance().setFriendCircleID(id, -1); @@ -208,7 +242,7 @@ void FriendWidget::contextMenuEvent(QContextMenuEvent * event) if (circle != nullptr) { - circle->addFriendWidget(this, FriendList::findFriend(friendId)->getStatus()); + circle->addFriendWidget(FriendList::findFriend(friendId)->getFriendWidget(), FriendList::findFriend(friendId)->getStatus()); circle->setExpanded(true); Widget::getInstance()->searchCircle(circle); Settings::getInstance().savePersonal(); @@ -278,7 +312,7 @@ void FriendWidget::updateStatusLight() statusPic.setMargin(0); } -QString FriendWidget::getStatusString() +QString FriendWidget::getStatusString() const { Friend* f = FriendList::findFriend(friendId); Status status = f->getStatus(); @@ -296,6 +330,11 @@ QString FriendWidget::getStatusString() return QString::null; } +Friend* FriendWidget::getFriend() const +{ + return FriendList::findFriend(friendId); +} + void FriendWidget::search(const QString &searchString, bool hide) { searchName(searchString, hide); @@ -304,10 +343,16 @@ void FriendWidget::search(const QString &searchString, bool hide) circleWidget->search(searchString); } -void FriendWidget::setChatForm(Ui::MainWindow &ui) +bool FriendWidget::chatFormIsSet(bool focus) const { Friend* f = FriendList::findFriend(friendId); - f->getChatForm()->show(ui); + return ContentDialog::existsFriendWidget(friendId, focus) || f->getChatForm()->isVisible(); +} + +void FriendWidget::setChatForm(ContentLayout* contentLayout) +{ + Friend* f = FriendList::findFriend(friendId); + f->getChatForm()->show(contentLayout); } void FriendWidget::resetEventFlags() diff --git a/src/widget/friendwidget.h b/src/widget/friendwidget.h index e1f6270f7..9c90bf4cd 100644 --- a/src/widget/friendwidget.h +++ b/src/widget/friendwidget.h @@ -32,9 +32,11 @@ public: virtual void setAsActiveChatroom() override; virtual void setAsInactiveChatroom() override; virtual void updateStatusLight() override; - virtual void setChatForm(Ui::MainWindow &) override; + virtual bool chatFormIsSet(bool focus) const override; + virtual void setChatForm(ContentLayout* contentLayout) override; virtual void resetEventFlags() override; - virtual QString getStatusString() override; + virtual QString getStatusString() const override; + virtual Friend* getFriend() const override; void search(const QString &searchString, bool hide = false); signals: @@ -56,7 +58,6 @@ public: int friendId; bool isDefaultAvatar; bool historyLoaded; - QPoint dragStartPos; }; #endif // FRIENDWIDGET_H diff --git a/src/widget/genericchatroomwidget.cpp b/src/widget/genericchatroomwidget.cpp index 09983df98..be25f919a 100644 --- a/src/widget/genericchatroomwidget.cpp +++ b/src/widget/genericchatroomwidget.cpp @@ -144,6 +144,16 @@ QString GenericChatroomWidget::getStatusMsg() const return statusMessageLabel->text(); } +QString GenericChatroomWidget::getTitle() const +{ + QString title = getName(); + + if (!getStatusString().isNull()) + title += QStringLiteral(" (") + getStatusString() + QStringLiteral(")"); + + return title; +} + void GenericChatroomWidget::reloadTheme() { QPalette p; diff --git a/src/widget/genericchatroomwidget.h b/src/widget/genericchatroomwidget.h index dcccb2985..f2670c7e1 100644 --- a/src/widget/genericchatroomwidget.h +++ b/src/widget/genericchatroomwidget.h @@ -26,10 +26,9 @@ class CroppingLabel; class MaskablePixmapWidget; class QVBoxLayout; class QHBoxLayout; - -namespace Ui { - class MainWindow; -} +class ContentLayout; +class Friend; +class Group; class GenericChatroomWidget : public GenericChatItemWidget { @@ -40,9 +39,12 @@ public: virtual void setAsActiveChatroom() = 0; virtual void setAsInactiveChatroom() = 0; virtual void updateStatusLight() = 0; - virtual void setChatForm(Ui::MainWindow &) = 0; + virtual bool chatFormIsSet(bool focus) const = 0; + virtual void setChatForm(ContentLayout* contentLayout) = 0; virtual void resetEventFlags() = 0; - virtual QString getStatusString() = 0; + virtual QString getStatusString() const = 0; + virtual Friend* getFriend() const{return nullptr;} + virtual Group* getGroup() const{return nullptr;} virtual bool eventFilter(QObject *, QEvent *) final override; @@ -52,6 +54,7 @@ public: void setName(const QString& name); void setStatusMsg(const QString& status); QString getStatusMsg() const; + QString getTitle() const; void reloadTheme(); @@ -59,13 +62,15 @@ public slots: void compactChange(bool compact); signals: - void chatroomWidgetClicked(GenericChatroomWidget* widget); + void chatroomWidgetClicked(GenericChatroomWidget* widget, bool group = false); protected: virtual void mouseReleaseEvent(QMouseEvent* event) override; virtual void enterEvent(QEvent* e) override; virtual void leaveEvent(QEvent* e) override; + QPoint dragStartPos; + protected: QColor lastColor; QHBoxLayout* mainLayout = nullptr; diff --git a/src/widget/groupwidget.cpp b/src/widget/groupwidget.cpp index 4e78d71b5..7957340a1 100644 --- a/src/widget/groupwidget.cpp +++ b/src/widget/groupwidget.cpp @@ -19,8 +19,10 @@ #include "groupwidget.h" #include "maskablepixmapwidget.h" +#include "contentdialog.h" #include "src/grouplist.h" #include "src/group.h" +#include "src/persistence/settings.h" #include "form/groupchatform.h" #include "src/widget/style.h" #include "src/core/core.h" @@ -30,6 +32,8 @@ #include #include #include +#include +#include GroupWidget::GroupWidget(int GroupId, QString Name) : groupId{GroupId} @@ -62,6 +66,22 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent* event) installEventFilter(this); // Disable leave event. QMenu menu(this); + + QAction* openChat = menu.addAction(tr("Open chat")); + QAction* openChatWindow = nullptr; + QAction* removeChatWindow = nullptr; + + ContentDialog* contentDialog = ContentDialog::getGroupDialog(groupId); + bool notAlone = contentDialog != nullptr && contentDialog->chatroomWidgetCount() > 1; + + if (contentDialog == nullptr || notAlone) + openChatWindow = menu.addAction(tr("Open chat in new window")); + + if (contentDialog->hasGroupWidget(groupId, this)) + removeChatWindow = menu.addAction(tr("Remove chat from this window")); + + menu.addSeparator(); + QAction* setTitle = menu.addAction(tr("Set title...")); QAction* quitGroup = menu.addAction(tr("Quit group","Menu to quit a groupchat")); @@ -78,6 +98,22 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent* event) { emit removeGroup(groupId); } + else if (selectedItem == openChat) + { + emit chatroomWidgetClicked(this); + return; + } + else if (selectedItem == openChatWindow) + { + emit chatroomWidgetClicked(this, true); + return; + } + else if (selectedItem == removeChatWindow) + { + ContentDialog* contentDialog = ContentDialog::getGroupDialog(groupId); + contentDialog->removeGroup(groupId); + return; + } else if (selectedItem == setTitle) { editName(); @@ -85,6 +121,32 @@ void GroupWidget::contextMenuEvent(QContextMenuEvent* event) } } +void GroupWidget::mousePressEvent(QMouseEvent *ev) +{ + if (ev->button() == Qt::LeftButton) + dragStartPos = ev->pos(); + + GenericChatroomWidget::mousePressEvent(ev); +} + +void GroupWidget::mouseMoveEvent(QMouseEvent *ev) +{ + if (!(ev->buttons() & Qt::LeftButton)) + return; + + if ((dragStartPos - ev->pos()).manhattanLength() > QApplication::startDragDistance()) + { + QDrag* drag = new QDrag(this); + QMimeData* mdata = new QMimeData; + mdata->setData("group", QString::number(groupId).toLatin1()); + + drag->setMimeData(mdata); + drag->setPixmap(avatar->getPixmap()); + + drag->exec(Qt::CopyAction | Qt::MoveAction); + } +} + void GroupWidget::onUserListChanged() { Group* g = GroupList::findGroup(groupId); @@ -122,7 +184,7 @@ void GroupWidget::updateStatusLight() } } -QString GroupWidget::getStatusString() +QString GroupWidget::getStatusString() const { Group *g = GroupList::findGroup(groupId); @@ -137,10 +199,22 @@ void GroupWidget::editName() nameLabel->editBegin(); } -void GroupWidget::setChatForm(Ui::MainWindow &ui) +Group* GroupWidget::getGroup() const +{ + return GroupList::findGroup(groupId); +} + +bool GroupWidget::chatFormIsSet(bool focus) const +{ + (void)focus; + Group* g = GroupList::findGroup(groupId); + return ContentDialog::existsGroupWidget(groupId, focus) || g->getChatForm()->isVisible(); +} + +void GroupWidget::setChatForm(ContentLayout* contentLayout) { Group* g = GroupList::findGroup(groupId); - g->getChatForm()->show(ui); + g->getChatForm()->show(contentLayout); } void GroupWidget::resetEventFlags() diff --git a/src/widget/groupwidget.h b/src/widget/groupwidget.h index 371a586de..4e70ae6df 100644 --- a/src/widget/groupwidget.h +++ b/src/widget/groupwidget.h @@ -30,9 +30,11 @@ public: virtual void setAsInactiveChatroom() final override; virtual void setAsActiveChatroom() final override; virtual void updateStatusLight() final override; - virtual void setChatForm(Ui::MainWindow &) final override; + virtual bool chatFormIsSet(bool focus) const final override; + virtual void setChatForm(ContentLayout* contentLayout) override; virtual void resetEventFlags() final override; - virtual QString getStatusString() final override; + virtual QString getStatusString() const final override; + virtual Group* getGroup() const override; void setName(const QString& name); void onUserListChanged(); void editName(); @@ -44,6 +46,8 @@ signals: protected: virtual void contextMenuEvent(QContextMenuEvent * event) final override; + virtual void mousePressEvent(QMouseEvent* event) final override; + virtual void mouseMoveEvent(QMouseEvent* event) final override; virtual void dragEnterEvent(QDragEnterEvent* ev) override; virtual void dragLeaveEvent(QDragLeaveEvent* ev); virtual void dropEvent(QDropEvent* ev) override; diff --git a/src/widget/loginscreen.cpp b/src/widget/loginscreen.cpp index bf9ae2cd2..140502f9a 100644 --- a/src/widget/loginscreen.cpp +++ b/src/widget/loginscreen.cpp @@ -97,6 +97,16 @@ void LoginScreen::reset() ui->autoLoginCB->setChecked(Settings::getInstance().getAutoLogin()); } +#ifdef Q_OS_MAC +bool LoginScreen::event(QEvent* event) +{ + if (event->type() == QEvent::WindowActivate || event->type() == QEvent::WindowStateChange) + emit windowStateChanged(windowState()); + + return QWidget::event(event); +} +#endif + void LoginScreen::onNewProfilePageClicked() { ui->stackedWidget->setCurrentIndex(0); diff --git a/src/widget/loginscreen.h b/src/widget/loginscreen.h index 2c3cedcfc..66e027d34 100644 --- a/src/widget/loginscreen.h +++ b/src/widget/loginscreen.h @@ -37,6 +37,13 @@ public: ~LoginScreen(); void reset(); ///< Resets the UI, clears all fields +#ifdef Q_OS_MAC + bool event(QEvent* event) final override; + +signals: + void windowStateChanged(Qt::WindowStates states); +#endif + private slots: void onAutoLoginToggled(int state); void onLoginUsernameSelected(const QString& name); diff --git a/src/widget/tool/activatedialog.cpp b/src/widget/tool/activatedialog.cpp new file mode 100644 index 000000000..1e4426ca8 --- /dev/null +++ b/src/widget/tool/activatedialog.cpp @@ -0,0 +1,36 @@ +/* + Copyright © 2015 by The qTox Project + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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. + + qTox 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 qTox. If not, see . +*/ + + +#include "activatedialog.h" +#include + +ActivateDialog::ActivateDialog(QWidget *parent, Qt::WindowFlags f) + : QDialog(parent, f) +{ + +} + +bool ActivateDialog::event(QEvent* event) +{ + if (event->type() == QEvent::WindowActivate || event->type() == QEvent::WindowStateChange) + emit windowStateChanged(windowState()); + + return QDialog::event(event); +} diff --git a/src/widget/tool/activatedialog.h b/src/widget/tool/activatedialog.h new file mode 100644 index 000000000..d6b71cfb8 --- /dev/null +++ b/src/widget/tool/activatedialog.h @@ -0,0 +1,36 @@ +/* + Copyright © 2015 by The qTox Project + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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. + + qTox 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 qTox. If not, see . +*/ + +#ifndef ACTIVATEDIALOG_H +#define ACTIVATEDIALOG_H + +#include + +class ActivateDialog : public QDialog +{ + Q_OBJECT +public: + ActivateDialog(QWidget* parent = 0, Qt::WindowFlags f = 0); + bool event(QEvent* event) override; + +signals: + void windowStateChanged(Qt::WindowStates state); +}; + +#endif // ACTIVATEDIALOG_H diff --git a/src/widget/tool/callconfirmwidget.cpp b/src/widget/tool/callconfirmwidget.cpp index 7a528fbc0..e61a495c2 100644 --- a/src/widget/tool/callconfirmwidget.cpp +++ b/src/widget/tool/callconfirmwidget.cpp @@ -19,7 +19,6 @@ #include "callconfirmwidget.h" -#include "src/widget/gui.h" #include "src/widget/widget.h" #include #include @@ -33,7 +32,7 @@ #include CallConfirmWidget::CallConfirmWidget(const QWidget *Anchor, const Friend& f) : - QWidget(GUI::getMainWidget()), anchor(Anchor), f(f), + QWidget(), anchor(Anchor), f(f), rectW{120}, rectH{85}, spikeW{30}, spikeH{15}, roundedFactor{20}, @@ -47,6 +46,7 @@ CallConfirmWidget::CallConfirmWidget(const QWidget *Anchor, const Friend& f) : QVBoxLayout *layout = new QVBoxLayout(this); QLabel *callLabel = new QLabel(QObject::tr("Incoming call..."), this); + callLabel->setStyleSheet("color: white"); callLabel->setWordWrap(true); callLabel->setAlignment(Qt::AlignHCenter); QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal, this); @@ -66,8 +66,6 @@ CallConfirmWidget::CallConfirmWidget(const QWidget *Anchor, const Friend& f) : connect(buttonBox, &QDialogButtonBox::accepted, this, &CallConfirmWidget::accepted); connect(buttonBox, &QDialogButtonBox::rejected, this, &CallConfirmWidget::rejected); - connect(&GUI::getInstance(), &GUI::resized, this, &CallConfirmWidget::reposition); - layout->setMargin(12); layout->addSpacing(spikeH); layout->addWidget(callLabel); @@ -79,7 +77,13 @@ CallConfirmWidget::CallConfirmWidget(const QWidget *Anchor, const Friend& f) : void CallConfirmWidget::reposition() { - QWidget* w = GUI::getMainWidget(); + if (parentWidget()) + parentWidget()->removeEventFilter(this); + + setParent(anchor->window()); + parentWidget()->installEventFilter(this); + + QWidget* w = anchor->window(); QPoint pos = anchor->mapToGlobal({(anchor->width()-rectW)/2,anchor->height()})-w->mapToGlobal({0,0}); // We don't want the widget to overflow past the right of the screen @@ -114,11 +118,27 @@ void CallConfirmWidget::showEvent(QShowEvent*) // If someone does show() from Widget or lower, the event will reach us // because it's our parent, and we could show up in the wrong form. // So check here if our friend's form is actually the active one. - if (!Widget::getInstance()->isFriendWidgetCurActiveWidget(&f)) + //if (!Widget::getInstance()->isFriendWidgetCurActiveWidget(&f)) { - QWidget::hide(); - return; + //QWidget::hide(); + //return; } reposition(); update(); } + +void CallConfirmWidget::hideEvent(QHideEvent *) +{ + if (parentWidget()) + parentWidget()->removeEventFilter(this); + + setParent(nullptr); +} + +bool CallConfirmWidget::eventFilter(QObject*, QEvent* event) +{ + if (event->type() == QEvent::Resize) + reposition(); + + return false; +} diff --git a/src/widget/tool/callconfirmwidget.h b/src/widget/tool/callconfirmwidget.h index e04eade0b..3c50fec9a 100644 --- a/src/widget/tool/callconfirmwidget.h +++ b/src/widget/tool/callconfirmwidget.h @@ -43,12 +43,14 @@ signals: void accepted(); void rejected(); +public slots: + void reposition(); ///< Recalculate our positions to track the anchor + protected: virtual void paintEvent(QPaintEvent* event) final override; virtual void showEvent(QShowEvent* event) final override; - -protected slots: - void reposition(); ///< Recalculate our positions to track the anchor + virtual void hideEvent(QHideEvent* event) final override; + virtual bool eventFilter(QObject *, QEvent* event) final override; private: const QWidget* anchor; ///< The widget we're going to be tracking diff --git a/src/widget/tool/croppinglabel.cpp b/src/widget/tool/croppinglabel.cpp index 7eb6b3a00..ad151462e 100644 --- a/src/widget/tool/croppinglabel.cpp +++ b/src/widget/tool/croppinglabel.cpp @@ -20,6 +20,7 @@ #include "croppinglabel.h" #include #include +#include CroppingLabel::CroppingLabel(QWidget* parent) : QLabel(parent) @@ -29,13 +30,31 @@ CroppingLabel::CroppingLabel(QWidget* parent) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - textEdit = new QLineEdit(this); + class LineEdit : public QLineEdit + { + public: + LineEdit(QWidget* parent = 0) : + QLineEdit(parent) + {} + + protected: + void keyPressEvent(QKeyEvent* event) override + { + if (event->key() == Qt::Key_Escape) + clearFocus(); + + QLineEdit::keyPressEvent(event); + } + }; + + textEdit = new LineEdit(this); textEdit->hide(); textEdit->setInputMethodHints(Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhPreferLatin); - connect(textEdit, &QLineEdit::editingFinished, this, &CroppingLabel::editingFinished); + connect(textEdit, &QLineEdit::returnPressed, this, &CroppingLabel::editingFinished); + connect(textEdit, &QLineEdit::editingFinished, this, &CroppingLabel::hideTextEdit); } void CroppingLabel::editBegin() @@ -114,6 +133,12 @@ void CroppingLabel::setElidedText() QLabel::setText(elidedText); } +void CroppingLabel::hideTextEdit() +{ + textEdit->hide(); + blockPaintEvents = false; +} + void CroppingLabel::showTextEdit() { blockPaintEvents = true; @@ -142,7 +167,5 @@ void CroppingLabel::editingFinished() if (origText != newText) emit editFinished(textEdit->text()); - textEdit->hide(); - blockPaintEvents = false; emit editRemoved(); } diff --git a/src/widget/tool/croppinglabel.h b/src/widget/tool/croppinglabel.h index 6a731f662..83d86322b 100644 --- a/src/widget/tool/croppinglabel.h +++ b/src/widget/tool/croppinglabel.h @@ -49,7 +49,7 @@ signals: protected: void paintEvent(QPaintEvent* paintEvent) override; void setElidedText(); - void hideTextEdit(bool acceptText); + void hideTextEdit(); void showTextEdit(); virtual void resizeEvent(QResizeEvent *ev) final override; virtual QSize sizeHint() const final override; diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index 0caaf1860..da6e89869 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -18,9 +18,11 @@ */ #include "widget.h" +#include "contentlayout.h" #include "ui_mainwindow.h" #include "src/core/core.h" #include "src/persistence/settings.h" +#include "contentdialog.h" #include "src/friend.h" #include "src/friendlist.h" #include "tool/friendrequestdialog.h" @@ -48,6 +50,7 @@ #include "src/widget/form/profileform.h" #include "src/widget/form/settingswidget.h" #include "tool/removefrienddialog.h" +#include "src/widget/tool/activatedialog.h" #include #include #include @@ -68,11 +71,13 @@ #include #include #include +#include #include #ifdef Q_OS_MAC #include #include +#include #endif #ifdef Q_OS_ANDROID @@ -113,11 +118,6 @@ void Widget::init() offlineMsgTimer = new QTimer(); offlineMsgTimer->start(15000); - //restore window state - restoreGeometry(Settings::getInstance().getWindowGeometry()); - restoreState(Settings::getInstance().getWindowState()); - ui->mainSplitter->restoreState(Settings::getInstance().getSplitterState()); - statusOnline = new QAction(this); statusOnline->setIcon(getStatusIcon(Status::Online, 10, 10)); connect(statusOnline, SIGNAL(triggered()), this, SLOT(setStatusOnline())); @@ -176,21 +176,7 @@ void Widget::init() ui->searchContactFilterBox->setMenu(filterMenu); - ui->mainContent->setLayout(new QVBoxLayout()); - ui->mainHead->setLayout(new QVBoxLayout()); - ui->mainHead->layout()->setMargin(0); - ui->mainHead->layout()->setSpacing(0); - - if (QStyleFactory::keys().contains(Settings::getInstance().getStyle()) - && Settings::getInstance().getStyle() != "None") - { - ui->mainHead->setStyle(QStyleFactory::create(Settings::getInstance().getStyle())); - ui->mainContent->setStyle(QStyleFactory::create(Settings::getInstance().getStyle())); - } - #ifndef Q_OS_MAC - ui->mainHead->setStyleSheet(Style::getStylesheet(":ui/settings/mainHead.css")); - ui->mainContent->setStyleSheet(Style::getStylesheet(":ui/settings/mainContent.css")); ui->statusHead->setStyleSheet(Style::getStylesheet(":/ui/window/statusPanel.css")); ui->statusPanel->setStyleSheet(Style::getStylesheet(":/ui/window/statusPanel.css")); #endif @@ -259,13 +245,17 @@ void Widget::init() new QShortcut(Qt::CTRL + Qt::Key_PageDown, this, SLOT(nextContact())); #ifdef Q_OS_MAC + QMenuBar* globalMenu = Nexus::getInstance().globalMenuBar; QAction* windowMenu = Nexus::getInstance().windowMenu->menuAction(); - QAction* fileMenu = Nexus::getInstance().globalMenuBar->insertMenu(windowMenu, new QMenu(tr("File"), this)); + QAction* viewMenu = Nexus::getInstance().viewMenu->menuAction(); + QAction* frontAction = Nexus::getInstance().frontAction; - QAction* editProfileAction = fileMenu->menu()->addAction(tr("Edit Profile")); + fileMenu = globalMenu->insertMenu(viewMenu, new QMenu(this)); + + editProfileAction = fileMenu->menu()->addAction(QString()); connect(editProfileAction, &QAction::triggered, this, &Widget::showProfile); - QMenu* changeStatusMenu = fileMenu->menu()->addMenu(tr("Change Status")); + changeStatusMenu = fileMenu->menu()->addMenu(QString()); fileMenu->menu()->addAction(changeStatusMenu->menuAction()); changeStatusMenu->addAction(statusOnline); changeStatusMenu->addSeparator(); @@ -273,50 +263,47 @@ void Widget::init() changeStatusMenu->addAction(statusBusy); fileMenu->menu()->addSeparator(); - QAction* logoutAction = fileMenu->menu()->addAction(tr("Log out")); + logoutAction = fileMenu->menu()->addAction(QString()); connect(logoutAction, &QAction::triggered, [this]() { Nexus::getInstance().showLogin(); }); - QAction* editMenu = Nexus::getInstance().globalMenuBar->insertMenu(windowMenu, new QMenu(tr("Edit"), this)); + editMenu = globalMenu->insertMenu(viewMenu, new QMenu(this)); editMenu->menu()->addSeparator(); - QAction* viewMenu = Nexus::getInstance().globalMenuBar->insertMenu(windowMenu, new QMenu(tr("View"), this)); + viewMenu->menu()->insertMenu(Nexus::getInstance().fullscreenAction, filterMenu); - QMenu* filterMenu = viewMenu->menu()->addMenu(tr("Filter...")); - filterMenu->addAction(filterDisplayName); - filterMenu->addAction(filterDisplayActivity); - filterMenu->addSeparator(); - filterMenu->addAction(filterAllAction); - filterMenu->addAction(filterOnlineAction); - filterMenu->addAction(filterOfflineAction); - filterMenu->addAction(filterFriendsAction); - filterMenu->addAction(filterGroupsAction); + viewMenu->menu()->insertSeparator(Nexus::getInstance().fullscreenAction); - viewMenu->menu()->addSeparator(); - fullscreenAction = viewMenu->menu()->addAction(tr("Enter Fullscreen")); - fullscreenAction->setShortcut(QKeySequence::FullScreen); - connect(fullscreenAction, &QAction::triggered, this, &Widget::toggleFullscreen); + contactMenu = globalMenu->insertMenu(windowMenu, new QMenu(this)); - QAction* contactMenu = Nexus::getInstance().globalMenuBar->insertMenu(windowMenu, new QMenu(tr("Contacts"), this)); - - QAction* addContactAction = contactMenu->menu()->addAction(tr("Add Contact...")); + addContactAction = contactMenu->menu()->addAction(QString()); connect(addContactAction, &QAction::triggered, this, &Widget::onAddClicked); - QAction* nextConversationAction = new QAction(tr("Next Conversation"), this); - Nexus::getInstance().windowMenu->addAction(nextConversationAction); + nextConversationAction = new QAction(this); + Nexus::getInstance().windowMenu->insertAction(frontAction, nextConversationAction); nextConversationAction->setShortcut(QKeySequence::SelectNextPage); - connect(nextConversationAction, &QAction::triggered, this, &Widget::nextContact); + connect(nextConversationAction, &QAction::triggered, [this]() + { + if (ContentDialog::current() == QApplication::activeWindow()) + ContentDialog::current()->cycleContacts(true); + else if (QApplication::activeWindow() == this) + cycleContacts(true); + }); - QAction* previousConversationAction = new QAction(tr("Previous Conversation"), this); - Nexus::getInstance().windowMenu->addAction(previousConversationAction); + previousConversationAction = new QAction(this); + Nexus::getInstance().windowMenu->insertAction(frontAction, previousConversationAction); previousConversationAction->setShortcut(QKeySequence::SelectPreviousPage); - connect(previousConversationAction, &QAction::triggered, this, &Widget::previousContact); + connect(previousConversationAction, &QAction::triggered, [this] + { + if (ContentDialog::current() == QApplication::activeWindow()) + ContentDialog::current()->cycleContacts(false); + else if (QApplication::activeWindow() == this) + cycleContacts(false); + }); - Nexus::getInstance().windowMenu->addSeparator(); - QAction* frontAction = Nexus::getInstance().windowMenu->addAction(tr("Bring All to Front")); - connect(frontAction, &QAction::triggered, this, &Widget::bringAllToFront); + windowMenu->menu()->insertSeparator(frontAction); QAction* preferencesAction = viewMenu->menu()->addAction(QString()); preferencesAction->setMenuRole(QAction::PreferencesRole); @@ -329,17 +316,36 @@ void Widget::init() onSettingsClicked(); settingsWidget->showAbout(); }); + + QMenu* dockChangeStatusMenu = new QMenu(tr("Status"), this); + dockChangeStatusMenu->addAction(statusOnline); + statusOnline->setIconVisibleInMenu(true); + dockChangeStatusMenu->addSeparator(); + dockChangeStatusMenu->addAction(statusAway); + dockChangeStatusMenu->addAction(statusBusy); + Nexus::getInstance().dockMenu->addAction(dockChangeStatusMenu->menuAction()); + + connect(this, &Widget::windowStateChanged, &Nexus::getInstance(), &Nexus::onWindowStateChanged); #endif - addFriendForm->show(*ui); - setWindowTitle(tr("Add friend")); + contentLayout = nullptr; + onSeparateWindowChanged(Settings::getInstance().getSeparateWindow(), false); + ui->addButton->setCheckable(true); ui->transferButton->setCheckable(true); ui->settingsButton->setCheckable(true); - setActiveToolMenuButton(Widget::AddButton); + + if (contentLayout != nullptr) + onAddClicked(); + + //restore window state + restoreGeometry(Settings::getInstance().getWindowGeometry()); + restoreState(Settings::getInstance().getWindowState()); + ui->mainSplitter->restoreState(Settings::getInstance().getSplitterState()); connect(settingsWidget, &SettingsWidget::compactToggled, contactListWidget, &FriendListWidget::onCompactChanged); connect(settingsWidget, &SettingsWidget::groupchatPositionToggled, contactListWidget, &FriendListWidget::onGroupchatPositionChanged); + connect(settingsWidget, &SettingsWidget::separateWindowToggled, this, &Widget::onSeparateWindowClicked); #if (AUTOUPDATE_ENABLED) if (Settings::getInstance().getCheckUpdates()) AutoUpdater::checkUpdatesAsyncInteractive(); @@ -350,6 +356,10 @@ void Widget::init() if (!Settings::getInstance().getShowSystemTray()) show(); + +#ifdef Q_OS_MAC + Nexus::getInstance().updateWindows(); +#endif } bool Widget::eventFilter(QObject *obj, QEvent *event) @@ -364,6 +374,10 @@ bool Widget::eventFilter(QObject *obj, QEvent *event) else wasMaximized = false; } + +#ifdef Q_OS_MAC + emit windowStateChanged(windowState()); +#endif } return false; } @@ -399,18 +413,26 @@ void Widget::updateIcons() Widget::~Widget() { + QWidgetList windowList = QApplication::topLevelWidgets(); + + for (QWidget* window : windowList) + { + if (window != this) + window->close(); + } + Translator::unregister(this); AutoUpdater::abortUpdates(); if (icon) icon->hide(); - hideMainForms(); delete profileForm; delete settingsWidget; delete addFriendForm; delete filesForm; delete timer; delete offlineMsgTimer; + delete contentLayout; FriendList::clear(); GroupList::clear(); @@ -517,11 +539,87 @@ void Widget::onStatusSet(Status status) updateIcons(); } +void Widget::onSeparateWindowClicked(bool separate) +{ + onSeparateWindowChanged(separate, true); +} + +void Widget::onSeparateWindowChanged(bool separate, bool clicked) +{ + if (!separate) + { + QWindowList windowList = QGuiApplication::topLevelWindows(); + + for (QWindow* window : windowList) + { + if (window->objectName() == "detachedWindow") + window->close(); + } + + QWidget* contentWidget = new QWidget(this); + contentLayout = new ContentLayout(contentWidget); + ui->mainSplitter->addWidget(contentWidget); + + setMinimumWidth(775); + + onSettingsClicked(); + } + else + { + int width = ui->friendList->size().width(); + QSize size; + QPoint pos; + + if (contentLayout) + { + pos = mapToGlobal(ui->mainSplitter->widget(1)->pos()); + size = ui->mainSplitter->widget(1)->size(); + } + + if (contentLayout != nullptr) + { + contentLayout->clear(); + contentLayout->parentWidget()->setParent(0); // Remove from splitter. + contentLayout->parentWidget()->hide(); + contentLayout->parentWidget()->deleteLater(); + contentLayout->deleteLater(); + contentLayout = nullptr; + } + + setMinimumWidth(ui->tooliconsZone->sizeHint().width()); + + if (clicked) + { + showNormal(); + resize(width, height()); + } + + setWindowTitle(QString()); + setActiveToolMenuButton(None); + + if (clicked) + { + ContentLayout* contentLayout = createContentDialog((SettingDialog)); + contentLayout->parentWidget()->resize(size); + contentLayout->parentWidget()->move(pos); + settingsWidget->show(contentLayout); + setActiveToolMenuButton(Widget::None); + } + } +} + void Widget::setWindowTitle(const QString& title) { - QString tmp = title; - /// <[^>]*> Regexp to remove HTML tags, in case someone used them in title - QMainWindow::setWindowTitle("qTox - " + tmp.remove(QRegExp("<[^>]*>"))); + if (title.isEmpty()) + { + QMainWindow::setWindowTitle(QApplication::applicationName()); + } + else + { + QString tmp = title; + /// <[^>]*> Regexp to remove HTML tags, in case someone used them in title + QMainWindow::setWindowTitle(QApplication::applicationName() + QStringLiteral(" - ") + tmp.remove(QRegExp("<[^>]*>"))); + } } void Widget::forceShow() @@ -533,11 +631,20 @@ void Widget::forceShow() void Widget::onAddClicked() { - hideMainForms(); - addFriendForm->show(*ui); - setWindowTitle(tr("Add friend")); - setActiveToolMenuButton(Widget::AddButton); - activeChatroomWidget = nullptr; + if (Settings::getInstance().getSeparateWindow()) + { + if (!addFriendForm->isShown()) + addFriendForm->show(createContentDialog(AddDialog)); + + setActiveToolMenuButton(Widget::None); + } + else + { + hideMainForms(nullptr); + addFriendForm->show(contentLayout); + setWindowTitle(fromDialogType(AddDialog)); + setActiveToolMenuButton(Widget::AddButton); + } } void Widget::onGroupClicked() @@ -547,11 +654,20 @@ void Widget::onGroupClicked() void Widget::onTransferClicked() { - hideMainForms(); - filesForm->show(*ui); - setWindowTitle(tr("File transfers")); - setActiveToolMenuButton(Widget::TransferButton); - activeChatroomWidget = nullptr; + if (Settings::getInstance().getSeparateWindow()) + { + if (!filesForm->isShown()) + filesForm->show(createContentDialog(TransferDialog)); + + setActiveToolMenuButton(Widget::None); + } + else + { + hideMainForms(nullptr); + filesForm->show(contentLayout); + setWindowTitle(fromDialogType(TransferDialog)); + setActiveToolMenuButton(Widget::TransferButton); + } } void Widget::confirmExecutableOpen(const QFileInfo file) @@ -626,33 +742,51 @@ void Widget::onIconClick(QSystemTrayIcon::ActivationReason reason) void Widget::onSettingsClicked() { - hideMainForms(); - settingsWidget->show(*ui); - setWindowTitle(tr("Settings")); - setActiveToolMenuButton(Widget::SettingButton); - activeChatroomWidget = nullptr; + if (Settings::getInstance().getSeparateWindow()) + { + if (!settingsWidget->isShown()) + settingsWidget->show(createContentDialog(SettingDialog)); + + setActiveToolMenuButton(Widget::None); + } + else + { + hideMainForms(nullptr); + settingsWidget->show(contentLayout); + setWindowTitle(fromDialogType(SettingDialog)); + setActiveToolMenuButton(Widget::SettingButton); + } } void Widget::showProfile() // onAvatarClicked, onUsernameClicked { - hideMainForms(); - profileForm->show(*ui); - setWindowTitle(tr("Profile")); - activeChatroomWidget = nullptr; + if (Settings::getInstance().getSeparateWindow()) + { + if (!profileForm->isShown()) + profileForm->show(createContentDialog(ProfileDialog)); + + setActiveToolMenuButton(Widget::None); + } + else + { + hideMainForms(nullptr); + profileForm->show(contentLayout); + setWindowTitle(fromDialogType(ProfileDialog)); + setActiveToolMenuButton(Widget::None); + } } -void Widget::hideMainForms() +void Widget::hideMainForms(GenericChatroomWidget* chatroomWidget) { setActiveToolMenuButton(Widget::None); - QLayoutItem* item; - while ((item = ui->mainHead->layout()->takeAt(0)) != 0) - item->widget()->hide(); - while ((item = ui->mainContent->layout()->takeAt(0)) != 0) - item->widget()->hide(); + if (contentLayout != nullptr) + contentLayout->clear(); if (activeChatroomWidget != nullptr) activeChatroomWidget->setAsInactiveChatroom(); + + activeChatroomWidget = chatroomWidget; } void Widget::onUsernameChanged(const QString& newUsername, const QString& oldUsername) @@ -705,7 +839,7 @@ void Widget::addFriend(int friendId, const QString &userId) Core* core = Nexus::getCore(); connect(newfriend, &Friend::displayedNameChanged, this, &Widget::onFriendDisplayChanged); connect(settingsWidget, &SettingsWidget::compactToggled, newfriend->getFriendWidget(), &GenericChatroomWidget::compactChange); - connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*))); + connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*,bool)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*,bool))); connect(newfriend->getFriendWidget(), SIGNAL(removeFriend(int)), this, SLOT(removeFriend(int))); connect(newfriend->getFriendWidget(), SIGNAL(copyFriendIdToClipboard(int)), this, SLOT(copyFriendIdToClipboard(int))); connect(newfriend->getFriendWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newfriend->getChatForm(), SLOT(focusInput())); @@ -786,12 +920,9 @@ void Widget::onFriendStatusChanged(int friendId, Status status) f->setStatus(status); f->getFriendWidget()->updateStatusLight(); if(f->getFriendWidget()->isActive()) - { - QString windowTitle = f->getFriendWidget()->getName(); - if (!f->getFriendWidget()->getStatusString().isNull()) - windowTitle += " (" + f->getFriendWidget()->getStatusString() + ")"; - setWindowTitle(windowTitle); - } + setWindowTitle(f->getFriendWidget()->getTitle()); + + ContentDialog::updateFriendStatus(friendId); //won't print the message if there were no messages before if (!f->getChatForm()->isEmpty() @@ -831,6 +962,8 @@ void Widget::onFriendStatusMessageChanged(int friendId, const QString& message) QString str = message; str.replace('\n', ' '); str.remove('\r'); str.remove(QChar((char)0)); // null terminator... f->setStatusMessage(str); + + ContentDialog::updateFriendStatusMessage(friendId, message); } void Widget::onFriendUsernameChanged(int friendId, const QString& username) @@ -858,23 +991,47 @@ void Widget::onFriendDisplayChanged(FriendWidget *friendWidget, Status s) } -void Widget::onChatroomWidgetClicked(GenericChatroomWidget *widget) +void Widget::onChatroomWidgetClicked(GenericChatroomWidget *widget, bool group) { - hideMainForms(); - - if (activeChatroomWidget != nullptr) - activeChatroomWidget->setAsInactiveChatroom(); - activeChatroomWidget = widget; - - widget->setAsActiveChatroom(); - widget->setChatForm(*ui); - setWindowTitle(widget->getName()); widget->resetEventFlags(); widget->updateStatusLight(); - QString windowTitle = widget->getName(); - if (!widget->getStatusString().isNull()) - windowTitle += " (" + widget->getStatusString() + ")"; - setWindowTitle(windowTitle); + + if (widget->chatFormIsSet(true) && !group) + return; + + if (Settings::getInstance().getSeparateWindow() || group) + { + ContentDialog* dialog = nullptr; + + if (!Settings::getInstance().getDontGroupWindows() && !group) + dialog = ContentDialog::current(); + + if (dialog == nullptr) + dialog = createContentDialog(); + + dialog->show(); + Friend* frnd = widget->getFriend(); + + if (frnd != nullptr) + { + addFriendDialog(frnd, dialog); + } + else + { + Group* group = widget->getGroup(); + addGroupDialog(group, dialog); + } + + dialog->raise(); + dialog->activateWindow(); + } + else + { + hideMainForms(widget); + widget->setChatForm(contentLayout); + widget->setAsActiveChatroom(); + setWindowTitle(widget->getTitle()); + } } void Widget::onFriendMessageReceived(int friendId, const QString& message, bool isAction) @@ -889,16 +1046,7 @@ void Widget::onFriendMessageReceived(int friendId, const QString& message, bool HistoryKeeper::getInstance()->addChatEntry(f->getToxId().publicKey, isAction ? "/me " + f->getDisplayedName() + " " + message : message, f->getToxId().publicKey, timestamp, true); - f->setEventFlag(f->getFriendWidget() != activeChatroomWidget); - newMessageAlert(f->getFriendWidget()); - f->getFriendWidget()->updateStatusLight(); - if (f->getFriendWidget()->isActive()) - { - QString windowTitle = f->getFriendWidget()->getName(); - if (!f->getFriendWidget()->getStatusString().isNull()) - windowTitle += " (" + f->getFriendWidget()->getStatusString() + ")"; - setWindowTitle(windowTitle); - } + newFriendMessageAlert(friendId); } void Widget::onReceiptRecieved(int friendId, int receipt) @@ -910,44 +1058,170 @@ void Widget::onReceiptRecieved(int friendId, int receipt) f->getChatForm()->getOfflineMsgEngine()->dischargeReceipt(receipt); } -void Widget::newMessageAlert(GenericChatroomWidget* chat) +void Widget::addFriendDialog(Friend *frnd, ContentDialog *dialog) { - bool inactiveWindow = isMinimized() || !isActiveWindow(); - if (!inactiveWindow && activeChatroomWidget != nullptr && chat == activeChatroomWidget) - return; + if (!ContentDialog::getFriendDialog(frnd->getFriendID()) && !Settings::getInstance().getSeparateWindow() && activeChatroomWidget == frnd->getFriendWidget()) + onAddClicked(); - if (ui->statusButton->property("status").toString() == "busy") - return; + FriendWidget* friendWidget = dialog->addFriend(frnd->getFriendID(), frnd->getDisplayedName()); - QApplication::alert(this); + friendWidget->setStatusMsg(frnd->getFriendWidget()->getStatusMsg()); - if (inactiveWindow) - eventFlag = true; + connect(friendWidget, SIGNAL(removeFriend(int)), this, SLOT(removeFriend(int))); + connect(friendWidget, SIGNAL(copyFriendIdToClipboard(int)), this, SLOT(copyFriendIdToClipboard(int))); - if (Settings::getInstance().getShowWindow()) + connect(Core::getInstance(), &Core::friendAvatarChanged, friendWidget, &FriendWidget::onAvatarChange); + connect(Core::getInstance(), &Core::friendAvatarRemoved, friendWidget, &FriendWidget::onAvatarRemoved); + + QPixmap avatar = Settings::getInstance().getSavedAvatar(frnd->getToxId().toString()); + if (!avatar.isNull()) + friendWidget->onAvatarChange(frnd->getFriendID(), avatar); +} + +void Widget::addGroupDialog(Group *group, ContentDialog *dialog) +{ + if (!ContentDialog::getGroupDialog(group->getGroupId()) && !Settings::getInstance().getSeparateWindow() && activeChatroomWidget == group->getGroupWidget()) + onAddClicked(); + + GroupWidget* groupWidget = dialog->addGroup(group->getGroupId(), group->getName()); + connect(groupWidget, SIGNAL(removeGroup(int)), this, SLOT(removeGroup(int))); + connect(groupWidget, SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), group->getChatForm(), SLOT(focusInput())); +} + +bool Widget::newFriendMessageAlert(int friendId) +{ + bool hasActive; + QWidget* currentWindow; + ContentDialog* contentDialog = ContentDialog::getFriendDialog(friendId); + Friend* f = FriendList::findFriend(friendId); + + if (contentDialog != nullptr) { - show(); - if (inactiveWindow && Settings::getInstance().getShowInFront()) - setWindowState(Qt::WindowActive); + currentWindow = contentDialog->window(); + hasActive = ContentDialog::isFriendWidgetActive(friendId); + } + else + { + currentWindow = window(); + hasActive = f->getFriendWidget() == activeChatroomWidget; } - if (Settings::getInstance().getNotifySound()) + if (newMessageAlert(currentWindow, hasActive)) { - static QFile sndFile(":audio/notification.pcm"); - static QByteArray sndData; + f->setEventFlag(true); + f->getFriendWidget()->updateStatusLight(); - if (sndData.isEmpty()) + if (contentDialog == nullptr) { - sndFile.open(QIODevice::ReadOnly); - sndData = sndFile.readAll(); - sndFile.close(); + if (hasActive) + setWindowTitle(f->getFriendWidget()->getTitle()); + } + else + { + ContentDialog::updateFriendStatus(friendId); } - Audio::playMono16Sound(sndData); + return true; } - if (activeChatroomWidget != chat) - ui->friendList->trackWidget(chat); + return false; +} + +bool Widget::newGroupMessageAlert(int groupId, bool notify) +{ + bool hasActive; + QWidget* currentWindow; + ContentDialog* contentDialog = ContentDialog::getGroupDialog(groupId); + Group* g = GroupList::findGroup(groupId); + + if (contentDialog != nullptr) + { + currentWindow = contentDialog->window(); + hasActive = ContentDialog::isGroupWidgetActive(groupId); + } + else + { + currentWindow = window(); + hasActive = g->getGroupWidget() == activeChatroomWidget; + } + + if (newMessageAlert(currentWindow, hasActive, notify)) + { + g->setEventFlag(true); + g->getGroupWidget()->updateStatusLight(); + + if (contentDialog == nullptr) + { + if (hasActive) + setWindowTitle(g->getGroupWidget()->getTitle()); + } + else + { + ContentDialog::updateGroupStatus(groupId); + } + + return true; + } + + return false; +} + +QString Widget::fromDialogType(DialogType type) +{ + switch (type) + { + case AddDialog: + return tr("Add friend"); + case TransferDialog: + return tr("File transfers"); + case SettingDialog: + return tr("Settings"); + case ProfileDialog: + return tr("Profile"); + default: + return QString(); + } +} + +bool Widget::newMessageAlert(QWidget* currentWindow, bool isActive, bool notify) +{ + bool inactiveWindow = isMinimized() || !currentWindow->isActiveWindow(); + + if (!inactiveWindow && isActive) + return false; + + if (ui->statusButton->property("status").toString() == "busy") + return false; + + if (notify) + { + QApplication::alert(currentWindow); + eventFlag = true; + + if (Settings::getInstance().getShowWindow()) + { + currentWindow->show(); + if (inactiveWindow && Settings::getInstance().getShowInFront()) + currentWindow->activateWindow(); + } + + if (Settings::getInstance().getNotifySound()) + { + static QFile sndFile(":audio/notification.pcm"); + static QByteArray sndData; + + if (sndData.isEmpty()) + { + sndFile.open(QIODevice::ReadOnly); + sndData = sndFile.readAll(); + sndFile.close(); + } + + Audio::playMono16Sound(sndData); + } + } + + return true; } void Widget::playRingtone() @@ -955,8 +1229,6 @@ void Widget::playRingtone() if (ui->statusButton->property("status").toString() == "busy") return; - QApplication::alert(this); - // for whatever reason this plays slower/downshifted from what any other program plays the file as... but whatever static QFile sndFile1(":audio/ToxicIncomingCall.pcm"); static QByteArray sndData1; @@ -1005,7 +1277,7 @@ void Widget::removeFriend(Friend* f, bool fake) } f->getFriendWidget()->setAsInactiveChatroom(); - if (static_cast(f->getFriendWidget()) == activeChatroomWidget) + if (f->getFriendWidget() == activeChatroomWidget) { activeChatroomWidget = nullptr; onAddClicked(); @@ -1013,11 +1285,16 @@ void Widget::removeFriend(Friend* f, bool fake) contactListWidget->removeFriendWidget(f->getFriendWidget()); + ContentDialog* lastDialog = ContentDialog::getFriendDialog(f->getFriendID()); + + if (lastDialog != nullptr) + lastDialog->removeFriend(f->getFriendID()); + FriendList::removeFriend(f->getFriendID(), fake); Nexus::getCore()->removeFriend(f->getFriendID(), fake); delete f; - if (ui->mainHead->layout()->isEmpty()) + if (contentLayout != nullptr && contentLayout->mainHead->layout()->isEmpty()) onAddClicked(); contactListWidget->reDraw(); @@ -1041,6 +1318,86 @@ void Widget::clearContactsList() removeGroup(g, true); } +ContentDialog* Widget::createContentDialog() const +{ + ContentDialog* contentDialog = new ContentDialog(settingsWidget); + contentDialog->show(); +#ifdef Q_OS_MAC + connect(contentDialog, &ContentDialog::destroyed, &Nexus::getInstance(), &Nexus::updateWindowsClosed); + connect(contentDialog, &ContentDialog::windowStateChanged, &Nexus::getInstance(), &Nexus::onWindowStateChanged); + connect(contentDialog->windowHandle(), &QWindow::windowTitleChanged, &Nexus::getInstance(), &Nexus::updateWindows); + Nexus::getInstance().updateWindows(); +#endif + return contentDialog; +} + +ContentLayout* Widget::createContentDialog(DialogType type) +{ + class Dialog : public ActivateDialog + { + public: + Dialog(DialogType type) + : ActivateDialog() + , type(type) + { + restoreGeometry(Settings::getInstance().getDialogSettingsGeometry()); + Translator::registerHandler(std::bind(&Dialog::retranslateUi, this), this); + retranslateUi(); + + connect(Core::getInstance(), &Core::usernameSet, this, &Dialog::retranslateUi); + } + + ~Dialog() + { + Translator::unregister(this); + } + + public slots: + + void retranslateUi() + { + setWindowTitle(Core::getInstance()->getUsername() + QStringLiteral(" - ") + Widget::fromDialogType(type)); + } + + protected: + void resizeEvent(QResizeEvent* event) override + { + Settings::getInstance().setDialogSettingsGeometry(saveGeometry()); + QDialog::resizeEvent(event); + } + + void moveEvent(QMoveEvent* event) override + { + Settings::getInstance().setDialogSettingsGeometry(saveGeometry()); + QDialog::moveEvent(event); + } + + private: + DialogType type; + }; + + Dialog* dialog = new Dialog(type); + dialog->setAttribute(Qt::WA_DeleteOnClose); + ContentLayout* contentLayoutDialog = new ContentLayout(dialog); + + dialog->setObjectName("detached"); + dialog->setLayout(contentLayoutDialog); + dialog->layout()->setMargin(0); + dialog->layout()->setSpacing(0); + dialog->setMinimumSize(720, 400); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); + +#ifdef Q_OS_MAC + connect(dialog, &Dialog::destroyed, &Nexus::getInstance(), &Nexus::updateWindowsClosed); + connect(dialog, &ActivateDialog::windowStateChanged, &Nexus::getInstance(), &Nexus::updateWindowsStates); + connect(dialog->windowHandle(), &QWindow::windowTitleChanged, &Nexus::getInstance(), &Nexus::updateWindows); + Nexus::getInstance().updateWindows(); +#endif + + return contentLayoutDialog; +} + void Widget::copyFriendIdToClipboard(int friendId) { Friend* f = FriendList::findFriend(friendId); @@ -1085,22 +1442,7 @@ void Widget::onGroupMessageReceived(int groupnumber, int peernumber, const QStri else g->getChatForm()->addMessage(author, message, isAction, QDateTime::currentDateTime(), true); - g->setEventFlag(static_cast(g->getGroupWidget()) != activeChatroomWidget); - - if (targeted || Settings::getInstance().getGroupAlwaysNotify()) - newMessageAlert(g->getGroupWidget()); - - if (targeted) - g->setMentionedFlag(true); // useful for highlighting line or desktop notifications - - g->getGroupWidget()->updateStatusLight(); - if (g->getGroupWidget()->isActive()) - { - QString windowTitle = g->getGroupWidget()->getName(); - if (!g->getGroupWidget()->getStatusString().isNull()) - windowTitle += " (" + g->getGroupWidget()->getStatusString() + ")"; - setWindowTitle(windowTitle); - } + newGroupMessageAlert(g->getGroupId(), targeted || Settings::getInstance().getGroupAlwaysNotify()); } void Widget::onGroupNamelistChanged(int groupnumber, int peernumber, uint8_t Change) @@ -1170,9 +1512,15 @@ void Widget::removeGroup(Group* g, bool fake) onAddClicked(); } GroupList::removeGroup(g->getGroupId(), fake); + + ContentDialog* contentDialog = ContentDialog::getGroupDialog(g->getGroupId()); + + if (contentDialog != nullptr) + contentDialog->removeGroup(g->getGroupId()); + Nexus::getCore()->removeGroup(g->getGroupId(), fake); delete g; - if (ui->mainHead->layout()->isEmpty()) + if (contentLayout != nullptr && contentLayout->mainHead->layout()->isEmpty()) onAddClicked(); contactListWidget->reDraw(); @@ -1201,7 +1549,7 @@ Group *Widget::createGroup(int groupId) newgroup->getGroupWidget()->updateStatusLight(); connect(settingsWidget, &SettingsWidget::compactToggled, newgroup->getGroupWidget(), &GenericChatroomWidget::compactChange); - connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*))); + connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*,bool)), this, SLOT(onChatroomWidgetClicked(GenericChatroomWidget*,bool))); connect(newgroup->getGroupWidget(), SIGNAL(removeGroup(int)), this, SLOT(removeGroup(int))); connect(newgroup->getGroupWidget(), SIGNAL(chatroomWidgetClicked(GenericChatroomWidget*)), newgroup->getChatForm(), SLOT(focusInput())); connect(newgroup->getChatForm(), &GroupChatForm::sendMessage, core, &Core::sendGroupMessage); @@ -1223,14 +1571,6 @@ void Widget::onEmptyGroupCreated(int groupId) group->getGroupWidget()->editName(); } -bool Widget::isFriendWidgetCurActiveWidget(const Friend* f) const -{ - if (!f) - return false; - - return (activeChatroomWidget == static_cast(f->getFriendWidget())); -} - bool Widget::event(QEvent * e) { switch (e->type()) @@ -1240,10 +1580,7 @@ bool Widget::event(QEvent * e) { activeChatroomWidget->resetEventFlags(); activeChatroomWidget->updateStatusLight(); - QString windowTitle = activeChatroomWidget->getName(); - if (!activeChatroomWidget->getStatusString().isNull()) - windowTitle += " (" + activeChatroomWidget->getStatusString() + ")"; - setWindowTitle(windowTitle); + setWindowTitle(activeChatroomWidget->getTitle()); } if (eventFlag) { @@ -1251,6 +1588,14 @@ bool Widget::event(QEvent * e) eventIcon = false; updateIcons(); } + +#ifdef Q_OS_MAC + emit windowStateChanged(windowState()); + + case QEvent::WindowStateChange: + Nexus::getInstance().updateWindowsStates(); +#endif + default: break; } @@ -1332,16 +1677,7 @@ void Widget::onTryCreateTrayIcon() } #ifdef Q_OS_MAC - QMenu* changeStatusMenu = new QMenu(tr("Status"), this); - changeStatusMenu->addAction(statusOnline); - statusOnline->setIconVisibleInMenu(true); - changeStatusMenu->addSeparator(); - changeStatusMenu->addAction(statusAway); - changeStatusMenu->addAction(statusBusy); - - QMenu* dockMenu = new QMenu(this); - dockMenu->addAction(changeStatusMenu->menuAction()); - qt_mac_set_dock_menu(dockMenu); + qt_mac_set_dock_menu(Nexus::getInstance().dockMenu); #endif } else if (!isVisible()) @@ -1702,34 +2038,23 @@ void Widget::retranslateUi() statusOnline->setText(tr("Online", "Button to set your status to 'Online'")); statusAway->setText(tr("Away", "Button to set your status to 'Away'")); statusBusy->setText(tr("Busy", "Button to set your status to 'Busy'")); - setWindowTitle(tr("Settings")); -} + + if (!Settings::getInstance().getSeparateWindow()) + setWindowTitle(fromDialogType(SettingDialog)); #ifdef Q_OS_MAC -void Widget::bringAllToFront() -{ - QWindowList windowList = QApplication::topLevelWindows(); - for (QWindow* window : windowList) - { - window->raise(); - window->showNormal(); - window->requestActivate(); - } -} + Nexus::getInstance().retranslateUi(); -void Widget::toggleFullscreen() -{ - QWidget* window = QApplication::activeWindow(); + filterMenu->menuAction()->setText(tr("Filter...")); - if (window->isFullScreen()) - { - window->showNormal(); - fullscreenAction->setText(tr("Enter Fullscreen")); - } - else - { - window->showFullScreen(); - fullscreenAction->setText(tr("Exit Fullscreen")); - } -} + fileMenu->setText(tr("File")); + editMenu->setText(tr("Edit")); + contactMenu->setText(tr("Contacts")); + changeStatusMenu->menuAction()->setText(tr("Change Status")); + editProfileAction->setText(tr("Edit Profile")); + logoutAction->setText(tr("Log out")); + addContactAction->setText(tr("Add Contact...")); + nextConversationAction->setText(tr("Next Conversation")); + previousConversationAction->setText(tr("Previous Conversation")); #endif +} diff --git a/src/widget/widget.h b/src/widget/widget.h index 443fd7068..5ee59bfa5 100644 --- a/src/widget/widget.h +++ b/src/widget/widget.h @@ -51,6 +51,8 @@ class SettingsWidget; class AddFriendForm; class CircleWidget; class QActionGroup; +class ContentLayout; +class ContentDialog; class Widget final : public QMainWindow { @@ -63,12 +65,26 @@ public: QString getUsername(); Camera* getCamera(); static Widget* getInstance(); - void newMessageAlert(GenericChatroomWidget* chat); - bool isFriendWidgetCurActiveWidget(const Friend* f) const; + void addFriendDialog(Friend* frnd, ContentDialog* dialog); + void addGroupDialog(Group* group, ContentDialog* dialog); + bool newFriendMessageAlert(int friendId); + bool newGroupMessageAlert(int groupId, bool notify); bool getIsWindowMinimized(); void updateIcons(); void clearContactsList(); + enum DialogType + { + AddDialog, + TransferDialog, + SettingDialog, + ProfileDialog + }; + + static QString fromDialogType(DialogType type); + ContentDialog* createContentDialog() const; + ContentLayout* createContentDialog(DialogType type); + static void confirmExecutableOpen(const QFileInfo file); void clearAllReceipts(); @@ -87,6 +103,8 @@ public: public slots: void onSettingsClicked(); + void onSeparateWindowClicked(bool separate); + void onSeparateWindowChanged(bool separate, bool clicked); void setWindowTitle(const QString& title); void forceShow(); void onConnected(); @@ -129,6 +147,9 @@ signals: void usernameChanged(const QString& username); void statusMessageChanged(const QString& statusMessage); void resized(); +#ifdef Q_OS_MAC + void windowStateChanged(Qt::WindowStates states); +#endif protected: virtual bool eventFilter(QObject *obj, QEvent *event) final override; @@ -143,8 +164,8 @@ private slots: void onTransferClicked(); void showProfile(); void onUsernameChanged(const QString& newUsername, const QString& oldUsername); + void onChatroomWidgetClicked(GenericChatroomWidget *, bool group); void onStatusMessageChanged(const QString& newStatusMessage); - void onChatroomWidgetClicked(GenericChatroomWidget *); void removeFriend(int friendId); void copyFriendIdToClipboard(int friendId); void removeGroup(int groupId); @@ -160,11 +181,6 @@ private slots: void processOfflineMsgs(); void friendListContextMenu(const QPoint &pos); -#ifdef Q_OS_MAC - void bringAllToFront(); - void toggleFullscreen(); -#endif - private: enum ActiveToolMenuButton { AddButton, @@ -184,8 +200,9 @@ private: }; private: + bool newMessageAlert(QWidget* currentWindow, bool isActive, bool notify = true); void setActiveToolMenuButton(ActiveToolMenuButton newActiveButton); - void hideMainForms(); + void hideMainForms(GenericChatroomWidget* chatroomWidget); Group *createGroup(int groupId); void removeFriend(Friend* f, bool fake = false); void removeGroup(Group* g, bool fake = false); @@ -225,6 +242,7 @@ private: Ui::MainWindow *ui; QSplitter *centralLayout; QPoint dragPosition; + ContentLayout* contentLayout; AddFriendForm *addFriendForm; ProfileForm *profileForm; SettingsWidget *settingsWidget; @@ -243,7 +261,15 @@ private: bool wasMaximized = false; #ifdef Q_OS_MAC - QAction* fullscreenAction; + QAction* fileMenu; + QAction* editMenu; + QAction* contactMenu; + QMenu* changeStatusMenu; + QAction* editProfileAction; + QAction* logoutAction; + QAction* addContactAction; + QAction* nextConversationAction; + QAction* previousConversationAction; #endif };