All Downloads are FREE. Search and download functionalities are using the official Maven repository.

at.spardat.xma.appshell.AppShell Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     s IT Solutions AT Spardat GmbH - initial API and implementation
 *******************************************************************************/

/*******************************************************************************
 * Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     s IT Solutions AT Spardat GmbH - initial API and implementation
 *******************************************************************************/

/*
 * @(#) $Id: AppShell.java 8768 2011-11-23 15:48:49Z laslovd $
 *
 *
 *
 *
 */
package at.spardat.xma.appshell;

import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;

import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

import at.spardat.xma.boot.component.IXMAControl;
import at.spardat.xma.component.ComponentClient;
import at.spardat.xma.page.DialogPage;
import at.spardat.xma.page.PageClient;

/**
 * Base class for application shells in XMA. An application shell is the window opened at startup
 * of the client side application. It stays open during the whole user session. It typically contains
 * a header area showing some context information, a menu area containing the menu tree of the
 * application and a client area showing the currently active component.
 *
 * @author s2877
 * @since 1.4.0
 */
public abstract class AppShell extends DialogPage implements IAppShell {

    AppShellDelegate[] delegates = new AppShellDelegate[0]; 
    
    /**
     * Initializes an AppShell.
     *
     * @param component the Component containing the DialogPage.
     * @param stateless indicating if this page is stateless on the server.
     * @param style The SWT-Style for the Shell of the DialogPage.
     * @throws IllegalArgumentException if component is null.
     */
    public AppShell(ComponentClient component, boolean stateless, int style) {
        super(component, stateless, style);
    }
    
    /**
     * Initializes an AppShell inside a given Component.
     *
     * @param component the Component containing the DialogPage.
     * @param parentShell the Shell which shall be the parent of the Shell of the DialogPage.
     * @param style The SWT-Style for the Shell of the DialogPage.
     * @param stateless indicating if this page is stateless on the server.
     * @throws IllegalArgumentException if component is null.
     */
    public AppShell(ComponentClient component, Shell parentShell, boolean stateless, int style) {
        super(component,parentShell,stateless,style);
    }

    /**
     * Initializes an AppShell inside the same Component as the parent PageClient.
     *
     * @param parent the PageClient calling this DialogPage.
     * @param stateless indicating if this page is stateless on the server.
     * @param style The SWT-Style for the Shell of the DialogPage.
     */
    public AppShell(PageClient parent, boolean stateless, int style) {
        super(parent,stateless,style);
    }
    
    // TODO: comment me
    public void addDelegate ( AppShellDelegate delegate ) {
        delegate.setAppShell(this);
        AppShellDelegate[] newDelegates = new AppShellDelegate[delegates.length + 1];
        System.arraycopy(delegates,0,newDelegates,0,delegates.length);
        newDelegates[delegates.length] = delegate;
        delegates = newDelegates; 
    }

    /**
     * Get the SWT-composite where client pages or composites
     * can be embedded.
     * @return  the composite representing the client area.
     */
    abstract public Composite getClientComposite();

    /**
     * Create the MenuItem which will serve as root of all
     * other MenuItems.
     * @return the newly created root of the menu tree.
     */
    abstract public ITask createRootTask(IMenuItem rootMenu);

    /**
     * Creates the Task with the given name. The default implementation
     * takes the name as the fully qualified classname of the Task.
     * @param name unique name for the task within the client side application.
     */
    public ITask createTask(String name) {
        return getComponent().createTask(name);
    }

    /**
     * Locks and disables the menu. Internally increments a lock counter.
     * To unlock the menu again unlockMenu() must be called as often
     * as lockMenu().
     */
    public void lockMenu() {
        for ( int i = 0; i < delegates.length; i++ ) {
            delegates[i].lockMenu();
        }
    }

    /**
     * Unlocks the menu. Internally decrements a lock counter.
     * To unlock the menu unlockMenu() must be called as often
     * as lockMenu() before.
     */
    public void unlockMenu() {
        for ( int i = 0; i < delegates.length; i++ ) {
            delegates[i].unlockMenu();
        }
    }

    /**
     * Completely enable/disable the menu.
     * @param enabled true: enable, false: disable.
     */
    protected void setMenuEnabled(boolean enabled) {
        for ( int i = 0; i < delegates.length; i++ ) {
            delegates[i].setMenuEnabled(enabled);
        }
    }

    /**
     * Enables the given menu item if the argument  enabled is true,
     * and disables it otherwise. A disabled menu item is
     * not selectable from the user interface and
     * draws with an inactive or "grayed" look.
     * Disabling a menu item makes all its children not selectable, too.
     * @since 2.2.0
     */
    protected void setMenuItemEnabled(IMenuItem item,boolean enabled) {
        for ( int i = 0; i < delegates.length; i++ ) {
            if ( delegates[i].rootMenu.containsItem(item,true) ) {
                delegates[i].setMenuItemEnabled(item,enabled);
                break;
            }
        }
    }

    /**
     * Returns true if the given menu item is enabled and false
     * otherwise. A disabled menu item is not selectable from the user interface
     * and draws with an inactive or "grayed" look. A menu item is disabled, if
     * it is either directly disabled or any of its parents is disabled.
     * @since 2.2.0
     */
    protected boolean isMenuItemEnabled(IMenuItem item) {
        for ( int i = 0; i < delegates.length; i++ ) {
            if ( delegates[i].rootMenu.containsItem(item,true) ) {
                return delegates[i].isMenuItemEnabled(item);
            }
        }
        return false;
    }

    /**
     * Enables the given menu item if the argument  enabled is true,
     * and disables it otherwise. A disabled menu item is
     * not selectable from the user interface and
     * draws with an inactive or "grayed" look.
     * Disabling a menu item makes all its children not selectable, too.
     * @since 2.2.0
     */
    protected void setMenuItemEnabled(String menuId,boolean enabled) {
        setMenuItemEnabled(getMenu(menuId), enabled);
    }

    /**
     * Returns true if the given menu item is enabled and false
     * otherwise. A disabled menu item is not selectable from the user interface
     * and draws with an inactive or "grayed" look. A menu item is disabled, if
     * it is either directly disabled or any of its parents is disabled.
     * @since 2.2.0
     */
    protected boolean isMenuItemEnabled(String menuId) {
        return isMenuItemEnabled(getMenu(menuId));
    }

    /**
     * Register the given MenuItem at the AppShell.
     * After this, the MenuItem will be attached to
     * its visual representation. It will be visible
     * and events will be delivered.
     * @param item to register
     * @deprecated use {@link AppShellDelegate#registerMenu(IMenuItem)} instead
     */
    public void registerMenu(IMenuItem item) {
        IMenuItem parentItem = item.getParent();
        for ( int i = 0; i < delegates.length; i++ ) {
            if ( parentItem == null || delegates[i].rootMenu.containsItem(parentItem,true) ) {
                delegates[i].registerMenu(item);
                break;
            }
        }
    }

    /**
     * Unregister the given MenuItem fromt the AppShell.
     * After this, the MenuItem will no longer be attached
     * to its visual representation. It will not be
     * visible and no events will be delivered any more.
     * @param item to remove
     * @deprecated use {@link AppShellDelegate#unregisterMenu(IMenuItem)} instead
     */
    public void unregisterMenu(IMenuItem item) {
        IMenuItem parentItem = item.getParent();
        for ( int i = 0; i < delegates.length; i++ ) {
            if ( parentItem == null || delegates[i].rootMenu.containsItem(parentItem,true) ) {
                delegates[i].unregisterMenu(item);
                break;
            }
        }
    }

    /**
     * Calls the action associated which the given MenuItem.
     * Before it is actually called, all currently open Tasks
     * outside the given MenuItem are closed.
     * If there is no Task attached to the given MenuItem, nothing happens.
     * @param item the MenuItem to call.
     */
    public void callMenu(IMenuItem item) {
        if(item.getTask()==null) return; // no task -> nothing to do
        for ( int i = 0; i < delegates.length; i++ ) {
            if ( item == null || delegates[i].rootMenu.containsItem(item,true) ) {
                delegates[i].callMenu(item);
                break;
            }
        }
    }

    /**
     * Calls the action associated which the named MenuItem.
     * Before it is actually called, all currently open Tasks
     * outside the named MenuItem are closed.
     * If there is no Task attached to the named MenuItem, nothing happens.
     * @param menuId the name of the MenuItem to call.
     */
    public void callMenu(String menuId) {
        IMenuItem item = getMenu(menuId);
        if(item==null) {
            throw new IllegalArgumentException("no such menu item: "+menuId);
        } else {
            callMenu(item);
        }
    }

    /**
     * Get the MenuItem registered under the given menuId.
     * If there are more than one MenuItem registered under the same name,
     * the one registerd last is returned.
     * @param menuId the name of the MenuItem you want.
     * @return the MenuItem with the given name or null if not found.
     */
    public IMenuItem getMenu(String menuId) {
        for ( int i = 0; i < delegates.length; i++ ) {
            IMenuItem item = delegates[i].getMenu(menuId); 
            if ( item != null ) {
                return item;
            }
        }
        return null;
    }

    /**
     * Selects the named MenuItem. This method behaves like
     * the user has selected the MenuItem via the GUI.
     * @param menuId the name of the MenuItem to select.
     */
    public void selectMenu(String menuId) {
        for ( int i = 0; i < delegates.length; i++ ) {
            IMenuItem item = delegates[i].getMenu(menuId); 
            if ( item != null ) {
                delegates[i].selectMenu(menuId);
                break;
            }
        }
    }

    /**
     * Visibly marks the named MenuItem as selected.
     * @param menuId the name of the MenuItem to mark as selected.
     */
    public void markMenu(String menuId) {
        for ( int i = 0; i < delegates.length; i++ ) {
            IMenuItem item = delegates[i].getMenu(menuId); 
            if ( item != null ) {
                delegates[i].markMenu(menuId);
                break;
            }
        }
    }

    /**
     * Get the ResourceBundle containing the labels for all menu items.
     * @return the ResourceBundle for the menu.
     */
    abstract protected ResourceBundle getMenuResource();

    /**
     * Method called to start the AppShell.
     * The AppShell will be shown and executed.
     * It blocks until the AppShell is closed.
     * @return true
     */
    public boolean invoke() {
        initGUI();
        for ( int i = 0; i < delegates.length; i++ ) {
            delegates[i].prepare();
        }
        enterBase();
        stateChangedBase();
        updateErrorStatus(getFocusControl());
        getShell().open();
        List livingDelegates = new ArrayList(); 
        for ( int i = 0; i < delegates.length; i++ ) {
            if ( delegates[i].rootTask != null ) {
                delegates[i].rootTask.run();
                if ( delegates[i].rootMenu.getItems().size() > 0 )
                    livingDelegates.add(delegates[i]);
            }
        }
        // so we remove all the delegates witch has no root task and, after executing that task, no menus
        delegates = (AppShellDelegate[]) livingDelegates.toArray(new AppShellDelegate[livingDelegates.size()]); 
        setEventsEnabled(true);
        if ( ! getShell().isDisposed() ) {
            clientEventLoop(this);
        }
        return true;
    }

    /**
     * Get the task on top of the call stack. This is the currently active Task.
     * @deprecated use {@link AppShellDelegate#getTopTask()} instead
     */
    public ITask getTopTask() {
        for ( int i = 0; i < delegates.length; i++ ) {
            ITask t = delegates[i].getTopTask();
            if ( t != null )
                return t;
        }
        return null;
    }

    /**
     * Pushes the given Component or Page on top of the call stack.
     * It is shown embedded in the client area.
     * If modal is true, this method blocks until the Component or Page is
     * closed.
     * @param newClient the component or page to embedd.
     * @param modal if modal is true this method blocks until newClient is finished
     *              if modal if false this method returns immediately.
     * @deprecated use {@link AppShellDelegate#pushClientComponent(IXMAControl, boolean)} instead
     */
    public void pushClientComponent(final IXMAControl newClient, boolean modal) {
        getTopTask().call(new SimpleTask(newClient,modal));
    }

    /**
     * Closes the dialog and all tasks with exitStatus true.
     */
    public void closeOK() {
        closeTasks();
        super.closeOK();
    }

    /**
     * Closes the dialog and all tasks with exitStatus false.
     */
    public void closeCancel() {
        closeTasks();
        super.closeCancel();
    }

    /**
     * Closes the component or page on top of the call stack.
     */
    public void closeTasks () {
        for ( int i = 0; i < delegates.length; i++ ) {
            ITask top = delegates[i].getTopTask();
            if ( top.isRunfinished() ) {
                top.closeRequested(true);
            }
            else {
                top.pageClosed(true);
            }
        }
    }

    /**
     * Notify the AppShell, all Tasks and all Subpages, that the Page no longer is visible.
     */
    public void leaveBase() {
        for ( int i = 0; i < delegates.length; i++ ) {
            for ( ITask top = delegates[i].getTopTask(); top != null; top = top.getParentTask() ) {
                try {
                    if ( top.isRunfinished() ) {
                        top.closeRequested(false);
                    }
                    else {
                        top.pageClosed(false);
                    }
                }
                catch ( Exception exc ) {
                    showException(exc);
                }
            }
        }
        super.leaveBase();
    }

    /**
     * Closes the AppShell.
     */
    public void exit() {
        super.closeCancel();
    }

    /**
     * This method will be called every time the Shell is tried to be closed.
     * It is intended to be implemented by the application programmer if he
     * wants to react on this event. The application programmer my decide if
     * the shell will actually be closed.
     * Per default closing is permitted if the menu is enabled - no blocking Task is active.
     *
     * @return true means it is ok to close the Shell
     *      false means do not close the Shell
     */
    protected boolean close() {
        for ( int i = 0; i < delegates.length; i++ ) {
            if ( ! delegates[i].mayClose() )
                return false;
        }
        return true;
    }


    /**
     * Closes all tasks on the call stack which are outside the given
     * MenuItem. The call stack is rewinded until the task is on top which
     * corresponds to the given MenuItem.
     * @param item the menu item outside which all tasks should be closed.
     * @return true if all this tasks could be successfully closed.
     *          false if a task refused to close.
     * @deprecated use {@link AppShellDelegate#closeTasks(IMenuItem)} instead
     */
    protected boolean closeTasks(IMenuItem item) {
        for ( int i = 0; i < delegates.length; i++ ) {
            if ( delegates[i].rootMenu.containsItem(item,true) )
                return delegates[i].closeTasks(item);
        }
        return false;
    }

    /**
     * Determine if the AppShell still contains widgets. Can be used to
     * test if it is disposed.
     * @return true if it has widgets.
     */
    public boolean hasWidgets() {
        return getWidgets()!=null;
    }

    /**
     * Execute an eventLoop for modal clients. This call blocks until
     * the client is detached from the appshell by calling removeChild().
     * Dispatches all events in the meantime.
     */
    private void clientEventLoop(IXMAControl client) {
        Display display = getShell().getDisplay();
        Composite comp = client.getComposite();
        while (!comp.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    // TODO: comment me
    public void removeWidgetsBase() {
        super.removeWidgetsBase();
        for ( int i = 0; i < delegates.length; i++ ) {
            delegates[i].removeWidgets();
        }
    }

    /**
     * Calls the clientevent configured for the widget of the event by
     * calling {@link #clientEvent(SelectionEvent,int)}. Handles the selection
     * events of the menu-tree, too.
     *
     * @param event the SWT-Event that happened.
     * @param type the type of the Event, as defined by the event type constants in class SWT.
     */
    protected void clientEventBase(SelectionEvent event,int type) {
        super.clientEventBase(event,type);
        for ( int i = 0; i < delegates.length; i++ ) {
            delegates[i].clientEvent(event,type);
        }
    }

    /**
     * Replaces the embedded page or component in the client area. The currently embedded
     * page or component is removed from this AppShell and the new one is shown in
     * the client area.
     * @param newClient the new page or component to embedd.
     * @deprecated use {@link AppShellDelegate#setClientArea(IXMAControl)} instead 
     */
    final public void setClientArea(IXMAControl newClient) {
    }


    /**
     * Removes the embedded page or componet form the client area.
     * Nothing is embedded afterwards.
     * @deprecated use {@link AppShellDelegate#clearClientArea()} instead 
     */
    final public void clearClientArea() {
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy