001    /*
002     * Copyright 2002-2008 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.richclient.application.support;
017    
018    import java.beans.PropertyChangeEvent;
019    import java.beans.PropertyChangeListener;
020    import java.util.ArrayList;
021    import java.util.Collections;
022    import java.util.HashSet;
023    import java.util.Iterator;
024    import java.util.List;
025    
026    import org.springframework.context.ApplicationListener;
027    import org.springframework.context.event.ApplicationEventMulticaster;
028    import org.springframework.context.support.AbstractApplicationContext;
029    import org.springframework.richclient.application.ApplicationPage;
030    import org.springframework.richclient.application.ApplicationServicesLocator;
031    import org.springframework.richclient.application.ApplicationWindow;
032    import org.springframework.richclient.application.PageComponent;
033    import org.springframework.richclient.application.PageComponentDescriptor;
034    import org.springframework.richclient.application.PageComponentListener;
035    import org.springframework.richclient.application.PageComponentPane;
036    import org.springframework.richclient.application.PageComponentPaneFactory;
037    import org.springframework.richclient.application.PageDescriptor;
038    import org.springframework.richclient.application.View;
039    import org.springframework.richclient.application.ViewDescriptor;
040    import org.springframework.richclient.application.ViewDescriptorRegistry;
041    import org.springframework.richclient.factory.AbstractControlFactory;
042    import org.springframework.richclient.util.EventListenerListHelper;
043    import org.springframework.util.Assert;
044    
045    /**
046     * Abstract "convenience" implementation of <code>ApplicationPage</code>.
047     * 
048     * @author Peter De Bruycker
049     */
050    public abstract class AbstractApplicationPage extends AbstractControlFactory implements ApplicationPage {
051    
052        private final EventListenerListHelper pageComponentListeners = new EventListenerListHelper(
053                PageComponentListener.class);
054    
055        private ViewDescriptorRegistry viewDescriptorRegistry;
056    
057        private PageComponentPaneFactory pageComponentPaneFactory;
058    
059        private final List<PageComponent> pageComponents = new ArrayList<PageComponent>();
060    
061        private PageComponent activeComponent;
062    
063        private SharedCommandTargeter sharedCommandTargeter;
064    
065        private PageDescriptor descriptor;
066    
067        private ApplicationWindow window;
068    
069        private boolean settingActiveComponent;
070    
071        private ApplicationEventMulticaster applicationEventMulticaster;
072    
073        private PropertyChangeListener pageComponentUpdater = new PropertyChangeListener() {
074            public void propertyChange(PropertyChangeEvent evt) {
075                if (evt.getSource() instanceof PageComponent) {
076                    updatePageComponentProperties((PageComponent) evt.getSource());
077                }
078            }
079        };
080    
081        public AbstractApplicationPage() {
082    
083        }
084    
085        public AbstractApplicationPage(ApplicationWindow window, PageDescriptor pageDescriptor) {
086            setApplicationWindow(window);
087            setDescriptor(pageDescriptor);
088        }
089    
090        /**
091         * Called when the <code>PageComponent</code> changes any of its properties (display name, caption, icon, ...).
092         * <p>
093         * This method should be overridden when these changes must be reflected in the ui.
094         * 
095         * @param pageComponent
096         *            the <code>PageComponent</code> that has changed
097         */
098        protected void updatePageComponentProperties(PageComponent pageComponent) {
099            // do nothing by default
100        }
101    
102        protected PageComponent findPageComponent(final String viewDescriptorId) {
103            for (PageComponent component : pageComponents) {
104                if (component.getId().equals(viewDescriptorId)) {
105                    return component;
106                }
107            }
108    
109            return null;
110        }
111    
112        @SuppressWarnings("unchecked")
113        public <T extends View> T getView(String id) {
114            return (T) findPageComponent(id);
115        }
116    
117        public void addPageComponentListener(PageComponentListener listener) {
118            pageComponentListeners.add(listener);
119        }
120    
121        public void removePageComponentListener(PageComponentListener listener) {
122            pageComponentListeners.remove(listener);
123        }
124    
125        protected void fireOpened(PageComponent component) {
126            component.componentOpened();
127            pageComponentListeners.fire("componentOpened", component);
128        }
129    
130        protected void fireFocusGained(PageComponent component) {
131            component.componentFocusGained();
132            pageComponentListeners.fire("componentFocusGained", component);
133        }
134    
135        /**
136         * Sets the first {@link PageComponent} as the active one.
137         */
138        protected void setActiveComponent() {
139            if (pageComponents.size() > 0) {
140                setActiveComponent((PageComponent) pageComponents.get(0));
141            }
142        }
143    
144        protected ViewDescriptor getViewDescriptor(String viewDescriptorId) {
145            return getViewDescriptorRegistry().getViewDescriptor(viewDescriptorId);
146        }
147    
148        /**
149         * Returns the active <code>PageComponent</code>, or <code>null</code> if none.
150         * 
151         * @return the active <code>PageComponent</code>
152         */
153        public PageComponent getActiveComponent() {
154            return activeComponent;
155        }
156    
157        /**
158         * Activates the given <code>PageComponent</code>. Does nothing if it is already the active one.
159         * <p>
160         * Does nothing if this <code>ApplicationPage</code> doesn't contain the given <code>PageComponent</code>.
161         * 
162         * @param pageComponent
163         *            the <code>PageComponent</code>
164         */
165        public void setActiveComponent(PageComponent pageComponent) {
166            if (!pageComponents.contains(pageComponent)) {
167                return;
168            }
169    
170            // if pageComponent is already active, don't do anything
171            if (this.activeComponent == pageComponent || settingActiveComponent) {
172                return;
173            }
174    
175            settingActiveComponent = true;
176    
177            if (this.activeComponent != null) {
178                fireFocusLost(this.activeComponent);
179            }
180            giveFocusTo(pageComponent);
181            this.activeComponent = pageComponent;
182            fireFocusGained(this.activeComponent);
183    
184            settingActiveComponent = false;
185        }
186    
187        protected void fireFocusLost(PageComponent component) {
188            component.componentFocusLost();
189            pageComponentListeners.fire("componentFocusLost", component);
190        }
191    
192        /**
193         * This method must add the given <code>PageComponent</code> in the ui.
194         * <p>
195         * Implementors may choose to add the <code>PageComponent</code>'s control directly, or add the
196         * <code>PageComponentPane</code>'s control.
197         * 
198         * @param pageComponent
199         *            the <code>PageComponent</code> to add
200         */
201        protected abstract void doAddPageComponent(PageComponent pageComponent);
202    
203        /**
204         * This method must remove the given <code>PageComponent</code> from the ui.
205         * 
206         * @param pageComponent
207         *            the <code>PageComponent</code> to remove
208         */
209        protected abstract void doRemovePageComponent(PageComponent pageComponent);
210    
211        /**
212         * This method must transfer the focus to the given <code>PageComponent</code>. This could involve making an
213         * internal frame visible, selecting a tab in a tabbed pane, ...
214         * 
215         * @param pageComponent
216         *            the <code>PageComponent</code>
217         * @return <code>true</code> if the operation was successful, <code>false</code> otherwise
218         */
219        protected abstract boolean giveFocusTo(PageComponent pageComponent);
220    
221        protected PageComponentPane createPageComponentPane(PageComponent pageComponent) {
222            return getPageComponentPaneFactory().createPageComponentPane(pageComponent);
223        }
224    
225        protected void fireClosed(PageComponent component) {
226            component.componentClosed();
227            pageComponentListeners.fire("componentClosed", component);
228        }
229    
230        public String getId() {
231            return descriptor.getId();
232        }
233    
234        public ApplicationWindow getWindow() {
235            return window;
236        }
237    
238        /**
239         * Closes the given <code>PageComponent</code>. This method disposes the <code>PageComponent</code>, triggers all
240         * necessary events ("focus lost" and "closed"), and will activate another <code>PageComponent</code> (if there is
241         * one).
242         * <p>
243         * Returns <code>false</code> if this <code>ApplicationPage</code> doesn't contain the given
244         * <code>PageComponent</code>.
245         * 
246         * @param pageComponent
247         *            the <code>PageComponent</code>
248         * @return boolean <code>true</code> if pageComponent was successfully closed.
249         */
250        public boolean close(PageComponent pageComponent) {
251            if (!pageComponent.canClose()) {
252                return false;
253            }
254    
255            if (!pageComponents.contains(pageComponent)) {
256                return false;
257            }
258    
259            if (pageComponent == activeComponent) {
260                fireFocusLost(pageComponent);
261                activeComponent = null;
262            }
263    
264            doRemovePageComponent(pageComponent);
265            pageComponents.remove(pageComponent);
266            pageComponent.removePropertyChangeListener(pageComponentUpdater);
267            if (pageComponent instanceof ApplicationListener && getApplicationEventMulticaster() != null) {
268                getApplicationEventMulticaster().removeApplicationListener((ApplicationListener) pageComponent);
269            }
270    
271            pageComponent.dispose();
272            fireClosed(pageComponent);
273            if (activeComponent == null) {
274                setActiveComponent();
275            }
276            return true;
277        }
278    
279        /**
280         * Closes this <code>ApplicationPage</code>. This method calls {@link #close(PageComponent)} for each open
281         * <code>PageComponent</code>.
282         * 
283         * @return <code>true</code> if the operation was successful, <code>false</code> otherwise.
284         */
285        public boolean close() {
286            for (Iterator<PageComponent> iter = new HashSet<PageComponent>(pageComponents).iterator(); iter.hasNext();) {
287                PageComponent component = iter.next();
288                if (!close(component))
289                    return false;
290            }
291            return true;
292        }
293    
294        public View showView(String id) {
295            Assert.hasText(id, "id cannot be empty");
296    
297            return showView(getViewDescriptor(id), false, null);
298        }
299    
300        public View showView(String id, Object input) {
301            Assert.hasText(id, "id cannot be empty");
302    
303            return showView(getViewDescriptor(id), true, input);
304        }
305    
306        private View showView(ViewDescriptor viewDescriptor, boolean setInput, Object input) {
307            Assert.notNull(viewDescriptor, "viewDescriptor cannot be null");
308    
309            View view = (View) findPageComponent(viewDescriptor.getId());
310            if (view == null) {
311                view = (View) createPageComponent(viewDescriptor);
312    
313                if (setInput) {
314                    // trigger control creation before input is set to avoid npe
315                    view.getControl();
316                    
317                    view.setInput(input);
318                }
319    
320                addPageComponent(view);
321            } else {
322                if (setInput) {
323                    view.setInput(input);
324                }
325            }
326            setActiveComponent(view);
327            
328            return view;
329        }
330    
331        public void openEditor(Object editorInput) {
332            // TODO implement editors
333        }
334    
335        public boolean closeAllEditors() {
336            // TODO implement editors
337            return true;
338        }
339    
340        /**
341         * Adds the pageComponent to the components list while registering listeners and firing appropriate events. (not yet
342         * setting the component as the active one)
343         * 
344         * @param pageComponent
345         *            the pageComponent to add.
346         */
347        protected void addPageComponent(PageComponent pageComponent) {
348            pageComponents.add(pageComponent);
349            doAddPageComponent(pageComponent);
350            pageComponent.addPropertyChangeListener(pageComponentUpdater);
351    
352            fireOpened(pageComponent);
353        }
354    
355        /**
356         * Creates a PageComponent for the given PageComponentDescriptor.
357         * 
358         * @param descriptor
359         *            the descriptor
360         * @return the created PageComponent
361         */
362        protected PageComponent createPageComponent(PageComponentDescriptor descriptor) {
363            PageComponent pageComponent = descriptor.createPageComponent();
364            pageComponent.setContext(new DefaultViewContext(this, createPageComponentPane(pageComponent)));
365            if (pageComponent instanceof ApplicationListener && getApplicationEventMulticaster() != null) {
366                getApplicationEventMulticaster().addApplicationListener((ApplicationListener) pageComponent);
367            }
368    
369            return pageComponent;
370        }
371    
372        public List<PageComponent> getPageComponents() {
373            return Collections.unmodifiableList(pageComponents);
374        }
375    
376        public final void setApplicationWindow(ApplicationWindow window) {
377            Assert.notNull(window, "The containing window is required");
378            Assert.state(this.window == null, "Page window already set: it should only be set once, during initialization");
379            this.window = window;
380            sharedCommandTargeter = new SharedCommandTargeter(window);
381            addPageComponentListener(sharedCommandTargeter);
382        }
383    
384        public final void setDescriptor(PageDescriptor descriptor) {
385            Assert.notNull(descriptor, "The page's descriptor is required");
386            Assert.state(this.descriptor == null,
387                    "Page descriptor already set: it should only be set once, during initialization");
388            this.descriptor = descriptor;
389        }
390    
391        protected PageDescriptor getPageDescriptor() {
392            return descriptor;
393        }
394    
395        public ApplicationEventMulticaster getApplicationEventMulticaster() {
396            if ((applicationEventMulticaster == null) && (getApplicationContext() != null)) {
397                final String beanName = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME;
398                if (getApplicationContext().containsBean(beanName)) {
399                    applicationEventMulticaster = (ApplicationEventMulticaster) getApplicationContext().getBean(beanName);
400                }
401            }
402            return applicationEventMulticaster;
403        }
404    
405        public void setViewDescriptorRegistry(ViewDescriptorRegistry viewDescriptorRegistry) {
406            this.viewDescriptorRegistry = viewDescriptorRegistry;
407        }
408    
409        public ViewDescriptorRegistry getViewDescriptorRegistry() {
410            if (viewDescriptorRegistry == null) {
411                viewDescriptorRegistry = (ViewDescriptorRegistry) ApplicationServicesLocator.services().getService(
412                        ViewDescriptorRegistry.class);
413            }
414    
415            return viewDescriptorRegistry;
416        }
417    
418        public void setPageComponentPaneFactory(PageComponentPaneFactory pageComponentPaneFactory) {
419            this.pageComponentPaneFactory = pageComponentPaneFactory;
420        }
421    
422        public PageComponentPaneFactory getPageComponentPaneFactory() {
423            if (pageComponentPaneFactory == null) {
424                pageComponentPaneFactory = (PageComponentPaneFactory) ApplicationServicesLocator.services().getService(
425                        PageComponentPaneFactory.class);
426            }
427    
428            return pageComponentPaneFactory;
429        }
430    }