class MetaRuby::GUI::HTML::Page

A helper class that gives us easy-to-use page elements on a Qt::WebView

Such a page is managed as a list of sections (called {Fragment}). A new fragment is added or updated with {#push}

Constants

ASSETS

Assets (CSS, javascript) that are included in every page

FRAGMENT_TEMPLATE

The ERB template for a page fragment

@see push

LIST_TEMPLATE

The ERB template for a list

@see render_list

PAGE_BODY_TEMPLATE

The ERB template for a page body

@see html_body

PAGE_TEMPLATE

The ERB template for a page

@see html

Attributes

exception_rendering[R]

Object used to render exceptions in {#push_exception}

It is set by {#enable_exception_rendering}

@return [#render]

fragments[R]

List of fragments

@return [Array<Fragment>]

head[R]

Content to be rendered in the page head

@return [Array<String>]

object_uris[RW]

Static mapping of objects to URIs

@see uri_fo

page[R]

The underlying page rendering object

@return [Qt::WebPage,HTMLPage]

page_name[RW]

The content of the <title> tag @return [String,nil]

scripts[R]

Scripts to be loaded in the page

@return [Array<String>]

title[RW]

The content of a toplevel <h1> tag @return [String,nil]

Public Class Methods

copy_assets_to(target_dir, assets = ASSETS) click to toggle source

Copy the assets to a target directory

This can be used to create self-contained HTML pages using the Page class, by providing a different ressource dir to e.g. {#html} or {#html_body} and copying the assets to it.

# File lib/metaruby/gui/html/page.rb, line 208
def self.copy_assets_to(target_dir, assets = ASSETS)
    FileUtils.mkdir_p target_dir
    assets.each do |file|
        FileUtils.cp File.join(RESSOURCES_DIR, file), target_dir
    end
end
main_doc(text) click to toggle source

Converts the given text from markdown to HTML and generates the necessary <div> context.

@return [String] the HTML snippet that should be used to render

the given text as main documentation
# File lib/metaruby/gui/html/page.rb, line 176
def self.main_doc(text)
    "<div class=\"doc-main\">#{Kramdown::Document.new(text).to_html}</div>"
end
new(page) click to toggle source

Creates a new Page object

@param [Qt::WebPage,HTMLPage] page

Calls superclass method
# File lib/metaruby/gui/html/page.rb, line 65
def initialize(page)
    super()
    @page = page
    @head = Array.new
    @scripts = Array.new
    @fragments = []
    @templates = Hash.new
    @auto_id = 0

    if defined?(Qt::WebPage) && page.kind_of?(Qt::WebPage)
        page.link_delegation_policy = Qt::WebPage::DelegateAllLinks
        Qt::Object.connect(page, SIGNAL('linkClicked(const QUrl&)'), self, SLOT('pageLinkClicked(const QUrl&)'))
    end
    @object_uris = Hash.new
end
to_html(object, renderer, ressource_dir: RESSOURCES_DIR, **options) click to toggle source

Renders an object into a HTML page

@param (see to_html_page) @return [String]

# File lib/metaruby/gui/html/page.rb, line 467
def self.to_html(object, renderer, ressource_dir: RESSOURCES_DIR, **options)
    to_html_page(object, renderer, **options).
        html(ressource_dir: ressource_dir)
end
to_html_body(object, renderer, ressource_dir: RESSOURCES_DIR, **options) click to toggle source

Renders an object into a HTML body

@param (see to_html_page) @return [String]

# File lib/metaruby/gui/html/page.rb, line 476
def self.to_html_body(object, renderer, ressource_dir: RESSOURCES_DIR, **options)
    to_html_page(object, renderer, **options).
        html_body(ressource_dir: ressource_dir)
end
to_html_page(object, renderer, **options) click to toggle source

Renders an object into a HTML page

@param [Object] object @param [#render] renderer the object that renders into the page.

The object must accept a {Page} at initialization and its
#render method gets called passing the object and rendering
options

@return [Page]

# File lib/metaruby/gui/html/page.rb, line 456
def self.to_html_page(object, renderer, **options)
    webpage = HTMLPage.new
    page = new(webpage)
    renderer.new(page).render(object, **options)
    page
end

Public Instance Methods

add_script(html) click to toggle source

Add content to {#scripts}

@param [String] html

# File lib/metaruby/gui/html/page.rb, line 124
def add_script(html)
    scripts << html
end
add_to_head(html) click to toggle source

Add content to {#head}

@param [String] html

# File lib/metaruby/gui/html/page.rb, line 117
def add_to_head(html)
    head << html
end
add_to_setup(obj) click to toggle source

Add content to the page setup (head and scripts)

@param [#head,#scripts] obj the object defining the content to be

added

@see add_to_head add_scripts

# File lib/metaruby/gui/html/page.rb, line 109
def add_to_setup(obj)
    add_to_head(obj.head)
    add_script(obj.scripts)
end
auto_id() click to toggle source

Automatic generation of a fragment ID

# File lib/metaruby/gui/html/page.rb, line 393
def auto_id
    "metaruby-html-page-fragment-#{@auto_id += 1}"
end
clear() click to toggle source

Removes all existing displays

# File lib/metaruby/gui/html/page.rb, line 244
def clear
    page.main_frame.html = ""
    fragments.clear
end
enable_exception_rendering(renderer = ExceptionRendering.new(self)) click to toggle source

Enable rendering of exceptions using the given renderer

@param [ExceptionRendering] renderer

# File lib/metaruby/gui/html/page.rb, line 400
def enable_exception_rendering(renderer = ExceptionRendering.new(self))
    add_to_setup(renderer)
    @exception_rendering = renderer
end
find_button_by_url(url) click to toggle source

Find a button from its URI

@return [Button,nil]

# File lib/metaruby/gui/html/page.rb, line 287
def find_button_by_url(url)
    id = url.path
    fragments.each do |fragment|
        if result = fragment.buttons.find { |b| b.id == id }
            return result
        end
    end
    nil
end
find_first_element(selector) click to toggle source
# File lib/metaruby/gui/html/page.rb, line 297
def find_first_element(selector)
    page.main_frame.find_first_element(selector)
end
html(ressource_dir: RESSOURCES_DIR) click to toggle source

Generate the HTML

@param [String] ressource_dir the path to the ressource directory

that {#path_in_resource} should use
# File lib/metaruby/gui/html/page.rb, line 264
def html(ressource_dir: RESSOURCES_DIR)
    load_template(PAGE_TEMPLATE).result(binding)
end
html_body(ressource_dir: RESSOURCES_DIR) click to toggle source

Generate the body of the HTML document

@param [String] ressource_dir the path to the ressource directory

that {#path_in_resource} should use
# File lib/metaruby/gui/html/page.rb, line 272
def html_body(ressource_dir: RESSOURCES_DIR)
    load_template(PAGE_BODY_TEMPLATE).result(binding)
end
html_fragment(fragment, ressource_dir: RESSOURCES_DIR) click to toggle source

Generate the HTML of a fragment

@param [String] ressource_dir the path to the ressource directory

that {#path_in_resource} should use
# File lib/metaruby/gui/html/page.rb, line 280
def html_fragment(fragment, ressource_dir: RESSOURCES_DIR)
    load_template(FRAGMENT_TEMPLATE).result(binding)
end
load_javascript(file) click to toggle source

Load a javascript file in the head

# File lib/metaruby/gui/html/page.rb, line 141
def load_javascript(file)
    add_to_head(
        "<script type=\"text/javascript\" src=\"#{path_in_resource(file)}\"></script>")
end
load_template(*path) click to toggle source

Lazy loads a template

@return [ERB]

# File lib/metaruby/gui/html/page.rb, line 218
def load_template(*path)
    path = File.join(*path)
    @templates[path] ||= ERB.new(File.read(path))
    @templates[path].filename = path
    @templates[path]
end
main_doc(text) click to toggle source
# File lib/metaruby/gui/html/page.rb, line 180
def main_doc(text)
    self.class.main_doc(text)
end
pageLinkClicked(url) click to toggle source

@api private

Slot that catches the page's link-clicked signal and dispatches into the buttonClicked signal (for buttons), fileClicked for files and linkClicked for links

# File lib/metaruby/gui/html/page.rb, line 306
def pageLinkClicked(url)
    if url.scheme == 'btn' && url.host == 'metaruby'
        if btn = find_button_by_url(url)
            new_state = if url.fragment == 'on' then true
                        else false
                        end

            btn.state = new_state
            new_text = btn.text
            element = find_first_element("a##{btn.html_id}")
            element.replace(btn.render)

            emit buttonClicked(btn.id, new_state)
        else
            MetaRuby.warn "invalid button URI #{url.to_string}: could not find corresponding handler (known buttons are #{fragments.flat_map { |f| f.buttons.map { |btn| btn.id.to_string } }.sort.join(", ")})"
        end
    elsif url.scheme == 'link' && url.host == 'metaruby'
        emit linkClicked(url)
    elsif url.scheme == "file"
        emit fileOpenClicked(url)
    else
        MetaRuby.warn "MetaRuby::GUI::HTML::Page: ignored link #{url.toString}"
    end
end
path_in_resource(path) click to toggle source

Resolves a relative path to a path in the underlying application's resource folder

@return [String]

# File lib/metaruby/gui/html/page.rb, line 132
def path_in_resource(path)
    if Pathname.new(path).absolute?
        path
    else
        File.join('${RESOURCE_DIR}', path)
    end
end
push(title, html, id: auto_id, **view_options) click to toggle source

Adds a fragment to this page, with the given title and HTML content

The added fragment is enclosed in a div block to allow for dynamic replacement

@option view_options [String] id the ID of the fragment. If given,

and if an existing fragment with the same ID exists, the new
fragment replaces the existing one, and the view is updated
accordingly.
# File lib/metaruby/gui/html/page.rb, line 373
def push(title, html, id: auto_id, **view_options)
    if id
        # Check whether we should replace the existing content or
        # push it new
        fragment = fragments.find do |fragment|
            fragment.id == id
        end
        if fragment
            fragment.html = html
            element = find_first_element("div##{fragment.id}")
            element.replace(html_fragment(fragment))
            return
        end
    end

    fragments << Fragment.new(title, html, id: id, **view_options)
    update_html
end
push_exception(title, e, id: auto_id, **options) click to toggle source

Push a fragment that represents the given exception

{#enable_exception_rendering} must have been called first

@param [String] title the fragment title @param [Exception] e the exception to render @param [String] id the fragment ID @param [Hash] options additional options passed to {#push}

# File lib/metaruby/gui/html/page.rb, line 413
def push_exception(title, e, id: auto_id, **options)
    html = exception_rendering.render(e, nil, id)
    push(title, html, id: id, **options)
end
render_item(name, value = nil) click to toggle source

Create an item for the rendering in tables

# File lib/metaruby/gui/html/page.rb, line 419
def render_item(name, value = nil)
    if value
        "<li><b>#{name}</b>: #{value}</li>"
    else
        "<li>#{name}</li>"
    end
end
render_list(title, items, filter: false, id: nil, **push_options) click to toggle source

Render a list of objects into HTML and push it to this page

@param [String,nil] title the section's title. If nil, no new

section is created

@param [Array<Object>,Array<(Object,Hash)>] items the list

items, one item per line. If a hash is provided, it is used as
HTML attributes for the lines

@param [Boolean] filter only render the items with the given 'id' @param [String] id the id to filter if filter: is true @param [Hash] push_options options that are passed to

{#push}. The id: option is added to it.
# File lib/metaruby/gui/html/page.rb, line 438
def render_list(title, items, filter: false, id: nil, **push_options)
    if filter && !id
        raise ArgumentError, ":filter is true, but no :id has been given"
    end
    html = load_template(LIST_TEMPLATE).result(binding)
    push(title, html, push_options.merge(id: id))
end
restore() click to toggle source

Restore the page at the state it was at the last call to {#save}

# File lib/metaruby/gui/html/page.rb, line 340
def restore
    return if !@saved_state

    fragments_by_id = Hash.new
    @saved_state.each do |fragment|
        fragments_by_id[fragment.id] = fragment
    end

    # Delete all fragments that are not in the saved state
    fragments.delete_if do |fragment|
        element = find_first_element("div##{fragment.id}")
        if old_fragment = fragments_by_id[fragment.id]
            if old_fragment.html != fragment.html
                element.replace(old_fragment.html)
            end
        else
            element.replace("")
            true
        end
    end
end
save() click to toggle source

Save the current state of the page, so that it can be restored by calling {#restore}

# File lib/metaruby/gui/html/page.rb, line 335
def save
    @saved_state = fragments.map(&:dup)
end
scale_attribute(node, name, scale) click to toggle source
# File lib/metaruby/gui/html/page.rb, line 249
def scale_attribute(node, name, scale)
    node.attributes[name] = node.attributes[name].gsub /[\d\.]+/ do |n|
        (Float(n) * scale).to_s
    end
end
update_html() click to toggle source

Generate the HTML and update the underlying {#page}

# File lib/metaruby/gui/html/page.rb, line 256
def update_html
    page.main_frame.html = html
end
uri_for(object) click to toggle source

Generate a URI for an object

The method must either return a string that is a URI representing the object, or nil if there is none. The choice of the URI is application-specific, used by the application to recognize links

The default application returns a file:/// URI for a Pathname object, and then uses {#object_uris}

@see link_to

# File lib/metaruby/gui/html/page.rb, line 235
def uri_for(object)
    if object.kind_of?(Pathname)
        "file://#{object.expand_path}"
    else
        object_uris[object]
    end
end