module RuGUI
# A base class for views.
#
# To use this class create a subclass and reimplement #setup_widgets, if you
# want to create your interface by hand. If you want to use a builder file
# just call #use_builder and, optionally, call #builder_file passing the
# filename to use.
#
# Each adapter may implement additional features, extending this class, look
# for it in adapter classes.
#
# The view may have ViewHelpers, which works as 'models' for views, i.e., they
# have observable properties that can be observed by the view. A default
# helper, named as {view_name}Helper
is registered if it exists.
# For example, for a view named *MyView*, the default view helper should be
# named *MyViewHelper*. This helper can be accessed as a helper
# attribute. Other helpers may be registered if needed.
#
# Example (using GTK framework adapter):
# class MyGladeView < RuGUI::BaseView
# builder_file 'my_file.glade' # this is optional, if the glade file was called my_glade_view.glade this wouldn't be needed.
# root :top_window
# use_builder
# end
#
# class MyHandView < RuGUI::BaseView
# def setup_widgets
# # do your hand-made code here...
# end
# end
class BaseView < BaseObject
include RuGUI::LogSupport
include RuGUI::PropertyObserver
include RuGUI::SignalSupport
attr_accessor :controllers
attr_reader :widgets
attr_reader :unnamed_widgets
class_inheritable_accessor :configured_builder_file
class_inheritable_accessor :configured_builder_file_usage
class_inheritable_accessor :configured_builder_file_extension
class_inheritable_accessor :configured_root
class_inheritable_accessor :configured_display_root
def initialize
@controllers = {}
@helpers = {}
@unnamed_widgets = []
@widgets = {}
register_default_helper
setup_view_helpers
build_from_builder_file if use_builder?
setup_widgets
autoconnect_signals(self)
end
# This is included here so that the initialize method is properly updated.
include RuGUI::InitializeHooks
# Returns the framework_adapter for this class.
def framework_adapter
framework_adapter_for('BaseView')
end
# Reimplement this method to create widgets by hand.
def setup_widgets
end
# Reimplement this method to setup view helpers.
def setup_view_helpers
end
# Adds the given widget to a container widget.
def add_widget_to_container(widget, container_widget_or_name)
self.framework_adapter.add_widget_to_container(widget, from_widget_or_name(container_widget_or_name))
end
# Adds the given widget to a container widget.
def remove_widget_from_container(widget, container_widget_or_name)
self.framework_adapter.remove_widget_from_container(widget, from_widget_or_name(container_widget_or_name))
end
# Includes a view root widget inside the given container widget.
def include_view(container_widget_name, view)
raise RootWidgetNotSetForIncludedView, "You must set a root for views to be included." if view.root_widget.nil?
add_widget_to_container(view.root_widget, container_widget_name)
end
# Removes a view root widget from the given container widget.
def remove_view(container_widget_name, view)
raise RootWidgetNotSetForIncludedView, "You must set a root for views to be removed." if view.root_widget.nil?
remove_widget_from_container(view.root_widget, container_widget_name)
end
# Removes all children from the given container widget
def remove_all_children(container_widget)
self.framework_adapter.remove_all_children(container_widget)
end
# Registers a controller as receiver of signals from the view widgets.
def register_controller(controller, name = nil)
name ||= controller.class.to_s.underscore
autoconnect_signals(controller)
@controllers[name.to_sym] = controller
end
# Registers a view helper for the view.
def register_helper(helper, name = nil)
helper = create_instance_if_possible(helper) if helper.is_a?(String) or helper.is_a?(Symbol)
unless helper.nil?()
name ||= helper.class.to_s.underscore
helper.register_observer(self, name)
@helpers[name.to_sym] = helper
create_attribute_reader(:helpers, name)
helper.post_registration(self)
end
end
# Called after the view is registered in a controller.
def post_registration(controller)
end
# Returns the root widget if one is set.
def root_widget
send(root.to_sym) if not root.nil?
end
# Returns the builder file.
def builder_file
self.configured_builder_file
end
# Returns the builder file extension.
def builder_file_extension
self.configured_builder_file_extension
end
# Returns true if builder file is being used for this view.
def use_builder?
self.configured_builder_file_usage
end
# Framework adapters should implement this if they support builder files.
def build_from_builder_file
filename = get_builder_file
raise BuilderFileNotFoundError, "Could not find builder file for view #{self.class.name}. UI file paths: #{RuGUI.configuration.builder_files_paths.join(', ')}." if filename.nil?
self.framework_adapter.build_widgets_from(filename)
self.framework_adapter.register_widgets
end
# Returns the name of the root widget for this view.
def root
self.configured_root.to_s unless self.configured_root.nil?
end
def display_root?
!!self.configured_display_root
end
class << self
# Sets the name of the root widget for this view.
#
# This is specially useful when more than one view uses the same glade
# file, but each one uses a diferent widget tree inside that glade file.
#
# Other use for this is when building a reusable widget, composed of the
# contents of a glade file. One could create a window, place a vertical
# box, and then place elements inside this vertical box. Later, this glade
# file is used to insert the contents of the vertical box inside another
# vertical box in other glade file.
def root(root_widget_name)
self.configured_root = root_widget_name
end
# Sets the builder file to use when creating this view.
def builder_file(file)
self.configured_builder_file = file
end
# Tells whether we should use a builder file when creating this view.
#
# By default the root widget will be displayed, but you can pass false to
# this method to prevent it for being displayed.
def use_builder(display_root = true)
self.configured_builder_file_usage = true
self.configured_display_root = display_root
self.configured_builder_file_extension = self.framework_adapter_class.builder_file_extension
default_builder_file_path = RuGUI.root.join('app', 'resources', "#{self.configured_builder_file_extension}")
RuGUI.configuration.builder_files_paths << default_builder_file_path unless RuGUI.configuration.builder_files_paths.include?(default_builder_file_path)
end
def framework_adapter_class
class_adapter_for('BaseView')
end
end
protected
# Builds a widget of the given type, possibly adding it to a parent
# widget, and display it.
#
# The *args are passed to the widget constructor.
def build_widget(widget_type, widget_name = nil, parent = nil, *args)
widget = widget_type.new(*args)
self.framework_adapter.set_widget_name(widget, widget_name)
add_widget(widget, widget_name)
add_widget_to_container(widget, parent) unless parent.nil?
widget.show
end
# Adds the widget to the view.
#
# If +widget_name+ is not given one is assumed the widget`s name will be
# used instead. If the widget doesn't have a name it will be added as an
# unnamed widget (accessible through #unnamed_widgets property.
def add_widget(widget, widget_name = nil)
widget_name ||= widget.name
widget_name = widget_name.to_s
unless widget_name.nil? || widget_name.empty?
create_attribute_for_widget(widget_name)
send("#{widget_name}=", widget)
@widgets[widget_name] = widget
else
@unnamed_widgets << widget
end
end
def from_widget_or_name(widget_or_name)
if widget_or_name.is_a?(String) || widget_or_name.is_a?(Symbol)
send(widget_or_name)
else
widget_or_name
end
end
def autoconnect_signals(receiver)
receiver.autoconnect_declared_signals(self)
self.framework_adapter.autoconnect_signals(receiver)
end
private
def get_builder_file
filename = (not self.builder_file.nil?) ? self.builder_file : "#{self.class.to_s.underscore}.#{builder_file_extension}"
# The builder file given may already contain a full path to a glade file.
return filename if File.file?(filename)
filename = "#{filename}.#{builder_file_extension}" unless File.extname(filename) == ".#{builder_file_extension}"
paths = RuGUI.configuration.builder_files_paths.select do |path|
File.file?(File.join(path, filename))
end
File.join(paths.first, filename) unless paths.empty?
end
# Attempts to register the default helper for the view
def register_default_helper
register_helper("#{self.class.name}Helper", :helper)
end
def create_attribute_for_widget(widget_name)
self.instance_eval <<-instance_eval
def #{widget_name}
@#{widget_name}
end
def #{widget_name}=(widget)
@#{widget_name} = widget
end
instance_eval
end
# Creates an attribute reader for the some entity.
def create_attribute_reader(entity, name)
self.class.class_eval <<-class_eval
def #{name}
@#{entity}[:#{name}]
end
class_eval
end
# Creates an instance of the given class.
def create_instance_if_possible(klass_name, *args)
klass_name.to_s.camelize.constantize.new(*args)
rescue NameError
# Couldn't create instance.
end
end
# Exception thrown when the builder file for this view could not be found.
class BuilderFileNotFoundError < Exception
end
# Exception thrown when attempting to include a view which don't have a root
# set.
class RootWidgetNotSetForIncludedView < Exception
end
end