metaruby 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/History.txt +1 -0
- data/Manifest.txt +40 -0
- data/README.md +318 -0
- data/Rakefile +39 -0
- data/lib/metaruby/class.rb +120 -0
- data/lib/metaruby/dsls/doc.rb +78 -0
- data/lib/metaruby/dsls/find_through_method_missing.rb +76 -0
- data/lib/metaruby/dsls.rb +2 -0
- data/lib/metaruby/gui/exception_view.rb +124 -0
- data/lib/metaruby/gui/html/button.rb +65 -0
- data/lib/metaruby/gui/html/collection.rb +103 -0
- data/lib/metaruby/gui/html/exception_view.css +8 -0
- data/lib/metaruby/gui/html/jquery.min.js +154 -0
- data/lib/metaruby/gui/html/jquery.selectfilter.js +65 -0
- data/lib/metaruby/gui/html/jquery.tagcloud.min.js +8 -0
- data/lib/metaruby/gui/html/jquery.tinysort.min.js +8 -0
- data/lib/metaruby/gui/html/list.rhtml +24 -0
- data/lib/metaruby/gui/html/page.css +55 -0
- data/lib/metaruby/gui/html/page.rb +283 -0
- data/lib/metaruby/gui/html/page.rhtml +13 -0
- data/lib/metaruby/gui/html/page_body.rhtml +17 -0
- data/lib/metaruby/gui/html/rock-website.css +694 -0
- data/lib/metaruby/gui/html.rb +16 -0
- data/lib/metaruby/gui/model_browser.rb +262 -0
- data/lib/metaruby/gui/model_selector.rb +266 -0
- data/lib/metaruby/gui/rendering_manager.rb +112 -0
- data/lib/metaruby/gui/ruby_constants_item_model.rb +253 -0
- data/lib/metaruby/gui.rb +9 -0
- data/lib/metaruby/inherited_attribute.rb +482 -0
- data/lib/metaruby/module.rb +158 -0
- data/lib/metaruby/registration.rb +157 -0
- data/lib/metaruby/test.rb +79 -0
- data/lib/metaruby.rb +17 -0
- data/manifest.xml +12 -0
- data/test/suite.rb +15 -0
- data/test/test_attributes.rb +323 -0
- data/test/test_class.rb +68 -0
- data/test/test_dsls.rb +49 -0
- data/test/test_module.rb +105 -0
- data/test/test_registration.rb +182 -0
- metadata +160 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'metaruby/gui/html/button'
|
2
|
+
require 'metaruby/gui/html/page'
|
3
|
+
require 'metaruby/gui/html/collection'
|
4
|
+
|
5
|
+
module MetaRuby
|
6
|
+
module GUI
|
7
|
+
module HTML
|
8
|
+
def self.escape_html(string)
|
9
|
+
string.
|
10
|
+
gsub('<', '<').
|
11
|
+
gsub('>', '>')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,262 @@
|
|
1
|
+
module MetaRuby
|
2
|
+
module GUI
|
3
|
+
# Widget that allows to browse the currently available models and
|
4
|
+
# display information about them
|
5
|
+
#
|
6
|
+
# It contains a model selection widget, which lists all available models
|
7
|
+
# and allows to select them, and a visualization pane in which the
|
8
|
+
# corresponding model visualizations are rendered as HTML
|
9
|
+
#
|
10
|
+
# The object display itself is delegated to rendering objects. These
|
11
|
+
# objects must respond to:
|
12
|
+
#
|
13
|
+
# #enable: enable this renderer. This is called so that the rendering
|
14
|
+
# object listens to relevant Qt signals if it has e.g. the ability to
|
15
|
+
# interact with the user through HTML buttons
|
16
|
+
# #disable: disables this renderer. This is called so that the rendering
|
17
|
+
# object can stop listening to relevant Qt signals if it has e.g. the ability to
|
18
|
+
# interact with the user through HTML buttons
|
19
|
+
# #clear: clear existing data
|
20
|
+
# #render(model): render the given model
|
21
|
+
#
|
22
|
+
class ModelBrowser < Qt::Widget
|
23
|
+
# @return [ModelSelector] the widget that lists available models and
|
24
|
+
# allows to select them
|
25
|
+
attr_reader :model_selector
|
26
|
+
# @return [Page] the page object that handles compositing the
|
27
|
+
# results of different rendering objects, as well as the ability
|
28
|
+
# to e.g. handle buttons
|
29
|
+
attr_reader :page
|
30
|
+
# @return [Qt::WebView] the HTML view widget
|
31
|
+
attr_reader :display
|
32
|
+
# @return [ExceptionView] view that allows to display errors to the
|
33
|
+
# user
|
34
|
+
attr_reader :exception_view
|
35
|
+
# @return [Qt::PushButton] button that causes model reloading
|
36
|
+
attr_reader :btn_reload_models
|
37
|
+
# @return [RenderingManager] the object that manages all the
|
38
|
+
# rendering objects available
|
39
|
+
attr_reader :manager
|
40
|
+
# @return [Array<Exception>] set of exceptions raised during the
|
41
|
+
# last rendering step
|
42
|
+
attr_reader :registered_exceptions
|
43
|
+
# @return [Array<Model,[String]>] the browsing history, as either
|
44
|
+
# direct modules or module name path (suitable to be given to
|
45
|
+
# #select_by_path)
|
46
|
+
attr_reader :history
|
47
|
+
# @return [Integer] the index of the current link in the history
|
48
|
+
attr_reader :history_index
|
49
|
+
|
50
|
+
# A Page object with a #link_to method that is suitable for the
|
51
|
+
# model browser
|
52
|
+
class Page < HTML::Page
|
53
|
+
def uri_for(object)
|
54
|
+
if (obj_name = object.name) && (obj_name =~ /^[\w:]+$/)
|
55
|
+
path = obj_name.split("::")
|
56
|
+
"/" + path.join("/")
|
57
|
+
else super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def initialize(main = nil)
|
64
|
+
super
|
65
|
+
|
66
|
+
@manager = RenderingManager.new
|
67
|
+
|
68
|
+
main_layout = Qt::VBoxLayout.new(self)
|
69
|
+
|
70
|
+
menu_layout = Qt::HBoxLayout.new
|
71
|
+
main_layout.add_layout(menu_layout)
|
72
|
+
central_layout = Qt::HBoxLayout.new
|
73
|
+
main_layout.add_layout(central_layout, 3)
|
74
|
+
splitter = Qt::Splitter.new(self)
|
75
|
+
central_layout.add_widget(splitter)
|
76
|
+
@exception_view = ExceptionView.new
|
77
|
+
main_layout.add_widget(exception_view, 1)
|
78
|
+
|
79
|
+
@available_renderers = Hash.new
|
80
|
+
@registered_exceptions = Array.new
|
81
|
+
|
82
|
+
@btn_reload_models = Qt::PushButton.new("Reload", self)
|
83
|
+
menu_layout.add_widget(btn_reload_models)
|
84
|
+
menu_layout.add_stretch(1)
|
85
|
+
update_exceptions
|
86
|
+
|
87
|
+
@history = Array.new
|
88
|
+
@history_index = -1
|
89
|
+
|
90
|
+
add_central_widgets(splitter)
|
91
|
+
setTabOrder(model_selector, display)
|
92
|
+
setTabOrder(display, btn_reload_models)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Update the model selector after {register_type} got called
|
96
|
+
def update_model_selector
|
97
|
+
model_selector.update
|
98
|
+
end
|
99
|
+
|
100
|
+
# Registers a certain kind of model as well as the information
|
101
|
+
# needed to display it
|
102
|
+
#
|
103
|
+
# It registers the given type on the model browser so that it gets
|
104
|
+
# displayed there.
|
105
|
+
#
|
106
|
+
# You must call {update_model_selector} after this call for the
|
107
|
+
# modification to have any effect (i.e. for the newly registered
|
108
|
+
# models to appear on the selector)
|
109
|
+
#
|
110
|
+
# @param [Model] type the base model class for the models that are
|
111
|
+
# considered here
|
112
|
+
# @param [Class] rendering_class a class from which a relevant
|
113
|
+
# rendering object can be created. The generated instances must
|
114
|
+
# follow the rules described in the documentation of
|
115
|
+
# {ModelBrowser}
|
116
|
+
# @param [String] name the name that should be used for this
|
117
|
+
# category
|
118
|
+
# @param [Integer] priority the priority of this category. Some
|
119
|
+
# models might be submodels of various types at the same time (as
|
120
|
+
# e.g. when both a model and its supermodel are registered here).
|
121
|
+
# The one with the highest priority will be used.
|
122
|
+
def register_type(type, rendering_class, name, priority = 0)
|
123
|
+
model_selector.register_type(type, name, priority)
|
124
|
+
manager.register_type(type, rendering_class)
|
125
|
+
end
|
126
|
+
|
127
|
+
# Sets up the widgets that form the central part of the browser
|
128
|
+
def add_central_widgets(splitter)
|
129
|
+
@model_selector = ModelSelector.new
|
130
|
+
splitter.add_widget(model_selector)
|
131
|
+
|
132
|
+
# Create a central stacked layout
|
133
|
+
display = @display = Qt::WebView.new
|
134
|
+
browser = self
|
135
|
+
display.singleton_class.class_eval do
|
136
|
+
define_method :contextMenuEvent do |event|
|
137
|
+
menu = Qt::Menu.new(self)
|
138
|
+
act = page.action(Qt::WebPage::Back)
|
139
|
+
act.enabled = true
|
140
|
+
menu.add_action act
|
141
|
+
connect(act, SIGNAL(:triggered), browser, SLOT(:back))
|
142
|
+
act = page.action(Qt::WebPage::Forward)
|
143
|
+
act.enabled = true
|
144
|
+
connect(act, SIGNAL(:triggered), browser, SLOT(:forward))
|
145
|
+
menu.add_action act
|
146
|
+
menu.popup(event.globalPos)
|
147
|
+
event.accept
|
148
|
+
end
|
149
|
+
end
|
150
|
+
splitter.add_widget(display)
|
151
|
+
splitter.set_stretch_factor(1, 2)
|
152
|
+
self.page = Page.new(display.page)
|
153
|
+
|
154
|
+
model_selector.connect(SIGNAL('model_selected(QVariant)')) do |mod|
|
155
|
+
mod = mod.to_ruby
|
156
|
+
push_to_history(mod)
|
157
|
+
render_model(mod)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Sets the page object that should be used for rendering
|
162
|
+
#
|
163
|
+
# @param [Page] page the new page object
|
164
|
+
def page=(page)
|
165
|
+
manager.page = page
|
166
|
+
page.connect(SIGNAL('linkClicked(const QUrl&)')) do |url|
|
167
|
+
if url.scheme == "link"
|
168
|
+
path = url.path
|
169
|
+
path = path.split('/')[1..-1]
|
170
|
+
select_by_path(*path)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
connect(page, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
|
174
|
+
connect(manager, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
|
175
|
+
@page = page
|
176
|
+
end
|
177
|
+
|
178
|
+
# Call to render the given model
|
179
|
+
#
|
180
|
+
# @param [Model] mod the model that should be rendered
|
181
|
+
# @raises [ArgumentError] if there is no view available for the
|
182
|
+
# given model
|
183
|
+
def render_model(mod, options = Hash.new)
|
184
|
+
page.clear
|
185
|
+
@registered_exceptions.clear
|
186
|
+
reference_model, _ = manager.find_renderer(mod)
|
187
|
+
if mod
|
188
|
+
page.title = "#{mod.name} (#{reference_model.name})"
|
189
|
+
begin
|
190
|
+
manager.render(mod, options)
|
191
|
+
rescue ::Exception => e
|
192
|
+
@registered_exceptions << e
|
193
|
+
end
|
194
|
+
else
|
195
|
+
@registered_exceptions << ArgumentError.new("no view available for #{mod} (#{mod.class})")
|
196
|
+
end
|
197
|
+
update_exceptions
|
198
|
+
end
|
199
|
+
|
200
|
+
# Updates {#exception_view} from the set of registered exceptions
|
201
|
+
def update_exceptions
|
202
|
+
exception_view.exceptions = registered_exceptions +
|
203
|
+
manager.registered_exceptions
|
204
|
+
end
|
205
|
+
slots 'update_exceptions()'
|
206
|
+
|
207
|
+
# (see ModelSelector#select_by_module)
|
208
|
+
def select_by_path(*path)
|
209
|
+
if model_selector.select_by_path(*path)
|
210
|
+
push_to_history(path)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# (see ModelSelector#select_by_module)
|
215
|
+
def select_by_module(model)
|
216
|
+
if model_selector.select_by_module(model)
|
217
|
+
push_to_history(model)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# (see ModelSelector#current_selection)
|
222
|
+
def current_selection
|
223
|
+
model_selector.current_selection
|
224
|
+
end
|
225
|
+
|
226
|
+
# Pushes one element in the history
|
227
|
+
#
|
228
|
+
# If the history index is not at the end, the remainder is discarded
|
229
|
+
def push_to_history(object)
|
230
|
+
return if object == history[history_index]
|
231
|
+
|
232
|
+
@history = history[0, history_index + 1]
|
233
|
+
history << object
|
234
|
+
@history_index = history.size - 1
|
235
|
+
end
|
236
|
+
|
237
|
+
# Go forward in the browsing history
|
238
|
+
def forward
|
239
|
+
return if history_index == history.size - 1
|
240
|
+
@history_index += 1
|
241
|
+
select_by_history_element(history[history_index])
|
242
|
+
end
|
243
|
+
|
244
|
+
# Go back in the browsing history
|
245
|
+
def back
|
246
|
+
return if history_index <= 0
|
247
|
+
@history_index -= 1
|
248
|
+
select_by_history_element(history[history_index])
|
249
|
+
end
|
250
|
+
|
251
|
+
slots :back, :forward
|
252
|
+
|
253
|
+
# Selects a given model based on a value in the history
|
254
|
+
def select_by_history_element(h)
|
255
|
+
if h.respond_to?(:to_ary)
|
256
|
+
select_by_path(*h)
|
257
|
+
else select_by_module(h)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
module MetaRuby
|
2
|
+
module GUI
|
3
|
+
# A Qt widget that allows to browse the models registered in the Ruby
|
4
|
+
# constanat hierarchy
|
5
|
+
class ModelSelector < Qt::Widget
|
6
|
+
attr_reader :btn_type_filter_menu
|
7
|
+
attr_reader :type_filters
|
8
|
+
attr_reader :model_list
|
9
|
+
attr_reader :model_filter
|
10
|
+
# @return [Qt::LineEdit] the line edit widget that allows to modify
|
11
|
+
# the tree view filter
|
12
|
+
attr_reader :filter_box
|
13
|
+
# @return [Qt::Completer] auto-completion for {filter_box}
|
14
|
+
attr_reader :filter_completer
|
15
|
+
attr_reader :type_info
|
16
|
+
attr_reader :browser_model
|
17
|
+
|
18
|
+
def initialize(parent = nil)
|
19
|
+
super
|
20
|
+
|
21
|
+
@type_info = Hash.new
|
22
|
+
@type_filters = Hash.new
|
23
|
+
|
24
|
+
layout = Qt::VBoxLayout.new(self)
|
25
|
+
filter_button = Qt::PushButton.new('Filters', self)
|
26
|
+
layout.add_widget(filter_button)
|
27
|
+
@btn_type_filter_menu = Qt::Menu.new
|
28
|
+
filter_button.menu = btn_type_filter_menu
|
29
|
+
|
30
|
+
setup_tree_view(layout)
|
31
|
+
setTabOrder(filter_box, filter_button)
|
32
|
+
setTabOrder(filter_button, model_list)
|
33
|
+
end
|
34
|
+
|
35
|
+
def register_type(model_base, name, priority = 0)
|
36
|
+
type_info[model_base] = RubyConstantsItemModel::TypeInfo.new(name, priority)
|
37
|
+
action = Qt::Action.new(name, self)
|
38
|
+
action.checkable = true
|
39
|
+
action.checked = true
|
40
|
+
type_filters[model_base] = action
|
41
|
+
btn_type_filter_menu.add_action(action)
|
42
|
+
connect(action, SIGNAL('triggered()')) do
|
43
|
+
update_model_filter
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def update
|
48
|
+
update_model_filter
|
49
|
+
reload
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_model_filter
|
53
|
+
type_rx = type_filters.map do |model_base, act|
|
54
|
+
if act.checked?
|
55
|
+
type_info[model_base].name
|
56
|
+
end
|
57
|
+
end
|
58
|
+
type_rx = type_rx.compact.join("|")
|
59
|
+
|
60
|
+
model_filter.filter_role = Qt::UserRole # this contains the keywords (ancestry and/or name)
|
61
|
+
# This workaround a problem that I did not have time to
|
62
|
+
# investigate. Adding new characters to the filter updates the
|
63
|
+
# browser just fine, while removing some characters does not
|
64
|
+
#
|
65
|
+
# This successfully resets the filter
|
66
|
+
model_filter.filter_reg_exp = Qt::RegExp.new("")
|
67
|
+
# The pattern has to match every element in the hierarchy. We
|
68
|
+
# achieve this by making the suffix part optional
|
69
|
+
name_rx = filter_box.text.downcase.gsub(/:+/, "/")
|
70
|
+
model_filter.filter_reg_exp = Qt::RegExp.new("(#{type_rx}).*;.*#{name_rx}")
|
71
|
+
auto_open
|
72
|
+
end
|
73
|
+
|
74
|
+
def auto_open(threshold = 5)
|
75
|
+
current_level = [Qt::ModelIndex.new]
|
76
|
+
while !current_level.empty?
|
77
|
+
count = current_level.inject(0) do |total, index|
|
78
|
+
total + model_filter.rowCount(index)
|
79
|
+
end
|
80
|
+
close_this_level = (count > threshold)
|
81
|
+
current_level.each do |index|
|
82
|
+
model_filter.rowCount(index).times.each do |row|
|
83
|
+
model_list.setExpanded(model_filter.index(row, 0, index), !close_this_level)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
return if close_this_level
|
87
|
+
|
88
|
+
last_level, current_level = current_level, []
|
89
|
+
last_level.each do |index|
|
90
|
+
model_filter.rowCount(index).times.each do |row|
|
91
|
+
current_level << model_filter.index(row, 0, index)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def model?(obj)
|
98
|
+
type_info.any? do |model_base, _|
|
99
|
+
obj.kind_of?(model_base) ||
|
100
|
+
(obj.kind_of?(Module) && obj <= model_base)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class ModelPathCompleter < Qt::Completer
|
105
|
+
def splitPath(path)
|
106
|
+
path.split('/')
|
107
|
+
end
|
108
|
+
def pathFromIndex(index)
|
109
|
+
index.data(Qt::UserRole).split(";").last
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def all_leaves(model, limit = nil, item = Qt::ModelIndex.new, result = [])
|
114
|
+
if !model.hasChildren(item)
|
115
|
+
result << item
|
116
|
+
return result
|
117
|
+
end
|
118
|
+
|
119
|
+
row, child_item = 0, model.index(0, 0, item)
|
120
|
+
while child_item.valid?
|
121
|
+
all_leaves(model, limit, child_item, result)
|
122
|
+
if limit && result.size == limit
|
123
|
+
return result
|
124
|
+
end
|
125
|
+
row += 1
|
126
|
+
child_item = model.index(row, 0, item)
|
127
|
+
end
|
128
|
+
return result
|
129
|
+
end
|
130
|
+
|
131
|
+
def select_first_item
|
132
|
+
if item = all_leaves(model_filter, 1).first
|
133
|
+
model_list.setCurrentIndex(item)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def setup_tree_view(layout)
|
138
|
+
@model_list = Qt::TreeView.new(self)
|
139
|
+
@model_filter = Qt::SortFilterProxyModel.new
|
140
|
+
model_filter.filter_case_sensitivity = Qt::CaseInsensitive
|
141
|
+
model_filter.dynamic_sort_filter = true
|
142
|
+
model_filter.filter_role = Qt::UserRole
|
143
|
+
model_list.model = model_filter
|
144
|
+
|
145
|
+
@filter_box = Qt::LineEdit.new(self)
|
146
|
+
filter_box.connect(SIGNAL('textChanged(QString)')) do |text|
|
147
|
+
update_model_filter
|
148
|
+
end
|
149
|
+
filter_box.connect(SIGNAL('returnPressed()')) do |text|
|
150
|
+
select_first_item
|
151
|
+
end
|
152
|
+
@filter_completer = ModelPathCompleter.new(browser_model, self)
|
153
|
+
filter_completer.case_sensitivity = Qt::CaseInsensitive
|
154
|
+
filter_box.completer = filter_completer
|
155
|
+
layout.add_widget(filter_box)
|
156
|
+
layout.add_widget(model_list)
|
157
|
+
|
158
|
+
model_list.selection_model.connect(SIGNAL('currentChanged(const QModelIndex&, const QModelIndex&)')) do |index, _|
|
159
|
+
index = model_filter.map_to_source(index)
|
160
|
+
mod = browser_model.info_from_index(index)
|
161
|
+
if model?(mod.this)
|
162
|
+
emit model_selected(Qt::Variant.from_ruby(mod.this, mod.this))
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
signals 'model_selected(QVariant)'
|
167
|
+
|
168
|
+
def reload
|
169
|
+
if current = current_selection
|
170
|
+
current_module = current.this
|
171
|
+
current_path = []
|
172
|
+
while current
|
173
|
+
current_path.unshift current.name
|
174
|
+
current = current.parent
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
@browser_model = RubyConstantsItemModel.new(type_info) do |mod|
|
179
|
+
model?(mod)
|
180
|
+
end
|
181
|
+
browser_model.reload
|
182
|
+
model_filter.source_model = browser_model
|
183
|
+
|
184
|
+
if current_path && !select_by_path(*current_path)
|
185
|
+
select_by_module(current_module)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Resets the current filter
|
190
|
+
def reset_filter
|
191
|
+
# If there is a filter, reset it and try again
|
192
|
+
if !filter_box.text.empty?
|
193
|
+
filter_box.text = ""
|
194
|
+
true
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Maps a model index from the source index to the filtered index,
|
199
|
+
# e.g. to select something in the view.
|
200
|
+
#
|
201
|
+
# In addition to the normal map_from_source call, it allows to
|
202
|
+
# control whether the filter should be reset if the index given as
|
203
|
+
# parameter is currently filtered out
|
204
|
+
#
|
205
|
+
# @param [Qt::ModelIndex] an index valid in {browser_model}
|
206
|
+
# @option options [Boolean] :reset_filter (true) if true, the filter
|
207
|
+
# is reset if the requested index is currently filtered out
|
208
|
+
# @return [Qt::ModelIndex] an index filtered by {model_filter}
|
209
|
+
def map_index_from_source(source_index, options = Hash.new)
|
210
|
+
options = Kernel.validate_options options, :reset_filter => true
|
211
|
+
index = model_filter.map_from_source(source_index)
|
212
|
+
if !index
|
213
|
+
return
|
214
|
+
elsif !index.valid?
|
215
|
+
if !options[:reset_filter]
|
216
|
+
return index
|
217
|
+
end
|
218
|
+
reset_filter
|
219
|
+
model_filter.map_from_source(source_index)
|
220
|
+
else index
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Selects the current model given a path in the constant names
|
225
|
+
# This emits the model_selected signal
|
226
|
+
#
|
227
|
+
# @return [Boolean] true if the path resolved to something known,
|
228
|
+
# and false otherwise
|
229
|
+
def select_by_path(*path)
|
230
|
+
if index = browser_model.find_index_by_path(*path)
|
231
|
+
index = map_index_from_source(index)
|
232
|
+
model_list.selection_model.set_current_index(index, Qt::ItemSelectionModel::ClearAndSelect)
|
233
|
+
true
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Selects the given model if it registered in the model list
|
238
|
+
# This emits the model_selected signal
|
239
|
+
#
|
240
|
+
# @return [Boolean] true if the path resolved to something known,
|
241
|
+
# and false otherwise
|
242
|
+
def select_by_module(model)
|
243
|
+
if index = browser_model.find_index_by_model(model)
|
244
|
+
index = map_index_from_source(index)
|
245
|
+
model_list.selection_model.set_current_index(index, Qt::ItemSelectionModel::ClearAndSelect)
|
246
|
+
true
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
# Returns the currently selected item
|
251
|
+
# @return [RubyModuleModel::ModuleInfo,nil] nil if there are no
|
252
|
+
# selections
|
253
|
+
def current_selection
|
254
|
+
index = model_list.selection_model.current_index
|
255
|
+
if index.valid?
|
256
|
+
index = model_filter.map_to_source(index)
|
257
|
+
browser_model.info_from_index(index)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def object_paths
|
262
|
+
browser_model.object_paths
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module MetaRuby
|
2
|
+
module GUI
|
3
|
+
class RenderingManager < Qt::Object
|
4
|
+
# @return [#push] the page object on which we render
|
5
|
+
attr_reader :page
|
6
|
+
# @return [{Model=>Object}] set of rendering objects that are
|
7
|
+
# declared. The Model is a class or module that represents the
|
8
|
+
# set of models that the renderer can handle.
|
9
|
+
# It is ordered by order of priority, i.e. the first model that
|
10
|
+
# matches will be used.
|
11
|
+
# Do not modify directly, use {#register_type} instead
|
12
|
+
attr_reader :available_renderers
|
13
|
+
# The last used rendering objects
|
14
|
+
attr_reader :current_renderer
|
15
|
+
|
16
|
+
def initialize(page = nil)
|
17
|
+
super()
|
18
|
+
@page = page
|
19
|
+
@available_renderers = Hash.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def registered_exceptions
|
23
|
+
if current_renderer.respond_to?(:registered_exceptions)
|
24
|
+
current_renderer.registered_exceptions
|
25
|
+
else []
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Registers a certain kind of model as well as the information
|
30
|
+
# needed to display it
|
31
|
+
#
|
32
|
+
# It registers the given type on the model browser so that it gets
|
33
|
+
# displayed there.
|
34
|
+
#
|
35
|
+
# @param [Class] type the base model class for the models that are
|
36
|
+
# considered here
|
37
|
+
# @param [Class] rendering_class a class from which a relevant
|
38
|
+
# rendering object can be created. The generated instances must
|
39
|
+
# follow the rules described in the documentation of
|
40
|
+
# {ModelBrowser}
|
41
|
+
def register_type(type, rendering_class, render_options = Hash.new)
|
42
|
+
render = if rendering_class.kind_of?(Class)
|
43
|
+
rendering_class.new(page)
|
44
|
+
else
|
45
|
+
rendering_class
|
46
|
+
end
|
47
|
+
available_renderers[type] = [render, render_options]
|
48
|
+
connect(render, SIGNAL('updated()'), self, SIGNAL('updated()'))
|
49
|
+
end
|
50
|
+
|
51
|
+
# Changes on which page this rendering manager should act
|
52
|
+
def page=(page)
|
53
|
+
return if @page == page
|
54
|
+
@page = page
|
55
|
+
@available_renderers = available_renderers.map_value do |key, (value, render_options)|
|
56
|
+
new_render = value.class.new(page)
|
57
|
+
disconnect(value, SIGNAL('updated()'))
|
58
|
+
connect(new_render, SIGNAL('updated()'), self, SIGNAL('updated()'))
|
59
|
+
[new_render, render_options]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def find_renderer(mod)
|
64
|
+
available_renderers.find do |model, _|
|
65
|
+
mod.kind_of?(model) || (mod.kind_of?(Module) && model.kind_of?(Module) && mod <= model)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def disable
|
70
|
+
if current_renderer
|
71
|
+
current_renderer.disable
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def enable
|
76
|
+
if current_renderer
|
77
|
+
current_renderer.enable
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear
|
82
|
+
if current_renderer
|
83
|
+
current_renderer.clear
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
# Call to render the given model
|
89
|
+
#
|
90
|
+
# @param [Model] mod the model that should be rendered
|
91
|
+
# @raises [ArgumentError] if there is no view available for the
|
92
|
+
# given model
|
93
|
+
def render(object, push_options = Hash.new)
|
94
|
+
_, (renderer, render_options) = find_renderer(object)
|
95
|
+
if renderer
|
96
|
+
if current_renderer
|
97
|
+
current_renderer.clear
|
98
|
+
current_renderer.disable
|
99
|
+
end
|
100
|
+
renderer.enable
|
101
|
+
renderer.render(object, render_options.merge(push_options))
|
102
|
+
@current_renderer = renderer
|
103
|
+
else
|
104
|
+
Kernel.raise ArgumentError, "no view available for #{object} (#{object.class})"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
signals 'updated()'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|