feat(gui): add OriginSelectorWidget for file origin selection (#13)
Some checks failed
Build and Test / build (push) Has been cancelled
Some checks failed
Build and Test / build (push) Has been cancelled
- Create OriginSelectorWidget class (QToolButton with dropdown menu) - Add OriginSelectorAction to create widget in toolbars - Add Std_Origin command registered in CommandStd.cpp - Add widget to File toolbar (before New/Open/Save) - Connect to OriginManager fastsignals for origin changes - Add Catppuccin Mocha styling for the widget - Widget shows current origin name/icon with connection status overlay This implements Issue #13: Origin selector toolbar widget
This commit is contained in:
265
src/Gui/OriginSelectorWidget.cpp
Normal file
265
src/Gui/OriginSelectorWidget.cpp
Normal file
@@ -0,0 +1,265 @@
|
||||
/***************************************************************************
|
||||
* 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 <QApplication>
|
||||
|
||||
#include "OriginSelectorWidget.h"
|
||||
#include "OriginManager.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(70);
|
||||
setMaximumWidth(120);
|
||||
|
||||
// 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()
|
||||
{
|
||||
// TODO: Open origins management dialog (Issue #15)
|
||||
// For now, this is a placeholder
|
||||
}
|
||||
|
||||
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 = BitmapFactory::mergePixmap(
|
||||
baseIcon, overlay, BitmapFactoryInst::BottomRight);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ConnectionState::Error:
|
||||
// Overlay error indicator
|
||||
{
|
||||
QPixmap overlay = BitmapFactory().pixmapFromSvg(
|
||||
"Warning", QSizeF(8, 8));
|
||||
if (!overlay.isNull()) {
|
||||
baseIcon = BitmapFactory::mergePixmap(
|
||||
baseIcon, overlay, BitmapFactoryInst::BottomRight);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return baseIcon;
|
||||
}
|
||||
|
||||
} // namespace Gui
|
||||
Reference in New Issue
Block a user