package votorola.s.gwt.scene; // Copyright 2010-2013, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. import com.google.gwt.core.client.*; import com.google.gwt.dom.client.*; import com.google.gwt.event.logical.shared.*; import com.google.gwt.regexp.shared.*; import com.google.gwt.user.client.*; import com.google.gwt.view.client.*; import com.google.web.bindery.event.shared.*; import votorola.a.diff.*; import votorola.a.web.gwt.*; import votorola.g.hold.*; import votorola.g.lang.*; import votorola.g.web.gwt.*; import votorola.g.web.gwt.event.*; import votorola.s.gwt.scene.feed.*; import votorola.s.gwt.stage.*; /** Scenes for Crossforum Theatre. The navigable state of the UI is specified by switches * (fragment parameters) in the {@linkplain #history() history stack}. Navigation * includes structural modifications to the UI such as major component changes, or true * navigation such as zoom and pan. It does not include decorative changes such as those * caused by track selections in the {@linkplain votorola.s.gwt.stage.StageV surrounding * stage}. The switches currently reserved are: * * * * * * * * * * * * * * * * * * * * * *
SwitchControlled state
{@linkplain votorola.s.gwt.scene.vote.Votespace#aSacSwitch() a}Superaccount selection ({@linkplain * votorola.s.gwt.scene.vote.Votespace#aSacSwitch() deprecated}).
{@linkplain #cCompositionSwitch() c}Composition of the theatre UI.
{@linkplain #sScopingSwitch() s}Scoping of the scene and other scope dependent views such as the feed.
*/ public final class Scenes { /** Does nothing itself but the call forces static initialization of this class. */ public static void forceInitClass() {} /** Creates the single instance of Scenes. * * @throws IllegalStateException if an instance of Scenes was already * constructed. */ Scenes() { new BiteStager(); } // called by SceneIn /** The single instance of Scenes. */ public static Scenes i() { return instance; } private static Scenes instance; { if( instance != null ) throw new IllegalStateException(); instance = Scenes.this; } // ------------------------------------------------------------------------------------ /** The selection model for individual bites. Note that deselection is not supported * (GWT 2.1). Instead try setSelected(BiteJS.{@linkplain BiteJS#EMPTY_BITE * EMPTY_BITE},true). * * @see Deselection */ public SingleSelectionModel biteSelection() { return biteSelection; } // The deselection limitation is unclear from the above notes because the cited // thread concerns deselection in the CellList view (not supported), wheras the // workaround I offered concerns deselection in the model. What was I thinking? // If the model itself has this limitation, then it would be trivial to roll our // own implementation. Just grep the code for an example. - MCA private final SingleSelectionModel biteSelection = new SingleSelectionModel(); /** The URL history stacker. To keep the URLs short, only gross structural changes * ought to be encoded in bookmarkable form and stacked in history, not the merely * "decorative" changes such as item selection. * *

Decorative state cannot be revisted through the history controls but neither * can it be destroyed. A user's attempt to navigate a series of decorative changes * by going backward in history will fail, resulting instead in a structural change. * Going forward again will restore both the structure and the decorations, however, * because decorative state is persistent as a rule.

*/ public HistoryX history() { return history; } private final HistoryX history = new HistoryX( /*isFragmentShared*/false, GWTX.i().bus() ); /** The 'c' composition switch. It directly controls the composition of the theatre * UI. It consists of two short mnemonics: the first for the {@linkplain * votorola.s.gwt.scene.feed.Feed.SwitchMnemonic feed} and the second for the {@linkplain * Scene.SwitchMnemonic scene}. For example, c=DG specifies a diff feed * and a geomap. * *

If no value is specified, or if it is cleared at runtime, then the window * location is {@linkplain HistoryX#replace() replaced} by a new URL with a * switch specifing a default composition.

*/ Switch cCompositionSwitch() { return cCompositionSwitch; } private final Switch cCompositionSwitch = new CSwitch(); /** The scene that is currently shown. The value is bound via the {@linkplain * GWTX#bus() event bus} to property name scene. It is never null, but it * may briefly be set to {@linkplain Scene0#i() Scene0} during component transitions. */ public Scene scene() { return scene; } private Scene scene = Scene0.i(); /** Clears the scene that is currently shown by setting it to {@linkplain Scene0#i() * Scene0}. */ void clearMap() { setScene( Scene0.i() ); } /** Sets the scene that is currently shown. */ void setScene( final Scene newScene ) { if( newScene.equals( scene )) return; // throws NullPointerException if newScene null scene = newScene; fireEvent( new PropertyChange( "scene" )); } /** The 's' scoping switch. The format of the switch value depends on the particular * scene shown. The value ultimately controls the scoping of the scene and other * scope dependent views such as the feed. Scoping works through the intermediation * of scoping models. Clients may register with those models either {@linkplain * Scoping#addHandler(ScopeChangeHandler) directly} or {@linkplain * ScopeChangeEvent#addHandler(ScopeChangeHandler) indirectly} to receive their * events. */ public Switch sScopingSwitch() { return sScopingSwitch; } private final Switch sScopingSwitch = new Switch( "s", history ); /** Whether to show views and controls for resource accounting that are unfinished or * deprecated. * * @see #setUseRAC(boolean) */ public static @GWTConfigCallback boolean toUseRAC() { return toUseRAC; } private static boolean toUseRAC; private static native void exposeUseRAC() /*-{ $wnd.s_gwt_scene_Scenes_toUseRAC = $entry( @votorola.s.gwt.scene.Scenes::toUseRAC() ); $wnd.s_gwt_scene_Scenes_setUseRAC = $entry( @votorola.s.gwt.scene.Scenes::setUseRAC(Z) ); }-*/; static { assert SceneIn.isForcedInit(): "forced init " + Scenes.class.getName(); exposeUseRAC(); } /** Sets whether to show views and controls for resource accounting that are * unfinished or deprecated. This configuration method is for developers. Call * it from the global configuration function {@linkplain SceneIn * voGWTConfig.s_gwt_scene} like this for example:
   s_gwt_scene_Scenes_setUseRAC( true ); // default is false
* *

You may also want to set the flag CountWAP.toSimulate, and recompile.

* *

This is now broken. See the comments in the constructor of * SacSelectionV.SacVCell.

* * @see #toUseRAC() */ @Warning("broken") public static @GWTConfigCallback void setUseRAC( boolean _to ) { toUseRAC = _to; } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private void fireEvent( final com.google.gwt.event.shared.GwtEvent e ) { GWTX.i().bus().fireEventFromSource( e, Scenes.this ); } // ==================================================================================== private final class BiteStager implements SelectionChangeEvent.Handler, TheatreInitializer { BiteStager() { Stage.i().addInitializer( BiteStager.this ); } // auto-removed private void init() { biteSelection.addSelectionChangeHandler( BiteStager.this ); // no need to unregister, registry does not outlive this listener restage(); // init state } // -------------------------------------------------------------------------------- private void restage() { final DiffLookJS diff; final Message message; final BiteJS bite = biteSelection.getSelectedObject(); if( bite == null ) { diff = null; message = null; } else { diff = bite.difference(); message = bite.message(); } final Stage stage = Stage.i(); stage.setDifference( diff ); stage.setMessage( message ); } // - S e l e c t i o n - C h a n g e - E v e n t . H a n d l e r ------------------ public void onSelectionChange( SelectionChangeEvent _e ) { restage(); } // - T h e a t r e - I n i t i a l i z e r ---------------------------------------- public void initFrom( Stage _s, boolean _isReferencePending ) { init(); } // Does the reverse (init to) because the feeds are sunsetting and it's not // worth the effort to modify them extensively. The best thing to do here is to // read the state of the stage (difference and message) and select the matching // feed bite. The next best (what's done here) is the reverse: initialize the // stage based on feed state, thus preventing a discontinuity between stage and // feed views that might confuse the user. public void initFromComplete( Stage _stage, boolean _isReferencePending ) {} public void initTo( Stage _s ) { init(); } public void initTo( Stage _s, TheatrePage _referrer ) { init(); } public void initToComplete( Stage _s, boolean _isReferencePending ) {} public void initUltimately( Stage _s, TheatrePage _referrer ) {} } // ==================================================================================== private static final class CSwitch extends Switch implements Scheduler.ScheduledCommand { CSwitch() { super( "c", i().history() ); spool.add( new Hold() { final HandlerRegistration hR = history().addPreviewHandler( new ValueChangeHandler() { // Begin recomposition in the preview dispatch where old components // can be released before they learn of value changes that might break // them. Scope handlers in particular are brittle. This cannot be // done in the regular dispatch, regardless of how early, because the // release of handlers has no effect once a dispatch is in progress. public final void onValueChange( final ValueChangeEvent e ) { final String oldValue = lastValue; // courtesy Switch.syncFromHistory() final String newValue = history().getSwitchValue( name() ); if( ObjectX.nullEquals( newValue, oldValue )) return; // no actual change recompose( newValue, e ); } }); public void release() { hR.removeHandler(); } }); Scheduler.get().scheduleFinally( new Scheduler.ScheduledCommand() // after ScenesV constructed { public void execute() { recompose( get(), null ); } // init state }); } private static final RegExp C_PATTERN = RegExp.compile( "^([A-Z][^A-Z]*)([A-Z][^A-Z]*)$" ); private void recompose( final String newValue, final ValueChangeEvent e ) { if( newValue == null || newValue.length() == 0 ) { set( "DG" ); // default composition history().replace(); return; // replace fires a change event, so this method will re-run } final MatchResult m = C_PATTERN.exec( newValue ); if( m == null ) { Window.alert( "Malformed composition switch: c=" + newValue ); return; } Feed.SwitchMnemonic feedSMNew = Feed.SwitchMnemonic.Dum; // till proven otherwise Scene.SwitchMnemonic sceneSMNew = Scene.SwitchMnemonic.Dum; String mnemonicString; mnemonicString = m.getGroup( 1 ); try { feedSMNew = Feed.SwitchMnemonic.valueOf( mnemonicString ); } catch( IllegalArgumentException x ) { Window.alert( "Malformed feed mnemonic '" + mnemonicString + "' in composition switch: c=" + newValue ); } mnemonicString = m.getGroup( 2 ); try { sceneSMNew = Scene.SwitchMnemonic.valueOf( mnemonicString ); } catch( IllegalArgumentException x ) { Window.alert( "Malformed scene mnemonic '" + mnemonicString + "' in composition switch: c=" + newValue ); } feedChanging = feedSM != feedSMNew; if( feedChanging ) { feedSMHold.release(); feedSM = feedSMNew; } mapChanging = sceneSM != sceneSMNew; if( mapChanging ) { sceneSMHold.release(); sceneSM = sceneSMNew; } Document.get().setTitle( "Crossforum Theatre " + feedSM.name() + sceneSM.name() ); if( e == null ) execute(); // not a preview dispatch, rather init, so no need to wait else Scheduler.get().scheduleFinally( CSwitch.this ); // continues in execute() // This effectively puts the constructive part of the recomposition after // the regular dispatch, thus saving the newly emplaced components from // being exposed to transitional state and a flurry of accompanying events. } private boolean feedChanging; private Feed.SwitchMnemonic feedSM = null; private Hold feedSMHold = Hold0.i(); private boolean mapChanging; private Scene.SwitchMnemonic sceneSM = null; private Hold sceneSMHold = feedSMHold; // - S c h e d u l e r . S c h e d u l e d - C o m m a n d ------------------------ public void execute() // continuing from compose() { if( feedChanging ) feedSMHold = feedSM.emplace(); if( mapChanging ) sceneSMHold = sceneSM.emplace(); } } }