/*
* TerminalPopupMenu.java
*
* Copyright (C) 2009-17 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.workbench.views.terminal;
import org.rstudio.core.client.Debug;
import org.rstudio.core.client.StringUtil;
import org.rstudio.core.client.command.AppCommand;
import org.rstudio.core.client.widget.ToolbarButton;
import org.rstudio.core.client.widget.ToolbarPopupMenu;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.common.icons.StandardIcons;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.model.WorkbenchServerOperations;
import org.rstudio.studio.client.workbench.views.terminal.events.SwitchToTerminalEvent;
import org.rstudio.studio.client.workbench.views.terminal.events.TerminalBusyEvent;
import org.rstudio.studio.client.server.ServerError;
import org.rstudio.studio.client.server.ServerRequestCallback;
import org.rstudio.studio.client.server.Void;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.inject.Inject;
/**
* Drop-down menu used in terminal pane. Has commands, and a list of
* terminal sessions.
*/
public class TerminalPopupMenu extends ToolbarPopupMenu
{
public TerminalPopupMenu(TerminalList terminals)
{
RStudioGinjector.INSTANCE.injectMembers(this);
terminals_ = terminals;
eventBus_.addHandler(TerminalBusyEvent.TYPE,
new TerminalBusyEvent.Handler()
{
@Override
public void onTerminalBusy(TerminalBusyEvent event)
{
refreshActiveTerminal();
}
});
}
@Inject
private void initialize(Commands commands,
EventBus events,
WorkbenchServerOperations server)
{
commands_ = commands;
eventBus_ = events;
server_ = server;
}
@Override
public void getDynamicPopupMenu(final DynamicPopupMenuCallback callback)
{
// clean out existing entries
clearItems();
addItem(commands_.newTerminal().createMenuItem(false));
addSeparator();
if (terminals_.terminalCount() > 0)
{
for (final String handle : terminals_)
{
Scheduler.ScheduledCommand cmd = new Scheduler.ScheduledCommand()
{
@Override
public void execute()
{
eventBus_.fireEvent(new SwitchToTerminalEvent(handle));
}
};
String caption = trimCaption(terminals_.getCaption(handle));
if (caption == null)
{
continue;
}
// visual indicator that terminal has processes running
caption = addBusyIndicator(caption, terminals_.getHasSubprocs(handle));
String menuHtml = AppCommand.formatMenuLabel(
null, /*icon*/
caption, /*label*/
false, /*html*/
null, /*shortcut*/
null, /*rightImage*/
null); /*rightImageDesc*/
addItem(new MenuItem(menuHtml, true, cmd));
}
addSeparator();
addItem(commands_.renameTerminal().createMenuItem(false));
addItem(commands_.showTerminalInfo().createMenuItem(false));
addSeparator();
addItem(commands_.previousTerminal().createMenuItem(false));
addItem(commands_.nextTerminal().createMenuItem(false));
addSeparator();
addItem(commands_.clearTerminalScrollbackBuffer().createMenuItem(false));
addItem(commands_.closeTerminal().createMenuItem(false));
}
callback.onPopupMenu(this);
}
public ToolbarButton getToolbarButton()
{
if (toolbarButton_ == null)
{
String buttonText = "Terminal";
toolbarButton_ = new ToolbarButton(
buttonText,
StandardIcons.INSTANCE.empty_command(),
this,
false);
setNoActiveTerminal();
}
return toolbarButton_;
}
/**
*
* @param caption caption of the active terminal
* @param handle handle of the active terminal
*/
public void setActiveTerminal(String caption, String handle)
{
activeTerminalHandle_ = handle;
String trimmed = trimCaption(caption);
if (handle != null)
{
trimmed = addBusyIndicator(trimmed, terminals_.getHasSubprocs(handle));
}
toolbarButton_.setText(trimmed);
// Update terminal commands based on current selection
boolean haveActiveTerminal = activeTerminalHandle_ != null;
commands_.closeTerminal().setEnabled(haveActiveTerminal);
commands_.renameTerminal().setEnabled(haveActiveTerminal);
commands_.clearTerminalScrollbackBuffer().setEnabled(haveActiveTerminal);
commands_.showTerminalInfo().setEnabled(haveActiveTerminal);
commands_.previousTerminal().setEnabled(getPreviousTerminalHandle() != null);
commands_.nextTerminal().setEnabled(getNextTerminalHandle() != null);
// inform server of the selection
server_.processNotifyVisible(activeTerminalHandle_, new ServerRequestCallback<Void>() {
@Override
public void onError(ServerError error)
{
Debug.logError(error);
}
});
}
public void setActiveTerminalByCaption(String caption)
{
String handle = terminals_.handleForCaption(caption);
if (StringUtil.isNullOrEmpty(handle))
return;
eventBus_.fireEvent(new SwitchToTerminalEvent(handle));
}
/**
* Refresh caption of active terminal based on busy status.
* @param caption caption of the active terminal
* @param handle handle of the active terminal
*/
private void refreshActiveTerminal()
{
if (toolbarButton_ == null || activeTerminalHandle_ == null)
return;
String caption = terminals_.getCaption(activeTerminalHandle_);
if (caption == null)
return;
toolbarButton_.setText(addBusyIndicator(trimCaption(caption),
terminals_.getHasSubprocs(activeTerminalHandle_)));
}
/**
* set state to indicate no active terminals
*/
public void setNoActiveTerminal()
{
setActiveTerminal("Terminal", null);
}
/**
* @return Handle of active terminal, or null if no active terminal.
*/
public String getActiveTerminalHandle()
{
return activeTerminalHandle_;
}
/**
* Switch to previous terminal tab.
*/
public void previousTerminal()
{
String prevHandle = getPreviousTerminalHandle();
if (prevHandle != null)
{
eventBus_.fireEvent(new SwitchToTerminalEvent(prevHandle));
}
}
/**
* Switch to next terminal tab.
*/
public void nextTerminal()
{
String nextHandle = getNextTerminalHandle();
if (nextHandle != null)
{
eventBus_.fireEvent(new SwitchToTerminalEvent(nextHandle));
}
}
/**
* Add indicator of busy status to a caption.
* @param caption
* @return Caption with busy indicator added.
*/
private String addBusyIndicator(String caption, boolean busy)
{
if (busy)
return caption + " (busy)";
else
return caption;
}
private String trimCaption(String caption)
{
// TODO (gary) look at doing this via css text-overflow
// when I do the theming work
// Enforce a sane visual limit on terminal captions
if (caption.length() > 32)
{
caption = caption.substring(0, 31) + "...";
}
return caption;
}
/**
* @return handle of previous terminal or null if there is no previous
* terminal
*/
private String getPreviousTerminalHandle()
{
if (terminals_.terminalCount() > 0 && activeTerminalHandle_ != null)
{
String prevHandle = null;
for (final String handle : terminals_)
{
if (activeTerminalHandle_.equals(handle))
{
if (prevHandle == null)
{
return null;
}
return prevHandle;
}
else
{
prevHandle = handle;
}
}
}
return null;
}
/**
* @return handle of next terminal or null if no next terminal
*/
private String getNextTerminalHandle()
{
if (terminals_.terminalCount() > 0 && activeTerminalHandle_ != null)
{
boolean foundCurrent = false;
for (final String handle : terminals_)
{
if (foundCurrent == true)
{
return handle;
}
if (activeTerminalHandle_.equals(handle))
{
foundCurrent = true;
}
}
}
return null;
}
private ToolbarButton toolbarButton_;
private String activeTerminalHandle_;
private TerminalList terminals_;
// Injected ----
private Commands commands_;
private EventBus eventBus_;
private WorkbenchServerOperations server_;
}