rugui 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +165 -0
- data/README +67 -0
- data/README.rdoc +67 -0
- data/Rakefile +56 -0
- data/VERSION.yml +4 -0
- data/bin/rugui +16 -0
- data/lib/rugui/base_controller.rb +194 -0
- data/lib/rugui/base_model.rb +22 -0
- data/lib/rugui/base_object.rb +73 -0
- data/lib/rugui/base_view.rb +302 -0
- data/lib/rugui/base_view_helper.rb +23 -0
- data/lib/rugui/configuration.rb +136 -0
- data/lib/rugui/framework_adapters/GTK.rb +233 -0
- data/lib/rugui/framework_adapters/Qt4.rb +171 -0
- data/lib/rugui/framework_adapters/base_framework_adapter.rb +90 -0
- data/lib/rugui/framework_adapters/framework_adapter_support.rb +35 -0
- data/lib/rugui/gem_builder.rb +21 -0
- data/lib/rugui/gem_dependency.rb +282 -0
- data/lib/rugui/initialize_hooks.rb +36 -0
- data/lib/rugui/initializer.rb +162 -0
- data/lib/rugui/log_support.rb +118 -0
- data/lib/rugui/observable_property_proxy.rb +73 -0
- data/lib/rugui/observable_property_support.rb +251 -0
- data/lib/rugui/plugin/loader.rb +77 -0
- data/lib/rugui/property_observer.rb +58 -0
- data/lib/rugui/signal_support.rb +57 -0
- data/lib/rugui/tasks/gems_application.rake +71 -0
- data/lib/rugui/tasks/rugui.rb +8 -0
- data/lib/rugui/tasks/rugui_framework.rb +4 -0
- data/lib/rugui/tasks/runner_application.rake +4 -0
- data/lib/rugui/tasks/spec_application.rake +64 -0
- data/lib/rugui/tasks/spec_framework.rake +27 -0
- data/lib/rugui/tasks/test_application.rake +77 -0
- data/lib/rugui/vendor_gem_source_index.rb +140 -0
- data/lib/rugui/version.rb +9 -0
- data/lib/rugui.rb +37 -0
- data/spec/framework/base_controller_spec.rb +48 -0
- data/spec/framework/base_model_spec.rb +13 -0
- data/spec/framework/base_view_helper_spec.rb +13 -0
- data/spec/framework/base_view_spec.rb +92 -0
- data/spec/framework/log_support_spec.rb +16 -0
- data/spec/framework/observable_property_proxy_spec.rb +67 -0
- data/spec/framework/observable_property_support_spec.rb +283 -0
- data/spec/framework/property_observer_spec.rb +88 -0
- data/spec/helpers/controllers.rb +29 -0
- data/spec/helpers/initialize_hooks_helper.rb +18 -0
- data/spec/helpers/models.rb +9 -0
- data/spec/helpers/observables.rb +210 -0
- data/spec/helpers/view_helpers.rb +9 -0
- data/spec/helpers/views.rb +72 -0
- data/spec/rcov.opts +1 -0
- data/spec/resource_files/my_other_view.glade +46 -0
- data/spec/resource_files/my_view.glade +46 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +15 -0
- metadata +149 -0
@@ -0,0 +1,302 @@
|
|
1
|
+
module RuGUI
|
2
|
+
# A base class for views.
|
3
|
+
#
|
4
|
+
# To use this class create a subclass and reimplement #setup_widgets, if you
|
5
|
+
# want to create your interface by hand. If you want to use a builder file
|
6
|
+
# just call #use_builder and, optionally, call #builder_file passing the
|
7
|
+
# filename to use.
|
8
|
+
#
|
9
|
+
# Each adapter may implement additional features, extending this class, look
|
10
|
+
# for it in adapter classes.
|
11
|
+
#
|
12
|
+
# The view may have ViewHelpers, which works as 'models' for views, i.e., they
|
13
|
+
# have observable properties that can be observed by the view. A default
|
14
|
+
# helper, named as <code>{view_name}Helper</code> is registered if it exists.
|
15
|
+
# For example, for a view named *MyView*, the default view helper should be
|
16
|
+
# named *MyViewHelper*. This helper can be accessed as a <code>helper</code>
|
17
|
+
# attribute. Other helpers may be registered if needed.
|
18
|
+
#
|
19
|
+
# Example (using GTK framework adapter):
|
20
|
+
# class MyGladeView < RuGUI::BaseView
|
21
|
+
# builder_file 'my_file.glade' # this is optional, if the glade file was called my_glade_view.glade this wouldn't be needed.
|
22
|
+
# root :top_window
|
23
|
+
# use_builder
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# class MyHandView < RuGUI::BaseView
|
27
|
+
# def setup_widgets
|
28
|
+
# # do your hand-made code here...
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
class BaseView < BaseObject
|
32
|
+
include RuGUI::LogSupport
|
33
|
+
include RuGUI::PropertyObserver
|
34
|
+
include RuGUI::SignalSupport
|
35
|
+
|
36
|
+
attr_accessor :controllers
|
37
|
+
attr_reader :widgets
|
38
|
+
attr_reader :unnamed_widgets
|
39
|
+
|
40
|
+
class_inheritable_accessor :configured_builder_file
|
41
|
+
class_inheritable_accessor :configured_builder_file_usage
|
42
|
+
class_inheritable_accessor :configured_builder_file_extension
|
43
|
+
class_inheritable_accessor :configured_root
|
44
|
+
class_inheritable_accessor :configured_display_root
|
45
|
+
|
46
|
+
def initialize
|
47
|
+
@controllers = {}
|
48
|
+
@helpers = {}
|
49
|
+
@unnamed_widgets = []
|
50
|
+
@widgets = {}
|
51
|
+
|
52
|
+
register_default_helper
|
53
|
+
setup_view_helpers
|
54
|
+
build_from_builder_file if use_builder?
|
55
|
+
setup_widgets
|
56
|
+
autoconnect_signals(self)
|
57
|
+
end
|
58
|
+
|
59
|
+
# This is included here so that the initialize method is properly updated.
|
60
|
+
include RuGUI::InitializeHooks
|
61
|
+
|
62
|
+
# Returns the framework_adapter for this class.
|
63
|
+
def framework_adapter
|
64
|
+
framework_adapter_for('BaseView')
|
65
|
+
end
|
66
|
+
|
67
|
+
# Reimplement this method to create widgets by hand.
|
68
|
+
def setup_widgets
|
69
|
+
end
|
70
|
+
|
71
|
+
# Reimplement this method to setup view helpers.
|
72
|
+
def setup_view_helpers
|
73
|
+
end
|
74
|
+
|
75
|
+
# Adds the given widget to a container widget.
|
76
|
+
def add_widget_to_container(widget, container_widget_or_name)
|
77
|
+
self.framework_adapter.add_widget_to_container(widget, from_widget_or_name(container_widget_or_name))
|
78
|
+
end
|
79
|
+
|
80
|
+
# Adds the given widget to a container widget.
|
81
|
+
def remove_widget_from_container(widget, container_widget_or_name)
|
82
|
+
self.framework_adapter.remove_widget_from_container(widget, from_widget_or_name(container_widget_or_name))
|
83
|
+
end
|
84
|
+
|
85
|
+
# Includes a view root widget inside the given container widget.
|
86
|
+
def include_view(container_widget_name, view)
|
87
|
+
raise RootWidgetNotSetForIncludedView, "You must set a root for views to be included." if view.root_widget.nil?
|
88
|
+
add_widget_to_container(view.root_widget, container_widget_name)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Removes a view root widget from the given container widget.
|
92
|
+
def remove_view(container_widget_name, view)
|
93
|
+
raise RootWidgetNotSetForIncludedView, "You must set a root for views to be removed." if view.root_widget.nil?
|
94
|
+
remove_widget_from_container(view.root_widget, container_widget_name)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Removes all children from the given container widget
|
98
|
+
def remove_all_children(container_widget)
|
99
|
+
self.framework_adapter.remove_all_children(container_widget)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Registers a controller as receiver of signals from the view widgets.
|
103
|
+
def register_controller(controller, name = nil)
|
104
|
+
name ||= controller.class.to_s.underscore
|
105
|
+
autoconnect_signals(controller)
|
106
|
+
@controllers[name.to_sym] = controller
|
107
|
+
end
|
108
|
+
|
109
|
+
# Registers a view helper for the view.
|
110
|
+
def register_helper(helper, name = nil)
|
111
|
+
helper = create_instance_if_possible(helper) if helper.is_a?(String) or helper.is_a?(Symbol)
|
112
|
+
unless helper.nil?()
|
113
|
+
name ||= helper.class.to_s.underscore
|
114
|
+
helper.register_observer(self, name)
|
115
|
+
@helpers[name.to_sym] = helper
|
116
|
+
create_attribute_reader(:helpers, name)
|
117
|
+
helper.post_registration(self)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Called after the view is registered in a controller.
|
122
|
+
def post_registration(controller)
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns the root widget if one is set.
|
126
|
+
def root_widget
|
127
|
+
send(root.to_sym) if not root.nil?
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns the builder file.
|
131
|
+
def builder_file
|
132
|
+
self.configured_builder_file
|
133
|
+
end
|
134
|
+
|
135
|
+
# Returns the builder file extension.
|
136
|
+
def builder_file_extension
|
137
|
+
self.configured_builder_file_extension
|
138
|
+
end
|
139
|
+
|
140
|
+
# Returns true if builder file is being used for this view.
|
141
|
+
def use_builder?
|
142
|
+
self.configured_builder_file_usage
|
143
|
+
end
|
144
|
+
|
145
|
+
# Framework adapters should implement this if they support builder files.
|
146
|
+
def build_from_builder_file
|
147
|
+
filename = get_builder_file
|
148
|
+
raise BuilderFileNotFoundError, "Could not find builder file for view #{self.class.name}. UI file paths: #{RuGUI.configuration.builder_files_paths.join(', ')}." if filename.nil?
|
149
|
+
|
150
|
+
self.framework_adapter.build_widgets_from(filename)
|
151
|
+
self.framework_adapter.register_widgets
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns the name of the root widget for this view.
|
155
|
+
def root
|
156
|
+
self.configured_root.to_s unless self.configured_root.nil?
|
157
|
+
end
|
158
|
+
|
159
|
+
def display_root?
|
160
|
+
!!self.configured_display_root
|
161
|
+
end
|
162
|
+
|
163
|
+
class << self
|
164
|
+
# Sets the name of the root widget for this view.
|
165
|
+
#
|
166
|
+
# This is specially useful when more than one view uses the same glade
|
167
|
+
# file, but each one uses a diferent widget tree inside that glade file.
|
168
|
+
#
|
169
|
+
# Other use for this is when building a reusable widget, composed of the
|
170
|
+
# contents of a glade file. One could create a window, place a vertical
|
171
|
+
# box, and then place elements inside this vertical box. Later, this glade
|
172
|
+
# file is used to insert the contents of the vertical box inside another
|
173
|
+
# vertical box in other glade file.
|
174
|
+
def root(root_widget_name)
|
175
|
+
self.configured_root = root_widget_name
|
176
|
+
end
|
177
|
+
|
178
|
+
# Sets the builder file to use when creating this view.
|
179
|
+
def builder_file(file)
|
180
|
+
self.configured_builder_file = file
|
181
|
+
end
|
182
|
+
|
183
|
+
# Tells whether we should use a builder file when creating this view.
|
184
|
+
#
|
185
|
+
# By default the root widget will be displayed, but you can pass false to
|
186
|
+
# this method to prevent it for being displayed.
|
187
|
+
def use_builder(display_root = true)
|
188
|
+
self.configured_builder_file_usage = true
|
189
|
+
self.configured_display_root = display_root
|
190
|
+
|
191
|
+
self.configured_builder_file_extension = self.framework_adapter_class.builder_file_extension
|
192
|
+
default_builder_file_path = RuGUI.root.join('app', 'resources', "#{self.configured_builder_file_extension}")
|
193
|
+
RuGUI.configuration.builder_files_paths << default_builder_file_path unless RuGUI.configuration.builder_files_paths.include?(default_builder_file_path)
|
194
|
+
end
|
195
|
+
|
196
|
+
def framework_adapter_class
|
197
|
+
class_adapter_for('BaseView')
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
protected
|
202
|
+
# Builds a widget of the given type, possibly adding it to a parent
|
203
|
+
# widget, and display it.
|
204
|
+
#
|
205
|
+
# The *args are passed to the widget constructor.
|
206
|
+
def build_widget(widget_type, widget_name = nil, parent = nil, *args)
|
207
|
+
widget = widget_type.new(*args)
|
208
|
+
self.framework_adapter.set_widget_name(widget, widget_name)
|
209
|
+
add_widget(widget, widget_name)
|
210
|
+
add_widget_to_container(widget, parent) unless parent.nil?
|
211
|
+
widget.show
|
212
|
+
end
|
213
|
+
|
214
|
+
# Adds the widget to the view.
|
215
|
+
#
|
216
|
+
# If +widget_name+ is not given one is assumed the widget`s name will be
|
217
|
+
# used instead. If the widget doesn't have a name it will be added as an
|
218
|
+
# unnamed widget (accessible through #unnamed_widgets property.
|
219
|
+
def add_widget(widget, widget_name = nil)
|
220
|
+
widget_name ||= widget.name
|
221
|
+
widget_name = widget_name.to_s
|
222
|
+
|
223
|
+
unless widget_name.nil? || widget_name.empty?
|
224
|
+
create_attribute_for_widget(widget_name)
|
225
|
+
send("#{widget_name}=", widget)
|
226
|
+
@widgets[widget_name] = widget
|
227
|
+
else
|
228
|
+
@unnamed_widgets << widget
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def from_widget_or_name(widget_or_name)
|
233
|
+
if widget_or_name.is_a?(String) || widget_or_name.is_a?(Symbol)
|
234
|
+
send(widget_or_name)
|
235
|
+
else
|
236
|
+
widget_or_name
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def autoconnect_signals(receiver)
|
241
|
+
receiver.autoconnect_declared_signals(self)
|
242
|
+
self.framework_adapter.autoconnect_signals(receiver)
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
def get_builder_file
|
247
|
+
filename = (not self.builder_file.nil?) ? self.builder_file : "#{self.class.to_s.underscore}.#{builder_file_extension}"
|
248
|
+
|
249
|
+
# The builder file given may already contain a full path to a glade file.
|
250
|
+
return filename if File.file?(filename)
|
251
|
+
|
252
|
+
filename = "#{filename}.#{builder_file_extension}" unless File.extname(filename) == ".#{builder_file_extension}"
|
253
|
+
|
254
|
+
paths = RuGUI.configuration.builder_files_paths.select do |path|
|
255
|
+
File.file?(File.join(path, filename))
|
256
|
+
end
|
257
|
+
File.join(paths.first, filename) unless paths.empty?
|
258
|
+
end
|
259
|
+
|
260
|
+
# Attempts to register the default helper for the view
|
261
|
+
def register_default_helper
|
262
|
+
register_helper("#{self.class.name}Helper", :helper)
|
263
|
+
end
|
264
|
+
|
265
|
+
def create_attribute_for_widget(widget_name)
|
266
|
+
self.instance_eval <<-instance_eval
|
267
|
+
def #{widget_name}
|
268
|
+
@#{widget_name}
|
269
|
+
end
|
270
|
+
|
271
|
+
def #{widget_name}=(widget)
|
272
|
+
@#{widget_name} = widget
|
273
|
+
end
|
274
|
+
instance_eval
|
275
|
+
end
|
276
|
+
|
277
|
+
# Creates an attribute reader for the some entity.
|
278
|
+
def create_attribute_reader(entity, name)
|
279
|
+
self.class.class_eval <<-class_eval
|
280
|
+
def #{name}
|
281
|
+
@#{entity}[:#{name}]
|
282
|
+
end
|
283
|
+
class_eval
|
284
|
+
end
|
285
|
+
|
286
|
+
# Creates an instance of the given class.
|
287
|
+
def create_instance_if_possible(klass_name, *args)
|
288
|
+
klass_name.to_s.camelize.constantize.new(*args)
|
289
|
+
rescue NameError
|
290
|
+
# Couldn't create instance.
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Exception thrown when the builder file for this view could not be found.
|
295
|
+
class BuilderFileNotFoundError < Exception
|
296
|
+
end
|
297
|
+
|
298
|
+
# Exception thrown when attempting to include a view which don't have a root
|
299
|
+
# set.
|
300
|
+
class RootWidgetNotSetForIncludedView < Exception
|
301
|
+
end
|
302
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module RuGUI
|
2
|
+
# A base class for view helpers.
|
3
|
+
class BaseViewHelper < BaseObject
|
4
|
+
include RuGUI::ObservablePropertySupport
|
5
|
+
include RuGUI::LogSupport
|
6
|
+
|
7
|
+
def initialize(observable_properties_values = {})
|
8
|
+
initialize_observable_property_support(observable_properties_values)
|
9
|
+
end
|
10
|
+
|
11
|
+
# This is included here so that the initialize method is properly updated.
|
12
|
+
include RuGUI::InitializeHooks
|
13
|
+
|
14
|
+
# Returns the framework_adapter for this class.
|
15
|
+
def framework_adapter
|
16
|
+
framework_adapter_for('BaseViewHelper')
|
17
|
+
end
|
18
|
+
|
19
|
+
# Called after the view helper is registered in a view.
|
20
|
+
def post_registration(view)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# Defines the environment to be used by the application.
|
2
|
+
RUGUI_ENV = (ENV['RUGUI_ENV'] || 'development').dup unless defined?(RUGUI_ENV)
|
3
|
+
|
4
|
+
module RuGUI
|
5
|
+
# Defines configurations for a RuGUI application.
|
6
|
+
class Configuration
|
7
|
+
# The application root path, defined by ::APPLICATION_ROOT
|
8
|
+
attr_reader :root_path
|
9
|
+
|
10
|
+
# The environment for this application.
|
11
|
+
attr_accessor :environment
|
12
|
+
|
13
|
+
# The framework adapter to be used for this application. Defaults to GTK.
|
14
|
+
# For now it can only be GTK.
|
15
|
+
attr_accessor :framework_adapter
|
16
|
+
|
17
|
+
# The specific logger to use. By default, a logger will be created and
|
18
|
+
# initialized using #log_path and #log_level, but a programmer may
|
19
|
+
# specifically set the logger to use via this accessor and it will be
|
20
|
+
# used directly.
|
21
|
+
attr_accessor :logger
|
22
|
+
|
23
|
+
# An array of paths for builder files.
|
24
|
+
attr_accessor :builder_files_paths
|
25
|
+
|
26
|
+
# An array of paths which should be automaticaly loaded.
|
27
|
+
attr_accessor :load_paths
|
28
|
+
|
29
|
+
# An array of paths which should be searched for gtkrc styles files.
|
30
|
+
#
|
31
|
+
# It searchs for files which have the '.rc' extension or files which have
|
32
|
+
# the string 'gtkrc' in its filename.
|
33
|
+
#
|
34
|
+
# The order in which the files are loaded is random, so do not rely on it.
|
35
|
+
#
|
36
|
+
# If you need to use absolute paths in a gtkrc file, such as set the pixmap
|
37
|
+
# path, you can use "{ROOT_PATH}", which will be substituted by the
|
38
|
+
# application root path when the file is read.
|
39
|
+
attr_accessor :styles_paths
|
40
|
+
|
41
|
+
# The timeout for queued calls. Useful when performing long tasks.
|
42
|
+
attr_accessor :queue_timeout
|
43
|
+
|
44
|
+
# A hash of application specific configurations.
|
45
|
+
attr_accessor :application
|
46
|
+
|
47
|
+
# An array of gems that this RuGUI application depends on. RuGUI will automatically load
|
48
|
+
# these gems during installation, and allow you to install any missing gems with:
|
49
|
+
#
|
50
|
+
# rake gems:install
|
51
|
+
#
|
52
|
+
# You can add gems with the #gem method.
|
53
|
+
attr_accessor :gems
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
set_root_path!
|
57
|
+
|
58
|
+
self.environment = default_environment
|
59
|
+
self.framework_adapter = default_framework_adapter
|
60
|
+
self.load_paths = default_load_paths
|
61
|
+
self.builder_files_paths = default_builder_files_paths
|
62
|
+
self.styles_paths = default_styles_paths
|
63
|
+
self.queue_timeout = default_queue_timeout
|
64
|
+
self.gems = default_gems
|
65
|
+
self.logger = {}
|
66
|
+
self.application = {}
|
67
|
+
end
|
68
|
+
|
69
|
+
# The path to the current environment's file (<tt>development.rb</tt>, etc.). By
|
70
|
+
# default the file is at <tt>config/environments/#{environment}.rb</tt>.
|
71
|
+
def environment_path
|
72
|
+
root_path.join('config', 'environments', "#{environment}.rb")
|
73
|
+
end
|
74
|
+
|
75
|
+
def set_root_path!
|
76
|
+
raise 'APPLICATION_ROOT is not set' unless defined?(::APPLICATION_ROOT)
|
77
|
+
raise 'APPLICATION_ROOT is not a directory' unless File.directory?(::APPLICATION_ROOT)
|
78
|
+
|
79
|
+
@root_path = Pathname.new(File.expand_path(::APPLICATION_ROOT))
|
80
|
+
end
|
81
|
+
|
82
|
+
# Adds a single Gem dependency to the RuGUI application. By default, it will require
|
83
|
+
# the library with the same name as the gem. Use :lib to specify a different name.
|
84
|
+
#
|
85
|
+
# # gem 'aws-s3', '>= 0.4.0'
|
86
|
+
# # require 'aws/s3'
|
87
|
+
# config.gem 'aws-s3', :lib => 'aws/s3', :version => '>= 0.4.0', \
|
88
|
+
# :source => "http://code.whytheluckystiff.net"
|
89
|
+
#
|
90
|
+
# To require a library be installed, but not attempt to load it, pass :lib => false
|
91
|
+
#
|
92
|
+
# config.gem 'qrp', :version => '0.4.1', :lib => false
|
93
|
+
def gem(name, options = {})
|
94
|
+
@gems << RuGUI::GemDependency.new(name, options)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def default_environment
|
99
|
+
::RUGUI_ENV
|
100
|
+
end
|
101
|
+
|
102
|
+
def default_framework_adapter
|
103
|
+
'GTK'
|
104
|
+
end
|
105
|
+
|
106
|
+
def default_load_paths
|
107
|
+
paths = []
|
108
|
+
|
109
|
+
paths.concat %w(
|
110
|
+
app
|
111
|
+
app/models
|
112
|
+
app/controllers
|
113
|
+
app/views
|
114
|
+
app/views/helpers
|
115
|
+
config
|
116
|
+
lib
|
117
|
+
).map { |dir| root_path.join(dir) }.select { |dir| File.directory?(dir) }
|
118
|
+
end
|
119
|
+
|
120
|
+
def default_builder_files_paths
|
121
|
+
[]
|
122
|
+
end
|
123
|
+
|
124
|
+
def default_styles_paths
|
125
|
+
[root_path.join('app', 'resources', 'styles')]
|
126
|
+
end
|
127
|
+
|
128
|
+
def default_queue_timeout
|
129
|
+
50
|
130
|
+
end
|
131
|
+
|
132
|
+
def default_gems
|
133
|
+
[]
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'gtk2'
|
2
|
+
require 'libglade2'
|
3
|
+
|
4
|
+
# See the discussion here: http://eigenclass.org/hiki.rb?instance_exec
|
5
|
+
class Object
|
6
|
+
module InstanceExecHelper; end
|
7
|
+
include InstanceExecHelper
|
8
|
+
def instance_exec(*args, &block)
|
9
|
+
begin
|
10
|
+
old_critical, Thread.critical = Thread.critical, true
|
11
|
+
n = 0
|
12
|
+
n += 1 while respond_to?(mname="__instance_exec#{n}")
|
13
|
+
InstanceExecHelper.module_eval{ define_method(mname, &block) }
|
14
|
+
ensure
|
15
|
+
Thread.critical = old_critical
|
16
|
+
end
|
17
|
+
begin
|
18
|
+
ret = send(mname, *args)
|
19
|
+
ensure
|
20
|
+
InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
|
21
|
+
end
|
22
|
+
ret
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module Gtk
|
27
|
+
GTK_PENDING_BLOCKS = []
|
28
|
+
GTK_PENDING_BLOCKS_LOCK = Monitor.new
|
29
|
+
|
30
|
+
def Gtk.queue(&block)
|
31
|
+
if Thread.current == Thread.main
|
32
|
+
block.call
|
33
|
+
else
|
34
|
+
GTK_PENDING_BLOCKS_LOCK.synchronize do
|
35
|
+
GTK_PENDING_BLOCKS << block
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Adds a timeout to execute pending blocks in a queue.
|
41
|
+
def Gtk.queue_timeout(timeout)
|
42
|
+
Gtk.timeout_add timeout do
|
43
|
+
GTK_PENDING_BLOCKS_LOCK.synchronize do
|
44
|
+
GTK_PENDING_BLOCKS.each do |block|
|
45
|
+
block.call
|
46
|
+
end
|
47
|
+
GTK_PENDING_BLOCKS.clear
|
48
|
+
end
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def Gtk.load_style_paths
|
54
|
+
styles_paths = RuGUI.configuration.styles_paths.select { |path| File.directory?(path) }
|
55
|
+
styles_paths.each do |path|
|
56
|
+
Dir.new(path).each do |entry|
|
57
|
+
Gtk::RC.parse_string(get_style_file_contents(path, entry)) if is_style_file?(path, entry)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def Gtk.is_style_file?(path, filename)
|
63
|
+
File.extname(filename) == '.rc' or /gtkrc/.match(filename) if File.file?(File.join(path, filename))
|
64
|
+
end
|
65
|
+
|
66
|
+
def Gtk.get_style_file_contents(path, filename)
|
67
|
+
IO.read(File.join(path, filename)).sub('{ROOT_PATH}', RuGUI.configuration.root_path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
Gtk.load_style_paths
|
72
|
+
|
73
|
+
module RuGUI
|
74
|
+
module FrameworkAdapters
|
75
|
+
module GTK
|
76
|
+
class BaseController < RuGUI::FrameworkAdapters::BaseFrameworkAdapter::BaseController
|
77
|
+
def queue(&block)
|
78
|
+
Gtk.queue(&block)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class BaseMainController < RuGUI::FrameworkAdapters::GTK::BaseController
|
83
|
+
def run
|
84
|
+
Gtk.queue_timeout(RuGUI.configuration.queue_timeout)
|
85
|
+
Gtk.main
|
86
|
+
end
|
87
|
+
|
88
|
+
def quit
|
89
|
+
Gtk.main_quit
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class BaseView < RuGUI::FrameworkAdapters::BaseFrameworkAdapter::BaseView
|
94
|
+
# Queues the block call, so that it is only gets executed in the main thread.
|
95
|
+
def queue(&block)
|
96
|
+
Gtk.queue(&block)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Adds a widget to the given container widget.
|
100
|
+
def add_widget_to_container(widget, container_widget)
|
101
|
+
container_widget.add(widget)
|
102
|
+
end
|
103
|
+
|
104
|
+
# Removes a widget from the given container widget.
|
105
|
+
def remove_widget_from_container(widget, container_widget)
|
106
|
+
container_widget.remove(widget)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Removes all children from the given container widget.
|
110
|
+
def remove_all_children(container_widget)
|
111
|
+
container_widget.children.each do |child|
|
112
|
+
container_widget.remove(child)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Sets the widget name for the given widget if given.
|
117
|
+
def set_widget_name(widget, widget_name)
|
118
|
+
widget.name = widget_name.to_s unless widget_name.nil?
|
119
|
+
end
|
120
|
+
|
121
|
+
# Autoconnects signals handlers for the view. If +other_target+ is given
|
122
|
+
# it is used instead of the view itself.
|
123
|
+
def autoconnect_signals(other_target = nil)
|
124
|
+
if self.adapted_object.use_builder?
|
125
|
+
self.adapted_object.glade.signal_autoconnect_full do |source, target, signal_name, handler_name, signal_data, after|
|
126
|
+
target ||= other_target
|
127
|
+
self.adapted_object.glade.connect(source, target, signal_name, handler_name, signal_data) if target.respond_to?(handler_name)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Connects the signal from the widget to the given receiver block.
|
133
|
+
# The block is executed in the context of the receiver.
|
134
|
+
def connect_declared_signal_block(widget, signal, receiver, block)
|
135
|
+
widget.signal_connect(signal) do |*args|
|
136
|
+
receiver.instance_exec(*args, &block)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Connects the signal from the widget to the given receiver method.
|
141
|
+
def connect_declared_signal(widget, signal, receiver, method)
|
142
|
+
widget.signal_connect(signal) do |*args|
|
143
|
+
receiver.send(method, *args)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Builds widgets from the given filename, using the proper builder.
|
148
|
+
def build_widgets_from(filename)
|
149
|
+
self.adapted_object.glade = GladeXML.new(filename, self.adapted_object.root, nil, nil, GladeXML::FILE)
|
150
|
+
|
151
|
+
self.adapted_object.glade.widget_names.each do |widget_name|
|
152
|
+
self.adapted_object.send(:create_attribute_for_widget, widget_name) unless self.adapted_object.glade[widget_name].nil?
|
153
|
+
end
|
154
|
+
self.adapted_object.root_widget.show if self.adapted_object.display_root? and not self.adapted_object.root_widget.nil?
|
155
|
+
end
|
156
|
+
|
157
|
+
# Registers widgets as attributes of the view class.
|
158
|
+
def register_widgets
|
159
|
+
self.adapted_object.glade.widget_names.each do |widget_name|
|
160
|
+
unless self.adapted_object.glade[widget_name].nil?
|
161
|
+
self.adapted_object.send("#{widget_name}=".to_sym, self.adapted_object.glade[widget_name])
|
162
|
+
self.adapted_object.widgets[widget_name] = self.adapted_object.glade[widget_name]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class << self
|
168
|
+
# Returns the builder file extension to be used for this view class.
|
169
|
+
def builder_file_extension
|
170
|
+
'glade'
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
module RuGUI
|
179
|
+
class BaseView < BaseObject
|
180
|
+
class_inheritable_accessor :configured_glade_usage
|
181
|
+
|
182
|
+
attr_accessor :glade
|
183
|
+
|
184
|
+
# Adds a signal handler for all widgets of the given type.
|
185
|
+
def add_signal_handler_for_widget_type(widget_type, signal, &block)
|
186
|
+
widgets = []
|
187
|
+
widgets.concat(@widgets.values.select { |widget| widget.kind_of?(widget_type) }) unless @widgets.empty?
|
188
|
+
widgets.concat(@unnamed_widgets.select { |widget| widget.kind_of?(widget_type) }) unless @unnamed_widgets.empty?
|
189
|
+
|
190
|
+
widgets.each do |widget|
|
191
|
+
widget.signal_connect(signal, &block)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Changes the widget style to use the given widget style.
|
196
|
+
#
|
197
|
+
# This widget style should be declared in a gtkrc file, by specifying a
|
198
|
+
# style using a widget path, such as:
|
199
|
+
#
|
200
|
+
# widget "main_window" style "main_window_style"
|
201
|
+
# widget "main_window_other" style "main_window_other_style"
|
202
|
+
#
|
203
|
+
# In this example, if you called this method like this:
|
204
|
+
#
|
205
|
+
# change_widget_style(:main_window, 'main_window_other')
|
206
|
+
# change_widget_style(self.main_window, 'main_window_other') # or, passing the widget instance directly
|
207
|
+
#
|
208
|
+
# The widget style would be set to "main_window_other_style".
|
209
|
+
#
|
210
|
+
# NOTE: Unfortunately, gtk doesn't offer an API to get declared styles, so
|
211
|
+
# you must set a style to a widget. Since the widget name set in the style
|
212
|
+
# definition doesn't need to point to an existing widget we can use this
|
213
|
+
# to simplify the widget styling here.
|
214
|
+
def change_widget_style(widget_or_name, widget_path_style)
|
215
|
+
if widget_or_name.is_a?(Gtk::Widget)
|
216
|
+
widget = widget_or_name
|
217
|
+
else
|
218
|
+
widget = @glade[widget_or_name.to_s]
|
219
|
+
end
|
220
|
+
style = Gtk::RC.get_style_by_paths(Gtk::Settings.default, widget_path_style.to_s, nil, nil)
|
221
|
+
widget.style = style
|
222
|
+
end
|
223
|
+
|
224
|
+
class << self
|
225
|
+
# Call this method at class level if the view should be built from a glade
|
226
|
+
# file.
|
227
|
+
def use_glade
|
228
|
+
self.logger.warn('DEPRECATED - Call use_builder class method instead in your view.')
|
229
|
+
use_builder
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|