metaruby 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,65 @@
1
+ require 'metaruby/attributes'
2
+
1
3
  module MetaRuby
2
4
  module DSLs
5
+ # Common definition of #respond_to_missing? and #method_missing to be
6
+ # used in conjunction with {DSLs.find_through_method_missing} and
7
+ # {DSLs.has_through_method_missing?}
8
+ #
9
+ # @example resolve 'event' objects using method_missing
10
+ # class Task
11
+ # # Tests if this task has an event by this name
12
+ # #
13
+ # # @param [String] name
14
+ # # @return [Boolean]
15
+ # def has_event?(name)
16
+ # end
17
+ #
18
+ # # Finds an event by name
19
+ # #
20
+ # # @param [String] name
21
+ # # @return [Object,nil] the found event, or nil if there is no
22
+ # # event by this name
23
+ # def find_event(name)
24
+ # end
25
+ #
26
+ # include MetaRuby::DSLs::FindThroughMethodMissing
27
+ #
28
+ # # Check if the given method matches a find object
29
+ # def has_through_method_missing?(m)
30
+ # MetaRuby::DSLs.has_through_method_missing?(
31
+ # self, m, '_event' => :has_event?) || super
32
+ # end
33
+ #
34
+ # # Check if the given method matches a find object
35
+ # def find_through_method_missing(m, args)
36
+ # MetaRuby::DSLs.find_through_method_missing(
37
+ # self, m, args, '_event' => :find_event) || super
38
+ # end
39
+ # end
40
+ #
41
+ module FindThroughMethodMissing
42
+ # Empty implementation of has_through_method_missing? to allow for
43
+ # classes to call 'super'
44
+ def has_through_method_missing?(m)
45
+ end
46
+
47
+ # Empty implementation of find_through_method_missing to allow for
48
+ # classes to call 'super'
49
+ def find_through_method_missing(m, args)
50
+ end
51
+
52
+ # Resolves the given method using {#has_through_method_missing?}
53
+ def respond_to_missing?(m, include_private)
54
+ has_through_method_missing?(m) || super
55
+ end
56
+
57
+ # Resolves the given method using {#find_through_method_missing}
58
+ def method_missing(m, *args)
59
+ find_through_method_missing(m, args) || super
60
+ end
61
+ end
62
+
3
63
  # Generic implementation to create suffixed accessors for child objects
4
64
  # on a class
5
65
  #
@@ -45,32 +105,34 @@ module MetaRuby
45
105
  # object.add_state 'my'
46
106
  # object.my_state # will resolve the 'my' state
47
107
  #
48
- def self.find_through_method_missing(object, m, args, *suffixes)
49
- suffix_match = Hash.new
50
- if suffixes.last.kind_of?(Hash)
51
- suffix_match.merge!(suffixes.pop)
52
- end
53
- suffixes.each do |name|
54
- suffix_match[name] = "find_#{name}"
55
- end
108
+ def self.find_through_method_missing(object, m, args, suffix_match)
109
+ return false if m == :to_ary
56
110
 
57
111
  m = m.to_s
58
112
  suffix_match.each do |s, find_method_name|
59
- if m == find_method_name
60
- raise NoMethodError.new("#{object} has no method called #{find_method_name}", m)
61
- elsif m =~ /(.*)_#{s}$/
62
- name = $1
113
+ if m.end_with?(s)
114
+ name = m[0, m.size - s.size]
63
115
  if !args.empty?
64
116
  raise ArgumentError, "expected zero arguments to #{m}, got #{args.size}", caller(4)
65
- elsif found = object.send(find_method_name, name)
66
- return found
67
117
  else
68
- msg = "#{object} has no #{s} named #{name}"
69
- raise NoMethodError.new(msg, m), msg, caller(4)
118
+ return object.send(find_method_name, name)
70
119
  end
71
120
  end
72
121
  end
73
122
  nil
74
123
  end
124
+
125
+ def self.has_through_method_missing?(object, m, suffix_match)
126
+ return false if m == :to_ary
127
+
128
+ m = m.to_s
129
+ suffix_match.each do |s, has_method_name|
130
+ if m.end_with?(s)
131
+ name = m[0, m.size - s.size]
132
+ return !!object.send(has_method_name, name)
133
+ end
134
+ end
135
+ false
136
+ end
75
137
  end
76
138
  end
@@ -4,6 +4,7 @@ require 'kramdown'
4
4
  require 'pp'
5
5
  require 'metaruby/gui/html'
6
6
  require 'metaruby/gui/ruby_constants_item_model'
7
+ require 'metaruby/gui/model_hierarchy'
7
8
  require 'metaruby/gui/rendering_manager'
8
9
  require 'metaruby/gui/model_browser'
9
10
  require 'metaruby/gui/model_selector'
@@ -1,6 +1,6 @@
1
- .message { font-size: 80%; font-weight: normal; background-color: rgb(176,206,234); text-align: left; }
2
- .backtrace_links { font-size: 80%; margin-left: 2em;}
3
- .backtrace { font-size: 80%; background-color: rgb(219,230,241); padding-left: 1em; }
4
- .backtrace_summary { font-size: 80%; background-color: rgb(219,230,241); padding-left: 1em; }
1
+ .message { font-size: 100%; font-weight: normal; background-color: rgb(176,206,234); text-align: left; }
2
+ .backtrace_links { font-size: 100%; margin-left: 2em;}
3
+ .backtrace { font-size: 100%; background-color: rgb(219,230,241); padding-left: 1em; }
4
+ .backtrace_summary { font-size: 100%; background-color: rgb(219,230,241); padding-left: 1em; }
5
5
  .user_file { font-weight: bold; }
6
6
 
@@ -3,8 +3,5 @@
3
3
  <% end %>
4
4
  <% fragments.each do |fragment| %>
5
5
  <%= html_fragment(fragment, ressource_dir: ressource_dir) %>
6
- <% if fragment.id %>
7
- </div>
8
- <% end %>
9
6
  <% end %>
10
7
 
@@ -57,14 +57,19 @@ module MetaRuby
57
57
  # A Page object tunes to create URIs for objects that are suitable
58
58
  # for {#model_selector}
59
59
  class Page < HTML::Page
60
+ def initialize(model_selector, display_page)
61
+ super(display_page)
62
+ @model_selector = model_selector
63
+ end
64
+
60
65
  # Overloaded from {HTML::Page} to resolve object paths (in the
61
66
  # constant hierarchy, e.g. A::B::C) into the corresponding
62
67
  # path expected by {#model_selector} (e.g. /A/B/C)
63
68
  def uri_for(object)
64
- if object.respond_to?(:name) && (obj_name = object.name) && (obj_name =~ /^[\w:]+$/)
65
- path = obj_name.split("::")
66
- "/" + path.join("/")
67
- else super
69
+ if resolver = @model_selector.find_resolver_from_model(object)
70
+ "/" + resolver.split_name(object).join("/")
71
+ else
72
+ super
68
73
  end
69
74
  end
70
75
  end
@@ -151,9 +156,11 @@ module MetaRuby
151
156
  # models might be submodels of various types at the same time (as
152
157
  # e.g. when both a model and its supermodel are registered here).
153
158
  # The one with the highest priority will be used.
154
- def register_type(type, rendering_class, name, priority = 0)
155
- model_selector.register_type(type, name, priority)
156
- manager.register_type(type, rendering_class)
159
+ def register_type(root_model, rendering_class, name, priority = 0, categories: [], resolver: ModelHierarchy::Resolver.new)
160
+ model_selector.register_type(
161
+ root_model, name, priority,
162
+ categories: categories, resolver: resolver)
163
+ manager.register_type(root_model, rendering_class)
157
164
  end
158
165
 
159
166
  # Sets up the widgets that form the central part of the browser
@@ -181,7 +188,7 @@ module MetaRuby
181
188
  end
182
189
  splitter.add_widget(display)
183
190
  splitter.set_stretch_factor(1, 2)
184
- self.page = Page.new(display.page)
191
+ self.page = Page.new(@model_selector, display.page)
185
192
 
186
193
  model_selector.connect(SIGNAL('model_selected(QVariant)')) do |mod|
187
194
  mod = mod.to_ruby
@@ -194,20 +201,28 @@ module MetaRuby
194
201
  #
195
202
  # @param [Page] page the new page object
196
203
  def page=(page)
197
- manager.page = page
198
- page.connect(SIGNAL('linkClicked(const QUrl&)')) do |url|
199
- if url.scheme == "link"
200
- path = url.path
201
- path = path.split('/')[1..-1]
202
- select_by_path(*path)
203
- end
204
+ if @page
205
+ disconnect(@page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('linkClicked(const QUrl&)'))
206
+ disconnect(@page, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
207
+ disconnect(@page, SIGNAL('fileOpenClicked(const QUrl&)'), self, SLOT('fileOpenClicked(const QUrl&)'))
204
208
  end
209
+ manager.page = page
210
+ connect(page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('linkClicked(const QUrl&)'))
205
211
  connect(page, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
206
212
  connect(page, SIGNAL('fileOpenClicked(const QUrl&)'), self, SLOT('fileOpenClicked(const QUrl&)'))
207
213
  connect(manager, SIGNAL('updated()'), self, SLOT('update_exceptions()'))
208
214
  @page = page
209
215
  end
210
216
 
217
+ def linkClicked(url)
218
+ if url.scheme == "link"
219
+ path = url.path
220
+ path = path.split('/')[1..-1]
221
+ select_by_path(*path)
222
+ end
223
+ end
224
+ slots 'linkClicked(const QUrl&)'
225
+
211
226
  signals 'fileOpenClicked(const QUrl&)'
212
227
 
213
228
  # Call to render the given model
@@ -239,16 +254,16 @@ module MetaRuby
239
254
  end
240
255
  slots 'update_exceptions()'
241
256
 
242
- # (see ModelSelector#select_by_module)
257
+ # (see ModelSelector#select_by_model)
243
258
  def select_by_path(*path)
244
259
  if model_selector.select_by_path(*path)
245
260
  push_to_history(path)
246
261
  end
247
262
  end
248
263
 
249
- # (see ModelSelector#select_by_module)
250
- def select_by_module(model)
251
- if model_selector.select_by_module(model)
264
+ # (see ModelSelector#select_by_model)
265
+ def select_by_model(model)
266
+ if model_selector.select_by_model(model)
252
267
  push_to_history(model)
253
268
  end
254
269
  end
@@ -289,7 +304,7 @@ module MetaRuby
289
304
  def select_by_history_element(h)
290
305
  if h.respond_to?(:to_ary)
291
306
  select_by_path(*h)
292
- else select_by_module(h)
307
+ else select_by_model(h)
293
308
  end
294
309
  end
295
310
 
@@ -0,0 +1,267 @@
1
+ module MetaRuby
2
+ module GUI
3
+ class ModelHierarchy < Qt::StandardItemModel
4
+ Metadata = Struct.new :name, :search_key, :categories do
5
+ def merge(other)
6
+ a, b = search_key, other.search_key.dup
7
+ if a.size < b.size
8
+ a, b = b, a
9
+ end
10
+ b.size.times do |i|
11
+ a[i] |= b[i]
12
+ end
13
+ self.search_key = a
14
+ self.categories = categories | other.categories
15
+ end
16
+
17
+ def to_user_role
18
+ categories = self.categories.to_a.join(",")
19
+ search_key = self.search_key.map do |level_keys|
20
+ level_keys.join(",")
21
+ end.join(",;,")
22
+ ",#{categories},;,#{search_key},"
23
+ end
24
+ end
25
+
26
+ class Resolver
27
+ def initialize(root_model)
28
+ @root_model = root_model
29
+ end
30
+
31
+ def split_name(model)
32
+ name = model.name
33
+ split = model.name.split('::')
34
+ if name.start_with?('::')
35
+ split[1..-1]
36
+ else split
37
+ end
38
+ end
39
+
40
+ def each_submodel(model)
41
+ if model == @root_model
42
+ model.each_submodel do |m|
43
+ yield(m, !m.name)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def initialize
50
+ super()
51
+ @root_models = Array.new
52
+ end
53
+
54
+ # Find the resolver object that has been responsible for a given
55
+ # object's discovery
56
+ #
57
+ # @return [Object,nil]
58
+ def find_resolver_from_model(model)
59
+ @resolver_from_model[model]
60
+ end
61
+
62
+ # Returns the path to the given model or nil if it is not registered
63
+ def find_path_from_model(model)
64
+ if resolver = find_resolver_from_model(model)
65
+ resolver.split_name(model)
66
+ end
67
+ end
68
+
69
+ RootModel = Struct.new :model, :priority, :categories, :resolver
70
+
71
+ def add_root(root_model, priority, categories: [], resolver: Resolver.new(root_model))
72
+ @root_models << RootModel.new(root_model, priority, categories, resolver)
73
+ end
74
+
75
+ # Refresh the model to match the current hierarchy that starts with
76
+ # this object's root model
77
+ def reload
78
+ begin_reset_model
79
+ clear
80
+
81
+ @items_to_models = Hash.new
82
+ @models_to_items = Hash.new
83
+ @names_to_item = Hash.new
84
+ @items_metadata = Hash[self => Metadata.new([], [], Set.new)]
85
+ @resolver_from_model = Hash.new
86
+
87
+ seen = Set.new
88
+ sorted_roots = @root_models.
89
+ sort_by(&:priority).reverse
90
+
91
+ sorted_roots.each do |root_model|
92
+ models = discover_model_hierarchy(root_model.model, root_model.categories, root_model.resolver, seen)
93
+ models.each do |m|
94
+ @resolver_from_model[m] = root_model.resolver
95
+ end
96
+ end
97
+
98
+ rowCount.times do |row|
99
+ compute_and_store_metadata(item(row))
100
+ end
101
+ self.horizontal_header_labels = [""]
102
+ ensure
103
+ end_reset_model
104
+ end
105
+
106
+ # Returns the model that matches the QStandardItem
107
+ #
108
+ # @return [Object,nil]
109
+ def find_model_from_item(item)
110
+ @items_to_models[item]
111
+ end
112
+
113
+ # Returns the model that matches the QModelIndex
114
+ #
115
+ # @return [Object,nil]
116
+ def find_model_from_index(index)
117
+ @items_to_models[
118
+ item_from_index(index)]
119
+ end
120
+
121
+ # @api private
122
+ #
123
+ # Compute each item's metadata and stores it in UserRole. The
124
+ # metadata is stored as
125
+ # "Category0|Category1;name/with/slashes/between/parts". This is
126
+ # meant to be used in a seach/filter function.
127
+ def compute_and_store_metadata(item)
128
+ current = @items_metadata[item]
129
+
130
+ item.rowCount.times do |row|
131
+ current.merge compute_and_store_metadata(item.child(row))
132
+ end
133
+
134
+ item.set_data(Qt::Variant.new(current.to_user_role), Qt::UserRole)
135
+ current
136
+ end
137
+
138
+ # @api private
139
+ #
140
+ # Register a model in the hierarchy
141
+ def register_model(model, model_categories, resolver)
142
+ name = resolver.split_name(model)
143
+ if !name || name.empty?
144
+ #raise ArgumentError, "cannot resolve #{model.name}"
145
+ puts "Cannot resolve #{model}"
146
+ return
147
+ end
148
+
149
+ context = name[0..-2].inject(self) do |item, name|
150
+ resolve_namespace(item, name)
151
+ end
152
+ if !(item = find_item_child_by_name(context, name.last))
153
+ item = Qt::StandardItem.new(name.last)
154
+ context.append_row(item)
155
+ (@names_to_item[context] ||= Hash.new)[name.last] = item
156
+ end
157
+
158
+ item.flags = Qt::ItemIsEnabled
159
+
160
+ @items_metadata[item] = Metadata.new(name, name.map { |n| [n] }, model_categories)
161
+ @items_to_models[item] = model
162
+ @models_to_items[model] = item
163
+ end
164
+
165
+ # @api private
166
+ def find_item_child_by_name(item, name)
167
+ if context = @names_to_item[item]
168
+ context[name]
169
+ end
170
+ end
171
+
172
+ # Returns the QStandardItem object that sits at the given path
173
+ #
174
+ # @return [Qt::StandardItem,nil]
175
+ def find_item_by_path(*path)
176
+ path.inject(self) do |parent_item, name|
177
+ return if !name
178
+ if names = @names_to_item[parent_item]
179
+ names[name]
180
+ end
181
+ end
182
+ end
183
+
184
+ # Returns the ModelIndex object that sits at the given path
185
+ #
186
+ # @return [Qt::ModelIndex,nil]
187
+ def find_index_by_path(*path)
188
+ if item = find_item_by_path(*path)
189
+ item.index
190
+ end
191
+ end
192
+
193
+ # Returns the QStandardItem object that represents the given model
194
+ def find_item_by_model(model)
195
+ @models_to_items[model]
196
+ end
197
+
198
+ # Returns the QModelIndex object that represents the given model
199
+ def find_index_by_model(model)
200
+ if item = find_item_by_model(model)
201
+ item.index
202
+ end
203
+ end
204
+
205
+ # @api private
206
+ #
207
+ # Register a model and its whole submodels hierarchy
208
+ def discover_model_hierarchy(root_model, categories, resolver, seen)
209
+ discovered = Array.new
210
+ queue = [root_model]
211
+ categories = categories.to_set
212
+
213
+ while !queue.empty?
214
+ m = queue.shift
215
+ next if seen.include?(m)
216
+ seen << m
217
+ discovered << m
218
+
219
+ register_model(m, categories, resolver)
220
+ resolver.each_submodel(m) do |model, excluded|
221
+ raise if model.kind_of?(String)
222
+ if excluded
223
+ seen << model
224
+ else
225
+ queue << model
226
+ end
227
+ end
228
+ end
229
+ discovered
230
+ end
231
+
232
+ # @api private
233
+ #
234
+ # Find or create the StandardItem that represents a root in the
235
+ # hierarchy
236
+ #
237
+ # @param [String] name the name of the namespace
238
+ # @return [Qt::StandardItem]
239
+ def resolve_root_namespace(name)
240
+ resolve_namespace(self, name)
241
+ end
242
+
243
+ # @api private
244
+ #
245
+ # Find or create the StandardItem that represents a namespace in the
246
+ # hierarchy
247
+ #
248
+ # @param [Qt::StandardItem] parent_item the parent item
249
+ # @param [String] name the name of the namespace
250
+ # @return [Qt::StandardItem]
251
+ def resolve_namespace(parent_item, name)
252
+ if item = find_item_child_by_name(parent_item, name)
253
+ item
254
+ else
255
+ item = Qt::StandardItem.new(name)
256
+ item.flags = 0
257
+
258
+ parent_name = @items_metadata[parent_item].name
259
+ @items_metadata[item] = Metadata.new(parent_name + [name], [], Set.new)
260
+ parent_item.append_row item
261
+ (@names_to_item[parent_item] ||= Hash.new)[name] = item
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+