diff --git a/ApplicationLibCode/Commands/ApplicationCommands/RicEditPreferencesFeature.cpp b/ApplicationLibCode/Commands/ApplicationCommands/RicEditPreferencesFeature.cpp index 52f62e5f41..0f1f34864d 100644 --- a/ApplicationLibCode/Commands/ApplicationCommands/RicEditPreferencesFeature.cpp +++ b/ApplicationLibCode/Commands/ApplicationCommands/RicEditPreferencesFeature.cpp @@ -24,7 +24,7 @@ #include "RimProject.h" -#include "RiuPropertyViewTabWidget.h" +#include "RiuPropertyViewListWidget.h" #include "cafPdmSettings.h" #include "cafPdmUiModelChangeDetector.h" @@ -63,7 +63,7 @@ void RicEditPreferencesFeature::onActionTriggered( bool isChecked ) std::unique_ptr oldPreferences = clonePreferences( app->preferences() ); - RiuPropertyViewTabWidget propertyDialog( RiaGuiApplication::widgetToUseAsParent(), app->preferences(), "Preferences", tabNames ); + RiuPropertyViewListWidget propertyDialog( RiaGuiApplication::widgetToUseAsParent(), app->preferences(), "Preferences", tabNames ); auto pushButton = propertyDialog.dialogButtonBox()->addButton( "Help", QDialogButtonBox::HelpRole ); connect( pushButton, &QPushButton::clicked, this, &RicEditPreferencesFeature::showHelp ); diff --git a/ApplicationLibCode/UserInterface/CMakeLists_files.cmake b/ApplicationLibCode/UserInterface/CMakeLists_files.cmake index d8389c9a0d..2c6f23497c 100644 --- a/ApplicationLibCode/UserInterface/CMakeLists_files.cmake +++ b/ApplicationLibCode/UserInterface/CMakeLists_files.cmake @@ -19,6 +19,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RiuProjectPropertyView.cpp ${CMAKE_CURRENT_LIST_DIR}/RiuPropertyViewTabWidget.cpp ${CMAKE_CURRENT_LIST_DIR}/RiuPropertyViewWizard.cpp + ${CMAKE_CURRENT_LIST_DIR}/RiuPropertyViewListWidget.cpp ${CMAKE_CURRENT_LIST_DIR}/RiuPvtPlotPanel.cpp ${CMAKE_CURRENT_LIST_DIR}/RiuPvtPlotUpdater.cpp ${CMAKE_CURRENT_LIST_DIR}/RiuPvtPlotWidget.cpp diff --git a/ApplicationLibCode/UserInterface/RiuPropertyViewListWidget.cpp b/ApplicationLibCode/UserInterface/RiuPropertyViewListWidget.cpp new file mode 100644 index 0000000000..289b4270ad --- /dev/null +++ b/ApplicationLibCode/UserInterface/RiuPropertyViewListWidget.cpp @@ -0,0 +1,293 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RiuPropertyViewListWidget.h" + +#include "RiaPreferencesSystem.h" + +#include "cafPdmObject.h" +#include "cafPdmUiPropertyView.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +class InternalSelectDelegate : public QStyledItemDelegate +{ +public: + using QStyledItemDelegate::QStyledItemDelegate; + + QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const override + { + QSize size = QStyledItemDelegate::sizeHint( option, index ); + size.setHeight( 32 ); + return size; + } +}; +} // namespace + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RiuPropertyViewListWidget::RiuPropertyViewListWidget( QWidget* parent, + caf::PdmObject* object, + const QString& windowTitle, + const QStringList& uiConfigNameForTabs ) + : QDialog( parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint ) + , m_windowTitle( windowTitle ) + , m_objectClassKeyword( object ? object->classKeyword() : QString() ) +{ + setWindowTitle( windowTitle ); + + auto mainHBoxLayout = new QHBoxLayout(); + auto rightPaneLayout = new QVBoxLayout(); + + // use stacked widgets and a list view to select which widget to show + m_propertyStack = new QStackedWidget(); + m_propertyList = new QListView(); + m_propertyList->setMaximumWidth( 150 ); + + // use a custom item delegate to get larger, selectable items in the list view on all platforms + m_propertyList->setItemDelegate( new InternalSelectDelegate( m_propertyList ) ); + + // a label to better indicate which page we are on + m_pageTitle = new QLabel(); + QFont f = m_pageTitle->font(); + f.setPointSize( f.pointSize() + 4 ); + f.setBold( true ); + m_pageTitle->setFont( f ); + + auto* line = new QFrame(); + line->setFrameShape( QFrame::HLine ); + line->setFrameShadow( QFrame::Sunken ); + + rightPaneLayout->addWidget( m_pageTitle ); + rightPaneLayout->addWidget( line ); + rightPaneLayout->addWidget( m_propertyStack, 1 ); + + mainHBoxLayout->addWidget( m_propertyList ); + mainHBoxLayout->addLayout( rightPaneLayout, 1 ); + + m_propertyModel = new QStandardItemModel( this ); + m_propertyList->setModel( m_propertyModel ); + + // add the individual property pages to the stack + for ( int i = 0; i < uiConfigNameForTabs.size(); i++ ) + { + QHBoxLayout* widgetLayout = new QHBoxLayout; + widgetLayout->setContentsMargins( 0, 0, 0, 0 ); + + QWidget* containerWidget = new QWidget; + containerWidget->setLayout( widgetLayout ); + + caf::PdmUiPropertyView* pdmUiPropertyView = new caf::PdmUiPropertyView(); + pdmUiPropertyView->setUiConfigurationName( uiConfigNameForTabs[i] ); + + widgetLayout->addWidget( pdmUiPropertyView ); + + m_propertyStack->addWidget( containerWidget ); + auto item = new QStandardItem( uiConfigNameForTabs[i] ); + m_propertyModel->appendRow( item ); + + pdmUiPropertyView->showProperties( object ); + + m_pageWidgets.push_back( pdmUiPropertyView ); + } + + if ( uiConfigNameForTabs.size() > 0 ) + { + // initial selection is the first page + QModelIndex idx = m_propertyModel->index( 0, 0 ); + m_propertyList->setCurrentIndex( idx ); + m_pageTitle->setText( uiConfigNameForTabs[0] ); + } + + auto mainDlgLayout = new QVBoxLayout(); + mainDlgLayout->addLayout( mainHBoxLayout, 1 ); + + // Buttons + auto btnHbox = new QHBoxLayout(); + m_dialogButtonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel ); + btnHbox->addWidget( m_dialogButtonBox ); + mainDlgLayout->addLayout( btnHbox ); + + setLayout( mainDlgLayout ); + + connect( m_dialogButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) ); + connect( m_dialogButtonBox, SIGNAL( rejected() ), this, SLOT( reject() ) ); + connect( m_propertyList->selectionModel(), + &QItemSelectionModel::currentChanged, + this, + [this]( const QModelIndex& current, const QModelIndex& previous ) + { + Q_UNUSED( previous ); + + if ( current.isValid() ) m_propertyStack->setCurrentIndex( current.row() ); + m_pageTitle->setText( current.data().toString() ); + } ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RiuPropertyViewListWidget::~RiuPropertyViewListWidget() +{ + for ( auto w : m_pageWidgets ) + { + w->showProperties( nullptr ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QSize RiuPropertyViewListWidget::minimumSizeHint() const +{ + QSize minSizeHint( 0, 0 ); + + for ( auto w : m_pageWidgets ) + { + QSize pageSize = w->minimumSizeHint(); + pageSize += QSize( 200, 100 ); + + minSizeHint = minSizeHint.expandedTo( pageSize ); + } + + // The inner scroll area reports an artificially small minimum width, which lets some window + // managers open the dialog collapsed (issue #14104). Provide a sensible floor. + return minSizeHint.expandedTo( QSize( 400, 250 ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QSize RiuPropertyViewListWidget::sizeHint() const +{ + QSize sizeHint( 0, 0 ); + + for ( auto w : m_pageWidgets ) + { + QSize pageSize = w->sizeHint(); + pageSize += QSize( 270, 100 ); + + sizeHint = sizeHint.expandedTo( pageSize ); + } + + return sizeHint; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QDialogButtonBox* RiuPropertyViewListWidget::dialogButtonBox() +{ + return m_dialogButtonBox; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiuPropertyViewListWidget::showEvent( QShowEvent* event ) +{ + // Let the base class perform its initial sizing first, so the dialog is associated with the + // screen it actually opens on before we adjust its size. + QDialog::showEvent( event ); + + if ( !m_geometryRestored ) + { + m_geometryRestored = true; + + // Restore the stored size on first show. Doing this here rather than in the constructor + // ensures the size is applied reliably for a modal dialog and is not overridden by the + // initial sizing. + const bool restored = RiaPreferencesSystem::current()->isFeatureEnabled( "remember-dialog-size" ) && restoreDialogGeometry(); + if ( !restored ) + { + // No stored size: the dialog was laid out while associated with the screen it was built + // on, which on a multi-monitor setup can have a different DPI than the screen it ends up + // on. Recompute the layout for the current screen so the dialog is not shown collapsed + // (issue #14104). sizeHint()/minimumSizeHint() provide the floor. + adjustSize(); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiuPropertyViewListWidget::done( int result ) +{ + if ( RiaPreferencesSystem::current()->isFeatureEnabled( "remember-dialog-size" ) ) + { + saveDialogGeometry(); + } + + QDialog::done( result ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RiuPropertyViewListWidget::settingsKey() const +{ + QString title = m_windowTitle; + title.replace( '/', '_' ); + + return QString( "RiuPropertyViewListWidget/%1/%2" ).arg( m_objectClassKeyword, title ); +} + +//-------------------------------------------------------------------------------------------------- +/// Restore the dialog size from the stored width and height. Only the size is persisted; the window +/// position is left to the window manager to avoid the off-screen/wrong-monitor problems that come +/// with restoring an absolute position. +//-------------------------------------------------------------------------------------------------- +bool RiuPropertyViewListWidget::restoreDialogGeometry() +{ + QSettings settings; + QVariant width = settings.value( settingsKey() + "/width" ); + QVariant height = settings.value( settingsKey() + "/height" ); + if ( width.isValid() && height.isValid() ) + { + resize( width.toInt(), height.toInt() ); + return true; + } + + return false; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RiuPropertyViewListWidget::saveDialogGeometry() +{ + QSettings settings; + settings.setValue( settingsKey() + "/width", size().width() ); + settings.setValue( settingsKey() + "/height", size().height() ); +} diff --git a/ApplicationLibCode/UserInterface/RiuPropertyViewListWidget.h b/ApplicationLibCode/UserInterface/RiuPropertyViewListWidget.h new file mode 100644 index 0000000000..ef040a0678 --- /dev/null +++ b/ApplicationLibCode/UserInterface/RiuPropertyViewListWidget.h @@ -0,0 +1,67 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026 Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include + +namespace caf +{ +class PdmObject; +class PdmUiPropertyView; +} // namespace caf + +class QDialogButtonBox; +class QWidget; +class QString; +class QStackedWidget; +class QListView; +class QStandardItemModel; +class QLabel; + +class RiuPropertyViewListWidget : public QDialog +{ +public: + RiuPropertyViewListWidget( QWidget* parent, caf::PdmObject* object, const QString& windowTitle, const QStringList& uiConfigNameForTabs ); + ~RiuPropertyViewListWidget() override; + + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + QDialogButtonBox* dialogButtonBox(); + +protected: + void showEvent( QShowEvent* event ) override; + void done( int result ) override; + +private: + QString settingsKey() const; + bool restoreDialogGeometry(); + void saveDialogGeometry(); + +private: + QStandardItemModel* m_propertyModel; + QStackedWidget* m_propertyStack; + QListView* m_propertyList; + QLabel* m_pageTitle; + QString m_windowTitle; + QString m_objectClassKeyword; + std::vector m_pageWidgets; + QDialogButtonBox* m_dialogButtonBox; + bool m_geometryRestored = false; +};