488 lines
15 KiB
Racket
488 lines
15 KiB
Racket
#lang scribble/manual
|
|
|
|
@(require racket/base
|
|
racket/class
|
|
scribble/core
|
|
(for-label racket/base
|
|
racket/string
|
|
racket/class
|
|
racket/file
|
|
net/url
|
|
"../private/wv-context.rkt"
|
|
"../private/wv-settings.rkt"
|
|
"../private/wv-element.rkt"
|
|
"../private/wv-input.rkt"
|
|
"../private/rgba.rkt"))
|
|
|
|
@title{wv-window}
|
|
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
|
|
|
@defmodule[wv-window]
|
|
|
|
Window abstraction built on top of @racketmodname[racket-webview].
|
|
|
|
This module exports the @racket[wv-window%] class and re-exports the APIs from
|
|
@racketmodname[wv-element], @racketmodname[wv-input], @racketmodname[rgba], and
|
|
@racketmodname[wv-settings].
|
|
|
|
@section{Overview}
|
|
|
|
A @racket[wv-window%] object represents one webview window.
|
|
|
|
A window belongs to a context, loads one HTML path within that context, manages
|
|
its own title and geometry, dispatches incoming events, and provides access to
|
|
element wrappers through @racket[element].
|
|
|
|
The class also provides synchronous wrappers around the asynchronous dialog
|
|
interface by using continuations internally.
|
|
|
|
@section{Class: wv-window%}
|
|
|
|
@defclass[wv-window% object% ()]{
|
|
|
|
Represents one window in a webview context.
|
|
|
|
The class wraps a lower-level window created through
|
|
@racketmodname[racket-webview] and connects it to the surrounding class-based
|
|
API.
|
|
|
|
@defconstructor[([parent (or/c #f (is-a?/c wv-window%)) #f]
|
|
[wv-context (if (eq? parent #f)
|
|
(error "wv context is mandatory")
|
|
(get-field wv-context parent))]
|
|
[html-path path-string?]
|
|
[settings (send wv-context settings
|
|
(string->symbol (format "~a" html-path)))]
|
|
[title string? "Racket Webview Window"]
|
|
[width (or/c exact-integer? #f) #f]
|
|
[height (or/c exact-integer? #f) #f]
|
|
[x (or/c exact-integer? #f) #f]
|
|
[y (or/c exact-integer? #f) #f])]{
|
|
|
|
Creates a window.
|
|
|
|
The constructor requires @racket[html-path]. If @racket[parent] is @racket[#f],
|
|
@racket[wv-context] is mandatory. If a parent window is supplied,
|
|
@racket[wv-context] defaults to the context of that parent.
|
|
|
|
The default value of @racket[settings] is obtained as:
|
|
|
|
@racketblock[
|
|
(send wv-context settings (string->symbol (format "~a" html-path)))
|
|
]
|
|
|
|
After construction, the class:
|
|
|
|
@itemlist[#:style 'compact
|
|
@item{creates the underlying webview window}
|
|
@item{installs the internal event handler}
|
|
@item{sets the title}
|
|
@item{initializes position and size from settings or defaults}]
|
|
|
|
If a parent is supplied, the underlying lower-level parent window is passed to
|
|
@racket[webview-create].
|
|
}
|
|
|
|
@defmethod[(context) (is-a?/c wv-context%)]{
|
|
|
|
Returns the window context object supplied at construction.
|
|
}
|
|
|
|
@defmethod[(win-context) symbol?]{
|
|
|
|
Returns the settings context key for this window.
|
|
|
|
The value is computed as:
|
|
|
|
@racketblock[
|
|
(string->symbol (format "~a" html-path))
|
|
]
|
|
}
|
|
|
|
@defmethod[(info) hash?]{
|
|
|
|
Returns administrative information for the window as a hash table.
|
|
|
|
The returned hash contains the keys @racket['wv-context], @racket['wv],
|
|
@racket['html-path], and @racket['elements].
|
|
}
|
|
|
|
@defmethod[(element [id symbol?] [type symbol?] ...) any/c]{
|
|
|
|
Returns the wrapper object associated with the DOM element identified by
|
|
@racket[id].
|
|
|
|
Element wrappers are cached in an internal weak hash table. If no wrapper exists
|
|
yet, one is created and cached.
|
|
|
|
If no explicit @racket[type] is supplied, the method asks the browser for the
|
|
element's @tt{type} attribute. Depending on the resulting type, one of the
|
|
following classes is instantiated:
|
|
|
|
@itemlist[#:style 'compact
|
|
@item{@racket[wv-input/text%] for @racket['text]}
|
|
@item{@racket[wv-input/date%] for @racket['date]}
|
|
@item{@racket[wv-input/time%] for @racket['time]}
|
|
@item{@racket[wv-input/datetime%] for @racket['datetime-local]}
|
|
@item{@racket[wv-input/range%] for @racket['range]}
|
|
@item{@racket[wv-input/number%] for @racket['number]}
|
|
@item{@racket[wv-input/check%] for @racket['checkbox]}
|
|
@item{@racket[wv-input/radio%] for @racket['radio]}
|
|
@item{@racket[wv-input/color%] for @racket['color]}
|
|
@item{@racket[wv-element%] otherwise}]
|
|
|
|
Newly created wrappers receive this window through the @racket[window] init
|
|
argument and the element id through @racket[element-id].
|
|
}
|
|
|
|
@defmethod[(moved [xx exact-integer?] [yy exact-integer?]) any/c]{
|
|
|
|
Updates the internal position and stores the new coordinates in
|
|
@racket[settings] under @racket['x] and @racket['y].
|
|
}
|
|
|
|
@defmethod[(resized [w exact-integer?] [h exact-integer?]) any/c]{
|
|
|
|
Updates the internal size and stores the new dimensions in
|
|
@racket[settings] under @racket['width] and @racket['height].
|
|
}
|
|
|
|
@defmethod[(window-state-changed [st symbol?]) any/c]{
|
|
|
|
Called when the underlying window state changes.
|
|
|
|
The default implementation returns @racket[#t].
|
|
}
|
|
|
|
@defmethod[(page-loaded [oke any/c]) any/c]{
|
|
|
|
Called when a @racket['page-loaded] event is received.
|
|
|
|
The default implementation returns @racket[#t].
|
|
}
|
|
|
|
@defmethod[(can-close?) any/c]{
|
|
|
|
Called when a @racket['can-close?] event is received.
|
|
|
|
If this method returns a true value, the window closes itself by calling
|
|
@racket[close].
|
|
|
|
The default implementation returns @racket[#t].
|
|
}
|
|
|
|
@defmethod[(closed) any/c]{
|
|
|
|
Called when a @racket['closed] event is received.
|
|
|
|
The default implementation returns @racket[#t].
|
|
}
|
|
|
|
@defmethod[(js-event [js-event hash?]) any/c]{
|
|
|
|
Dispatches a JavaScript event received from the browser.
|
|
|
|
The incoming hash is expected to contain at least the keys @racket['evt],
|
|
@racket['id], and optionally @racket['data]. If the referenced element exists in
|
|
the internal element cache, the event is dispatched to that element by calling
|
|
its @racket[dispatch-event] method.
|
|
|
|
If no matching element wrapper exists, the method returns
|
|
@racket['wv-unhandled-js-event].
|
|
}
|
|
|
|
@defmethod[(navigation-request [type symbol?] [url url?]) (or/c 'internal 'external)]{
|
|
|
|
Handles a navigation request event.
|
|
|
|
If the requested URL starts with the context base URL, the request is treated as
|
|
internal, loaded into the current window, and @racket['internal] is returned.
|
|
|
|
Otherwise the URL is opened externally using @racket[send-url], and
|
|
@racket['external] is returned.
|
|
}
|
|
|
|
@defmethod[(add-class! [selector-or-id (or/c symbol? string?)]
|
|
[cl (or/c symbol? string? list?)])
|
|
(is-a?/c wv-window%)]{
|
|
|
|
Adds one or more CSS classes to the selected elements and returns this window.
|
|
}
|
|
|
|
@defmethod[(remove-class! [selector-or-id (or/c symbol? string?)]
|
|
[cl (or/c symbol? string? list?)])
|
|
(is-a?/c wv-window%)]{
|
|
|
|
Removes one or more CSS classes from the selected elements and returns this
|
|
window.
|
|
}
|
|
|
|
@defmethod[(devtools) (is-a?/c wv-window%)]{
|
|
|
|
Opens the developer tools and returns this window.
|
|
}
|
|
|
|
@defmethod[(move [x exact-integer?] [y exact-integer?]) (is-a?/c wv-window%)]{
|
|
|
|
Moves the window and returns this window.
|
|
}
|
|
|
|
@defmethod[(resize [w exact-integer?] [h exact-integer?]) (is-a?/c wv-window%)]{
|
|
|
|
Resizes the window and returns this window.
|
|
}
|
|
|
|
@defmethod[(close) (is-a?/c wv-window%)]{
|
|
|
|
Closes the window and returns this window.
|
|
}
|
|
|
|
@defmethod[(bind! [selector (or/c symbol? string?)]
|
|
[events (or/c symbol? list?)]
|
|
[callback procedure?])
|
|
list?]{
|
|
|
|
Binds one or more DOM events.
|
|
|
|
The method first delegates to @racket[webview-bind!]. For each returned binding,
|
|
it connects the corresponding element wrapper to @racket[callback].
|
|
|
|
If @racket[events] is a symbol, it is treated as a single-element list.
|
|
|
|
The result is the list produced by mapping over the binding items. In practice,
|
|
this yields the corresponding cached element wrapper objects.
|
|
}
|
|
|
|
@defmethod[(unbind! [selector (or/c symbol? string?)]
|
|
[events (or/c symbol? list?)])
|
|
list?]{
|
|
|
|
Removes one or more DOM event bindings.
|
|
|
|
The method first delegates to @racket[webview-unbind!], then disconnects the
|
|
corresponding callbacks from cached element wrappers.
|
|
|
|
If an element wrapper no longer has any event callbacks, it is removed from the
|
|
internal cache.
|
|
|
|
The returned value is the list produced by @racket[webview-unbind!].
|
|
}
|
|
|
|
@defmethod[(set-menu! [menu is-wv-menu?])
|
|
(is-a?/c wv-window%)]{
|
|
|
|
Installs @racket[menu] in this window and returns this window.
|
|
|
|
The accepted argument follows the contract of @racket[webview-set-menu!]. The
|
|
method delegates to:
|
|
|
|
@racketblock[
|
|
(webview-set-menu! wv menu)
|
|
]
|
|
}
|
|
|
|
@defmethod[(connect-menu! [id symbol?] [callback procedure?])
|
|
(is-a?/c wv-window%)]{
|
|
|
|
Connects @racket[callback] to the menu item identified by @racket[id].
|
|
|
|
The method installs a binding for the @racket['menu-item-choosen] event by
|
|
delegating to:
|
|
|
|
@racketblock[
|
|
(send this bind! id 'menu-item-choosen
|
|
(λ (el evt data)
|
|
(callback)))
|
|
]
|
|
|
|
The callback is invoked without arguments when the corresponding menu item is
|
|
chosen.
|
|
}
|
|
|
|
@defmethod[(set-title! [title string?]) any/c]{
|
|
|
|
Sets the window title.
|
|
}
|
|
|
|
@defmethod[(file-dialog-done [flag symbol?]
|
|
[file any/c]
|
|
[dir any/c]
|
|
[filter any/c])
|
|
any/c]{
|
|
|
|
Internal continuation callback used by file and directory dialogs.
|
|
|
|
The default implementation resumes the pending continuation stored by the window.
|
|
}
|
|
|
|
@defmethod[(choose-dir [title string?] [base-dir (or/c path-string? #f)])
|
|
(or/c 'showing path-string? #f)]{
|
|
|
|
Shows a directory chooser dialog.
|
|
|
|
At most one file or directory dialog can be active at a time. Attempting to open
|
|
another one raises an exception.
|
|
|
|
If @racket[base-dir] is @racket[#f], the user's home directory is used.
|
|
|
|
If the underlying dialog could not be started, the method returns @racket[#f].
|
|
If the dialog is shown successfully, the method returns @racket['showing] first
|
|
and later resumes through the internal continuation mechanism.
|
|
|
|
When resumed after completion:
|
|
|
|
@itemlist[#:style 'compact
|
|
@item{the selected directory is returned on success}
|
|
@item{@racket[#f] is returned if the dialog was canceled}]
|
|
}
|
|
|
|
@defmethod[(file-open [title string?]
|
|
[base-dir (or/c path-string? #f)]
|
|
[filters any/c])
|
|
(or/c 'showing list? #f)]{
|
|
|
|
Shows an open-file dialog.
|
|
|
|
At most one file or directory dialog can be active at a time. Attempting to open
|
|
another one raises an exception.
|
|
|
|
If @racket[base-dir] is @racket[#f], the user's home directory is used.
|
|
|
|
If the underlying dialog could not be started, the method returns @racket[#f].
|
|
If the dialog is shown successfully, the method returns @racket['showing] first
|
|
and later resumes through the internal continuation mechanism.
|
|
|
|
When resumed after completion:
|
|
|
|
@itemlist[#:style 'compact
|
|
@item{on success, the method returns @racket[(cdr r)] from the internal dialog result}
|
|
@item{on cancellation, the method returns @racket[#f]}]
|
|
|
|
This matches the current source code exactly.
|
|
}
|
|
|
|
@defmethod[(file-save [title string?]
|
|
[base-dir (or/c path-string? #f)]
|
|
[filters any/c])
|
|
(or/c 'showing list? #f)]{
|
|
|
|
Shows a save-file dialog.
|
|
|
|
At most one file or directory dialog can be active at a time. Attempting to open
|
|
another one raises an exception.
|
|
|
|
If @racket[base-dir] is @racket[#f], the user's home directory is used.
|
|
|
|
If the underlying dialog could not be started, the method returns @racket[#f].
|
|
If the dialog is shown successfully, the method returns @racket['showing] first
|
|
and later resumes through the internal continuation mechanism.
|
|
|
|
When resumed after completion:
|
|
|
|
@itemlist[#:style 'compact
|
|
@item{on success, the method returns @racket[(cdr r)] from the internal dialog result}
|
|
@item{on cancellation, the method returns @racket[#f]}]
|
|
|
|
This matches the current source code exactly.
|
|
}
|
|
|
|
@defmethod[(message-done [evt symbol?]) any/c]{
|
|
|
|
Internal continuation callback used by message boxes.
|
|
|
|
The default implementation resumes the pending message-box continuation stored by
|
|
the window.
|
|
}
|
|
|
|
@defmethod[(message [type symbol?]
|
|
[title string?]
|
|
[message string?]
|
|
[#:sub submessage string? ""])
|
|
(or/c 'showing symbol? #f)]{
|
|
|
|
Shows a message box.
|
|
|
|
At most one message box can be active at a time. Attempting to show another one
|
|
raises an exception.
|
|
|
|
If the underlying dialog could not be started, the method returns @racket[#f].
|
|
If the dialog is shown successfully, the method returns @racket['showing] first
|
|
and later resumes through the internal continuation mechanism.
|
|
|
|
When resumed after completion, the result is the event symbol returned by the
|
|
lower layer, such as the symbols corresponding to @tt{ok}, @tt{cancel},
|
|
@tt{yes}, or @tt{no}.
|
|
}
|
|
|
|
@defmethod[(init-size) any/c]{
|
|
|
|
Initializes the window position and size.
|
|
|
|
The method reads @racket['x], @racket['y], @racket['width], and
|
|
@racket['height] from @racket[settings]. If a value is missing, the constructor
|
|
argument is used if present; otherwise a default is chosen.
|
|
|
|
The resulting geometry is then applied through @racket[move] and @racket[resize].
|
|
}
|
|
}
|
|
|
|
@section{Events}
|
|
|
|
The window installs an internal event handler when it is created.
|
|
|
|
This handler reacts to the following event kinds produced by the lower layer:
|
|
|
|
@itemlist[#:style 'compact
|
|
@item{@racket['resize], forwarded to @racket[resized]}
|
|
@item{@racket['move], forwarded to @racket[moved]}
|
|
@item{@racket['page-loaded], forwarded to @racket[page-loaded]}
|
|
@item{@racket['show], @racket['hide], and @racket['maximize], forwarded to
|
|
@racket[window-state-changed]}
|
|
@item{@racket['can-close?], causing @racket[can-close?] to be queried}
|
|
@item{@racket['closed], forwarded to @racket[closed]}
|
|
@item{@racket['js-evt], decoded and forwarded to @racket[js-event]}
|
|
@item{@racket['navigation-request], forwarded to @racket[navigation-request]}
|
|
@item{file-dialog completion events, forwarded to @racket[file-dialog-done]}
|
|
@item{message-box completion events, forwarded to @racket[message-done]}]
|
|
|
|
Unhandled events are printed to the console by the current implementation.
|
|
|
|
@section{Function: root-file-not-found-handler}
|
|
|
|
@defproc[(root-file-not-found-handler [standard-file string?]
|
|
[not-found-handler procedure?] ...)
|
|
procedure?]{
|
|
|
|
Creates a file-not-found handler for use with file serving.
|
|
|
|
The returned procedure has the shape:
|
|
|
|
@racketblock[
|
|
(lambda (file base-path path) ...)
|
|
]
|
|
|
|
Its behavior is as follows:
|
|
|
|
@itemlist[#:style 'compact
|
|
@item{if @racket[file] is @tt{"/"}, it retries using @racket[standard-file]}
|
|
@item{if @racket[file] is @tt{"/index.html"}, it falls back to
|
|
@racket[standard-file] when the target does not exist}
|
|
@item{if the requested path exists, it is returned}
|
|
@item{otherwise, if an extra @racket[not-found-handler] was supplied, that
|
|
handler is called}
|
|
@item{if no extra handler was supplied, the original path is returned}]
|
|
|
|
Only the first optional @racket[not-found-handler] is used.
|
|
}
|
|
|
|
@section{Re-exports}
|
|
|
|
This module also re-exports the public APIs from:
|
|
|
|
@itemlist[#:style 'compact
|
|
@item{@racketmodname[wv-element]}
|
|
@item{@racketmodname[wv-input]}
|
|
@item{@racketmodname[rgba]}
|
|
@item{@racketmodname[wv-settings]}]
|
|
|
|
It also re-exports @racket[webview-version]. |