Class: MetaRuby::GUI::ModelSelector

Inherits:
Qt::Widget
  • Object
show all
Defined in:
lib/metaruby/gui/model_selector.rb

Overview

A Qt widget based on RubyConstantsItemModel to browse a set of models, and display them when the user selects one

Defined Under Namespace

Classes: ModelPathCompleter

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent = nil) ⇒ ModelSelector

Create a new widget with an optional parent

Parameters:

  • parent (Qt::Widget, nil) (defaults to: nil)


40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/metaruby/gui/model_selector.rb', line 40

def initialize(parent = nil)
    super

    @type_info = Hash.new
    @browser_model = RubyConstantsItemModel.new(type_info) do |mod|
        model?(mod)
    end
    @type_filters = Hash.new

    layout = Qt::VBoxLayout.new(self)
    filter_button = Qt::PushButton.new('Filters', self)
    layout.add_widget(filter_button)
    @btn_type_filter_menu = Qt::Menu.new
    filter_button.menu = btn_type_filter_menu

    setup_tree_view(layout)
    setTabOrder(filter_box, filter_button)
    setTabOrder(filter_button, model_list)
end

Instance Attribute Details

#browser_modelRubyConstantsItemModel (readonly)

The Qt item model that represents the object hierarchy



26
27
28
# File 'lib/metaruby/gui/model_selector.rb', line 26

def browser_model
  @browser_model
end

#btn_type_filter_menuQt::PushButton (readonly)

Returns a button allowing to filter models by type.

Returns:

  • (Qt::PushButton)

    a button allowing to filter models by type



30
31
32
# File 'lib/metaruby/gui/model_selector.rb', line 30

def btn_type_filter_menu
  @btn_type_filter_menu
end

#filter_boxQt::LineEdit (readonly)

Returns the line edit widget that allows to modify the tree view filter.

Returns:

  • (Qt::LineEdit)

    the line edit widget that allows to modify the tree view filter



33
34
35
# File 'lib/metaruby/gui/model_selector.rb', line 33

def filter_box
  @filter_box
end

#filter_completerQt::Completer (readonly)

Returns auto-completion for #filter_box.

Returns:



35
36
37
# File 'lib/metaruby/gui/model_selector.rb', line 35

def filter_completer
  @filter_completer
end

#model_filterQt::SortFilterProxyModel (readonly)

Qt model filter

Returns:

  • (Qt::SortFilterProxyModel)


19
20
21
# File 'lib/metaruby/gui/model_selector.rb', line 19

def model_filter
  @model_filter
end

#model_listQt::TreeView (readonly)

The view that shows the object hierarchy

Returns:

  • (Qt::TreeView)


15
16
17
# File 'lib/metaruby/gui/model_selector.rb', line 15

def model_list
  @model_list
end

#type_filtersHash<Module,Qt::Action> (readonly)

A per-type matching of the type to the actio that allows to filter/unfilter on this type

Returns:

  • (Hash<Module,Qt::Action>)


10
11
12
# File 'lib/metaruby/gui/model_selector.rb', line 10

def type_filters
  @type_filters
end

#type_infoObject (readonly)

Returns the value of attribute type_info.



22
23
24
# File 'lib/metaruby/gui/model_selector.rb', line 22

def type_info
  @type_info
end

Instance Method Details

#all_leaves(model, limit = nil, item = Qt::ModelIndex.new, result = []) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Helper method for #select_first_item



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/metaruby/gui/model_selector.rb', line 158

def all_leaves(model, limit = nil, item = Qt::ModelIndex.new, result = [])
    if !model.hasChildren(item)
        result << item
        return result
    end

    row, child_item = 0, model.index(0, 0, item)
    while child_item.valid?
        all_leaves(model, limit, child_item, result)
        if limit && result.size == limit
            return result
        end
        row += 1
        child_item = model.index(row, 0, item)
    end
    return result
end

#auto_open(threshold = 5) ⇒ Object

Auto-open in the current state

Parameters:

  • threshold (Integer) (defaults to: 5)

    the method opens items whose number of children is lower than this threshold



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/metaruby/gui/model_selector.rb', line 115

def auto_open(threshold = 5)
    current_level = [Qt::ModelIndex.new]
    while !current_level.empty?
        count = current_level.inject(0) do |total, index|
            total + model_filter.rowCount(index)
        end
        close_this_level = (count > threshold)
        current_level.each do |index|
            model_filter.rowCount(index).times.each do |row|
                model_list.setExpanded(model_filter.index(row, 0, index), !close_this_level)
            end
        end
        return if close_this_level

        last_level, current_level = current_level, []
        last_level.each do |index|
            model_filter.rowCount(index).times.each do |row|
                current_level << model_filter.index(row, 0, index)
            end
        end
    end
end

#current_selectionRubyModuleModel::ModuleInfo?

Returns the currently selected item

Returns:

  • (RubyModuleModel::ModuleInfo, nil)

    nil if there are no selections



299
300
301
302
303
304
305
# File 'lib/metaruby/gui/model_selector.rb', line 299

def current_selection
    index = model_list.selection_model.current_index
    if index.valid?
        index = model_filter.map_to_source(index)
        browser_model.info_from_index(index)
    end
end

#map_index_from_source(source_index, reset_filter: true) ⇒ Qt::ModelIndex

Maps a model index from the source index to the filtered index, e.g. to select something in the view.

In addition to the normal map_from_source call, it allows to control whether the filter should be reset if the index given as parameter is currently filtered out

Parameters:

  • source_index (Qt::ModelIndex)

    an index valid in #browser_model

  • reset_filter (Boolean) (defaults to: true)

    if true, the filter is reset if the requested index is currently filtered out

Returns:



256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'lib/metaruby/gui/model_selector.rb', line 256

def map_index_from_source(source_index, reset_filter: true)
    index = model_filter.map_from_source(source_index)
    if !index
        return
    elsif !index.valid?
        if !options[:reset_filter]
            return index
        end
        reset_filter
        model_filter.map_from_source(source_index)
    else index
    end
end

#model?(obj) ⇒ Boolean

Tests if an object if a model

Returns:

  • (Boolean)


139
140
141
142
143
144
# File 'lib/metaruby/gui/model_selector.rb', line 139

def model?(obj)
    type_info.any? do |model_base, _|
        obj.kind_of?(model_base) ||
            (obj.kind_of?(Module) && obj <= model_base)
    end
end

#object_pathsObject



307
308
309
# File 'lib/metaruby/gui/model_selector.rb', line 307

def object_paths
    browser_model.object_paths
end

#register_type(model_base, name, priority = 0) ⇒ Object

Register a new object type

Parameters:

  • model_base (Module)

    a module or class whose all objects of this type have as superclass

  • name (String)

    the string that should be used to represent objects of this type

  • priority (Integer) (defaults to: 0)

    if an object’s ancestry matches multiple types, only the ones of the highest priority will be retained



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/metaruby/gui/model_selector.rb', line 68

def register_type(model_base, name, priority = 0)
    type_info[model_base] = RubyConstantsItemModel::TypeInfo.new(name, priority)
    action = Qt::Action.new(name, self)
    action.checkable = true
    action.checked = true
    type_filters[model_base] = action
    btn_type_filter_menu.add_action(action)
    connect(action, SIGNAL('triggered()')) do
        update_model_filter
    end
end

#reloadObject

Reload the object model, keeping the current selection if possible



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/metaruby/gui/model_selector.rb', line 219

def reload
    if current = current_selection
        current_module = current.this
        current_path = []
        while current
            current_path.unshift current.name
            current = current.parent
        end
    end

    browser_model.reload

    if current_path && !select_by_path(*current_path)
        select_by_module(current_module)
    end
end

#reset_filterObject

Resets the current filter



237
238
239
240
241
242
243
# File 'lib/metaruby/gui/model_selector.rb', line 237

def reset_filter
    # If there is a filter, reset it and try again
    if !filter_box.text.empty?
        filter_box.text = ""
        true
    end
end

#select_by_module(model) ⇒ Boolean

Selects the given model if it registered in the model list This emits the model_selected signal

Returns:

  • (Boolean)

    true if the path resolved to something known, and false otherwise



288
289
290
291
292
293
294
# File 'lib/metaruby/gui/model_selector.rb', line 288

def select_by_module(model)
    if index = browser_model.find_index_by_model(model)
        index = map_index_from_source(index)
        model_list.current_index = index
        true
    end
end

#select_by_path(*path) ⇒ Boolean

Selects the current model given a path in the constant names This emits the model_selected signal

Returns:

  • (Boolean)

    true if the path resolved to something known, and false otherwise



275
276
277
278
279
280
281
# File 'lib/metaruby/gui/model_selector.rb', line 275

def select_by_path(*path)
    if index = browser_model.find_index_by_path(*path)
        index = map_index_from_source(index)
        model_list.current_index = index
        true
    end
end

#select_first_itemObject

Select the first displayed item



177
178
179
180
181
# File 'lib/metaruby/gui/model_selector.rb', line 177

def select_first_item
    if item = all_leaves(model_filter, 1).first
        model_list.setCurrentIndex(item)
    end
end

#setup_tree_view(layout) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Create and setup #model_list



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/metaruby/gui/model_selector.rb', line 186

def setup_tree_view(layout)
    @model_list = Qt::TreeView.new(self)
    @model_filter = Qt::SortFilterProxyModel.new
    model_filter.filter_case_sensitivity = Qt::CaseInsensitive
    model_filter.dynamic_sort_filter = true
    model_filter.filter_role = Qt::UserRole
    model_list.model = model_filter
    model_filter.source_model = browser_model

    @filter_box = Qt::LineEdit.new(self)
    filter_box.connect(SIGNAL('textChanged(QString)')) do |text|
        update_model_filter
    end
    filter_box.connect(SIGNAL('returnPressed()')) do |text|
        select_first_item
    end
    @filter_completer = ModelPathCompleter.new(browser_model, self)
    filter_completer.case_sensitivity = Qt::CaseInsensitive
    filter_box.completer = filter_completer
    layout.add_widget(filter_box)
    layout.add_widget(model_list)

    model_list.selection_model.connect(SIGNAL('currentChanged(const QModelIndex&, const QModelIndex&)')) do |index, _|
        index = model_filter.map_to_source(index)
        mod = browser_model.info_from_index(index)
        if model?(mod.this)
            emit model_selected(Qt::Variant.from_ruby(mod.this, mod.this))
        end
    end
end

#updateObject

Update the view, reloading the underlying model



81
82
83
84
# File 'lib/metaruby/gui/model_selector.rb', line 81

def update
    update_model_filter
    reload
end

#update_model_filterObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Update #model_filter to match the current filter setup



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/metaruby/gui/model_selector.rb', line 89

def update_model_filter
    type_rx = type_filters.map do |model_base, act|
        if act.checked?
            type_info[model_base].name
        end
    end
    type_rx = type_rx.compact.join("|")

    model_filter.filter_role = Qt::UserRole # this contains the keywords (ancestry and/or name)
    # This workaround a problem that I did not have time to
    # investigate. Adding new characters to the filter updates the
    # browser just fine, while removing some characters does not
    #
    # This successfully resets the filter
    model_filter.filter_reg_exp = Qt::RegExp.new("")
    # The pattern has to match every element in the hierarchy. We
    # achieve this by making the suffix part optional
    name_rx = filter_box.text.downcase.gsub(/:+/, "/")
    model_filter.filter_reg_exp = Qt::RegExp.new("(#{type_rx}).*;.*#{name_rx}")
    auto_open
end