The complete source code of IdealIRC
http://www.idealirc.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
439 lines
14 KiB
439 lines
14 KiB
/*
|
|
* IdealIRC - Internet Relay Chat client
|
|
* Copyright (C) 2021 Tom-Andre Barstad.
|
|
* This software is licensed under the Software Attribution License.
|
|
* See LICENSE for more information.
|
|
*/
|
|
|
|
#include "IWin/IWinStatus.h"
|
|
#include "IWin/IWinChannel.h"
|
|
#include "IWin/IWinPrivate.h"
|
|
#include "IWin/IWinDCCChat.h"
|
|
#include "MdiManager.h"
|
|
#include "ConfigMgr.h"
|
|
#include <QApplication>
|
|
#include <QDebug>
|
|
|
|
namespace {
|
|
constexpr int WINDOW_DEFAULT_WIDTH = 800;
|
|
constexpr int WINDOW_DEFAULT_HEIGHT = 600;
|
|
constexpr int WINDOW_STEP = 30;
|
|
constexpr int WINDOW_STEP_RESET_POSITION = 400;
|
|
constexpr int WINDOW_STEP_RESET_ADJUST = 60;
|
|
}
|
|
|
|
MdiManager* MdiManager::m_instance{ nullptr };
|
|
|
|
MdiManager::MdiManager(QMdiArea& mdiArea, QToolBar& buttonBar, QSystemTrayIcon& trayIcon)
|
|
: m_mdiArea(mdiArea)
|
|
, trayIcon(trayIcon)
|
|
, m_bbMgr(&buttonBar)
|
|
{
|
|
m_instance = this;
|
|
connect(&m_mdiArea, &QMdiArea::subWindowActivated, this, &MdiManager::subwinActivated);
|
|
connect(&m_bbMgr, &ButtonbarMgr::changeWindow, &m_mdiArea, &QMdiArea::setActiveSubWindow);
|
|
}
|
|
|
|
MdiManager::~MdiManager()
|
|
{
|
|
for (auto subwin : m_mdiArea.subWindowList()) {
|
|
auto win = qobject_cast<IWin*>(subwin->widget());
|
|
if (!win) {
|
|
qWarning() << "During destruction of MdiManager; subwindow not part of IWin!" << subwin->widget();
|
|
}
|
|
|
|
auto dccchat = qobject_cast<IWinDCCChat*>(win);
|
|
if (dccchat)
|
|
dccchat->disconnectFromDCC();
|
|
}
|
|
}
|
|
|
|
MdiManager& MdiManager::instance()
|
|
{
|
|
return *m_instance;
|
|
}
|
|
|
|
MdiManager::ButtonBarEntry::ButtonBarEntry(QAction* button_, const QString& buttonText, QMdiSubWindow* parent)
|
|
: subwin(parent)
|
|
, button(button_)
|
|
{
|
|
menu = new QMenu(parent);
|
|
menuHead = menu->addAction(buttonText);
|
|
QFont f = menuHead->font();
|
|
f.setBold(true);
|
|
menuHead->setFont(f);
|
|
menuHead->setDisabled(true);
|
|
menuSep = menu->addSeparator();
|
|
menuClose = menu->addAction(tr("Close"));
|
|
connect(menuClose, &QAction::triggered, [&](bool){
|
|
subwin->close();
|
|
});
|
|
}
|
|
|
|
IWin* MdiManager::createSubwindow(IWin* parent, const QString& buttonText, IWin::Type windowType, bool activate, Highlight buttonHighlight)
|
|
{
|
|
IWin* basePtr{ nullptr };
|
|
IWinStatus* statusParent{ nullptr };
|
|
QString iconPath;
|
|
|
|
if (parent && parent->getType() == IWin::Type::Status)
|
|
statusParent = dynamic_cast<IWinStatus*>(parent);
|
|
|
|
switch (windowType) {
|
|
case IWin::Type::Status:
|
|
basePtr = new IWinStatus(buttonText);
|
|
if (!m_activeStatus || activate)
|
|
m_activeStatus = dynamic_cast<IWinStatus*>(basePtr);
|
|
iconPath = ":/Icons/serverwindow.png";
|
|
break;
|
|
|
|
case IWin::Type::Channel:
|
|
basePtr = new IWinChannel(statusParent, buttonText);
|
|
iconPath = ":/Icons/channel.png";
|
|
break;
|
|
|
|
case IWin::Type::Private:
|
|
basePtr = new IWinPrivate(statusParent, buttonText);
|
|
iconPath = ":/Icons/private.png";
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!basePtr) {
|
|
qWarning() << "Failed to create regular subwindow of type" << IWin::TypeString[windowType] << "with button text" << buttonText;
|
|
return nullptr;
|
|
}
|
|
|
|
return createSubwindowCommons(parent, basePtr, buttonText, iconPath, activate, buttonHighlight);
|
|
}
|
|
|
|
IWin* MdiManager::createDCCSubwindow(IWin* parent, IWin::Type windowType, const std::shared_ptr<DCC>& dcc, const IRCPrefix& target)
|
|
{
|
|
IWin* basePtr{ nullptr };
|
|
IWinStatus* statusParent{ nullptr };
|
|
QString iconPath;
|
|
auto targetNickname{ QString::fromStdString(target.toString()) };
|
|
|
|
if (parent && parent->getType() == IWin::Type::Status)
|
|
statusParent = dynamic_cast<IWinStatus*>(parent);
|
|
|
|
switch (windowType) {
|
|
case IWin::Type::DCCChat: {
|
|
auto* winPtr = new IWinDCCChat(statusParent, dcc, targetNickname);
|
|
if (dcc->isReversed())
|
|
winPtr->markReverseInitiatePending();
|
|
|
|
basePtr = winPtr;
|
|
iconPath = ":/Icons/private.png";
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!basePtr) {
|
|
qWarning() << "Failed to create DCC subwindow of type" << IWin::TypeString[windowType] << "with target" << QString::fromStdString(target.toString());
|
|
return nullptr;
|
|
}
|
|
|
|
return createSubwindowCommons(parent, basePtr, QStringLiteral("DCC: %1").arg(targetNickname), iconPath, true, HL_None);
|
|
}
|
|
|
|
IWin* MdiManager::currentWindow() const
|
|
{
|
|
return m_active;
|
|
}
|
|
|
|
IWinStatus* MdiManager::currentStatus() const
|
|
{
|
|
return m_activeStatus;
|
|
}
|
|
|
|
IWin* MdiManager::findWindow(IWin* statusParent, const QString& buttonText)
|
|
{
|
|
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) {
|
|
IWin* subwin = dynamic_cast<IWin*>(mdiwin->widget());
|
|
if (subwin->getParent() == statusParent && buttonText.compare(subwin->getButtonText(), Qt::CaseInsensitive) == 0) {
|
|
subwin->setButtonText(buttonText);
|
|
renameWindowButton(subwin, buttonText);
|
|
return subwin;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
QMdiSubWindow* MdiManager::toMdiwin(IWin* win)
|
|
{
|
|
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) {
|
|
if (mdiwin->widget() == win)
|
|
return mdiwin;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
QList<QMdiSubWindow*> MdiManager::mdiChildrenOf(const IWin* statusParent, IWin::Type type) const
|
|
{
|
|
QList<QMdiSubWindow*> retlist;
|
|
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) {
|
|
IWin* subwin = dynamic_cast<IWin*>(mdiwin->widget());
|
|
if (type != IWin::Type::Undefined && subwin->getType() != type)
|
|
continue;
|
|
|
|
if (subwin->getParent() == statusParent)
|
|
retlist.push_back(mdiwin);
|
|
}
|
|
return retlist;
|
|
}
|
|
|
|
QList<IWin*> MdiManager::childrenOf(const IWin* statusParent, IWin::Type type) const
|
|
{
|
|
QList<IWin*> retlist;
|
|
QList<QMdiSubWindow*> subwinList = mdiChildrenOf(statusParent, type);
|
|
for (QMdiSubWindow* subwin : subwinList)
|
|
retlist.push_back(qobject_cast<IWin*>(subwin->widget()));
|
|
return retlist;
|
|
}
|
|
|
|
void MdiManager::showTrayInfo(const QString& title, const QString& message)
|
|
{
|
|
showTray(title, message, QSystemTrayIcon::MessageIcon::Information);
|
|
}
|
|
|
|
void MdiManager::showTrayWarn(const QString& title, const QString& message)
|
|
{
|
|
showTray(title, message, QSystemTrayIcon::MessageIcon::Warning);
|
|
}
|
|
|
|
int MdiManager::connectionsOnlineCount() const
|
|
{
|
|
int c = 0;
|
|
|
|
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) {
|
|
IWin* subwin = dynamic_cast<IWin*>(mdiwin->widget());
|
|
if (subwin->getType() != IWin::Type::Status)
|
|
continue;
|
|
auto* status = dynamic_cast<IWinStatus*>(subwin);
|
|
if (status->getConnection().isOnline())
|
|
++c;
|
|
}
|
|
return c;
|
|
}
|
|
|
|
void MdiManager::broadcastProgramExit()
|
|
{
|
|
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) {
|
|
IWin* subwin = dynamic_cast<IWin*>(mdiwin->widget());
|
|
if (subwin->getType() == IWin::Type::Status) {
|
|
auto* status = dynamic_cast<IWinStatus*>(subwin);
|
|
if (status->getConnection().isOnline()) {
|
|
connect(&status->getConnection(), &IRC::readyForExit,
|
|
[this]() {
|
|
if (connectionsOnlineCount() == 0)
|
|
emit readyForExit();
|
|
});
|
|
|
|
ConfigMgr& conf = ConfigMgr::instance();
|
|
status->getConnection().disconnectForExit(conf.common("QuitMessage"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MdiManager::renameWindowButton(IWin* window, const QString& text)
|
|
{
|
|
m_bbMgr.reloadButtonName(window, text);
|
|
window->setButtonText(text);
|
|
}
|
|
|
|
void MdiManager::print(IWinStatus* parent, const QString& target, const PrintType ptype, const QString& text)
|
|
{
|
|
IWin* subwin = findWindow(parent, target);
|
|
if (!subwin || !subwin->print(ptype, text)) {
|
|
if (ptype != PrintType::CTCP)
|
|
parent->print(ptype, QStringLiteral("[%1] %2").arg(target).arg(text));
|
|
else
|
|
parent->print(ptype, text);
|
|
}
|
|
}
|
|
|
|
void MdiManager::print(const QString& target, const PrintType ptype, const QString& text)
|
|
{
|
|
print(m_activeStatus, target, ptype, text);
|
|
}
|
|
|
|
void MdiManager::printToActive(IWinStatus* parent, const PrintType ptype, const QString& text)
|
|
{
|
|
if (!m_active)
|
|
parent->print(ptype, text);
|
|
else if (m_active->getParent() != parent)
|
|
parent->print(ptype, text);
|
|
else
|
|
m_active->print(ptype, text);
|
|
}
|
|
|
|
void MdiManager::printToTypes(IWinStatus* parent, const IWin::Type toType, const PrintType ptype, const QString& text)
|
|
{
|
|
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) {
|
|
IWin* subwin = qobject_cast<IWin*>(mdiwin->widget());
|
|
if (subwin->getParent() == parent && subwin->getType() == toType)
|
|
subwin->print(ptype, text);
|
|
}
|
|
}
|
|
|
|
void MdiManager::printToAll(IWinStatus* parent, const PrintType ptype, const QString& text)
|
|
{
|
|
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList()) {
|
|
IWin* subwin = qobject_cast<IWin*>(mdiwin->widget());
|
|
if (subwin->getParent() == parent || subwin == parent)
|
|
subwin->print(ptype, text);
|
|
}
|
|
}
|
|
|
|
IWin* MdiManager::createSubwindowCommons(IWin* parent, IWin* window, const QString& buttonText, const QString& iconPath, bool activate, Highlight buttonHighlight)
|
|
{
|
|
const auto windowType = window->getType();
|
|
|
|
connect(window, &IWin::aboutToClose,
|
|
this, &MdiManager::subwinAboutToClose);
|
|
|
|
QMdiSubWindow* mdiwin{ m_mdiArea.addSubWindow(window, Qt::SubWindow) };
|
|
|
|
if (!iconPath.isEmpty())
|
|
mdiwin->setWindowIcon(QIcon(iconPath));
|
|
|
|
mdiwin->setAttribute(Qt::WA_DeleteOnClose);
|
|
m_bbMgr.addButton(window, mdiwin);
|
|
m_bbMgr.highlight(window, buttonHighlight);
|
|
|
|
QString networkKey, windowKey;
|
|
if (windowType == IWin::Type::Channel || windowType == IWin::Type::Private) {
|
|
const auto& connection = dynamic_cast<IWinStatus*>(parent)->getConnection();
|
|
|
|
try {
|
|
networkKey = QString::fromStdString(connection.isupport().at("NETWORK"));
|
|
}
|
|
catch (...) {}
|
|
|
|
windowKey = buttonText;
|
|
}
|
|
|
|
QRect spawnCoord = generateSpawnCoordinates(networkKey, windowKey);
|
|
mdiwin->setGeometry(spawnCoord);
|
|
|
|
qInfo() << "Created a subwindow of type" << IWin::TypeString[windowType] << "with button text" << buttonText;
|
|
|
|
if (!m_active || m_active->isMaximized())
|
|
mdiwin->showMaximized();
|
|
else
|
|
mdiwin->show();
|
|
|
|
if (!activate) {
|
|
m_mdiArea.activatePreviousSubWindow();
|
|
mdiwin->lower();
|
|
}
|
|
|
|
if (windowType == IWin::Type::Status)
|
|
emit statusWindowCreated(dynamic_cast<IWinStatus*>(window));
|
|
|
|
return window;
|
|
}
|
|
|
|
void MdiManager::showTray(const QString& title, const QString& message, QSystemTrayIcon::MessageIcon icon)
|
|
{
|
|
if (QApplication::activeWindow() || QApplication::focusWidget()) return;
|
|
|
|
ConfigMgr& conf = ConfigMgr::instance();
|
|
if (conf.common("TrayNotify") != "1") return;
|
|
int delay = conf.common("TrayNotifyDelay").toInt();
|
|
trayIcon.showMessage(title, message, icon, delay*1000);
|
|
}
|
|
|
|
void MdiManager::subwinAboutToClose(IWin* who)
|
|
{
|
|
qInfo() << "Closing" << who;
|
|
m_bbMgr.delButton(who);
|
|
}
|
|
|
|
void MdiManager::subwinActivated(QMdiSubWindow* window)
|
|
{
|
|
if (!window) return;
|
|
|
|
IWin* cw = dynamic_cast<IWin*>(window->widget());
|
|
qInfo() << "Activated:" << cw;
|
|
if (!cw) {
|
|
qWarning() << "NULL window!";
|
|
return;
|
|
}
|
|
m_bbMgr.subwinActivated(m_active, cw);
|
|
m_active = cw;
|
|
if (cw->getParent() && cw->getParent()->getType() == IWin::Type::Status)
|
|
m_activeStatus = dynamic_cast<IWinStatus*>(cw->getParent());
|
|
else if (cw->getType() == IWin::Type::Status)
|
|
m_activeStatus = qobject_cast<IWinStatus*>(cw);
|
|
|
|
emit subwindowSwitched();
|
|
}
|
|
|
|
QRect MdiManager::generateSpawnCoordinates(const QString& networkKey, const QString& windowKey)
|
|
{
|
|
auto generate = [this] {
|
|
QRect rectangle(nextXY, nextXY, WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT);
|
|
nextXY += WINDOW_STEP;
|
|
|
|
if (nextXY > WINDOW_STEP_RESET_POSITION) {
|
|
nextXYadjust += WINDOW_STEP_RESET_ADJUST;
|
|
if (nextXYadjust > WINDOW_STEP_RESET_POSITION)
|
|
nextXYadjust = 0;
|
|
|
|
nextXY = nextXYadjust;
|
|
}
|
|
|
|
return rectangle;
|
|
};
|
|
|
|
if (windowKey.isEmpty()) {
|
|
return generate();
|
|
}
|
|
else {
|
|
auto persistentKey = QStringLiteral("%1_%2")
|
|
.arg(networkKey)
|
|
.arg(windowKey);
|
|
if (networkKey.isEmpty())
|
|
persistentKey = windowKey;
|
|
|
|
const auto& conf = ConfigMgr::instance();
|
|
auto geometryString = conf.window(persistentKey);
|
|
if (geometryString.isEmpty()) {
|
|
/*
|
|
* If the key (persistentKey) doesn't exist, check config if any window name that isn't
|
|
* tied to any network name exist, and use that.
|
|
*/
|
|
geometryString = conf.window(windowKey);
|
|
if (geometryString.isEmpty())
|
|
return generate();
|
|
}
|
|
|
|
QStringList geometry = geometryString.split(',');
|
|
if (geometry.count() < 4) {
|
|
qWarning() << "Configuration for subwindow geometry '" << persistentKey << "' is invalid, resolving using generic positioning algorithm.";
|
|
return generate();
|
|
}
|
|
|
|
const int X = geometry[0].toInt();
|
|
const int Y = geometry[1].toInt();
|
|
const int W = geometry[2].toInt();
|
|
const int H = geometry[3].toInt();
|
|
return { X, Y, W, H };
|
|
}
|
|
}
|
|
|
|
QList<QMdiSubWindow*> MdiManager::mdiChildren() const
|
|
{
|
|
QList<QMdiSubWindow*> ret;
|
|
for (QMdiSubWindow* mdiwin : m_mdiArea.subWindowList())
|
|
ret << mdiwin;
|
|
return ret;
|
|
}
|
|
|