metaruby 1.0.0.rc2 → 1.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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