sirens 0.0.1

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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/lib/components/component.rb +46 -0
  3. data/lib/components/component_behaviour.rb +160 -0
  4. data/lib/components/containers/splitter.rb +98 -0
  5. data/lib/components/containers/stack.rb +42 -0
  6. data/lib/components/containers/tabs.rb +24 -0
  7. data/lib/components/containers/window.rb +20 -0
  8. data/lib/components/primitive_component.rb +155 -0
  9. data/lib/components/widgets/button.rb +16 -0
  10. data/lib/components/widgets/checkbox.rb +39 -0
  11. data/lib/components/widgets/column_props.rb +23 -0
  12. data/lib/components/widgets/input_text.rb +27 -0
  13. data/lib/components/widgets/list.rb +68 -0
  14. data/lib/components/widgets/list_choice.rb +89 -0
  15. data/lib/components/widgets/radio_button.rb +33 -0
  16. data/lib/components/widgets/text.rb +31 -0
  17. data/lib/components/widgets/tree_choice.rb +82 -0
  18. data/lib/layouts/columns_builder.rb +63 -0
  19. data/lib/layouts/layout_builder.rb +203 -0
  20. data/lib/layouts/radio_button_group_builder.rb +28 -0
  21. data/lib/models/choice_model.rb +41 -0
  22. data/lib/models/list_model.rb +173 -0
  23. data/lib/models/tree_choice_model.rb +102 -0
  24. data/lib/models/value_model.rb +56 -0
  25. data/lib/models/virtual_tree_model.rb +150 -0
  26. data/lib/sirens/browsers/module_browser.rb +67 -0
  27. data/lib/sirens/browsers/object_browser.rb +103 -0
  28. data/lib/sirens/components/ancestors_list.rb +22 -0
  29. data/lib/sirens/components/class_browser.rb +38 -0
  30. data/lib/sirens/components/constants_list.rb +22 -0
  31. data/lib/sirens/components/method_source_code.rb +22 -0
  32. data/lib/sirens/components/methods_list.rb +63 -0
  33. data/lib/sirens/components/modules_list.rb +23 -0
  34. data/lib/sirens/components/namespaces_list.rb +21 -0
  35. data/lib/sirens/models/constant_model.rb +21 -0
  36. data/lib/sirens/models/method_model.rb +111 -0
  37. data/lib/sirens/models/module_browser_model.rb +225 -0
  38. data/lib/sirens/models/object_browser_model.rb +125 -0
  39. data/lib/sirens.rb +90 -0
  40. data/lib/views/button_view.rb +56 -0
  41. data/lib/views/check_button_view.rb +65 -0
  42. data/lib/views/entry_view.rb +25 -0
  43. data/lib/views/list_view.rb +230 -0
  44. data/lib/views/menu_view.rb +46 -0
  45. data/lib/views/notebook_view.rb +27 -0
  46. data/lib/views/paned_view.rb +26 -0
  47. data/lib/views/radio_button_view.rb +67 -0
  48. data/lib/views/stack_view.rb +26 -0
  49. data/lib/views/text_view.rb +112 -0
  50. data/lib/views/tree_view.rb +314 -0
  51. data/lib/views/view.rb +206 -0
  52. data/lib/views/window_view.rb +51 -0
  53. metadata +110 -0
@@ -0,0 +1,112 @@
1
+ module Sirens
2
+ class TextView < View
3
+ # Class methods
4
+
5
+ class << self
6
+ ##
7
+ # Answer the styles accepted by this view.
8
+ #
9
+ def view_accepted_styles()
10
+ super() + [:wrap_mode].freeze
11
+ end
12
+ end
13
+
14
+ # Initializing
15
+
16
+ def initialize_handles()
17
+ @text_view = Gtk::TextView.new
18
+
19
+ @main_handle = Gtk::ScrolledWindow.new
20
+ @main_handle.set_policy(:automatic, :always)
21
+
22
+ @main_handle.add(@text_view)
23
+ end
24
+
25
+ # Hooking GUI signals
26
+
27
+ def subscribe_to_ui_events()
28
+ text_view.signal_connect('populate-popup') do |text_view, menu|
29
+ menu_view = MenuView.new(menu_handle: menu)
30
+
31
+ @populate_popup_menu_block.call(menu: menu_view)
32
+
33
+ menu.show_all
34
+ end
35
+ end
36
+
37
+ # Styles
38
+
39
+ def wrap_mode=(value)
40
+ text_view.wrap_mode = value
41
+ end
42
+
43
+ def wrap_mode()
44
+ text_view.wrap_mode
45
+ end
46
+
47
+ def background_color=(value)
48
+ state_colors_from(value).each_pair do |state, value|
49
+ next if value.nil?
50
+
51
+ text_view.override_background_color( state, Gdk::RGBA.parse(value) )
52
+ end
53
+ end
54
+
55
+ def foreground_color=(value, state: :normal)
56
+ state_colors_from(value).each_pair do |state, value|
57
+ next if value.nil?
58
+
59
+ text_view.override_color( state, Gdk::RGBA.parse(value) )
60
+ end
61
+ end
62
+
63
+ def state_colors_from(value)
64
+ colors = Hash[
65
+ normal: nil,
66
+ active: nil,
67
+ prelight: nil,
68
+ selected: nil,
69
+ insensitive: nil,
70
+ ]
71
+
72
+ if value.kind_of?(Hash)
73
+ value.each_pair do |state, value|
74
+ colors[state] = value
75
+ end
76
+ else
77
+ colors[:normal] = value
78
+ end
79
+
80
+ colors
81
+ end
82
+
83
+ # Accessing
84
+
85
+ def text_view()
86
+ @text_view
87
+ end
88
+
89
+ def text()
90
+ text_view.buffer.text
91
+ end
92
+
93
+ def set_text(text)
94
+ text = '' if text.nil?
95
+
96
+ text_view.buffer.text = text
97
+ end
98
+
99
+ # Querying
100
+
101
+ ##
102
+ # Returns the selected text of nil if no text is selected.
103
+ #
104
+ def selected_text()
105
+ selection_bounds = text_view.buffer.selection_bounds
106
+
107
+ return if selection_bounds.nil?
108
+
109
+ text_view.buffer.get_text(selection_bounds[0], selection_bounds[1], false)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,314 @@
1
+ module Sirens
2
+ class TreeView < View
3
+
4
+ # Class methods
5
+
6
+ class << self
7
+ ##
8
+ # Answer the laze children placeholder.
9
+ #
10
+ def placeholder()
11
+ @placeholder ||= '__placeholder__'
12
+ end
13
+
14
+ ##
15
+ # Answer the styles accepted by this view.
16
+ #
17
+ def view_accepted_styles()
18
+ super() + [:show_headers, :clickable_headers, :on_selection_action ].freeze
19
+ end
20
+ end
21
+
22
+ # Initializing
23
+
24
+ def initialize_handles()
25
+ @tree_view = Gtk::TreeView.new
26
+ @tree_view.set_model(Gtk::TreeStore.new(String))
27
+
28
+ @main_handle = Gtk::ScrolledWindow.new
29
+ @main_handle.add(tree_view)
30
+ @main_handle.set_policy(:automatic, :automatic)
31
+
32
+ @current_selection_indices = []
33
+
34
+ @columns_props = []
35
+
36
+ @on_selection_changed_block = nil
37
+ @get_item_block = nil
38
+ @get_children_block = nil
39
+ end
40
+
41
+ # Configuring callbacks
42
+
43
+ def on_selection_changed_block(&block)
44
+ @on_selection_changed_block = block
45
+
46
+ self
47
+ end
48
+
49
+ def get_item_block(&block)
50
+ @get_item_block = block
51
+
52
+ self
53
+ end
54
+
55
+ def get_children_block(&block)
56
+ @get_children_block = block
57
+
58
+ self
59
+ end
60
+
61
+ # Building columns
62
+
63
+ def define_columns(columns_props_array)
64
+ @columns_props = columns_props_array
65
+
66
+ tree_store_types = @columns_props
67
+ .collect { |each_column_props| each_column_props.fetch(:type, :text) }
68
+ .collect { |type| String }
69
+
70
+ tree_view.set_model(Gtk::TreeStore.new(*tree_store_types))
71
+
72
+ @columns_props.each do |each_column_props|
73
+ add_column_with_props(each_column_props)
74
+ end
75
+ end
76
+
77
+ def add_column_with_props(props)
78
+ column_type = props.fetch(:type, :text)
79
+ column_index = tree_view.columns.size
80
+
81
+ renderer = Gtk::CellRendererText.new
82
+
83
+ col = Gtk::TreeViewColumn.new(props[:label], renderer, column_type => column_index)
84
+
85
+ tree_view.append_column(col)
86
+ end
87
+
88
+ # Hooking GUI signals
89
+
90
+ def subscribe_to_ui_events()
91
+ tree_view.selection.signal_connect('changed') { |tree_selection|
92
+ on_selection_changed(tree_selection)
93
+ }
94
+
95
+ tree_view.signal_connect('row-activated') { |tree_view, tree_path, tree_column|
96
+ on_selection_action(tree_path: tree_path, tree_column: tree_column)
97
+ }
98
+
99
+ tree_view.signal_connect('row-expanded') { |tree_view, iter, tree_path|
100
+ on_row_expanded(iter: iter, tree_path: tree_path)
101
+ }
102
+
103
+ tree_view.signal_connect('button_press_event') do |tree_view, event|
104
+ show_popup_menu(button: event.button, time: event.time) if (event.button == 3)
105
+ end
106
+ end
107
+
108
+ # Accessing
109
+
110
+ def tree_store()
111
+ tree_view.model
112
+ end
113
+
114
+ def tree_view()
115
+ @tree_view
116
+ end
117
+
118
+ ##
119
+ # Returns the rows contents of the tree.
120
+ # For testing and debugging only.
121
+ #
122
+ def rows()
123
+ tree_store.collect { |store, path, iter|
124
+ iter[0]
125
+ }
126
+ end
127
+
128
+ def get_item_at(path:)
129
+ @get_item_block.call(path: path)
130
+ end
131
+
132
+ def get_children_at(path:)
133
+ @get_children_block.call(path: path)
134
+ end
135
+
136
+ # Styles
137
+
138
+ def show_headers=(boolean)
139
+ tree_view.headers_visible = boolean
140
+ end
141
+
142
+ def show_headers?()
143
+ tree_view.headers_visible?
144
+ end
145
+
146
+ def clickable_headers=(boolean)
147
+ tree_view.headers_clickable = boolean
148
+ end
149
+
150
+ def clickable_headers?()
151
+ tree_view.headers_clickable?
152
+ end
153
+
154
+ def on_selection_action=(block)
155
+ @on_selection_action_block = block
156
+ end
157
+
158
+ # Handlers
159
+
160
+ def on_selection_changed(tree_selection)
161
+ indices = []
162
+ items = []
163
+
164
+ tree_selection.each do |tree_store, tree_path, iter|
165
+ indices_path = tree_path.indices
166
+
167
+ indices << indices_path
168
+ items << get_item_at(path: indices_path)
169
+ end
170
+
171
+ @on_selection_changed_block.call(
172
+ selection_items: items,
173
+ selection_paths: indices
174
+ )
175
+ end
176
+
177
+ def on_selection_action(tree_path:, tree_column:)
178
+ return if @on_selection_action_block.nil?
179
+
180
+ index_path = tree_path.indices
181
+
182
+ item = get_item_at(path: index_path)
183
+
184
+ @on_selection_action_block.call(index_path: index_path, item: item)
185
+ end
186
+
187
+ ##
188
+ # To emulate a virtual tree elements are added with a child placeholder, the class placeholder constant.
189
+ # The first time a node is expanded if it has the child placeholder it is replaced by the actual node
190
+ # children.
191
+ # The actual children are get using the get_children_block.
192
+ #
193
+ def on_row_expanded(iter:, tree_path:)
194
+ child_iter = iter.first_child
195
+
196
+ return if tree_store.get_value(child_iter, 0) != self.class.placeholder
197
+
198
+ indices_path = tree_path.indices
199
+
200
+ children = get_children_at(path: indices_path)
201
+
202
+ tree_store.remove(child_iter)
203
+
204
+ add_items(items: children, parent_iter: iter, index: 0)
205
+
206
+ tree_view.expand_row(tree_path, false)
207
+ end
208
+
209
+ # Actions
210
+
211
+ def clear_items()
212
+ tree_store.clear
213
+ end
214
+
215
+ # Adding
216
+
217
+ def add_items(items:, parent_iter:, index:)
218
+ items.each_with_index do |each_item, i|
219
+ add_item(item: each_item, parent_iter: parent_iter, index: index + i)
220
+ end
221
+ end
222
+
223
+ def add_item(item:, parent_iter:, index:)
224
+ iter = tree_store.insert(parent_iter, index)
225
+
226
+ set_item_column_values(item: item, iter: iter)
227
+
228
+ if parent_iter.nil?
229
+ indices_path = [index]
230
+ else
231
+ indices_path = parent_iter.path.indices + [index]
232
+ end
233
+
234
+ children_count = get_children_at(path: indices_path).size
235
+
236
+ if children_count > 0
237
+ placeholder = tree_store.insert(iter, 0)
238
+
239
+ placeholder[0] = self.class.placeholder
240
+ end
241
+ end
242
+
243
+ # Updating
244
+
245
+ def update_items(items:, indices:)
246
+ items.each_with_index do |each_item, i|
247
+ update_item(item: each_item, index: indices[i])
248
+ end
249
+ end
250
+
251
+ def update_item(item:, index:)
252
+ iter = tree_store.get_iter(index.to_s)
253
+
254
+ set_item_column_values(item: item, iter: iter)
255
+ end
256
+
257
+ # Removing
258
+
259
+ def remove_items(items:, indices:)
260
+ items.each_with_index do |each_item, i|
261
+ remove_item(item: each_item, index: indices[i])
262
+ end
263
+ end
264
+
265
+ def remove_item(item:, index:)
266
+ iter = tree_store.get_iter(index.to_s)
267
+
268
+ tree_store.remove(iter)
269
+ end
270
+
271
+ def set_item_column_values(item:, iter:)
272
+ @columns_props.each_with_index { |column, column_index|
273
+ iter[column_index] = column.display_text_of(item)
274
+ }
275
+ end
276
+
277
+ # Querying
278
+
279
+ def selection_indices()
280
+ indices = []
281
+
282
+ tree_view.selection.each { |tree, path, iter|
283
+ indices << path.to_s.to_i
284
+ }
285
+
286
+ indices
287
+ end
288
+
289
+ def set_selection_indices(indices)
290
+ if indices.empty?
291
+ tree_view.unselect_all
292
+ return
293
+ end
294
+
295
+ tree_path = Gtk::TreePath.new(
296
+ indices.join(':')
297
+ )
298
+
299
+ expand(path: indices[0..-2]) if indices.size > 1
300
+
301
+ tree_view.selection.select_path(tree_path)
302
+
303
+ tree_view.scroll_to_cell(tree_path, nil, false, 0.0, 0.0)
304
+ end
305
+
306
+ def expand(path:)
307
+ tree_path = Gtk::TreePath.new(
308
+ path.join(':')
309
+ )
310
+
311
+ tree_view.expand_row(tree_path, false)
312
+ end
313
+ end
314
+ end
data/lib/views/view.rb ADDED
@@ -0,0 +1,206 @@
1
+ require 'set'
2
+
3
+ module Sirens
4
+ ##
5
+ # A View is the library binding to a GUI interface handle.
6
+ # It is not a Component but is wrapped by a PrimitiveComponent.
7
+ # a View takes care of handling the internals of the GUI objects such as handles, events, default initialization,
8
+ # etc.
9
+ #
10
+ # By separating the View from the PrimitiveComponent that wraps it makes the PrimitiveComponents responsibilities
11
+ # more consistent with regular Components and it makes it easier to switch between GUI libraries
12
+ # (say, from Gtk to Qt).
13
+ #
14
+ class View
15
+
16
+ # Class methods
17
+
18
+ class << self
19
+ ##
20
+ # Answer the styles accepted by this view.
21
+ #
22
+ def accepted_styles()
23
+ @accepted_styles ||= Set.new(view_accepted_styles)
24
+ end
25
+
26
+ ##
27
+ # Answer the styles accepted by this view.
28
+ #
29
+ def view_accepted_styles()
30
+ [ :width, :height, :background_color, :foreground_color ].freeze
31
+ end
32
+ end
33
+
34
+ # Initializing
35
+
36
+ ##
37
+ # Initializes this View handles
38
+ #
39
+ def initialize()
40
+ @event_handles = Hash[]
41
+
42
+ @main_handle = nil
43
+
44
+ initialize_handles
45
+
46
+ subscribe_to_ui_events
47
+ end
48
+
49
+ ## Instantiates this view handles.
50
+ # A View usually has a single handle to the GUI library, but in same cases it may have more than one.
51
+ # For instance when adding a Scroll decorator to the actual widget.
52
+ #
53
+ def initialize_handles()
54
+ raise RuntimeError.new("Subclass #{self.class.name} must implement the method ::initialize_handles().")
55
+ end
56
+
57
+ ##
58
+ # Subscribes this View to the events/signals emitted by the GUI handle(s) of interest.
59
+ # When an event/signal is received calls the proper event_handler provided by the PrimitiveComponent,
60
+ # if one was given.
61
+ #
62
+ # This mechanism of event handler callbacks is more simple and lightweight than making this View to announce
63
+ # events using the Observer pattern, and since there is only one PrimitiveComponent wrapping each View
64
+ # using the Observer pattern would be unnecessary complex.
65
+ #
66
+ def subscribe_to_ui_events()
67
+ raise RuntimeError.new("Subclass #{self.class.name} must implement the method ::subscribe_to_ui_events().")
68
+ end
69
+
70
+ # Accessing
71
+
72
+ ##
73
+ # Returns the main handle of this View.
74
+ # The main handle is the one that this View parent add as its child.
75
+ # Also, it is the handle that receives the style props and events by default.
76
+ #
77
+ def main_handle()
78
+ @main_handle
79
+ end
80
+
81
+ # Styling
82
+
83
+ ##
84
+ # Answer the styles accepted by this view.
85
+ #
86
+ def accepted_styles()
87
+ self.class.accepted_styles
88
+ end
89
+
90
+ ##
91
+ # Applies each prop in props to the actual GUI object.
92
+ #
93
+ def apply_props(props)
94
+ accepted_styles = self.accepted_styles
95
+
96
+ props.each_pair { |prop, value|
97
+ apply_prop(prop, value) if accepted_styles.include?(prop)
98
+ }
99
+ end
100
+
101
+ ##
102
+ # Apply the prop to the actual GUI object.
103
+ #
104
+ def apply_prop(prop, value)
105
+ setter = prop.to_s + '='
106
+
107
+ send(setter, value)
108
+ end
109
+
110
+ # Styles
111
+
112
+ def width=(value)
113
+ main_handle.width_request = value
114
+ end
115
+
116
+ def width()
117
+ main_handle.width_request
118
+ end
119
+
120
+ def height=(value)
121
+ main_handle.height_request = value
122
+ end
123
+
124
+ def height()
125
+ main_handle.height_request
126
+ end
127
+
128
+ def background_color=(value)
129
+ state_colors_from(value).each_pair do |state, value|
130
+ next if value.nil?
131
+
132
+ main_handle.override_background_color( state, Gdk::RGBA.parse(value) )
133
+ end
134
+ end
135
+
136
+ def foreground_color=(value, state: :normal)
137
+ state_colors_from(value).each_pair do |state, value|
138
+ next if value.nil?
139
+
140
+ main_handle.override_color( state, Gdk::RGBA.parse(value) )
141
+ end
142
+ end
143
+
144
+ def state_colors_from(value)
145
+ colors = Hash[
146
+ normal: nil,
147
+ active: nil,
148
+ prelight: nil,
149
+ selected: nil,
150
+ insensitive: nil,
151
+ ]
152
+
153
+ if value.kind_of?(Hash)
154
+ value.each_pair do |state, value|
155
+ colors[state] = value
156
+ end
157
+ else
158
+ colors[:normal] = value
159
+ end
160
+
161
+ colors
162
+ end
163
+
164
+ # Showing/hiding
165
+
166
+ ##
167
+ # Makes this component visible.
168
+ #
169
+ def show()
170
+ main_handle.show_all
171
+ end
172
+
173
+ # Child views
174
+
175
+ ##
176
+ # Adds a child_view.
177
+ #
178
+ def add_view(child_view)
179
+ main_handle.add(child_view.main_handle)
180
+ end
181
+
182
+ ##
183
+ # Removes a child view.
184
+ #
185
+ def remove_view(child_view)
186
+ main_handle.remove(child_view.main_handle)
187
+ end
188
+
189
+ # Popup menu
190
+
191
+ def populate_popup_menu_block=(populate_popup_menu_block)
192
+ @populate_popup_menu_block = populate_popup_menu_block
193
+ end
194
+
195
+ ##
196
+ # Create and show a popup menu
197
+ #
198
+ def show_popup_menu(props)
199
+ menu = MenuView.new
200
+
201
+ @populate_popup_menu_block.call(menu: menu)
202
+
203
+ menu.open(props) unless menu.empty?
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,51 @@
1
+ module Sirens
2
+ class WindowView < View
3
+
4
+ # Class methods
5
+
6
+ class << self
7
+ ##
8
+ # Answer the styles accepted by this view.
9
+ #
10
+ def view_accepted_styles()
11
+ super() + [:title].freeze
12
+ end
13
+ end
14
+
15
+ # Initializing
16
+
17
+ def initialize_handles()
18
+ @main_handle = Gtk::Window.new()
19
+
20
+ Sirens.register_window
21
+ end
22
+
23
+ def subscribe_to_ui_events()
24
+ main_handle.signal_connect("delete_event") {
25
+ false
26
+ }
27
+
28
+ main_handle.signal_connect("destroy") {
29
+ Sirens.unregister_window
30
+ }
31
+ end
32
+
33
+ # Styles
34
+
35
+ def width=(value)
36
+ main_handle.default_width = value
37
+ end
38
+
39
+ def height=(value)
40
+ main_handle.default_height = value
41
+ end
42
+
43
+ def title=(value)
44
+ main_handle.title = value
45
+ end
46
+
47
+ def title()
48
+ main_handle.title
49
+ end
50
+ end
51
+ end