169 changes: 169 additions & 0 deletions src/gui/qgsoptionsdialogbase.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
/***************************************************************************
qgsoptionsdialogbase.cpp - base vertical tabs option dialog
---------------------
begin : March 24, 2013
copyright : (C) 2013 by Larry Shaffer
email : larrys at dakcarto dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsoptionsdialogbase.h"

#include <QDialog>
#include <QDialogButtonBox>
#include <QListWidget>
#include <QMessageBox>
#include <QScrollBar>
#include <QSettings>
#include <QStackedWidget>
#include <QSplitter>
#include <QTimer>


QgsOptionsDialogBase::QgsOptionsDialogBase( QString settingsKey, QWidget* parent, Qt::WFlags fl )
: QDialog( parent, fl ), mOptsKey( settingsKey ), mInit( false )
{
}

QgsOptionsDialogBase::~QgsOptionsDialogBase()
{
if ( mInit )
{
QSettings settings;
settings.setValue( QString( "/Windows/%1/geometry" ).arg( mOptsKey ), saveGeometry() );
settings.setValue( QString( "/Windows/%1/splitState" ).arg( mOptsKey ), mOptSplitter->saveState() );
settings.setValue( QString( "/Windows/%1/tab" ).arg( mOptsKey ), mOptStackedWidget->currentIndex() );
}
}

void QgsOptionsDialogBase::initOptionsBase( bool restoreUi )
{
// start with copy of qgsoptionsdialog_template.ui to ensure existence of these objects
mOptListWidget = findChild<QListWidget*>( "mOptionsListWidget" );
mOptStackedWidget = findChild<QStackedWidget*>( "mOptionsStackedWidget" );
mOptSplitter = findChild<QSplitter*>( "mOptionsSplitter" );
mOptButtonBox = findChild<QDialogButtonBox*>( "buttonBox" );

if ( !mOptListWidget || !mOptStackedWidget || !mOptSplitter )
{
return;
}

if ( mOptButtonBox )
{
// enforce only one connection per signal, in case added in Qt Designer
disconnect( mOptButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
connect( mOptButtonBox, SIGNAL( accepted() ), this, SLOT( accept() ) );
disconnect( mOptButtonBox, SIGNAL( rejected() ), this, SLOT( reject() ) );
connect( mOptButtonBox, SIGNAL( rejected() ), this, SLOT( reject() ) );
}
connect( mOptSplitter, SIGNAL( splitterMoved( int, int ) ), this, SLOT( updateOptionsListVerticalTabs() ) );
connect( mOptStackedWidget, SIGNAL( currentChanged( int ) ), this, SLOT( optionsStackedWidget_CurrentChanged( int ) ) );

if ( restoreUi )
restoreOptionsBaseUi();

mInit = true;
}

void QgsOptionsDialogBase::restoreOptionsBaseUi()
{
if ( !mInit )
{
return;
}

QSettings settings;
restoreGeometry( settings.value( QString( "/Windows/%1/geometry" ).arg( mOptsKey ) ).toByteArray() );
// mOptListWidget width is fixed to take up less space in QtDesigner
// revert it now unless the splitter's state hasn't been saved yet
mOptListWidget->setMaximumWidth(
settings.value( QString( "/Windows/%1/splitState" ).arg( mOptsKey ) ).isNull() ? 150 : 16777215 );
mOptSplitter->restoreState( settings.value( QString( "/Windows/%1/splitState" ).arg( mOptsKey ) ).toByteArray() );
int curIndx = settings.value( QString( "/Windows/%1/tab" ).arg( mOptsKey ), 0 ).toInt();
mOptStackedWidget->setCurrentIndex( curIndx );
mOptListWidget->setCurrentRow( curIndx );

// get rid of annoying outer focus rect on Mac
mOptListWidget->setAttribute( Qt::WA_MacShowFocusRect, false );
}

void QgsOptionsDialogBase::showEvent( QShowEvent* e )
{
if ( mInit )
{
updateOptionsListVerticalTabs();
}
else
{
QTimer::singleShot( 0, this, SLOT( warnAboutMissingObjects() ) );
}

QDialog::showEvent( e );
}

void QgsOptionsDialogBase::paintEvent( QPaintEvent* e )
{
if ( mInit )
QTimer::singleShot( 0, this, SLOT( updateOptionsListVerticalTabs() ) );

QDialog::paintEvent( e );
}

void QgsOptionsDialogBase::updateOptionsListVerticalTabs()
{
if ( !mInit )
return;

if ( mOptListWidget->maximumWidth() != 16777215 )
mOptListWidget->setMaximumWidth( 16777215 );
// auto-resize splitter for vert scrollbar without covering icons in icon-only mode
// TODO: mOptListWidget has fixed 32px wide icons for now, allow user-defined
// Note: called on splitter resize and dialog paint event, so only update when necessary
int iconWidth = mOptListWidget->iconSize().width();
int snapToIconWidth = iconWidth + 32;

QList<int> splitSizes = mOptSplitter->sizes();
bool iconOnly = ( splitSizes.at( 0 ) <= snapToIconWidth );

int newWidth = mOptListWidget->verticalScrollBar()->isVisible() ? iconWidth + 26 : iconWidth + 12;
bool diffWidth = mOptListWidget->minimumWidth() != newWidth;

if ( diffWidth )
mOptListWidget->setMinimumWidth( newWidth );

if ( iconOnly && ( diffWidth || mOptListWidget->width() != newWidth ) )
{
splitSizes[1] = splitSizes.at( 1 ) - ( splitSizes.at( 0 ) - newWidth );
splitSizes[0] = newWidth;
mOptSplitter->setSizes( splitSizes );
}
if ( mOptListWidget->wordWrap() && iconOnly )
mOptListWidget->setWordWrap( false );
if ( !mOptListWidget->wordWrap() && !iconOnly )
mOptListWidget->setWordWrap( true );
}

void QgsOptionsDialogBase::optionsStackedWidget_CurrentChanged( int indx )
{
mOptListWidget->blockSignals( true );
mOptListWidget->setCurrentRow( indx );
mOptListWidget->blockSignals( false );
}

void QgsOptionsDialogBase::warnAboutMissingObjects()
{
QMessageBox::warning( 0, tr( "Missing objects" ),
tr( "Base options dialog could not be initialized.\n\n"
"Missing some of the .ui template objects:\n" )
+ " mOptionsListWidget,\n mOptionsStackedWidget,\n mOptionsSplitter",
QMessageBox::Ok,
QMessageBox::Ok );
}
85 changes: 85 additions & 0 deletions src/gui/qgsoptionsdialogbase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/***************************************************************************
qgsoptionsdialogbase.h - base vertical tabs option dialog
---------------------
begin : March 24, 2013
copyright : (C) 2013 by Larry Shaffer
email : larrys at dakcarto dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSOPTIONSDIALOGBASE_H
#define QGSOPTIONSDIALOGBASE_H

#include "qgisgui.h"

#include <QDialog>

class QDialogButtonBox;
class QListWidget;
class QStackedWidget;
class QSplitter;

/** \ingroup gui
* \class QgsOptionsDialogBase
* A base dialog for options and properties dialogs that offers vertical tabs.
* It handles saving/restoring of geometry, splitter and current tab states,
* switching vertical tabs between icon/text to icon-only modes (splitter collapsed to left),
* and connecting QDialogButtonBox's accepted/rejected signals to dialog's accept/reject slots
*
* To use:
* 1) Start with copy of qgsoptionsdialog_template.ui and build options/properties dialog.
* 2) In source file for dialog, inherit this class instead of QDialog, then in constructor:
* ...
* setupUi( this ); // set up .ui file objects
* initOptionsBase( false ); // set up this class to use .ui objects, optionally restoring base ui
* ...
* restoreOptionsBaseUi(); // restore the base ui with initOptionsBase or use this later on
* @note added in 1.9
*/

class GUI_EXPORT QgsOptionsDialogBase : public QDialog
{
Q_OBJECT

public:
/** Constructor
* @param settingsKey QSettings subgroup key for saving/restore ui states, e.g. "ProjectProperties".
*/
QgsOptionsDialogBase( QString settingsKey, QWidget* parent = 0, Qt::WFlags fl = 0 );
~QgsOptionsDialogBase();

/** Set up the base ui connections for vertical tabs.
* @param restoreUi Whether to restore the base ui at this time.
*/
void initOptionsBase( bool restoreUi = true );

/** Restore the base ui.
* Sometimes useful to do at end of subclass's constructor.
*/
void restoreOptionsBaseUi();

protected slots:
void updateOptionsListVerticalTabs();
void optionsStackedWidget_CurrentChanged( int indx );
void warnAboutMissingObjects();

protected:
void showEvent( QShowEvent* e );
void paintEvent( QPaintEvent* e );

QString mOptsKey;
bool mInit;
QListWidget* mOptListWidget;
QStackedWidget* mOptStackedWidget;
QSplitter* mOptSplitter;
QDialogButtonBox* mOptButtonBox;
};

#endif // QGSOPTIONSDIALOGBASE_H
3,312 changes: 1,879 additions & 1,433 deletions src/ui/qgsprojectpropertiesbase.ui

Large diffs are not rendered by default.

234 changes: 234 additions & 0 deletions src/ui/templates/qgsoptionsdialog_template.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsOpstionDialogTemplate</class>
<widget class="QDialog" name="QgsOpstionDialogTemplate">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>827</width>
<height>759</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>700</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Options Dialog Template</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QSplitter" name="mOptionsSplitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<widget class="QFrame" name="mOptionsListFrame">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QListWidget" name="mOptionsListWidget">
<property name="minimumSize">
<size>
<width>58</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
<property name="textElideMode">
<enum>Qt::ElideNone</enum>
</property>
<property name="resizeMode">
<enum>QListView::Adjust</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QFrame" name="mOptionsFrame">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QStackedWidget" name="mOptionsStackedWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="mOptsPage_01">
<layout class="QVBoxLayout" name="verticalLayout_14">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label">
<property name="styleSheet">
<string notr="true">font-weight:bold;</string>
</property>
<property name="text">
<string>Section</string>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="scrollArea">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>646</width>
<height>669</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_13">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>GroupBox</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QFrame" name="mButtonBoxFrame">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../images/images.qrc"/>
</resources>
<connections>
<connection>
<sender>mOptionsListWidget</sender>
<signal>currentRowChanged(int)</signal>
<receiver>mOptionsStackedWidget</receiver>
<slot>setCurrentIndex(int)</slot>
<hints>
<hint type="sourcelabel">
<x>86</x>
<y>325</y>
</hint>
<hint type="destinationlabel">
<x>794</x>
<y>14</y>
</hint>
</hints>
</connection>
</connections>
</ui>