using afIoc** Views are 'Panels' that are associated with an (editable) resource.**** For a 'View' to be displayed, a 'Resource' must list it as one of its 'viewTypes()'.**** 'Views' are automatically added to the 'EventHub', so to receive events they only need to implement the required event mixin.@Jsabstractclass View : Panel { @Inject private Reflux _reflux @Inject private Errors _errors @Inject private RefluxEvents _eventsinternal UndoRedo[] _undoStack := UndoRedo[,]internal UndoRedo[] _redoStack := UndoRedo[,]** The resource associated with this view.** Set via 'load()'. Resource? resource** Subclasses should define the following ctor:**** syntax: fantom** ** new make(|This| in) : super(in) { ... }protectednew make(|This| in) : super(in){}** Returns 'true' if the resource has unsaved change.** 'Views' are responsible for setting this themselves. Bool isDirty { set {if(&isDirty == it)return &isDirty = itif(it){ name = "* ${name}"}else{if(name.startsWith("* ")) name = name[2..-1]}// could call this->onModify() but setting 'name' already does that}}** Return 'true' if the View should re-used for the given resource.**** By default this returns 'false'.virtual Bool reuseView(Resource resource){false}** Callback when the View should load the given resource.**** By default this sets the resource, name and icon.virtual Void load(Resource resource){if(isDirty) confirmClose(true)this.resource = resourcethis.icon = resource.iconthis.name = resource.name}** Callback for when the panel should refresh it's contents.**** By default this calls 'load()'.override Void refresh(Resource? resource := null){if(resource != null && resource == this.resource){ load(resource)return}if(resource == null && this.resource != null){ load(this.resource)return}}** Callback when the View should save its resource. Only called when 'isDirty' is 'true'.**** By default this just clears the dirty flag.virtual Void save(){ isDirty = false}** Callback when the view is being closed.** Return 'false' if the view should be kept open.**** Note: If 'force' is 'true' then the view **will** close regardless of the return value.**** By default this returns 'true'.virtual Bool confirmClose(Bool force){true}** Callback to handle dropped files.** Return 'true' to specify the drop event has been handled.virtual Bool onDrop(File[] droppedFiles){returnfalse}** Add a pair of Undo / Redo commands. Void addUndoRedo(|->| undo, |->| redo){// cap the history at something large but reasonableif(_undoStack.size > 999) _undoStack.size = 999 _undoStack.add(UndoRedo {it.undo = undoit.redo = redo})// you can't return to the same future once you've changed the past! _redoStack.clear _events.onViewModified(this)}** Undoes the last command. Void undo(){ undoRedo := _undoStack.popif(undoRedo == null)returntry undoRedo.undo.call()catch(Err err) _errors.add(err) _redoStack.push(undoRedo) _events.onViewModified(this)}** Redoes the last command. Void redo(){ undoRedo := _redoStack.popif(undoRedo == null)returntry undoRedo.redo.call()catch(Err err) _errors.add(err) _undoStack.push(undoRedo) _events.onViewModified(this)}}@Jsinternalclass UndoRedo { |->| undo |->| redonew make(|This|f){ f(this)}}