Files
qelectrotech-source-mirror/sources/qetapp.h
T
ispyisail 2e4bb486bb Add 3D mouse (SpaceMouse/SpacePilot) pan/zoom support via libspnav
Implements discussion #599's phase 1 (Linux, libspnav): a 3Dconnexion
6-DOF device pans and zooms the active diagram view, via spacenavd.

## Off by default, zero cost when off

QET_ENABLE_SPACEMOUSE (cmake/developer_options.cmake) is OFF. Verified in
two separate build directories from a clean configure: with it off, the
new cmake/find_spacemouse.cmake step runs and does nothing (no library
lookup, no definition, no new source files compiled), and qetapp.cpp/.h
produce zero new object code -- both are entirely #ifdef'd out. The
default build is byte-for-byte the same shape as before this commit.

With it on, libspnav is located via its pkg-config file (spnav.pc, shipped
by libspnav-dev on Debian/Ubuntu and equivalent packages elsewhere). If the
option is on but the library isn't found, this does not hard-fail
configure: it downgrades back to off with a warning, so an opt-in feature
never blocks a developer who doesn't have the library installed.

## No new navigation logic -- a new input source for the existing one

DiagramView::wheelEvent() already turns a physical wheel's delta into
horizontalScrollBar()/verticalScrollBar() calls for pan and a
zoom(1 + value/1000) call for zoom -- see diagramview.cpp:661-687.
SpaceMouseListener calls exactly those same primitives from spnav motion
events instead of wheel events. It does not reimplement panning or
zooming.

## Safe by construction even when compiled in

The overwhelming majority of users of a build with the option on still
won't have spacenavd running or a device attached -- that must never
surface as an error dialog or a startup warning. SpaceMouseListener::
isAvailable() reflects this: spnav_open() failing is treated as the
ordinary case, not an error, and the object then does nothing at all.
Verified for real in this environment, which genuinely has no spacenavd
running: built with the option on, ran the resulting binary to completion,
and confirmed zero crashes and zero spnav-related output of any kind --
the silence is the point.

Motion is read via a QSocketNotifier on spnav_fd() (event-driven, no
polling loop, no idle cost) and applied to whichever DiagramView is
currently active, found via qApp->activeWindow() -> QETDiagramEditor ->
currentProjectView() -> currentDiagram(): a 6-DOF device is one ambient
input source for the whole application, not something tied to a
particular window, so there is exactly one listener, owned by QETApp.

## What could not be verified without hardware

The Z-axis-to-zoom-factor mapping (SpaceMouseListener::zoomFactorForZAxis)
is a pure function specifically so it could be tested without a live
device: confirmed a centered device (z=0) yields exactly 1.0 (an exact
no-op, not an epsilon-off value that could trip DiagramView::zoom()'s
>=1 branch), and that push/pull produce symmetric zoom-in/out factors.

What genuinely cannot be checked in this environment: which physical axis
is "left/right" vs "up/down" vs "push/forward", their sign, and whether
the ZOOM_DIVISOR/PAN_SCALE constants feel right on real hardware. Both are
named constants specifically so recalibrating them is a one-line change
once someone with a device tries it -- flagged plainly in the PR rather
than presented as verified.

## Not in this commit

Windows/macOS (proprietary 3DxWare SDK, materially bigger lift) and
device button mapping are both explicitly out of scope for this phase per
the discussion.
2026-08-02 21:04:57 +12:00

311 lines
9.8 KiB
C++

/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free 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 2 of the License, or
(at your option) any later version.
QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef QET_APP_H
#define QET_APP_H
#include "ElementsCollection/elementslocation.h"
#include "qetarguments.h"
#include "titleblock/templatelocation.h"
#include <QByteArray>
#include <QPalette>
#include <QSystemTrayIcon>
#include <QTranslator>
class QSplashScreen;
class QMenu;
class QAction;
class QMainWindow;
#define QETAPP_COMMON_TBT_PROTOCOL "commontbt"
#define QETAPP_COMPANY_TBT_PROTOCOL "companytbt"
#define QETAPP_CUSTOM_TBT_PROTOCOL "customtbt"
class AboutQET;
class QETDiagramEditor;
class QETElementEditor;
class ElementsCollectionCache;
class TitleBlockTemplate;
class TitleBlockTemplatesCollection;
class TitleBlockTemplatesFilesCollection;
class QETProject;
class QETTitleBlockTemplateEditor;
class QTextOrientationSpinBoxWidget;
class RecentFiles;
#ifdef QET_SPACEMOUSE_SUPPORT
class SpaceMouseListener;
#endif
/**
@brief The QETApp class
This class represents the QElectroTech application.
*/
class QETApp : public QObject
{
Q_OBJECT
// constructors, destructor
public:
QETApp();
~QETApp() override;
private:
QETApp(const QETApp &);
// methods
public:
static QETApp *instance();
void setLanguage(const QString &);
static QString langFromSetting ();
void switchLayout(Qt::LayoutDirection);
static void printHelp();
static void printVersion();
static void printLicense();
static ElementsCollectionCache *collectionCache();
static TitleBlockTemplatesFilesCollection *commonTitleBlockTemplatesCollection();
static TitleBlockTemplatesFilesCollection *companyTitleBlockTemplatesCollection();
static TitleBlockTemplatesFilesCollection *customTitleBlockTemplatesCollection();
static QList<TitleBlockTemplatesCollection *> availableTitleBlockTemplatesCollections();
static TitleBlockTemplatesCollection *titleBlockTemplatesCollection(const QString &);
static QString commonElementsDir();
static QString companyElementsDir();
static QString customElementsDir();
static QString commonElementsDirN();
static QString companyElementsDirN();
static QString customElementsDirN();
static void resetCollectionsPath();
static QString commonTitleBlockTemplatesDir();
static QString companyTitleBlockTemplatesDir();
static QString customTitleBlockTemplatesDir();
static QString userMacrosDir();
static bool registerProject(QETProject *);
static bool unregisterProject(QETProject *);
static QMap<uint, QETProject *> registeredProjects();
static QETProject *project(const uint &);
static int projectId(const QETProject *);
static QString configDir();
static QString dataDir();
static QString documentDir();
static QString pictureDir();
static QString languagesPath();
static QString realPath(const QString &);
static QString symbolicPath(const QString &);
static QStringList handledFileExtensions();
static QStringList handledFiles(const QList<QUrl> &);
static RecentFiles *projectsRecentFiles();
static RecentFiles *elementsRecentFiles();
#ifdef QET_ALLOW_OVERRIDE_CED_OPTION
public:
static void overrideCommonElementsDir(const QString &);
private:
/**
@brief common_elements_dir
Directory containing the common elements collection
*/
static QString m_overrided_common_elements_dir;
#endif
#ifdef QET_ALLOW_OVERRIDE_CTBTD_OPTION
public:
static void overrideCommonTitleBlockTemplatesDir(const QString &);
private:
/**
@brief common_tbt_dir_
Directory containing
the common title block templates collection
*/
static QString common_tbt_dir_;
#endif
#ifdef QET_ALLOW_OVERRIDE_CD_OPTION
public:
static void overrideConfigDir(const QString &);
private:
/**
@brief config_dir
Directory containing the user configuration
and the custom elements collection
*/
static QString config_dir;
#endif
#ifdef QET_ALLOW_OVERRIDE_DD_OPTION
public:
static void overrideDataDir(const QString &);
private:
/**
@brief data_dir
Directory containing the users data
and the custom elements collection
*/
static QString data_dir;
#endif
public:
static void overrideLangDir(const QString &);
/**
@brief lang_dir
Directory containing localization files.
*/
static QString lang_dir;
static QFont diagramTextsFont(qreal = -1.0);
static QFont diagramTextsItemFont(qreal = -1.0);
static QFont dynamicTextsItemFont(qreal = -1.0);
static QFont indiTextsItemFont (qreal = -1.0);
static QETDiagramEditor *diagramEditorForFile(const QString &);
static QETDiagramEditor *diagramEditorAncestorOf (const QWidget *child);
static QList<QETDiagramEditor *> diagramEditors();
static QETDiagramEditor* diagramEditor(QETProject *project);
static QList<QETElementEditor *> elementEditors();
static QList<QETElementEditor *> elementEditors(QETProject *);
static QList<QETTitleBlockTemplateEditor *> titleBlockTemplateEditors();
static QList<QETTitleBlockTemplateEditor *> titleBlockTemplateEditors(QETProject *);
static QTextOrientationSpinBoxWidget *createTextOrientationSpinBoxWidget();
static TitleBlockTemplate *defaultTitleBlockTemplate();
#ifdef Q_OS_DARWIN
public:
bool eventFilter(QObject *object, QEvent *) override;
#endif
// attributes
private:
static QETApp *m_qetapp;
QTranslator qtTranslator;
QTranslator qetTranslator;
QSystemTrayIcon *m_qsti;
QSplashScreen *m_splash_screen;
QMenu *menu_systray;
QAction *quitter_qet;
QAction *reduce_appli;
QAction *restore_appli;
QAction *reduce_diagrams;
QAction *restore_diagrams;
QAction *reduce_elements;
QAction *restore_elements;
QAction *reduce_templates;
QAction *restore_templates;
QAction *new_diagram;
QAction *new_element;
QHash<QMainWindow *, QByteArray> window_geometries;
QHash<QMainWindow *, QByteArray> window_states;
bool every_editor_reduced;
bool every_diagram_reduced;
bool every_diagram_visible;
bool every_element_reduced;
bool every_element_visible;
bool every_template_reduced;
bool every_template_visible;
QSignalMapper signal_map;
QETArguments qet_arguments_; ///< Command-line arguments parser
/**
@brief non_interactive_execution_
Whether the application will end
without any user interaction
*/
bool non_interactive_execution_;
QPalette initial_palette_; ///< System color palette
static TitleBlockTemplatesFilesCollection *m_common_tbt_collection;
static TitleBlockTemplatesFilesCollection *m_company_tbt_collection;
static TitleBlockTemplatesFilesCollection *m_custom_tbt_collection;
static ElementsCollectionCache *collections_cache_;
#ifdef QET_SPACEMOUSE_SUPPORT
/// One per application, not per window: a physical 6-DOF device
/// is a single ambient input source, and motion is applied to
/// whichever DiagramView is currently active. See
/// SpaceMouseListener's class comment.
SpaceMouseListener *m_space_mouse_listener = nullptr;
#endif
static QMap<uint, QETProject *> registered_projects_;
static uint next_project_id;
static RecentFiles *m_projects_recent_files;
static RecentFiles *m_elements_recent_files;
static TitleBlockTemplate *default_titleblock_template_;
static QString m_common_element_dir;
static bool m_common_element_dir_is_set;
static QString m_company_element_dir;
static bool m_company_element_dir_is_set;
static QString m_custom_element_dir;
static bool m_custom_element_dir_is_set;
static QString m_user_company_tbt_dir;
static QString m_user_custom_tbt_dir;
static QString m_user_macros_dir;
public slots:
void systray(QSystemTrayIcon::ActivationReason);
void reduceEveryEditor();
void restoreEveryEditor();
void reduceDiagramEditors();
void restoreDiagramEditors();
void reduceElementEditors();
void restoreElementEditors();
void reduceTitleBlockTemplateEditors();
void restoreTitleBlockTemplateEditors();
void newDiagramEditor();
void newElementEditor();
bool closeEveryEditor();
void setMainWindowVisible(QMainWindow *, bool);
void invertMainWindowVisibility(QWidget *);
void useSystemPalette(bool);
void quitQET();
void checkRemainingWindows();
void openFiles(const QETArguments &);
void openProjectFiles(const QStringList &);
void openElementFiles(const QStringList &);
void openElementLocations(const QList<ElementsLocation> &);
void openTitleBlockTemplate(const TitleBlockTemplateLocation &, bool = false);
void openTitleBlockTemplate(const QString &);
void openTitleBlockTemplateFiles(const QStringList &);
void configureQET();
void aboutQET();
void receiveMessage(int instanceId, QByteArray message);
private:
template <class T> QList<T *> detectWindows() const;
template <class T> void setMainWindowsVisible(bool);
QList<QWidget *> floatingToolbarsAndDocksForMainWindow(QMainWindow *) const;
void parseArguments();
void initSplashScreen();
void setSplashScreenStep(const QString & = QString());
void initLanguage();
void initFonts();
void initStyle();
void initConfiguration();
void initSystemTray();
void buildSystemTrayMenu();
void checkBackupFiles();
void fetchWindowStats(
const QList<QETDiagramEditor *> &,
const QList<QETElementEditor *> &,
const QList<QETTitleBlockTemplateEditor *> &
);
template <class T> void addWindowsListToMenu(QMenu *, const QList<T *> &);
static int projectIdFromString(const QString &);
static QETProject *projectFromString(const QString &);
};
#endif