#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].