#30: Adaptives Layout (https://polymap.org/mosaic/ticket/30)
[polymap3:falkos-polymap3-rhei.git] / plugins / org.polymap.rhei.batik / src / org / polymap / rhei / batik / internal / desktop / DesktopAppManager.java
1 /*
2  * polymap.org
3  * Copyright 2013, Falko Bräutigam. All rights reserved.
4  *
5  * This is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU Lesser General Public License as
7  * published by the Free Software Foundation; either version 3.0 of
8  * the License, or (at your option) any later version.
9  *
10  * This software is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * Lesser General Public License for more details.
14  */
15 package org.polymap.rhei.batik.internal.desktop;
16
17 import static org.polymap.rhei.batik.Panels.withPrefix;
18
19 import java.util.ArrayList;
20 import java.util.List;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24
25 import com.google.common.base.Predicate;
26
27 import org.eclipse.swt.SWT;
28 import org.eclipse.swt.graphics.Image;
29 import org.eclipse.swt.graphics.Point;
30 import org.eclipse.swt.widgets.Composite;
31 import org.eclipse.swt.widgets.Display;
32 import org.eclipse.swt.widgets.Layout;
33
34 import org.eclipse.rwt.IBrowserHistory;
35 import org.eclipse.rwt.RWT;
36 import org.eclipse.rwt.events.BrowserHistoryEvent;
37 import org.eclipse.rwt.events.BrowserHistoryListener;
38
39 import org.eclipse.jface.action.IAction;
40 import org.eclipse.jface.action.IContributionItem;
41 import org.eclipse.jface.window.Window;
42
43 import org.eclipse.ui.forms.widgets.ScrolledPageBook;
44
45 import org.eclipse.core.runtime.IStatus;
46 import org.eclipse.core.runtime.Status;
47
48 import org.polymap.core.runtime.event.EventManager;
49
50 import org.polymap.rhei.batik.IApplicationLayouter;
51 import org.polymap.rhei.batik.IPanel;
52 import org.polymap.rhei.batik.IPanelSite;
53 import org.polymap.rhei.batik.PanelChangeEvent;
54 import org.polymap.rhei.batik.PanelChangeEvent.TYPE;
55 import org.polymap.rhei.batik.PanelIdentifier;
56 import org.polymap.rhei.batik.PanelPath;
57 import org.polymap.rhei.batik.app.BatikApplication;
58 import org.polymap.rhei.batik.internal.BatikComponentFactory;
59 import org.polymap.rhei.batik.internal.DefaultAppContext;
60 import org.polymap.rhei.batik.internal.PanelContextInjector;
61 import org.polymap.rhei.batik.internal.desktop.DesktopActionBar.PLACE;
62 import org.polymap.rhei.batik.toolkit.ConstraintLayout;
63 import org.polymap.rhei.batik.toolkit.IPanelToolkit;
64
65 /**
66  *
67  *
68  * @author <a href="http://www.polymap.de">Falko Bräutigam</a>
69  */
70 public class DesktopAppManager
71         implements IApplicationLayouter, BrowserHistoryListener {
72
73     private static Log log = LogFactory.getLog( DesktopAppManager.class );
74     
75     static final int                    DEFAULT_LAYOUT_SPACING = 10;
76     static final int                    DEFAULT_LAYOUT_MARGINS = 20;
77
78     private DesktopAppContext           context = new DesktopAppContext();
79
80     private DesktopToolkit              tk = new DesktopToolkit( context );
81
82     private DesktopAppWindow            mainWindow;
83
84     private DesktopActionBar            actionBar;
85
86     private ScrolledPageBook            scrolledPanelContainer;
87
88     private IPanel                      activePanel;
89
90     private IBrowserHistory             browserHistory;
91
92     private UserPreferences             userPrefs;
93
94     private PanelNavigator              panelNavigator;
95     
96
97     @Override
98     public Window initMainWindow( Display display ) {
99         browserHistory = RWT.getBrowserHistory();
100         browserHistory.createEntry( "Start", "Start" );
101         browserHistory.addBrowserHistoryListener( this );
102         
103         // panel navigator area
104         actionBar = new DesktopActionBar( context, tk );
105 //        actionBar.add( new PanelSearchField( ), PLACE.SEARCH );
106         actionBar.add( new PanelToolbar( this ), PLACE.PANEL_TOOLBAR );
107         actionBar.add( panelNavigator = new PanelNavigator( this ), PLACE.PANEL_NAVI );
108         actionBar.add( userPrefs = new UserPreferences( this ), PLACE.USER_PREFERENCES );
109
110         // mainWindow
111         mainWindow = new DesktopAppWindow( this ) {
112             @Override
113             protected Composite fillNavigationArea( Composite parent ) {
114                 return actionBar.createContents( parent );
115             }
116             @Override
117             protected Composite fillPanelArea( Composite parent ) {
118                 scrolledPanelContainer = new ScrolledPageBook( parent, SWT.V_SCROLL );
119                 scrolledPanelContainer.showEmptyPage();
120                 
121 //                scrolledPanelContainer = (ScrolledComposite)tk.createComposite( parent, SWT.BORDER, SWT.V_SCROLL );
122 //                panelArea = (Composite)scrolledPanelContainer.getContent();
123 //                panelArea.setLayout( new FillLayout( SWT.VERTICAL ) );
124 //                tk.createLabel( panelArea, "Panels..." );
125                 return scrolledPanelContainer;
126             }
127         };
128         // open root panel / after main window is created
129         display.asyncExec( new Runnable() {
130             public void run() {
131                 openPanel( new PanelIdentifier( "start" ) );
132             }
133         });
134         return mainWindow;
135     }
136
137
138     @Override
139     public void dispose() {
140         browserHistory.removeBrowserHistoryListener( this );
141     }
142
143
144     /** 
145      * Browser history event. 
146      */
147     @Override
148     public void navigated( BrowserHistoryEvent ev ) {
149         log.info( "BROWSER: " + ev.entryId );
150         
151         // go to start panel (no matter what)
152         while (activePanel.getSite().getPath().size() > 1) {
153             closePanel();
154             activePanel = getActivePanel();
155         }
156
157 //        if ("start".equalsIgnoreCase( ev.entryId )) {
158 //            //mainWindow.getShell().dispose();
159 //            //JSExecutor.executeJS( "window.location.reload();" );
160 //        }
161     }
162
163
164     /**
165      * Opens the {@link IPanel} for the given id and adds it to the top of the current
166      * panel path.
167      *
168      * @param panelId
169      * @throws IllegalStateException If no panel could be found for the given id.
170      */
171     protected IPanel openPanel( final PanelIdentifier panelId ) {
172         // find and initialize panels
173         final PanelPath prefix = activePanel != null ? activePanel.getSite().getPath() : PanelPath.ROOT;
174         List<IPanel> panels = BatikComponentFactory.instance().createPanels( new Predicate<IPanel>() {
175             public boolean apply( IPanel panel ) {
176                 new PanelContextInjector( panel, context ).run();
177                 EventManager.instance().publish( new PanelChangeEvent( panel, TYPE.INITIALIZING ) );
178                 
179                 PanelPath path = prefix.append( panel.id() );
180                 boolean wantsToBeShown = panel.init( new DesktopPanelSite( path ), context );
181                 
182                 if (panel.id().equals( panelId ) || wantsToBeShown) {
183                     EventManager.instance().publish( new PanelChangeEvent( panel, TYPE.INITIALIZED ) );
184                     return true;
185                 }
186                 return false;
187             }
188         });
189
190         // add to context
191         for (IPanel panel : panels) {
192             context.addPanel( panel );
193         }
194
195         //
196         IPanel panel = context.getPanel( prefix.append( panelId ) );
197         if (panel == null) {
198             throw new IllegalStateException( "No panel for ID: " + panelId );
199         }
200
201         // update UI
202         EventManager.instance().publish( new PanelChangeEvent( panel, TYPE.ACTIVATING ) );
203         
204         final Composite page = scrolledPanelContainer.createPage( panel.id() );
205         page.setLayout( newPanelLayout() );
206
207         panel.createContents( page );
208         scrolledPanelContainer.showPage( panel.id() );
209
210         Point panelSize = page.computeSize( SWT.DEFAULT, SWT.DEFAULT );
211         scrolledPanelContainer.setMinHeight( panelSize.y );
212
213         activePanel = panel;
214         EventManager.instance().publish( new PanelChangeEvent( panel, TYPE.ACTIVATED ) );
215
216         browserHistory.createEntry( panelId.id(), activePanel.getSite().getTitle() );
217         mainWindow.delayedRefresh( null );
218         
219         return activePanel;
220     }
221
222     
223     protected Layout newPanelLayout() {
224         ConstraintLayout result = new ConstraintLayout();
225         // 1000 -> 30px margin
226         result.marginWidth = result.spacing = 
227                 (int)(BatikApplication.sessionDisplay().getBounds().width * 0.03 );
228         result.marginHeight = result.marginWidth / 2;
229         log.info( "display width: " + BatikApplication.sessionDisplay().getBounds().width + " -> margin: " + result.marginWidth );
230         return result;
231     }
232     
233     
234     protected void closePanel() {
235         assert activePanel != null;
236         
237         PanelPath activePath = activePanel.getSite().getPath();
238         // remove/dispose activePanel and siblings
239         for (IPanel panel : context.findPanels( withPrefix( activePath.removeLast( 1 ) ) )) {
240             EventManager.instance().publish( new PanelChangeEvent( panel, TYPE.DISPOSING ) );
241             panel.dispose();
242             context.removePanel( panel.getSite().getPath() );
243             if (scrolledPanelContainer.hasPage( panel.id() )) {
244                 scrolledPanelContainer.removePage( panel.id() );
245             }
246             EventManager.instance().publish( new PanelChangeEvent( panel, TYPE.DISPOSED ) );
247         }
248         
249         EventManager.instance().publish( new PanelChangeEvent( activePanel, TYPE.DEACTIVATING ) );
250         scrolledPanelContainer.removePage( activePanel.id() );
251         EventManager.instance().publish( new PanelChangeEvent( activePanel, TYPE.DEACTIVATED ) );
252         
253         // activate child panel
254         activePath = activePath.removeLast( 1 );
255         activePanel = context.getPanel( activePath );
256         EventManager.instance().publish( new PanelChangeEvent( activePanel, TYPE.ACTIVATING ) );
257         scrolledPanelContainer.showPage( activePanel.id() );
258         EventManager.instance().publish( new PanelChangeEvent( activePanel, TYPE.ACTIVATED ) );
259
260         browserHistory.createEntry( activePanel.id().id(), activePanel.getSite().getTitle() );
261     }
262
263     
264     public void activatePanel( PanelIdentifier panelId ) {
265         PanelPath prefix = activePanel != null ? activePanel.getSite().getPath().removeLast( 1 ) : PanelPath.ROOT;
266         PanelPath activePath = prefix.append( panelId );
267
268         // deactivating
269         EventManager.instance().publish( new PanelChangeEvent( activePanel, TYPE.DEACTIVATING ) );
270         IPanel previous = activePanel = context.getPanel( activePath );
271         EventManager.instance().publish( new PanelChangeEvent( previous, TYPE.DEACTIVATED ) );
272
273         // activating
274         EventManager.instance().publish( new PanelChangeEvent( activePanel, TYPE.ACTIVATING ) );
275         if (scrolledPanelContainer.hasPage( panelId )) {
276             scrolledPanelContainer.showPage( panelId );
277         }
278         else {
279             EventManager.instance().publish( new PanelChangeEvent( activePanel, TYPE.ACTIVATING ) );
280             Composite page = scrolledPanelContainer.createPage( activePanel.id() );
281             page.setLayout( newPanelLayout() );
282
283             activePanel.createContents( page );
284             page.layout( true );
285             scrolledPanelContainer.showPage( activePanel.id() );
286             
287             Point panelSize = page.computeSize( SWT.DEFAULT, SWT.DEFAULT );
288             scrolledPanelContainer.setMinHeight( panelSize.y );
289         }
290         EventManager.instance().publish( new PanelChangeEvent( activePanel, TYPE.ACTIVATED ) );
291         
292         browserHistory.createEntry( panelId.id(), activePanel.getSite().getTitle() );
293         mainWindow.delayedRefresh( null );
294     }
295
296
297     protected DesktopAppContext getContext() {
298         return context;
299     }
300
301     
302     public IPanel getActivePanel() {
303         return activePanel;
304     }
305
306
307     /**
308      *
309      */
310     class DesktopAppContext
311             extends DefaultAppContext {
312
313         @Override
314         public void setUserName( String username ) {
315             userPrefs.setUsername( username );
316         }
317
318         @Override
319         public void addPreferencesAction( IAction action ) {
320             userPrefs.addMenuContribution( action );
321         }
322
323         @Override
324         public IPanel openPanel( PanelIdentifier panelId ) {
325             return DesktopAppManager.this.openPanel( panelId );
326         }
327
328         @Override
329         public void closePanel() {
330             DesktopAppManager.this.closePanel();
331         }
332     }
333
334
335     /**
336      *
337      */
338     protected class DesktopPanelSite
339             implements IPanelSite {
340
341         private PanelPath           path;
342
343         private String              title = "Untitled";
344         
345         private Image               icon;
346
347         /** Toolbar tools: {@link IAction} or {@link IContributionItem}. */
348         private List                tools = new ArrayList();
349         
350         private IStatus             status = Status.OK_STATUS;
351
352
353         protected DesktopPanelSite( PanelPath path ) {
354             assert path != null;
355             this.path = path;
356         }
357
358         @Override
359         public PanelPath getPath() {
360             return path;
361         }
362
363         @Override
364         public void setStatus( IStatus status ) {
365             this.status = status;
366             mainWindow.setStatus( status );
367         }
368
369         @Override
370         public IStatus getStatus() {
371             return status;
372         }
373
374         @Override
375         public void addToolbarAction( IAction action ) {
376             tools.add( action );
377         }
378
379         @Override
380         public void addToolbarItem( IContributionItem item ) {
381             tools.add( item );
382         }
383
384         public List getTools() {
385             return tools;
386         }
387
388         @Override
389         public String getTitle() {
390             return title;
391         }
392
393         @Override
394         public void setTitle( String title ) {
395             this.title = title;
396             panelNavigator.updateBreadcrumb();
397         }
398
399         @Override
400         public Image getIcon() {
401             return icon;
402         }
403         
404         @Override
405         public void setIcon( Image icon ) {
406             this.icon = icon;
407             panelNavigator.updateBreadcrumb();
408         }
409
410         @Override
411         public void addSidekick() {
412             // XXX Auto-generated method stub
413             throw new RuntimeException( "not yet implemented." );
414         }
415
416         @Override
417         public IPanelToolkit toolkit() {
418             return tk;
419         }
420
421         @Override
422         public void layout( boolean changed ) {
423             mainWindow.delayedRefresh( null );
424         }
425
426         @Override
427         public <T> T getLayoutPreference( String key ) {
428             if (LAYOUT_SPACING_KEY.equals( key )) {
429                 return (T)Integer.valueOf( DEFAULT_LAYOUT_SPACING );
430             }
431             else if (LAYOUT_MARGINS_KEY.equals( key )) {
432                 return (T)Integer.valueOf( DEFAULT_LAYOUT_MARGINS );
433             }
434             return null;
435         }
436
437     }
438
439 }