sourceafReflux::Panel.fan

using afIoc
using gfx
using fwt

** Panels are widget panes that decorate the edges of the main window.
** Only one instance of each panel type may exist.
** They are (typically) created at application startup and live until the application shuts down.
**
** 'Panel' implementations should be *autobuilt* and contributed to the 'Panels' service:
**
**   syntax: fantom
** 
**   @Contribute { serviceType=Panels# }
**   static Void contributePanels(Configuration config) {
**       config["myPanel"] = config.autobuild(MyPanel#)
**   }
**
** 'Panels' are automatically added to the 'EventHub', so to receive events they only need to implement the required event mixin.
@Js
abstract class Panel {

    @Inject private Log             _log
    @Inject private Registry        _registry
    @Inject private Errors          _errors
    @Inject private RefluxIcons     _icons
    @Inject private RefluxEvents    _events
            internal |->Widget|?    _parentFunc

    ** The content displayed in the panel. May be set / re-set at any time.
    Widget? content {
        set {
            _parent := (Widget?) _parentFunc?.call()    // nullable, as content may be set in the ctor
            if (_parent is ContentPane) {
                ((ContentPane) _parent).content = it
            } else {
                _parent?.remove(&content)
                _parent?.add(it)
            }
            &content = it
            _parent?.relayout
        }
    }

    ** Return this panel's preferred alignment which is used to
    ** determine its default position.  Valid values are:
    **   - 'Halign.left' (default)
    **   - 'Halign.right'
    **   - 'Valign.bottom'
    Obj prefAlign := Halign.left

    ** As displayed in the panel's tab.
    ** If no name is given, it defaults the the Panel's type, minus any 'Panel' suffix.
    Str name := "" {
        set { &name = it; if (content?.parent?.typeof?.qname == "fwt::Tab" || content?.parent?.typeof?.qname == "afReflux::CTab") content.parent->text = it; if (isShowing) this->onModify }
    }

    ** As displayed in the panel's tab.
    Image? icon {
        set { &icon = it; if (content?.parent?.typeof?.qname == "fwt::Tab" || content?.parent?.typeof?.qname == "afReflux::CTab") content.parent->image = it; if (isShowing) this->onModify }
    }

    ** Subclasses should define the following ctor:
    **
    **   new make(|This| in) : super(in) { ... }
    new make(|This| in) {
        in(this)

        baseName := typeof.name
        if (baseName.endsWith("Panel"))
            baseName = baseName[0..<-"Panel".size]
        if (baseName.endsWith("View"))
            baseName = baseName[0..<-"View".size]

        this.name = baseName.toDisplayName
        this.icon = _icons.get("ico${typeof.name}", false)
    }

    @PostInjection
    private Void _setup(EventHub eventHub) {
        // also see RefluxCommand
        eventHub.register(this, false)
    }

    ** Is the panel currently the active tab in the tab pane?
    Bool isActive := false { internal set }

    ** Is the panel currently showing in the tab pane?
    Bool isShowing := false { internal set }

    ** Callback when this panel is shown in the tab pane.
    virtual Void onShow() {}

    ** Callback when this panel is removed from the tab pane.
    virtual Void onHide() {}

    ** Callback when this panel is selected as the active tab.
    virtual Void onActivate() {}

    ** Callback when this panel is unselected as the active tab.
    virtual Void onDeactivate() {}

    ** Callback when panel details are modified, such as the name or icon.
    virtual Void onModify() {}

    ** Callback for when the panel should refresh it's contents.
    ** Typically, active panels are asked to refresh when the 'refresh' button is clicked.
    **
    ** The given resource is a hint as to what's been updated.
    ** If 'null' then all content should be refreshed
    virtual Void refresh(Resource? resource := null) {}

    ** It is common to handle events from FWT widgets, such as 'onSelect()', but should these throw
    ** an Err they are usually just swallowed (or at best traced to std err). To work around it,
    ** follow this pattern:
    **
    ** pre>
    ** class MyPanel : Panel {
    **     new make(|This| in) : super(in) {
    **         content = Tree() { it.onSelect.add |e| { this->onSelect(e) } }
    **     }
    **
    **     private Void onSelect(Event e) {
    **         ...
    **     }
    ** }
    ** <pre
    **
    ** Routing the event to a handler method via '->' calls this 'trap()' method. Should that
    ** method return 'Void' and be prefixed with 'onXXX' then any Err thrown is logged with the
    ** Errors service.
    **
    ** Simple Err handling without fuss!
    override Obj? trap(Str name, Obj?[]? args := null) {
        retVal := null

        // FIXME: see super.trap() in Javascript - http://fantom.org/forum/topic/2457
//      try retVal = super.trap(name, args)
        try retVal = this.typeof.method(name).callOn(this, args)
        catch (Err err) {
            // because we handle the err and return null, we want to make sure we only do it for fwt events
            if (name.startsWith("on") && typeof.method(name, false)?.returns == Void#) {
                _errors.add(err)
                return null
            }
            else throw err
        }

        if (this is View)
            switch (name) {
                case #onShow.name       : _events.onViewShown(this)
                case #onHide.name       : _events.onViewHidden(this)
                case #onActivate.name   : _events.onViewActivated(this)
                case #onDeactivate.name : _events.onViewDeactivated(this)
                case #onModify.name     : _events.onViewModified(this)
            }
        else
            switch (name) {
                case #onShow.name       : _events.onPanelShown(this)
                case #onHide.name       : _events.onPanelHidden(this)
                case #onActivate.name   : _events.onPanelActivated(this)
                case #onDeactivate.name : _events.onPanelDeactivated(this)
                case #onModify.name     : _events.onPanelModified(this)
            }

        return retVal
    }
}