/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.core.windows;


import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;

import org.netbeans.core.windows.model.DockingStatus;
import org.netbeans.core.windows.model.Model;
import org.netbeans.core.windows.model.ModelElement;
import org.netbeans.core.windows.model.ModelFactory;
import org.netbeans.core.windows.options.WinSysPrefs;
import org.netbeans.core.windows.view.ControllerHandler;
import org.netbeans.core.windows.view.View;
import org.netbeans.core.windows.view.dnd.TopComponentDraggable;
import org.openide.windows.Mode;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;



/**
 * This class is a central unit of window system. It controls communication
 * flows to model, to view, from controller and from API calls.
 *
 * @author Peter Zavadsky
 */
final class Central implements ControllerHandler {
    
    /** Model of window system. */
    private final Model model = ModelFactory.createWindowSystemModel();
    
    /** Helper class for managing requests to view. */
    private final ViewRequestor viewRequestor = new ViewRequestor(this);
    
    private ModeImpl modeBeingMaximized = null;
    
    /** Constructor. */
    public Central() {
    }

    
    public void topComponentRequestAttention (ModeImpl mode, TopComponent tc) {
        String modeName = getModeName(mode);
        viewRequestor.scheduleRequest (
            new ViewRequest(modeName, View.TOPCOMPONENT_REQUEST_ATTENTION, tc, tc));
    }

    public void topComponentCancelRequestAttention (ModeImpl mode, TopComponent tc) {
        String modeName = getModeName(mode);
        viewRequestor.scheduleRequest (
            new ViewRequest(modeName, View.TOPCOMPONENT_CANCEL_REQUEST_ATTENTION, tc, tc));
    }

    /**
     * Turns tab highlight on/off
     * @param mode
     * @param tc
     * @param highlight 
     * @since 2.54
     */
    public void topComponentAttentionHighlight (ModeImpl mode, TopComponent tc, boolean highlight) {
        String modeName = getModeName(mode);
        viewRequestor.scheduleRequest (
            new ViewRequest(modeName, highlight ? View.TOPCOMPONENT_ATTENTION_HIGHLIGHT_ON
                                                : View.TOPCOMPONENT_ATTENTION_HIGHLIGHT_OFF, tc, tc));
    }
    
    /////////////////////
    // Mutators >>
    /** Sets visible or invisible window system and requests view accordingly. */
    public void setVisible(boolean visible) {
        if(isVisible() == visible) {
            return;
        }
        
        model.setVisible(visible);
        
        viewRequestor.scheduleRequest(
            new ViewRequest(null, View.CHANGE_VISIBILITY_CHANGED, null, Boolean.valueOf(visible)));
    }
    
    /** Sets main window bounds (joined[tiled] state) into model and requests view (if needed). */
    public void setMainWindowBoundsJoined(Rectangle mainWindowBoundsJoined) {
        if(mainWindowBoundsJoined == null) {
            return;
        }
        
        Rectangle old = getMainWindowBoundsJoined();
        if(old.equals(mainWindowBoundsJoined)) {
            return;
        }
        
        model.setMainWindowBoundsJoined(mainWindowBoundsJoined);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_MAIN_WINDOW_BOUNDS_JOINED_CHANGED,
                    old, mainWindowBoundsJoined));
        }
    }
    
    /** Sets main window bounds (separated state) into model and requests view (if needed). */
    public void setMainWindowBoundsSeparated(Rectangle mainWindowBoundsSeparated) {
        if(mainWindowBoundsSeparated == null) {
            return;
        }
        
        Rectangle old = getMainWindowBoundsSeparated();
        if(old.equals(mainWindowBoundsSeparated)) {
            return;
        }
        
        model.setMainWindowBoundsSeparated(mainWindowBoundsSeparated);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_MAIN_WINDOW_BOUNDS_SEPARATED_CHANGED,
                    old, mainWindowBoundsSeparated));
        }
    }
    
    public void setMainWindowFrameStateJoined(int frameState) {
        int old = getMainWindowFrameStateJoined();
        if(old == frameState) {
            return;
        }
        
        model.setMainWindowFrameStateJoined(frameState);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(null, View.CHANGE_MAIN_WINDOW_FRAME_STATE_JOINED_CHANGED,
                Integer.valueOf(old), Integer.valueOf(frameState)));
        }
    }
    
    public void setMainWindowFrameStateSeparated(int frameState) {
        int old = getMainWindowFrameStateSeparated();
        if(old == frameState) {
            return;
        }
        
        model.setMainWindowFrameStateSeparated(frameState);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(null, View.CHANGE_MAIN_WINDOW_FRAME_STATE_SEPARATED_CHANGED,
                Integer.valueOf(old), Integer.valueOf(frameState)));
        }
    }
    
    /** Sets active mode into model and requests view (if needed). */
    public void setActiveMode(final ModeImpl activeMode) {
        //#87843: Do not activate mode when it does not contain any opened TC
        if (activeMode != null) {
            List<TopComponent> l = activeMode.getOpenedTopComponents();
            if (l.isEmpty()) {
                return;
            }
        }
        
        final ModeImpl old = getActiveMode();
        if(activeMode == old) {
            // kind of workaround to the scenario when a window slides out automatically
            // and user clicks in the currently active mode, not allow to exit in such case and fire changes to
            // force the slided-out window to disappear.
            ModeImpl impl = model.getSlidingMode(Constants.BOTTOM);
            boolean bottom = (impl == null || impl.getSelectedTopComponent() == null);
            impl = model.getSlidingMode(Constants.LEFT);
            boolean left = (impl == null || impl.getSelectedTopComponent() == null);
            impl = model.getSlidingMode(Constants.RIGHT);
            boolean right = (impl == null || impl.getSelectedTopComponent() == null);
            impl = model.getSlidingMode(Constants.TOP);
            boolean top = (impl == null || impl.getSelectedTopComponent() == null);
            if (bottom && left && right && top) {
                return;
            }
        }
        
        model.setActiveMode(activeMode);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_ACTIVE_MODE_CHANGED,
                    old, activeMode));
        }

        
        
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManagerImpl.PROP_ACTIVE_MODE, old, activeMode);
        
        // Notify registry.
        // active mode can be null, Active mode info is stored in winsys config (system layer) and modes in
        // project layer, that can cause out of synch state when switching projects.
        // all subsequent calls should handle the null value correctly.
        if (activeMode != null) {
            WindowManagerImpl.notifyRegistryTopComponentActivated(
            activeMode.getSelectedTopComponent());
        } else {
            WindowManagerImpl.notifyRegistryTopComponentActivated(null);
        }
    }

    /** Sets editor area bounds into model and requests view (if needed). */
    public void setEditorAreaBounds(Rectangle editorAreaBounds) {
        if(editorAreaBounds == null) {
            return;
        }
        
        Rectangle old = getEditorAreaBounds();
        if(old.equals(editorAreaBounds)) {
            return;
        }
        
        model.setEditorAreaBounds(editorAreaBounds);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_EDITOR_AREA_BOUNDS_CHANGED,
                            old, editorAreaBounds));
        }
    }

    /** Sets editor area constraints into model and requests view (if needed). */
    public void setEditorAreaConstraints(SplitConstraint[] editorAreaConstraints) {
        SplitConstraint[] old = getEditorAreaConstraints();
        if(Arrays.equals(old, editorAreaConstraints)) {
            return;
        }
        
        model.setEditorAreaConstraints(editorAreaConstraints);

        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_EDITOR_AREA_CONSTRAINTS_CHANGED,
                                old, editorAreaConstraints));
        }
    }

    /** Sets editor area state into model and requests view (if needed). */
    public void setEditorAreaState(int editorAreaState) {
        int old = getEditorAreaState();
        if(editorAreaState == old) {
            return;
        }
        
        int requiredState = editorAreaState == Constants.EDITOR_AREA_JOINED
                                                ? Constants.MODE_STATE_JOINED
                                                : Constants.MODE_STATE_SEPARATED;
                                                
        for(Iterator it = getModes().iterator(); it.hasNext(); ) {
            ModeImpl mode = (ModeImpl)it.next();
            if(mode.getKind() == Constants.MODE_KIND_VIEW
            && mode.getState() != requiredState) {
                model.setModeState(mode, requiredState);
                // Adjust bounds if necessary.
                if(editorAreaState == Constants.EDITOR_AREA_SEPARATED) {
                    Rectangle bounds = model.getModeBounds(mode);
                    if(bounds.isEmpty()) {
                        model.setModeBounds(mode, model.getModeBoundsSeparatedHelp(mode));
                    }
                }
            }
            // when switching to SDI, undock sliding windows
            // #51992 -start
            if (mode.getKind() == Constants.MODE_KIND_SLIDING && editorAreaState == Constants.EDITOR_AREA_SEPARATED) {
                TopComponent[] tcs = mode.getTopComponents();
                for (int i = 0; i < tcs.length;i++) {
                    String tcID = WindowManagerImpl.getInstance().findTopComponentID(tcs[i]);
                    ModeImpl targetMode = model.getModeTopComponentPreviousMode(mode, tcID);
                    if ((targetMode == null) || !model.getModes().contains(targetMode)) {
                        SplitConstraint[] constraints = model.getModeTopComponentPreviousConstraints(mode, tcID);
                        constraints = constraints == null ? new SplitConstraint[0] : constraints;
                        // create mode to dock topcomponent back into
                        targetMode = WindowManagerImpl.getInstance().createModeImpl(
                            ModeImpl.getUnusedModeName(), Constants.MODE_KIND_VIEW, false);
                        model.setModeState(targetMode, requiredState);
                        model.addMode(targetMode, constraints);
                    }
                    moveTopComponentIntoMode(targetMode, tcs[i] );                    
                }
            }
            // #51992 - end
        }
                                                
        if(editorAreaState == Constants.EDITOR_AREA_SEPARATED) {
            Rectangle editorAreaBounds = model.getEditorAreaBounds();
            // Adjust bounds if necessary.
            if(editorAreaBounds.isEmpty()) {
                model.setEditorAreaBounds(model.getEditorAreaBoundsHelp());
            }
            
            // Adjust bounds if necessary.
            Rectangle mainWindowBoundsSeparated = model.getMainWindowBoundsSeparated();
            if(mainWindowBoundsSeparated.isEmpty()) {
                model.setMainWindowBoundsSeparated(model.getMainWindowBoundsSeparatedHelp());
            }
        }
        
        model.setEditorAreaState(editorAreaState);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_EDITOR_AREA_STATE_CHANGED,
                        Integer.valueOf(old), Integer.valueOf(editorAreaState)));
        }
        
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManagerImpl.PROP_EDITOR_AREA_STATE, Integer.valueOf(old), Integer.valueOf(editorAreaState));
    }

    public void setEditorAreaFrameState(int frameState) {
        int old = getEditorAreaFrameState();
        if(old == frameState) {
            return;
        }
        model.setEditorAreaFrameState(frameState);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(null, View.CHANGE_EDITOR_AREA_FRAME_STATE_CHANGED,
                Integer.valueOf(old), Integer.valueOf(frameState)));
        }
    }
    
    /** Sets new maximized mode into model and requests view update (if needed). */
    void switchMaximizedMode(ModeImpl newMaximizedMode) {
        ModeImpl old = getCurrentMaximizedMode();
        if(newMaximizedMode == old) {
            return;
        }

        WindowManagerImpl wm = WindowManagerImpl.getInstance();
        if( null == newMaximizedMode ) {
            //cancel current maximized mode
            if( isViewMaximized() ) {
                //some non-editor TopComponent is currently maximized
                
                //restore docking status of other components
                if( isEditorMaximized() ) {
                    restoreViews( model.getMaximizedDockingStatus() );
                } else {
                    restoreViews( model.getDefaultDockingStatus() );
                }
                
                //return the maximized TopComponent to its original mode
                ModeImpl currentMaximizedMode = getViewMaximizedMode();
                if( currentMaximizedMode.getTopComponents().length == 1 ) {
                    TopComponent maximizedTC = currentMaximizedMode.getTopComponents()[0];
                    String tcID = wm.findTopComponentID( maximizedTC );
                    //find the mode where the TopComponent was before its maximization
                    ModeImpl prevMode = getModeTopComponentPreviousMode( tcID, currentMaximizedMode );
                    int prevIndex = model.getModeTopComponentPreviousIndex( currentMaximizedMode, tcID );
                    if( null == prevMode ) {
                        //TODO log a warning here because we somehow lost the previous mode
                        if ((prevMode == null) || !model.getModes().contains(prevMode)) {
                            // mode to return to isn't valid anymore, try constraints
                            SplitConstraint[] constraints = model.getModeTopComponentPreviousConstraints(currentMaximizedMode, tcID);
                            if (constraints != null) {
                                // create mode with the same constraints to dock topcomponent back into
                                prevMode = WindowManagerImpl.getInstance().createModeImpl(
                                        ModeImpl.getUnusedModeName(), Constants.MODE_KIND_VIEW, false);
                                model.addMode(prevMode, constraints);
                            }
                        }

                        if (prevMode == null) {
                            // fallback, previous saved mode not found somehow, use default modes
                            prevMode = WindowManagerImpl.getInstance().getDefaultViewMode();
                        }
                    }
                    prevMode.addOpenedTopComponent( maximizedTC, prevIndex );
                    prevMode.setSelectedTopComponent( maximizedTC );
                    setActiveMode(prevMode);
                    model.removeMode( currentMaximizedMode );
                } else {
                    Logger.getLogger( Central.class.getName() ).log( Level.WARNING, 
                            "A 'view' mode is maximized but it has wrong number of TopComponents, Mode=[" 
                            + currentMaximizedMode.getName() + "], TC count=" + currentMaximizedMode.getTopComponents().length );
                }
                //cancel the maximized mode
                setViewMaximizedMode( null );
                
            } else if( isEditorMaximized() ) {
                //an editor TopComponent is maximized
                model.getMaximizedDockingStatus().mark();
                ModeImpl prevActiveMode = getActiveMode();
                //restore the docking status of other components
                restoreViews( model.getDefaultDockingStatus() );
                
                //cancel the maximized mode
                setEditorMaximizedMode( null );
                
                setActiveMode( prevActiveMode );
                
            }
        } else {
            assert !isViewMaximized();
            
            //set new maximized mode
            if( newMaximizedMode.getKind() == Constants.MODE_KIND_EDITOR ) {
                //the new maximized mode is an editor TopComponent
                
                //remember the current docking status of opened TopComponents
                model.getDefaultDockingStatus().mark();
                //slide-out/dock some TopComponents according to their previous state in maximized mode
                restoreViews( model.getMaximizedDockingStatus() );

                setEditorMaximizedMode( newMaximizedMode );

            } else if( newMaximizedMode.getKind() == Constants.MODE_KIND_VIEW ) {
                //the new maximized mode is non-editor TopComponent

                //get the TopComponent that will be maximized
                TopComponent tcToMaximize = newMaximizedMode.getSelectedTopComponent();
                if( null == tcToMaximize ) {
                    if( newMaximizedMode.getOpenedTopComponents().isEmpty() ) {
                        return;
                    }
                    tcToMaximize = newMaximizedMode.getOpenedTopComponents().get(0);
                }
                
                //remember the docking status of opened components
                if( isEditorMaximized() ) {
                    model.getMaximizedDockingStatus().mark();
                } else {
                    model.getDefaultDockingStatus().mark();
                }
                
                modeBeingMaximized = newMaximizedMode;

                String tcID = wm.findTopComponentID( tcToMaximize );
                int prevIndex = newMaximizedMode.getOpenedTopComponents().indexOf( tcToMaximize );

                //create a new mode for the maximization
                ModeImpl mode = WindowManagerImpl.getInstance().createModeImpl(ModeImpl.getUnusedModeName(), Constants.MODE_KIND_VIEW, true);
                model.addMode(mode, new SplitConstraint[0]);
                //the mode has just one TopComponent
                mode.addOpenedTopComponent( tcToMaximize );
                mode.setSelectedTopComponent( tcToMaximize );
                //remember where to put the TopComponent back when un-maximizing
                setModeTopComponentPreviousMode( tcID, mode, newMaximizedMode, prevIndex );

                setViewMaximizedMode( mode );
                
                //slide-out all other TopComponents (the editor area won't be visible)
                slideAllViews();
                
                setActiveMode( mode );

                modeBeingMaximized = null;
            } else {
                throw new IllegalArgumentException( "Cannot maximize a sliding view" );
            }
        }

        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_MAXIMIZED_MODE_CHANGED,
                    old, getCurrentMaximizedMode()));
        }
        
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManagerImpl.PROP_MAXIMIZED_MODE, old, getCurrentMaximizedMode());
    }

    /** Sets editor mode that is currenlty maximized (used when the window system loads) */
    void setEditorMaximizedMode(ModeImpl editorMaximizedMode) {
        model.setEditorMaximizedMode( editorMaximizedMode );
    }
    
    /** Sets view mode that is currenlty maximized (used when the window system loads) */
    void setViewMaximizedMode(ModeImpl viewMaximizedMode) {
        model.setViewMaximizedMode( viewMaximizedMode );
    }
    
    /** Sets constraints for mode into model and requests view (if needed). */
    public void setModeConstraints(ModeImpl mode, SplitConstraint[] modeConstraints) {
        SplitConstraint[] old = getModeConstraints(mode);
        if(Arrays.equals(modeConstraints, old)) {
            return;
        }
        
        model.setModeConstraints(mode, modeConstraints);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_MODE_CONSTRAINTS_CHANGED,
                        old, modeConstraints));
        }
    }

    /** Adds mode into model and requests view (if needed). */
    public void addMode(ModeImpl mode, SplitConstraint[] modeConstraints) {
        // PENDING which one to use?
//        if(getModes().contains(mode)) {
//            return;
//        }
        SplitConstraint[] old = getModeConstraints(mode);
        if(modeConstraints == old) {
            return;
        }
        
        model.addMode(mode, modeConstraints);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_MODE_ADDED, null, mode));
        }
        
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManager.PROP_MODES, null, null);
    }
    
    final void setModeName(ModeImpl mode, String text) {
        model.setModeName(mode, text);
    }

    /** Removes mode from model and requests view (if needed). */
    public void removeMode(ModeImpl mode) {
        if(!getModes().contains(mode)) {
            return;
        }
//        debugLog("removeMode()=" + mode.getDisplayName());
        model.removeMode(mode);
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_MODE_REMOVED, null, mode));
        }
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManager.PROP_MODES, null, null);
    }

    /** Sets toolbar configuration name and requests view (if needed). */
    public void setToolbarConfigName(String toolbarConfigName) {
        String old = getToolbarConfigName();
        if(old.equals(toolbarConfigName)) {
            return;
        }
            
        model.setToolbarConfigName(toolbarConfigName);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_TOOLBAR_CONFIGURATION_CHANGED,
                            old, toolbarConfigName));
        }
    }

    /** Updates UI. */
    public void updateUI() {
        // Pure request, no model change.
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(null, View.CHANGE_UI_UPDATE, null, null));
        }
    }
    
    ////////////////////////////
    // Mode specific >>
    private void closeMode(ModeImpl mode) {
        if(mode == null) {
            return;
        }
        
        TopComponent[] tcs = getModeOpenedTopComponents(mode).toArray(new TopComponent[0]);
        
        for (int i = 0; i < tcs.length; i++) {
            if (PersistenceHandler.isTopComponentPersistentWhenClosed(tcs[i])) {
                model.addModeClosedTopComponent(mode, tcs[i]);
            } else {
                if (Boolean.TRUE.equals(tcs[i].getClientProperty(Constants.KEEP_NON_PERSISTENT_TC_IN_MODEL_WHEN_CLOSED))) {
                    model.addModeClosedTopComponent(mode, tcs[i]);
                } else {
                    model.removeModeTopComponent(mode, tcs[i], null);
                }
            }
        }
        
        ModeImpl oldActive = getActiveMode();
        ModeImpl newActive;
        if(mode == oldActive) {
            newActive = setSomeModeActive();
        } else {
            newActive = oldActive;
        }
//        debugLog("closeMode()");
        
        // Remove mode from model if is not permanennt and emptied.
        boolean modeRemoved = false;
        if(!mode.isPermanent() && model.getModeTopComponents(mode).isEmpty()) {
            // only if no sliding modes' tc points to this mode, then it's ok to remove it.
            if (doCheckSlidingModes(mode)) {
//                debugLog("do close mode=" + mode.getDisplayName());
                model.removeMode(mode);
                modeRemoved = true;
            }
        }
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(mode, View.CHANGE_MODE_CLOSED, null, null));
        }

        // Notify closed.
        for(int i = 0; i < tcs.length; i++) {
            // Notify TopComponent was closed.
            WindowManagerImpl.getInstance().notifyTopComponentClosed(tcs[i]);
        }
        
        if(oldActive != newActive) {
            WindowManagerImpl.getInstance().doFirePropertyChange(
                WindowManagerImpl.PROP_ACTIVE_MODE, oldActive, newActive);
        }
    
        if(modeRemoved) {
            WindowManagerImpl.getInstance().doFirePropertyChange(
                WindowManager.PROP_MODES, null, null);
        }
        
        // Notify new active.
        if(newActive != null) {
            // Notify registry.
            WindowManagerImpl.notifyRegistryTopComponentActivated(
                newActive.getSelectedTopComponent());
        } else {
            WindowManagerImpl.notifyRegistryTopComponentActivated(null);
        }
    }
    
    // XXX TODO Model should handle this on its own.
    private ModeImpl setSomeModeActive() {
        for(Iterator it = getModes().iterator(); it.hasNext(); ) {
            ModeImpl mode = (ModeImpl)it.next();
            if(!mode.getOpenedTopComponents().isEmpty() && Constants.MODE_KIND_SLIDING != mode.getKind()) {
                model.setActiveMode(mode);
                return mode;
            }
        }
        model.setActiveMode(null);
        return model.getActiveMode();
    }

    
    /** Sets bounds into model and requests view (if needed). */
    public void setModeBounds(ModeImpl mode, Rectangle bounds) {
        if(bounds == null) {
            return;
        }
        
        Rectangle old = getModeBounds(mode);
        if(old.equals(bounds)) {
            return;
        }
        
        model.setModeBounds(mode, bounds);
        
        if(isVisible() && getEditorAreaState() == Constants.EDITOR_AREA_SEPARATED) {
            viewRequestor.scheduleRequest(new ViewRequest(
                mode, View.CHANGE_MODE_BOUNDS_CHANGED, old, bounds));
        }
        
        mode.doFirePropertyChange(ModeImpl.PROP_BOUNDS, old, bounds);
    }
    
    /** Sets frame state. */
    public void setModeFrameState(ModeImpl mode, int frameState) {
        int old = getModeFrameState(mode);
        if(frameState == old) {
            return;
        }
        
        model.setModeFrameState(mode, frameState);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(
                mode, View.CHANGE_MODE_FRAME_STATE_CHANGED,
                Integer.valueOf(old), Integer.valueOf(frameState)));
        }
    }
    
    /** Sets seleted TopComponent into model and requests view (if needed). */
    public void setModeSelectedTopComponent(ModeImpl mode, TopComponent selected) {
        // don't apply check for sliding kind when clearing selection to null
        if (mode.getKind() != Constants.MODE_KIND_SLIDING || selected != null) {
            if(!getModeOpenedTopComponents(mode).contains(selected)) {
                return;
            }
        }
        
        TopComponent old = getModeSelectedTopComponent(mode);
        if(selected == old) {
            return;
        }
        
        model.setModeSelectedTopComponent(mode, selected);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(mode, View.CHANGE_MODE_SELECTED_TOPCOMPONENT_CHANGED,
                old, selected));
        }
        
        // Notify registry.
        if(mode == getActiveMode()) {
            WindowManagerImpl.notifyRegistryTopComponentActivated(selected);
        }
    }
    
    /**
     * Remember which TopComponent was previously the selected one, used when switching to/from maximized mode.
     * 
     * @param mode 
     * @param tcId ID of TopComponent that was previously selected.
     */
    public void setModePreviousSelectedTopComponentID(ModeImpl mode, String tcId) {
        model.setModePreviousSelectedTopComponentID( mode, tcId );
    }

    /** Adds opened TopComponent into model and requests view (if needed). */
    public void addModeOpenedTopComponent(ModeImpl mode, TopComponent tc) {
        boolean wasOpened = tc.isOpened();
        if(getModeOpenedTopComponents(mode).contains(tc)) {
            return;
        }

        // Validate the TopComponent was removed from other modes.
        removeTopComponentFromOtherModes(mode, tc);

        model.addModeOpenedTopComponent(mode, tc);

        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(mode, View.CHANGE_MODE_TOPCOMPONENT_ADDED,
                null, tc));
        }

        if( !wasOpened ) { //make sure componentOpened() is called just once
            // Notify opened.
            WindowManagerImpl.getInstance().notifyTopComponentOpened(tc);
        }
    }

    /** Adds opened TopComponent into model and requests view (if needed). */
    void addModeOpenedTopComponentNoNotify(ModeImpl mode, TopComponent tc) {
        if(getModeOpenedTopComponents(mode).contains(tc)) {
            return;
        }

        // Validate the TopComponent was removed from other modes.
        removeTopComponentFromOtherModes(mode, tc);

        model.addModeOpenedTopComponent(mode, tc);

        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(mode, View.CHANGE_MODE_TOPCOMPONENT_ADDED,
                null, tc));
        }
    }
    
    public void insertModeOpenedTopComponent(ModeImpl mode, TopComponent tc, int index) {
        boolean wasOpened = tc.isOpened();
        List openedTcs = getModeOpenedTopComponents(mode);
        if(index >= 0 && !openedTcs.isEmpty()
        && openedTcs.size() > index && openedTcs.get(index) == tc) {
            return;
        }
        
        // Validate the TopComponent was removed from other modes.
        removeTopComponentFromOtherModes(mode, tc);
        
        model.insertModeOpenedTopComponent(mode, tc, index);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(mode, View.CHANGE_MODE_TOPCOMPONENT_ADDED, // PENDING inserted?
                null, tc));
        }
        
        if( !wasOpened ) { //make sure componentOpened() is called just once
            // #102258: Notify opened when opened through openAtTabPosition as well
            WindowManagerImpl.getInstance().notifyTopComponentOpened(tc);
        }
    }
    
    public boolean addModeClosedTopComponent(ModeImpl mode, TopComponent tc) {
        boolean opened = getModeOpenedTopComponents(mode).contains(tc);
        
        if(opened && !tc.canClose()) {
            return false;
        }
        
        if(containsModeTopComponent(mode,tc) && !opened) {
            return false;
        }
        
        if( isViewMaximized() && mode.getKind() == Constants.MODE_KIND_SLIDING ) {
            //134622 - unslide first if some other view is maximized, otherwise
            //the view being closed will reopen in slidebar after restoring from maximized mode
            mode = unSlide(tc, mode);
        }
        // Validate the TopComponent was removed from other modes.
        removeTopComponentFromOtherModes(mode, tc);
        
        model.addModeClosedTopComponent(mode, tc);

        ModeImpl oldActive = getActiveMode();
        ModeImpl newActive;
        if(model.getModeOpenedTopComponents(mode).isEmpty() && mode == oldActive) {
            newActive = setSomeModeActive();
        } else {
            newActive = oldActive;
        }

        // Unmaximize mode if necessary.
        if(getCurrentMaximizedMode() == mode && model.getModeOpenedTopComponents(mode).isEmpty()) {
            switchMaximizedMode(null);
        }
        
        if(isVisible() && opened) {
            viewRequestor.scheduleRequest(
                new ViewRequest(mode, View.CHANGE_MODE_TOPCOMPONENT_REMOVED,
                null, tc));
        }
        
        if(oldActive != newActive) {
            WindowManagerImpl.getInstance().doFirePropertyChange(
                WindowManagerImpl.PROP_ACTIVE_MODE, oldActive, newActive);
        }

        if(newActive != null) {
            // Notify registry.
            WindowManagerImpl.notifyRegistryTopComponentActivated(
                newActive.getSelectedTopComponent());
        } else {
            WindowManagerImpl.notifyRegistryTopComponentActivated(null);
        }
        
        // Notify closed.
        if(opened) {
            WindowManagerImpl.getInstance().notifyTopComponentClosed(tc);
        }
        return true;
    }

    // XXX Could be called only during load phase of window system.
    public void addModeUnloadedTopComponent(ModeImpl mode, String tcID, int index) {
        TopComponentTracker.getDefault().add( tcID, mode );
        model.addModeUnloadedTopComponent(mode, tcID, index);
    }
    
    // XXX
    public void setUnloadedSelectedTopComponent(ModeImpl mode, String tcID) {
        model.setModeUnloadedSelectedTopComponent(mode, tcID);
    }
    
    public void setUnloadedPreviousSelectedTopComponent(ModeImpl mode, String tcID) {
        model.setModeUnloadedPreviousSelectedTopComponent(mode, tcID);
    }

    // XXX
    public List<String> getModeOpenedTopComponentsIDs(ModeImpl mode) {
        return model.getModeOpenedTopComponentsIDs(mode);
    }
    // XXX
    public List getModeClosedTopComponentsIDs(ModeImpl mode) {
        return model.getModeClosedTopComponentsIDs(mode);
    }
    // XXX
    public List<String> getModeTopComponentsIDs(ModeImpl mode) {
        return model.getModeTopComponentsIDs(mode);
    }
    
    /** Helper validation. */
    private boolean removeTopComponentFromOtherModes(ModeImpl mode, TopComponent tc) {
        boolean tcRemoved = false;
        for(Iterator it = model.getModes().iterator(); it.hasNext(); ) {
            ModeImpl m = (ModeImpl)it.next();
            if(m == mode) {
                continue;
            }
            
            if(model.containsModeTopComponent(m, tc)) {
                tcRemoved = true;
                model.removeModeTopComponent(m, tc, null);
//                debugLog("removeTopComponentFromOtherModes()");

                // Remove mode from model if is not permanennt and emptied.
                boolean modeRemoved = false;
                if(!m.isPermanent() && m.isEmpty() && doCheckSlidingModes(m) 
                    // now the tc is not added to the sliding mode yet, but is *somehow* expected to be..
                    // maybe needs redesign..
                        && mode.getKind() != Constants.MODE_KIND_SLIDING
                        //do not let remove a mode whose only TC is just being maximized
                        && m != modeBeingMaximized ) {
//                    debugLog("removeTopComponentFromOtherModes() - really removing=" + m.getDisplayName());
                    model.removeMode(m);
                    modeRemoved = true;
                }
            
                if(modeRemoved) {
                    WindowManagerImpl.getInstance().doFirePropertyChange(
                        WindowManager.PROP_MODES, null, null);
                }
            }
        }
        
        return tcRemoved;
    }
    
    /** Removed top component from model and requests view (if needed). */
    public boolean removeModeTopComponent(ModeImpl mode, TopComponent tc) {
        if(!containsModeTopComponent(mode, tc)) {
            return false;
        }
        
        boolean viewChange = getModeOpenedTopComponents(mode).contains(tc);
        
        if(viewChange && !tc.canClose()) {
            return false;
        }
        
        TopComponent recentTc = null;
        if( mode.getKind() == Constants.MODE_KIND_EDITOR ) {
            //an editor document is being closed so let's find the most recent editor to select
            recentTc = getRecentTopComponent( mode, tc );
        }
        model.removeModeTopComponent(mode, tc, recentTc);

        ModeImpl oldActive = getActiveMode();
        ModeImpl newActive;
        if(model.getModeOpenedTopComponents(mode).isEmpty() && mode == oldActive) {
            newActive = setSomeModeActive();
        } else {
            newActive = oldActive;
        }

        // Unmaximize mode if necessary.
        if(getCurrentMaximizedMode() == mode && model.getModeOpenedTopComponents(mode).isEmpty()) {
            switchMaximizedMode(null);
        }
        
//        debugLog("removeModeTopComponent()");
        // Remove mode from model if is not permanennt and emptied.
        boolean modeRemoved = false;
        if(!mode.isPermanent() && model.getModeTopComponents(mode).isEmpty()) {
            // remove only if there's no other component in sliding modes that has this one as the previous mode.
            //TODO
            if (doCheckSlidingModes(mode)) {
//                debugLog("removeModeTopComponent() -removing " + mode.getDisplayName());
                model.removeMode(mode);
                modeRemoved = true;
            }
        }
        
        
        
        if(viewChange && isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(
                mode, View.CHANGE_MODE_TOPCOMPONENT_REMOVED,
                null, tc));
        }
        
        // Notify closed.
        if(viewChange) {
            WindowManagerImpl.getInstance().notifyTopComponentClosed(tc);
        }
        
        if(oldActive != newActive) {
            WindowManagerImpl.getInstance().doFirePropertyChange(
                WindowManagerImpl.PROP_ACTIVE_MODE, oldActive, newActive);
        }
    
        if(modeRemoved) {
            WindowManagerImpl.getInstance().doFirePropertyChange(
                WindowManager.PROP_MODES, null, null);
        }
            
        if(newActive != null) {
            // Notify registry.
            WindowManagerImpl.notifyRegistryTopComponentActivated(
                newActive.getSelectedTopComponent());
        } else {
            WindowManagerImpl.notifyRegistryTopComponentActivated(
                null);
        }
        return true;
    }
    
    /**
     * Find TopComponent to be selected when the currently selected TC is closed
     * in the given mode.
     * @param editorMode Editor mode
     * @param closedTc TopComponent which is being closed in the given mode.
     * @return TopComponent to select or null (e.g. the mode will be empty after close)
     */
    TopComponent getRecentTopComponent( ModeImpl editorMode, TopComponent closedTc ) {
        WindowManagerImpl wm = WindowManagerImpl.getInstance();
        if (!WinSysPrefs.HANDLER.getBoolean(WinSysPrefs.EDITOR_CLOSE_ACTIVATES_RECENT, true)) {
            List<TopComponent> open = editorMode.getOpenedTopComponents();
            int pos = open.indexOf(closedTc);
            if (open.size() > 1 && pos >= 0) {
                if (pos > 0) { // select previous one
                    return open.get(pos - 1);
                } else { // this was first, so select next one
                    return open.get(1);
                }
            }
        }
        String[] ids = wm.getRecentViewIDList();
        
        for( String tcId : ids ) {
            ModeImpl mode = findMode(tcId);
            if (mode == null 
               || mode != editorMode) {
                continue;
            }
            TopComponent tc = wm.findTopComponent( tcId );
            if( tc == closedTc )
                continue;
            return tc;
        }
        return null;
    }

    private ModeImpl findMode( String tcId ) {
        for( ModeImpl mode : getModes() ) {
            if( mode.getTopComponentsIDs().contains( tcId ) )
                return mode;
        }
        return null;
    }
    
   // remove the mode only if there's no other component in sliding modes that has this one as the previous mode.
    boolean doCheckSlidingModes(ModeImpl mode) {
        ModeImpl slid = model.getSlidingMode(Constants.BOTTOM);
        if (slid != null) {
            TopComponent[] tcs = slid.getTopComponents();
            for (int i = 0; i < tcs.length; i++) {
                String tcID = WindowManagerImpl.getInstance().findTopComponentID(tcs[i]);
                ModeImpl impl = model.getModeTopComponentPreviousMode(slid, tcID);
                if (impl == mode) {
                    return false;
                }
            }
        }
        slid = model.getSlidingMode(Constants.LEFT);
        if (slid != null) {
            TopComponent[] tcs = slid.getTopComponents();
            for (int i = 0; i < tcs.length; i++) {
                String tcID = WindowManagerImpl.getInstance().findTopComponentID(tcs[i]);
                ModeImpl impl = model.getModeTopComponentPreviousMode(slid, tcID);
                if (impl == mode) {
                    return false;
                }
            }
        }
        slid = model.getSlidingMode(Constants.RIGHT);
        if (slid != null) {
            TopComponent[] tcs = slid.getTopComponents();
            for (int i = 0; i < tcs.length; i++) {
                String tcID = WindowManagerImpl.getInstance().findTopComponentID(tcs[i]);
                ModeImpl impl = model.getModeTopComponentPreviousMode(slid, tcID);
                if (impl == mode) {
                    return false;
                }
            }
        }        
        slid = model.getSlidingMode(Constants.TOP);
        if (slid != null) {
            TopComponent[] tcs = slid.getTopComponents();
            for (int i = 0; i < tcs.length; i++) {
                String tcID = WindowManagerImpl.getInstance().findTopComponentID(tcs[i]);
                ModeImpl impl = model.getModeTopComponentPreviousMode(slid, tcID);
                if (impl == mode) {
                    return false;
                }
            }
        }        
        return true;
    }
    
    // XXX
    public void removeModeClosedTopComponentID(ModeImpl mode, String tcID) {
        // It is silent now, has to be used only for closed yet unloaded components!
        model.removeModeClosedTopComponentID(mode, tcID);
    }
    /// << Mode specific    
    //////////////////////////////
    
    // TopComponentGroup>>
    public boolean isGroupOpened(TopComponentGroupImpl tcGroup) {
        return model.isGroupOpened(tcGroup);
    }
    
    /** Opens TopComponentGroup. */
    public void openGroup(TopComponentGroupImpl tcGroup) {
        if(isGroupOpened(tcGroup)) {
            return;
        }
        
        if( isEditorMaximized() && isViewMaximized() )
            switchMaximizedMode( null );

        Set<TopComponent> openedBeforeTopComponents = new HashSet<TopComponent>();
        Set<TopComponent> tcs = tcGroup.getTopComponents();
        for(Iterator<TopComponent> it = tcs.iterator(); it.hasNext(); ) {
            TopComponent tc = it.next();
            if( tc.isOpened() ) {
                openedBeforeTopComponents.add( tc );
            }
        }
        
        tcs = tcGroup.getOpeningSet();
        HashSet<ModeImpl> openedModes = new HashSet<ModeImpl>( tcs.size() );
        List<TopComponent> openedTcs = new ArrayList<TopComponent>();
        for(Iterator<TopComponent> it = tcs.iterator(); it.hasNext(); ) {
            TopComponent tc = it.next();
            if(!tc.isOpened()) {
                WindowManagerImpl wm = WindowManagerImpl.getInstance();
                ModeImpl mode = (ModeImpl)wm.findMode(tc);
                if(mode == null) {
                    // Only view TopComponent is in group.
                    mode = wm.getDefaultViewMode();
                } else {
                    if( mode.getOpenedTopComponentsIDs().isEmpty() ) {
                        openedModes.add( mode );
                    }
                }
                model.addModeOpenedTopComponent(mode, tc);
                if (tc.getClientProperty(GROUP_SELECTED) != null) {
                    tc.requestVisible();
                }                
                if( isEditorMaximized() && mode.getState() != Constants.MODE_STATE_SEPARATED ) {
                    String tcID = wm.findTopComponentID( tc );
                    if( !isTopComponentDockedInMaximizedMode( tcID ) && mode.getKind() != Constants.MODE_KIND_SLIDING ) {
                        //slide the TopComponent to edgebar and slide it out
                        slide( tc, mode, getSlideSideForMode( mode ) );
                    }
                }
                openedTcs.add(tc);
            }
        }

        
        model.openGroup(tcGroup, new HashSet<TopComponent>(openedTcs), openedBeforeTopComponents);
        
        //restore selected TopComponents
        for( ModeImpl mode : openedModes ) {
            TopComponent prevSelTC = mode.getPreviousSelectedTopComponent();
            if( null != prevSelTC )
                mode.setSelectedTopComponent( prevSelTC );
        }
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(tcGroup, 
                View.CHANGE_TOPCOMPONENT_ARRAY_ADDED, null,
                openedTcs.toArray(new TopComponent[0])));
        }

        // Notify oepned.
        for(TopComponent tc: openedTcs) {
            WindowManagerImpl.getInstance().notifyTopComponentOpened(tc);
        }
    }
    
    /** Used to remember selected group member TC in mode so that it can be
     *  selected when group is opened.
     */
    private static final Object GROUP_SELECTED = new Object();
    
    /** Closes TopComponentGroup. */
    public void closeGroup(TopComponentGroupImpl tcGroup) {
        if(!isGroupOpened(tcGroup)) {
            return;
        }

        if( isViewMaximized() ) {
            //#222210
            switchMaximizedMode( null );
        }
        
        Set tcs = tcGroup.getClosingSet();
        List<TopComponent> closedTcs = new ArrayList<TopComponent>();
        
        Set<TopComponent> openedTcsByGroup = model.getGroupOpenedTopComponents(tcGroup);
        
        // Find out TC which were opened before the group was opened.
        Set<TopComponent> openedTcsBefore = model.getGroupOpenedBeforeTopComponents(tcGroup);

        // Adjust opening flags.
        for(Iterator<TopComponent> it = model.getGroupTopComponents(tcGroup).iterator(); it.hasNext(); ) {
            TopComponent tc = it.next();
            boolean wasOpenedBefore = openedTcsBefore.contains(tc);
            boolean openedByGroup = openedTcsByGroup.contains(tc);
            
            if(tc.isOpened()) {
                if(!wasOpenedBefore && !openedByGroup) {
                    // Open by group next time, user opened it while group was opened.
                    model.addGroupOpeningTopComponent(tcGroup, tc);
                }
            } else {
                if(wasOpenedBefore || openedByGroup) {
                    // Don't open by group next time, user closed it while group was opened.
                    model.removeGroupOpeningTopComponent(tcGroup, tc);
                }
            }
        }

        ArrayList<ModeImpl> groupModes = new ArrayList<ModeImpl>( tcs.size() );
        //remember which TCs are active
        for(Iterator it = tcs.iterator(); it.hasNext(); ) {
            TopComponent tc = (TopComponent)it.next();
            if( !tc.isOpened() || openedTcsBefore.contains(tc)) {
                continue;
            }
            ModeImpl mode = (ModeImpl)WindowManagerImpl.getInstance().findMode(tc);
            if( null != mode )
                groupModes.add( mode );
        }
        for( ModeImpl mode : groupModes ) {
            TopComponent selTC = mode.getSelectedTopComponent();
            if( null != selTC ) {
                String tcId = WindowManagerImpl.getInstance().findTopComponentID(selTC);
                if( null != tcId )
                    setModePreviousSelectedTopComponentID( mode, tcId );
            }
        }
        
        WindowManagerImpl wmi = WindowManagerImpl.getInstance();

        // Now close those which needed.
        for(Iterator it = tcs.iterator(); it.hasNext(); ) {
            TopComponent tc = (TopComponent)it.next();
            if(tc.isOpened()) {
                // Whether to ignore closing flag.
                if(openedTcsBefore.contains(tc)) {
                    continue;
                }
                //#202131 - avoid endless opening and closing of window groups
                //when non-document window from a TC group is dropped into editor area
                if( wmi.isEditorTopComponent( tc ) ) {
                    continue;
                }
                
                boolean ignore = false;
                for(Iterator it2 = model.getTopComponentGroups().iterator(); it2.hasNext(); ) {
                    TopComponentGroupImpl group = (TopComponentGroupImpl)it2.next();
                    if(group == tcGroup) {
                        continue;
                    }
                    if(group.isOpened() && group.getOpeningSet().contains(tc)) {
                        ignore = true;
                        break;
                    }
                }
                if(ignore) {
                    continue;
                }
                
                // Now you can close it.
                ModeImpl mode = (ModeImpl)WindowManagerImpl.getInstance().findMode(tc);
                if(mode != null) {
                    if (mode.getSelectedTopComponent() == tc) {
                        tc.putClientProperty(GROUP_SELECTED, Boolean.TRUE);
                    } else {
                        tc.putClientProperty(GROUP_SELECTED, null);
                    }
                    if (PersistenceHandler.isTopComponentPersistentWhenClosed(tc)) {
                        model.addModeClosedTopComponent(mode, tc);
                    } else {
                        if (Boolean.TRUE.equals(tc.getClientProperty(Constants.KEEP_NON_PERSISTENT_TC_IN_MODEL_WHEN_CLOSED))) {
                            model.addModeClosedTopComponent(mode, tc);
                        } else {
                            model.removeModeTopComponent(mode, tc, null);
                        }
                    }
                    closedTcs.add(tc);
                }
            }
        }

        model.closeGroup(tcGroup);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(tcGroup, 
                View.CHANGE_TOPCOMPONENT_ARRAY_REMOVED, null,
                closedTcs.toArray(new TopComponent[0])));
        }
        
        // Notify closed.
        for(TopComponent tc: closedTcs) {
            WindowManagerImpl.getInstance().notifyTopComponentClosed(tc);
        }
    }
    
    /** Adds TopComponentGroup into model. */
    public void addTopComponentGroup(TopComponentGroupImpl tcGroup) {
        model.addTopComponentGroup(tcGroup);
    }
    
    /** Removes TopComponentGroup from model. */
    public void removeTopComponentGroup(TopComponentGroupImpl tcGroup) {
        model.removeTopComponentGroup(tcGroup);
    }
    
    public boolean addGroupUnloadedTopComponent(TopComponentGroupImpl tcGroup, String tcID) {
        return model.addGroupUnloadedTopComponent(tcGroup, tcID);
    }
    
    public boolean removeGroupUnloadedTopComponent(TopComponentGroupImpl tcGroup, String tcID) {
        return model.removeGroupUnloadedTopComponent(tcGroup, tcID);
    }
    
    /** Adds opening top component for set into model. */
    public boolean addGroupUnloadedOpeningTopComponent(TopComponentGroupImpl tcGroup, String tcID) {
        return model.addGroupUnloadedOpeningTopComponent(tcGroup, tcID);
    }
    
    /** Removes opening top component from model. */
    public boolean removeGroupUnloadedOpeningTopComponent(TopComponentGroupImpl tcGroup, String tcID) {
        return model.removeGroupUnloadedOpeningTopComponent(tcGroup, tcID);
    }
    
    /** Adds closing top component for set into model. */
    public boolean addGroupUnloadedClosingTopComponent(TopComponentGroupImpl tcGroup, String tcID) {
        return model.addGroupUnloadedClosingTopComponent(tcGroup, tcID);
    }
    
    /** Removes closing top component for set from model. */
    public boolean removeGroupUnloadedClosingTopComponent(TopComponentGroupImpl tcGroup, String tcID) {
        return model.removeGroupUnloadedClosingTopComponent(tcGroup, tcID);
    }
    
    // XXX Just helper for persistence.
    public boolean addGroupUnloadedOpenedTopComponent(TopComponentGroupImpl tcGroup, String tcID) {
        if(!isGroupOpened(tcGroup)) {
            return false;
        }
        
        return model.addGroupUnloadedOpenedTopComponent(tcGroup, tcID);
    }
    
    // XXX Just helper for persistence
    public Set getGroupOpenedTopComponents(TopComponentGroupImpl tcGroup) {
        return model.getGroupOpenedTopComponents(tcGroup);
    }
    
    // XXX>>
    public Set<String> getGroupTopComponentsIDs(TopComponentGroupImpl tcGroup) {
        return model.getGroupTopComponentsIDs(tcGroup);
    }
    
    public Set<String> getGroupOpeningSetIDs(TopComponentGroupImpl tcGroup) {
        return model.getGroupOpeningSetIDs(tcGroup);
    }
    
    public Set<String> getGroupClosingSetIDs(TopComponentGroupImpl tcGroup) {
        return model.getGroupClosingSetIDs(tcGroup);
    }
    
    public Set<String> getGroupOpenedTopComponentsIDs(TopComponentGroupImpl tcGroup) {
        return model.getGroupOpenedTopComponentsIDs(tcGroup);
    }
    // XXX<<
    // TopComponentGroup<<
    //////////////////////////////
    
    // Mutators <<
    /////////////////////

    
    /////////////////////
    // Accessors>>
    
    /** Indicates whether windows system shows GUI. */
    public boolean isVisible() {
        return model.isVisible();
    }
    
    /** Gets <code>Set</code> of all <code>Mode</code>'s. */
    public Set<? extends ModeImpl> getModes () {
        return model.getModes();
    }

    /** Gets main window bounds for joined(tiled) state. */
    public Rectangle getMainWindowBoundsJoined() {
        return model.getMainWindowBoundsJoined();
    }
    
    /** Gets main window bounds for separated state. */
    public Rectangle getMainWindowBoundsSeparated() {
        return model.getMainWindowBoundsSeparated();
    }
    
    public int getMainWindowFrameStateJoined() {
        return model.getMainWindowFrameStateJoined();
    }
    
    public int getMainWindowFrameStateSeparated() {
        return model.getMainWindowFrameStateSeparated();
    }

    /** Gets active mode from model. */
    public ModeImpl getActiveMode () {
        return model.getActiveMode();
    }
    
    /** Gets last active editor mode from model. */
    public ModeImpl getLastActiveEditorMode () {
        return model.getLastActiveEditorMode();
    }
    
    /** Gets editor area bounds from model. */
    public Rectangle getEditorAreaBounds() {
        return model.getEditorAreaBounds();
    }

    /** Gets editor area constraints from model. */
    public SplitConstraint[] getEditorAreaConstraints() {
        return model.getEditorAreaConstraints();
    }

    /** Gets editor area state from model. */
    public int getEditorAreaState() {
        return model.getEditorAreaState();
    }
    
    public int getEditorAreaFrameState() {
        return model.getEditorAreaFrameState();
    }
    
    public Component getEditorAreaComponent() {
        return viewRequestor.getEditorAreaComponent();
    }

    /** Gets mode that is currently maximized (can be an editor or view component). */
    ModeImpl getCurrentMaximizedMode() {
        if( isViewMaximized() )
            return model.getViewMaximizedMode();
        if( isEditorMaximized() )
            return model.getEditorMaximizedMode();
        return null;
    }

    /** Gets editor maximized mode. */
    ModeImpl getEditorMaximizedMode() {
        return model.getEditorMaximizedMode();
    }

    /** Gets view maximized mode. */
    ModeImpl getViewMaximizedMode() {
        return model.getViewMaximizedMode();
    }
    
    /** Gets constraints for mode from model. */
    public SplitConstraint[] getModeConstraints(ModeImpl mode) {
        return model.getModeConstraints(mode);
    }


    /** Gets toolbar configuration name from model. */
    public String getToolbarConfigName () {
        return model.getToolbarConfigName();
    }

    ////////////////////////////////
    /// >> Mode specific
    /** Gets programatic name of mode. */
    public String getModeName(ModeImpl mode) {
        return model.getModeName(mode);
    }
    /** Gets bounds. */
    public Rectangle getModeBounds(ModeImpl mode) {
        return model.getModeBounds(mode);
    }
    /** Gets State. */
    public int getModeState(ModeImpl mode) {
        return model.getModeState(mode);
    }
    /** Gets kind. */
    public int getModeKind(ModeImpl mode) {
        return model.getModeKind(mode);
    }
    
    /** Gets side. */
    public String getModeSide(ModeImpl mode) {
        return model.getModeSide(mode);
    }
    
    /** Gets frame state. */
    public int getModeFrameState(ModeImpl mode) {
        return model.getModeFrameState(mode);
    }
    /** Gets used defined. */
    public boolean isModePermanent(ModeImpl mode) {
        return model.isModePermanent(mode);
    }
    public boolean isModeEmpty(ModeImpl mode) {
        return model.isModeEmpty(mode);
    }
    /** */
    public boolean containsModeTopComponent(ModeImpl mode, TopComponent tc) {
        return model.containsModeTopComponent(mode, tc);
    }
    /** Gets selected TopComponent. */
    public TopComponent getModeSelectedTopComponent(ModeImpl mode) {
        return model.getModeSelectedTopComponent(mode);
    }
    /**
     * @return ID of TopComponent that was previously selected in the given mode or null.
     */
    public String getModePreviousSelectedTopComponentID(ModeImpl mode) {
        return model.getModePreviousSelectedTopComponentID( mode );
    }
    /** Gets list of top components in this workspace. */
    public List<TopComponent> getModeTopComponents(ModeImpl mode) {
        return model.getModeTopComponents(mode);
    }
    /** Gets list of top components in this workspace. */
    public List<TopComponent> getModeOpenedTopComponents(ModeImpl mode) {
        return model.getModeOpenedTopComponents(mode);
    }
    
    /** Gets position of top component in given mode. */
    public int getModeTopComponentTabPosition(ModeImpl mode, TopComponent tc) {
        return model.getModeOpenedTopComponentTabPosition(mode, tc);
    }

    /**
     * 
     * @param mode
     * @return True if the given mode is minimized.
     * @since 2.30
     */
    boolean isModeMinimized( ModeImpl mode ) {
        return model.isModeMinimized( mode );
    }

    /**
     * Mark the given mode as minimized or docked.
     * @param mode
     * @param minimized True if the mode is minimized, false if docked.
     * @since 2.30
     */
    void setModeMinimized( ModeImpl mode, boolean minimized ) {
        model.setModeMinimized( mode, minimized );
    }

    /**
     * @param mode
     * @return List of Mode's additional names.
     * @since 2.30
     */
    Collection<String> getModeOtherNames( ModeImpl mode ) {
        return model.getModeOtherNames( mode );
    }

    /**
     * Add another mode name.
     * @param mode
     * @param modeOtherName 
     * @since 2.30
     */
    void addModeOtherName( ModeImpl mode, String modeOtherName ) {
        model.addModeOtherName( mode, modeOtherName );
    }
    
    /// << Mode specific
    ////////////////////////////////
    
    ////////////////////////////////////
    // TopComponentGroup specific >>
    public Set<TopComponentGroupImpl> getTopComponentGroups() {
        return model.getTopComponentGroups();
    }
    
    public String getGroupName(TopComponentGroupImpl tcGroup) {
        return model.getGroupName(tcGroup);
    }
    
    public Set<TopComponent> getGroupTopComponents(TopComponentGroupImpl tcGroup) {
        return model.getGroupTopComponents(tcGroup);
    }
    
    /** Gets opening top components for group from model. */
    public Set<TopComponent> getGroupOpeningTopComponents(TopComponentGroupImpl tcGroup) {
        return model.getGroupOpeningTopComponents(tcGroup);
    }
    
    /** Gets closing top components for group from model. */
    public Set getGroupClosingTopComponents(TopComponentGroupImpl tcGroup) {
        return model.getGroupClosingTopComponents(tcGroup);
    }
    // TopComponentGroup specific <<
    ////////////////////////////////////

    // Accessors<<
    /////////////////////
    
    
    // Others>>
    // PENDING>>
    public void topComponentDisplayNameChanged(ModeImpl mode, TopComponent tc) {
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(
                mode, View.CHANGE_TOPCOMPONENT_DISPLAY_NAME_CHANGED, null, tc));
        }
    }

    public void topComponentDisplayNameAnnotation(ModeImpl mode, TopComponent tc) {
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(
                mode, View.CHANGE_TOPCOMPONENT_DISPLAY_NAME_ANNOTATION_CHANGED, null, tc));
        }
    }
    // PENDING<<
    
    public void topComponentToolTipChanged(ModeImpl mode, TopComponent tc) {
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(
                mode, View.CHANGE_TOPCOMPONENT_TOOLTIP_CHANGED, null, tc));
        }
    }
    
    public void topComponentIconChanged(ModeImpl mode, TopComponent tc) {
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(
                mode, View.CHANGE_TOPCOMPONENT_ICON_CHANGED, null, tc));
        }
    }

    /**
     * 
     * @param mode
     * @param tc
     * @param busy 
     * @since 2.45
     */
    public void topComponentMakeBusy( ModeImpl mode, TopComponent tc, boolean busy ) {
        String modeName = getModeName(mode);
        viewRequestor.scheduleRequest (
            new ViewRequest(modeName, busy ? View.TOPCOMPONENT_SHOW_BUSY : View.TOPCOMPONENT_HIDE_BUSY, tc, tc));
    }
    
    public void resetModel() {
        model.reset();
    }
    
    // Others<<
    
    
    // Compound ones>>
    
    /**
     * Creates a new mode on the side of the reference mode.
     * @param referenceMode
     * @param side
     * @param modeName
     * @param modeKind
     * @param permanent
     * @return 
     */
    ModeImpl attachModeToSide( ModeImpl referenceMode, String side, String modeName, int modeKind, boolean permanent ) {
        ModeImpl newMode = WindowManagerImpl.getInstance().createModeImpl(modeName, modeKind, permanent);
        
        model.addModeToSide(newMode, referenceMode, side);
        
        return newMode;
    }

    /** Creates new mode on side of specified one and puts there the TopComponentS. */
    private ModeImpl attachModeToSide(ModeImpl attachMode, String side, int modeKind) {
        return attachModeToSide( attachMode, side, ModeImpl.getUnusedModeName(), modeKind, false);
    }

    /** Creates new mode on side of desktop */
    private ModeImpl attachModeAroundDesktop(String side) {
        // New mode. It is necessary to add it yet.
        ModeImpl newMode = WindowManagerImpl.getInstance().createModeImpl(
            ModeImpl.getUnusedModeName(), Constants.MODE_KIND_VIEW, false);

        model.addModeAround(newMode, side);
        
        return newMode;
    }
    
    /** Creates new mode on side of editor area and puts there the TopComponentS. */
    private ModeImpl attachModeAroundEditor(String side, int modeKind) {
        // New mode. It is necessary to add it yet.
        ModeImpl newMode = WindowManagerImpl.getInstance().createModeImpl(
            ModeImpl.getUnusedModeName(), modeKind, false);

        model.addModeAroundEditor(newMode, side);

        return newMode;
    }
    
    private ModeImpl createFloatingMode(Rectangle bounds, int modeKind) {
        WindowManagerImpl wmi = WindowManagerImpl.getInstance();
        // New mode. It is necessary to add it yet.
        ModeImpl newMode = wmi.createModeImpl(
            ModeImpl.getUnusedModeName(), modeKind, Constants.MODE_STATE_SEPARATED, false);
        newMode.setBounds(bounds);
        
        model.addMode(newMode, new SplitConstraint[] {new SplitConstraint(Constants.HORIZONTAL, 100, 0.5f)});

        return newMode;
    }

    /** */
    public void activateModeTopComponent(ModeImpl mode, TopComponent tc) {
        if(!getModeOpenedTopComponents(mode).contains(tc)) {
            return;
        }
        
        ModeImpl oldActiveMode = getActiveMode();
        //#45650 -some API users call the activation all over again all the time on one item.
        // improve performance for such cases.
        if (oldActiveMode != null && oldActiveMode.equals(mode)) {
            if (tc != null && tc.equals(model.getModeSelectedTopComponent(mode))) {
                // #82385, #139319 do repeat activation if focus is not
                // owned by tc to be activated
                Component fOwn = KeyboardFocusManager.getCurrentKeyboardFocusManager().
                        getFocusOwner();
                if (fOwn != null && SwingUtilities.isDescendingFrom(fOwn, tc)) {
                    //#70173 - activation request came probably from a sliding
                    //window in 'hover' mode, so let's hide it
                    slideOutSlidingWindows( mode );
                    return;
                }
            }
        }
        model.setActiveMode(mode);
        model.setModeSelectedTopComponent(mode, tc);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(new ViewRequest(mode, 
                View.CHANGE_TOPCOMPONENT_ACTIVATED, null, tc));

            //restore floating windows if iconified
            if( mode.getState() == Constants.MODE_STATE_SEPARATED ) {
                Frame frame = (Frame) SwingUtilities.getAncestorOfClass(Frame.class, tc);
                if( null != frame && frame != WindowManagerImpl.getInstance().getMainWindow()
                        && (frame.getExtendedState() & Frame.ICONIFIED) > 0 ) {
                    frame.setExtendedState(frame.getExtendedState() - Frame.ICONIFIED );
                }
            }
        }
        
        // Notify registry.
        WindowManagerImpl.notifyRegistryTopComponentActivated(tc);
        
        if(oldActiveMode != mode) {
            WindowManagerImpl.getInstance().doFirePropertyChange(
                WindowManagerImpl.PROP_ACTIVE_MODE, oldActiveMode, mode);
        }
    }
    // Compound ones<<
    
    
    /**
     * Make sure no sliding window is slided-in.
     */
    protected void slideOutSlidingWindows( ModeImpl newActiveMode ) {
        for( ModeImpl mode : getModes() ) {
            if( !newActiveMode.equals(mode)
                && mode.getKind() == Constants.MODE_KIND_SLIDING
                && null != mode.getSelectedTopComponent() ) {
                
                setModeSelectedTopComponent( mode, null );
            }
        }
    }
    

    // Other >>
    public boolean isDragInProgress() {
        // XXX
        return viewRequestor.isDragInProgress();
    }
    
    public Frame getMainWindow() {
        // XXX
        return viewRequestor.getMainWindow(); 
    }
    
    public String guessSlideSide(TopComponent tc) {
        return viewRequestor.guessSlideSide(tc);
    }
    
    /**
     * Find the side (LEFT/RIGHT/BOTTOM) where the TopComponent from the given
     * mode should slide to.
     * 
     * @param mode Mode
     * @return The slide side for TopComponents from the given mode.
     */
    String getSlideSideForMode(ModeImpl mode) {
        return model.getSlideSideForMode( mode );
    }

    /** Tells whether given top component is inside joined mode (in main window)
     * or inside separate mode (separate window).
     *
     * @param the component in question
     * @return True when given component is docked, which means lives now
     * inside main window. False if component lives inside separate window.
     */
    public boolean isDocked (TopComponent comp) {
        ModeImpl mode = (ModeImpl)WindowManagerImpl.getInstance().findMode(comp);
        return mode != null && mode.getState() == Constants.MODE_STATE_JOINED;
    }

    
    // Other <<
    
    // Helper methods
    /** Creates model for mode, used internally. */
    public void createModeModel(ModeImpl mode, String name, int state, int kind, boolean permanent) {
        model.createModeModel(mode, name, state, kind, permanent); 
    }
    
    
    /** Creates model for top component group, used internally. */
    public void createGroupModel(TopComponentGroupImpl tcGroup, String name, boolean opened) {
        model.createGroupModel(tcGroup, name, opened);
    }
    
    // snapshot
    /** Creates window system model snapshot, used for requesting view. */
    public WindowSystemSnapshot createWindowSystemSnapshot() {
        return model.createWindowSystemSnapshot();
    }
    
    
    ///////////////////////////
    // ControllerHandler>>
    @Override
    public void userActivatedMode(ModeImpl mode) {
        if(mode != null) {
            setActiveMode(mode);
        }
    }
    
    @Override
    public void userActivatedModeWindow(ModeImpl mode) {
        if(mode != null) {
            setActiveMode(mode);
        }
    }
    
    @Override
    public void userActivatedEditorWindow() {
        WindowManagerImpl wm = WindowManagerImpl.getInstance();
        TopComponent[] tcs = wm.getRecentViewList(); 
        for(int i = 0; i < tcs.length; i++) {
            TopComponent tc = tcs[i];
            ModeImpl mode = (ModeImpl)wm.findMode(tc);
            if(mode != null 
            && mode.getKind() == Constants.MODE_KIND_EDITOR
            && !mode.getOpenedTopComponents().isEmpty()) {
                setActiveMode(mode);
                return;
            }
        }
        
        ModeImpl mode = wm.getDefaultEditorMode();
        if(mode != null && !mode.getOpenedTopComponents().isEmpty()) {
            setActiveMode(mode);
        } else {
            // when someone calls this as a matter of activating editor mode as a fallback, but none is opened,
            // do unactivate the current selection.
            // #44389
            setActiveMode(null);
        }
    }
    
    @Override
    public void userActivatedTopComponent(ModeImpl mode, TopComponent selected) {
        if(mode != null) {
            setModeSelectedTopComponent(mode, selected);
        }
    }
    
    @Override
    public void userResizedMainWindow(Rectangle bounds) {
        if(getEditorAreaState() == Constants.EDITOR_AREA_JOINED) {
            model.setMainWindowBoundsJoined(bounds);
        } else {
            model.setMainWindowBoundsSeparated(bounds);
        }
    }
    
    @Override
    public void userResizedMainWindowBoundsSeparatedHelp(Rectangle bounds) {
        if(getEditorAreaState() == Constants.EDITOR_AREA_JOINED
        && getMainWindowBoundsSeparated().isEmpty()) {
            model.setMainWindowBoundsUserSeparatedHelp(bounds);
        }
    }
    
    @Override
    public void userResizedEditorArea(Rectangle bounds) {
        model.setEditorAreaBounds(bounds);
    }
    
    @Override
    public void userResizedEditorAreaBoundsHelp(Rectangle bounds) {
        if(getEditorAreaState() == Constants.EDITOR_AREA_JOINED
        && getEditorAreaBounds().isEmpty()) {
            model.setEditorAreaBoundsUserHelp(bounds);
        }
    }

    @Override
    public void userResizedModeBounds(ModeImpl mode, Rectangle bounds) {
        Rectangle old = model.getModeBounds(mode);
        model.setModeBounds(mode, bounds);
        
        mode.doFirePropertyChange(ModeImpl.PROP_BOUNDS, old, bounds);
    }
    
    @Override
    public void userResizedModeBoundsSeparatedHelp(ModeImpl mode, Rectangle bounds) {
        model.setModeBoundsSeparatedHelp(mode, bounds);
    }
    
    @Override
    public void userChangedFrameStateMainWindow(int frameState) {
        if(getEditorAreaState() == Constants.EDITOR_AREA_JOINED) {
            model.setMainWindowFrameStateJoined(frameState);
        } else {
            model.setMainWindowFrameStateSeparated(frameState);
        }
    }
    
    @Override
    public void userChangedFrameStateEditorArea(int frameState) {
        model.setEditorAreaFrameState(frameState);
    }
    
    @Override
    public void userChangedFrameStateMode(ModeImpl mode, int frameState) {
        model.setModeFrameState(mode, frameState);
    }
    
    @Override
    public void userChangedSplit( ModelElement[] snapshots, double[] splitWeights ) {
        model.setSplitWeights( snapshots, splitWeights );
    }

    @Override
    public void userClosedTopComponent(ModeImpl mode, TopComponent tc) {
        if( mode == getCurrentMaximizedMode() && isViewMaximized() ) {
            switchMaximizedMode( null );
            for(Iterator it = getModes().iterator(); it.hasNext(); ) {
                ModeImpl newMode = (ModeImpl)it.next();

                if(newMode.containsTopComponent(tc)) {
                    userClosedTopComponent( newMode, tc );
                    return;
                }
            }
        }
        TopComponent recentTc = null;
        if( mode.getKind() == Constants.MODE_KIND_EDITOR ) {
            //an editor document is being closed so let's find the most recent editor to select
            recentTc = getRecentTopComponent( mode, tc );
        }
        boolean wasTcClosed = false;
        if (PersistenceHandler.isTopComponentPersistentWhenClosed(tc)) {
            wasTcClosed = addModeClosedTopComponent(mode, tc);
        } else {
            if (Boolean.TRUE.equals(tc.getClientProperty(Constants.KEEP_NON_PERSISTENT_TC_IN_MODEL_WHEN_CLOSED))) {
                wasTcClosed = addModeClosedTopComponent(mode, tc);
            } else {
                wasTcClosed = removeModeTopComponent(mode, tc);
            }
        }
        if( wasTcClosed 
                && mode.getKind() == Constants.MODE_KIND_EDITOR 
                && "editor".equals(mode.getName())  //NOI18N
                && mode.getOpenedTopComponentsIDs().isEmpty() ) {
            
            //134945 - if user just closed the last topcomponent in the default
            //and permanent "editor" mode then pick some other arbitrary editor mode
            //and move its topcomponents to the default editor mode. otherwise opening
            //of a new editor window will cause a split in the editor area.
            ModeImpl otherEditorMode = findSomeOtherEditorModeImpl();
            if( null != otherEditorMode ) {
                for( String closedTcId : otherEditorMode.getClosedTopComponentsIDs() ) {
                    mode.addUnloadedTopComponent(closedTcId);
                }
                List<TopComponent> tcs = otherEditorMode.getOpenedTopComponents();
                for( TopComponent t : tcs ) {
                    int index = otherEditorMode.getTopComponentTabPosition(t);
                    mode.addOpenedTopComponent(t, index);
                }
                removeMode(otherEditorMode);
            }
        }
        if ((recentTc != null) && wasTcClosed) {
            recentTc.requestActive();
        }
        //#177986 - repaint the main window when closing the last tc
        if( TopComponent.getRegistry().getOpened().isEmpty() ) {
            SwingUtilities.invokeLater( new Runnable() {
                @Override
                public void run() {
                    java.awt.Frame f = getMainWindow();
                    if( null != f && f.isVisible() ) {
                        f.invalidate();
                        f.repaint();
                    }
                }
            });
        }
    }
    
    /**
     * @return ModeImpl with opened TopComponents which is 'editor' kind but 
     * not the default and permanent one. Returns null if there is no such mode.
     */
    private ModeImpl findSomeOtherEditorModeImpl() {
        for( ModeImpl m : getModes() ) {
            if( m.getKind() == Constants.MODE_KIND_EDITOR 
                    && !"editor".equals(m.getName()) //NOI18N
                    && !m.getOpenedTopComponentsIDs().isEmpty()
                    && !m.isPermanent()
                    && m.getState() != Constants.MODE_STATE_SEPARATED )
                return m;
        }
        return null;
    }
    
    @Override
    public void userClosedMode(ModeImpl mode) {
        if(mode != null) {
            boolean allAreClosable = true;
            for( TopComponent tc : mode.getOpenedTopComponents() ) {
                if( !Switches.isClosingEnabled(tc) ) {
                    allAreClosable = false;
                    break;
                }
            }
            if( allAreClosable ) {
                closeMode(mode);
            } else {
                ArrayList<TopComponent> tcs = new ArrayList<>(mode.getOpenedTopComponents());
                for( TopComponent tc : tcs ) {
                    if( Switches.isClosingEnabled(tc) ) {
                        userClosedTopComponent(mode, tc);
                    }
                }
                
            }
            // Unmaximize if necessary.
            if(mode.getOpenedTopComponents().isEmpty()
                && mode == getCurrentMaximizedMode()) 
            {
                switchMaximizedMode(null);
            }
        }
        
    }
    
    
    // DnD
    @Override
    public void userDroppedTopComponents(ModeImpl mode, TopComponentDraggable draggable) {
        boolean unmaximize = false;
        if( draggable.isTopComponentTransfer() ) {
            unmaximize = moveTopComponentIntoMode( mode, draggable.getTopComponent() );
        } else {
            TopComponent selTC = draggable.getMode().getSelectedTopComponent();
            mergeModes( draggable.getMode(), mode, -1 );
            if( null != selTC )
                mode.setSelectedTopComponent( selTC );
        }
        updateViewAfterDnD(unmaximize);
    }
    
    @Override
    public void userDroppedTopComponents(ModeImpl mode, TopComponentDraggable draggable, int index) {
        boolean unmaximize = false;
        if( draggable.isTopComponentTransfer() ) {
            unmaximize = moveTopComponentIntoMode( mode, draggable.getTopComponent(), index );
        } else {
            TopComponent selTC = draggable.getMode().getSelectedTopComponent();
            mergeModes( draggable.getMode(), mode, index );
            if( null != selTC )
                mode.setSelectedTopComponent( selTC );
        }
        updateViewAfterDnD(unmaximize);
    }
    
    @Override
    public void userDroppedTopComponents(ModeImpl mode, TopComponentDraggable draggable, String side) {
        ModeImpl newMode = attachModeToSide( mode, side, mode.getKind() );
        if( draggable.isTopComponentTransfer() ) {
            moveTopComponentIntoMode( newMode, draggable.getTopComponent() );
        } else {
            if( newMode.getKind() != draggable.getKind() ) {
                mergeModes( draggable.getMode(), newMode, -1 );
            } else {
                dockMode( newMode, draggable.getMode() );
            }
        }
        
        updateViewAfterDnD(true);
    }
    
    @Override
    public void userDroppedTopComponentsIntoEmptyEditor(TopComponentDraggable draggable) {
        // PENDING
        ModeImpl mode = (ModeImpl)WindowManagerImpl.getInstance().findMode("editor"); // NOI18N
        if( null == mode || mode.getState() == Constants.MODE_STATE_SEPARATED ) {
            for( ModeImpl m : getModes() ) {
                if( m.getKind() == Constants.MODE_KIND_EDITOR && m.getState() == Constants.MODE_STATE_JOINED ) {
                    mode = m;
                    break;
                }
            }
        }
        if( null == mode || mode == draggable.getMode() ) {
            if( draggable.isModeTransfer() && draggable.getMode().getKind() == Constants.MODE_KIND_EDITOR ) {
                userDockedMode( draggable.getMode() );
            }
            return;
        }
        if( draggable.isTopComponentTransfer() ) {
            moveTopComponentIntoMode(mode, draggable.getTopComponent());
        } else {
            if( mode.getKind() != draggable.getKind() ) {
                mergeModes( draggable.getMode(), mode, 0 );
            } else {
                dockMode( mode, draggable.getMode() );
            }
        }
        updateViewAfterDnD(true);
    }
    
    @Override
    public void userDroppedTopComponentsAround(TopComponentDraggable draggable, String side) {
        ModeImpl newMode = attachModeAroundDesktop( side );
        if( draggable.isTopComponentTransfer() ) {
            moveTopComponentIntoMode( newMode, draggable.getTopComponent() );
        } else {
            dockMode( newMode, draggable.getMode() );
        }

        updateViewAfterDnD(true);
    }
    
    @Override
    public void userDroppedTopComponentsAroundEditor(TopComponentDraggable draggable, String side) {
        ModeImpl newMode = attachModeAroundEditor( side, draggable.getKind() );
        if( draggable.isTopComponentTransfer() ) {
            moveTopComponentIntoMode( newMode, draggable.getTopComponent() );
        } else {
            dockMode( newMode, draggable.getMode() );
        }
        updateViewAfterDnD(true);
    }
    
    @Override
    public void userDroppedTopComponentsIntoFreeArea(TopComponentDraggable draggable, Rectangle bounds) {
        if( draggable.isTopComponentTransfer() ) {
            ModeImpl newMode = createFloatingMode( bounds, draggable.getKind() );
            moveTopComponentIntoMode( newMode, draggable.getTopComponent() );
            newMode.setSelectedTopComponent( draggable.getTopComponent() );
        } else {
            userUndockedMode( draggable.getMode(), bounds );
        }
        updateViewAfterDnD(false);
    }

    /**
     * User made the given Mode floating.
     * @param mode 
     * @since 2.30
     */
    public void userUndockedMode( ModeImpl mode ) {
        if (getCurrentMaximizedMode() == mode) {
            switchMaximizedMode(null);
        }
        Rectangle modeBounds = null;
        TopComponent tc = mode.getSelectedTopComponent();
        if( null != tc ) {
            Point tcLoc = tc.getLocation();
            Dimension tcSize = tc.getSize();
            SwingUtilities.convertPointToScreen(tcLoc, tc);
            modeBounds = new Rectangle(tcLoc, tcSize);
        }

        userUndockedMode( mode, modeBounds );
    }

    private void userUndockedMode( ModeImpl mode, Rectangle modeBounds ) {
        int modeKind = mode.getKind();
        if (modeKind == Constants.MODE_KIND_SLIDING) {
            modeKind = Constants.MODE_KIND_VIEW;
        }
        if (getCurrentMaximizedMode() == mode) {
            switchMaximizedMode(null);
        }
        if( null != modeBounds ) {
            model.setModeBounds( mode, modeBounds );
        }
        
        SplitConstraint[] constraints = mode.getConstraints();
        model.setModeState( mode, Constants.MODE_STATE_SEPARATED );
        
        ModeImpl previousMode = WindowManagerImpl.getInstance().createMode( null, mode.getKind(), Constants.MODE_STATE_JOINED, true, constraints);
        constraints = previousMode.getConstraints();
        List<String> openedIDs = mode.getOpenedTopComponentsIDs();
        for( String tcID : getModeTopComponentsIDs( mode ) ) {
            model.setModeTopComponentPreviousMode(mode, tcID, previousMode, openedIDs.indexOf( tcID ));
            model.setModeTopComponentPreviousConstraints(mode, tcID, constraints);
        }
        model.setModeConstraints( mode, new SplitConstraint[0] );
        updateViewAfterDnD(false);
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManager.PROP_MODES, null, null);
    }

    /**
     * User docked the given mode (from floating state).
     * @param mode 
     * @since 2.30
     */
    public void userDockedMode( ModeImpl mode ) {
        int modeKind = mode.getKind();
        if (modeKind == Constants.MODE_KIND_SLIDING) {
            modeKind = Constants.MODE_KIND_VIEW;
        }
        switchMaximizedMode(null);

        TopComponent selectedTC = mode.getSelectedTopComponent();

        if( !mode.isPermanent() ) {
            for( TopComponent tc : mode.getOpenedTopComponents() ) {
                userDockedTopComponent( tc, mode );
            }
        } else {
        
            List<String> ids = mode.getTopComponentsIDs();
            if( !ids.isEmpty() ) {
                String tcID = ids.get( 0 );
                ModeImpl previousMode = model.getModeTopComponentPreviousMode( mode, tcID );
                if( null == previousMode || !model.getModes().contains(previousMode) ) {
                    SplitConstraint[] constraints = model.getModeTopComponentPreviousConstraints( mode, tcID );
                    if( null != constraints ) {
                        previousMode = findJoinedMode( modeKind, constraints);
                    }
                }
                if( null == previousMode ) {
                    SplitConstraint[] constraints = model.getModeTopComponentPreviousConstraints( mode, tcID );
                    if( null != constraints )
                        model.setModeConstraints( mode, constraints );
                    model.setModeState( mode, Constants.MODE_STATE_JOINED );
                } else {
                    if( previousMode.isPermanent() && !previousMode.getTopComponentsIDs().isEmpty() ) {
                        List<String> opened = mode.getOpenedTopComponentsIDs();
                        for( String id : opened ) {
                            int prevIndex = model.getModeTopComponentPreviousIndex( mode, id );
                            TopComponent tc = WindowManagerImpl.getInstance().findTopComponent( id );
                            previousMode.addOpenedTopComponent( tc, prevIndex );
                        }
                        mergeModes( mode, previousMode, -1 );
                        mode = null;
                    } else {
                        dockMode( previousMode, mode );
                    }
                }
            }
            if( null != mode ) {
                SplitConstraint[] constraints = mode.getConstraints();
                if( null == constraints || constraints.length == 0 ) {
                    //just a fallback, it shouldn't really happen
                    model.setModeConstraints( mode, new SplitConstraint[] { new SplitConstraint( Constants.HORIZONTAL, 0, 0.2) } );
                }
                model.setModeState( mode, Constants.MODE_STATE_JOINED );
            }
        }
        updateViewAfterDnD(false);
        if( null != selectedTC )
            selectedTC.requestActive();
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManager.PROP_MODES, null, null);
    }
    
    /**
     * Merge the given Modes into a single name.
     * @param source Mode which will disappear after the merge.
     * @param target Mode which will receive all TopComponents from the source
     * mode.
     * @since 2.30
     */
    private void mergeModes( ModeImpl source, ModeImpl target, int index ) {
        ModeImpl prevMode = null;
        SplitConstraint[] prevConstraints = null;
        if( source.isPermanent() && !target.isPermanent() ) {
            prevMode = getPreviousMode( source );
            prevConstraints = getPreviousConstraints( source );
        } else {
            prevMode = getPreviousMode( target );
            prevConstraints = getPreviousConstraints( target );
        }
        int tmpIndex = index;
        for( String tcID: source.getTopComponentsIDs() ) {
            target.addUnloadedTopComponent( tcID, tmpIndex );
            if( tmpIndex >= 0 )
                tmpIndex++;
        }
        tmpIndex = index;
        List<TopComponent> opened = source.getOpenedTopComponents();
        for( TopComponent tc : opened ) {
            target.addOpenedTopComponent( tc, tmpIndex );
            if( tmpIndex >= 0 )
                tmpIndex++;
            TopComponentTracker.getDefault().add( tc, target );
        }
        if( source.isPermanent() ) {
            target.addOtherName( source.getName() );
            for( String otherName : source.getOtherNames() ) {
                target.addOtherName( otherName );
            }
        }
        if( source.isPermanent() )
            model.makeModePermanent( target );
        if( target.getState() == Constants.MODE_STATE_SEPARATED ) {
            setPreviousMode( target, prevMode );
            setPreviousConstraints( target, prevConstraints );
        }
        model.removeMode( source );
    }
    
    /**
     * Find the mode which the TopComponents of the given mode were in before.
     * @param mode
     * @return 
     * @since 2.30
     */
    private ModeImpl getPreviousMode( ModeImpl mode ) {
        ModeImpl prevMode = null;
        if( mode.getState() == Constants.MODE_STATE_JOINED ) {
            prevMode = mode;
        } else {
            List<String> ids = mode.getTopComponentsIDs();
            if( !ids.isEmpty() )
                prevMode = model.getModeTopComponentPreviousMode( mode, ids.get( 0 ) );
        }
        if( prevMode != null && !getModes().contains( prevMode ) )
            prevMode = null;
        return prevMode;
    }
    
    /**
     * Find previous split constraints of TopComponents in the given mode.
     * @param mode
     * @return 
     * @since 2.30
     */
    private SplitConstraint[] getPreviousConstraints( ModeImpl mode ) {
        ModeImpl prevMode = getPreviousMode( mode );
        if( null != prevMode )
            return mode.getConstraints();
        List<String> ids = mode.getTopComponentsIDs();
        if( !ids.isEmpty() )
            return model.getModeTopComponentPreviousConstraints( mode, ids.get( 0 ) );
        return null;
    }
    
    /**
     * Set previous mode for all TopComponents in given mode.
     * @param mode Mode to adjust
     * @param prevMode Previous mode
     * @since 2.30
     */
    private void setPreviousMode( ModeImpl mode, ModeImpl prevMode ) {
        for( String tcId : mode.getTopComponentsIDs() ) {
            int prevIndex = model.getModeTopComponentPreviousIndex( mode, tcId );
            model.setModeTopComponentPreviousMode( mode, tcId, prevMode, prevIndex );
        }
    }
    
    /**
     * Set previous split constraints for all TopComponents in given mode.
     * @param mode
     * @param prevConstraints 
     * @since 2.30
     */
    private void setPreviousConstraints( ModeImpl mode, SplitConstraint[] prevConstraints ) {
        for( String tcId : mode.getTopComponentsIDs() ) {
            model.setModeTopComponentPreviousConstraints( mode, tcId, prevConstraints );
        }
    }
    
    private void dockMode( ModeImpl prevMode, ModeImpl floatingMode ) {
        ModeImpl floatingPrevMode = getPreviousMode( floatingMode );
        List<TopComponent> opened = prevMode.getOpenedTopComponents();
        for( TopComponent tc : opened ) {
            floatingMode.addOpenedTopComponent( tc );
        }
        for( String tcID: prevMode.getClosedTopComponentsIDs() ) {
            floatingMode.addUnloadedTopComponent( tcID );
        }
        model.dockMode( prevMode, floatingMode );
        setPreviousMode( floatingMode, null );
        setPreviousConstraints( floatingMode, null );
        if( null != floatingPrevMode && floatingPrevMode.getTopComponentsIDs().isEmpty() ) {
            model.removeMode( floatingPrevMode );
        }
    }

    /**
     * User minimized the whole Mode
     * @param mode 
     * @since 2.30
     */
    void userMinimizedMode( ModeImpl mode ) {
        List<TopComponent> opened = mode.getOpenedTopComponents();
        TopComponent selTc = mode.getSelectedTopComponent();
        String side = getSlideSideForMode( mode );
        for( TopComponent tc : opened ) {
            slide( tc, mode, side );
        }
        WindowManagerImpl wm = WindowManagerImpl.getInstance();
        int index = 0;
        for( TopComponent tc : opened ) {
            ModeImpl newMode = ( ModeImpl ) wm.findMode( tc );
            if( null == newMode )
                continue;
            String tcId = wm.findTopComponentID( tc );
            model.setModeTopComponentPreviousMode(newMode, tcId, mode, index++);
        }
        setModeMinimized( mode, true );
        if( null != selTc ) {
            mode.setPreviousSelectedTopComponentID( wm.findTopComponentID(selTc) );
        }
    }

    /**
     * User restored a Mode from minimized state in given sliding Mode.
     * @param slidingMode
     * @param modeToRestore 
     * @since 2.35
     */
    void userRestoredMode( ModeImpl slidingMode, ModeImpl modeToRestore ) {
        TopComponent tcToSelect = modeToRestore.getPreviousSelectedTopComponent();
        setModeMinimized( modeToRestore, false );
        WindowManagerImpl wm = WindowManagerImpl.getInstance();
        for( TopComponent tc : slidingMode.getOpenedTopComponents() ) {
            String id = wm.findTopComponentID( tc );
            ModeImpl prevMode = model.getModeTopComponentPreviousMode( slidingMode, id );
            if( null != prevMode && prevMode.equals( modeToRestore ) ) {
                int prevIndex = model.getModeTopComponentPreviousIndex(slidingMode, id);
                moveTopComponentIntoMode(prevMode, tc, prevIndex);

            }
        }
        if( null != tcToSelect )
            modeToRestore.setSelectedTopComponent( tcToSelect );
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_TOPCOMPONENT_AUTO_HIDE_DISABLED, null, null));
        }
        setActiveMode( modeToRestore );
    }

    @Override
    public void userUndockedTopComponent(TopComponent tc, ModeImpl mode) {
        Point tcLoc = tc.getLocation();
        Dimension tcSize = tc.getSize();
        SwingUtilities.convertPointToScreen(tcLoc, tc);
        Rectangle bounds = new Rectangle(tcLoc, tcSize);
        // #89100: update mode kind when undocking view in sliding mode
        int modeKind = mode.getKind();
        if (modeKind == Constants.MODE_KIND_SLIDING) {
            modeKind = Constants.MODE_KIND_VIEW;
        }
        // #81479: unmaximize only if desirable
        if (getCurrentMaximizedMode() == mode &&
            mode.getOpenedTopComponents().size() == 1 &&
            mode.getOpenedTopComponents().get(0) == tc) {
            switchMaximizedMode(null);
        }
        ModeImpl newMode = createFloatingMode( bounds, modeKind );
        moveTopComponentIntoMode( newMode, tc );
        updateViewAfterDnD(false);
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManager.PROP_MODES, null, null);
    }

    @Override
    public void userDockedTopComponent(TopComponent tc, ModeImpl mode) {
        ModeImpl dockTo = null;
        // find saved previous mode or at least constraints (=the place) to dock back into
        String tcID = WindowManagerImpl.getInstance().findTopComponentID(tc);
        ModeImpl source = (ModeImpl) WindowManagerImpl.getInstance().findMode(tc);
        dockTo = model.getModeTopComponentPreviousMode(source, tcID);
        int dockIndex = model.getModeTopComponentPreviousIndex(source, tcID);
        int modeKind = mode.getKind();
        
        if ((dockTo == null) || !model.getModes().contains(dockTo) || dockTo.getState() == Constants.MODE_STATE_SEPARATED) {
            // mode to dock to back isn't valid anymore, try constraints
            SplitConstraint[] constraints = model.getModeTopComponentPreviousConstraints(source, tcID);
            if (constraints != null) {
                //there might be some mode with the same constraints already
                dockTo = findJoinedMode( modeKind, constraints );
                if( null == dockTo ) {
                    // create mode with the same constraints to dock topcomponent back into
                    dockTo = WindowManagerImpl.getInstance().createModeImpl(
                            ModeImpl.getUnusedModeName(), modeKind, false);
                    model.addMode(dockTo, constraints);
                }
            }
        }
        
        if (dockTo == null) {
            // fallback, previous saved mode not found somehow, use default modes
            dockTo = modeKind == Constants.MODE_KIND_EDITOR
                    ? WindowManagerImpl.getInstance().getDefaultEditorMode()
                    : WindowManagerImpl.getInstance().getDefaultViewMode();
        }
        moveTopComponentIntoMode(dockTo, tc, dockIndex);
        updateViewAfterDnD(false);
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManager.PROP_MODES, null, null);
    }
    
    /**
     * 
     * @param draggable 
     * @since 2.37
     */
    void userStartedKeyboardDragAndDrop( TopComponentDraggable draggable ) {
        viewRequestor.userStartedKeyboardDragAndDrop( draggable );
    }

    private ModeImpl findJoinedMode( int modeKind, SplitConstraint[] constraints ) {
        for( ModeImpl m : getModes() ) {
            if( m.getKind() != modeKind || m.getState() != Constants.MODE_STATE_JOINED )
                continue;
            
            SplitConstraint[] modeConstraints = m.getConstraints();
            if( modeConstraints.length != constraints.length )
                continue;
            boolean match = true;
            for( int i=0; i<constraints.length; i++ ) {
                if( constraints[i].orientation != modeConstraints[i].orientation 
                    || constraints[i].index != modeConstraints[i].index ) {
                    match = false;
                    break;
                }
            }
            if( match )
                return m;
        }
        return null;
    }
    
    private boolean moveTopComponentIntoMode(ModeImpl mode, TopComponent tc) {
        return moveTopComponentIntoMode(mode, tc, -1);
    }
    
    private boolean moveTopComponentIntoMode(ModeImpl mode, TopComponent tc, int index) {
        boolean moved = false;
        boolean intoSliding = mode.getKind() == Constants.MODE_KIND_SLIDING;
        boolean intoSeparate = mode.getState() == Constants.MODE_STATE_SEPARATED;
        ModeImpl prevMode = null;
        String tcID = WindowManagerImpl.getInstance().findTopComponentID(tc);
        // XXX
        if(!mode.canContain(tc)) {
            return false;
        }
        TopComponentTracker.getDefault().add( tc, mode );
        for(Iterator it = model.getModes().iterator(); it.hasNext(); ) {
            ModeImpl m = (ModeImpl)it.next();
            if(model.containsModeTopComponent(m, tc)) {
                if (m.getKind() == Constants.MODE_KIND_SLIDING ||
                    m.getState() == Constants.MODE_STATE_SEPARATED) {
                    prevMode = model.getModeTopComponentPreviousMode(m, tcID);
                } else {
                    prevMode = m;
                }
                break;
            }
        }
        int prevIndex = prevMode != null && (intoSliding || intoSeparate) ? prevMode.getOpenedTopComponentsIDs().indexOf( tcID ) : -1;
        if(removeTopComponentFromOtherModes(mode, tc)) {
            moved = true;
        }
        if (index > -1) {
            model.insertModeOpenedTopComponent(mode, tc, index);
        } else {
            model.addModeOpenedTopComponent(mode, tc);
        }
        if (prevMode != null && (intoSliding || intoSeparate)) {
            if( intoSeparate && mode.isPermanent() ) {
                //TC is being moved to a undocked mode so from now it will be
                //groupped with other TCs in that mode
                //so change the previous mode to ensure it docks back into
                //the same mode as other TCs in this mode
                List<String> ids = mode.getTopComponentsIDs();
                if( !ids.isEmpty() ) {
                    ModeImpl groupPrevMode = model.getModeTopComponentPreviousMode( mode, ids.get( 0 ) );
                    if( null != groupPrevMode ) {
                        prevMode = groupPrevMode;
                    }
                }
            }
            // remember previous mode and constraints for precise de-auto-hide
            model.setModeTopComponentPreviousMode(mode, tcID, prevMode, prevIndex);
            model.setModeTopComponentPreviousConstraints(mode, tcID, model.getModeConstraints(prevMode));
        }
        if (!intoSliding) {
            // make the target mode active after dragging..
            model.setActiveMode(mode);
            model.setModeSelectedTopComponent(mode, tc);
        } else {
            sortSlidedOutTopComponentsByPrevModes( mode );
            // don't activate sliding modes, it means the component slides out, that's a bad thing..
            // make some other desktop mode active
            if(prevMode != null && prevMode == getActiveMode() 
                   && prevMode.getOpenedTopComponents().isEmpty()) {
                setSomeModeActive();
            }
            // check the drop mode if it was already used, if not, assign it some reasonable size, 
            // according to the current component.
            if (mode.getBounds().width == 0 && mode.getBounds().height == 0) {
                // now we have the sliding mode in initial state
                mode.setBounds(tc.getBounds());
            }            
        }
        //#232061 
        if( null != tc.getClientProperty("windnd_cloned_tc")) {
            tc.putClientProperty("windnd_cloned_tc", null);
            WindowManagerImpl.getInstance().notifyTopComponentOpened(tc);
        }
        return moved;
    }

    private void sortSlidedOutTopComponentsByPrevModes( ModeImpl slidingMode ) {
        if( !Switches.isModeSlidingEnabled() )
            return;
        
        List<String> opened = slidingMode.getOpenedTopComponentsIDs();
    }
    
    private void updateViewAfterDnD(boolean unmaximize) {
        if( unmaximize ) {
            switchMaximizedMode(null);
        }
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_DND_PERFORMED, null, null));
            FloatingWindowTransparencyManager.getDefault().update();
        }
    }

    
    // Sliding
    
   /** Adds mode into model and requests view (if needed). */
    public void addSlidingMode(ModeImpl mode, ModeImpl original, String side, Map<String,Integer> slideInSizes) {
        ModeImpl targetMode = model.getSlidingMode(side);
        if (targetMode != null) {
            //TODO what to do here.. something there already
            return;
        }
            targetMode = WindowManagerImpl.getInstance().createModeImpl(
                ModeImpl.getUnusedModeName(), Constants.MODE_KIND_SLIDING, false);
        
        model.addSlidingMode(mode, side, slideInSizes);
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_MODE_ADDED, null, mode));
        }
        
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManager.PROP_MODES, null, null);
    }    
    
    @Override
    public void userEnabledAutoHide(TopComponent tc, ModeImpl source, String targetSide) {
        if( isViewMaximized() )
            switchMaximizedMode( null );

        String tcID = WindowManagerImpl.getInstance().findTopComponentID(tc);        
        if( isEditorMaximized() )
            setTopComponentDockedInMaximizedMode( tcID, false );
    
        slide( tc, source, targetSide );
    }
    
    /**
     * Slide out the given TopComponent
     */
    void slide(TopComponent tc, ModeImpl source, String targetSide) {
        ModeImpl targetMode = model.getSlidingMode(targetSide);
        if (targetMode == null) {
            targetMode = WindowManagerImpl.getInstance().createModeImpl(
                ModeImpl.getUnusedModeName(), Constants.MODE_KIND_SLIDING, false);
            model.addSlidingMode(targetMode, targetSide, null);
            model.setModeBounds(targetMode, new Rectangle(tc.getBounds()));
        }

        ModeImpl oldActive = getActiveMode();
        moveTopComponentIntoMode(targetMode, tc);
        ModeImpl newActive = getActiveMode();
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_TOPCOMPONENT_AUTO_HIDE_ENABLED, null, null));
        }
        
        if(oldActive != newActive) {
            WindowManagerImpl.getInstance().doFirePropertyChange(
                WindowManagerImpl.PROP_ACTIVE_MODE, oldActive, newActive);
        }
    
        // Notify new active.
        if(newActive != null) {
            // Notify registry.
            WindowManagerImpl.notifyRegistryTopComponentActivated(
                newActive.getSelectedTopComponent());
        } else {
            WindowManagerImpl.notifyRegistryTopComponentActivated(null);
        }        
    }
    
    @Override
    public void userResizedSlidingMode(ModeImpl mode, Rectangle rect) {
        model.setModeBounds(mode, new Rectangle(rect));
        //remember user's settings for the slided-in TopComponent size
        String side = model.getSlidingModeConstraints( mode );
        model.setSlideInSize( side, 
                mode.getSelectedTopComponent(), 
                Constants.BOTTOM.equals( side ) || Constants.TOP.equals(side) ? rect.height : rect.width );
        if( null != mode.getSelectedTopComponent() ) {
            String tcID = WindowManagerImpl.getInstance().findTopComponentID( mode.getSelectedTopComponent() );
            model.setTopComponentMaximizedWhenSlidedIn( tcID, false );
        }
    }
    
    
    @Override
    public void userDisabledAutoHide(TopComponent tc, ModeImpl source) {
        // unmaximize if needed
        if( isViewMaximized() ) {
            switchMaximizedMode(null);
        }
        
        String tcID = WindowManagerImpl.getInstance().findTopComponentID(tc);        
        if( isEditorMaximized() )
            setTopComponentDockedInMaximizedMode( tcID, true );
        
        unSlide( tc, source );
    }
    
    /**
     * Cancel the sliding mode of the given TopComponent.
     */
    private ModeImpl unSlide(TopComponent tc, ModeImpl source) {
        String tcID = WindowManagerImpl.getInstance().findTopComponentID(tc);        
        
        ModeImpl targetMode = model.getModeTopComponentPreviousMode(source, tcID);
        int targetIndex = model.getModeTopComponentPreviousIndex(source, tcID);
        
        if ((targetMode == null) || !model.getModes().contains(targetMode)) {
            // mode to return to isn't valid anymore, try constraints
            SplitConstraint[] constraints = model.getModeTopComponentPreviousConstraints(source, tcID);
            if (constraints != null) {
                // create mode with the same constraints to dock topcomponent back into
                targetMode = WindowManagerImpl.getInstance().createModeImpl(
                        ModeImpl.getUnusedModeName(), source.getKind(), false);
                model.addMode(targetMode, constraints);
            }
        }

        if (targetMode == null) {
            // fallback, previous saved mode not found somehow, use default modes
            targetMode = source.getKind() == Constants.MODE_KIND_EDITOR
                    ? WindowManagerImpl.getInstance().getDefaultEditorMode()
                    : WindowManagerImpl.getInstance().getDefaultViewMode();
        }

        moveTopComponentIntoMode(targetMode, tc, targetIndex);
        
        targetMode.setMinimized( false );
        
        if (source.isEmpty()) {
            model.removeMode(source);
        }
        
        if(isVisible()) {
            viewRequestor.scheduleRequest(
                new ViewRequest(null, View.CHANGE_TOPCOMPONENT_AUTO_HIDE_DISABLED, null, null));
        }
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManagerImpl.PROP_ACTIVE_MODE, null, getActiveMode());
        return targetMode;
    }

    void setTopComponentMinimized( TopComponent tc, boolean minimized ) {
        if( !tc.isOpened() ) {
            return;
        }
        if( isTopComponentMinimized( tc ) == minimized ) {
            return; //nothing todo
        }
        ModeImpl mode = ( ModeImpl ) WindowManagerImpl.getInstance().findMode( tc );
        if( null == mode || mode.getState() != Constants.MODE_STATE_JOINED ) {
            return;
        }
        if( minimized ) {
            slide( tc, mode, guessSlideSide( tc ) );
        } else {
            unSlide( tc, mode );
        }
        //#207438 - make sure global minimize/dock actions get updated
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManager.PROP_MODES, null, null);
    }

    boolean isTopComponentMinimized( TopComponent tc ) {
        if( !tc.isOpened() ) {
            return false;
        }
        ModeImpl mode = ( ModeImpl ) WindowManagerImpl.getInstance().findMode( tc );
        return null != mode && mode.getKind() == Constants.MODE_KIND_SLIDING;
    }
    
    /**
     * @return The mode where the given TopComponent had been before it was moved to sliding or separate mode.
     */
    public ModeImpl getModeTopComponentPreviousMode(String tcID, ModeImpl currentSlidingMode) {
        return  model.getModeTopComponentPreviousMode(currentSlidingMode, tcID);
    }
    
    /**
     * @return The position (tab index) of the given TopComponent before it was moved to sliding or separate mode.
     */
    public int getModeTopComponentPreviousIndex(String tcID, ModeImpl currentSlidingMode) {
        return  model.getModeTopComponentPreviousIndex(currentSlidingMode, tcID);
    }
    
    /**
     * Remember the mode and position where the given TopComponent was before moving into sliding or separate mode.
     * 
     * @param tcID TopComponent's id
     * @param currentSlidingMode The mode where the TopComponent is at the moment.
     * @param prevMode The mode where the TopComponent had been before it was moved to the sliding mode.
     * @param prevIndex Tab index of the TopComponent before it was moved to the new mode.
     */
    public void setModeTopComponentPreviousMode(String tcID, ModeImpl currentSlidingMode, ModeImpl prevMode, int prevIndex) {
        model.setModeTopComponentPreviousMode(currentSlidingMode, tcID, prevMode, prevIndex);
    }
    
    Map<String,Integer> getSlideInSizes( String side ) {
        return model.getSlideInSizes( side );
    }
    
    /**
     * Set the state of the TopComponent when the editor is maximized.
     * 
     * @param tcID TopComponent id
     * @param docked True if the TopComponent should stay docked in maximized editor mode,
     * false if it should slide out when the editor is maximized.
     */
    void setTopComponentDockedInMaximizedMode( String tcID, boolean docked ) {
        if( docked )
            model.getMaximizedDockingStatus().addDocked( tcID );
        else
            model.getMaximizedDockingStatus().addSlided( tcID );
    }
    
    /**
     * Get the state of the TopComponent when the editor is maximized.
     * 
     * @param tcID TopComponent id.
     * @return True if the TopComponent should stay docked in maximized editor mode,
     * false if it should slide out when the editor is maximized.
     */
    boolean isTopComponentDockedInMaximizedMode( String tcID ) {
        return model.getMaximizedDockingStatus().isDocked( tcID );
    }
    
    /**
     * Set the state of the TopComponent when no mode is maximized.
     * 
     * @param tcID TopComponent id
     * @param slided True if the TopComponent is slided in the default mode,
     * false if it is docked.
     */
    void setTopComponentSlidedInDefaultMode( String tcID, boolean slided ) {
        if( slided )
            model.getDefaultDockingStatus().addSlided( tcID );
        else
            model.getDefaultDockingStatus().addDocked( tcID );
    }
    
    /**
     * Get the state of the TopComponent when it is slided-in.
     * 
     * @param tcID TopComponent id. 
     * @return true if the TopComponent is maximized when slided-in.
     */
    boolean isTopComponentMaximizedWhenSlidedIn( String tcID ) {
        return model.isTopComponentMaximizedWhenSlidedIn( tcID );
    }
    
    /**
     * Set the state of the TopComponent when it is slided-in.
     * 
     * @param tcID TopComponent id. 
     * @param maximized true if the TopComponent is maximized when slided-in.
     */
    void setTopComponentMaximizedWhenSlidedIn( String tcID, boolean maximized ) {
        model.setTopComponentMaximizedWhenSlidedIn( tcID, maximized );
    }
    
    void userToggledTopComponentSlideInMaximize( String tcID ) {
        setTopComponentMaximizedWhenSlidedIn( tcID, !isTopComponentMaximizedWhenSlidedIn( tcID ) );
        if( isVisible() ) {
            TopComponent tc = WindowManagerImpl.getInstance().findTopComponent( tcID );
            ModeImpl mode = WindowManagerImpl.getInstance().findModeForOpenedID( tcID );
            if( null != tc && null != mode && null != mode.getSide() ) {
                viewRequestor.scheduleRequest (
                    new ViewRequest(tc, View.CHANGE_MAXIMIZE_TOPCOMPONENT_SLIDE_IN, null, mode.getSide()));
            }
        }
    }
    
    /**
     * Get the state of the TopComponent when no mode is maximized.
     * 
     * @param tcID TopComponent id.
     * @return True if the TopComponent is slided in the default mode,
     * false if it is docked.
     */
    boolean isTopComponentSlidedInDefaultMode( String tcID ) {
        return model.getDefaultDockingStatus().isSlided( tcID );
    }
    
    boolean isEditorMaximized() {
        return null != model.getEditorMaximizedMode();
    }
    
    boolean isViewMaximized() {
        return null != model.getViewMaximizedMode();
    }
    
    /**
     * Slide-out or dock opened TopComponent according to their previous state.
     */
    private void restoreViews( DockingStatus viewStatus ) {
        WindowManagerImpl wm = WindowManagerImpl.getInstance();
        Set<? extends Mode> modes = getModes();
        for( Iterator<? extends Mode> i=modes.iterator(); i.hasNext(); ) {
            ModeImpl modeImpl = (ModeImpl)i.next();
            if( modeImpl.getState() == Constants.MODE_STATE_SEPARATED )
                continue;
            
            if( modeImpl.getKind() == Constants.MODE_KIND_VIEW ) {
                List<TopComponent> views = getModeOpenedTopComponents( modeImpl );
                Collections.reverse( views );
                for( Iterator<TopComponent> j=views.iterator(); j.hasNext(); ) {
                    TopComponent tc = j.next();
                    String tcID = wm.findTopComponentID( tc );
                    if( viewStatus.shouldSlide( tcID ) ) {
                        slide( tc, modeImpl, guessSlideSide( tc ) );
                    }
                }
            } else if( modeImpl.getKind() == Constants.MODE_KIND_SLIDING ) {
                List<TopComponent> views = getModeOpenedTopComponents( modeImpl );
                Collections.reverse( views );
                for( Iterator<TopComponent> j=views.iterator(); j.hasNext(); ) {
                    TopComponent tc = j.next();
                    String tcID = wm.findTopComponentID( tc );
                    if( viewStatus.shouldDock( tcID ) ) {
                        unSlide( tc, modeImpl );
                    }
                }
            }
        }
        
        //now that all views are slided/restore make sure the right views are selected in each mode
        for( Iterator<? extends Mode> i=modes.iterator(); i.hasNext(); ) {
            ModeImpl modeImpl = (ModeImpl)i.next();
            if( modeImpl.getState() == Constants.MODE_STATE_SEPARATED )
                continue;
            
            if( modeImpl.getKind() == Constants.MODE_KIND_VIEW ) {
                //make sure that the same view is selected as before
                TopComponent prevActiveTc = modeImpl.getPreviousSelectedTopComponent();
                if( null != prevActiveTc ) {
                    setModeSelectedTopComponent( modeImpl, prevActiveTc );
                }
            }
        }
    }
    
    /**
     * Slide out all non-editor TopComponents.
     */
    private void slideAllViews() {
        //find appropriate sliding bars first, otherwise the split hierarchy
        //will change while sliding some windows so the sliding positions would be wrong
        Map<TopComponent, String> tc2slideSide = new HashMap<TopComponent, String>(30);
        Set<? extends Mode> modes = getModes();
        for( Iterator<? extends Mode> i=modes.iterator(); i.hasNext(); ) {
            ModeImpl modeImpl = (ModeImpl)i.next();
            if( modeImpl.getKind() == Constants.MODE_KIND_VIEW 
                    && modeImpl != getViewMaximizedMode()
                    && modeImpl.getState() != Constants.MODE_STATE_SEPARATED ) {
                List<TopComponent> views = getModeOpenedTopComponents( modeImpl );
                for( Iterator<TopComponent> j=views.iterator(); j.hasNext(); ) {
                    TopComponent tc = j.next();
                    tc2slideSide.put( tc, guessSlideSide( tc ) );
                }
            }
        }
        for( Iterator<? extends Mode> i=modes.iterator(); i.hasNext(); ) {
            ModeImpl modeImpl = (ModeImpl)i.next();
            if( modeImpl.getKind() == Constants.MODE_KIND_VIEW 
                    && modeImpl != getViewMaximizedMode()
                    && modeImpl.getState() != Constants.MODE_STATE_SEPARATED ) {
                List<TopComponent> views = getModeOpenedTopComponents( modeImpl );
                Collections.reverse( views );
                for( Iterator<TopComponent> j=views.iterator(); j.hasNext(); ) {
                    TopComponent tc = j.next();
                    slide( tc, modeImpl, tc2slideSide.get( tc ) );
                }
            }
        }
    }
    
    // ControllerHandler <<
    ////////////////////////////
    
    /**
     * Creates a new mode and moves the given TopComponent into it.
     * @param tc 
     */
    void newTabGroup( TopComponent tc ) {
        ModeImpl currentMode = ( ModeImpl ) WindowManagerImpl.getInstance().findMode( tc );
        if( null == currentMode ) 
            return;
        ModeImpl newMode = attachModeToSide( currentMode, null, currentMode.getKind() );
        moveTopComponentIntoMode( newMode, tc );
        updateViewAfterDnD( true );
        tc.requestActive();
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManager.PROP_MODES, null, null);
    }

    /**
     * Removes the given mode and moves all its TopComponents to some other mode.
     * @param mode 
     */
    void collapseTabGroup( ModeImpl mode ) {
        ModeImpl neighbor = findClosestNeighbor( mode );
        if( null == neighbor )
            return;
        TopComponent selTC = mode.getSelectedTopComponent();
        mergeModes( mode, neighbor, -1 );
        if( null != selTC )
            selTC.requestActive();
        updateViewAfterDnD( true );
        WindowManagerImpl.getInstance().doFirePropertyChange(
            WindowManager.PROP_MODES, null, null);
    }

    /**
     * Find mode that is closest to the given mode. Usually it's a neighbor at the 
     * same level in the split hierarchy.
     * @param mode
     * @return Closest neighbor to the given mode or null.
     */
    private ModeImpl findClosestNeighbor( ModeImpl mode ) {
        ArrayList<ModeImpl> modes = new ArrayList<ModeImpl>( model.getModes().size() );
        ModeImpl inSplitLeftNeighbor = null;
        ModeImpl inSplitRightNeighbor = null;
        SplitConstraint[] sc = mode.getConstraints();
        int index = sc[sc.length-1].index;
        for( ModeImpl m : model.getModes() ) {
            if( mode == m || m.getKind() != mode.getKind() || m.getState() != mode.getState() )
                continue;
            SplitConstraint[] otherSc = m.getConstraints();
            if( sameSplit( sc, otherSc) ) {
                int otherIndex = otherSc[sc.length-1].index;
                if( index < otherIndex ) {
                    if( null == inSplitLeftNeighbor || otherIndex > inSplitLeftNeighbor.getConstraints()[sc.length-1].index )
                        inSplitLeftNeighbor = m;
                } else {
                    if( null == inSplitRightNeighbor || otherIndex < inSplitRightNeighbor.getConstraints()[sc.length-1].index )
                        inSplitRightNeighbor = m;
                }
            }
            modes.add( m );
        }
        if( modes.isEmpty() )
            return null;
        if( null != inSplitLeftNeighbor )
            return inSplitLeftNeighbor;
        if( null != inSplitRightNeighbor )
            return inSplitRightNeighbor;
        Collections.sort( modes, new Comparator<ModeImpl>() {
            @Override
            public int compare( ModeImpl o1, ModeImpl o2 ) {
                SplitConstraint[] sc1 = o1.getConstraints();
                SplitConstraint[] sc2 = o2.getConstraints();
                return sc1.length - sc2.length;
            }
        });
        return modes.get( 0 );
    }
    
    /**
     * Check if the given constraints point to docking areas at the same level
     * in the split hierarchy.
     * @param sc1
     * @param sc2
     * @return 
     */
    private boolean sameSplit( SplitConstraint[] sc1, SplitConstraint[] sc2 ) {
        if( sc1.length != sc2.length )
            return false;
        for( int i=0; i<sc1.length-1; i++ ) {
            if( sc1[i].orientation != sc2[i].orientation || sc1[i].index != sc2[i].index )
                return false;
        }
        return sc1[sc1.length-1].orientation == sc2[sc2.length-1].orientation;
    }
}
