metaruby 1.0.0.rc2 → 1.0.0.rc3

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.
@@ -1,20 +1,43 @@
1
1
  require 'metaruby/gui/html'
2
+ require 'metaruby/gui/exception_rendering'
2
3
 
3
4
  module MetaRuby
4
5
  module GUI
5
6
  # Widget that allows to display a list of exceptions
7
+ #
8
+ # @deprecated use {HTML::Page} and {HTML::Page#push_exception} directly
9
+ # instead
6
10
  class ExceptionView < Qt::WebView
7
- RESSOURCES_DIR = File.expand_path('html', File.dirname(__FILE__))
8
-
9
11
  attr_reader :displayed_exceptions
12
+
13
+ # @return [HTML::Page] the page object that allows to infer
10
14
  attr_reader :metaruby_page
11
15
 
16
+ # @return [#head,#scripts,#render] an object that allows to render
17
+ # exceptions in HTML
18
+ attr_reader :exception_rendering
19
+
12
20
  def initialize(parent = nil)
13
21
  super
14
- @metaruby_page = HTML::Page.new(self.page)
15
- connect(@metaruby_page, SIGNAL('fileOpenClicked(const QUrl&)'), self, SLOT('fileOpenClicked(const QUrl&)'))
22
+
16
23
  @displayed_exceptions = []
17
24
  self.focus_policy = Qt::NoFocus
25
+
26
+ @metaruby_page = HTML::Page.new(self.page)
27
+ connect(@metaruby_page, SIGNAL('fileOpenClicked(const QUrl&)'),
28
+ self, SLOT('fileOpenClicked(const QUrl&)'))
29
+ @exception_rendering = ExceptionRendering.new(metaruby_page)
30
+
31
+ if ENV['METARUBY_GUI_DEBUG_HTML']
32
+ page.settings.setAttribute(Qt::WebSettings::DeveloperExtrasEnabled, true)
33
+ @inspector = Qt::WebInspector.new
34
+ @inspector.page = page
35
+ @inspector.show
36
+ end
37
+ end
38
+
39
+ def user_file_filter=(filter)
40
+ exception_rendering.user_file_filter = filter
18
41
  end
19
42
 
20
43
  def push(exception, reason = nil)
@@ -33,29 +56,14 @@ module MetaRuby
33
56
 
34
57
  TEMPLATE = <<-EOD
35
58
  <head>
36
- <link rel="stylesheet" href="file://#{File.join(RESSOURCES_DIR, 'exception_view.css')}" type="text/css" />
37
- <script type="text/javascript" src="file://#{File.join(RESSOURCES_DIR, 'jquery.min.js')}"></script>
59
+ <%= exception_rendering.head %>
38
60
  </head>
39
- <script type="text/javascript">
40
- $(document).ready(function () {
41
- $("tr.backtrace").hide()
42
- $("a.backtrace_toggle_filtered").click(function (event) {
43
- var eventId = $(this).attr("id");
44
- $("#backtrace_full_" + eventId).hide();
45
- $("#backtrace_filtered_" + eventId).toggle();
46
- event.preventDefault();
47
- });
48
- $("a.backtrace_toggle_full").click(function (event) {
49
- var eventId = $(this).attr("id");
50
- $("#backtrace_full_" + eventId).toggle();
51
- $("#backtrace_filtered_" + eventId).hide();
52
- event.preventDefault();
53
- });
54
- });
55
- </script>
61
+ <%= exception_rendering.scripts %>
56
62
  <body>
57
63
  <table class="exception_list">
58
- <%= each_exception.each_with_index.map { |(e, reason), idx| render_exception(e, reason, idx) }.join("\\n") %>
64
+ <%= each_exception.each_with_index.map do |(e, reason), idx|
65
+ exception_rendering.render(e, reason, idx)
66
+ end.join("\\n") %>
59
67
  </table>
60
68
  </body>
61
69
  EOD
@@ -64,93 +72,6 @@ module MetaRuby
64
72
  self.html = ERB.new(TEMPLATE).result(binding)
65
73
  end
66
74
 
67
- class BacktraceParser
68
- def initialize(backtrace)
69
- @backtrace = backtrace || []
70
- end
71
-
72
- def parse
73
- call_stack(0)
74
- end
75
-
76
- def pp_callstack(level)
77
- @backtrace[level..-1]
78
- end
79
-
80
- def pp_call_stack(level)
81
- @backtrace[level..-1]
82
- end
83
- end
84
-
85
- EXCEPTION_TEMPLATE_WITHOUT_BACKTRACE = <<-EOF
86
- <tr class="message">
87
- <td id="<%= idx %>"><%= escape_html(reason) if reason %><pre><%= message.join("\n") %></pre></td>
88
- </tr>
89
- EOF
90
-
91
- EXCEPTION_TEMPLATE_WITH_BACKTRACE = <<-EOF
92
- <tr class="message">
93
- <td id="<%= idx %>"><%= escape_html(reason) if reason %><pre><%= message.join("\n") %></pre>
94
- <span class="backtrace_links">
95
- (show: <a class="backtrace_toggle_filtered" id="<%= idx %>">filtered backtrace</a>,
96
- <a class=\"backtrace_toggle_full\" id="<%= idx %>">full backtrace</a>)
97
- </span>
98
- </td>
99
- </tr>
100
- <tr class="backtrace_summary">
101
- <td>from <%= origin_file %>:<%= origin_line %>:in <%= escape_html(origin_method.to_s) %></td>
102
- </tr>
103
- <tr class="backtrace" id="backtrace_filtered_<%= idx %>">
104
- <td><%= render_backtrace(filtered_backtrace) %></td>
105
- </tr>
106
- <tr class="backtrace" id="backtrace_full_<%= idx %>">
107
- <td><%= render_backtrace(full_backtrace) %></td>
108
- </tr>
109
- EOF
110
-
111
- def filter_backtrace(backtrace)
112
- backtrace
113
- end
114
-
115
- def user_file?(file)
116
- true
117
- end
118
-
119
- def render_exception(e, reason, idx)
120
- message = PP.pp(e, "").split("\n").map { |line| escape_html(line) }
121
-
122
- if e.backtrace && !e.backtrace.empty?
123
- filtered_backtrace = BacktraceParser.new(filter_backtrace(e.backtrace)).parse
124
- full_backtrace = BacktraceParser.new(e.backtrace).parse
125
- origin_file, origin_line, origin_method =
126
- filtered_backtrace.find { |file, _| user_file?(file) } ||
127
- filtered_backtrace.first ||
128
- full_backtrace.first
129
-
130
- origin_file = metaruby_page.link_to(Pathname.new(origin_file), origin_file, lineno: origin_line)
131
- ERB.new(EXCEPTION_TEMPLATE_WITH_BACKTRACE).result(binding)
132
- else
133
- ERB.new(EXCEPTION_TEMPLATE_WITHOUT_BACKTRACE).result(binding)
134
- end
135
- end
136
-
137
- def render_backtrace(backtrace)
138
- result = []
139
- backtrace.each do |file, line, method|
140
- file_link = metaruby_page.link_to(Pathname.new(file), file, lineno: line)
141
- if user_file?(file)
142
- result << " <span class=\"app_file\">#{file_link}:#{line}:in #{escape_html(method.to_s)}</span><br/>"
143
- else
144
- result << " #{file_link}:#{line}:in #{escape_html(method.to_s)}<br/>"
145
- end
146
- end
147
- result.join("\n")
148
- end
149
-
150
- def escape_html(l)
151
- l.gsub('<', '&lt;').gsub('>', '&gt;')
152
- end
153
-
154
75
  def exceptions=(list)
155
76
  @displayed_exceptions = list.dup
156
77
  update_html
@@ -4,7 +4,14 @@ require 'metaruby/gui/html/collection'
4
4
 
5
5
  module MetaRuby
6
6
  module GUI
7
+ # Basic functionality to generate HTML pages
8
+ #
9
+ # The core functionality is in {Page}
7
10
  module HTML
11
+ # Escape the string to include in HTML
12
+ #
13
+ # @param [String] string
14
+ # @return [String]
8
15
  def self.escape_html(string)
9
16
  string.
10
17
  gsub('<', '&lt;').
@@ -1,59 +1,101 @@
1
1
  module MetaRuby
2
2
  module GUI
3
3
  module HTML
4
+ # Representation of a button in {Page}
4
5
  class Button
6
+ # The button ID
7
+ #
8
+ # It is used to generate the button's {#base_url}
9
+ #
10
+ # @return [String]
5
11
  attr_reader :id
12
+
13
+ # The text when the button is ON
14
+ #
15
+ # @return [String]
6
16
  attr_reader :on_text
17
+
18
+ # The text when the button is OFF
19
+ #
20
+ # @return [String]
7
21
  attr_reader :off_text
8
- attr_accessor :state
9
22
 
10
- def initialize(id, options = Hash.new)
11
- options = Kernel.validate_options options,
12
- :text => nil,
13
- :on_text => "#{id} (on)", :off_text => "#{id} (off)",
14
- :state => false
23
+ # The current button state
24
+ attr_accessor :state
15
25
 
26
+ # Create a button
27
+ #
28
+ # @param [String] id the button {#id}
29
+ # @param [String] text the button text for a non-toggling button
30
+ # @param [String] on_text the button text for a toggling button
31
+ # when it is ON
32
+ # @param [String] off_text the button text for a toggling button
33
+ # when it is OFF
34
+ # @param [Boolean] state the initial button state
35
+ def initialize(id, text: nil, on_text: "#{id} (on)", off_text: "#{id} (off)", state: false)
16
36
  if id[0, 1] != '/'
17
37
  id = "/#{id}"
18
38
  elsif id[-1, 1] == '/'
19
39
  id = id[0..-2]
20
40
  end
21
41
  @id = id
22
- if options[:text]
23
- @on_text = options[:text]
24
- @off_text = options[:text]
42
+ if text
43
+ @on_text = text
44
+ @off_text = text
25
45
  @state = true
26
46
  else
27
- @on_text = options[:on_text]
28
- @off_text = options[:off_text]
29
- @state = options[:state]
47
+ @on_text = on_text
48
+ @off_text = off_text
49
+ @state = state
30
50
  end
31
51
  end
32
52
 
53
+ # @api private
54
+ #
55
+ # The ID, quoted for HTML
56
+ #
57
+ # @return [String]
33
58
  def html_id; id.gsub(/[^\w]/, '_') end
34
59
 
60
+ # The button base URL
61
+ #
62
+ # @return [String]
35
63
  def base_url; "btn://metaruby#{id}" end
64
+
65
+ # The URL that would toggle the button (i.e. turn it off if it
66
+ # is ON)
36
67
  def toggle_url
37
68
  if state then "#{base_url}#off"
38
69
  else "#{base_url}#on"
39
70
  end
40
71
  end
72
+
73
+ # The URL that represents this button and its current state
41
74
  def url
42
75
  if state then "#{base_url}#on"
43
76
  else "#{base_url}#off"
44
77
  end
45
78
  end
79
+
80
+ # The button text
46
81
  def text
47
82
  if state then off_text
48
83
  else on_text
49
84
  end
50
85
  end
51
86
 
87
+ # Render the button as HTML
88
+ #
89
+ # @return [String]
52
90
  def render
53
91
  "<a id=\"#{html_id}\" href=\"#{toggle_url}\">#{text}</a>"
54
92
  end
55
93
  end
56
94
 
95
+ # Render a button bar into HTML
96
+ #
97
+ # @param [Array<Button>] buttons
98
+ # @return [String]
57
99
  def self.render_button_bar(buttons)
58
100
  if !buttons.empty?
59
101
  "<div class=\"button_bar\"><span>#{buttons.map(&:render).join(" / ")}</span></div>"
@@ -1,7 +1,11 @@
1
1
  module MetaRuby::GUI
2
2
  module HTML
3
- # Base class providing functionality to render collections of objects
4
- # whose rendering can be delegated
3
+ # Base class providing functionality to first rendering a collection of
4
+ # object, and then render one item in the collection when the user
5
+ # clicks on the collection element
6
+ #
7
+ # Rendering is delegated to other objects through a {RenderingManager}.
8
+ # The main interface to the manager is {#register_type}
5
9
  class Collection < Qt::Object
6
10
  # @return [#push] the page on which we publish the HTML
7
11
  attr_reader :page
@@ -15,8 +19,11 @@ module MetaRuby::GUI
15
19
  # @return [<Exception>] exceptions caught during element rendering
16
20
  attr_reader :registered_exceptions
17
21
 
18
- Element = Struct.new :object, :format, :url, :text, :rendering_options, :attributes
22
+ # Representation of an element in the collection
23
+ class Element < Struct.new(:object, :format, :url, :text, :rendering_options, :attributes)
24
+ end
19
25
 
26
+ # Create a collection that acts on a page
20
27
  def initialize(page)
21
28
  super()
22
29
  @page = page
@@ -26,26 +33,31 @@ module MetaRuby::GUI
26
33
  @registered_exceptions = Array.new
27
34
  end
28
35
 
29
- def register_type(model, rendering_class, render_options = Hash.new)
30
- manager.register_type(model, rendering_class, render_options)
36
+ # (see RenderingManager#register_type)
37
+ def register_type(type, rendering_class, render_options = Hash.new)
38
+ manager.register_type(type, rendering_class, render_options)
31
39
  end
32
40
 
41
+ # (see RenderingManager#enable)
33
42
  def enable
34
- connect(page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('linkClicked(const QUrl&)'))
43
+ connect(page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('linkClickedHandler(const QUrl&)'))
35
44
  manager.enable
36
45
  end
37
46
 
47
+ # (see RenderingManager#disable)
38
48
  def disable
39
- disconnect(page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('linkClicked(const QUrl&)'))
49
+ disconnect(page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('linkClickedHandler(const QUrl&)'))
40
50
  manager.disable
41
51
  end
42
52
 
53
+ # Clear the current view
43
54
  def clear
44
55
  @object_id_to_object.clear
45
56
  registered_exceptions.clear
46
57
  manager.clear
47
58
  end
48
59
 
60
+ # The namespace used on URIs generated by the collection
49
61
  def namespace
50
62
  object_id.to_s + "/"
51
63
  end
@@ -58,6 +70,12 @@ module MetaRuby::GUI
58
70
  end
59
71
  end
60
72
 
73
+ # Render a list of {Element}
74
+ #
75
+ # @param [String] title the section title
76
+ # @param [Array<Element>] links the links that should be rendered
77
+ # @param [Hash] push_options additional options that should be
78
+ # passed to page#render_list
61
79
  def render_links(title, links, push_options = Hash.new)
62
80
  links.each do |el|
63
81
  object_id_to_object[el.object.object_id] = el.object
@@ -79,7 +97,12 @@ module MetaRuby::GUI
79
97
  end
80
98
  end
81
99
 
82
- def linkClicked(url)
100
+ # @api private
101
+ #
102
+ # Handler called when a link is clicked on the page. It renders an
103
+ # object when the link is a link to an object of the collection, or
104
+ # passes the URL to the linkClicked signal otherwise.
105
+ def linkClickedHandler(url)
83
106
  if url.host == "metaruby" && url.path =~ /^\/#{Regexp.quote(namespace)}(\d+)/
84
107
  object = object_id_to_object[Integer($1)]
85
108
  render_element(object)
@@ -87,7 +110,8 @@ module MetaRuby::GUI
87
110
  super
88
111
  end
89
112
  end
90
- slots 'linkClicked(const QUrl&)'
113
+ slots 'linkClickedHandler(const QUrl&)'
114
+ signals 'linkClicked(const QUrl&)'
91
115
 
92
116
  def render_element(object, options = Hash.new)
93
117
  page.restore
@@ -1,8 +1,6 @@
1
- body {font-family: arial, sans-serif; font-size: 12px; }
2
- table { width: 100%; font-size: 12px; }
3
- .message td { font-size: 12px; font-weight: normal; background-color: rgb(176,206,234); text-align: left; }
1
+ .message { font-size: 80%; font-weight: normal; background-color: rgb(176,206,234); text-align: left; }
4
2
  .backtrace_links { font-size: 80%; margin-left: 2em;}
5
- .backtrace td { font-size: 12px; background-color: rgb(219,230,241); padding-left: 1em; }
6
- .backtrace_summary td { background-color: rgb(219,230,241); padding-left: 1em; }
7
- .app_file { font-weight: bold; }
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; }
5
+ .user_file { font-weight: bold; }
8
6
 
@@ -1,12 +1,12 @@
1
- <% if options[:filter] %>
1
+ <% if filter %>
2
2
  <script type="text/javascript">
3
3
  jQuery(document).ready(function(){
4
- jQuery("div#<%= push_options[:id] %>-list").selectFilter();
4
+ jQuery("div#<%= id %>-list").selectFilter();
5
5
  });
6
6
  </script>
7
7
  <% end %>
8
8
 
9
- <div name="index_filter" id="<%= push_options[:id] %>-list">
9
+ <div name="index_filter" id="<%= id %>-list">
10
10
  <%
11
11
  items.each_with_index do |(data, attributes), index|
12
12
  if attributes
@@ -1,43 +1,157 @@
1
1
  module MetaRuby::GUI
2
2
  module HTML
3
+ # The directory relative to which ressources (such as css or javascript
4
+ # files) are resolved by default
3
5
  RESSOURCES_DIR = File.expand_path(File.dirname(__FILE__))
4
6
 
5
7
  # A class that can be used as the webpage container for the Page class
6
8
  class HTMLPage
9
+ # The HTML content
7
10
  attr_accessor :html
8
11
 
12
+ # Method expected by {Page}
9
13
  def main_frame; self end
10
14
  end
11
15
 
12
16
  # A helper class that gives us easy-to-use page elements on a
13
17
  # Qt::WebView
18
+ #
19
+ # Such a page is managed as a list of sections (called {Fragment}). A
20
+ # new fragment is added or updated with {#push}
14
21
  class Page < Qt::Object
22
+ # The content of the <title> tag
23
+ # @return [String,nil]
24
+ attr_accessor :page_name
25
+
26
+ # The content of a toplevel <h1> tag
27
+ # @return [String,nil]
28
+ attr_accessor :title
29
+
30
+ # The underlying page rendering object
31
+ #
32
+ # @return [Qt::WebPage,HTMLPage]
33
+ attr_reader :page
34
+
35
+ # List of fragments
36
+ #
37
+ # @return [Array<Fragment>]
15
38
  attr_reader :fragments
16
- attr_reader :view
39
+
40
+ # Static mapping of objects to URIs
41
+ #
42
+ # @see #uri_fo
17
43
  attr_accessor :object_uris
18
- attr_reader :javascript
19
44
 
45
+ # Content to be rendered in the page head
46
+ #
47
+ # @return [Array<String>]
48
+ attr_reader :head
49
+
50
+ # Scripts to be loaded in the page
51
+ #
52
+ # @return [Array<String>]
53
+ attr_reader :scripts
54
+
55
+ # Object used to render exceptions in {#push_exception}
56
+ #
57
+ # It is set by {#enable_exception_rendering}
58
+ #
59
+ # @return [#render]
60
+ attr_reader :exception_rendering
61
+
62
+ # Creates a new Page object
63
+ #
64
+ # @param [Qt::WebPage,HTMLPage] page
65
+ def initialize(page)
66
+ super()
67
+ @page = page
68
+ @head = Array.new
69
+ @scripts = Array.new
70
+ @fragments = []
71
+ @templates = Hash.new
72
+ @auto_id = 0
73
+
74
+ if page.kind_of?(Qt::WebPage)
75
+ page.link_delegation_policy = Qt::WebPage::DelegateAllLinks
76
+ Qt::Object.connect(page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('pageLinkClicked(const QUrl&)'))
77
+ end
78
+ @object_uris = Hash.new
79
+ end
80
+
81
+ # A page fragment (or section)
20
82
  class Fragment
83
+ # The fragmen title (rendered with <h2>)
21
84
  attr_accessor :title
85
+ # The fragment's HTML content
22
86
  attr_accessor :html
87
+ # The fragment ID (as, in, HTML id)
23
88
  attr_accessor :id
89
+ # A list of buttons to be rendered before the fragment title and
90
+ # its content
91
+ #
92
+ # @return [Array<Button>]
24
93
  attr_reader :buttons
25
94
 
26
- def initialize(title, html, view_options = Hash.new)
27
- view_options = Kernel.validate_options view_options,
28
- :id => nil, :buttons => []
95
+ # Create a new fragment
96
+ def initialize(title, html, id: nil, buttons: Array.new)
29
97
  @title = title
30
98
  @html = html
31
- @id = view_options[:id]
32
- @buttons = view_options[:buttons]
99
+ @id = id
100
+ @buttons = buttons
101
+ end
102
+ end
103
+
104
+ # Add content to the page setup (head and scripts)
105
+ #
106
+ # @param [#head,#scripts] obj the object defining the content to be
107
+ # added
108
+ # @see add_to_head add_scripts
109
+ def add_to_setup(obj)
110
+ add_to_head(obj.head)
111
+ add_script(obj.scripts)
112
+ end
113
+
114
+ # Add content to {#head}
115
+ #
116
+ # @param [String] html
117
+ def add_to_head(html)
118
+ head << html
119
+ end
120
+
121
+ # Add content to {#scripts}
122
+ #
123
+ # @param [String] html
124
+ def add_script(html)
125
+ scripts << html
126
+ end
127
+
128
+ # Resolves a relative path to a path in the underlying application's
129
+ # resource folder
130
+ #
131
+ # @return [String]
132
+ def path_in_resource(path)
133
+ if Pathname.new(path).absolute?
134
+ path
135
+ else
136
+ File.join('${RESOURCE_DIR}', path)
33
137
  end
34
138
  end
35
139
 
140
+ # Load a javascript file in the head
36
141
  def load_javascript(file)
37
- javascript << File.expand_path(file)
142
+ add_to_head(
143
+ "<script type=\"text/javascript\" src=\"#{path_in_resource(file)}\"></script>")
38
144
  end
39
145
 
40
- def link_to(object, text = nil, args = Hash.new)
146
+ # Helper that generates a HTML link to a given object
147
+ #
148
+ # The object URI is resolved using {#uri_for}. If there is no known
149
+ # link to the object, it is returned as text
150
+ #
151
+ # @param [Object] object the object to create a link to
152
+ # @param [String] text the link text. Defaults to object#name
153
+ # @return [String]
154
+ def link_to(object, text = nil, **args)
41
155
  text = HTML.escape_html(text || object.name || "<anonymous>")
42
156
  if uri = uri_for(object)
43
157
  if uri !~ /^\w+:\/\//
@@ -67,12 +181,30 @@ module MetaRuby::GUI
67
181
  self.class.main_doc(text)
68
182
  end
69
183
 
184
+ # The ERB template for a page
185
+ #
186
+ # @see html
70
187
  PAGE_TEMPLATE = File.join(RESSOURCES_DIR, "page.rhtml")
188
+ # The ERB template for a page body
189
+ #
190
+ # @see html_body
71
191
  PAGE_BODY_TEMPLATE = File.join(RESSOURCES_DIR, "page_body.rhtml")
192
+ # The ERB template for a page fragment
193
+ #
194
+ # @see push
72
195
  FRAGMENT_TEMPLATE = File.join(RESSOURCES_DIR, "fragment.rhtml")
196
+ # The ERB template for a list
197
+ #
198
+ # @see render_list
73
199
  LIST_TEMPLATE = File.join(RESSOURCES_DIR, "list.rhtml")
200
+ # Assets (CSS, javascript) that are included in every page
74
201
  ASSETS = %w{page.css jquery.min.js jquery.selectfilter.js}
75
202
 
203
+ # Copy the assets to a target directory
204
+ #
205
+ # This can be used to create self-contained HTML pages using the
206
+ # Page class, by providing a different ressource dir to e.g. {#html}
207
+ # or {#html_body} and copying the assets to it.
76
208
  def self.copy_assets_to(target_dir, assets = ASSETS)
77
209
  FileUtils.mkdir_p target_dir
78
210
  assets.each do |file|
@@ -80,6 +212,9 @@ module MetaRuby::GUI
80
212
  end
81
213
  end
82
214
 
215
+ # Lazy loads a template
216
+ #
217
+ # @return [ERB]
83
218
  def load_template(*path)
84
219
  path = File.join(*path)
85
220
  @templates[path] ||= ERB.new(File.read(path))
@@ -87,27 +222,16 @@ module MetaRuby::GUI
87
222
  @templates[path]
88
223
  end
89
224
 
90
- attr_reader :page
91
-
92
- attr_accessor :page_name
93
- attr_accessor :title
94
-
95
- def initialize(page)
96
- super()
97
- @page = page
98
- @fragments = []
99
- @templates = Hash.new
100
- @auto_id = 0
101
- @javascript = Array.new
102
-
103
- if page.kind_of?(Qt::WebPage)
104
- page.link_delegation_policy = Qt::WebPage::DelegateAllLinks
105
- Qt::Object.connect(page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('pageLinkClicked(const QUrl&)'))
106
- end
107
- @object_uris = Hash.new
108
- end
109
-
110
-
225
+ # Generate a URI for an object
226
+ #
227
+ # The method must either return a string that is a URI representing
228
+ # the object, or nil if there is none. The choice of the URI is
229
+ # application-specific, used by the application to recognize links
230
+ #
231
+ # The default application returns a file:/// URI for a Pathname
232
+ # object, and then uses {#object_uris}
233
+ #
234
+ # @see link_to
111
235
  def uri_for(object)
112
236
  if object.kind_of?(Pathname)
113
237
  "file://#{object.expand_path}"
@@ -128,22 +252,38 @@ module MetaRuby::GUI
128
252
  end
129
253
  end
130
254
 
255
+ # Generate the HTML and update the underlying {#page}
131
256
  def update_html
132
257
  page.main_frame.html = html
133
258
  end
134
259
 
260
+ # Generate the HTML
261
+ #
262
+ # @param [String] ressource_dir the path to the ressource directory
263
+ # that {#path_in_resource} should use
135
264
  def html(ressource_dir: RESSOURCES_DIR)
136
265
  load_template(PAGE_TEMPLATE).result(binding)
137
266
  end
138
267
 
268
+ # Generate the body of the HTML document
269
+ #
270
+ # @param [String] ressource_dir the path to the ressource directory
271
+ # that {#path_in_resource} should use
139
272
  def html_body(ressource_dir: RESSOURCES_DIR)
140
273
  load_template(PAGE_BODY_TEMPLATE).result(binding)
141
274
  end
142
275
 
276
+ # Generate the HTML of a fragment
277
+ #
278
+ # @param [String] ressource_dir the path to the ressource directory
279
+ # that {#path_in_resource} should use
143
280
  def html_fragment(fragment, ressource_dir: RESSOURCES_DIR)
144
281
  load_template(FRAGMENT_TEMPLATE).result(binding)
145
282
  end
146
283
 
284
+ # Find a button from its URI
285
+ #
286
+ # @return [Button,nil]
147
287
  def find_button_by_url(url)
148
288
  id = url.path
149
289
  fragments.each do |fragment|
@@ -158,6 +298,11 @@ module MetaRuby::GUI
158
298
  page.main_frame.find_first_element(selector)
159
299
  end
160
300
 
301
+ # @api private
302
+ #
303
+ # Slot that catches the page's link-clicked signal and dispatches
304
+ # into the buttonClicked signal (for buttons), fileClicked for files
305
+ # and linkClicked for links
161
306
  def pageLinkClicked(url)
162
307
  if url.scheme == 'btn' && url.host == 'metaruby'
163
308
  if btn = find_button_by_url(url)
@@ -186,12 +331,12 @@ module MetaRuby::GUI
186
331
  signals 'linkClicked(const QUrl&)', 'buttonClicked(const QString&,bool)', 'fileOpenClicked(const QUrl&)'
187
332
 
188
333
  # Save the current state of the page, so that it can be restored by
189
- # calling {restore}
334
+ # calling {#restore}
190
335
  def save
191
336
  @saved_state = fragments.map(&:dup)
192
337
  end
193
338
 
194
- # Restore the page at the state it was at the last call to {save}
339
+ # Restore the page at the state it was at the last call to {#save}
195
340
  def restore
196
341
  return if !@saved_state
197
342
 
@@ -225,8 +370,8 @@ module MetaRuby::GUI
225
370
  # fragment replaces the existing one, and the view is updated
226
371
  # accordingly.
227
372
  #
228
- def push(title, html, view_options = Hash.new)
229
- if id = view_options[:id]
373
+ def push(title, html, id: auto_id, **view_options)
374
+ if id
230
375
  # Check whether we should replace the existing content or
231
376
  # push it new
232
377
  fragment = fragments.find do |fragment|
@@ -240,14 +385,36 @@ module MetaRuby::GUI
240
385
  end
241
386
  end
242
387
 
243
- fragments << Fragment.new(title, html, Hash[:id => auto_id].merge(view_options))
388
+ fragments << Fragment.new(title, html, id: id, **view_options)
244
389
  update_html
245
390
  end
246
391
 
392
+ # Automatic generation of a fragment ID
247
393
  def auto_id
248
394
  "metaruby-html-page-fragment-#{@auto_id += 1}"
249
395
  end
250
396
 
397
+ # Enable rendering of exceptions using the given renderer
398
+ #
399
+ # @param [ExceptionRendering] renderer
400
+ def enable_exception_rendering(renderer = ExceptionRendering.new(self))
401
+ add_to_setup(renderer)
402
+ @exception_rendering = renderer
403
+ end
404
+
405
+ # Push a fragment that represents the given exception
406
+ #
407
+ # {#enable_exception_rendering} must have been called first
408
+ #
409
+ # @param [String] title the fragment title
410
+ # @param [Exception] e the exception to render
411
+ # @param [String] id the fragment ID
412
+ # @param [Hash] options additional options passed to {#push}
413
+ def push_exception(title, e, id: auto_id, **options)
414
+ html = exception_rendering.render(e, nil, id)
415
+ push(title, html, id: id, **options)
416
+ end
417
+
251
418
  # Create an item for the rendering in tables
252
419
  def render_item(name, value = nil)
253
420
  if value
@@ -264,38 +431,51 @@ module MetaRuby::GUI
264
431
  # @param [Array<Object>,Array<(Object,Hash)>] items the list
265
432
  # items, one item per line. If a hash is provided, it is used as
266
433
  # HTML attributes for the lines
267
- # @param [Hash] options
268
- # @option options [Boolean] filter (false) if true, a filter is
269
- # added at the top of the page. You must provide a :id option for
270
- # the list for this to work
271
- # @option (see #push)
272
- def render_list(title, items, options = Hash.new)
273
- options, push_options = Kernel.filter_options options, :filter => false, :id => nil
274
- if options[:filter] && !options[:id]
434
+ # @param [Boolean] filter only render the items with the given 'id'
435
+ # @param [String] id the id to filter if filter: is true
436
+ # @param [Hash] push_options options that are passed to
437
+ # {#push}. The id: option is added to it.
438
+ def render_list(title, items, filter: false, id: nil, **push_options)
439
+ if filter && !id
275
440
  raise ArgumentError, ":filter is true, but no :id has been given"
276
441
  end
277
442
  html = load_template(LIST_TEMPLATE).result(binding)
278
- push(title, html, push_options.merge(:id => options[:id]))
443
+ push(title, html, push_options.merge(id: id))
279
444
  end
280
445
 
281
446
  signals 'updated()'
282
447
 
283
- def self.to_html_page(object, renderer, options = Hash.new)
448
+ # Renders an object into a HTML page
449
+ #
450
+ # @param [Object] object
451
+ # @param [#render] renderer the object that renders into the page.
452
+ # The object must accept a {Page} at initialization and its
453
+ # #render method gets called passing the object and rendering
454
+ # options
455
+ # @return [Page]
456
+ def self.to_html_page(object, renderer, **options)
284
457
  webpage = HTMLPage.new
285
458
  page = new(webpage)
286
- renderer.new(page).render(object, options)
459
+ renderer.new(page).render(object, **options)
287
460
  page
288
461
  end
289
462
 
290
- # Renders an object to HTML using a given rendering class
291
- def self.to_html(object, renderer, options = Hash.new)
292
- html_options, options = Kernel.filter_options options, :ressource_dir => RESSOURCES_DIR
293
- to_html_page(object, renderer, options).html(html_options)
463
+ # Renders an object into a HTML page
464
+ #
465
+ # @param (see to_html_page)
466
+ # @return [String]
467
+ def self.to_html(object, renderer, ressource_dir: RESSOURCES_DIR, **options)
468
+ to_html_page(object, renderer, **options).
469
+ html(ressource_dir: ressource_dir)
294
470
  end
295
471
 
296
- def self.to_html_body(object, renderer, options = Hash.new)
297
- html_options, options = Kernel.filter_options options, :ressource_dir => RESSOURCES_DIR
298
- to_html_page(object, renderer, options).html_body(html_options)
472
+ # Renders an object into a HTML body
473
+ #
474
+ # @param (see to_html_page)
475
+ # @return [String]
476
+ def self.to_html_body(object, renderer, ressource_dir: RESSOURCES_DIR, **options)
477
+ to_html_page(object, renderer, **options).
478
+ html_body(ressource_dir: ressource_dir)
299
479
  end
300
480
  end
301
481
  end