Package org.rstudio.studio.client.common.satellite

Source Code of org.rstudio.studio.client.common.satellite.SatelliteManager$ActiveSatellite

/*
* SatelliteManager.java
*
* Copyright (C) 2009-12 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.studio.client.common.satellite;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;

import com.google.inject.Provider;

import org.rstudio.core.client.BrowseCap;
import org.rstudio.core.client.Debug;
import org.rstudio.core.client.Point;
import org.rstudio.core.client.Size;
import org.rstudio.core.client.command.AppCommand;
import org.rstudio.core.client.dom.WindowEx;
import org.rstudio.core.client.layout.ScreenUtils;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.application.ApplicationUncaughtExceptionHandler;
import org.rstudio.studio.client.application.Desktop;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.common.GlobalDisplay.NewWindowOptions;
import org.rstudio.studio.client.common.satellite.events.WindowClosedEvent;
import org.rstudio.studio.client.common.satellite.events.WindowOpenedEvent;
import org.rstudio.studio.client.workbench.model.Session;

import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Document;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.Window;
import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
public class SatelliteManager implements CloseHandler<Window>
{
   @Inject
   public SatelliteManager(
         Session session,
         EventBus events,
         Provider<ApplicationUncaughtExceptionHandler> pUncaughtExceptionHandler)
   {
      session_ = session;
      events_ = events;
      pUncaughtExceptionHandler_ = pUncaughtExceptionHandler;
   }
  
   // the main window should call this method during startup to set itself
   // up to manage and communicate with the satellite windows
   public void initialize()
   {
      // export the registration hook used by satellites
      exportSatelliteRegistrationCallback();
     
      // handle onClosed to automatically close all satellites
      Window.addCloseHandler(this);
   }
   
   // open a satellite window (re-activate existing if possible)
   public void openSatellite(String name,
                             JavaScriptObject params,
                             Size preferredSize)
   {
      openSatellite(name, params, preferredSize, true, null);
   }

   // open a satellite window (re-activate existing if possible)
   private void openSatellite(String name,
                             JavaScriptObject params,
                             Size preferredSize,
                             boolean adjustSize,
                             Point position)
   {
      // satellites can't launch other satellites -- this is because the
      // delegating/forwarding of remote server calls and events doesn't
      // cascade correctly -- it wouldn't be totally out of the question
      // to make this work but we'd rather not have this complexity
      // if we don't need to.
      if (isCurrentWindowSatellite())
      {
         Debug.log("Satellite windows can't launch other satellites");
         assert false;
         return;
      }

      // check for a re-activation of an existing window
      for (ActiveSatellite satellite : satellites_)
      {
         if (satellite.getName().equals(name))
         {
            WindowEx window = satellite.getWindow();
            if (!window.isClosed())
            {
               // for web mode bring the window to the front, notify
               // it that it has been reactivated, then exit.
               if (!Desktop.isDesktop())
               {
                  // don't do this for chrome (since it doesn't allow
                  // window.focus). for chrome we'll just fall through
                  // and openSatelliteWindow will be called and the
                  // window will be reloaded)
                  if (!BrowseCap.isChrome())
                  {
                     window.focus();
                     callNotifyReactivated(window, params);
                     return;
                  }
                  else
                  {
                     // for Chrome, let the window know it's about to be
                     // closed and reopened
                     callNotifyPendingReactivate(window);
                  }
               }
               // desktop mode: activate and return
               else
               {
                  Desktop.getFrame().activateSatelliteWindow(
                    SatelliteUtils.getSatelliteWindowName(satellite.getName()));
                  callNotifyReactivated(window, params);
                  return;
               }
            }
         }
      }
     
      // Start buffering events sent to this satellite. That way, we won't miss
      // anything while the satellite is being loaded/reactivated
      if (!pendingEventsBySatelliteName_.containsKey(name))
      {
         pendingEventsBySatelliteName_.put(name,
                                           new ArrayList<JavaScriptObject>());
      }

      // record satellite params for subsequent setting (this value is read
      // by the satellite within the call to registerAsSatellite)
      if (params != null)
         satelliteParams_.put(name, params);
      // set size and position, if desired
      Size windowSize = adjustSize ?
            ScreenUtils.getAdjustedWindowSize(preferredSize) :
            preferredSize;
      NewWindowOptions options = new NewWindowOptions();
      if (position != null)
         options.setPosition(position);

      // open the satellite - it will call us back on registerAsSatellite
      // at which time we'll call setSessionInfo, setParams, etc.
      RStudioGinjector.INSTANCE.getGlobalDisplay().openSatelliteWindow(
                                              name,
                                              windowSize.width,
                                              windowSize.height,
                                              options);
   }
  
   // Forcefully reopen a satellite window. This refreshes the window and
   // pushes it to the front in Chrome. It should be used as a last resort;
   // if responding to a UI event, use openSatellite instead, since Chrome
   // permits window.open to reactivate windows in that context.
   public void forceReopenSatellite(final String name,
                                    final JavaScriptObject params)
   {
      Size preferredSize = null;
      Point preferredPos = null;
      for (ActiveSatellite satellite : satellites_)
      {
         if (satellite.getName().equals(name) &&
             !satellite.getWindow().isClosed())
         {
            // save the window's geometry so we can restore it after the window
            // is destroyed
            final WindowEx win = satellite.getWindow();
            Document doc = win.getDocument();
            preferredSize = new Size(doc.getClientWidth(), doc.getClientHeight());
            preferredPos = new Point(win.getLeft(), win.getTop());
            callNotifyPendingReactivate(win);

            // close the window
            try
            {
               win.close();
            }
            catch(Throwable e)
            {
            }
            break;
         }
      }  
     
      // didn't find an open window to reopen
      if (preferredSize == null)
         return;
     
      // open a new window with the same geometry as the one we just destroyed,
      // but with the newly supplied set of parameters
      final Size windowSize = preferredSize;
      final Point windowPos = preferredPos;
      openSatellite(name, params, windowSize, false, windowPos);
   }

   public boolean satelliteWindowExists(String name)
   {
      return getSatelliteWindowObject(name) != null;
   }
  
   public WindowEx getSatelliteWindowObject(String name)
   {
      for (ActiveSatellite satellite : satellites_)
         if (satellite.getName().equals(name) &&
             !satellite.getWindow().isClosed())
            return satellite.getWindow();
           
      return null;
   }
  
   public void activateSatelliteWindow(String name)
   {
      if (Desktop.isDesktop())
      {
         Desktop.getFrame().activateSatelliteWindow(
               SatelliteUtils.getSatelliteWindowName(name));
      }
      else
      {
         for (ActiveSatellite satellite : satellites_)
         {
            if (satellite.getName().equals(name) &&
                !satellite.getWindow().isClosed())
            {
               satellite.getWindow().focus();
               break;
            }
         }  
      }
   }
  
   // close all satellite windows
   public void closeAllSatellites()
   {
      for (ActiveSatellite satellite : satellites_)
      {
         try
         {
            satellite.getWindow().close();
         }
         catch(Throwable e)
         {
         }
      }
      satellites_.clear();
      pendingEventsBySatelliteName_.clear();
   }
  
   // close one satellite window
   public void closeSatelliteWindow(String name)
   {
      for (ActiveSatellite satellite : satellites_)
      {
         if (satellite.getName().equals(name) &&
             !satellite.getWindow().isClosed())
         {
            try
            {
               satellite.getWindow().close();
            }
            catch(Throwable e)
            {
            }
            break;
         }
      }  
   }
  
   // dispatch an event to all satellites
   public void dispatchEvent(JavaScriptObject clientEvent)
   {
      // list of windows to remove (because they were closed)
      ArrayList<ActiveSatellite> removeWindows = null;
       
      // iterate over the satellites (make a copy to avoid races if
      // for some reason firing an event creates or destroys a satellite)
      @SuppressWarnings("unchecked")
      ArrayList<ActiveSatellite> satellites =
                           (ArrayList<ActiveSatellite>)satellites_.clone();
      for (ActiveSatellite satellite : satellites)
      {
         try
         {
            // If we're buffering events for this satellite, then don't dispatch
            // them
            if (pendingEventsBySatelliteName_.containsKey(satellite.getName()))
               continue;

            WindowEx satelliteWnd = satellite.getWindow();
            if (satelliteWnd.isClosed())
            {
               if (removeWindows == null)
                  removeWindows = new ArrayList<ActiveSatellite>();
               removeWindows.add(satellite);
            }
            else
            {
               callDispatchEvent(satelliteWnd, clientEvent);
            }
         }
         catch(Throwable e)
         {
         }
      }

      for (Entry<String, ArrayList<JavaScriptObject>> entry :
                                       pendingEventsBySatelliteName_.entrySet())
      {
         entry.getValue().add(clientEvent);
      }
     
      // remove windows if necessary
      if (removeWindows != null)
      {
         for (ActiveSatellite satellite : removeWindows)
         {
            satellites_.remove(satellite);
         }
      }
   }
  
   // dispatch a command to all satellites.
   public void dispatchCommand(AppCommand command)
   {
      for (ActiveSatellite satellite: satellites_)
      {
         callDispatchCommand(satellite.getWindow(), command.getId());
      }
   }

   // close all satellites when we are closed
   @Override
   public void onClose(CloseEvent<Window> event)
   {
      closeAllSatellites();
   }
  
   // call notifyPendingReactivate on a satellite
   public native static void callNotifyPendingReactivate(JavaScriptObject satellite) /*-{
      satellite.notifyPendingReactivate();
   }-*/;

   // called by satellites to connect themselves with the main window
   private void registerAsSatellite(final String name, JavaScriptObject wnd)
   {
      // get the satellite and add it to our list. in some cases (such as
      // the Ctrl+R reload of an existing satellite window) we actually
      // already have a reference to this satellite in our list so in that
      // case we make sure not to add a duplicate
      WindowEx satelliteWnd = wnd.<WindowEx>cast();
      ActiveSatellite satellite = new ActiveSatellite(name, satelliteWnd);
      if (!satellites_.contains(satellite))
         satellites_.add(satellite);
     
      // call setSessionInfo
      callSetSessionInfo(satelliteWnd, session_.getSessionInfo());
     
      // call setParams
      JavaScriptObject params = satelliteParams_.get(name);
      if (params != null)
         callSetParams(satelliteWnd, params);
   }
  
   // called to register child windows (not necessarily full-fledged
   // satellites). only used in desktop mode, since in server mode we have the
   // child window object as a return value from window.open.
   private void registerDesktopChildWindow (String name, JavaScriptObject window)
   {
      events_.fireEvent(new WindowOpenedEvent(name, (WindowEx) window.cast()));
   }
  
   private void unregisterDesktopChildWindow (String name)
   {
      if (SatelliteUtils.windowNameIsSatellite(name))
         name = SatelliteUtils.getWindowNameFromSatelliteName(name);
      events_.fireEvent(new WindowClosedEvent(name));
   }

   private void flushPendingEvents(String name)
   {
      ArrayList<JavaScriptObject> events =
                                    pendingEventsBySatelliteName_.remove(name);

      if (events == null || events.size() == 0)
         return;

      for (ActiveSatellite satellite :
                                    new ArrayList<ActiveSatellite>(satellites_))
      {
         if (satellite.getName().equals(name)
             && !satellite.getWindow().isClosed())
         {
            for (JavaScriptObject evt : events)
            {
               try
               {
                  callDispatchEvent(satellite.getWindow(), evt);
               }
               catch (Exception e)
               {
                  pUncaughtExceptionHandler_.get().onUncaughtException(e);
               }
            }
         }
      }
   }
  
   // export the global function required for satellites to register
   private native void exportSatelliteRegistrationCallback() /*-{
      var manager = this;    
      $wnd.registerAsRStudioSatellite = $entry(
         function(name, satelliteWnd) {
            manager.@org.rstudio.studio.client.common.satellite.SatelliteManager::registerAsSatellite(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(name, satelliteWnd);
         }
      );
      $wnd.flushPendingEvents = $entry(
         function(name) {
            manager.@org.rstudio.studio.client.common.satellite.SatelliteManager::flushPendingEvents(Ljava/lang/String;)(name);
         }
      );
      $wnd.registerDesktopChildWindow = $entry(
         function(name, wnd) {
            manager.@org.rstudio.studio.client.common.satellite.SatelliteManager::registerDesktopChildWindow(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;)(name, wnd);
         }
      );
      $wnd.unregisterDesktopChildWindow = $entry(
         function(name, wnd) {
            manager.@org.rstudio.studio.client.common.satellite.SatelliteManager::unregisterDesktopChildWindow(Ljava/lang/String;)(name);
         }
      );
   }-*/;
  
   // call setSessionInfo on a satellite
   private native void callSetSessionInfo(JavaScriptObject satellite,
                                          JavaScriptObject sessionInfo) /*-{
      satellite.setRStudioSatelliteSessionInfo(sessionInfo);
   }-*/;
  
   // call setParams on a satellite
   private native void callSetParams(JavaScriptObject satellite,
                                     JavaScriptObject params) /*-{
      satellite.setRStudioSatelliteParams(params);
   }-*/;
  
   // call notifyReactivated on a satellite
   private native void callNotifyReactivated(JavaScriptObject satellite,
                                         JavaScriptObject params) /*-{
      satellite.notifyRStudioSatelliteReactivated(params);
   }-*/;
  
   // dispatch event to a satellite
   private native void callDispatchEvent(JavaScriptObject satellite,
                                         JavaScriptObject clientEvent) /*-{
      satellite.dispatchEventToRStudioSatellite(clientEvent);
   }-*/;
  
   // dispatch command to a satellite
   private native void callDispatchCommand(JavaScriptObject satellite,
                                           String commandId) /*-{
      satellite.dispatchCommandToRStudioSatellite(commandId);
   }-*/;
  
   // check whether the current window is a satellite (note this method
   // is also implemented in the Satellite class -- we don't want this class
   // to depend on Satellite so we duplicate the definition)
   private native boolean isCurrentWindowSatellite() /*-{
      return !!$wnd.isRStudioSatellite;
   }-*/;
  
  
   // alert callback (used for testing html preview sandbox)
   //private void showAlert(String message)
   //{
   //   RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage("Alert",
   //                                                                 message);
   //}
  
   //private native void exportSatelliteAlertCallback() /*-{
   //   var manager = this;
   //   $wnd.rstudioSatelliteAlert = $entry(
   //      function(message) {
   //        manager.@org.rstudio.studio.client.common.satellite.SatelliteManager::showAlert(Ljava/lang/String;)(message);
   //      }
   //   );
   //}-*/;
  
   private final Session session_;
   private final EventBus events_;
   private final Provider<ApplicationUncaughtExceptionHandler> pUncaughtExceptionHandler_;
   private final ArrayList<ActiveSatellite> satellites_ =
                                          new ArrayList<ActiveSatellite>();
  
   private final HashMap<String,JavaScriptObject> satelliteParams_ =
                                new HashMap<String,JavaScriptObject>();

   private final HashMap<String, ArrayList<JavaScriptObject>>
         pendingEventsBySatelliteName_ = new HashMap<String, ArrayList<JavaScriptObject>>();

   private class ActiveSatellite
   {
      public ActiveSatellite(String name, WindowEx window)
      {
         name_ = name;
         window_ = window;
      }
     
      public String getName()
      {
         return name_;
      }
     
      public WindowEx getWindow()
      {
         return window_;
      }
     
      @Override
      public boolean equals(Object other)
      {
         if (other == null)
            return false;
        
         ActiveSatellite otherSatellite = (ActiveSatellite)other;
        
         return getName().equals(otherSatellite.getName()) &&
                getWindow().equals(otherSatellite.getWindow());
      }
     
      private final String name_;
      private final WindowEx window_;
   }
  
}

TOP

Related Classes of org.rstudio.studio.client.common.satellite.SatelliteManager$ActiveSatellite

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.