mirror of
https://github.com/OpenSpace/OpenSpace.git
synced 2026-02-21 12:29:04 -06:00
Launcher improvements (#3523)
- Adding support for subfolders in configurations and profiles
- Disable the edit button if profile does not exist
- Disable the Start button if the profile or window configuration does not exist
- Change the layout of the Window Configuration editor
- Explicitly specify which windows should have Overlay rendering and 3D rendering
- Add keyboard shortcuts for adding and removing windows
- Use a normal Save dialog for the profile editor instead of the renaming + Duplicate button
This commit is contained in:
@@ -25,9 +25,12 @@
|
||||
include(${PROJECT_SOURCE_DIR}/support/cmake/set_openspace_compile_settings.cmake)
|
||||
|
||||
set(HEADER_FILES
|
||||
include/backgroundimage.h
|
||||
include/filesystemaccess.h
|
||||
include/launcherwindow.h
|
||||
include/settingsdialog.h
|
||||
include/splitcombobox.h
|
||||
include/windowcolors.h
|
||||
include/profile/actiondialog.h
|
||||
include/profile/additionalscriptsdialog.h
|
||||
include/profile/assetsdialog.h
|
||||
@@ -47,16 +50,17 @@ set(HEADER_FILES
|
||||
include/profile/propertiesdialog.h
|
||||
include/sgctedit/displaywindowunion.h
|
||||
include/sgctedit/monitorbox.h
|
||||
include/sgctedit/orientationdialog.h
|
||||
include/sgctedit/settingswidget.h
|
||||
include/sgctedit/sgctedit.h
|
||||
include/sgctedit/windowcontrol.h
|
||||
)
|
||||
|
||||
set(SOURCE_FILES
|
||||
src/backgroundimage.cpp
|
||||
src/launcherwindow.cpp
|
||||
src/filesystemaccess.cpp
|
||||
src/settingsdialog.cpp
|
||||
src/splitcombobox.cpp
|
||||
src/windowcolors.cpp
|
||||
src/profile/actiondialog.cpp
|
||||
src/profile/additionalscriptsdialog.cpp
|
||||
src/profile/assetsdialog.cpp
|
||||
@@ -77,8 +81,6 @@ set(SOURCE_FILES
|
||||
src/sgctedit/sgctedit.cpp
|
||||
src/sgctedit/displaywindowunion.cpp
|
||||
src/sgctedit/monitorbox.cpp
|
||||
src/sgctedit/orientationdialog.cpp
|
||||
src/sgctedit/settingswidget.cpp
|
||||
src/sgctedit/windowcontrol.cpp
|
||||
)
|
||||
|
||||
|
||||
@@ -22,35 +22,18 @@
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_UI_LAUNCHER___ORIENTATIONDIALOG___H__
|
||||
#define __OPENSPACE_UI_LAUNCHER___ORIENTATIONDIALOG___H__
|
||||
#ifndef __OPENSPACE_UI_LAUNCHER___BACKGROUNDIMAGE___H__
|
||||
#define __OPENSPACE_UI_LAUNCHER___BACKGROUNDIMAGE___H__
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
|
||||
#include <sgct/math.h>
|
||||
#include <filesystem>
|
||||
|
||||
class QLineEdit;
|
||||
class QWidget;
|
||||
|
||||
class OrientationDialog final : public QDialog {
|
||||
class BackgroundImage final : public QLabel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* Constructor for OrientationDialog object which contains the input text boxes for
|
||||
* orientation x,y,z values,
|
||||
*
|
||||
* \param orientation The x,y,z angles in degrees contained in sgct::quat object
|
||||
* \param parent Pointer to Qt QWidget parent object
|
||||
*/
|
||||
OrientationDialog(sgct::quat& orientation, QWidget* parent);
|
||||
|
||||
private:
|
||||
void ok();
|
||||
|
||||
QLineEdit* _linePitch = nullptr;
|
||||
QLineEdit* _lineRoll = nullptr;
|
||||
QLineEdit* _lineYaw = nullptr;
|
||||
sgct::quat& _orientationValue;
|
||||
BackgroundImage(QRect size, const std::filesystem::path& syncFolder,
|
||||
QWidget* parent = nullptr);
|
||||
};
|
||||
|
||||
#endif // __OPENSPACE_UI_LAUNCHER___ORIENTATIONDIALOG___H__
|
||||
#endif // __OPENSPACE_UI_LAUNCHER___BACKGROUNDIMAGE___H__
|
||||
@@ -35,10 +35,9 @@
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
namespace openspace { struct Configuration; }
|
||||
class SplitComboBox;
|
||||
|
||||
class QComboBox;
|
||||
class QLabel;
|
||||
namespace openspace { struct Configuration; }
|
||||
|
||||
class LauncherWindow final : public QMainWindow {
|
||||
Q_OBJECT
|
||||
@@ -51,11 +50,9 @@ public:
|
||||
* \param sgctConfigEnabled `true` if window selection combo box will be enabled
|
||||
* \param sgctConfigName The name of the sgct configuration function used to generate
|
||||
* window config (blank if file is used)
|
||||
* \param parent The parent that contains this (and possibly other) children in the
|
||||
* tree structure.
|
||||
*/
|
||||
LauncherWindow(bool profileEnabled, const openspace::Configuration& globalConfig,
|
||||
bool sgctConfigEnabled, std::string sgctConfigName, QWidget* parent);
|
||||
bool sgctConfigEnabled, std::string sgctConfigName);
|
||||
|
||||
/**
|
||||
* Returns whether "Start OpenSpace" was chosen when this window closed.
|
||||
@@ -80,14 +77,6 @@ public:
|
||||
*/
|
||||
std::string selectedWindowConfig() const;
|
||||
|
||||
/**
|
||||
* Returns `true` if the window configuration filename selected in the combo box
|
||||
* is a file in the user configurations section.
|
||||
*
|
||||
* \return `true` if window configuration is a user configuration file
|
||||
*/
|
||||
bool isUserConfigSelected() const;
|
||||
|
||||
/**
|
||||
* Handles keypresses while the Qt launcher window is open.
|
||||
*
|
||||
@@ -95,20 +84,42 @@ public:
|
||||
*/
|
||||
void keyPressEvent(QKeyEvent* evt) override;
|
||||
|
||||
private slots:
|
||||
// Open the profile editor to the currently selected profile
|
||||
void editProfile();
|
||||
|
||||
// Open the profile editor to a new empty profile
|
||||
void newProfile();
|
||||
|
||||
// A new profile has been selected. In this case `selection` is guaranteed to have a
|
||||
// value
|
||||
void selectProfile(std::optional<std::string> selection);
|
||||
|
||||
// Open the window configuration on the currently selected file
|
||||
void editConfiguration();
|
||||
|
||||
// Open the window configuration on a new empty configuration
|
||||
void newConfiguration();
|
||||
|
||||
// A new configuration has been selected. `selection` might be `std::nullopt` if the
|
||||
// newly selected configuration was the value provided in the openspace.cfg file
|
||||
void selectConfiguration(std::optional<std::string> selection);
|
||||
|
||||
// The main start button has been pressed
|
||||
void start();
|
||||
|
||||
// Open the settings dialog window
|
||||
void openSettings();
|
||||
|
||||
private:
|
||||
QWidget* createCentralWidget();
|
||||
void setBackgroundImage(const std::filesystem::path& syncPath);
|
||||
|
||||
// Actually open the profile editor
|
||||
void openProfileEditor(const std::string& profile, bool isUserProfile);
|
||||
void openWindowEditor(const std::string& winCfg, bool isUserWinCfg);
|
||||
void editRefusalDialog(const std::string& title, const std::string& msg,
|
||||
const std::string& detailedText);
|
||||
|
||||
void populateProfilesList(const std::string& preset);
|
||||
void populateWindowConfigsList(const std::string& preset);
|
||||
void handleReturnFromWindowEditor(const sgct::config::Cluster& cluster,
|
||||
std::filesystem::path savePath, const std::filesystem::path& saveWindowCfgPath);
|
||||
void onNewWindowConfigSelection(int newIndex);
|
||||
void handleReturnFromWindowEditor(std::filesystem::path savePath);
|
||||
|
||||
// Enables or disables the start button depending on the validity of the selected
|
||||
// profile and configurations
|
||||
void updateStartButton() const;
|
||||
|
||||
const std::filesystem::path _assetPath;
|
||||
const std::filesystem::path _userAssetPath;
|
||||
@@ -117,16 +128,11 @@ private:
|
||||
const std::filesystem::path _profilePath;
|
||||
const std::filesystem::path _userProfilePath;
|
||||
bool _shouldLaunch = false;
|
||||
int _userAssetCount = 0;
|
||||
int _userConfigStartingIdx = 0;
|
||||
int _userConfigCount = 0;
|
||||
int _preDefinedConfigStartingIdx = 0;
|
||||
const std::string _sgctConfigName;
|
||||
int _windowConfigBoxIndexSgctCfgDefault = 0;
|
||||
|
||||
QComboBox* _profileBox = nullptr;
|
||||
QComboBox* _windowConfigBox = nullptr;
|
||||
QLabel* _backgroundImage = nullptr;
|
||||
SplitComboBox* _profileBox = nullptr;
|
||||
SplitComboBox* _windowConfigBox = nullptr;
|
||||
QPushButton* _editProfileButton = nullptr;
|
||||
QPushButton* _editWindowButton = nullptr;
|
||||
QPushButton* _startButton = nullptr;
|
||||
};
|
||||
#endif // __OPENSPACE_UI_LAUNCHER___LAUNCHERWINDOW___H__
|
||||
|
||||
@@ -52,9 +52,8 @@ public:
|
||||
* \param profileBasePath The path to the folder in which all profiles live
|
||||
* \param parent Pointer to parent Qt widget
|
||||
*/
|
||||
ProfileEdit(openspace::Profile& profile, const std::string& profileName,
|
||||
ProfileEdit(openspace::Profile& profile, std::string profileName,
|
||||
std::filesystem::path assetBasePath, std::filesystem::path userAssetBasePath,
|
||||
std::filesystem::path builtInProfileBasePath,
|
||||
std::filesystem::path profileBasePath, QWidget* parent);
|
||||
|
||||
/**
|
||||
@@ -87,7 +86,6 @@ signals:
|
||||
void raiseExitWindow();
|
||||
|
||||
private slots:
|
||||
void duplicateProfile();
|
||||
void openMeta();
|
||||
void openProperties();
|
||||
void openModules();
|
||||
@@ -101,7 +99,7 @@ private slots:
|
||||
void approved();
|
||||
|
||||
private:
|
||||
void createWidgets(const std::string& profileName);
|
||||
void createWidgets();
|
||||
void initSummaryTextForEachCategory();
|
||||
|
||||
openspace::Profile& _profile;
|
||||
@@ -111,6 +109,8 @@ private:
|
||||
const std::filesystem::path _builtInProfilesPath;
|
||||
bool _saveSelected = false;
|
||||
|
||||
std::string _profileFilename;
|
||||
|
||||
QLineEdit* _profileEdit = nullptr;
|
||||
QLabel* _modulesLabel = nullptr;
|
||||
QLabel* _assetsLabel = nullptr;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <sgct/config.h>
|
||||
#include <vector>
|
||||
|
||||
class QFrame;
|
||||
@@ -45,31 +46,17 @@ public:
|
||||
* each monitor
|
||||
* \param nMaxWindows The maximum number of windows allowed (depends on the number of
|
||||
* monitors in the system)
|
||||
* \param windowColors An array of QColor objects for window colors. The indexing of
|
||||
* this array matches the window indexing used elsewhere in the class. This
|
||||
* allows for a unique color for each window
|
||||
* \param resetToDefault If set to true, all display and window settings will be
|
||||
* initialized to their default values
|
||||
* \param parent The parent to which this widget belongs
|
||||
*/
|
||||
DisplayWindowUnion(const std::vector<QRect>& monitorSizeList,
|
||||
int nMaxWindows, const std::array<QColor, 4>& windowColors, bool resetToDefault,
|
||||
QWidget* parent = nullptr);
|
||||
DisplayWindowUnion(const std::vector<QRect>& monitorResolutions,
|
||||
int nMaxWindows, QWidget* parent = nullptr);
|
||||
|
||||
/**
|
||||
* Returns a vector of pointers to the WindowControl objects for all visible windows.
|
||||
*
|
||||
* \return The vector of pointers of WindowControl objects
|
||||
*/
|
||||
std::vector<WindowControl*> activeWindowControls() const;
|
||||
void initialize(const std::vector<QRect>& monitorSizeList,
|
||||
const sgct::config::Cluster& cluster);
|
||||
|
||||
/**
|
||||
* Returns a vector of pointers to the WindowControl objects for all windows, whether
|
||||
* they are visible or not.
|
||||
*
|
||||
* \return The vector of pointers of all WindowControl objects
|
||||
*/
|
||||
std::vector<WindowControl*>& windowControls();
|
||||
void applyWindowSettings(std::vector<sgct::config::Window>& windows);
|
||||
|
||||
/**
|
||||
* When called will add a new window to the set of windows, which will, in turn, send
|
||||
@@ -83,14 +70,6 @@ public:
|
||||
*/
|
||||
void removeWindow();
|
||||
|
||||
/**
|
||||
* Returns the number of windows that are displayed (there can be more window
|
||||
* objects than are currently displayed).
|
||||
*
|
||||
* \return The number of displayed windows in the current configuration
|
||||
*/
|
||||
unsigned int numWindowsDisplayed() const;
|
||||
|
||||
signals:
|
||||
/**
|
||||
* This signal is emitted when a window has changed.
|
||||
@@ -109,13 +88,11 @@ signals:
|
||||
void nWindowsChanged(int newCount);
|
||||
|
||||
private:
|
||||
void createWidgets(int nMaxWindows, const std::vector<QRect>& monitorResolutions,
|
||||
std::array<QColor, 4> windowColors, bool resetToDefault);
|
||||
void showWindows();
|
||||
void updateWindows();
|
||||
|
||||
unsigned int _nWindowsDisplayed = 0;
|
||||
|
||||
std::vector<WindowControl*> _windowControl;
|
||||
std::vector<WindowControl*> _windowControls;
|
||||
QPushButton* _addWindowButton = nullptr;
|
||||
QPushButton* _removeWindowButton = nullptr;
|
||||
std::vector<QFrame*> _frameBorderLines;
|
||||
|
||||
@@ -38,30 +38,26 @@ public:
|
||||
* Constructor for MonitorBox class, which displays the system's monitor(s), their
|
||||
* relative position and size, and window(s) that they contain.
|
||||
*
|
||||
* \param widgetDims The size of the display widget in pixels, stored in QRect
|
||||
* \param monitorResolution A vector containing the monitor's maximum display size in
|
||||
* pixels in a QRect object
|
||||
* \param nWindows The current number of windows that has been selected by the user
|
||||
* \param winColors An array of QColor objects for window colors. The indexing of this
|
||||
* array matches the window indexing used elsewhere in the class. This allows
|
||||
* for a unique color for each window
|
||||
* \param widgetSize The size of the display widget in pixels
|
||||
* \param monitorResolution A vector containing each monitor's maximum display size in
|
||||
* pixels
|
||||
* \param parent The parent which to which this MonitorBox belongs
|
||||
*/
|
||||
MonitorBox(QRect widgetDims, const std::vector<QRect>& monitorResolutions,
|
||||
unsigned int nWindows, const std::array<QColor, 4>& windowColors,
|
||||
MonitorBox(QRect widgetSize, const std::vector<QRect>& monitorResolutions,
|
||||
QWidget* parent = nullptr);
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* Called when window dimensions or monitor location have changed, requiring redraw.
|
||||
* This will also map the window resolution into the scaled resolution of the display
|
||||
* widget.
|
||||
*
|
||||
* \param mIdx The zero-based monitor index (primary monitor is 0)
|
||||
* \param wIdx The zero-based window index
|
||||
* \param newDimensions Dimensions (pixels) of window to be mapped in QRect
|
||||
* \param monitorIdx The zero-based monitor index (primary monitor is 0)
|
||||
* \param windowIdx The zero-based window index
|
||||
* \param dimension Dimensions (pixels) of window to be mapped in QRect
|
||||
*/
|
||||
void windowDimensionsChanged(unsigned int mIdx, unsigned int wIdx,
|
||||
const QRectF& newDimensions);
|
||||
void windowDimensionsChanged(unsigned int monitorIdx, unsigned int windowIdx,
|
||||
const QRectF& dimension);
|
||||
|
||||
/**
|
||||
* Called when the number of windows that should be displayed changes.
|
||||
@@ -74,21 +70,10 @@ protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
std::vector<QSizeF> computeScaledResolutionLandscape(QRectF arrangement,
|
||||
const std::vector<QRect>& resolutions);
|
||||
std::vector<QSizeF> computeScaledResolutionPortrait(QRectF arrangement,
|
||||
const std::vector<QRect>& resolutions);
|
||||
|
||||
std::vector<QRectF> _monitorDimensionsScaled;
|
||||
std::array<QRectF, 4> _windowRendering = {
|
||||
QRectF(0.f, 0.f, 0.f, 0.f),
|
||||
QRectF(0.f, 0.f, 0.f, 0.f),
|
||||
QRectF(0.f, 0.f, 0.f, 0.f),
|
||||
QRectF(0.f, 0.f, 0.f, 0.f)
|
||||
};
|
||||
int _nWindows = 1;
|
||||
const std::array<QColor, 4> _colorsForWindows;
|
||||
float _monitorScaleFactor = 1.0;
|
||||
std::vector<QRectF> _windowRendering;
|
||||
int _nWindows = 0;
|
||||
float _monitorScaleFactor = 1.f;
|
||||
};
|
||||
|
||||
#endif // __OPENSPACE_UI_LAUNCHER___MONITORBOX___H__
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2025 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_UI_LAUNCHER___SETTINGSWIDGET___H__
|
||||
#define __OPENSPACE_UI_LAUNCHER___SETTINGSWIDGET___H__
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
#include <sgct/math.h>
|
||||
|
||||
class QCheckBox;
|
||||
class QLabel;
|
||||
|
||||
class SettingsWidget final : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* Constructor for Orientation class, which manages the overall control layout
|
||||
* including monitorBox, multiple WindowControl columns, and additional controls.
|
||||
*/
|
||||
explicit SettingsWidget(sgct::quat orientation, QWidget* parent = nullptr);
|
||||
|
||||
/**
|
||||
* Gets the user-provided x,y,z orientation values (degrees).
|
||||
*
|
||||
* \return The orientation angles provided in sgct::quat object
|
||||
*/
|
||||
sgct::quat orientation() const;
|
||||
|
||||
/**
|
||||
* Gets the value for if VSync is enabled.
|
||||
*
|
||||
* \return `true` if the VSync option is checked/enabled
|
||||
*/
|
||||
bool vsync() const;
|
||||
|
||||
/**
|
||||
* Gets whether the UI should be restricted to the first window.
|
||||
*
|
||||
* \return `true` if the UI should only be on the first window
|
||||
*/
|
||||
bool showUiOnFirstWindow() const;
|
||||
|
||||
/**
|
||||
* Sets the value of the checkbox for putting the GUI only on the first window. If
|
||||
* this is enabled, then the first window will draw2D but not draw3D. All subsequent
|
||||
* windows will be the opposite of this.
|
||||
*
|
||||
* \param setUiOnFirstWindow If `true` then the GUI will only be on the first window
|
||||
*/
|
||||
void setShowUiOnFirstWindow(bool setUiOnFirstWindow);
|
||||
|
||||
/**
|
||||
* Sets value for whether or not the checkbox for having the UI only on the first
|
||||
* window is enabled. This checkbox should only be clickable if the number of
|
||||
* windows is 2 or more.
|
||||
*/
|
||||
void setEnableShowUiOnFirstWindowCheckbox(bool enable);
|
||||
|
||||
/**
|
||||
* Gets the value of the selection for which display first window should mirror if
|
||||
* the option to show the Ui in the first window is enabled. Note that this will
|
||||
* return a value even if the checkbox is not enabled.
|
||||
*
|
||||
* \return `-1` if in a disabled state (e.g. when showUiOnFirstWindow() returns
|
||||
* false). `0` if no window graphics are selected (only the UI will appear).
|
||||
* `(1-4)` for which window's setting will be used for window 1 graphics
|
||||
*/
|
||||
int graphicsSelectionForShowUiOnFirstWindow() const;
|
||||
|
||||
/**
|
||||
* Sets value of the graphics selection combo box for which other window that the
|
||||
* first window will mirror.
|
||||
*
|
||||
* \param selection integer value for the combo box selection. `0` if no window
|
||||
* graphics are selected (only the UI will appear). `(1-4)` for which window's
|
||||
* setting to use for window 1 graphics
|
||||
*/
|
||||
void setGraphicsSelectionForShowUiOnFirstWindow(int selection);
|
||||
|
||||
/**
|
||||
* Sets the value of the checkbox for enabling VSync.
|
||||
*
|
||||
* \param enableVsync If set `true` then VSync is enabled
|
||||
*/
|
||||
void setVsync(bool enableVsync);
|
||||
|
||||
/**
|
||||
* Called when the number of windows that should be displayed changes.
|
||||
*
|
||||
* \param newCount The new number of windows included
|
||||
*/
|
||||
void nWindowsDisplayedChanged(int newCount);
|
||||
|
||||
/**
|
||||
* Gets the pointer to the QComboBox that selects the graphics for first window.
|
||||
*
|
||||
* \return Pointer to the QComboBox object
|
||||
*/
|
||||
QComboBox* firstWindowGraphicsSelection();
|
||||
|
||||
/**
|
||||
* Gets the pointer to the QCheckBox that selects if UI is in first window only.
|
||||
*
|
||||
* \return Pointer to the QCheckBox object
|
||||
*/
|
||||
QCheckBox* showUiOnFirstWindowCheckbox();
|
||||
|
||||
signals:
|
||||
void firstWindowGraphicsSelected(int selection);
|
||||
|
||||
private:
|
||||
void showUiOnFirstWindowClicked(bool checked);
|
||||
void firstWindowGraphicsSelectionChanged(const QString& text);
|
||||
|
||||
sgct::quat _orientationValue = sgct::quat(0.f, 0.f, 0.f, 0.f);
|
||||
QCheckBox* _checkBoxVsync = nullptr;
|
||||
QCheckBox* _showUiOnFirstWindow = nullptr;
|
||||
QComboBox* _firstWindowGraphicsSelection = nullptr;
|
||||
QBoxLayout* _firstWindowSelectionLayout = nullptr;
|
||||
int _stateOfUiOnFirstWindowPreviousCount = 1;
|
||||
bool _stateOfUiOnFirstWindowWhenDisabled = false;
|
||||
};
|
||||
|
||||
#endif // __OPENSPACE_UI_LAUNCHER___SETTINGSWIDGET___H__
|
||||
@@ -28,34 +28,21 @@
|
||||
#include <QDialog>
|
||||
|
||||
#include <sgct/config.h>
|
||||
#include <sgctedit/windowcontrol.h>
|
||||
#include <QColor>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
class DisplayWindowUnion;
|
||||
class SettingsWidget;
|
||||
class QBoxLayout;
|
||||
class QWidget;
|
||||
class QCheckBox;
|
||||
class QComboBox;
|
||||
class QLineEdit;
|
||||
|
||||
const sgct::config::GeneratorVersion versionMin { "SgctWindowConfig", 1, 1 };
|
||||
const sgct::config::GeneratorVersion versionLegacy18 { "OpenSpace", 0, 18 };
|
||||
const sgct::config::GeneratorVersion versionLegacy19 { "OpenSpace", 0, 19 };
|
||||
const sgct::config::GeneratorVersion VersionMin { "SgctWindowConfig", 1, 1 };
|
||||
const sgct::config::GeneratorVersion VersionLegacy18 { "OpenSpace", 0, 18 };
|
||||
const sgct::config::GeneratorVersion VersionLegacy19 { "OpenSpace", 0, 19 };
|
||||
|
||||
class SgctEdit final : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/**
|
||||
* Constructor for SgctEdit class, the underlying class for the full window
|
||||
* configuration editor. Used when creating a new config.
|
||||
*
|
||||
* \param parent The Qt QWidget parent object
|
||||
* \param userConfigPath A string containing the file path of the user config
|
||||
* directory where all window configs are stored
|
||||
*/
|
||||
SgctEdit(QWidget* parent, std::filesystem::path userConfigPath);
|
||||
|
||||
/**
|
||||
* Constructor for SgctEdit class, the underlying class for the full window
|
||||
* configuration editor. Used when editing an existing config.
|
||||
@@ -66,8 +53,8 @@ public:
|
||||
* \param configBasePath The path to the folder where default config files reside
|
||||
* \param parent Pointer to parent Qt widget
|
||||
*/
|
||||
SgctEdit(sgct::config::Cluster& cluster, std::string configName,
|
||||
std::filesystem::path& configBasePath, QWidget* parent);
|
||||
SgctEdit(sgct::config::Cluster cluster, std::string configName,
|
||||
std::filesystem::path configBasePath, QWidget* parent);
|
||||
|
||||
/**
|
||||
* Returns the saved filename.
|
||||
@@ -76,69 +63,21 @@ public:
|
||||
*/
|
||||
std::filesystem::path saveFilename() const;
|
||||
|
||||
/**
|
||||
* Returns the generated Cluster object.
|
||||
*
|
||||
* \return The generated Cluster object
|
||||
*/
|
||||
sgct::config::Cluster cluster() const;
|
||||
|
||||
/**
|
||||
* Called when the number of windows that should be displayed changes.
|
||||
*
|
||||
* \param newCount The new number of windows included
|
||||
*/
|
||||
void nWindowsDisplayedChanged(int newCount);
|
||||
|
||||
/**
|
||||
* Called when the checkbox for GUI only on first window is clicked.
|
||||
*
|
||||
* \param checked `true` if GUI is selected for first window only.
|
||||
*/
|
||||
void firstWindowGuiOptionClicked(bool checked);
|
||||
|
||||
/**
|
||||
* Called when the QComboBox is selected and has a new value
|
||||
*
|
||||
* \param text The QString of the selected value
|
||||
*/
|
||||
void firstWindowGraphicsSelectionChanged(const QString& text);
|
||||
|
||||
private:
|
||||
std::vector<QRect> createMonitorInfoSet();
|
||||
void createWidgets(const std::vector<QRect>& monitorSizes, unsigned int nWindows,
|
||||
bool setToDefaults);
|
||||
void generateConfiguration();
|
||||
void generateConfigSetupVsync();
|
||||
void generateConfigUsers();
|
||||
void generateConfigAddresses(sgct::config::Node& node);
|
||||
void generateConfigResizeWindowsAccordingToSelected(sgct::config::Node& node);
|
||||
void generateConfigIndividualWindowSettings(sgct::config::Node& node);
|
||||
void setupProjectionTypeInGui(sgct::config::Viewport& vPort, WindowControl* wCtrl);
|
||||
void setupStateOfUiOnFirstWindow(size_t nWindows);
|
||||
void deleteFromTags(sgct::config::Window& window);
|
||||
|
||||
void save();
|
||||
void saveCluster();
|
||||
void apply();
|
||||
|
||||
DisplayWindowUnion* _displayWidget = nullptr;
|
||||
SettingsWidget* _settingsWidget = nullptr;
|
||||
|
||||
QCheckBox* _checkBoxVsync = nullptr;
|
||||
QLineEdit* _linePitch = nullptr;
|
||||
QLineEdit* _lineRoll = nullptr;
|
||||
QLineEdit* _lineYaw = nullptr;
|
||||
|
||||
sgct::config::Cluster _cluster;
|
||||
const std::filesystem::path _userConfigPath;
|
||||
const std::array<QColor, 4> _colorsForWindows = {
|
||||
QColor(0x2B, 0x9E, 0xC3),
|
||||
QColor(0xFC, 0xAB, 0x10),
|
||||
QColor(0x44, 0xAF, 0x69),
|
||||
QColor(0xF8, 0x33, 0x3C)
|
||||
};
|
||||
std::string _configurationFilename;
|
||||
|
||||
QBoxLayout* _layoutButtonBox = nullptr;
|
||||
QPushButton* _saveButton = nullptr;
|
||||
QPushButton* _cancelButton = nullptr;
|
||||
QPushButton* _applyButton = nullptr;
|
||||
std::string _saveTarget;
|
||||
bool _didImportValues = false;
|
||||
std::string _configurationFilename;
|
||||
};
|
||||
|
||||
#endif // __OPENSPACE_UI_LAUNCHER___SGCTEDIT___H__
|
||||
|
||||
@@ -41,14 +41,6 @@ class QSpinBox;
|
||||
class WindowControl final : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class ProjectionIndices {
|
||||
Planar = 0,
|
||||
Fisheye,
|
||||
SphericalMirror,
|
||||
Cylindrical,
|
||||
Equirectangular
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor for WindowControl class, which contains settings and configuration for
|
||||
* individual windows.
|
||||
@@ -57,14 +49,10 @@ public:
|
||||
* resides in
|
||||
* \param windowIndex The zero-based window index
|
||||
* \param monitorDims Vector of monitor dimensions in QRect form
|
||||
* \param winColor A QColor object for this window's unique color
|
||||
* \param resetToDefault If this is `true`, the widgets will be initialized to their
|
||||
* default values
|
||||
* \param parent The parent widget
|
||||
*/
|
||||
WindowControl(int monitorIndex, int windowIndex,
|
||||
const std::vector<QRect>& monitorDims, const QColor& winColor,
|
||||
bool resetToDefault, QWidget* parent);
|
||||
const std::vector<QRect>& monitorDims, QWidget* parent);
|
||||
|
||||
/**
|
||||
* Makes the window label at top of a window control column visible.
|
||||
@@ -83,7 +71,7 @@ public:
|
||||
*
|
||||
* \param newDims The x, y dimensions to set the window to
|
||||
*/
|
||||
void setDimensions(QRectF newDims);
|
||||
void setDimensions(int x, int y, int width, int height);
|
||||
|
||||
/**
|
||||
* Sets the monitor selection combobox.
|
||||
@@ -112,6 +100,9 @@ public:
|
||||
*/
|
||||
void setSpoutOutputState(bool shouldSpoutOutput);
|
||||
|
||||
void setRender2D(bool state);
|
||||
void setRender3D(bool state);
|
||||
|
||||
/**
|
||||
* Generates window configuration (sgct::config::Window struct) based on the
|
||||
* GUI settings.
|
||||
@@ -171,27 +162,10 @@ public:
|
||||
*/
|
||||
void setProjectionEquirectangular(int quality);
|
||||
|
||||
/**
|
||||
* Controls the visibility of all projection controls, including those that are only
|
||||
* shown when the projection type is set to certain values.
|
||||
*
|
||||
* \param enable `true` if the projections controls should be visible
|
||||
*/
|
||||
void setVisibilityOfProjectionGui(bool enable);
|
||||
|
||||
/**
|
||||
* Returns an #sgct::config::Projections struct containing the projection information
|
||||
* for this window.
|
||||
*
|
||||
* \return The object containing the projection information
|
||||
*/
|
||||
sgct::config::Projections generateProjectionInformation() const;
|
||||
|
||||
signals:
|
||||
void windowChanged(int monitorIndex, int windowIndex, const QRectF& newDimensions);
|
||||
|
||||
private:
|
||||
void createWidgets(const QColor& windowColor);
|
||||
QWidget* createPlanarWidget();
|
||||
QWidget* createFisheyeWidget();
|
||||
QWidget* createSphericalMirrorWidget();
|
||||
@@ -208,7 +182,6 @@ private:
|
||||
void onFovLockClicked();
|
||||
|
||||
void updatePlanarLockedFov();
|
||||
void setQualityComboBoxFromLinesResolution(int lines, QComboBox* combo);
|
||||
|
||||
static constexpr float IdealAspectRatio = 16.f / 9.f;
|
||||
float _aspectRatioSize = IdealAspectRatio;
|
||||
@@ -218,7 +191,7 @@ private:
|
||||
bool _aspectRatioLocked = false;
|
||||
bool _fovLocked = true;
|
||||
std::vector<QRect> _monitorResolutions;
|
||||
QRectF _windowDimensions;
|
||||
QRect _windowDimensions;
|
||||
|
||||
QLabel* _windowNumber = nullptr;
|
||||
QLineEdit* _windowName = nullptr;
|
||||
@@ -228,9 +201,11 @@ private:
|
||||
QSpinBox* _offsetX = nullptr;
|
||||
QSpinBox* _offsetY = nullptr;
|
||||
QCheckBox* _windowDecoration = nullptr;
|
||||
QComboBox* _projectionType = nullptr;
|
||||
QLabel* _projectionLabel = nullptr;
|
||||
QCheckBox* _spoutOutput = nullptr;
|
||||
QCheckBox* _render2D = nullptr;
|
||||
QCheckBox* _render3D = nullptr;
|
||||
QFrame* _projectionGroup = nullptr;
|
||||
QComboBox* _projectionType = nullptr;
|
||||
|
||||
struct {
|
||||
QWidget* widget = nullptr;
|
||||
|
||||
65
apps/OpenSpace/ext/launcher/include/splitcombobox.h
Normal file
65
apps/OpenSpace/ext/launcher/include/splitcombobox.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2025 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_UI_LAUNCHER___SPLITCOMBOBOX___H__
|
||||
#define __OPENSPACE_UI_LAUNCHER___SPLITCOMBOBOX___H__
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class SplitComboBox final : public QComboBox {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SplitComboBox(QWidget* parent, std::filesystem::path userPath, std::string userHeader,
|
||||
std::filesystem::path hardcodedPath, std::string hardcodedHeader,
|
||||
std::string specialFirst,
|
||||
std::function<bool(const std::filesystem::path&)> fileFilter,
|
||||
std::function<std::string(const std::filesystem::path&)> createTooltip);
|
||||
|
||||
void populateList(const std::string& preset);
|
||||
|
||||
std::pair<std::string, std::string> currentSelection() const;
|
||||
|
||||
signals:
|
||||
// Sends the path to the selection or `std::nullopt` iff there was a special non-file
|
||||
// entry at the top and that one has been selected
|
||||
void selectionChanged(std::optional<std::string> selection);
|
||||
|
||||
private:
|
||||
std::filesystem::path _userPath;
|
||||
std::string _userHeader;
|
||||
std::filesystem::path _hardCodedPath;
|
||||
std::string _hardCodedHeader;
|
||||
|
||||
std::string _specialFirst;
|
||||
|
||||
std::function<bool(const std::filesystem::path&)> _fileFilter;
|
||||
std::function<std::string(const std::filesystem::path&)> _createTooltip;
|
||||
};
|
||||
|
||||
#endif // __OPENSPACE_UI_LAUNCHER___SPLITCOMBOBOX___H__
|
||||
41
apps/OpenSpace/ext/launcher/include/windowcolors.h
Normal file
41
apps/OpenSpace/ext/launcher/include/windowcolors.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2025 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#ifndef __OPENSPACE_UI_LAUNCHER___WINDOWCOLORS___H__
|
||||
#define __OPENSPACE_UI_LAUNCHER___WINDOWCOLORS___H__
|
||||
|
||||
#include <QColor>
|
||||
|
||||
/**
|
||||
* This function returns the color that a component should use for a specific window. The
|
||||
* same index will always return the same color.
|
||||
*
|
||||
* \param idx The index of the window for which the color should be calculated
|
||||
* \return The color for the provided window index
|
||||
*
|
||||
* \pre \p idx must be non-negative
|
||||
*/
|
||||
QColor colorForWindow(int idx);
|
||||
|
||||
#endif // __OPENSPACE_UI_LAUNCHER___WINDOWCOLORS___H__
|
||||
@@ -74,6 +74,10 @@ LauncherWindow QPushButton#start {
|
||||
LauncherWindow QPushButton#start:hover {
|
||||
background: rgb(120, 120, 120);
|
||||
}
|
||||
LauncherWindow QPushButton#start:disabled {
|
||||
background: rgb(175, 175, 175);
|
||||
color: rgb(225, 225, 225);
|
||||
}
|
||||
|
||||
LauncherWindow QPushButton#small {
|
||||
background: rgb(86, 86, 86);
|
||||
|
||||
101
apps/OpenSpace/ext/launcher/src/backgroundimage.cpp
Normal file
101
apps/OpenSpace/ext/launcher/src/backgroundimage.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2025 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "backgroundimage.h"
|
||||
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <random>
|
||||
|
||||
BackgroundImage::BackgroundImage(QRect size, const std::filesystem::path& syncFolder,
|
||||
QWidget* parent)
|
||||
: QLabel(parent)
|
||||
{
|
||||
setGeometry(size);
|
||||
|
||||
// Set a backup image in case no other images have been downloaded yet
|
||||
setPixmap(QPixmap(":/images/launcher-background.png"));
|
||||
|
||||
std::filesystem::path imagePath = syncFolder / "http" / "launcher_images";
|
||||
|
||||
if (!std::filesystem::exists(imagePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// First, we iterate through all folders in the launcher_images sync folder and we get
|
||||
// the folder with the highest number
|
||||
struct {
|
||||
fs::directory_entry path;
|
||||
int version = -1;
|
||||
} latest;
|
||||
for (const fs::directory_entry& p : fs::directory_iterator(imagePath)) {
|
||||
if (!p.is_directory()) {
|
||||
continue;
|
||||
}
|
||||
const std::string versionStr = p.path().stem().string();
|
||||
// All folder names in the sync folder should only be a digit, so we should be
|
||||
// find to just convert it here
|
||||
const int version = std::stoi(versionStr);
|
||||
|
||||
if (version > latest.version) {
|
||||
latest.version = version;
|
||||
latest.path = p;
|
||||
}
|
||||
}
|
||||
|
||||
if (latest.version == -1) {
|
||||
// The sync folder existed, but nothing was in there. Kinda weird, but still
|
||||
return;
|
||||
}
|
||||
|
||||
// Now we know which folder to use, we will pick an image at random
|
||||
std::vector<std::filesystem::path> files = ghoul::filesystem::walkDirectory(
|
||||
latest.path,
|
||||
ghoul::filesystem::Recursive::No,
|
||||
ghoul::filesystem::Sorted::No,
|
||||
[](const std::filesystem::path& p) {
|
||||
return p.extension() == ".png" && p.filename() != "overlay.png";
|
||||
}
|
||||
);
|
||||
std::random_device rd;
|
||||
std::mt19937 g(rd());
|
||||
std::shuffle(files.begin(), files.end(), g);
|
||||
|
||||
// There better be at least one file left, but just in in case
|
||||
if (!files.empty()) {
|
||||
// Take the selected image and overpaint the overlay increasing the contrast
|
||||
std::string image = files.front().string();
|
||||
QPixmap pixmap = QPixmap(QString::fromStdString(image));
|
||||
QPainter painter = QPainter(&pixmap);
|
||||
painter.setOpacity(0.7);
|
||||
QPixmap overlay = QPixmap(QString::fromStdString(
|
||||
std::format("{}/overlay.png", latest.path.path())
|
||||
));
|
||||
painter.drawPixmap(pixmap.rect(), overlay);
|
||||
setPixmap(pixmap);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,6 +38,7 @@
|
||||
#include <openspace/scene/profile.h>
|
||||
#include <ghoul/format.h>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QKeyEvent>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
@@ -47,8 +48,13 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <Windows.h>
|
||||
#endif // WIN32
|
||||
|
||||
using namespace openspace;
|
||||
|
||||
namespace {
|
||||
@@ -96,10 +102,9 @@ namespace {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ProfileEdit::ProfileEdit(Profile& profile, const std::string& profileName,
|
||||
ProfileEdit::ProfileEdit(Profile& profile, std::string profileName,
|
||||
std::filesystem::path assetBasePath,
|
||||
std::filesystem::path userAssetBasePath,
|
||||
std::filesystem::path builtInProfileBasePath,
|
||||
std::filesystem::path profileBasePath,
|
||||
QWidget* parent)
|
||||
: QDialog(parent)
|
||||
@@ -107,15 +112,15 @@ ProfileEdit::ProfileEdit(Profile& profile, const std::string& profileName,
|
||||
, _assetBasePath(std::move(assetBasePath))
|
||||
, _userAssetBasePath(std::move(userAssetBasePath))
|
||||
, _profileBasePath(std::move(profileBasePath))
|
||||
, _builtInProfilesPath(std::move(builtInProfileBasePath))
|
||||
, _profileFilename(std::move(profileName))
|
||||
{
|
||||
setWindowTitle("Profile Editor");
|
||||
createWidgets(profileName);
|
||||
createWidgets();
|
||||
|
||||
initSummaryTextForEachCategory();
|
||||
}
|
||||
|
||||
void ProfileEdit::createWidgets(const std::string& profileName) {
|
||||
void ProfileEdit::createWidgets() {
|
||||
QBoxLayout* layout = new QVBoxLayout(this);
|
||||
QBoxLayout* topLayout = new QHBoxLayout;
|
||||
QBoxLayout* leftLayout = new QVBoxLayout;
|
||||
@@ -125,20 +130,12 @@ void ProfileEdit::createWidgets(const std::string& profileName) {
|
||||
profileLabel->setObjectName("profile");
|
||||
container->addWidget(profileLabel);
|
||||
|
||||
_profileEdit = new QLineEdit(QString::fromStdString(profileName));
|
||||
_profileEdit->setPlaceholderText("required");
|
||||
_profileEdit = new QLineEdit(QString::fromStdString(_profileFilename));
|
||||
container->addWidget(_profileEdit);
|
||||
|
||||
QPushButton* duplicateButton = new QPushButton("Duplicate Profile");
|
||||
connect(
|
||||
duplicateButton, &QPushButton::clicked,
|
||||
this, &ProfileEdit::duplicateProfile
|
||||
);
|
||||
container->addWidget(duplicateButton);
|
||||
|
||||
layout->addLayout(container);
|
||||
}
|
||||
layout->addWidget(new Line);
|
||||
|
||||
{
|
||||
QGridLayout* container = new QGridLayout;
|
||||
container->setColumnStretch(1, 1);
|
||||
@@ -211,8 +208,6 @@ void ProfileEdit::createWidgets(const std::string& profileName) {
|
||||
}
|
||||
topLayout->addLayout(leftLayout, 3);
|
||||
|
||||
topLayout->addWidget(new Line);
|
||||
|
||||
QBoxLayout* rightLayout = new QVBoxLayout;
|
||||
{
|
||||
QBoxLayout* container = new QVBoxLayout;
|
||||
@@ -327,6 +322,7 @@ void ProfileEdit::createWidgets(const std::string& profileName) {
|
||||
container->addWidget(additionalScriptsEdit);
|
||||
rightLayout->addLayout(container);
|
||||
}
|
||||
rightLayout->addStretch();
|
||||
topLayout->addLayout(rightLayout);
|
||||
layout->addLayout(topLayout);
|
||||
|
||||
@@ -369,49 +365,6 @@ void ProfileEdit::initSummaryTextForEachCategory() {
|
||||
);
|
||||
}
|
||||
|
||||
void ProfileEdit::duplicateProfile() {
|
||||
std::string profile = _profileEdit->text().toStdString();
|
||||
if (profile.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr char Separator = '_';
|
||||
int version = 0;
|
||||
if (const size_t it = profile.rfind(Separator); it != std::string::npos) {
|
||||
// If the value exists, we have a profile that potentially already has a version
|
||||
// number attached to it
|
||||
const std::string versionStr = profile.substr(it + 1);
|
||||
try {
|
||||
version = std::stoi(versionStr);
|
||||
|
||||
// We will re-add the separator with the new version string to the file, so we
|
||||
// will remove the suffix here first
|
||||
profile = profile.substr(0, it);
|
||||
}
|
||||
catch (const std::invalid_argument&) {
|
||||
// If this exception is thrown, we did find a separator character but the
|
||||
// substring afterwards was not a number, so the user just added a separator
|
||||
// by themselves. In this case we don't do anything
|
||||
}
|
||||
}
|
||||
|
||||
// By this point we have our current profile (without any suffix) in 'profile' and the
|
||||
// currently active version in 'version'. Now we need to put both together again and
|
||||
// also make sure that we don't pick a version number that already exists
|
||||
while (true) {
|
||||
version++;
|
||||
|
||||
const std::string candidate = std::format("{}{}{}", profile, Separator, version);
|
||||
const std::string candidatePath = std::format(
|
||||
"{}{}.profile", _profileBasePath, candidate
|
||||
);
|
||||
|
||||
if (!std::filesystem::exists(candidatePath)) {
|
||||
_profileEdit->setText(QString::fromStdString(candidate));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProfileEdit::openMeta() {
|
||||
MetaDialog(this, &_profile.meta).exec();
|
||||
@@ -475,34 +428,93 @@ bool ProfileEdit::wasSaved() const {
|
||||
}
|
||||
|
||||
std::string ProfileEdit::specifiedFilename() const {
|
||||
return _profileEdit->text().toStdString();
|
||||
return _profileFilename;
|
||||
}
|
||||
|
||||
void ProfileEdit::approved() {
|
||||
std::string profileName = _profileEdit->text().toStdString();
|
||||
if (profileName.empty()) {
|
||||
QMessageBox::critical(this, "No profile name", "Profile name must be specified");
|
||||
_profileEdit->setFocus();
|
||||
// Show the save dialog
|
||||
std::filesystem::path fullPath =
|
||||
_profileBasePath / _profileEdit->text().toStdString();
|
||||
|
||||
// We might need to create intermediate directories if the user specified them in the
|
||||
// name field. We do a "parent_path" here, since the last component of the path is the
|
||||
// actual desired filename
|
||||
std::filesystem::create_directories(std::filesystem::path(fullPath).parent_path());
|
||||
|
||||
const QString path = QFileDialog::getSaveFileName(
|
||||
this,
|
||||
"Save Profile",
|
||||
QString::fromStdString(fullPath.string()),
|
||||
"Profile (*.profile)",
|
||||
nullptr
|
||||
#ifdef __linux__
|
||||
// Linux in Qt5 and Qt6 crashes when trying to access the native dialog here
|
||||
, QFileDialog::DontUseNativeDialog
|
||||
#endif // __linux__
|
||||
);
|
||||
|
||||
// The user canceled the saving
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::filesystem::path p = std::format(
|
||||
"{}/{}.profile", _builtInProfilesPath, profileName
|
||||
);
|
||||
if (std::filesystem::exists(p)) {
|
||||
// The filename exists in the OpenSpace-provided folder, so we don't want to allow
|
||||
// a user to overwrite it
|
||||
//
|
||||
// If we got this far then we have a new or existing file that the user wanted to save
|
||||
// the profile into
|
||||
|
||||
// Check if the user saved the profile in the correct folder
|
||||
std::string profileBase = std::filesystem::canonical(_profileBasePath).string();
|
||||
std::string file = std::filesystem::weakly_canonical(path.toStdString()).string();
|
||||
if (!file.starts_with(profileBase)) {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Reserved profile name",
|
||||
"This is a read-only profile. Click 'Duplicate' or rename profile and save"
|
||||
"File Location",
|
||||
"Profiles can only be stored directly in the user profile folder or "
|
||||
"subfolders inside it"
|
||||
);
|
||||
_profileEdit->setFocus();
|
||||
return;
|
||||
}
|
||||
else {
|
||||
_saveSelected = true;
|
||||
accept();
|
||||
// +1 for the directory separator
|
||||
std::string filename = file.substr(profileBase.size() + 1);
|
||||
|
||||
try {
|
||||
std::ofstream outFile;
|
||||
outFile.exceptions(std::ofstream::badbit | std::ofstream::failbit);
|
||||
outFile.open(path.toStdString(), std::ofstream::out);
|
||||
outFile << _profile.serialize();
|
||||
}
|
||||
catch (const std::ofstream::failure& e) {
|
||||
#ifdef WIN32
|
||||
if (std::filesystem::exists(file)) {
|
||||
// Check if the file is hidden, since that causes ofstream to fail
|
||||
DWORD res = GetFileAttributesA(file.c_str());
|
||||
if (res & FILE_ATTRIBUTE_HIDDEN) {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Exception",
|
||||
QString::fromStdString(std::format(
|
||||
"Error writing data to file '{}' as file is marked hidden", file
|
||||
))
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif // WIN32
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Exception",
|
||||
QString::fromStdString(std::format(
|
||||
"Error writing data to file '{}': {}", file, e.what()
|
||||
))
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path p = filename;
|
||||
p.replace_extension("");
|
||||
_profileFilename = p.string();
|
||||
_saveSelected = true;
|
||||
accept();
|
||||
}
|
||||
|
||||
void ProfileEdit::keyPressEvent(QKeyEvent* evt) {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
|
||||
#include "sgctedit/windowcontrol.h"
|
||||
#include <ghoul/format.h>
|
||||
#include <ghoul/misc/assert.h>
|
||||
#include <QColor>
|
||||
#include <QFrame>
|
||||
#include <QPushButton>
|
||||
@@ -33,45 +34,24 @@
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
DisplayWindowUnion::DisplayWindowUnion(const std::vector<QRect>& monitorSizeList,
|
||||
int nMaxWindows,
|
||||
const std::array<QColor, 4>& windowColors,
|
||||
bool resetToDefault, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
createWidgets(
|
||||
nMaxWindows,
|
||||
monitorSizeList,
|
||||
windowColors,
|
||||
resetToDefault
|
||||
);
|
||||
showWindows();
|
||||
}
|
||||
namespace {
|
||||
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
} // namespace
|
||||
|
||||
void DisplayWindowUnion::createWidgets(int nMaxWindows,
|
||||
const std::vector<QRect>& monitorResolutions,
|
||||
std::array<QColor, 4> windowColors,
|
||||
bool resetToDefault)
|
||||
DisplayWindowUnion::DisplayWindowUnion(const std::vector<QRect>& monitorResolutions,
|
||||
int nMaxWindows, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
// Add all window controls (some will be hidden from GUI initially)
|
||||
for (int i = 0; i < nMaxWindows; i++) {
|
||||
const int monitorNumForThisWindow =
|
||||
(monitorResolutions.size() > 1 && i >= 2) ? 1 : 0;
|
||||
|
||||
WindowControl* ctrl = new WindowControl(
|
||||
monitorNumForThisWindow,
|
||||
i,
|
||||
monitorResolutions,
|
||||
windowColors[i],
|
||||
resetToDefault,
|
||||
this
|
||||
);
|
||||
_windowControl.push_back(ctrl);
|
||||
|
||||
const int monitorIdx = (monitorResolutions.size() > 1 && i >= 2) ? 1 : 0;
|
||||
WindowControl* ctrl = new WindowControl(monitorIdx, i, monitorResolutions, this);
|
||||
connect(
|
||||
ctrl, &WindowControl::windowChanged,
|
||||
this, &DisplayWindowUnion::windowChanged
|
||||
);
|
||||
_windowControls.push_back(ctrl);
|
||||
}
|
||||
|
||||
QBoxLayout* layout = new QVBoxLayout(this);
|
||||
@@ -80,7 +60,7 @@ void DisplayWindowUnion::createWidgets(int nMaxWindows,
|
||||
|
||||
{
|
||||
QBoxLayout* layoutMonButton = new QHBoxLayout;
|
||||
_removeWindowButton = new QPushButton("Remove Window");
|
||||
_removeWindowButton = new QPushButton("&Remove Window");
|
||||
_removeWindowButton->setFocusPolicy(Qt::NoFocus);
|
||||
_removeWindowButton->setToolTip(
|
||||
"Remove window from the configuration (at least one window is required)"
|
||||
@@ -93,7 +73,7 @@ void DisplayWindowUnion::createWidgets(int nMaxWindows,
|
||||
|
||||
layoutMonButton->addStretch(1);
|
||||
|
||||
_addWindowButton = new QPushButton("Add Window");
|
||||
_addWindowButton = new QPushButton("&Add Window");
|
||||
_addWindowButton->setToolTip(QString::fromStdString(std::format(
|
||||
"Add a window to the configuration (up to {} windows allowed)", nMaxWindows
|
||||
)));
|
||||
@@ -106,70 +86,172 @@ void DisplayWindowUnion::createWidgets(int nMaxWindows,
|
||||
layout->addLayout(layoutMonButton);
|
||||
}
|
||||
|
||||
QFrame* line = new QFrame;
|
||||
line->setFrameShape(QFrame::HLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
layout->addWidget(line);
|
||||
{
|
||||
QFrame* line = new QFrame;
|
||||
line->setFrameShape(QFrame::HLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
layout->addWidget(line);
|
||||
}
|
||||
|
||||
QBoxLayout* layoutWindows = new QHBoxLayout;
|
||||
layoutWindows->setContentsMargins(0, 0, 0, 0);
|
||||
layoutWindows->setSpacing(0);
|
||||
for (int i = 0; i < nMaxWindows; i++) {
|
||||
layoutWindows->addWidget(_windowControl[i]);
|
||||
if (i < (nMaxWindows - 1)) {
|
||||
QFrame* frameForNextWindow = new QFrame;
|
||||
frameForNextWindow->setFrameShape(QFrame::VLine);
|
||||
_frameBorderLines.push_back(frameForNextWindow);
|
||||
layoutWindows->addWidget(frameForNextWindow);
|
||||
{
|
||||
QBoxLayout* layoutWindows = new QHBoxLayout;
|
||||
layoutWindows->setContentsMargins(0, 0, 0, 0);
|
||||
layoutWindows->setSpacing(0);
|
||||
for (int i = 0; i < nMaxWindows; i++) {
|
||||
layoutWindows->addWidget(_windowControls[i]);
|
||||
if (i < (nMaxWindows - 1)) {
|
||||
QFrame* frameForNextWindow = new QFrame;
|
||||
frameForNextWindow->setFrameShape(QFrame::VLine);
|
||||
_frameBorderLines.push_back(frameForNextWindow);
|
||||
layoutWindows->addWidget(frameForNextWindow);
|
||||
}
|
||||
}
|
||||
layout->addLayout(layoutWindows);
|
||||
}
|
||||
layout->addLayout(layoutWindows);
|
||||
layout->addStretch();
|
||||
}
|
||||
|
||||
std::vector<WindowControl*> DisplayWindowUnion::activeWindowControls() const {
|
||||
std::vector<WindowControl*> res;
|
||||
res.reserve(_nWindowsDisplayed);
|
||||
for (unsigned int i = 0; i < _nWindowsDisplayed; i++) {
|
||||
res.push_back(_windowControl[i]);
|
||||
void DisplayWindowUnion::initialize(const std::vector<QRect>& monitorSizeList,
|
||||
const sgct::config::Cluster& cluster)
|
||||
{
|
||||
for (int i = 0; i < cluster.nodes.front().windows.size(); i++) {
|
||||
addWindow();
|
||||
}
|
||||
|
||||
const size_t nWindows = std::min(
|
||||
cluster.nodes.front().windows.size(),
|
||||
_windowControls.size()
|
||||
);
|
||||
for (size_t i = 0; i < nWindows; i++) {
|
||||
const sgct::config::Window& w = cluster.nodes.front().windows[i];
|
||||
WindowControl* wCtrl = _windowControls[i];
|
||||
ghoul_assert(wCtrl, "No window control");
|
||||
|
||||
//
|
||||
// Get monitor index for the window
|
||||
uint8_t monitorNum = 0;
|
||||
if (w.monitor.has_value()) {
|
||||
monitorNum = *w.monitor;
|
||||
if (monitorNum > (monitorSizeList.size() - 1)) {
|
||||
monitorNum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Get position for the window in monitor coordinates
|
||||
unsigned int posX = 0;
|
||||
unsigned int posY = 0;
|
||||
wCtrl->setMonitorSelection(monitorNum);
|
||||
if (w.pos.has_value()) {
|
||||
posX = w.pos->x;
|
||||
posY = w.pos->y;
|
||||
// Convert offsets to coordinates relative to the selected monitor bounds,
|
||||
// since window offsets are stored n the sgct config file relative to the
|
||||
// coordinates of the total "canvas" of all displays
|
||||
if (monitorSizeList.size() > monitorNum) {
|
||||
posX -= monitorSizeList[monitorNum].x();
|
||||
posY -= monitorSizeList[monitorNum].y();
|
||||
}
|
||||
}
|
||||
wCtrl->setDimensions(posX, posY, w.size.x, w.size.y);
|
||||
|
||||
//
|
||||
// Get Window name
|
||||
if (w.name.has_value()) {
|
||||
wCtrl->setWindowName(*w.name);
|
||||
}
|
||||
|
||||
//
|
||||
// Get decoration state
|
||||
if (w.isDecorated.has_value()) {
|
||||
wCtrl->setDecorationState(*w.isDecorated);
|
||||
}
|
||||
|
||||
//
|
||||
// Get Spout state
|
||||
wCtrl->setSpoutOutputState(w.spout.has_value() && w.spout->enabled);
|
||||
|
||||
//
|
||||
// Get render states
|
||||
if (w.draw2D.has_value()) {
|
||||
wCtrl->setRender2D(*w.draw2D);
|
||||
}
|
||||
if (w.draw3D.has_value()) {
|
||||
wCtrl->setRender3D(*w.draw3D);
|
||||
}
|
||||
|
||||
//
|
||||
// Get projection-based settings depending on the projection in the window
|
||||
std::visit(overloaded{
|
||||
[&](const sgct::config::CylindricalProjection& p) {
|
||||
if (p.quality.has_value() && p.heightOffset.has_value()) {
|
||||
wCtrl->setProjectionCylindrical(*p.quality, *p.heightOffset);
|
||||
}
|
||||
},
|
||||
[&](const sgct::config::EquirectangularProjection& p) {
|
||||
if (p.quality.has_value()) {
|
||||
wCtrl->setProjectionEquirectangular(*p.quality);
|
||||
}
|
||||
},
|
||||
[&](const sgct::config::FisheyeProjection& p) {
|
||||
if (p.quality.has_value()) {
|
||||
wCtrl->setProjectionFisheye(*p.quality);
|
||||
}
|
||||
},
|
||||
[&](const sgct::config::PlanarProjection& p) {
|
||||
wCtrl->setProjectionPlanar(
|
||||
std::abs(p.fov.left) + std::abs(p.fov.right),
|
||||
std::abs(p.fov.up) + std::abs(p.fov.down)
|
||||
);
|
||||
},
|
||||
[&](const sgct::config::SphericalMirrorProjection& p) {
|
||||
if (p.quality.has_value()) {
|
||||
wCtrl->setProjectionSphericalMirror(*p.quality);
|
||||
}
|
||||
},
|
||||
[&](const sgct::config::NoProjection&) {},
|
||||
[&](const sgct::config::ProjectionPlane&) {},
|
||||
[&](const sgct::config::CubemapProjection&) {},
|
||||
},
|
||||
w.viewports.back().projection
|
||||
);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<WindowControl*>& DisplayWindowUnion::windowControls() {
|
||||
return _windowControl;
|
||||
void DisplayWindowUnion::applyWindowSettings(std::vector<sgct::config::Window>& windows) {
|
||||
windows.resize(_nWindowsDisplayed);
|
||||
for (size_t wIdx = 0; wIdx < _nWindowsDisplayed; wIdx++) {
|
||||
ghoul_assert(_windowControls[wIdx], "No window control");
|
||||
_windowControls[wIdx]->generateWindowInformation(windows[wIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayWindowUnion::addWindow() {
|
||||
if (_nWindowsDisplayed < _windowControl.size()) {
|
||||
_windowControl[_nWindowsDisplayed]->resetToDefaults();
|
||||
if (_nWindowsDisplayed < _windowControls.size()) {
|
||||
_windowControls[_nWindowsDisplayed]->resetToDefaults();
|
||||
_nWindowsDisplayed++;
|
||||
showWindows();
|
||||
updateWindows();
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayWindowUnion::removeWindow() {
|
||||
if (_nWindowsDisplayed > 1) {
|
||||
_nWindowsDisplayed--;
|
||||
showWindows();
|
||||
updateWindows();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int DisplayWindowUnion::numWindowsDisplayed() const {
|
||||
return _nWindowsDisplayed;
|
||||
}
|
||||
|
||||
void DisplayWindowUnion::showWindows() {
|
||||
for (size_t i = 0; i < _windowControl.size(); i++) {
|
||||
_windowControl[i]->setVisible(i < _nWindowsDisplayed);
|
||||
void DisplayWindowUnion::updateWindows() {
|
||||
for (size_t i = 0; i < _windowControls.size(); i++) {
|
||||
_windowControls[i]->setVisible(i < _nWindowsDisplayed);
|
||||
}
|
||||
for (size_t i = 0; i < _frameBorderLines.size(); i++) {
|
||||
_frameBorderLines[i]->setVisible(i < (_nWindowsDisplayed - 1));
|
||||
}
|
||||
_removeWindowButton->setEnabled(_nWindowsDisplayed > 1);
|
||||
_addWindowButton->setEnabled(_nWindowsDisplayed != _windowControl.size());
|
||||
for (WindowControl* w : _windowControl) {
|
||||
_addWindowButton->setEnabled(_nWindowsDisplayed != _windowControls.size());
|
||||
for (WindowControl* w : _windowControls) {
|
||||
w->showWindowLabel(_nWindowsDisplayed > 1);
|
||||
}
|
||||
|
||||
emit nWindowsChanged(_nWindowsDisplayed);
|
||||
}
|
||||
|
||||
@@ -24,178 +24,70 @@
|
||||
|
||||
#include "sgctedit/monitorbox.h"
|
||||
|
||||
#include <ghoul/misc/assert.h>
|
||||
#include "windowcolors.h"
|
||||
#include <QPainter>
|
||||
|
||||
namespace {
|
||||
constexpr float MarginFractionWidgetSize = 0.05f;
|
||||
constexpr int WindowOpacity = 170;
|
||||
|
||||
QRectF computeUnion(const std::vector<QRect>& monitorResolutions) {
|
||||
QRectF res = QRectF(0.f, 0.f, 0.f, 0.f);
|
||||
for (const QRect& m : monitorResolutions) {
|
||||
res |= m;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
MonitorBox::MonitorBox(QRect widgetDims, const std::vector<QRect>& monitorResolutions,
|
||||
unsigned int nWindows, const std::array<QColor, 4>& windowColors,
|
||||
MonitorBox::MonitorBox(QRect widgetSize, const std::vector<QRect>& monitorResolutions,
|
||||
QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, _nWindows(nWindows)
|
||||
, _colorsForWindows(windowColors)
|
||||
{
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
|
||||
const QRectF monitorArrangement = computeUnion(monitorResolutions);
|
||||
|
||||
constexpr float MarginFractionWidgetSize = 0.05f;
|
||||
|
||||
//
|
||||
// Calculate the collective size of the monitors
|
||||
QRectF monitorArrangement = QRectF(0.f, 0.f, 0.f, 0.f);
|
||||
for (const QRect& m : monitorResolutions) {
|
||||
monitorArrangement |= m;
|
||||
}
|
||||
|
||||
//
|
||||
// Set the size of the widget according to the aspect ratio of the total size
|
||||
const float aspectRatio = monitorArrangement.width() / monitorArrangement.height();
|
||||
if (aspectRatio > 1.0) {
|
||||
const float borderMargin = 2.f * MarginFractionWidgetSize * widgetDims.width();
|
||||
widgetDims.setHeight(widgetDims.width() / aspectRatio + borderMargin);
|
||||
if (aspectRatio > 1.f) {
|
||||
const float borderMargin = 2.f * MarginFractionWidgetSize * widgetSize.width();
|
||||
widgetSize.setHeight(widgetSize.width() / aspectRatio + borderMargin);
|
||||
}
|
||||
else {
|
||||
const float borderMargin = 2.f * MarginFractionWidgetSize * widgetDims.height();
|
||||
widgetDims.setWidth(widgetDims.height() * aspectRatio + borderMargin);
|
||||
const float borderMargin = 2.f * MarginFractionWidgetSize * widgetSize.height();
|
||||
widgetSize.setWidth(widgetSize.height() * aspectRatio + borderMargin);
|
||||
}
|
||||
setFixedSize(widgetDims.width(), widgetDims.height());
|
||||
setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
setFixedSize(widgetSize.width(), widgetSize.height());
|
||||
|
||||
//
|
||||
// Map monitor resolution to widget coordinates
|
||||
std::vector<QSizeF> offsets =
|
||||
aspectRatio >= 1.f ?
|
||||
computeScaledResolutionLandscape(monitorArrangement, monitorResolutions) :
|
||||
computeScaledResolutionPortrait(monitorArrangement, monitorResolutions);
|
||||
|
||||
for (size_t i = 0; i < monitorResolutions.size(); i++) {
|
||||
_monitorDimensionsScaled.emplace_back(
|
||||
offsets[i].width(),
|
||||
offsets[i].height(),
|
||||
monitorResolutions[i].width() * _monitorScaleFactor,
|
||||
monitorResolutions[i].height() * _monitorScaleFactor
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void MonitorBox::paintEvent(QPaintEvent*) {
|
||||
QPainter painter(this);
|
||||
|
||||
//
|
||||
// Draw widget border
|
||||
constexpr int Radius = 10;
|
||||
painter.setPen(QPen(Qt::gray, 4));
|
||||
painter.drawRoundedRect(0, 0, width() - 1, height() - 1, Radius, Radius);
|
||||
|
||||
//
|
||||
// Draw window out-of-bounds region(s) first
|
||||
for (int i = 0; i < _nWindows; i++) {
|
||||
painter.setBrush(Qt::BDiagPattern);
|
||||
painter.setPen(QPen(_colorsForWindows[i], 0));
|
||||
painter.drawRect(_windowRendering[i]);
|
||||
}
|
||||
|
||||
// Draw & fill monitors over the out-of-bounds regions
|
||||
painter.setPen(QPen(Qt::black, 2));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
|
||||
for (size_t i = 0; i < _monitorDimensionsScaled.size(); i++) {
|
||||
const QColor Grey = QColor(0xDD, 0xDD, 0xDD);
|
||||
|
||||
painter.drawRect(_monitorDimensionsScaled[i]);
|
||||
painter.fillRect(_monitorDimensionsScaled[i], QBrush(Grey, Qt::SolidPattern));
|
||||
|
||||
if (_monitorDimensionsScaled.size() > 1 && i == 0) {
|
||||
// We only want to render the "Primary" if there are multiple windows
|
||||
const QPointF textPos = QPointF(
|
||||
_monitorDimensionsScaled[i].left() + 4.0,
|
||||
_monitorDimensionsScaled[i].top() + 24.0
|
||||
);
|
||||
QFont f("Arial");
|
||||
f.setPixelSize(24);
|
||||
painter.setFont(f);
|
||||
painter.drawText(textPos, "Primary");
|
||||
}
|
||||
}
|
||||
|
||||
// Draw window number(s) first for darker contrast, then window(s) over both
|
||||
// out-of-bounds and monitors
|
||||
for (int i = 0; i < _nWindows; i++) {
|
||||
QPointF p = QPointF(
|
||||
_windowRendering[i].left() + 5.0,
|
||||
_windowRendering[i].bottom() - 5.0
|
||||
);
|
||||
p.setX(std::clamp(p.x(), 0.0, static_cast<double>(size().width()) - 10.0));
|
||||
p.setY(std::clamp(p.y(), 20.0, static_cast<double>(size().height())));
|
||||
painter.drawText(p, QString::number(i + 1));
|
||||
}
|
||||
|
||||
//
|
||||
// Paint window
|
||||
for (int i = 0; i < _nWindows; i++) {
|
||||
painter.setPen(QPen(_colorsForWindows[i], 1));
|
||||
painter.drawRect(_windowRendering[i]);
|
||||
|
||||
QColor fillColor = _colorsForWindows[i];
|
||||
fillColor.setAlpha(WindowOpacity);
|
||||
painter.fillRect(_windowRendering[i], QBrush(fillColor, Qt::SolidPattern));
|
||||
}
|
||||
}
|
||||
|
||||
void MonitorBox::windowDimensionsChanged(unsigned int mIdx, unsigned int wIdx,
|
||||
const QRectF& newDimensions)
|
||||
{
|
||||
_windowRendering[wIdx] = QRectF(
|
||||
_monitorDimensionsScaled[mIdx].x() + newDimensions.left() * _monitorScaleFactor,
|
||||
_monitorDimensionsScaled[mIdx].y() + newDimensions.top() * _monitorScaleFactor,
|
||||
newDimensions.width() * _monitorScaleFactor,
|
||||
newDimensions.height() * _monitorScaleFactor
|
||||
);
|
||||
update();
|
||||
}
|
||||
|
||||
std::vector<QSizeF> MonitorBox::computeScaledResolutionLandscape(QRectF arrangement,
|
||||
const std::vector<QRect>& resolutions)
|
||||
{
|
||||
std::vector<QSizeF> offsets;
|
||||
|
||||
const float margin = size().width() * MarginFractionWidgetSize;
|
||||
const float virtualWidth = size().width() * (1.f - MarginFractionWidgetSize * 2.f);
|
||||
_monitorScaleFactor = virtualWidth / arrangement.width();
|
||||
_monitorScaleFactor = virtualWidth / monitorArrangement.width();
|
||||
|
||||
const float aspectRatio = arrangement.width() / arrangement.height();
|
||||
const float newHeight = virtualWidth / aspectRatio;
|
||||
|
||||
for (const QRect& res : resolutions) {
|
||||
const float x = margin + (res.x() - arrangement.x()) * _monitorScaleFactor;
|
||||
for (const QRect& res : monitorResolutions) {
|
||||
const float x = margin + (res.x() - monitorArrangement.x()) * _monitorScaleFactor;
|
||||
const float y = margin + (size().height() - newHeight - margin) / 4.f +
|
||||
(res.y() - arrangement.y()) * _monitorScaleFactor;
|
||||
offsets.emplace_back(x, y);
|
||||
(res.y() - monitorArrangement.y()) * _monitorScaleFactor;
|
||||
const float width = res.width() * _monitorScaleFactor;
|
||||
const float height = res.height() * _monitorScaleFactor;
|
||||
_monitorDimensionsScaled.emplace_back(x, y, width, height);
|
||||
}
|
||||
|
||||
return offsets;
|
||||
}
|
||||
|
||||
std::vector<QSizeF> MonitorBox::computeScaledResolutionPortrait(QRectF arrangement,
|
||||
const std::vector<QRect>& resolutions)
|
||||
void MonitorBox::windowDimensionsChanged(unsigned int monitorIdx, unsigned int windowIdx,
|
||||
const QRectF& dimension)
|
||||
{
|
||||
std::vector<QSizeF> offsets;
|
||||
|
||||
const float marginWidget = size().height() * MarginFractionWidgetSize;
|
||||
const float virtualHeight = size().height() * (1.f - MarginFractionWidgetSize * 2.f);
|
||||
_monitorScaleFactor = virtualHeight / arrangement.height();
|
||||
|
||||
const float aspectRatio = arrangement.width() / arrangement.height();
|
||||
const float newWidth = virtualHeight * aspectRatio;
|
||||
|
||||
for (const QRect& res : resolutions) {
|
||||
const float x = marginWidget + (size().width() - newWidth - marginWidget) / 4.f +
|
||||
(res.x() - arrangement.x()) * _monitorScaleFactor;
|
||||
const float y = marginWidget + (res.y() - arrangement.y()) * _monitorScaleFactor;
|
||||
offsets.emplace_back(x, y);
|
||||
if (windowIdx >= _windowRendering.size()) {
|
||||
// We were told about a window change for a window we haven't created yet, so
|
||||
// initialize the vector so that we have enough windows available
|
||||
_windowRendering.resize(windowIdx + 1, QRectF(0.f, 0.f, 0.f, 0.f));
|
||||
}
|
||||
|
||||
return offsets;
|
||||
|
||||
_windowRendering[windowIdx] = QRectF(
|
||||
_monitorDimensionsScaled[monitorIdx].x() + dimension.left() * _monitorScaleFactor,
|
||||
_monitorDimensionsScaled[monitorIdx].y() + dimension.top() * _monitorScaleFactor,
|
||||
dimension.width() * _monitorScaleFactor,
|
||||
dimension.height() * _monitorScaleFactor
|
||||
);
|
||||
update();
|
||||
}
|
||||
|
||||
void MonitorBox::nWindowsDisplayedChanged(int nWindows) {
|
||||
@@ -204,3 +96,72 @@ void MonitorBox::nWindowsDisplayedChanged(int nWindows) {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void MonitorBox::paintEvent(QPaintEvent*) {
|
||||
QPainter painter(this);
|
||||
|
||||
//
|
||||
// Draw widget border
|
||||
constexpr double RectRadius = 10.0;
|
||||
painter.setPen(QPen(Qt::gray, 4));
|
||||
painter.drawRoundedRect(0, 0, width() - 1, height() - 1, RectRadius, RectRadius);
|
||||
|
||||
//
|
||||
// Draw window out-of-bounds region(s) first
|
||||
for (int i = 0; i < _nWindows; i++) {
|
||||
painter.setBrush(Qt::BDiagPattern);
|
||||
painter.setPen(QPen(colorForWindow(i), 0));
|
||||
painter.drawRect(_windowRendering[i]);
|
||||
}
|
||||
|
||||
//
|
||||
// Draw & fill monitors over the out-of-bounds regions
|
||||
painter.setPen(QPen(Qt::black, 2));
|
||||
painter.setBrush(Qt::NoBrush);
|
||||
|
||||
for (size_t i = 0; i < _monitorDimensionsScaled.size(); i++) {
|
||||
constexpr QColor Grey = QColor(221, 221, 221);
|
||||
|
||||
painter.drawRect(_monitorDimensionsScaled[i]);
|
||||
painter.fillRect(_monitorDimensionsScaled[i], QBrush(Grey, Qt::SolidPattern));
|
||||
|
||||
// We only want to render the "Primary" if there are multiple windows
|
||||
if (_monitorDimensionsScaled.size() > 1 && i == 0) {
|
||||
const QPointF textPos = QPointF(
|
||||
_monitorDimensionsScaled[i].left() + 4.0,
|
||||
_monitorDimensionsScaled[i].top() + 24.0
|
||||
);
|
||||
QFont f = QFont("Arial");
|
||||
f.setPixelSize(24);
|
||||
painter.setFont(f);
|
||||
painter.drawText(textPos, "Primary");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Draw window number(s) first for darker contrast, then window(s) over both
|
||||
// out-of-bounds and monitors
|
||||
for (int i = 0; i < _nWindows; i++) {
|
||||
const double x = _windowRendering[i].left() + 5.0;
|
||||
const double y = _windowRendering[i].bottom() - 5.0;
|
||||
const QPointF p = QPointF(
|
||||
std::clamp(x, 0.0, static_cast<double>(size().width()) - 10.0),
|
||||
std::clamp(y, 20.0, static_cast<double>(size().height()))
|
||||
);
|
||||
painter.drawText(p, QString::number(i + 1));
|
||||
}
|
||||
|
||||
//
|
||||
// Paint window
|
||||
for (int i = 0; i < _nWindows; i++) {
|
||||
constexpr int WindowOpacity = 170;
|
||||
|
||||
QColor color = colorForWindow(i);
|
||||
painter.setPen(QPen(color, 1));
|
||||
painter.drawRect(_windowRendering[i]);
|
||||
|
||||
QColor fillColor = color;
|
||||
fillColor.setAlpha(WindowOpacity);
|
||||
painter.fillRect(_windowRendering[i], QBrush(fillColor, Qt::SolidPattern));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2025 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "sgctedit/orientationdialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleValidator>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <ghoul/glm.h>
|
||||
|
||||
OrientationDialog::OrientationDialog(sgct::quat& orientation, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, _orientationValue(orientation)
|
||||
{
|
||||
setWindowTitle("Global Orientation");
|
||||
QGridLayout* layoutWindow = new QGridLayout(this);
|
||||
|
||||
{
|
||||
const QString pitchTip = "Pitch or elevation: negative numbers tilt the camera "
|
||||
"downwards; positive numbers tilt upwards.\nThe allowed range is [-90, 90]. "
|
||||
"Internally, this corresponds to the x value in the quaternion";
|
||||
|
||||
QLabel* labelPitch = new QLabel("Pitch");
|
||||
labelPitch->setToolTip(pitchTip);
|
||||
layoutWindow->addWidget(labelPitch, 0, 0);
|
||||
|
||||
_linePitch = new QLineEdit;
|
||||
_linePitch->setText(QString::number(glm::degrees(_orientationValue.x)));
|
||||
_linePitch->setToolTip(pitchTip);
|
||||
QDoubleValidator* validatorPitch = new QDoubleValidator(-90.0, 90.0, 15);
|
||||
validatorPitch->setNotation(QDoubleValidator::StandardNotation);
|
||||
_linePitch->setValidator(validatorPitch);
|
||||
layoutWindow->addWidget(_linePitch, 0, 1);
|
||||
}
|
||||
{
|
||||
const QString rollTip = "Roll or bank: negative numbers rotate the camera "
|
||||
"counter-clockwise; positive numbers clockwise.\nThe allowed range is "
|
||||
"[-180, 180]. Internally, this corresponds to the z value in the quaternion";
|
||||
|
||||
QLabel* labelRoll = new QLabel("Roll");
|
||||
labelRoll->setToolTip(rollTip);
|
||||
layoutWindow->addWidget(labelRoll, 1, 0);
|
||||
|
||||
_lineRoll = new QLineEdit;
|
||||
_lineRoll->setText(QString::number(glm::degrees(_orientationValue.z)));
|
||||
_lineRoll->setToolTip(rollTip);
|
||||
QDoubleValidator* validatorRoll = new QDoubleValidator(-360.0, 360.0, 15);
|
||||
validatorRoll->setNotation(QDoubleValidator::StandardNotation);
|
||||
_lineRoll->setValidator(validatorRoll);
|
||||
layoutWindow->addWidget(_lineRoll, 1, 1);
|
||||
}
|
||||
{
|
||||
const QString yawTip = "Yaw, heading, or azimuth: negative numbers pan the "
|
||||
"camera to the left; positive numbers pan to the\nright. The allowed range "
|
||||
"is [-360, 360]. Internally, this corresponds to the y value in the "
|
||||
"quaternion";
|
||||
|
||||
QLabel* labelYaw = new QLabel;
|
||||
labelYaw ->setText("Yaw");
|
||||
labelYaw->setToolTip(yawTip);
|
||||
layoutWindow->addWidget(labelYaw, 2, 0);
|
||||
|
||||
_lineYaw = new QLineEdit;
|
||||
_lineYaw->setText(QString::number(glm::degrees(_orientationValue.y)));
|
||||
_lineYaw->setToolTip(yawTip);
|
||||
QDoubleValidator* validatorYaw = new QDoubleValidator(-180.0, 180.0, 15, this);
|
||||
validatorYaw->setNotation(QDoubleValidator::StandardNotation);
|
||||
_lineYaw->setValidator(validatorYaw);
|
||||
layoutWindow->addWidget(_lineYaw, 2, 1);
|
||||
}
|
||||
{
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel
|
||||
);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &OrientationDialog::ok);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &OrientationDialog::reject);
|
||||
layoutWindow->addWidget(buttons, 3, 0, 1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
void OrientationDialog::ok() {
|
||||
_orientationValue.x = glm::radians(_linePitch->text().toFloat());
|
||||
_orientationValue.y = glm::radians(_lineYaw->text().toFloat());
|
||||
_orientationValue.z = glm::radians(_lineRoll->text().toFloat());
|
||||
_orientationValue.w = 1.0;
|
||||
accept();
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2025 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "sgctedit/settingswidget.h"
|
||||
|
||||
#include "sgctedit/orientationdialog.h"
|
||||
#include <QCheckBox>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
SettingsWidget::SettingsWidget(sgct::quat orientation, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, _orientationValue(std::move(orientation))
|
||||
, _showUiOnFirstWindow(new QCheckBox(
|
||||
"Show user interface only on first window using graphics:"
|
||||
))
|
||||
, _firstWindowGraphicsSelection(new QComboBox)
|
||||
, _firstWindowSelectionLayout(new QHBoxLayout)
|
||||
{
|
||||
QBoxLayout* layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
_showUiOnFirstWindow->setChecked(false);
|
||||
_showUiOnFirstWindow->setEnabled(false);
|
||||
_showUiOnFirstWindow->setToolTip(
|
||||
"If enabled the first window is marked as a GUI window resulting in the user "
|
||||
"interface only being shown\non that window and the rendering is suppressed on "
|
||||
"this first window. The remaining windows will render\nnormally but they will "
|
||||
"not show the user interface"
|
||||
);
|
||||
|
||||
_firstWindowGraphicsSelection->setToolTip(
|
||||
"Select the contents of the first window to match one of the other windows"
|
||||
);
|
||||
_firstWindowGraphicsSelection->setFixedWidth(150);
|
||||
connect(
|
||||
_showUiOnFirstWindow, &QCheckBox::clicked,
|
||||
this, &SettingsWidget::showUiOnFirstWindowClicked
|
||||
);
|
||||
|
||||
_firstWindowSelectionLayout->addWidget(_showUiOnFirstWindow);
|
||||
_firstWindowSelectionLayout->addWidget(_firstWindowGraphicsSelection);
|
||||
_firstWindowSelectionLayout->addStretch();
|
||||
layout->addLayout(_firstWindowSelectionLayout);
|
||||
|
||||
_checkBoxVsync = new QCheckBox("Enable VSync");
|
||||
_checkBoxVsync->setToolTip(
|
||||
"If enabled the framerate will be locked to the refresh rate of the monitor"
|
||||
);
|
||||
layout->addWidget(_checkBoxVsync);
|
||||
|
||||
QPushButton* orientationButton = new QPushButton("Global Orientation");
|
||||
orientationButton->setToolTip(
|
||||
"Opens a separate dialog for setting the pitch, yaw, and roll of the camera\n"
|
||||
"(the orientation applies to all viewports)"
|
||||
);
|
||||
orientationButton->setFocusPolicy(Qt::NoFocus);
|
||||
layout->addWidget(orientationButton);
|
||||
connect(
|
||||
orientationButton, &QPushButton::released,
|
||||
[this]() {
|
||||
OrientationDialog _orientationDialog(_orientationValue, this);
|
||||
_orientationDialog.exec();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
sgct::quat SettingsWidget::orientation() const {
|
||||
return _orientationValue;
|
||||
}
|
||||
|
||||
bool SettingsWidget::vsync() const {
|
||||
return _checkBoxVsync->isChecked();
|
||||
}
|
||||
|
||||
bool SettingsWidget::showUiOnFirstWindow() const {
|
||||
return (_showUiOnFirstWindow->isChecked() && _showUiOnFirstWindow->isEnabled());
|
||||
}
|
||||
|
||||
void SettingsWidget::setShowUiOnFirstWindow(bool setUiOnFirstWindow) {
|
||||
_showUiOnFirstWindow->setChecked(setUiOnFirstWindow);
|
||||
}
|
||||
|
||||
void SettingsWidget::setEnableShowUiOnFirstWindowCheckbox(bool enable) {
|
||||
_showUiOnFirstWindow->setEnabled(enable);
|
||||
_firstWindowGraphicsSelection->setEnabled(enable);
|
||||
}
|
||||
|
||||
int SettingsWidget::graphicsSelectionForShowUiOnFirstWindow() const {
|
||||
return _firstWindowGraphicsSelection->currentIndex();
|
||||
}
|
||||
|
||||
void SettingsWidget::setGraphicsSelectionForShowUiOnFirstWindow(int selection) {
|
||||
_firstWindowGraphicsSelection->setCurrentIndex(selection);
|
||||
}
|
||||
|
||||
void SettingsWidget::setVsync(bool enableVsync) {
|
||||
_checkBoxVsync->setChecked(enableVsync);
|
||||
}
|
||||
|
||||
void SettingsWidget::nWindowsDisplayedChanged(int newCount) {
|
||||
constexpr int CountOneWindow = 1;
|
||||
constexpr int CountTwoWindows = 2;
|
||||
int graphicsSelect = _firstWindowGraphicsSelection->currentIndex();
|
||||
graphicsSelect = std::max(0, graphicsSelect);
|
||||
|
||||
QList<QString> graphicsOptions = {"None (GUI only)"};
|
||||
for (int i = CountOneWindow; i <= newCount; i++) {
|
||||
graphicsOptions.append("Window " + QString::number(i));
|
||||
}
|
||||
_firstWindowGraphicsSelection->clear();
|
||||
_firstWindowGraphicsSelection->addItems(graphicsOptions);
|
||||
setEnableShowUiOnFirstWindowCheckbox(newCount > CountOneWindow);
|
||||
if (graphicsSelect > newCount) {
|
||||
graphicsSelect = newCount;
|
||||
}
|
||||
_firstWindowGraphicsSelection->setCurrentIndex(graphicsSelect);
|
||||
|
||||
if (newCount == CountOneWindow) {
|
||||
_stateOfUiOnFirstWindowWhenDisabled = _showUiOnFirstWindow->isChecked();
|
||||
_showUiOnFirstWindow->setChecked(false);
|
||||
_firstWindowGraphicsSelection->setEnabled(false);
|
||||
}
|
||||
else if (newCount == CountTwoWindows &&
|
||||
_stateOfUiOnFirstWindowPreviousCount == CountOneWindow)
|
||||
{
|
||||
if (_stateOfUiOnFirstWindowWhenDisabled) {
|
||||
_showUiOnFirstWindow->setChecked(true);
|
||||
}
|
||||
_firstWindowGraphicsSelection->setEnabled(_showUiOnFirstWindow->isChecked());
|
||||
}
|
||||
else {
|
||||
_firstWindowGraphicsSelection->setEnabled(_showUiOnFirstWindow->isChecked());
|
||||
}
|
||||
_stateOfUiOnFirstWindowPreviousCount = newCount;
|
||||
}
|
||||
|
||||
void SettingsWidget::showUiOnFirstWindowClicked(bool checked) {
|
||||
_firstWindowGraphicsSelection->setEnabled(checked);
|
||||
}
|
||||
|
||||
QComboBox* SettingsWidget::firstWindowGraphicsSelection() {
|
||||
return _firstWindowGraphicsSelection;
|
||||
}
|
||||
|
||||
QCheckBox* SettingsWidget::showUiOnFirstWindowCheckbox() {
|
||||
return _showUiOnFirstWindow;
|
||||
}
|
||||
@@ -26,20 +26,22 @@
|
||||
|
||||
#include <sgctedit/displaywindowunion.h>
|
||||
#include <sgctedit/monitorbox.h>
|
||||
#include <sgctedit/settingswidget.h>
|
||||
#include <sgctedit/windowcontrol.h>
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/misc/assert.h>
|
||||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
#include <QColor>
|
||||
#include <QComboBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QScreen>
|
||||
#include <QVBoxLayout>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
namespace {
|
||||
constexpr QRect MonitorWidgetSize = QRect(0, 0, 500, 500);
|
||||
constexpr int MaxNumberWindows = 4;
|
||||
|
||||
// Returns true if the windows are not ordered correctly. 'Correct' in this means that
|
||||
@@ -66,218 +68,50 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
|
||||
template <class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
std::vector<QRect> createMonitorInfoSet() {
|
||||
std::vector<QRect> monitorSizes;
|
||||
for (QScreen* screen : qApp->screens()) {
|
||||
const QSize size = screen->size();
|
||||
const QRect geometry = screen->availableGeometry();
|
||||
const int actualWidth = std::max(size.width(), geometry.width());
|
||||
const int actualHeight = std::max(size.height(), geometry.height());
|
||||
monitorSizes.emplace_back(
|
||||
geometry.x(),
|
||||
geometry.y(),
|
||||
static_cast<int>(actualWidth * screen->devicePixelRatio()),
|
||||
static_cast<int>(actualHeight * screen->devicePixelRatio())
|
||||
);
|
||||
}
|
||||
return monitorSizes;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SgctEdit::SgctEdit(QWidget* parent, std::filesystem::path userConfigPath)
|
||||
SgctEdit::SgctEdit(sgct::config::Cluster cluster, std::string configName,
|
||||
std::filesystem::path configBasePath, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, _userConfigPath(std::move(userConfigPath))
|
||||
{
|
||||
setWindowTitle("Window Configuration Editor");
|
||||
createWidgets(createMonitorInfoSet(), 1, true);
|
||||
}
|
||||
|
||||
SgctEdit::SgctEdit(sgct::config::Cluster& cluster, std::string configName,
|
||||
std::filesystem::path& configBasePath, QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, _cluster(cluster)
|
||||
, _userConfigPath(configBasePath)
|
||||
, _cluster(std::move(cluster))
|
||||
, _userConfigPath(std::move(configBasePath))
|
||||
, _configurationFilename(std::move(configName))
|
||||
, _didImportValues(true)
|
||||
{
|
||||
setWindowTitle("Window Configuration Editor");
|
||||
const size_t nWindows = _cluster.nodes.front().windows.size();
|
||||
|
||||
std::vector<QRect> monitorSizes = createMonitorInfoSet();
|
||||
createWidgets(monitorSizes, static_cast<unsigned int>(nWindows), false);
|
||||
const size_t existingWindowsControlSize = _displayWidget->windowControls().size();
|
||||
for (size_t i = 0; i < nWindows; i++) {
|
||||
sgct::config::Window& w = _cluster.nodes.front().windows[i];
|
||||
WindowControl* wCtrl = _displayWidget->windowControls()[i];
|
||||
if (i < existingWindowsControlSize && wCtrl) {
|
||||
unsigned int monitorNum = 0;
|
||||
if (w.monitor) {
|
||||
monitorNum = static_cast<unsigned int>(w.monitor.value());
|
||||
if (monitorNum > (monitorSizes.size() - 1)) {
|
||||
monitorNum = 0;
|
||||
}
|
||||
}
|
||||
unsigned int posX = 0;
|
||||
unsigned int posY = 0;
|
||||
wCtrl->setMonitorSelection(monitorNum);
|
||||
if (w.pos.has_value()) {
|
||||
posX = w.pos.value().x;
|
||||
posY = w.pos.value().y;
|
||||
// Convert offsets to coordinates relative to the selected monitor bounds,
|
||||
// since window offsets are stored n the sgct config file relative to the
|
||||
// coordinates of the total "canvas" of all displays
|
||||
if (monitorSizes.size() > monitorNum) {
|
||||
posX -= monitorSizes[monitorNum].x();
|
||||
posY -= monitorSizes[monitorNum].y();
|
||||
}
|
||||
}
|
||||
const QRectF newDims = QRectF(posX, posY, w.size.x, w.size.y);
|
||||
wCtrl->setDimensions(newDims);
|
||||
if (w.name.has_value()) {
|
||||
wCtrl->setWindowName(w.name.value());
|
||||
}
|
||||
wCtrl->setDecorationState(w.isDecorated.value());
|
||||
wCtrl->setSpoutOutputState(w.spout.has_value() && w.spout->enabled);
|
||||
}
|
||||
setupProjectionTypeInGui(w.viewports.back(), wCtrl);
|
||||
}
|
||||
setupStateOfUiOnFirstWindow(nWindows);
|
||||
_settingsWidget->setVsync(
|
||||
_cluster.settings &&
|
||||
_cluster.settings.value().display &&
|
||||
_cluster.settings.value().display.value().swapInterval
|
||||
);
|
||||
}
|
||||
|
||||
void SgctEdit::setupStateOfUiOnFirstWindow(size_t nWindows) {
|
||||
bool firstWindowGuiIsEnabled = (nWindows > 1);
|
||||
int graphicsSelectionForFirstWindow = 0;
|
||||
int nGuiRenderTagsFound = 0;
|
||||
_settingsWidget->nWindowsDisplayedChanged(static_cast<int>(nWindows));
|
||||
|
||||
for (size_t i = 0; i < nWindows; i++) {
|
||||
sgct::config::Window& w = _cluster.nodes.front().windows[i];
|
||||
//First window needs to have "GUI" tag if this mode is set
|
||||
if (i == 0) {
|
||||
firstWindowGuiIsEnabled =
|
||||
(std::find(w.tags.begin(), w.tags.end(), "GUI") != w.tags.end());
|
||||
if (std::find(w.tags.begin(), w.tags.end(), "GUI_No_Render") != w.tags.end())
|
||||
{
|
||||
graphicsSelectionForFirstWindow = 0;
|
||||
nGuiRenderTagsFound++;
|
||||
}
|
||||
for (int winNum = 0; winNum <= 4; ++winNum) {
|
||||
const std::string searchTag = "GUI_Render_Win" + std::to_string(winNum);
|
||||
if (std::find(w.tags.begin(), w.tags.end(), searchTag) != w.tags.end()) {
|
||||
graphicsSelectionForFirstWindow = winNum;
|
||||
nGuiRenderTagsFound++;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If first window only renders 2D, and all subsequent windows do not, then
|
||||
// will enable the checkbox option for showing GUI only on first window
|
||||
if (w.draw2D.has_value()) {
|
||||
firstWindowGuiIsEnabled &= (i == 0) ? w.draw2D.value() : !w.draw2D.value();
|
||||
}
|
||||
else {
|
||||
firstWindowGuiIsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((nGuiRenderTagsFound > 1) ||
|
||||
(graphicsSelectionForFirstWindow > static_cast<int>(nWindows)))
|
||||
{
|
||||
graphicsSelectionForFirstWindow = 0;
|
||||
}
|
||||
|
||||
_settingsWidget->setShowUiOnFirstWindow(firstWindowGuiIsEnabled);
|
||||
if (firstWindowGuiIsEnabled) {
|
||||
// Call these again in order to ensure that GUI is configured correctly based on
|
||||
// the values read from the config file
|
||||
_settingsWidget->setEnableShowUiOnFirstWindowCheckbox(true);
|
||||
_settingsWidget->nWindowsDisplayedChanged(static_cast<int>(nWindows));
|
||||
}
|
||||
_settingsWidget->setGraphicsSelectionForShowUiOnFirstWindow(
|
||||
graphicsSelectionForFirstWindow
|
||||
);
|
||||
}
|
||||
|
||||
void SgctEdit::setupProjectionTypeInGui(sgct::config::Viewport& vPort,
|
||||
WindowControl* wCtrl)
|
||||
{
|
||||
std::visit(overloaded {
|
||||
[&](const sgct::config::CylindricalProjection& p) {
|
||||
if (p.quality && p.heightOffset) {
|
||||
wCtrl->setProjectionCylindrical(
|
||||
*p.quality,
|
||||
*p.heightOffset
|
||||
);
|
||||
}
|
||||
},
|
||||
[&](const sgct::config::EquirectangularProjection& p) {
|
||||
if (p.quality) {
|
||||
wCtrl->setProjectionEquirectangular(*p.quality);
|
||||
}
|
||||
},
|
||||
[&](const sgct::config::FisheyeProjection& p) {
|
||||
if (p.quality) {
|
||||
wCtrl->setProjectionFisheye(*p.quality);
|
||||
}
|
||||
},
|
||||
[&](const sgct::config::PlanarProjection& p) {
|
||||
wCtrl->setProjectionPlanar(
|
||||
(std::abs(p.fov.left) + std::abs(p.fov.right)),
|
||||
(std::abs(p.fov.up) + std::abs(p.fov.down))
|
||||
);
|
||||
},
|
||||
[&](const sgct::config::SphericalMirrorProjection& p) {
|
||||
if (p.quality) {
|
||||
wCtrl->setProjectionSphericalMirror(
|
||||
*p.quality
|
||||
);
|
||||
}
|
||||
},
|
||||
[&](const sgct::config::NoProjection&) {},
|
||||
[&](const sgct::config::ProjectionPlane&) {},
|
||||
[&](const sgct::config::CubemapProjection&) {},
|
||||
}, vPort.projection);
|
||||
}
|
||||
|
||||
std::vector<QRect> SgctEdit::createMonitorInfoSet() {
|
||||
const QList<QScreen*> screens = qApp->screens();
|
||||
const int nScreensManaged = std::min(static_cast<int>(screens.length()), 4);
|
||||
std::vector<QRect> monitorSizes;
|
||||
for (int s = 0; s < nScreensManaged; ++s) {
|
||||
const QSize size = screens[s]->size();
|
||||
const QRect geometry = screens[s]->availableGeometry();
|
||||
const int actualWidth = std::max(size.width(), geometry.width());
|
||||
const int actualHeight = std::max(size.height(), geometry.height());
|
||||
monitorSizes.emplace_back(
|
||||
geometry.x(),
|
||||
geometry.y(),
|
||||
static_cast<int>(actualWidth * screens[s]->devicePixelRatio()),
|
||||
static_cast<int>(actualHeight * screens[s]->devicePixelRatio())
|
||||
);
|
||||
}
|
||||
return monitorSizes;
|
||||
}
|
||||
|
||||
void SgctEdit::createWidgets(const std::vector<QRect>& monitorSizes,
|
||||
unsigned int nWindows, bool setToDefaults)
|
||||
{
|
||||
QBoxLayout* layout = new QVBoxLayout(this);
|
||||
layout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
|
||||
sgct::quat orientation = sgct::quat(0.f, 0.f, 0.f, 0.f);
|
||||
if (_cluster.scene.has_value() && _cluster.scene->orientation.has_value()) {
|
||||
orientation = *_cluster.scene->orientation;
|
||||
}
|
||||
//
|
||||
// Monitor widget at the top of the window
|
||||
{
|
||||
MonitorBox* monitorBox = new MonitorBox(
|
||||
MonitorWidgetSize,
|
||||
monitorSizes,
|
||||
MaxNumberWindows,
|
||||
_colorsForWindows,
|
||||
this
|
||||
);
|
||||
constexpr QRect MonitorWidgetSize = QRect(0, 0, 500, 500);
|
||||
|
||||
MonitorBox* monitorBox = new MonitorBox(MonitorWidgetSize, monitorSizes);
|
||||
layout->addWidget(monitorBox, 0, Qt::AlignCenter);
|
||||
|
||||
QFrame* displayFrame = new QFrame;
|
||||
displayFrame->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
|
||||
|
||||
QBoxLayout* displayLayout = new QVBoxLayout(displayFrame);
|
||||
_displayWidget = new DisplayWindowUnion(
|
||||
monitorSizes,
|
||||
MaxNumberWindows,
|
||||
_colorsForWindows,
|
||||
setToDefaults,
|
||||
this
|
||||
);
|
||||
_displayWidget = new DisplayWindowUnion(monitorSizes, MaxNumberWindows, this);
|
||||
connect(
|
||||
_displayWidget, &DisplayWindowUnion::windowChanged,
|
||||
monitorBox, &MonitorBox::windowDimensionsChanged
|
||||
@@ -286,45 +120,112 @@ void SgctEdit::createWidgets(const std::vector<QRect>& monitorSizes,
|
||||
_displayWidget, &DisplayWindowUnion::nWindowsChanged,
|
||||
monitorBox, &MonitorBox::nWindowsDisplayedChanged
|
||||
);
|
||||
// We initialize the widget after making the connections so that the monitorbox
|
||||
// widget will get informed about the windows and their sizes automatically
|
||||
_displayWidget->initialize(monitorSizes, _cluster);
|
||||
layout->addWidget(_displayWidget);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < nWindows; i++) {
|
||||
_displayWidget->addWindow();
|
||||
}
|
||||
|
||||
displayLayout->addWidget(_displayWidget);
|
||||
|
||||
layout->addWidget(displayFrame);
|
||||
|
||||
QWidget* settingsContainer = new QWidget;
|
||||
layout->addWidget(settingsContainer);
|
||||
QBoxLayout* settingsLayout = new QVBoxLayout(settingsContainer);
|
||||
settingsLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
|
||||
//
|
||||
// VSync settings
|
||||
_checkBoxVsync = new QCheckBox("Enable VSync");
|
||||
_checkBoxVsync->setToolTip(
|
||||
"If enabled the framerate will be locked to the refresh rate of the monitor"
|
||||
);
|
||||
_checkBoxVsync->setChecked(
|
||||
_cluster.settings.has_value() &&
|
||||
_cluster.settings->display.has_value() &&
|
||||
_cluster.settings->display->swapInterval
|
||||
);
|
||||
settingsLayout->addWidget(_checkBoxVsync);
|
||||
|
||||
|
||||
//
|
||||
// Orientation specification
|
||||
QLabel* labelOrientation = new QLabel("Orientation");
|
||||
settingsLayout->addWidget(labelOrientation);
|
||||
|
||||
QWidget* orientationContainer = new QWidget;
|
||||
settingsLayout->addWidget(orientationContainer);
|
||||
QGridLayout* layoutWindow = new QGridLayout(orientationContainer);
|
||||
|
||||
sgct::quat orientation = sgct::quat(0.f, 0.f, 0.f, 0.f);
|
||||
if (_cluster.scene.has_value() && _cluster.scene->orientation.has_value()) {
|
||||
orientation = *_cluster.scene->orientation;
|
||||
}
|
||||
glm::quat q = glm::quat(orientation.w, orientation.x, orientation.y, orientation.z);
|
||||
|
||||
{
|
||||
const QString pitchTip = "Pitch or elevation: negative numbers tilt the camera "
|
||||
"downwards; positive numbers tilt upwards.\nThe allowed range is [-90, 90].";
|
||||
|
||||
QLabel* labelPitch = new QLabel("Pitch");
|
||||
labelPitch->setToolTip(pitchTip);
|
||||
layoutWindow->addWidget(labelPitch, 0, 0);
|
||||
|
||||
_linePitch = new QLineEdit;
|
||||
_linePitch->setText(QString::number(glm::degrees(glm::pitch(q))));
|
||||
_linePitch->setToolTip(pitchTip);
|
||||
QDoubleValidator* validatorPitch = new QDoubleValidator(-90.0, 90.0, 15);
|
||||
validatorPitch->setNotation(QDoubleValidator::StandardNotation);
|
||||
_linePitch->setValidator(validatorPitch);
|
||||
layoutWindow->addWidget(_linePitch, 0, 1);
|
||||
|
||||
QLabel* range = new QLabel("Range [-90, 90] in degrees");
|
||||
layoutWindow->addWidget(range, 0, 2);
|
||||
}
|
||||
{
|
||||
const QString rollTip = "Roll or bank: negative numbers rotate the camera "
|
||||
"counter-clockwise; positive numbers clockwise.\nThe allowed range is "
|
||||
"[-180, 180].";
|
||||
|
||||
QLabel* labelRoll = new QLabel("Roll");
|
||||
labelRoll->setToolTip(rollTip);
|
||||
layoutWindow->addWidget(labelRoll, 1, 0);
|
||||
|
||||
_lineRoll = new QLineEdit;
|
||||
_lineRoll->setText(QString::number(glm::degrees(glm::roll(q))));
|
||||
_lineRoll->setToolTip(rollTip);
|
||||
QDoubleValidator* validatorRoll = new QDoubleValidator(-360.0, 360.0, 15);
|
||||
validatorRoll->setNotation(QDoubleValidator::StandardNotation);
|
||||
_lineRoll->setValidator(validatorRoll);
|
||||
layoutWindow->addWidget(_lineRoll, 1, 1);
|
||||
|
||||
QLabel* range = new QLabel("Range [-360, 360] in degrees");
|
||||
layoutWindow->addWidget(range, 1, 2);
|
||||
}
|
||||
{
|
||||
const QString yawTip = "Yaw, heading, or azimuth: negative numbers pan the "
|
||||
"camera to the left; positive numbers pan to the\nright. The allowed range "
|
||||
"is [-360, 360].";
|
||||
|
||||
QLabel* labelYaw = new QLabel;
|
||||
labelYaw->setText("Yaw");
|
||||
labelYaw->setToolTip(yawTip);
|
||||
layoutWindow->addWidget(labelYaw, 2, 0);
|
||||
|
||||
_lineYaw = new QLineEdit;
|
||||
_lineYaw->setText(QString::number(glm::degrees(glm::yaw(q))));
|
||||
_lineYaw->setToolTip(yawTip);
|
||||
QDoubleValidator* validatorYaw = new QDoubleValidator(-180.0, 180.0, 15, this);
|
||||
validatorYaw->setNotation(QDoubleValidator::StandardNotation);
|
||||
_lineYaw->setValidator(validatorYaw);
|
||||
layoutWindow->addWidget(_lineYaw, 2, 1);
|
||||
|
||||
QLabel* range = new QLabel("Range [-180, 180] in degrees");
|
||||
layoutWindow->addWidget(range, 2, 2);
|
||||
}
|
||||
|
||||
_settingsWidget = new SettingsWidget(orientation, this);
|
||||
layout->addWidget(_settingsWidget);
|
||||
connect(
|
||||
_displayWidget, &DisplayWindowUnion::nWindowsChanged,
|
||||
_settingsWidget, &SettingsWidget::nWindowsDisplayedChanged
|
||||
);
|
||||
|
||||
connect(
|
||||
_displayWidget, &DisplayWindowUnion::nWindowsChanged,
|
||||
this, &SgctEdit::nWindowsDisplayedChanged
|
||||
);
|
||||
|
||||
if (_settingsWidget->firstWindowGraphicsSelection()) {
|
||||
connect(
|
||||
_settingsWidget->firstWindowGraphicsSelection(),
|
||||
&QComboBox::currentTextChanged,
|
||||
this,
|
||||
&SgctEdit::firstWindowGraphicsSelectionChanged
|
||||
);
|
||||
}
|
||||
if (_settingsWidget->showUiOnFirstWindowCheckbox()) {
|
||||
connect(
|
||||
_settingsWidget->showUiOnFirstWindowCheckbox(),
|
||||
&QCheckBox::clicked,
|
||||
this,
|
||||
&SgctEdit::firstWindowGuiOptionClicked
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Button box
|
||||
{
|
||||
QHBoxLayout* layoutButtonBox = new QHBoxLayout;
|
||||
layoutButtonBox->addStretch(1);
|
||||
@@ -333,35 +234,76 @@ void SgctEdit::createWidgets(const std::vector<QRect>& monitorSizes,
|
||||
bottomBorder->setFrameShape(QFrame::HLine);
|
||||
layout->addWidget(bottomBorder);
|
||||
|
||||
_cancelButton = new QPushButton("Cancel");
|
||||
_cancelButton->setToolTip("Cancel changes");
|
||||
_cancelButton->setFocusPolicy(Qt::NoFocus);
|
||||
connect(_cancelButton, &QPushButton::released, this, &SgctEdit::reject);
|
||||
layoutButtonBox->addWidget(_cancelButton);
|
||||
QPushButton* cancelButton = new QPushButton("Cancel");
|
||||
cancelButton->setToolTip("Cancel changes");
|
||||
cancelButton->setFocusPolicy(Qt::NoFocus);
|
||||
connect(cancelButton, &QPushButton::released, this, &SgctEdit::reject);
|
||||
layoutButtonBox->addWidget(cancelButton);
|
||||
|
||||
_saveButton = _didImportValues ?
|
||||
new QPushButton("Save") : new QPushButton("Save As");
|
||||
_saveButton->setToolTip("Save configuration changes");
|
||||
_saveButton->setFocusPolicy(Qt::NoFocus);
|
||||
connect(_saveButton, &QPushButton::released, this, &SgctEdit::save);
|
||||
layoutButtonBox->addWidget(_saveButton);
|
||||
QPushButton* saveButton = new QPushButton("Save");
|
||||
saveButton->setToolTip("Save configuration changes");
|
||||
saveButton->setFocusPolicy(Qt::NoFocus);
|
||||
connect(saveButton, &QPushButton::released, this, &SgctEdit::saveCluster);
|
||||
layoutButtonBox->addWidget(saveButton);
|
||||
|
||||
_applyButton = new QPushButton("Apply Without Saving");
|
||||
_applyButton->setToolTip("Apply configuration changes without saving to file");
|
||||
_applyButton->setFocusPolicy(Qt::NoFocus);
|
||||
connect(_applyButton, &QPushButton::released, this, &SgctEdit::apply);
|
||||
layoutButtonBox->addWidget(_applyButton);
|
||||
QPushButton* applyButton = new QPushButton("Apply Without Saving");
|
||||
applyButton->setToolTip("Apply configuration changes without saving to file");
|
||||
applyButton->setFocusPolicy(Qt::NoFocus);
|
||||
connect(applyButton, &QPushButton::released, this, &SgctEdit::apply);
|
||||
layoutButtonBox->addWidget(applyButton);
|
||||
|
||||
layout->addLayout(layoutButtonBox);
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path SgctEdit::saveFilename() const {
|
||||
return _saveTarget;
|
||||
return _configurationFilename;
|
||||
}
|
||||
|
||||
void SgctEdit::save() {
|
||||
generateConfiguration();
|
||||
void SgctEdit::saveCluster() {
|
||||
//
|
||||
// Generate configuration
|
||||
// Reconstitute the quaternion if the provided values are not 0
|
||||
const float pitch = glm::radians(_linePitch->text().toFloat());
|
||||
const float yaw = glm::radians(_lineYaw->text().toFloat());
|
||||
const float roll = glm::radians(_lineRoll->text().toFloat());
|
||||
if (pitch != 0.f && yaw != 0.f && roll != 0.f) {
|
||||
glm::quat q = glm::quat(glm::vec3(pitch, yaw, roll));
|
||||
_cluster.scene = {
|
||||
.orientation = sgct::quat(q.x, q.y, q.z, q.w)
|
||||
};
|
||||
}
|
||||
|
||||
ghoul_assert(!_cluster.nodes.empty(), "There must be at least one node");
|
||||
sgct::config::Node& node = _cluster.nodes.back();
|
||||
|
||||
//
|
||||
// Generate vsync setup
|
||||
if (_checkBoxVsync->isChecked()) {
|
||||
if (!_cluster.settings || !_cluster.settings->display ||
|
||||
!_cluster.settings->display->swapInterval)
|
||||
{
|
||||
_cluster.settings = sgct::config::Settings{
|
||||
.display = sgct::config::Settings::Display {
|
||||
.swapInterval = 1
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
_cluster.settings = std::nullopt;
|
||||
}
|
||||
|
||||
_displayWidget->applyWindowSettings(node.windows);
|
||||
|
||||
//
|
||||
// Generate individual window settings
|
||||
for (size_t i = 0; i < node.windows.size(); i++) {
|
||||
// First apply default settings to each window
|
||||
node.windows[i].id = static_cast<int8_t>(i);
|
||||
node.windows[i].viewports.back().isTracked = true;
|
||||
}
|
||||
|
||||
if (hasWindowIssues(_cluster)) {
|
||||
const int ret = QMessageBox::warning(
|
||||
this,
|
||||
@@ -378,11 +320,7 @@ void SgctEdit::save() {
|
||||
}
|
||||
}
|
||||
|
||||
if (_didImportValues) {
|
||||
_saveTarget = _configurationFilename;
|
||||
accept();
|
||||
}
|
||||
else {
|
||||
if (_configurationFilename.empty()) {
|
||||
const QString fileName = QFileDialog::getSaveFileName(
|
||||
this,
|
||||
"Save Window Configuration File",
|
||||
@@ -392,201 +330,42 @@ void SgctEdit::save() {
|
||||
#ifdef __linux__
|
||||
// Linux in Qt5 and Qt6 crashes when trying to access the native dialog here
|
||||
, QFileDialog::DontUseNativeDialog
|
||||
#endif
|
||||
#endif // __linux__
|
||||
);
|
||||
if (!fileName.isEmpty()) {
|
||||
_saveTarget = fileName.toStdString();
|
||||
accept();
|
||||
if (fileName.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_configurationFilename = fileName.toStdString();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Save the cluster configuration
|
||||
ghoul_assert(!_configurationFilename.empty(), "Filename must not be empty");
|
||||
std::ofstream outFile;
|
||||
outFile.open(_configurationFilename, std::ofstream::out);
|
||||
if (outFile.good()) {
|
||||
sgct::config::GeneratorVersion genEntry = VersionMin;
|
||||
outFile << sgct::serializeConfig(_cluster, genEntry);
|
||||
accept();
|
||||
}
|
||||
else {
|
||||
QMessageBox::critical(
|
||||
this,
|
||||
"Exception",
|
||||
QString::fromStdString(std::format(
|
||||
"Error writing data to file '{}'", _configurationFilename
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void SgctEdit::apply() {
|
||||
generateConfiguration();
|
||||
if (hasWindowIssues(_cluster)) {
|
||||
const int ret = QMessageBox::warning(
|
||||
this,
|
||||
"Window Sizes Incompatible",
|
||||
"Window sizes for multiple windows have to be strictly ordered, meaning that "
|
||||
"the size of window 1 has to be bigger in each dimension than window 2, "
|
||||
"window 2 has to be bigger than window 3 (if it exists), and window 3 has to "
|
||||
"be bigger than window 4.\nOtherwise, rendering errors might occur.\n\nAre "
|
||||
"you sure you want to continue?",
|
||||
QMessageBox::Yes | QMessageBox::No
|
||||
);
|
||||
if (ret == QMessageBox::No) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string userCfgTempDir = _userConfigPath.string();
|
||||
if (userCfgTempDir.back() != '/') {
|
||||
userCfgTempDir += '/';
|
||||
}
|
||||
userCfgTempDir += "temp";
|
||||
if (!std::filesystem::is_directory(absPath(userCfgTempDir))) {
|
||||
std::filesystem::create_directories(absPath(userCfgTempDir));
|
||||
}
|
||||
_saveTarget = userCfgTempDir + "/apply-without-saving.json";
|
||||
accept();
|
||||
}
|
||||
|
||||
void SgctEdit::generateConfiguration() {
|
||||
_cluster.scene = sgct::config::Scene();
|
||||
_cluster.scene->orientation = _settingsWidget->orientation();
|
||||
if (_cluster.nodes.empty()) {
|
||||
_cluster.nodes.emplace_back();
|
||||
}
|
||||
sgct::config::Node& node = _cluster.nodes.back();
|
||||
|
||||
generateConfigSetupVsync();
|
||||
generateConfigUsers();
|
||||
generateConfigAddresses(node);
|
||||
generateConfigResizeWindowsAccordingToSelected(node);
|
||||
generateConfigIndividualWindowSettings(node);
|
||||
}
|
||||
|
||||
void SgctEdit::generateConfigSetupVsync() {
|
||||
if (_settingsWidget->vsync()) {
|
||||
if (!_cluster.settings || !_cluster.settings->display ||
|
||||
!_cluster.settings->display->swapInterval)
|
||||
{
|
||||
sgct::config::Settings::Display display;
|
||||
display.swapInterval = 1;
|
||||
sgct::config::Settings settings;
|
||||
settings.display = display;
|
||||
_cluster.settings = settings;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_cluster.settings = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void SgctEdit::generateConfigUsers() {
|
||||
if (!_didImportValues) {
|
||||
sgct::config::User user;
|
||||
user.eyeSeparation = 0.065f;
|
||||
user.position = { 0.f, 0.f, 0.f };
|
||||
_cluster.users = { user };
|
||||
}
|
||||
}
|
||||
|
||||
void SgctEdit::generateConfigAddresses(sgct::config::Node& node) {
|
||||
if (!_didImportValues) {
|
||||
_cluster.masterAddress = "localhost";
|
||||
node.address = "localhost";
|
||||
node.port = 20401;
|
||||
}
|
||||
}
|
||||
|
||||
void SgctEdit::generateConfigResizeWindowsAccordingToSelected(sgct::config::Node& node) {
|
||||
std::vector<WindowControl*> windowControls = _displayWidget->activeWindowControls();
|
||||
for (size_t wIdx = 0; wIdx < windowControls.size(); ++wIdx) {
|
||||
if (node.windows.size() <= wIdx) {
|
||||
node.windows.emplace_back();
|
||||
}
|
||||
if (windowControls[wIdx]) {
|
||||
windowControls[wIdx]->generateWindowInformation(node.windows[wIdx]);
|
||||
}
|
||||
}
|
||||
while (node.windows.size() > windowControls.size()) {
|
||||
node.windows.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
void SgctEdit::generateConfigIndividualWindowSettings(sgct::config::Node& node) {
|
||||
for (size_t i = 0; i < node.windows.size(); i++) {
|
||||
// First apply default settings to each window...
|
||||
node.windows[i].id = static_cast<int>(i);
|
||||
node.windows[i].draw2D = true;
|
||||
node.windows[i].draw3D = true;
|
||||
node.windows[i].viewports.back().isTracked = true;
|
||||
deleteFromTags(node.windows[i]);
|
||||
// If "show UI on first window" option is enabled, then modify the settings
|
||||
// depending on if this is the first window or not
|
||||
if (_settingsWidget->showUiOnFirstWindow()) {
|
||||
if (i == 0) {
|
||||
node.windows[i].tags.emplace_back("GUI");
|
||||
const int selectedGraphics =
|
||||
_settingsWidget->graphicsSelectionForShowUiOnFirstWindow();
|
||||
|
||||
if (selectedGraphics == 0) {
|
||||
node.windows[i].viewports.back().isTracked = false;
|
||||
node.windows[i].tags.emplace_back("GUI_No_Render");
|
||||
}
|
||||
else if (selectedGraphics > 0 &&
|
||||
selectedGraphics <= static_cast<int>(node.windows.size()))
|
||||
{
|
||||
node.windows[i].tags.emplace_back(
|
||||
"GUI_Render_Win" + std::to_string(selectedGraphics)
|
||||
);
|
||||
// Set first window viewport to mirror the selected window's viewport
|
||||
node.windows[i].viewports =
|
||||
node.windows[(selectedGraphics - 1)].viewports;
|
||||
}
|
||||
node.windows[i].draw2D = true;
|
||||
node.windows[i].draw3D = (selectedGraphics > 0);
|
||||
}
|
||||
else {
|
||||
node.windows[i].draw2D = false;
|
||||
node.windows[i].draw3D = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SgctEdit::deleteFromTags(sgct::config::Window& window) {
|
||||
constexpr std::array<std::string_view, 6> Tags = {
|
||||
"GUI",
|
||||
"GUI_No_Render",
|
||||
"GUI_Render_Win1",
|
||||
"GUI_Render_Win2",
|
||||
"GUI_Render_Win3",
|
||||
"GUI_Render_Win4"
|
||||
};
|
||||
for (const std::string_view tag : Tags) {
|
||||
window.tags.erase(
|
||||
std::remove(window.tags.begin(), window.tags.end(), tag),
|
||||
window.tags.end()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sgct::config::Cluster SgctEdit::cluster() const {
|
||||
return _cluster;
|
||||
}
|
||||
|
||||
void SgctEdit::firstWindowGraphicsSelectionChanged(const QString&) {
|
||||
if (!_settingsWidget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_settingsWidget->showUiOnFirstWindow()) {
|
||||
const int newSetting = _settingsWidget->graphicsSelectionForShowUiOnFirstWindow();
|
||||
_displayWidget->activeWindowControls()[0]->setVisibilityOfProjectionGui(
|
||||
newSetting == 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void SgctEdit::nWindowsDisplayedChanged(int newCount) {
|
||||
if (!_settingsWidget) {
|
||||
return;
|
||||
}
|
||||
if (newCount == 1) {
|
||||
_displayWidget->activeWindowControls()[0]->setVisibilityOfProjectionGui(true);
|
||||
}
|
||||
else {
|
||||
firstWindowGraphicsSelectionChanged("");
|
||||
}
|
||||
}
|
||||
|
||||
void SgctEdit::firstWindowGuiOptionClicked(bool checked) {
|
||||
if (checked) {
|
||||
firstWindowGraphicsSelectionChanged("");
|
||||
}
|
||||
else {
|
||||
_displayWidget->activeWindowControls()[0]->setVisibilityOfProjectionGui(true);
|
||||
std::filesystem::path userTmp = _userConfigPath / "temp";
|
||||
if (!std::filesystem::is_directory(absPath(userTmp))) {
|
||||
std::filesystem::create_directories(absPath(userTmp));
|
||||
}
|
||||
_configurationFilename = (userTmp / "apply-without-saving.json").string();
|
||||
saveCluster();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include <ghoul/format.h>
|
||||
#include <ghoul/misc/assert.h>
|
||||
#include "sgctedit/displaywindowunion.h"
|
||||
#include "sgctedit/monitorbox.h"
|
||||
#include "windowcolors.h"
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QGridLayout>
|
||||
@@ -39,34 +39,35 @@
|
||||
#include <numbers>
|
||||
|
||||
namespace {
|
||||
std::array<std::string, 4> MonitorNames = {
|
||||
"Primary", "Secondary", "Tertiary", "Quaternary"
|
||||
std::array<std::pair<int, std::string>, 10> Quality = {
|
||||
std::pair{ 256, "Low (256)" },
|
||||
std::pair{ 512, "Medium (512)" },
|
||||
std::pair{ 1024, "High (1K)" },
|
||||
std::pair{ 1536, "1.5K (1536)" },
|
||||
std::pair{ 2048, "2K (2048)" },
|
||||
std::pair{ 4096, "4K (4096)" },
|
||||
std::pair{ 8192, "8K (8192)" },
|
||||
std::pair{ 16384, "16K (16384)" },
|
||||
std::pair{ 32768, "32K (32768)" },
|
||||
std::pair{ 65536, "64K (65536)" }
|
||||
};
|
||||
|
||||
constexpr int nQualityTypes = 10;
|
||||
|
||||
const QList<QString> QualityTypes = {
|
||||
"Low (256)", "Medium (512)", "High (1K)", "1.5K (1536)", "2K (2048)", "4K (4096)",
|
||||
"8K (8192)", "16K (16384)", "32K (32768)", "64K (65536)"
|
||||
constexpr std::array<QRect, 4> DefaultWindowSizes = {
|
||||
QRect(50, 50, 1280, 720),
|
||||
QRect(150, 150, 1280, 720),
|
||||
QRect(50, 50, 1280, 720),
|
||||
QRect(150, 150, 1280, 720)
|
||||
};
|
||||
|
||||
constexpr std::array<int, nQualityTypes> QualityValues = {
|
||||
256, 512, 1024, 1536, 2048, 4096, 8192, 16384, 32768, 65536
|
||||
enum class ProjectionIndices {
|
||||
Planar = 0,
|
||||
Fisheye,
|
||||
SphericalMirror,
|
||||
Cylindrical,
|
||||
Equirectangular
|
||||
};
|
||||
|
||||
const QList<QString> ProjectionTypes = {
|
||||
"Planar Projection", "Fisheye", "Spherical Mirror Projection",
|
||||
"Cylindrical Projection", "Equirectangular Projection"
|
||||
};
|
||||
|
||||
constexpr std::array<QRectF, 4> DefaultWindowSizes = {
|
||||
QRectF(50.f, 50.f, 1280.f, 720.f),
|
||||
QRectF(150.f, 150.f, 1280.f, 720.f),
|
||||
QRectF(50.f, 50.f, 1280.f, 720.f),
|
||||
QRectF(150.f, 150.f, 1280.f, 720.f)
|
||||
};
|
||||
|
||||
constexpr int LineEditWidthFixedWindowSize = 64;
|
||||
constexpr int LineEditWidthFixedWindowSize = 95;
|
||||
constexpr float DefaultFovLongEdge = 80.f;
|
||||
constexpr float DefaultFovShortEdge = 50.534f;
|
||||
constexpr float DefaultHeightOffset = 0.f;
|
||||
@@ -74,21 +75,46 @@ namespace {
|
||||
constexpr double FovEpsilon = 0.00001;
|
||||
|
||||
QList<QString> monitorNames(const std::vector<QRect>& resolutions) {
|
||||
std::array<std::string, 4> MonitorNames = {
|
||||
"Primary", "Secondary", "Tertiary", "Quaternary"
|
||||
};
|
||||
|
||||
QList<QString> monitorNames;
|
||||
for (size_t i = 0; i < resolutions.size(); i++) {
|
||||
const std::string name = i < 4 ? MonitorNames[i] : std::format("{}th", i);
|
||||
const std::string fullName = std::format(
|
||||
"{} ({}x{})",
|
||||
MonitorNames[i], resolutions[i].width(), resolutions[i].height()
|
||||
name,
|
||||
resolutions[i].width(),
|
||||
resolutions[i].height()
|
||||
);
|
||||
monitorNames.push_back(QString::fromStdString(fullName));
|
||||
}
|
||||
return monitorNames;
|
||||
}
|
||||
|
||||
QStringList qualityList() {
|
||||
QStringList res;
|
||||
for (const std::pair<int, std::string>& p : Quality) {
|
||||
res.append(QString::fromStdString(p.second));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int indexForQuality(int quality) {
|
||||
auto it = std::find_if(
|
||||
Quality.cbegin(),
|
||||
Quality.cend(),
|
||||
[quality](const std::pair<int, std::string>& p) { return p.first == quality; }
|
||||
);
|
||||
ghoul_assert(it != Quality.cend(), "Combobox has too many values");
|
||||
return std::distance(Quality.cbegin(), it);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WindowControl::WindowControl(int monitorIndex, int windowIndex,
|
||||
const std::vector<QRect>& monitorDims,
|
||||
const QColor& winColor, bool resetToDefault, QWidget* parent)
|
||||
const std::vector<QRect>& monitorDims, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, _monitorIndexDefault(monitorIndex)
|
||||
, _windowIndex(windowIndex)
|
||||
@@ -96,13 +122,6 @@ WindowControl::WindowControl(int monitorIndex, int windowIndex,
|
||||
, _lockIcon(":/images/outline_locked.png")
|
||||
, _unlockIcon(":/images/outline_unlocked.png")
|
||||
{
|
||||
createWidgets(winColor);
|
||||
if (resetToDefault) {
|
||||
resetToDefaults();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowControl::createWidgets(const QColor& windowColor) {
|
||||
// Col 0 Col 1 Col 2 Col 3 Col 4 Col 5 Col 6 Col 7
|
||||
// *----------*----------*-------*----------*-------*--------*-------*-------*
|
||||
// | Window {n} | R0
|
||||
@@ -110,8 +129,8 @@ void WindowControl::createWidgets(const QColor& windowColor) {
|
||||
// | Monitor * DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD> | R2
|
||||
// | Size * [xxxxxx] * x * [yyyyyy] * px * <lock> * < Set to | R3
|
||||
// | Offset * [xxxxxx] * , * [yyyyyy] * px * * Fullscreen> | R4
|
||||
// | [] Window Decoration | R5
|
||||
// | [] UI only in this window | R6
|
||||
// | [] Window Decoration [] Render 2D | R5
|
||||
// | [] Spout Output [] Render 3D | R6
|
||||
// | ~~~~~~~~~~~~~~~~~~~~~~~~~Projection components~~~~~~~~~~~~~~~~~~~~~~~~~ | R7
|
||||
// *----------*----------*-------*----------*-------*--------*-------*-------*
|
||||
|
||||
@@ -120,13 +139,19 @@ void WindowControl::createWidgets(const QColor& windowColor) {
|
||||
layout->setContentsMargins(margins.left(), 0, margins.right(), 0);
|
||||
layout->setColumnStretch(6, 1);
|
||||
layout->setRowStretch(8, 1);
|
||||
|
||||
|
||||
//
|
||||
// Window title
|
||||
_windowNumber = new QLabel("Window " + QString::number(_windowIndex + 1));
|
||||
QColor windowColor = colorForWindow(_windowIndex);
|
||||
_windowNumber->setStyleSheet(QString::fromStdString(std::format(
|
||||
"QLabel {{ color : #{:02x}{:02x}{:02x}; }}",
|
||||
windowColor.red(), windowColor.green(), windowColor.blue()
|
||||
)));
|
||||
layout->addWidget(_windowNumber, 0, 0, 1, 8, Qt::AlignCenter);
|
||||
|
||||
//
|
||||
// Name
|
||||
{
|
||||
const QString tip = "The name for the window (displayed in title bar)";
|
||||
|
||||
@@ -140,6 +165,8 @@ void WindowControl::createWidgets(const QColor& windowColor) {
|
||||
}
|
||||
const QString tip = "The monitor where this window is located";
|
||||
|
||||
//
|
||||
// Monitor
|
||||
_monitor = new QComboBox;
|
||||
_monitor->addItems(monitorNames(_monitorResolutions));
|
||||
_monitor->setCurrentIndex(_monitorIndexDefault);
|
||||
@@ -155,12 +182,18 @@ void WindowControl::createWidgets(const QColor& windowColor) {
|
||||
}
|
||||
);
|
||||
if (_monitorResolutions.size() > 1) {
|
||||
// We only add the monitor dropdown menu if we are running on a system with
|
||||
// multiple monitors. We are still creating the combobox to guard against
|
||||
// potential nullpointer accesses elsewhere in the code
|
||||
QLabel* labelLocation = new QLabel("Monitor");
|
||||
labelLocation->setToolTip(tip);
|
||||
layout->addWidget(labelLocation, 2, 0);
|
||||
|
||||
layout->addWidget(_monitor, 2, 1, 1, 7);
|
||||
}
|
||||
|
||||
//
|
||||
// Window size
|
||||
{
|
||||
QLabel* size = new QLabel("Size");
|
||||
size->setToolTip("The window's width & height in pixels");
|
||||
@@ -213,8 +246,11 @@ void WindowControl::createWidgets(const QColor& windowColor) {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Position
|
||||
{
|
||||
QLabel* offset = new QLabel("Offset");
|
||||
QLabel* offset = new QLabel("Position");
|
||||
offset->setToolTip(
|
||||
"The x,y location of the window's upper left corner from monitor's "
|
||||
"upper-left corner origin (pixels)"
|
||||
@@ -258,9 +294,10 @@ void WindowControl::createWidgets(const QColor& windowColor) {
|
||||
unit->setSizePolicy(QSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum));
|
||||
layout->addWidget(unit, 4, 4, Qt::AlignLeft);
|
||||
}
|
||||
{
|
||||
QBoxLayout* holderLayout = new QHBoxLayout;
|
||||
|
||||
//
|
||||
// Fullscreen button
|
||||
{
|
||||
QPushButton* setFullscreen = new QPushButton("Set Window\nto Fullscreen");
|
||||
setFullscreen->setToolTip(
|
||||
"If enabled, the window will be created in an exclusive fullscreen mode. The "
|
||||
@@ -272,10 +309,7 @@ void WindowControl::createWidgets(const QColor& windowColor) {
|
||||
QSizePolicy::MinimumExpanding,
|
||||
QSizePolicy::MinimumExpanding
|
||||
);
|
||||
holderLayout->addStretch();
|
||||
holderLayout->addWidget(setFullscreen);
|
||||
holderLayout->addStretch();
|
||||
layout->addLayout(holderLayout, 3, 6, 2, 2);
|
||||
layout->addWidget(setFullscreen, 3, 6, 2, 2);
|
||||
connect(
|
||||
setFullscreen, &QPushButton::released,
|
||||
this, &WindowControl::onFullscreenClicked
|
||||
@@ -287,8 +321,11 @@ void WindowControl::createWidgets(const QColor& windowColor) {
|
||||
"If disabled, the window will not have a border frame or title bar, and no\n "
|
||||
"controls for minimizing/maximizing, resizing, or closing the window"
|
||||
);
|
||||
layout->addWidget(_windowDecoration, 5, 0, 1, 8);
|
||||
layout->addWidget(_windowDecoration, 5, 0, 1, 4);
|
||||
}
|
||||
|
||||
//
|
||||
// Spout output
|
||||
{
|
||||
_spoutOutput = new QCheckBox("Spout Output");
|
||||
_spoutOutput->setToolTip(
|
||||
@@ -297,35 +334,51 @@ void WindowControl::createWidgets(const QColor& windowColor) {
|
||||
"images available to other real-time applications on the same machine for "
|
||||
"further processing"
|
||||
);
|
||||
layout->addWidget(_spoutOutput);
|
||||
layout->addWidget(_spoutOutput, 6, 0, 1, 4);
|
||||
}
|
||||
|
||||
//
|
||||
// Render 2D & 3D
|
||||
{
|
||||
QFrame* projectionGroup = new QFrame;
|
||||
projectionGroup->setVisible(true);
|
||||
projectionGroup->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
|
||||
_render2D = new QCheckBox("Render GUI and Overlays");
|
||||
_render2D->setToolTip(
|
||||
"Determines whether any overlays should be\nrendered in this window. "
|
||||
"Overlays in this case are\nthe user interface, dashboard information, and "
|
||||
"other\nelements that are only useful for a pilot."
|
||||
);
|
||||
layout->addWidget(_render2D, 5, 4, 1, 4);
|
||||
|
||||
_render3D = new QCheckBox("Render Scene");
|
||||
_render3D->setToolTip(
|
||||
"Determines whether the main 3D scene should be\nrendered in this window, "
|
||||
"like 3D models, the planets, stars, etc."
|
||||
);
|
||||
connect(
|
||||
_render3D, &QCheckBox::clicked,
|
||||
[this](bool checked) {
|
||||
_projectionGroup->setVisible(checked);
|
||||
}
|
||||
);
|
||||
layout->addWidget(_render3D, 6, 4, 1, 4);
|
||||
}
|
||||
|
||||
//
|
||||
// Projections
|
||||
{
|
||||
_projectionGroup = new QFrame;
|
||||
_projectionGroup->setVisible(true);
|
||||
_projectionGroup->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
|
||||
|
||||
//
|
||||
// Projection combobox
|
||||
QBoxLayout* projectionLayout = new QVBoxLayout(projectionGroup);
|
||||
QBoxLayout* projectionLayout = new QVBoxLayout(_projectionGroup);
|
||||
projectionLayout->setContentsMargins(0, 0, 0, 0);
|
||||
projectionLayout->setSpacing(0);
|
||||
_projectionLabel = new QLabel(
|
||||
"Projection information not shown while user interface is set to display "
|
||||
"on the first window only"
|
||||
);
|
||||
_projectionLabel->setWordWrap(true);
|
||||
_projectionLabel->setObjectName("notice");
|
||||
_projectionLabel->setVisible(false);
|
||||
_projectionLabel->setEnabled(false);
|
||||
projectionLayout->addWidget(_projectionLabel);
|
||||
|
||||
_projectionType = new QComboBox;
|
||||
_projectionType->addItems({
|
||||
ProjectionTypes[0],
|
||||
ProjectionTypes[1],
|
||||
ProjectionTypes[2],
|
||||
ProjectionTypes[3],
|
||||
ProjectionTypes[4]
|
||||
"Planar Projection", "Fisheye", "Spherical Mirror Projection",
|
||||
"Cylindrical Projection", "Equirectangular Projection"
|
||||
});
|
||||
_projectionType->setToolTip("Select from the supported window projection types");
|
||||
_projectionType->setCurrentIndex(0);
|
||||
@@ -353,8 +406,10 @@ void WindowControl::createWidgets(const QColor& windowColor) {
|
||||
// We need to trigger this once to ensure that all of the defaults are correct
|
||||
onProjectionChanged(0);
|
||||
|
||||
layout->addWidget(projectionGroup, 7, 0, 1, 8);
|
||||
layout->addWidget(_projectionGroup, 7, 0, 1, 8);
|
||||
}
|
||||
|
||||
resetToDefaults();
|
||||
}
|
||||
|
||||
QWidget* WindowControl::createPlanarWidget() {
|
||||
@@ -373,7 +428,7 @@ QWidget* WindowControl::createPlanarWidget() {
|
||||
"This projection type is the 'regular' projection with a horizontal and a "
|
||||
"vertical field of view, given in degrees. The wider the field of view, the "
|
||||
"more content is shown at the same time, but everything becomes smaller. Very "
|
||||
"large values will introduce distorions on the corners"
|
||||
"large values will introduce distortions on the corners."
|
||||
);
|
||||
_planar.labelInfo->setObjectName("info");
|
||||
_planar.labelInfo->setWordWrap(true);
|
||||
@@ -449,7 +504,7 @@ QWidget* WindowControl::createFisheyeWidget() {
|
||||
"This projection provides a rendering in a format that is suitable for "
|
||||
"planetariums and other immersive environments. A field-of-view of 180 degrees "
|
||||
"is presented as a circular image in the center of the screen. For this "
|
||||
"projection a square window is suggested, but not necessary"
|
||||
"projection a square window is suggested, but not necessary."
|
||||
);
|
||||
_fisheye.labelInfo->setObjectName("info");
|
||||
_fisheye.labelInfo->setWordWrap(true);
|
||||
@@ -463,7 +518,7 @@ QWidget* WindowControl::createFisheyeWidget() {
|
||||
layout->addWidget(_fisheye.labelQuality, 1, 0);
|
||||
|
||||
_fisheye.quality = new QComboBox;
|
||||
_fisheye.quality->addItems(QualityTypes);
|
||||
_fisheye.quality->addItems(qualityList());
|
||||
_fisheye.quality->setToolTip(qualityTip);
|
||||
_fisheye.quality->setCurrentIndex(2);
|
||||
layout->addWidget(_fisheye.quality, 1, 1);
|
||||
@@ -485,7 +540,7 @@ QWidget* WindowControl::createSphericalMirrorWidget() {
|
||||
"This projection is rendering a image suite for use with a spherical mirror "
|
||||
"projection as described by Paul Bourke (http://paulbourke.net/dome/mirrordome/) "
|
||||
"and which is a low-cost yet effective way to provide content for a sphericalal "
|
||||
"display surface using a regular projector"
|
||||
"display surface using a regular projector."
|
||||
);
|
||||
_sphericalMirror.labelInfo->setObjectName("info");
|
||||
_sphericalMirror.labelInfo->setWordWrap(true);
|
||||
@@ -499,7 +554,7 @@ QWidget* WindowControl::createSphericalMirrorWidget() {
|
||||
layout->addWidget(_sphericalMirror.labelQuality, 1, 0);
|
||||
|
||||
_sphericalMirror.quality = new QComboBox;
|
||||
_sphericalMirror.quality->addItems(QualityTypes);
|
||||
_sphericalMirror.quality->addItems(qualityList());
|
||||
_sphericalMirror.quality->setToolTip(qualityTip);
|
||||
_sphericalMirror.quality->setCurrentIndex(2);
|
||||
layout->addWidget(_sphericalMirror.quality, 1, 1);
|
||||
@@ -522,7 +577,7 @@ QWidget* WindowControl::createCylindricalWidget() {
|
||||
"This projection type provides a cylindrical rendering that covers 360 degrees "
|
||||
"around the camera, which can be useful in immersive environments that are not "
|
||||
"spherical, but where, for example, all walls of a room are covered with "
|
||||
"projectors"
|
||||
"projectors."
|
||||
);
|
||||
_cylindrical.labelInfo->setObjectName("info");
|
||||
_cylindrical.labelInfo->setWordWrap(true);
|
||||
@@ -536,7 +591,7 @@ QWidget* WindowControl::createCylindricalWidget() {
|
||||
layout->addWidget(_cylindrical.labelQuality, 1, 0);
|
||||
|
||||
_cylindrical.quality = new QComboBox;
|
||||
_cylindrical.quality->addItems(QualityTypes);
|
||||
_cylindrical.quality->addItems(qualityList());
|
||||
_cylindrical.quality->setToolTip(qualityTip);
|
||||
_cylindrical.quality->setCurrentIndex(2);
|
||||
layout->addWidget(_cylindrical.quality, 1, 1);
|
||||
@@ -575,7 +630,7 @@ QWidget* WindowControl::createEquirectangularWidget() {
|
||||
"This projection provides the rendering as an image in equirectangular "
|
||||
"projection, which is a common display type for 360 surround video. When "
|
||||
"uploading a video in equirectangular projection to YouTube, for example, it "
|
||||
"will use it as a 360 video"
|
||||
"will use it as a 360 video."
|
||||
);
|
||||
_equirectangular.labelInfo->setObjectName("info");
|
||||
_equirectangular.labelInfo->setWordWrap(true);
|
||||
@@ -589,7 +644,7 @@ QWidget* WindowControl::createEquirectangularWidget() {
|
||||
layout->addWidget(_equirectangular.labelQuality, 1, 0);
|
||||
|
||||
_equirectangular.quality = new QComboBox;
|
||||
_equirectangular.quality->addItems(QualityTypes);
|
||||
_equirectangular.quality->addItems(qualityList());
|
||||
_equirectangular.quality->setToolTip(qualityTip);
|
||||
_equirectangular.quality->setCurrentIndex(2);
|
||||
layout->addWidget(_equirectangular.quality, 1, 1);
|
||||
@@ -622,6 +677,8 @@ void WindowControl::resetToDefaults() {
|
||||
}
|
||||
_windowDecoration->setChecked(true);
|
||||
_spoutOutput->setChecked(false);
|
||||
_render2D->setChecked(true);
|
||||
_render3D->setChecked(true);
|
||||
_projectionType->setCurrentIndex(static_cast<int>(ProjectionIndices::Planar));
|
||||
_planar.fovV->setValue(DefaultFovLongEdge);
|
||||
_planar.fovV->setValue(DefaultFovShortEdge);
|
||||
@@ -633,12 +690,12 @@ void WindowControl::resetToDefaults() {
|
||||
emit windowChanged(_monitorIndexDefault, _windowIndex, _windowDimensions);
|
||||
}
|
||||
|
||||
void WindowControl::setDimensions(QRectF newDims) {
|
||||
_windowDimensions = newDims;
|
||||
_sizeX->setValue(_windowDimensions.width());
|
||||
_sizeY->setValue(_windowDimensions.height());
|
||||
_offsetX->setValue(_windowDimensions.x());
|
||||
_offsetY->setValue(_windowDimensions.y());
|
||||
void WindowControl::setDimensions(int x, int y, int width, int height) {
|
||||
_windowDimensions = QRect(x, y, width, height);
|
||||
_sizeX->setValue(width);
|
||||
_sizeY->setValue(height);
|
||||
_offsetX->setValue(x);
|
||||
_offsetY->setValue(y);
|
||||
}
|
||||
|
||||
void WindowControl::setMonitorSelection(int monitorIndex) {
|
||||
@@ -661,40 +718,69 @@ void WindowControl::setSpoutOutputState(bool shouldSpoutOutput) {
|
||||
_spoutOutput->setChecked(shouldSpoutOutput);
|
||||
}
|
||||
|
||||
sgct::config::Projections WindowControl::generateProjectionInformation() const {
|
||||
const ProjectionIndices type =
|
||||
static_cast<WindowControl::ProjectionIndices>(_projectionType->currentIndex());
|
||||
void WindowControl::setRender2D(bool state) {
|
||||
_render2D->setChecked(state);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
void WindowControl::setRender3D(bool state) {
|
||||
_render3D->setChecked(state);
|
||||
}
|
||||
|
||||
void WindowControl::generateWindowInformation(sgct::config::Window& window) const {
|
||||
window.size = { _sizeX->value(), _sizeY->value() };
|
||||
window.monitor = _monitor->currentIndex();
|
||||
const QRect resolution = _monitorResolutions[_monitor->currentIndex()];
|
||||
window.pos = sgct::ivec2(
|
||||
resolution.x() + _offsetX->value(),
|
||||
resolution.y() + _offsetY->value()
|
||||
);
|
||||
window.draw2D = _render2D->isChecked();
|
||||
window.draw3D = _render3D->isChecked();
|
||||
window.isDecorated = _windowDecoration->isChecked();
|
||||
if (_spoutOutput->isChecked()) {
|
||||
window.spout = sgct::config::Window::Spout{
|
||||
.enabled = true
|
||||
};
|
||||
}
|
||||
if (!_windowName->text().isEmpty()) {
|
||||
window.name = _windowName->text().toStdString();
|
||||
}
|
||||
|
||||
// The rest of this function is just specifying the rendering, which we can skip if we
|
||||
// don't want to render 3D anyway
|
||||
if (!window.draw3D) {
|
||||
return;
|
||||
}
|
||||
|
||||
sgct::config::Viewport vp;
|
||||
vp.isTracked = true;
|
||||
vp.position = sgct::vec2(0.f, 0.f);
|
||||
vp.size = sgct::vec2(1.f, 1.f);
|
||||
|
||||
switch (static_cast<ProjectionIndices>(_projectionType->currentIndex())) {
|
||||
case ProjectionIndices::Fisheye:
|
||||
{
|
||||
sgct::config::FisheyeProjection projection;
|
||||
projection.quality = QualityValues[_fisheye.quality->currentIndex()];
|
||||
projection.fov = 180.f;
|
||||
projection.tilt = 0.f;
|
||||
return projection;
|
||||
}
|
||||
vp.projection = sgct::config::FisheyeProjection {
|
||||
.fov = 180.f,
|
||||
.quality = Quality[_fisheye.quality->currentIndex()].first,
|
||||
.tilt = 0.f
|
||||
};
|
||||
break;
|
||||
case ProjectionIndices::SphericalMirror:
|
||||
{
|
||||
sgct::config::SphericalMirrorProjection projection;
|
||||
projection.quality =
|
||||
QualityValues[_sphericalMirror.quality->currentIndex()];
|
||||
return projection;
|
||||
}
|
||||
vp.projection = sgct::config::SphericalMirrorProjection {
|
||||
.quality = Quality[_sphericalMirror.quality->currentIndex()].first
|
||||
};
|
||||
break;
|
||||
case ProjectionIndices::Cylindrical:
|
||||
{
|
||||
sgct::config::CylindricalProjection projection;
|
||||
projection.quality = QualityValues[_cylindrical.quality->currentIndex()];
|
||||
projection.heightOffset = _cylindrical.heightOffset->text().toFloat();
|
||||
return projection;
|
||||
}
|
||||
vp.projection = sgct::config::CylindricalProjection {
|
||||
.quality = Quality[_cylindrical.quality->currentIndex()].first,
|
||||
.heightOffset = static_cast<float>(_cylindrical.heightOffset->value())
|
||||
};
|
||||
break;
|
||||
case ProjectionIndices::Equirectangular:
|
||||
{
|
||||
sgct::config::EquirectangularProjection projection;
|
||||
projection.quality =
|
||||
QualityValues[_equirectangular.quality->currentIndex()];
|
||||
return projection;
|
||||
}
|
||||
vp.projection = sgct::config::EquirectangularProjection {
|
||||
.quality = Quality[_equirectangular.quality->currentIndex()].first
|
||||
};
|
||||
break;
|
||||
case ProjectionIndices::Planar:
|
||||
{
|
||||
double fovH = _planar.fovH->value();
|
||||
@@ -709,38 +795,13 @@ sgct::config::Projections WindowControl::generateProjectionInformation() const {
|
||||
projection.fov.left = -projection.fov.right;
|
||||
projection.fov.up = fovV / 2.0;
|
||||
projection.fov.down = -projection.fov.up;
|
||||
return projection;
|
||||
vp.projection = projection;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw ghoul::MissingCaseException();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowControl::generateWindowInformation(sgct::config::Window& window) const {
|
||||
window.size = { _sizeX->text().toInt(), _sizeY->text().toInt() };
|
||||
window.monitor = _monitor->currentIndex();
|
||||
const QRect resolution = _monitorResolutions[_monitor->currentIndex()];
|
||||
window.pos = sgct::ivec2(
|
||||
resolution.x() + _offsetX->text().toInt(),
|
||||
resolution.y() + _offsetY->text().toInt()
|
||||
);
|
||||
|
||||
sgct::config::Viewport vp;
|
||||
vp.isTracked = true;
|
||||
vp.position = sgct::vec2(0.f, 0.f);
|
||||
vp.size = sgct::vec2(1.f, 1.f);
|
||||
vp.projection = generateProjectionInformation();
|
||||
window.viewports.clear();
|
||||
window.viewports.push_back(vp);
|
||||
|
||||
window.isDecorated = _windowDecoration->isChecked();
|
||||
if (_spoutOutput->isChecked()) {
|
||||
window.spout = sgct::config::Window::Spout();
|
||||
window.spout->enabled = true;
|
||||
}
|
||||
if (!_windowName->text().isEmpty()) {
|
||||
window.name = _windowName->text().toStdString();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowControl::setProjectionPlanar(float hfov, float vfov) {
|
||||
@@ -750,66 +811,30 @@ void WindowControl::setProjectionPlanar(float hfov, float vfov) {
|
||||
}
|
||||
|
||||
void WindowControl::setProjectionFisheye(int quality) {
|
||||
setQualityComboBoxFromLinesResolution(quality, _fisheye.quality);
|
||||
_fisheye.quality->setCurrentIndex(indexForQuality(quality));
|
||||
_projectionType->setCurrentIndex(static_cast<int>(ProjectionIndices::Fisheye));
|
||||
}
|
||||
|
||||
void WindowControl::setProjectionSphericalMirror(int quality) {
|
||||
setQualityComboBoxFromLinesResolution(quality, _sphericalMirror.quality);
|
||||
_sphericalMirror.quality->setCurrentIndex(indexForQuality(quality));
|
||||
_projectionType->setCurrentIndex(
|
||||
static_cast<int>(ProjectionIndices::SphericalMirror)
|
||||
);
|
||||
}
|
||||
|
||||
void WindowControl::setProjectionCylindrical(int quality, float heightOffset) {
|
||||
setQualityComboBoxFromLinesResolution(quality, _cylindrical.quality);
|
||||
_cylindrical.quality->setCurrentIndex(indexForQuality(quality));
|
||||
_cylindrical.heightOffset->setValue(heightOffset);
|
||||
_projectionType->setCurrentIndex(static_cast<int>(ProjectionIndices::Cylindrical));
|
||||
}
|
||||
|
||||
void WindowControl::setProjectionEquirectangular(int quality) {
|
||||
setQualityComboBoxFromLinesResolution(quality, _equirectangular.quality);
|
||||
_equirectangular.quality->setCurrentIndex(indexForQuality(quality));
|
||||
_projectionType->setCurrentIndex(
|
||||
static_cast<int>(ProjectionIndices::Equirectangular)
|
||||
);
|
||||
}
|
||||
|
||||
void WindowControl::setVisibilityOfProjectionGui(bool enable) {
|
||||
_projectionType->setVisible(enable);
|
||||
_planar.labelInfo->setVisible(enable);
|
||||
_planar.fovH->setVisible(enable);
|
||||
_planar.labelFovH->setVisible(enable);
|
||||
_planar.fovV->setVisible(enable);
|
||||
_planar.labelFovV->setVisible(enable);
|
||||
_planar.buttonLockFov->setVisible(enable);
|
||||
_fisheye.labelInfo->setVisible(enable);
|
||||
_fisheye.quality->setVisible(enable);
|
||||
_fisheye.labelQuality->setVisible(enable);
|
||||
_sphericalMirror.labelInfo->setVisible(enable);
|
||||
_sphericalMirror.quality->setVisible(enable);
|
||||
_sphericalMirror.labelQuality->setVisible(enable);
|
||||
_cylindrical.labelInfo->setVisible(enable);
|
||||
_cylindrical.heightOffset->setVisible(enable);
|
||||
_cylindrical.labelHeightOffset->setVisible(enable);
|
||||
_cylindrical.quality->setVisible(enable);
|
||||
_cylindrical.labelQuality->setVisible(enable);
|
||||
_equirectangular.labelInfo->setVisible(enable);
|
||||
_equirectangular.quality->setVisible(enable);
|
||||
_equirectangular.labelQuality->setVisible(enable);
|
||||
|
||||
_projectionLabel->setVisible(!enable);
|
||||
}
|
||||
|
||||
void WindowControl::setQualityComboBoxFromLinesResolution(int lines, QComboBox* combo) {
|
||||
ghoul_assert(combo, "Invalid pointer");
|
||||
for (unsigned int v = 0; v < nQualityTypes; ++v) {
|
||||
if (lines == QualityValues[v]) {
|
||||
combo->setCurrentIndex(v);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WindowControl::onSizeXChanged(int newValue) {
|
||||
_windowDimensions.setWidth(newValue);
|
||||
if (_aspectRatioLocked) {
|
||||
@@ -876,34 +901,28 @@ void WindowControl::onProjectionChanged(int newSelection) const {
|
||||
void WindowControl::onAspectRatioLockClicked() {
|
||||
_aspectRatioLocked = !_aspectRatioLocked;
|
||||
if (_aspectRatioLocked) {
|
||||
_aspectRatioSize = _windowDimensions.width() / _windowDimensions.height();
|
||||
const float w = static_cast<float>(_windowDimensions.width());
|
||||
const float h = static_cast<float>(_windowDimensions.height());
|
||||
_aspectRatioSize = w / h;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowControl::onFovLockClicked() {
|
||||
_fovLocked = !_fovLocked;
|
||||
_planar.fovH->setEnabled(!_fovLocked);
|
||||
_planar.fovV->setEnabled(!_fovLocked);
|
||||
if (_fovLocked) {
|
||||
_planar.fovH->setEnabled(false);
|
||||
_planar.fovV->setEnabled(false);
|
||||
updatePlanarLockedFov();
|
||||
}
|
||||
else {
|
||||
_planar.fovH->setEnabled(true);
|
||||
_planar.fovV->setEnabled(true);
|
||||
}
|
||||
_planar.buttonLockFov->setIcon(_fovLocked ? _lockIcon : _unlockIcon);
|
||||
}
|
||||
|
||||
void WindowControl::updatePlanarLockedFov() {
|
||||
const float w = static_cast<float>(_windowDimensions.width());
|
||||
const float h = static_cast<float>(_windowDimensions.height());
|
||||
const bool landscapeOrientation =
|
||||
(_windowDimensions.width() >= _windowDimensions.height());
|
||||
float aspectRatio = 0.f;
|
||||
if (landscapeOrientation) {
|
||||
aspectRatio = _windowDimensions.width() / _windowDimensions.height();
|
||||
}
|
||||
else {
|
||||
aspectRatio = _windowDimensions.height() / _windowDimensions.width();
|
||||
}
|
||||
const float aspectRatio = landscapeOrientation ? (w / h) : (h / w);
|
||||
|
||||
float adjustedFov = 2.f * std::atan(aspectRatio * std::tan(DefaultFovShortEdge
|
||||
* std::numbers::pi_v<float> / 180.f / 2.f));
|
||||
|
||||
165
apps/OpenSpace/ext/launcher/src/splitcombobox.cpp
Normal file
165
apps/OpenSpace/ext/launcher/src/splitcombobox.cpp
Normal file
@@ -0,0 +1,165 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2025 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "splitcombobox.h"
|
||||
|
||||
#include <ghoul/filesystem/filesystem.h>
|
||||
#include <ghoul/misc/assert.h>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
SplitComboBox::SplitComboBox(QWidget* parent, std::filesystem::path userPath,
|
||||
std::string userHeader, std::filesystem::path hardcodedPath,
|
||||
std::string hardcodedHeader, std::string specialFirst,
|
||||
std::function<bool(const std::filesystem::path&)> fileFilter,
|
||||
std::function<std::string(const std::filesystem::path&)> createTooltip)
|
||||
: QComboBox(parent)
|
||||
, _userPath(std::move(userPath))
|
||||
, _userHeader(std::move(userHeader))
|
||||
, _hardCodedPath(std::move(hardcodedPath))
|
||||
, _hardCodedHeader(std::move(hardcodedHeader))
|
||||
, _specialFirst(std::move(specialFirst))
|
||||
, _fileFilter(std::move(fileFilter))
|
||||
, _createTooltip(std::move(createTooltip))
|
||||
{
|
||||
connect(
|
||||
this,
|
||||
QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
[this](int index) {
|
||||
if (!_specialFirst.empty() && index == 0) {
|
||||
// We have a special entry which is at the top of the list and that entry
|
||||
// was selected
|
||||
emit selectionChanged(std::nullopt);
|
||||
}
|
||||
else {
|
||||
std::string path = itemData(index).toString().toStdString();
|
||||
emit selectionChanged(std::move(path));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void SplitComboBox::populateList(const std::string& preset) {
|
||||
// We don't want any signals to be fired while we are manipulating the list
|
||||
blockSignals(true);
|
||||
|
||||
// Clear the previously existing entries since we might call this function again
|
||||
clear();
|
||||
|
||||
|
||||
//
|
||||
// Special item (if it was specified)
|
||||
if (!_specialFirst.empty()) {
|
||||
addItem(
|
||||
QString::fromStdString(_specialFirst),
|
||||
QString::fromStdString(_specialFirst)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// User entries
|
||||
addItem(QString::fromStdString(_userHeader));
|
||||
qobject_cast<QStandardItemModel*>(model())->item(count() - 1)->setEnabled(false);
|
||||
|
||||
std::vector<std::filesystem::path> userFiles = ghoul::filesystem::walkDirectory(
|
||||
_userPath,
|
||||
ghoul::filesystem::Recursive::Yes,
|
||||
ghoul::filesystem::Sorted::Yes,
|
||||
_fileFilter
|
||||
);
|
||||
for (const std::filesystem::path& p : userFiles) {
|
||||
std::filesystem::path relPath = std::filesystem::relative(p, _userPath);
|
||||
relPath.replace_extension();
|
||||
|
||||
// Display the relative path, but store the full path in the user data segment
|
||||
addItem(
|
||||
QString::fromStdString(relPath.string()),
|
||||
QString::fromStdString(p.string())
|
||||
);
|
||||
|
||||
const QString toolTip = QString::fromStdString(std::format(
|
||||
"<p>{}</p>",
|
||||
_createTooltip ? _createTooltip(p) : "(no description available)"
|
||||
));
|
||||
setItemData(count() - 1, toolTip, Qt::ToolTipRole);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Hardcoded entries
|
||||
addItem(QString::fromStdString(_hardCodedHeader));
|
||||
qobject_cast<QStandardItemModel*>(model())->item(count() - 1)->setEnabled(false);
|
||||
|
||||
std::vector<std::filesystem::path> hcFiles = ghoul::filesystem::walkDirectory(
|
||||
_hardCodedPath,
|
||||
ghoul::filesystem::Recursive::Yes,
|
||||
ghoul::filesystem::Sorted::Yes,
|
||||
_fileFilter
|
||||
);
|
||||
for (const std::filesystem::path& p : hcFiles) {
|
||||
std::filesystem::path relPath = std::filesystem::relative(p, _hardCodedPath);
|
||||
relPath.replace_extension();
|
||||
|
||||
// Display the relative path, but store the full path in the user data segment
|
||||
addItem(
|
||||
QString::fromStdString(relPath.string()),
|
||||
QString::fromStdString(p.string())
|
||||
);
|
||||
|
||||
std::string tooltipDescription = "(no description available)";
|
||||
if (_createTooltip) {
|
||||
tooltipDescription = _createTooltip(p);
|
||||
}
|
||||
const QString toolTip = QString::fromStdString(
|
||||
std::format("<p>{}</p>", tooltipDescription)
|
||||
);
|
||||
setItemData(count() - 1, toolTip, Qt::ToolTipRole);
|
||||
}
|
||||
|
||||
|
||||
// Now we are ready to send signals again
|
||||
blockSignals(false);
|
||||
|
||||
|
||||
//
|
||||
// Find the provided preset and set it as the current one
|
||||
const int idx = findText(QString::fromStdString(preset));
|
||||
if (idx != -1) {
|
||||
// Found the preset, set it as the current index
|
||||
setCurrentIndex(idx);
|
||||
}
|
||||
else {
|
||||
// The preset wasn't found, so we add it to the top
|
||||
std::string text = std::format("'{}' not found", preset);
|
||||
insertItem(0, QString::fromStdString(text));
|
||||
setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<std::string, std::string> SplitComboBox::currentSelection() const {
|
||||
return {
|
||||
currentText().toStdString(),
|
||||
currentData().toString().toStdString()
|
||||
};
|
||||
}
|
||||
50
apps/OpenSpace/ext/launcher/src/windowcolors.cpp
Normal file
50
apps/OpenSpace/ext/launcher/src/windowcolors.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
/*****************************************************************************************
|
||||
* *
|
||||
* OpenSpace *
|
||||
* *
|
||||
* Copyright (c) 2014-2025 *
|
||||
* *
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
|
||||
* software and associated documentation files (the "Software"), to deal in the Software *
|
||||
* without restriction, including without limitation the rights to use, copy, modify, *
|
||||
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
|
||||
* permit persons to whom the Software is furnished to do so, subject to the following *
|
||||
* conditions: *
|
||||
* *
|
||||
* The above copyright notice and this permission notice shall be included in all copies *
|
||||
* or substantial portions of the Software. *
|
||||
* *
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
|
||||
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
|
||||
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
|
||||
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
|
||||
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
|
||||
****************************************************************************************/
|
||||
|
||||
#include "windowcolors.h"
|
||||
|
||||
#include <ghoul/misc/assert.h>
|
||||
|
||||
QColor colorForWindow(int idx) {
|
||||
constexpr std::array<QColor, 4> Hardcoded = {
|
||||
QColor(43, 158, 195),
|
||||
QColor(252, 171, 16),
|
||||
QColor(68, 175, 105),
|
||||
QColor(248, 51, 60)
|
||||
};
|
||||
|
||||
ghoul_assert(idx >= 0, "idx must be non-negative");
|
||||
|
||||
if (idx < 4) {
|
||||
return Hardcoded[idx];
|
||||
}
|
||||
else {
|
||||
// If someone asks for a color of an index greater than what we have, we generate
|
||||
// them by multiplying some prime numbers together
|
||||
const int r = (idx * 31 * 3083) % 256;
|
||||
const int g = (idx * 43 * 4273) % 256;
|
||||
const int b = (idx * 59 * 5857) % 256;
|
||||
return QColor(r, g, b);
|
||||
}
|
||||
}
|
||||
Submodule apps/OpenSpace/ext/sgct updated: 94ecb531ae...7554d35656
@@ -1063,69 +1063,6 @@ void setSgctDelegateFunctions() {
|
||||
};
|
||||
}
|
||||
|
||||
std::string setWindowConfigPresetForGui(const std::string& labelFromCfgFile,
|
||||
bool haveCliSGCTConfig)
|
||||
{
|
||||
openspace::Configuration& config = *global::configuration;
|
||||
|
||||
std::string preset;
|
||||
const bool sgctCfgFileSpecifiedByLua = !config.sgctConfigNameInitialized.empty();
|
||||
if (haveCliSGCTConfig) {
|
||||
preset = std::format("{} (from CLI)", config.windowConfiguration);
|
||||
}
|
||||
else if (sgctCfgFileSpecifiedByLua) {
|
||||
preset = config.sgctConfigNameInitialized + labelFromCfgFile;
|
||||
}
|
||||
else {
|
||||
preset = config.windowConfiguration;
|
||||
}
|
||||
return preset;
|
||||
}
|
||||
|
||||
std::pair<std::string, bool> selectedSgctProfileFromLauncher(LauncherWindow& lw,
|
||||
bool hasCliSGCTConfig,
|
||||
const std::string& windowConfiguration,
|
||||
const std::string& labelFromCfgFile)
|
||||
{
|
||||
std::string config = windowConfiguration;
|
||||
bool isGeneratedConfig = false;
|
||||
if (!hasCliSGCTConfig) {
|
||||
config = lw.selectedWindowConfig();
|
||||
if (config.find(labelFromCfgFile) != std::string::npos) {
|
||||
if (config.find("sgct.config") == std::string::npos) {
|
||||
config = config.substr(0, config.length() - labelFromCfgFile.length());
|
||||
}
|
||||
else {
|
||||
config = windowConfiguration;
|
||||
isGeneratedConfig = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const std::filesystem::path c = absPath(config);
|
||||
|
||||
std::filesystem::path cj = c;
|
||||
cj.replace_extension(".json");
|
||||
|
||||
if (c.extension().empty()) {
|
||||
if (std::filesystem::exists(cj)) {
|
||||
config += ".json";
|
||||
}
|
||||
else {
|
||||
throw ghoul::RuntimeError(std::format(
|
||||
"Error loading configuration file '{}'. File could not be found",
|
||||
config
|
||||
));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// user customized SGCT config
|
||||
}
|
||||
}
|
||||
global::configuration->windowConfiguration = config;
|
||||
}
|
||||
return { config, isGeneratedConfig };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -1325,9 +1262,6 @@ int main(int argc, char* argv[]) {
|
||||
global::configuration->bypassLauncher = *commandlineArguments.bypassLauncher;
|
||||
}
|
||||
|
||||
// Determining SGCT configuration file
|
||||
LDEBUG("SGCT Configuration file: " + global::configuration->windowConfiguration);
|
||||
|
||||
windowConfiguration = global::configuration->windowConfiguration;
|
||||
}
|
||||
catch (const documentation::SpecificationError& e) {
|
||||
@@ -1350,12 +1284,20 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
// Call profile GUI
|
||||
const std::string labelFromCfgFile = " (from .cfg)";
|
||||
std::string windowCfgPreset = setWindowConfigPresetForGui(
|
||||
labelFromCfgFile,
|
||||
commandlineArguments.windowConfig.has_value()
|
||||
);
|
||||
std::string windowCfgPreset;
|
||||
if (commandlineArguments.windowConfig.has_value()) {
|
||||
windowCfgPreset = std::format(
|
||||
"{} (from CLI)", global::configuration->windowConfiguration
|
||||
);
|
||||
}
|
||||
else if (!global::configuration->sgctConfigNameInitialized.empty()) {
|
||||
windowCfgPreset =
|
||||
global::configuration->sgctConfigNameInitialized + labelFromCfgFile;
|
||||
}
|
||||
else {
|
||||
windowCfgPreset = global::configuration->windowConfiguration;
|
||||
}
|
||||
|
||||
//TODO consider LFATAL if ${USER} doens't exist rather then recurisve create.
|
||||
global::openSpaceEngine->createUserDirectoriesIfNecessary();
|
||||
|
||||
// (abock, 2020-12-07) For some reason on Apple the keyboard handler in CEF will call
|
||||
@@ -1387,17 +1329,16 @@ int main(int argc, char* argv[]) {
|
||||
);
|
||||
}
|
||||
|
||||
LauncherWindow win = LauncherWindow(
|
||||
LauncherWindow launcher = LauncherWindow(
|
||||
!commandlineArguments.profile.has_value(),
|
||||
*global::configuration,
|
||||
!commandlineArguments.windowConfig.has_value(),
|
||||
std::move(windowCfgPreset),
|
||||
nullptr
|
||||
std::move(windowCfgPreset)
|
||||
);
|
||||
win.show();
|
||||
launcher.show();
|
||||
QApplication::exec();
|
||||
|
||||
if (!win.wasLaunchSelected()) {
|
||||
if (!launcher.wasLaunchSelected()) {
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
glfwInit();
|
||||
@@ -1442,14 +1383,23 @@ int main(int argc, char* argv[]) {
|
||||
size
|
||||
);
|
||||
|
||||
global::configuration->profile = win.selectedProfile();
|
||||
std::tie(windowConfiguration, isGeneratedWindowConfig) =
|
||||
selectedSgctProfileFromLauncher(
|
||||
win,
|
||||
commandlineArguments.windowConfig.has_value(),
|
||||
windowConfiguration,
|
||||
labelFromCfgFile
|
||||
);
|
||||
global::configuration->profile = launcher.selectedProfile();
|
||||
|
||||
std::string config = windowConfiguration;
|
||||
isGeneratedWindowConfig = false;
|
||||
if (!commandlineArguments.windowConfig.has_value()) {
|
||||
config = launcher.selectedWindowConfig();
|
||||
if (config.find(labelFromCfgFile) != std::string::npos) {
|
||||
if (config.find("sgct.config") == std::string::npos) {
|
||||
config = config.substr(0, config.length() - labelFromCfgFile.length());
|
||||
}
|
||||
else {
|
||||
config = windowConfiguration;
|
||||
isGeneratedWindowConfig = true;
|
||||
}
|
||||
}
|
||||
global::configuration->windowConfiguration = config;
|
||||
}
|
||||
}
|
||||
else {
|
||||
glfwInit();
|
||||
@@ -1478,7 +1428,10 @@ int main(int argc, char* argv[]) {
|
||||
// as well as the configuration file that sgct is supposed to use
|
||||
arguments.insert(arguments.begin(), argv[0]);
|
||||
arguments.insert(arguments.begin() + 1, "-config");
|
||||
arguments.insert(arguments.begin() + 2, absPath(windowConfiguration).string());
|
||||
arguments.insert(
|
||||
arguments.begin() + 2,
|
||||
absPath(global::configuration->windowConfiguration).string()
|
||||
);
|
||||
|
||||
// Need to set this before the creation of the sgct::Engine
|
||||
|
||||
@@ -1491,6 +1444,12 @@ int main(int argc, char* argv[]) {
|
||||
glfwWindowHint(GLFW_STENCIL_BITS, 8);
|
||||
#endif
|
||||
|
||||
// Determining SGCT configuration file
|
||||
LINFO(std::format(
|
||||
"SGCT Configuration file: {}", absPath(global::configuration->windowConfiguration)
|
||||
));
|
||||
|
||||
|
||||
LDEBUG("Creating SGCT Engine");
|
||||
std::vector<std::string> arg(argv + 1, argv + argc);
|
||||
LDEBUG("Parsing commandline arguments");
|
||||
@@ -1498,7 +1457,9 @@ int main(int argc, char* argv[]) {
|
||||
LDEBUG("Loading cluster information");
|
||||
config::Cluster cluster;
|
||||
try {
|
||||
cluster = loadCluster(absPath(windowConfiguration).string());
|
||||
cluster = loadCluster(
|
||||
absPath(global::configuration->windowConfiguration).string()
|
||||
);
|
||||
}
|
||||
catch (const std::runtime_error& e) {
|
||||
LFATALC("main", e.what());
|
||||
|
||||
Submodule ext/ghoul updated: ac4ac8b9be...1638f8e99f
@@ -1593,8 +1593,8 @@ void OpenSpaceEngine::handleDragDrop(std::filesystem::path file) {
|
||||
// each of the files contained in the directory
|
||||
std::vector<std::filesystem::path> files = ghoul::filesystem::walkDirectory(
|
||||
file,
|
||||
true,
|
||||
false,
|
||||
ghoul::filesystem::Recursive::Yes,
|
||||
ghoul::filesystem::Sorted::No,
|
||||
[](const std::filesystem::path& f) {
|
||||
return std::filesystem::is_regular_file(f);
|
||||
}
|
||||
|
||||
@@ -162,8 +162,8 @@ namespace {
|
||||
namespace fs = std::filesystem;
|
||||
return ghoul::filesystem::walkDirectory(
|
||||
path,
|
||||
recursive,
|
||||
sorted,
|
||||
ghoul::filesystem::Recursive(recursive),
|
||||
ghoul::filesystem::Sorted(sorted),
|
||||
[](const fs::path& p) { return fs::is_directory(p) || fs::is_regular_file(p); }
|
||||
);
|
||||
}
|
||||
@@ -187,8 +187,8 @@ namespace {
|
||||
namespace fs = std::filesystem;
|
||||
return ghoul::filesystem::walkDirectory(
|
||||
path,
|
||||
recursive,
|
||||
sorted,
|
||||
ghoul::filesystem::Recursive(recursive),
|
||||
ghoul::filesystem::Sorted(sorted),
|
||||
[](const fs::path& p) { return fs::is_regular_file(p); }
|
||||
);
|
||||
}
|
||||
@@ -212,8 +212,8 @@ namespace {
|
||||
namespace fs = std::filesystem;
|
||||
return ghoul::filesystem::walkDirectory(
|
||||
path,
|
||||
recursive,
|
||||
sorted,
|
||||
ghoul::filesystem::Recursive(recursive),
|
||||
ghoul::filesystem::Sorted(sorted),
|
||||
[](const fs::path& p) { return fs::is_directory(p); }
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user