//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 21 Jul 08 Brian Frank Creation
//
using gfx
using fwt
**
** Resource represents the objects a user navigates, views, and
** edits in a flux application. Resources are mapped to objects
** via the indexed prop 'flux.resource.{target}={resource}', where
** both "target" and "resource" are qualied type names. Subclasses
** must define a 'make(Uri, Obj)' constructor.
**
** See [pod doc]`pod-doc#resources` for details.
**
abstract class Resource
{
**
** Get the root resources.
**
static Resource[] roots()
{
acc := Resource[,]
File.osRoots.each |f| { if (f.exists) acc.add(resolve(f.uri)) }
return acc
}
**
** Resolve a uri into a resource:
** 1. Resolve uri to obj via `sys::Uri.get`
** 2. If obj is Resource, return it
** 3. Resolve to obj type to resource type via 'flux.resource.{qname}'
** indexed property for type hierarchy
**
** Throw UnresolvedErr if the uri can't be resolved, and UnsupportedErr
** if resource can't be mapped to a resource.
**
static Resource resolve(Uri uri)
{
// 1. resolve uri
if (uri.toStr.startsWith("/")) uri = "file:$uri".toUri
obj := uri.get
// 2. if already resource return it
if (obj is Resource) return obj
// 3. map via fluxResource indexed props
rtypes := Flux.indexForInheritance("flux.resource.", obj.typeof)
if (rtypes.isEmpty) throw UnsupportedErr("No resource mapping for $obj.typeof")
if (rtypes.size > 1)
{
// if we have multiple matches, take the non-flux version
nonFlux := rtypes.exclude |t| { t.pod === Resource#.pod }
if (!nonFlux.isEmpty) rtypes = nonFlux
}
return rtypes.first.make([uri, obj])
}
**
** Get the absolute Uri of this resource.
**
abstract Uri uri()
**
** Get the display name of the resource.
**
abstract Str name()
**
** Get a 16x16 icon for the resource.
**
virtual Image icon() { return Flux.icon(`/x16/file.png`) }
**
** Return if this resource has or might have children. This
** is an optimization to display the expansion control in a tree
** without loading all the children. The default calls 'children'.
**
virtual Bool hasChildren()
{
c := children
return c != null ? !c.isEmpty : false
}
**
** Get the navigation children of the resource. Return an
** empty list or null to indicate no children. Default
** returns null.
**
virtual Resource[]? children() { return null}
**
** Get the list of available `View` types for the resource.
** The first view should be the default view. The default
** implementation searches the type database index props formatted
** as "flux.view.{target}={view}", where "target" is this type (and
** its inherited classes, and "view" is view type qname.
**
virtual Type[] views()
{
Flux.indexForInheritance("flux.view.", typeof)
}
**
** Make a popup menu for this resource or return null.
** The default popup menu returns the `viewsMenu`.
**
virtual Menu? popup(Frame? frame, Event? event)
{
return Menu { add(viewsMenu(frame, event)) }
}
**
** Return a menu to hyperlink to the views supported
** by this resource.
**
virtual Menu? viewsMenu(Frame? frame, Event? event)
{
menu := Menu { text = Flux.locale("views.name") }
views.each |Type v, Int i|
{
viewUri := i == 0 ? uri : uri.plusQuery(["view":v.qname])
c := Command(v.name, null) { frame.load(viewUri, LoadMode(event)) }
menu.add(MenuItem { command = c })
}
return menu
}
**
** Return `uri`.
**
override Str toStr() { return uri.toStr }
}
**************************************************************************
** ErrResource
**************************************************************************
**
** ErrResource models a resource that cannot be resolved.
**
class ErrResource : Resource
{
new make(Uri uri) { this.uri = uri }
override Uri uri
override Str name() { n := uri.name; return n.isEmpty ? uri.toStr : n }
override Image icon() { return Flux.icon(`/x16/err.png`) }
override Type[] views() { return Type[,] }
}