Files
logos-basecamp/app/window.cpp
T

318 lines
8.9 KiB
C++
Raw Normal View History

2025-10-08 13:38:08 -04:00
#include "window.h"
#include <QApplication>
#include <QScreen>
#include <QDebug>
#include <QLabel>
#include <QVBoxLayout>
#include <QPluginLoader>
#include <QDir>
#include <QFile>
2025-11-25 11:36:34 -05:00
#include <QSystemTrayIcon>
#include <QMenu>
#include <QAction>
#include <QCloseEvent>
#include <QIcon>
#include <QPixmap>
#include <IComponent.h>
2026-02-26 11:05:34 -03:00
#include <QStandardPaths>
#include <QTimer>
2026-03-18 14:17:05 +01:00
#include "LogosBasecampPaths.h"
#ifdef Q_OS_MAC
#include "trafficLightsTitleBar.h"
#include "macWindowStyle.h"
#endif
2025-10-08 13:38:08 -04:00
Window::Window(QWidget *parent)
: QMainWindow(parent)
, m_logosAPI(nullptr)
2025-11-25 11:36:34 -05:00
, m_trayIcon(nullptr)
, m_trayIconMenu(nullptr)
, m_showHideAction(nullptr)
, m_quitAction(nullptr)
2025-10-08 13:38:08 -04:00
{
setupUi();
2025-11-25 11:36:34 -05:00
createTrayIcon();
2025-10-08 13:38:08 -04:00
}
Window::Window(LogosAPI* logosAPI, QWidget *parent)
: QMainWindow(parent)
, m_logosAPI(logosAPI)
2025-11-25 11:36:34 -05:00
, m_trayIcon(nullptr)
, m_trayIconMenu(nullptr)
, m_showHideAction(nullptr)
, m_quitAction(nullptr)
2025-10-08 13:38:08 -04:00
{
setupUi();
2025-11-25 11:36:34 -05:00
createTrayIcon();
2025-10-08 13:38:08 -04:00
}
Window::~Window()
{
2025-11-25 11:36:34 -05:00
if (m_trayIcon) {
delete m_trayIcon;
}
2025-10-08 13:38:08 -04:00
}
void Window::setupUi()
{
// Determine the appropriate plugin extension based on the platform
QString pluginExtension;
#if defined(Q_OS_WIN)
pluginExtension = ".dll";
#elif defined(Q_OS_MAC)
pluginExtension = ".dylib";
#else // Linux and other Unix-like systems
pluginExtension = ".so";
#endif
QString embeddedPluginsDir = LogosBasecampPaths::embeddedPluginsDirectory() + "/";
2026-03-18 14:17:05 +01:00
QString userPluginsDir = LogosBasecampPaths::pluginsDirectory() + "/";
2026-02-26 11:05:34 -03:00
// Search embedded (pre-installed at build time) first, then user-writable directory.
2026-02-26 11:05:34 -03:00
auto resolvePlugin = [&](const QString& subdir, const QString& name) -> QString {
QString embeddedPath = embeddedPluginsDir + subdir + "/" + name + pluginExtension;
if (QFile::exists(embeddedPath))
return embeddedPath;
2026-02-26 11:05:34 -03:00
return userPluginsDir + subdir + "/" + name + pluginExtension;
};
2026-01-23 11:14:48 -05:00
// Load the main_ui plugin with the appropriate extension (now in subdirectory)
2026-02-26 11:05:34 -03:00
QString mainUiPluginPath = resolvePlugin("main_ui", "main_ui");
QPluginLoader loader(mainUiPluginPath);
2025-10-08 13:38:08 -04:00
QWidget* mainContent = nullptr;
QObject* mainUiPlugin = nullptr;
2025-10-08 13:38:08 -04:00
if (loader.load()) {
mainUiPlugin = loader.instance();
if (mainUiPlugin) {
2025-10-08 13:38:08 -04:00
// Try to create the main window using the plugin's createWidget method
QMetaObject::invokeMethod(mainUiPlugin, "createWidget",
2025-10-08 13:38:08 -04:00
Qt::DirectConnection,
Q_RETURN_ARG(QWidget*, mainContent),
Q_ARG(LogosAPI*, m_logosAPI));
}
}
if (mainContent) {
setCentralWidget(mainContent);
} else {
qWarning() << "================================================";
qWarning() << "Failed to load main UI plugin from:" << mainUiPluginPath;
2025-10-08 13:38:08 -04:00
qWarning() << "Error:" << loader.errorString();
qWarning() << "================================================";
// Fallback: show a message when plugin is not found
QWidget* fallbackWidget = new QWidget(this);
QVBoxLayout* layout = new QVBoxLayout(fallbackWidget);
QLabel* messageLabel = new QLabel("No main UI module found", fallbackWidget);
QFont font = messageLabel->font();
font.setPointSize(14);
messageLabel->setFont(font);
messageLabel->setAlignment(Qt::AlignCenter);
layout->addWidget(messageLabel);
setCentralWidget(fallbackWidget);
qWarning() << "Failed to load main UI plugin from:" << mainUiPluginPath;
2025-10-08 13:38:08 -04:00
}
2026-02-10 21:52:58 +01:00
// Set window title and size
2026-05-25 13:03:20 -04:00
setWindowTitle("Logos Basecamp");
2025-10-08 13:38:08 -04:00
resize(1024, 768);
#ifdef Q_OS_MAC
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
setupMacOSDockReopen();
// Create title bar after resize() so it gets full width from the start
m_trafficLightsTitleBar = new TrafficLightsTitleBar(this);
m_trafficLightsTitleBar->setGeometry(0, 0, width(), TrafficLightsTitleBar::kTitleBarHeight);
m_trafficLightsTitleBar->show();
m_trafficLightsTitleBar->raise();
#endif
2025-11-25 11:36:34 -05:00
}
void Window::changeEvent(QEvent* event)
{
QMainWindow::changeEvent(event);
#ifdef Q_OS_MAC
if (event->type() == QEvent::WindowStateChange) {
const bool fullScreen = (windowState() & Qt::WindowFullScreen) != 0;
if (m_trafficLightsTitleBar) {
if (fullScreen)
m_trafficLightsTitleBar->hide();
else
m_trafficLightsTitleBar->show();
}
applyMacWindowRoundedCorners(this, !fullScreen);
// This is needed to fix squared corners after exiting fullscreen mode
if (!fullScreen) {
const int w = width();
const int h = height();
resize(w - 1, h - 1);
QTimer::singleShot(0, this, [this, w, h]() {
resize(w, h);
applyMacWindowRoundedCorners(this, true);
});
}
}
#endif
}
void Window::resizeEvent(QResizeEvent* event)
{
QMainWindow::resizeEvent(event);
#ifdef Q_OS_MAC
if (m_trafficLightsTitleBar && m_trafficLightsTitleBar->isVisible())
m_trafficLightsTitleBar->setGeometry(0, 0, width(), TrafficLightsTitleBar::kTitleBarHeight);
#endif
}
void Window::showEvent(QShowEvent* event)
{
QMainWindow::showEvent(event);
#ifdef Q_OS_MAC
applyMacWindowRoundedCorners(this);
#endif
}
#ifdef Q_OS_MAC
void Window::setupMacOSDockReopen()
{
connect(qApp, &QApplication::applicationStateChanged, this, [this](Qt::ApplicationState state) {
if (state == Qt::ApplicationActive && !isVisible()) {
show();
raise();
activateWindow();
}
});
}
#endif
2025-11-25 11:36:34 -05:00
void Window::createTrayIcon()
{
if (!QSystemTrayIcon::isSystemTrayAvailable()) {
qWarning() << "System tray is not available on this system";
return;
}
// Create tray icon
m_trayIcon = new QSystemTrayIcon(this);
setIcon();
2026-05-25 13:03:20 -04:00
m_trayIcon->setToolTip("Logos Basecamp");
2025-11-25 11:36:34 -05:00
// Create context menu
m_trayIconMenu = new QMenu(this);
m_showHideAction = m_trayIconMenu->addAction(tr("Show/Hide"));
m_showHideAction->setCheckable(false);
connect(m_showHideAction, &QAction::triggered, this, &Window::showHideWindow);
m_trayIconMenu->addSeparator();
m_quitAction = m_trayIconMenu->addAction(tr("Quit"));
connect(m_quitAction, &QAction::triggered, this, &Window::quitApplication);
m_trayIcon->setContextMenu(m_trayIconMenu);
// Connect icon activation signal
connect(m_trayIcon, &QSystemTrayIcon::activated, this, &Window::iconActivated);
// Show the tray icon
m_trayIcon->show();
}
void Window::setIcon()
{
if (!m_trayIcon) {
return;
}
QIcon icon(":/icons/logos.png");
if (icon.isNull()) {
qWarning() << "Failed to load tray icon from resource";
// Create a simple fallback icon
icon = QIcon::fromTheme("application-x-executable");
if (icon.isNull()) {
// Last resort: create a minimal icon
QPixmap pixmap(16, 16);
pixmap.fill(Qt::blue);
icon = QIcon(pixmap);
}
}
m_trayIcon->setIcon(icon);
}
void Window::closeEvent(QCloseEvent *event)
{
#ifdef Q_OS_MAC
// In full screen, close only exits full screen (Discord-style); do not hide
if (windowState() & Qt::WindowFullScreen) {
setWindowState(Qt::WindowNoState);
event->ignore();
return;
}
#endif
2025-11-25 11:36:34 -05:00
if (m_trayIcon && m_trayIcon->isVisible()) {
// Hide the window instead of closing
hide();
event->ignore();
// Show a message to inform the user
if (m_trayIcon->supportsMessages()) {
m_trayIcon->showMessage(
2026-05-25 13:03:20 -04:00
tr("Logos Basecamp"),
2025-11-25 11:36:34 -05:00
tr("The application will continue to run in the system tray. "
"Click the tray icon to restore the window."),
QSystemTrayIcon::Information,
2000
);
}
} else {
// If system tray is not available, quit normally
event->accept();
}
}
void Window::showHideWindow()
{
if (isVisible()) {
hide();
} else {
show();
raise();
activateWindow();
}
}
void Window::iconActivated(QSystemTrayIcon::ActivationReason reason)
{
switch (reason) {
case QSystemTrayIcon::Trigger:
// Single click - toggle window visibility
showHideWindow();
break;
case QSystemTrayIcon::DoubleClick:
// Double click - also toggle (some platforms use double click)
showHideWindow();
break;
case QSystemTrayIcon::MiddleClick:
// Middle click - toggle as well
showHideWindow();
break;
default:
break;
}
}
void Window::quitApplication()
{
// Hide tray icon first
if (m_trayIcon) {
m_trayIcon->hide();
}
// Quit the application
QApplication::quit();
2025-10-08 13:38:08 -04:00
}