Class: Ruber::SettingsDialogManager
- Defined in:
- lib/ruber/settings_dialog_manager.rb
Overview
Class which takes care of automatically syncronize (some of) the options in an SettingsContainer with the widgets in the SettingsDialog.
In the following documentation, the widgets passed as argument to the constructor will be called upper level widgets, because they’re the topmost widgets this class is interested in. The term child widget will be used to refer to any widget which is child of an upper level widget, while the generic widget can refer to both. The upper level widget corresponding to a widget is the upper level widget the given widget is child of (or the widget itself if it already is an upper level widget)
To syncronize options and widgets contents, this class looks among the upper level widgets and their children for the widget which have an object_name
of the form _group__option
, that is: an underscore, a group name, two underscores, an option name (both the group and the option name can contain underscores. This is the reason for which two underscores are used to separate them). Such a widget is associated with the option with name name belonging to the group group.
Having a widget associated with an option means three things:
-
when the widget emits some signals (which mean that the settings have changed, the dialog’s Apply button is enabled)
-
when the dialog is displayed, the widget is updated so that it displays the value stored in the SettingsContainer
-
when the Apply or the OK button of the dialog is clicked, the option in the SettingsContainer is updated with the value of the widget.
For all this to happen, who writes the widget (or the upper level widget the widget is child of) must give it several custom properties (this can be done with the UI designer, if it is used to create the widget, or by hand). These properties are:
-
signal: it’s a string containing the signal(s) on which the Apply button of the dialog will be enabled. If more than one signal should be used, you can specify them using a YAML array (that is, enclose them in square brackets and separate them with a comma followed by one space). If the widget has only a single signal with that name, you can omit the signal’s signature, otherwise you need to include it (for example,
Qt::LineEdit
has a single signall calledtextChanged
, so you can simply use that instead oftextChanged(QString)
. On the other hand,Qt::ComboBox
has two signals calledcurrentIndexChanged
, so you must fully specify them:currentIndexChanged(QString)
orcurrentIndexChanged(int)
). -
read: is the method to use to sync the value in the widget with that in the SettingsContainer. The reason of the name “read” is that it is used to read the value from the container.
-
store: is the method to use to sync the value in the SettingsContainer with that in the widget.
-
access: a method name to derive the
read
and thestore
methods from. Thestore
method has the same name as this property, while theread
method has the same name with ending “?” and “!” removed and an ending “=” added.
In the documentation for this class, the term read method refers to the method specified in the read
property or derived from the access
property by adding the =
to it. The term store method refers to the method specified in the store
or access
property.
If the read
, store
or access
properties start with a $
, they’ll be called on the upper level widget corresponding to the widget, instead than on the widget itself.
Not all of the above properties need to be specified. In particular, the access
property can’t coexhist the read
and the store
properties. On the other hand, you can’t give only one of the read
and store
properties. If you omit the access
, store
and read
property entierely, and the signal
property only contains one signal, then an access
property is automatically created using the name of the signal after having removed from its end the strings ‘Edited’, ‘Changed’, ‘Modified’, ‘_edited’, ‘_changed’, ‘_modified’ (for example, if the signal is ‘textChanged(QString)
, then the access
property will become text
.
If the signal
property hasn’t been specified, a default one will be used, depending on the class of the widgets. See the documentation for DEFAULT_PROPERTIES to see which signals will be used for which classes. If neither the access
property nor the read
and store
properties have been given, a default access
property will also be used. If the class of the widget is not included in DEFAULT_PROPERTIES, an exception will be raised.
A read method must accept one argument, which is the value of the option, and display it in the corresponding widget in whichever way is appropriate. A store method, instead, should take no arguments, retrieve the option value from the widget, again using the most appropriate way, and return it.
Often, the default access
method is almost, but not completely, enough. For example, if the widget is a KDE::UrlRequester
, but you want to store the option as a string, instead of using a KDE::Url
, you’d need to create a method whose only task is to convert a KDE::Url
to a string and vice versa. The same could happen with a symbol and a Qt::LineEdit
. To avoid such a need, this class also performs automatic conversions, when reading or storing an option. It works this way: if the value to store in the SettingsContainer
or in the widget are of a different class from the one previously contained there, the DEFAULT_CONVERSIONS
hash is scanned for an entry corresponding to the two classes and, if found, the value returned by the corresponding Proc
is stored instead of the original one.
Example
Consider the following situation:
Options:
OpenStruct.new({:name => :number, :group => :G1, :default => 4})
-
this is an option which contains an integral value, with default 4
OpenStruct.new({:name => :path, :group => :G1, :default => ENV[['HOME']]})
-
this is an option which contains a string representing a path. The default value is the user’s home directory (contained in the environment variable HOME)
OpenStruct.new({:name => :list, :group => :G2, :default => %w[a b c]})
-
this is an option which contains an array of strings, with default value
['a', 'b', 'c']
.
Widgets:
There’s a single upper level widget, of class MyWidget
, which contains a Qt::SpinBox
, a KDE::UrlRequester
and a Qt::LineEdit
. The value displayed in the spin box should be associated to the :number
option, while the url requester should be associated to the :path
option. The line edit widget should be associated with the :list
option, by splitting the text on commas.
We can make some observations:
-
the spin box doesn’t need anything except having its name set to match the
:number
option: the default signal and access method provided byDEFAULT_PROPERTIES
are perfectly adequate to this situation. -
the url requester doesn’t need any special settings, aside from the object name: the default signal and access method provided by
DEFAULT_PROPERTIES
are almost what we need and the only issue is that the methods take and return aKDE::Url
instead of a string. But since theDEFAULT_CONVERSIONS
contains conversion procs for this pair of classes, even this is handled automatically -
the line edit requires custom
read
andstore
methods (which can be specified with a signleaccess
property), because there’s no default conversion from array to string and vice versa. The default signal, instead, is suitable for our needs, so we don’t need to specify one.
Here’s how the class MyWidget
could be written (here, all widgets are created manually. In general, it’s more likely that you’d use the Qt Designer to create it. In that case, you can set the widgets’ properties using the designer itself). Note that in the constructor we make use of the block form of the widgets’ constructors, which evaluates the given block in the new widget’s context.
class MyWidget < Qt::Widget
def initialize parent = nil
super
@spin_box = Qt::SpinBox.new{ self.object_name = '_G1__number'}
@url_req = KDE::UrlRequester.new{self.object_name = '_G1__path'}
@line_edit = Qt::LineEdit.new do
self.object_name = '_G2__list'
set_property 'access', '$items'
end
end
# This is the store method for the list option. It takes the text in the line
# edit and splits it on commas, returning the array
def items
@line_edit.text.split ','
end
# This is the read method for the list option. It takes the array containing
# the value of the option (an array) as argument, then sets the text of the
# line edit to the string obtained by calling join on the array
def items= array
@line_edit.text = array.join ','
end
end
Constant Summary collapse
- DEFAULT_PROPERTIES =
A hash containing the default signal and access methods to use for a number of classes, when the
signal
property isn’t given. The keys of the hash are the classes, while the values are arrays of two elements. The first element is the name of the signal, while the second is the name of the access method. { Qt::CheckBox => [ 'toggled(bool)', "checked?"], Qt::PushButton => [ 'toggled(bool)', "checked?"], KDE::PushButton => [ 'toggled(bool)', "checked?"], KDE::ColorButton => [ 'changed(QColor)', "color"], KDE::IconButton => [ 'iconChanged(QString)', "icon"], Qt::LineEdit => [ 'textChanged(QString)', "text"], KDE::LineEdit => [ 'textChanged(QString)', "text"], KDE::RestrictedLine => [ 'textChanged(QString)', "text"], Qt::ComboBox => [ 'currentIndexChanged(int)', "current_index"], KDE::ComboBox => [ 'currentIndexChanged(int)', "current_index"], KDE::ColorCombo => [ 'currentIndexChanged(int)', "color"], Qt::TextEdit => [ 'textChanged(QString)', "text"], KDE::TextEdit => [ 'textChanged(QString)', "text"], Qt::PlainTextEdit => [ 'textChanged(QString)', "text"], Qt::SpinBox => [ 'valueChanged(int)', "value"], KDE::IntSpinBox => [ 'valueChanged(int)', "value"], Qt::DoubleSpinBox => [ 'valueChanged(double)', "value"], KDE::IntNumInput => [ 'valueChanged(int)', "value"], KDE::DoubleNumInput => [ 'valueChanged(double)', "value"], Qt::TimeEdit => [ 'timeChanged(QTime)', "time"], Qt::DateEdit => [ 'dateChanged(QDate)', "date"], Qt::DateTimeEdit => [ 'dateTimeChanged(QDateTime)', "date_time"], Qt::Dial => [ 'valueChanged(int)', "value"], Qt::Slider => [ 'valueChanged(int)', "value"], KDE::DatePicker => [ 'dateChanged(QDate)', "date"], KDE::DateTimeWidget => [ 'valueChanged(QDateTime)', "date_time"], KDE::DateWidget => [ 'changed(QDate)', "date"], KDE::FontComboBox => [ 'currentFontChanged(QFont)', "current_font"], KDE::FontRequester => [ 'fontSelected(QFont)', "font"], KDE::UrlRequester => [ 'textChanged(QString)', "url"] }
- DEFAULT_CONVERSIONS =
Hash which contains the Procs used by
convert_value
to convert a value from its class to another. Each key must an array of two classes, corresponding respectively to the class to convert from and to the class to convert to. The values should be Procs which take one argument of class corresponding to the first entry of the key and return an object of class equal to the second argument of the key.If you want to implement a new automatic conversion, all you need to do is to add the appropriate entries here. Note that usually you’ll want to add two entries: one for the conversion from A to B and one for the conversion in the opposite direction.
{ [Symbol, String] => proc{|sym| sym.to_s}, [String, Symbol] => proc{|str| str.to_sym}, [String, KDE::Url] => proc{|str| KDE::Url.from_path(str)}, # KDE::Url#path_or_url returns nil if the KDE::Url is not valid, so, we use # || '' to ensure the returned object is a string [KDE::Url, String] => proc{|url| url.path_or_url || ''}, [String, Fixnum] => proc{|str| str.to_i}, [Fixnum, String] => proc{|n| n.to_s}, [String, Float] => proc{|str| str.to_f}, [Float, String] => proc{|x| x.to_s}, }
Instance Method Summary collapse
-
#initialize(dlg, options, widgets) ⇒ SettingsDialogManager
constructor
Creates a new SettingsDialogManager.
-
#read_default_settings ⇒ Object
It works like
read_settings
except for the fact that it updates the widgets with the default values of the options. -
#read_settings ⇒ Object
Updates all the widgets corresponding to an automatically managed option by calling the associated reader methods, passing them the value read from the option container, converted as described in the the documentation for SettingsDialogManager,
convert_value
andDEFAULT_CONVERSIONS
. -
#store_settings ⇒ Object
Updates all the automatically managed options by calling setting them to the values returned by the associated ‘store’ methods, converted as described in the the documentation for
SettingsDialogManager
,convert_value
andDEFAULT_CONVERSIONS
.
Constructor Details
#initialize(dlg, options, widgets) ⇒ SettingsDialogManager
Creates a new SettingsDialogManager. The first argument is the SettingsDialog whose widgets will be managed by the new instance. The second argument is an array with the option objects corresponding to the options which are candidates for automatic management. The keys of the hash are the option objects, which contain all the information about the options, while the values are the values of the options. widgets is a list of widgets where to look for for widgets corresponding to the options.
When the SettingsDialogManager
instance is created, associations between options and widgets are created, but the widgets themselves aren’t updated with the values of the options.
274 275 276 277 278 279 280 |
# File 'lib/ruber/settings_dialog_manager.rb', line 274 def initialize dlg, , super(dlg) @widgets = @container = dlg.settings_container @associations = {} .each{|o| setup_option o} end |
Instance Method Details
#read_default_settings ⇒ Object
It works like read_settings
except for the fact that it updates the widgets with the default values of the options.
320 321 322 323 324 325 326 327 328 |
# File 'lib/ruber/settings_dialog_manager.rb', line 320 def read_default_settings @associations.each_pair do |o, data| group, name = o[1..-1].split('__').map(&:to_sym) value = @container.default(group, name) old_value = data value = convert_value(value, old_value) data, value end end |
#read_settings ⇒ Object
Updates all the widgets corresponding to an automatically managed option by calling the associated reader methods, passing them the value read from the option container, converted as described in the the documentation for SettingsDialogManager, convert_value
and DEFAULT_CONVERSIONS
. It emits the settings_changed
signal with true as argument.
289 290 291 292 293 294 295 296 297 |
# File 'lib/ruber/settings_dialog_manager.rb', line 289 def read_settings @associations.each_pair do |o, data| group, name = o[1..-1].split('__').map(&:to_sym) value = @container.relative_path?(group, name) ? @container[group, name, :abs] : @container[group, name] old_value = data value = convert_value(value, old_value) data, value end end |
#store_settings ⇒ Object
Updates all the automatically managed options by calling setting them to the values returned by the associated ‘store’ methods, converted as described in the the documentation for SettingsDialogManager
, convert_value
and DEFAULT_CONVERSIONS
. It emits the settings_changed
signal with false as argument.
306 307 308 309 310 311 312 313 314 |
# File 'lib/ruber/settings_dialog_manager.rb', line 306 def store_settings @associations.each_pair do |o, data| group, name = o[1..-1].split('__').map(&:to_sym) value = (data) old_value = @container[group, name] value = convert_value value, old_value @container[group, name] = value end end |