rugui 1.2.0
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.
- 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,162 @@
|
|
1
|
+
require 'rugui'
|
2
|
+
require 'rugui/gem_dependency'
|
3
|
+
|
4
|
+
module RuGUI
|
5
|
+
class Initializer
|
6
|
+
include RuGUI::LogSupport
|
7
|
+
|
8
|
+
# The configuration for this application.
|
9
|
+
attr_reader :configuration
|
10
|
+
|
11
|
+
# Whether or not all the gem dependencies have been met
|
12
|
+
attr_reader :gems_dependencies_loaded
|
13
|
+
|
14
|
+
@@processed = false
|
15
|
+
|
16
|
+
# Runs the initializer.
|
17
|
+
#
|
18
|
+
# It runs the process procedure by default, by this can be changed by
|
19
|
+
# specifying a command when calling this method. A block can be given
|
20
|
+
# in order to change the application configuration.
|
21
|
+
#
|
22
|
+
def self.run(command = :process, configuration = Configuration.new)
|
23
|
+
yield configuration if block_given?
|
24
|
+
initializer = new configuration
|
25
|
+
RuGUI.configuration = configuration
|
26
|
+
initializer.send(command)
|
27
|
+
@@processed = (command == :process) ? true : false
|
28
|
+
initializer
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create a new Initializer instance that references the given Configuration
|
32
|
+
# instance.
|
33
|
+
def initialize(configuration)
|
34
|
+
@configuration = configuration
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sequentially step through all of the available initialization routines,
|
38
|
+
# in order (view execution order in source).
|
39
|
+
def process
|
40
|
+
load_environment
|
41
|
+
load_logger
|
42
|
+
|
43
|
+
start_initialization_process_log
|
44
|
+
|
45
|
+
set_load_path
|
46
|
+
add_gem_load_paths
|
47
|
+
|
48
|
+
set_autoload_paths
|
49
|
+
load_framework_adapter
|
50
|
+
|
51
|
+
load_gems
|
52
|
+
load_plugins
|
53
|
+
|
54
|
+
# pick up any gems that plugins depend on
|
55
|
+
add_gem_load_paths
|
56
|
+
load_gems
|
57
|
+
check_gem_dependencies
|
58
|
+
|
59
|
+
# bail out if gems are missing - note that check_gem_dependencies will have
|
60
|
+
# already called abort() unless $gems_rake_task is set
|
61
|
+
return unless gems_dependencies_loaded
|
62
|
+
|
63
|
+
finish_initialization_process_log
|
64
|
+
end
|
65
|
+
|
66
|
+
# Set the <tt>$LOAD_PATH</tt> based on the value of
|
67
|
+
# Configuration#load_paths. Duplicates are removed.
|
68
|
+
def set_load_path
|
69
|
+
load_paths = configuration.load_paths
|
70
|
+
load_paths.reverse_each { |dir| $LOAD_PATH.unshift(dir) if File.directory?(dir) }
|
71
|
+
$LOAD_PATH.uniq!
|
72
|
+
end
|
73
|
+
|
74
|
+
# Set the paths from which RuGUI will automatically load source files.
|
75
|
+
def set_autoload_paths
|
76
|
+
ActiveSupport::Dependencies.load_paths = configuration.load_paths.uniq
|
77
|
+
end
|
78
|
+
|
79
|
+
# Loads the environment specified by Configuration#environment_path, which
|
80
|
+
# is typically one of development, or production.
|
81
|
+
def load_environment
|
82
|
+
return if @environment_loaded
|
83
|
+
@environment_loaded = true
|
84
|
+
|
85
|
+
if File.exist?(configuration.environment_path)
|
86
|
+
config = configuration
|
87
|
+
constants = self.class.constants
|
88
|
+
|
89
|
+
eval(IO.read(configuration.environment_path), binding, configuration.environment_path)
|
90
|
+
|
91
|
+
(self.class.constants - constants).each do |const|
|
92
|
+
Object.const_set(const, self.class.const_get(const))
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def load_plugins
|
98
|
+
plugin_loader.load_plugins
|
99
|
+
end
|
100
|
+
|
101
|
+
def add_gem_load_paths
|
102
|
+
RuGUI::GemDependency.add_frozen_gem_path
|
103
|
+
unless @configuration.gems.empty?
|
104
|
+
require "rubygems"
|
105
|
+
@configuration.gems.each { |gem| gem.add_load_paths }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def load_gems
|
110
|
+
unless $gems_build_rake_task
|
111
|
+
@configuration.gems.each { |gem| gem.load }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def check_gem_dependencies
|
116
|
+
unloaded_gems = @configuration.gems.reject { |g| g.loaded? }
|
117
|
+
if unloaded_gems.size > 0
|
118
|
+
@gems_dependencies_loaded = false
|
119
|
+
# don't print if the gems rake tasks are being run
|
120
|
+
unless $gems_rake_task
|
121
|
+
abort <<-end_error
|
122
|
+
Missing these required gems:
|
123
|
+
#{unloaded_gems.map { |gem| "#{gem.name} #{gem.requirement}" } * "\n "}
|
124
|
+
|
125
|
+
You're running:
|
126
|
+
ruby #{Gem.ruby_version} at #{Gem.ruby}
|
127
|
+
rubygems #{Gem::RubyGemsVersion} at #{Gem.path * ', '}
|
128
|
+
|
129
|
+
Run `rake gems:install` to install the missing gems.
|
130
|
+
end_error
|
131
|
+
end
|
132
|
+
else
|
133
|
+
@gems_dependencies_loaded = true
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def load_logger
|
138
|
+
RuGUILogger.logger
|
139
|
+
end
|
140
|
+
|
141
|
+
def start_initialization_process_log
|
142
|
+
logger.info "Starting RuGUI application with #{configuration.environment} environment..." unless silence_logs?
|
143
|
+
end
|
144
|
+
|
145
|
+
def finish_initialization_process_log
|
146
|
+
logger.info "RuGUI application configurations loaded." unless silence_logs?
|
147
|
+
end
|
148
|
+
|
149
|
+
def plugin_loader
|
150
|
+
@plugin_loader || RuGUI::Plugin::Loader.new(self, configuration)
|
151
|
+
end
|
152
|
+
|
153
|
+
def load_framework_adapter
|
154
|
+
require "rugui/framework_adapters/#{RuGUI.configuration.framework_adapter}"
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
def silence_logs?
|
159
|
+
@@processed or $gems_build_rake_task or $gems_rake_task
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module RuGUI
|
4
|
+
#
|
5
|
+
# A simple log support for registering problems, infos and debugs.
|
6
|
+
#
|
7
|
+
module LogSupport
|
8
|
+
# Returns the logger object.
|
9
|
+
def logger
|
10
|
+
@logger ||= RuGUILogger.logger
|
11
|
+
@logger.formatter = RuGUI::LogSupport::Formatter.new(self.class.name)
|
12
|
+
@logger
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def logger
|
17
|
+
@@logger ||= RuGUILogger.logger
|
18
|
+
@@logger.formatter = RuGUI::LogSupport::Formatter.new(self.name)
|
19
|
+
@@logger
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.included(base)
|
24
|
+
base.extend(ClassMethods)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
class Formatter
|
29
|
+
def initialize(classname = nil)
|
30
|
+
@classname = classname
|
31
|
+
end
|
32
|
+
|
33
|
+
def call(severity, timestamp, progname, msg)
|
34
|
+
timestamp = timestamp.strftime(RuGUI.configuration.logger[:format] || "%Y-%m-%d %H:%M:%S")
|
35
|
+
"#{timestamp} (#{severity}) (#{@classname}) #{msg}\n"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class RuGUILogger
|
41
|
+
class << self
|
42
|
+
def logger
|
43
|
+
@@rugui_logger ||= RuGUILogger.new
|
44
|
+
@@rugui_logger.logger
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def logger
|
49
|
+
@@logger
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def initialize
|
54
|
+
setup_logger
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Setup a new log support object. If a problem occurs a logger is setted up
|
59
|
+
# to warn level.
|
60
|
+
#
|
61
|
+
def setup_logger
|
62
|
+
begin
|
63
|
+
@@logger = Logger.new(defined_output)
|
64
|
+
@@logger.level = defined_level
|
65
|
+
rescue StandardError => e
|
66
|
+
@@logger = Logger.new(STDERR)
|
67
|
+
@@logger.level = LEVELS[:warn]
|
68
|
+
@@logger.warn "Log support problems: The log level has been raised to WARN and the output directed to STDERR until the problem is fixed."
|
69
|
+
@@logger.error "#{e} #{e.backtrace.join("\n")}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Defines a output based on params informed by user, params setted up in
|
75
|
+
# the configuration file, or default values.
|
76
|
+
#
|
77
|
+
def defined_output
|
78
|
+
output = RuGUI.configuration.logger[:output]
|
79
|
+
if output.nil? or [:stdout, :stderr].include?(output)
|
80
|
+
DEFAULT_OUTPUT
|
81
|
+
else
|
82
|
+
File.join(RuGUI.root, 'log', output)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# Defines a level based on params informed by user, params setted up in
|
88
|
+
# the configuration file, or default values.
|
89
|
+
#
|
90
|
+
def defined_level
|
91
|
+
level = RuGUI.configuration.logger[:level]
|
92
|
+
unless level
|
93
|
+
level = DEFAULT_LEVEL
|
94
|
+
else
|
95
|
+
level = LEVELS[level]
|
96
|
+
end
|
97
|
+
level
|
98
|
+
end
|
99
|
+
|
100
|
+
def defined_classname(classname)
|
101
|
+
classname || self.class.name
|
102
|
+
end
|
103
|
+
|
104
|
+
LEVELS = {
|
105
|
+
:debug => Logger::DEBUG,
|
106
|
+
:info => Logger::INFO,
|
107
|
+
:warn => Logger::WARN,
|
108
|
+
:error => Logger::ERROR,
|
109
|
+
:fatal => Logger::FATAL
|
110
|
+
}
|
111
|
+
|
112
|
+
#
|
113
|
+
# Default values to the log support object - aka logger
|
114
|
+
#
|
115
|
+
DEFAULT_OUTPUT = STDOUT
|
116
|
+
DEFAULT_LEVEL = LEVELS[:debug]
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module RuGUI
|
2
|
+
# A proxy class for observable properties.
|
3
|
+
#
|
4
|
+
# When creating an <code>ObservablePropertyProxy</code> you pass an
|
5
|
+
# instance of any object to it (which will now be the context for this
|
6
|
+
# proxy), the observable and the property name.
|
7
|
+
#
|
8
|
+
# The <code>ObservablePropertyProxy</code> instance will work as a
|
9
|
+
# proxy for each method send to the context. If the context is changed,
|
10
|
+
# it will notify any <code>PropertyObservers</code> registered for its
|
11
|
+
# <code>observable</code>, by calling their
|
12
|
+
# <code>property_changed</code> method.
|
13
|
+
#
|
14
|
+
# CAUTION: When using observable string properties as keys in a Hash make
|
15
|
+
# sure you call the Object#to_s or Object#to_sym methods before putting
|
16
|
+
# the property value as key. Hashes uses the method Object#eql? when
|
17
|
+
# comparing keys, and for some unknown reason it is always returning false
|
18
|
+
# when comparing observable string properties.
|
19
|
+
#
|
20
|
+
class ObservablePropertyProxy < BaseObject
|
21
|
+
include RuGUI::LogSupport
|
22
|
+
|
23
|
+
def initialize(context, observable, property)
|
24
|
+
@context = context
|
25
|
+
@observable = observable
|
26
|
+
@property = property
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
NON_DELEGATABLE_METHODS = ['__id__', '__send__']
|
31
|
+
|
32
|
+
# Delegating Object's methods to context. Since none of these methods
|
33
|
+
# really change the object we just send them to the context.
|
34
|
+
self.methods.each do |method_name|
|
35
|
+
unless NON_DELEGATABLE_METHODS.include?(method_name)
|
36
|
+
self.class_eval <<-class_eval
|
37
|
+
def #{method_name}(*args)
|
38
|
+
@context.send(:#{method_name}, *args)
|
39
|
+
end
|
40
|
+
class_eval
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Here we reimplement the method missing, adding code to notify observers
|
45
|
+
# when the property has changed, i.e., when the context before calling the
|
46
|
+
# method is different than the context after the method is called.
|
47
|
+
def method_missing(method, *args, &block)
|
48
|
+
old_context = get_context_copy
|
49
|
+
return_value = @context.send(method, *args, &block)
|
50
|
+
|
51
|
+
context_changed(@context, old_context) unless @context == old_context
|
52
|
+
return_value
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a copy of the context.
|
56
|
+
def get_context_copy
|
57
|
+
begin
|
58
|
+
return @context.clone
|
59
|
+
rescue TypeError
|
60
|
+
return @context
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Called when the context has changed.
|
66
|
+
#
|
67
|
+
# Notifies all registered observers
|
68
|
+
#
|
69
|
+
def context_changed(new_value, old_value)
|
70
|
+
@observable.property_changed(@property, new_value, old_value)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'rugui/observable_property_proxy'
|
2
|
+
|
3
|
+
module RuGUI
|
4
|
+
# Adds support to observable properties.
|
5
|
+
module ObservablePropertySupport
|
6
|
+
# Initializes the observable properties. If you override this method, make
|
7
|
+
# sure that you call the <code>initialize_observable_property_support</code>
|
8
|
+
# method, so that the observable properties are initialized.
|
9
|
+
def initialize(observable_properties_values = {})
|
10
|
+
initialize_observable_property_support(observable_properties_values)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Initializes observable properties, setting their initial value.
|
14
|
+
def initialize_observable_property_support(observable_properties_values = {})
|
15
|
+
self.class.observable_properties_options.each do |property, options|
|
16
|
+
value = (observable_properties_values.with_indifferent_access[property] || clone_if_possible(options[:initial_value]))
|
17
|
+
send("#{property}=", value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Registers an observer for this model.
|
22
|
+
#
|
23
|
+
# The observer must implement a method with this signature:
|
24
|
+
#
|
25
|
+
# property_updated(observable, property, new_value, old_value)
|
26
|
+
#
|
27
|
+
# This method is called whenever a property has changed its value. One
|
28
|
+
# option is to include the PropertyObserver module in the observer class.
|
29
|
+
#
|
30
|
+
# Optionally, if <code>observable_name</code> can be given, a method with
|
31
|
+
# this signature will also be called:
|
32
|
+
#
|
33
|
+
# named_observable_property_updated(observable_name, observable, property, new_value, old_value)
|
34
|
+
#
|
35
|
+
def register_observer(observer, observable_name = nil)
|
36
|
+
initialize_observers_if_needed
|
37
|
+
@observers << observer
|
38
|
+
@named_observers[observable_name] = observer unless observable_name.nil?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Called whenver the a property has changed.
|
42
|
+
def property_changed(property, new_value, old_value)
|
43
|
+
initialize_observers_if_needed
|
44
|
+
@observers.each do |observer|
|
45
|
+
observer.property_updated(self, property, new_value, old_value) if observer.respond_to?(:property_updated)
|
46
|
+
end
|
47
|
+
@named_observers.each do |observable_name, observer|
|
48
|
+
observer.named_observable_property_updated(observable_name, self, property, new_value, old_value) if observer.respond_to?(:named_observable_property_updated)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Resets all observable properties for this observer.
|
53
|
+
#
|
54
|
+
# Since an observable property may be another observable there may exist
|
55
|
+
# some observers observing this other observable. In this scenario one
|
56
|
+
# should not attempt to set a new object into the observable property,
|
57
|
+
# because the observers would still be looking for the old observable.
|
58
|
+
#
|
59
|
+
# By calling <code>reset!</code> all observable properties are reset to the
|
60
|
+
# values specified when creating it. Also if the property respond to reset
|
61
|
+
# the method will be called, unless a *reset_value* is configured, i.e., it
|
62
|
+
# is not <code>nil</code>. Also, if *prevent_reset* is true, that property
|
63
|
+
# will not be reseted, even if it has a *reset_value* configured.
|
64
|
+
def reset!
|
65
|
+
self.class.observable_properties_options.each do |property, options|
|
66
|
+
unless options[:prevent_reset]
|
67
|
+
property_value = send(property)
|
68
|
+
if options[:reset_value].nil? and property_value.respond_to?(:reset!)
|
69
|
+
property_value.reset!
|
70
|
+
else
|
71
|
+
send("#{property}=", clone_if_possible(options[:reset_value]))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns <code>true</code> if <code>obj</code> is equals to
|
78
|
+
# <code>self</code>.
|
79
|
+
#
|
80
|
+
# This method checks if <code>obj</code> is of the same type of
|
81
|
+
# <code>self</code> and if all *core* observable_properties are equals.
|
82
|
+
def ==(obj)
|
83
|
+
if obj.is_a?(self.class)
|
84
|
+
self.class.core_observable_properties.each do |property|
|
85
|
+
return false unless obj.respond_to?(property) and respond_to?(property)
|
86
|
+
return false unless send(property) == obj.send(property)
|
87
|
+
end
|
88
|
+
return true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Copies all observable properties from _other_observable_ to _self_
|
93
|
+
def copy_observable_properties_from(other_observable, deep = true)
|
94
|
+
self.class.observable_properties.each do |property|
|
95
|
+
if other_observable.respond_to?(property)
|
96
|
+
other_property_value = other_observable.send(property)
|
97
|
+
if other_property_value.class.include?(ObservablePropertySupport)
|
98
|
+
send(property).copy_observable_properties_from(other_property_value) if deep
|
99
|
+
else
|
100
|
+
send("#{property}=", other_property_value)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns a map of all observable properties with theirs values.
|
107
|
+
def observable_properties
|
108
|
+
self.class.observable_properties.inject({}) { |properties, property| properties.merge!({ property => send(property) }) }
|
109
|
+
end
|
110
|
+
|
111
|
+
# Update observable properties values given a map of values
|
112
|
+
def update_observable_properties(values = {})
|
113
|
+
values.each { |property, value| send("#{property}=", value) if self.respond_to?("#{property}=") }
|
114
|
+
end
|
115
|
+
|
116
|
+
module ClassMethods
|
117
|
+
# Creates the necessary class inheritable attributes an initializes them.
|
118
|
+
def create_class_inheritable_attributes
|
119
|
+
self.class_inheritable_accessor :observable_properties_options
|
120
|
+
|
121
|
+
self.observable_properties_options = {}
|
122
|
+
end
|
123
|
+
|
124
|
+
# Register a observable properties for this model.
|
125
|
+
#
|
126
|
+
# Properties may be given as symbols, or strings. You can pass some
|
127
|
+
# options, in a hash, which will be used when the observable is created:
|
128
|
+
#
|
129
|
+
# - *initial_value*: The initial value for the property. This value will
|
130
|
+
# be set when the observable instance is initialized (i.e., when the
|
131
|
+
# <code>initialize</code> method is called). Defaults to <code>nil</code>.
|
132
|
+
# - *reset_value*: The reset value for the property. This value will be
|
133
|
+
# set when the observable instance is reset (i.e., when the
|
134
|
+
# <code>reset!</code> method is called). If this is not given, the
|
135
|
+
# <code>initial_value</code> will be used instead.
|
136
|
+
# - *core*: Defines whether the property should be used when comparing two
|
137
|
+
# observables. Defaults to <code>false</code>.
|
138
|
+
# - *prevent_reset*: If this is <code>true</code> the property will not be
|
139
|
+
# reseted. Defaults to false.
|
140
|
+
# - *boolean*: If this is <code>true</code> a "question" method will be
|
141
|
+
# created for the property (i.e., for a property named <code>foo</code>
|
142
|
+
# a method named <code>foo?</code> will be created).
|
143
|
+
#
|
144
|
+
# Examples:
|
145
|
+
#
|
146
|
+
# class MyObservable
|
147
|
+
# include RuGUI::ObservablePropertySupport
|
148
|
+
#
|
149
|
+
# observable_property :foo, :initial_value => "bar"
|
150
|
+
# observable_property :bar, :initial_value => "foo", :reset_value => "bar"
|
151
|
+
# observable_property :core_property, :core => true
|
152
|
+
# observable_property :non_resetable_property, :prevent_reset => true
|
153
|
+
#
|
154
|
+
# # And so on...
|
155
|
+
# end
|
156
|
+
def observable_property(property, options = {})
|
157
|
+
create_observable_property_options(property, options)
|
158
|
+
create_observable_property_accessors(property)
|
159
|
+
create_observable_property_boolean_readers(property, options)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Returns the names of core observable properties for this class.
|
163
|
+
def core_observable_properties
|
164
|
+
core_observable_properties = []
|
165
|
+
observable_properties_options.each do |property, options|
|
166
|
+
core_observable_properties << property if options[:core] == true
|
167
|
+
end
|
168
|
+
core_observable_properties
|
169
|
+
end
|
170
|
+
|
171
|
+
# Returns the names of all observable properties for this class.
|
172
|
+
def observable_properties
|
173
|
+
observable_properties_options.keys
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
def create_observable_property_options(property, options = {})
|
178
|
+
self.observable_properties_options[property.to_sym] = prepare_options(options)
|
179
|
+
end
|
180
|
+
|
181
|
+
def create_observable_property_accessors(property)
|
182
|
+
self.class_eval <<-class_eval
|
183
|
+
def #{property}
|
184
|
+
@#{property}
|
185
|
+
end
|
186
|
+
|
187
|
+
def #{property}=(value)
|
188
|
+
old_value = get_old_value(@#{property})
|
189
|
+
if has_changed?(value, old_value)
|
190
|
+
@#{property} = ObservablePropertyProxy.new(value, self, '#{property}')
|
191
|
+
property_changed('#{property}', value, old_value)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
class_eval
|
195
|
+
end
|
196
|
+
|
197
|
+
def create_observable_property_boolean_readers(property, options)
|
198
|
+
if options[:boolean]
|
199
|
+
self.class_eval <<-class_eval
|
200
|
+
def #{property}?
|
201
|
+
self.#{property} == true
|
202
|
+
end
|
203
|
+
class_eval
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def prepare_options(options)
|
208
|
+
options = default_options.merge(options)
|
209
|
+
if options[:reset_value].nil? and not options[:initial_value].class.include?(ObservablePropertySupport)
|
210
|
+
options[:reset_value] = options[:initial_value]
|
211
|
+
end
|
212
|
+
options
|
213
|
+
end
|
214
|
+
|
215
|
+
def default_options
|
216
|
+
{ :core => false,
|
217
|
+
:initial_value => nil,
|
218
|
+
:reset_value => nil }
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.included(base)
|
223
|
+
base.extend(ClassMethods)
|
224
|
+
base.create_class_inheritable_attributes
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
def initialize_observers_if_needed
|
229
|
+
@observers = [] if not defined?(@observers) or @observers.nil?
|
230
|
+
@named_observers = {} if not defined?(@named_observers) or @named_observers.nil?
|
231
|
+
end
|
232
|
+
|
233
|
+
def get_old_value(property)
|
234
|
+
begin
|
235
|
+
return property.clone
|
236
|
+
rescue TypeError
|
237
|
+
return property
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def has_changed?(new_value, old_value)
|
242
|
+
!(new_value.kind_of?(old_value.class) && old_value == new_value)
|
243
|
+
end
|
244
|
+
|
245
|
+
def clone_if_possible(value)
|
246
|
+
value.clone
|
247
|
+
rescue TypeError
|
248
|
+
value
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module RuGUI
|
2
|
+
module Plugin
|
3
|
+
class Loader
|
4
|
+
attr_accessor :initializer
|
5
|
+
attr_accessor :configurations
|
6
|
+
cattr_accessor :located_plugins
|
7
|
+
|
8
|
+
def initialize(initializer, configurations)
|
9
|
+
self.initializer = initializer
|
10
|
+
self.configurations = configurations
|
11
|
+
@@located_plugins ||= []
|
12
|
+
end
|
13
|
+
|
14
|
+
def load_plugins
|
15
|
+
plugins.each do |plugin|
|
16
|
+
plugin.load unless plugin.loaded?
|
17
|
+
register_as_loaded(plugin)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def plugins
|
22
|
+
@plugins ||= locate_plugins
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
# Locate all plugins in APPLICATION_ROOT/vendor/plugins
|
27
|
+
def locate_plugins
|
28
|
+
Dir.glob(File.join(APPLICATION_ROOT, "vendor", "plugins", "*")).each do |dir|
|
29
|
+
@@located_plugins << Location.new(dir)
|
30
|
+
end
|
31
|
+
@@located_plugins
|
32
|
+
end
|
33
|
+
|
34
|
+
# Register plugins as loaded.
|
35
|
+
def register_as_loaded(plugin)
|
36
|
+
plugin.loaded = true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# This class is a representation of RuGUI plugins.
|
41
|
+
class Location
|
42
|
+
attr_accessor :plugin_name
|
43
|
+
attr_accessor :dir
|
44
|
+
attr_accessor :loaded
|
45
|
+
|
46
|
+
include RuGUI::LogSupport
|
47
|
+
|
48
|
+
def initialize(dir)
|
49
|
+
self.dir = dir
|
50
|
+
self.plugin_name = dir.split(File::SEPARATOR).last
|
51
|
+
end
|
52
|
+
|
53
|
+
# Load plugins.
|
54
|
+
def load
|
55
|
+
$LOAD_PATH.unshift(File.join(self.dir, "lib")) if File.directory?(self.dir)
|
56
|
+
$LOAD_PATH.uniq!
|
57
|
+
|
58
|
+
init_file = File.expand_path(File.join(self.dir, "init.rb"))
|
59
|
+
if File.exist?(init_file)
|
60
|
+
require_for init_file
|
61
|
+
else
|
62
|
+
logger.warn "The init file for (#{self.plugin_name}) was not found."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def require_for(init_file)
|
67
|
+
require init_file
|
68
|
+
rescue Exception
|
69
|
+
logger.error "An error occurred while loading #{self.plugin_name}. Checks its init file: #{$!}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def loaded?
|
73
|
+
self.loaded ||= false
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|