Class: Ruber::SettingsDialogManager

Inherits:
Qt::Object
  • Object
show all
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:

  1. when the widget emits some signals (which mean that the settings have changed, the dialog’s Apply button is enabled)

  2. when the dialog is displayed, the widget is updated so that it displays the value stored in the SettingsContainer

  3. 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 called textChanged, so you can simply use that instead of textChanged(QString). On the other hand, Qt::ComboBox has two signals called currentIndexChanged, so you must fully specify them: currentIndexChanged(QString) or currentIndexChanged(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 the store methods from. The store method has the same name as this property, while the read 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 by DEFAULT_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 a KDE::Url instead of a string. But since the DEFAULT_CONVERSIONS contains conversion procs for this pair of classes, even this is handled automatically

  • the line edit requires custom read and store methods (which can be specified with a signle access 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

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, options, widgets
  super(dlg)
  @widgets = widgets
  @container = dlg.settings_container
  @associations = {}
  options.each{|o| setup_option o}
end

Instance Method Details

#read_default_settingsObject

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 = call_widget_method data
    value = convert_value(value, old_value)
    call_widget_method data, value
  end
end

#read_settingsObject

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 = call_widget_method data
    value = convert_value(value, old_value)
    call_widget_method data, value
  end
end

#store_settingsObject

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 = call_widget_method(data)
    old_value = @container[group, name]
    value = convert_value value, old_value
    @container[group, name] = value
  end
end