/*************************************************************************** * Copyright (c) 2025 Kindred Systems * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #include #include "OriginSelectorWidget.h" #include "OriginManager.h" #include "OriginManagerDialog.h" #include "FileOrigin.h" #include "BitmapFactory.h" namespace Gui { OriginSelectorWidget::OriginSelectorWidget(QWidget* parent) : QToolButton(parent) , m_menu(nullptr) , m_originActions(nullptr) , m_manageAction(nullptr) { setupUi(); connectSignals(); rebuildMenu(); updateDisplay(); } OriginSelectorWidget::~OriginSelectorWidget() { disconnectSignals(); } void OriginSelectorWidget::setupUi() { setPopupMode(QToolButton::InstantPopup); setToolButtonStyle(Qt::ToolButtonTextBesideIcon); setMinimumWidth(80); setMaximumWidth(160); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); // Create menu m_menu = new QMenu(this); setMenu(m_menu); // Create action group for exclusive selection m_originActions = new QActionGroup(this); m_originActions->setExclusive(true); // Connect action group to selection handler connect(m_originActions, &QActionGroup::triggered, this, &OriginSelectorWidget::onOriginActionTriggered); } void OriginSelectorWidget::connectSignals() { auto* mgr = OriginManager::instance(); // Connect to OriginManager fastsignals m_connRegistered = mgr->signalOriginRegistered.connect( [this](const std::string& id) { onOriginRegistered(id); } ); m_connUnregistered = mgr->signalOriginUnregistered.connect( [this](const std::string& id) { onOriginUnregistered(id); } ); m_connChanged = mgr->signalCurrentOriginChanged.connect( [this](const std::string& id) { onCurrentOriginChanged(id); } ); } void OriginSelectorWidget::disconnectSignals() { m_connRegistered.disconnect(); m_connUnregistered.disconnect(); m_connChanged.disconnect(); } void OriginSelectorWidget::onOriginRegistered(const std::string& /*originId*/) { // Rebuild menu to include new origin rebuildMenu(); } void OriginSelectorWidget::onOriginUnregistered(const std::string& /*originId*/) { // Rebuild menu to remove origin rebuildMenu(); } void OriginSelectorWidget::onCurrentOriginChanged(const std::string& /*originId*/) { // Update display and menu checkmarks updateDisplay(); // Update checked state in menu auto* mgr = OriginManager::instance(); std::string currentId = mgr->currentOriginId(); for (QAction* action : m_originActions->actions()) { std::string actionId = action->data().toString().toStdString(); action->setChecked(actionId == currentId); } } void OriginSelectorWidget::onOriginActionTriggered(QAction* action) { if (!action) { return; } std::string originId = action->data().toString().toStdString(); auto* mgr = OriginManager::instance(); // Check if origin requires connection FileOrigin* origin = mgr->getOrigin(originId); if (origin && origin->requiresAuthentication()) { ConnectionState state = origin->connectionState(); if (state == ConnectionState::Disconnected || state == ConnectionState::Error) { // Try to connect if (!origin->connect()) { // Connection failed - don't switch // Revert the checkmark to current origin std::string currentId = mgr->currentOriginId(); for (QAction* a : m_originActions->actions()) { a->setChecked(a->data().toString().toStdString() == currentId); } return; } } } mgr->setCurrentOrigin(originId); } void OriginSelectorWidget::onManageOriginsClicked() { OriginManagerDialog dialog(this); dialog.exec(); // Refresh the menu in case origins changed rebuildMenu(); updateDisplay(); } void OriginSelectorWidget::updateDisplay() { FileOrigin* origin = OriginManager::instance()->currentOrigin(); if (!origin) { setText(tr("No Origin")); setIcon(QIcon()); setToolTip(QString()); return; } setText(QString::fromStdString(origin->nickname())); setIcon(iconForOrigin(origin)); setToolTip(QString::fromStdString(origin->name())); } void OriginSelectorWidget::rebuildMenu() { m_menu->clear(); // Remove old actions from action group for (QAction* action : m_originActions->actions()) { m_originActions->removeAction(action); } auto* mgr = OriginManager::instance(); std::string currentId = mgr->currentOriginId(); // Add origin entries for (const std::string& originId : mgr->originIds()) { FileOrigin* origin = mgr->getOrigin(originId); if (!origin) { continue; } QAction* action = m_menu->addAction( iconForOrigin(origin), QString::fromStdString(origin->nickname()) ); action->setCheckable(true); action->setChecked(originId == currentId); action->setData(QString::fromStdString(originId)); action->setToolTip(QString::fromStdString(origin->name())); m_originActions->addAction(action); } // Add separator and manage action m_menu->addSeparator(); m_manageAction = m_menu->addAction( BitmapFactory().iconFromTheme("preferences-system"), tr("Manage Origins...") ); connect(m_manageAction, &QAction::triggered, this, &OriginSelectorWidget::onManageOriginsClicked); } QIcon OriginSelectorWidget::iconForOrigin(FileOrigin* origin) const { if (!origin) { return QIcon(); } QIcon baseIcon = origin->icon(); // For origins that require authentication, overlay connection status if (origin->requiresAuthentication()) { ConnectionState state = origin->connectionState(); switch (state) { case ConnectionState::Connected: // No overlay needed - use base icon break; case ConnectionState::Connecting: // TODO: Animated connecting indicator break; case ConnectionState::Disconnected: // Overlay disconnected indicator { QPixmap overlay = BitmapFactory().pixmapFromSvg( "dagViewFail", QSizeF(8, 8)); if (!overlay.isNull()) { baseIcon = BitmapFactoryInst::mergePixmap( baseIcon, overlay, BitmapFactoryInst::BottomRight); } } break; case ConnectionState::Error: // Overlay error indicator { QPixmap overlay = BitmapFactory().pixmapFromSvg( "Warning", QSizeF(8, 8)); if (!overlay.isNull()) { baseIcon = BitmapFactoryInst::mergePixmap( baseIcon, overlay, BitmapFactoryInst::BottomRight); } } break; } } return baseIcon; } } // namespace Gui