metaruby 1.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|