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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +4 -0
- data/README.md +4 -4
- data/lib/metaruby.rb +8 -1
- data/lib/metaruby/class.rb +13 -9
- data/lib/metaruby/dsls.rb +6 -0
- data/lib/metaruby/gui.rb +16 -0
- data/lib/metaruby/gui/exception_rendering.rb +281 -0
- data/lib/metaruby/gui/exception_view.rb +32 -111
- data/lib/metaruby/gui/html.rb +7 -0
- data/lib/metaruby/gui/html/button.rb +54 -12
- data/lib/metaruby/gui/html/collection.rb +33 -9
- data/lib/metaruby/gui/html/exception_view.css +4 -6
- data/lib/metaruby/gui/html/list.rhtml +3 -3
- data/lib/metaruby/gui/html/page.rb +233 -53
- data/lib/metaruby/gui/html/page.rhtml +6 -5
- data/lib/metaruby/gui/model_browser.rb +15 -6
- data/lib/metaruby/gui/model_selector.rb +57 -11
- data/lib/metaruby/gui/rendering_manager.rb +36 -7
- data/lib/metaruby/gui/ruby_constants_item_model.rb +155 -42
- data/lib/metaruby/inherited_attribute.rb +59 -8
- data/lib/metaruby/module.rb +29 -12
- data/lib/metaruby/registration.rb +44 -12
- data/lib/metaruby/test.rb +6 -10
- data/lib/metaruby/version.rb +2 -1
- data/lib/yard-metaruby.rb +10 -3
- data/manifest.xml +1 -0
- data/metaruby.gemspec +1 -1
- metadata +5 -4
@@ -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
|
-
|
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
|
-
|
37
|
-
<script type="text/javascript" src="file://#{File.join(RESSOURCES_DIR, 'jquery.min.js')}"></script>
|
59
|
+
<%= exception_rendering.head %>
|
38
60
|
</head>
|
39
|
-
|
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
|
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('<', '<').gsub('>', '>')
|
152
|
-
end
|
153
|
-
|
154
75
|
def exceptions=(list)
|
155
76
|
@displayed_exceptions = list.dup
|
156
77
|
update_html
|
data/lib/metaruby/gui/html.rb
CHANGED
@@ -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('<', '<').
|
@@ -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
|
-
|
11
|
-
|
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
|
23
|
-
@on_text =
|
24
|
-
@off_text =
|
42
|
+
if text
|
43
|
+
@on_text = text
|
44
|
+
@off_text = text
|
25
45
|
@state = true
|
26
46
|
else
|
27
|
-
@on_text =
|
28
|
-
@off_text =
|
29
|
-
@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
|
4
|
-
#
|
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
|
-
|
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
|
-
|
30
|
-
|
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('
|
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('
|
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
|
-
|
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 '
|
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
|
-
|
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
|
6
|
-
.backtrace_summary
|
7
|
-
.
|
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
|
1
|
+
<% if filter %>
|
2
2
|
<script type="text/javascript">
|
3
3
|
jQuery(document).ready(function(){
|
4
|
-
jQuery("div#<%=
|
4
|
+
jQuery("div#<%= id %>-list").selectFilter();
|
5
5
|
});
|
6
6
|
</script>
|
7
7
|
<% end %>
|
8
8
|
|
9
|
-
<div name="index_filter" id="<%=
|
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
|
-
|
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
|
-
|
27
|
-
|
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 =
|
32
|
-
@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
|
-
|
142
|
+
add_to_head(
|
143
|
+
"<script type=\"text/javascript\" src=\"#{path_in_resource(file)}\"></script>")
|
38
144
|
end
|
39
145
|
|
40
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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,
|
229
|
-
if 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,
|
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 [
|
268
|
-
# @
|
269
|
-
#
|
270
|
-
#
|
271
|
-
|
272
|
-
|
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(:
|
443
|
+
push(title, html, push_options.merge(id: id))
|
279
444
|
end
|
280
445
|
|
281
446
|
signals 'updated()'
|
282
447
|
|
283
|
-
|
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
|
291
|
-
|
292
|
-
|
293
|
-
|
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
|
-
|
297
|
-
|
298
|
-
|
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
|