/*
* ConnectionsPane.java
*
* Copyright (C) 2009-16 by RStudio, Inc.
*
* 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.connections.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.layout.client.Layout.AnimationCallback;
import com.google.gwt.layout.client.Layout.Layer;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.cellview.client.Column;
import com.google.gwt.user.cellview.client.DataGrid;
import com.google.gwt.user.cellview.client.TextColumn;
import com.google.gwt.user.cellview.client.TextHeader;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.view.client.ListDataProvider;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.SelectionChangeEvent;
import com.google.gwt.view.client.SingleSelectionModel;
import com.google.inject.Inject;
import org.rstudio.core.client.BrowseCap;
import org.rstudio.core.client.StringUtil;
import org.rstudio.core.client.cellview.ImageButtonColumn;
import org.rstudio.core.client.command.AppCommand;
import org.rstudio.core.client.command.VisibleChangedHandler;
import org.rstudio.core.client.resources.ImageResource2x;
import org.rstudio.core.client.theme.RStudioDataGridResources;
import org.rstudio.core.client.theme.RStudioDataGridStyle;
import org.rstudio.core.client.theme.res.ThemeStyles;
import org.rstudio.core.client.widget.Base64ImageCell;
import org.rstudio.core.client.widget.OperationWithInput;
import org.rstudio.core.client.widget.SearchWidget;
import org.rstudio.core.client.widget.SecondaryToolbar;
import org.rstudio.core.client.widget.Toolbar;
import org.rstudio.core.client.widget.ToolbarButton;
import org.rstudio.core.client.widget.ToolbarLabel;
import org.rstudio.core.client.widget.ToolbarPopupMenu;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.workbench.commands.Commands;
import org.rstudio.studio.client.workbench.ui.WorkbenchPane;
import org.rstudio.studio.client.workbench.views.connections.ConnectionsPresenter;
import org.rstudio.studio.client.workbench.views.connections.events.ActiveConnectionsChangedEvent;
import org.rstudio.studio.client.workbench.views.connections.events.ExecuteConnectionActionEvent;
import org.rstudio.studio.client.workbench.views.connections.events.ExecuteConnectionActionEvent.Handler;
import org.rstudio.studio.client.workbench.views.connections.events.ExploreConnectionEvent;
import org.rstudio.studio.client.workbench.views.connections.events.PerformConnectionEvent;
import org.rstudio.studio.client.workbench.views.connections.model.Connection;
import org.rstudio.studio.client.workbench.views.connections.model.ConnectionAction;
import org.rstudio.studio.client.workbench.views.connections.model.ConnectionId;
import org.rstudio.studio.client.workbench.views.connections.model.ConnectionOptions;
public class ConnectionsPane extends WorkbenchPane
implements ConnectionsPresenter.Display,
ActiveConnectionsChangedEvent.Handler
{
@Inject
public ConnectionsPane(Commands commands, EventBus eventBus)
{
// initialize
super("Connections");
commands_ = commands;
eventBus_ = eventBus;
// track activation events to update the toolbar
eventBus_.addHandler(ActiveConnectionsChangedEvent.TYPE, this);
// create main panel
mainPanel_ = new LayoutPanel();
mainPanel_.addStyleName("ace_editor_theme");
// create data grid
keyProvider_ = new ProvidesKey<Connection>() {
@Override
public Object getKey(Connection connection)
{
return connection.hashCode();
}
};
selectionModel_ = new SingleSelectionModel<Connection>();
connectionsDataGrid_ = new DataGrid<Connection>(1000, RES, keyProvider_);
connectionsDataGrid_.setSelectionModel(selectionModel_);
selectionModel_.addSelectionChangeHandler(new SelectionChangeEvent.Handler()
{
@Override
public void onSelectionChange(SelectionChangeEvent event)
{
Connection selectedConnection = selectionModel_.getSelectedObject();
if (selectedConnection != null)
fireEvent(new ExploreConnectionEvent(selectedConnection));
}
});
// add type column; this is a package-provided image we scale to 16x16
typeColumn_ = new Column<Connection, String>(new Base64ImageCell(16, 16))
{
@Override
public String getValue(Connection connection)
{
if (StringUtil.isNullOrEmpty(connection.getIconData()))
return null;
return connection.getIconData();
}
};
connectionsDataGrid_.addColumn(typeColumn_, new TextHeader(""));
connectionsDataGrid_.setColumnWidth(typeColumn_, 20, Unit.PX);
// add host column
hostColumn_ = new TextColumn<Connection>() {
@Override
public String getValue(Connection connection)
{
return connection.getDisplayName();
}
};
connectionsDataGrid_.addColumn(hostColumn_, new TextHeader("Connection"));
connectionsDataGrid_.setColumnWidth(hostColumn_, 50, Unit.PCT);
// add status column
statusColumn_ = new TextColumn<Connection>() {
@Override
public String getValue(Connection connection)
{
if (isConnected(connection.getId()))
return "Connected";
else
return "";
}
};
statusColumn_.setCellStyleNames(RES.dataGridStyle().statusColumn());
connectionsDataGrid_.addColumn(statusColumn_, new TextHeader("Status"));
connectionsDataGrid_.setColumnWidth(statusColumn_, 75, Unit.PX);
// add explore column
ImageButtonColumn<Connection> exploreColumn =
new ImageButtonColumn<Connection>(
new ImageResource2x(RES.connectionExploreButton2x()),
new OperationWithInput<Connection>() {
@Override
public void execute(Connection connection)
{
fireEvent(new ExploreConnectionEvent(connection));
}
},
"Explore connection") {
@Override
protected boolean showButton(Connection connection)
{
return connection.getId().getType().equals("Spark");
}
};
connectionsDataGrid_.addColumn(exploreColumn, new TextHeader(""));
connectionsDataGrid_.setColumnWidth(exploreColumn, 30, Unit.PX);
// data provider
dataProvider_ = new ListDataProvider<Connection>();
dataProvider_.addDataDisplay(connectionsDataGrid_);
// add data grid to main panel
mainPanel_.add(connectionsDataGrid_);
mainPanel_.setWidgetTopBottom(connectionsDataGrid_, 0, Unit.PX, 0, Unit.PX);
mainPanel_.setWidgetLeftRight(connectionsDataGrid_, 0, Unit.PX, 0, Unit.PX);
// create connection explorer, add it, and hide it
connectionExplorer_ = new ConnectionExplorer();
connectionExplorer_.setSize("100%", "100%");
mainPanel_.add(connectionExplorer_);
mainPanel_.setWidgetTopBottom(connectionExplorer_, 0, Unit.PX, 0, Unit.PX);
mainPanel_.setWidgetLeftRight(connectionExplorer_, -5000, Unit.PX, 5000, Unit.PX);
// create widget
ensureWidget();
setSecondaryToolbarVisible(false);
}
@Override
public void setConnections(List<Connection> connections)
{
dataProvider_.setList(connections);
sortConnections();
}
@Override
public void setActiveConnections(List<ConnectionId> connections)
{
// update active connection
activeConnections_ = connections;
sortConnections();
// redraw the data grid
connectionsDataGrid_.redraw();
// update explored connection
connectionExplorer_.setConnected(exploredConnection_ != null &&
isConnected(exploredConnection_.getId()));
}
private boolean isConnected(ConnectionId id)
{
for (int i=0; i<activeConnections_.size(); i++)
if (activeConnections_.get(i).equalTo(id))
return true;
return false;
}
@Override
public String getSearchFilter()
{
return searchWidget_.getValue();
}
@Override
public HandlerRegistration addSearchFilterChangeHandler(
ValueChangeHandler<String> handler)
{
return searchWidget_.addValueChangeHandler(handler);
}
@Override
public HandlerRegistration addExploreConnectionHandler(
ExploreConnectionEvent.Handler handler)
{
return addHandler(handler, ExploreConnectionEvent.TYPE);
}
@Override
public HandlerRegistration addExecuteConnectionActionHandler(Handler handler)
{
return addHandler(handler, ExecuteConnectionActionEvent.TYPE);
}
@Override
public void showConnectionExplorer(final Connection connection,
String connectVia)
{
selectionModel_.clear();
setConnection(connection, connectVia);
installConnectionExplorerToolbar(connection);
transitionMainPanel(
connectionsDataGrid_,
connectionExplorer_,
true,
true,
new Command() {
@Override
public void execute()
{
connectionExplorer_.onResize();
}
});
}
@Override
public void setExploredConnection(Connection connection)
{
setConnection(connection, connectionExplorer_.getConnectVia());
}
private void setConnection(Connection connection, String connectVia)
{
exploredConnection_ = connection;
if (exploredConnection_ != null)
{
connectionExplorer_.setConnection(connection, connectVia);
connectionExplorer_.setConnected(isConnected(connection.getId()));
}
}
@Override
public void updateExploredConnection(String hint)
{
connectionExplorer_.updateObjectBrowser(hint);
}
@Override
public void showConnectionsList(boolean animate)
{
exploredConnection_ = null;
installConnectionsToolbar();
transitionMainPanel(
connectionExplorer_,
connectionsDataGrid_,
false,
animate,
new Command() {
@Override
public void execute()
{
}
});
}
@Override
public HasClickHandlers backToConnectionsButton()
{
return backToConnectionsButton_;
}
@Override
public String getConnectVia()
{
return connectionExplorer_.getConnectVia();
}
@Override
public String getConnectCode()
{
return connectionExplorer_.getConnectCode();
}
@Override
public void showConnectionProgress()
{
connectionExplorer_.showConnectionProgress();
}
@Override
public void onResize()
{
connectionExplorer_.onResize();
}
@Override
protected Toolbar createMainToolbar()
{
toolbar_ = new Toolbar();
searchWidget_ = new SearchWidget(new SuggestOracle() {
@Override
public void requestSuggestions(Request request, Callback callback)
{
// no suggestions
callback.onSuggestionsReady(
request,
new Response(new ArrayList<Suggestion>()));
}
});
backToConnectionsButton_ = new ToolbarButton(
commands_.helpBack().getImageResource(), (ClickHandler)null);
backToConnectionsButton_.setTitle("View all connections");
// connect meuu
ToolbarPopupMenu connectMenu = new ToolbarPopupMenu();
connectMenu.addItem(connectMenuItem(
commands_.historySendToConsole().getImageResource(),
"R Console",
ConnectionOptions.CONNECT_R_CONSOLE));
connectMenu.addSeparator();
connectMenu.addItem(connectMenuItem(
commands_.newSourceDoc().getImageResource(),
"New R Script",
ConnectionOptions.CONNECT_NEW_R_SCRIPT));
connectMenu.addItem(connectMenuItem(
commands_.newRNotebook().getImageResource(),
"New R Notebook",
ConnectionOptions.CONNECT_NEW_R_NOTEBOOK));
if (BrowseCap.INSTANCE.canCopyToClipboard())
{
connectMenu.addSeparator();
connectMenu.addItem(connectMenuItem(
commands_.copyPlotToClipboard().getImageResource(),
"Copy to Clipboard",
ConnectionOptions.CONNECT_COPY_TO_CLIPBOARD));
}
connectMenuButton_ = new ToolbarButton(
"Connect",
commands_.newConnection().getImageResource(),
connectMenu);
// manage connect menu visibility
connectMenuButton_.setVisible(!commands_.disconnectConnection().isVisible());
commands_.disconnectConnection().addVisibleChangedHandler(
new VisibleChangedHandler() {
@Override
public void onVisibleChanged(AppCommand command)
{
connectMenuButton_.setVisible(
!commands_.disconnectConnection().isVisible());
}
});
installConnectionsToolbar();
return toolbar_;
}
@Override
protected SecondaryToolbar createSecondaryToolbar()
{
secondaryToolbar_ = new SecondaryToolbar();
secondaryToolbar_.addLeftWidget(connectionName_ = new ToolbarLabel());
connectionIcon_ = new Image();
connectionIcon_.setWidth("16px");
connectionIcon_.setHeight("16px");
connectionType_ = new ToolbarLabel();
connectionType_.getElement().getStyle().setMarginLeft(5, Unit.PX);
connectionType_.getElement().getStyle().setMarginRight(10, Unit.PX);
secondaryToolbar_.addRightWidget(connectionIcon_);
secondaryToolbar_.addRightWidget(connectionType_);
ThemeStyles styles = ThemeStyles.INSTANCE;
secondaryToolbar_.getWrapper().addStyleName(styles.tallerToolbarWrapper());
return secondaryToolbar_;
}
@Override
protected Widget createMainWidget()
{
return mainPanel_;
}
@Override
public void onBeforeSelected()
{
super.onBeforeSelected();
connectionsDataGrid_.redraw();
}
private MenuItem connectMenuItem(ImageResource icon,
String text,
final String connectVia)
{
return new MenuItem(
AppCommand.formatMenuLabel(icon, text, null),
true,
new Scheduler.ScheduledCommand() {
@Override
public void execute()
{
eventBus_.fireEvent(
new PerformConnectionEvent(
connectVia,
connectionExplorer_.getConnectCode()));
}
});
}
private void transitionMainPanel(
final Widget from,
final Widget to,
boolean rightToLeft,
boolean animate,
final Command onComplete)
{
assert from != to;
int width = getOffsetWidth();
mainPanel_.setWidgetLeftWidth(from,
0, Unit.PX,
width, Unit.PX);
mainPanel_.setWidgetLeftWidth(to,
rightToLeft ? width : -width, Unit.PX,
width, Unit.PX);
mainPanel_.forceLayout();
mainPanel_.setWidgetLeftWidth(from,
rightToLeft ? -width : width, Unit.PX,
width, Unit.PX);
mainPanel_.setWidgetLeftWidth(to,
0, Unit.PX,
width, Unit.PX);
to.setVisible(true);
from.setVisible(true);
final Command completeLayout = new Command() {
@Override
public void execute()
{
mainPanel_.setWidgetLeftRight(to, 0, Unit.PX, 0, Unit.PX);
from.setVisible(false);
mainPanel_.forceLayout();
onComplete.execute();
}
};
if (animate)
{
mainPanel_.animate(300, new AnimationCallback()
{
public void onAnimationComplete()
{
completeLayout.execute();
}
public void onLayout(Layer layer, double progress)
{
}
});
}
else
{
completeLayout.execute();
}
}
private void installConnectionsToolbar()
{
toolbar_.removeAllWidgets();
toolbar_.addLeftWidget(commands_.newConnection().createToolbarButton());
toolbar_.addLeftSeparator();
toolbar_.addRightWidget(searchWidget_);
setSecondaryToolbarVisible(false);
}
private void installConnectionExplorerToolbar(final Connection connection)
{
toolbar_.removeAllWidgets();
toolbar_.addLeftWidget(backToConnectionsButton_);
toolbar_.addLeftSeparator();
toolbar_.addLeftWidget(connectMenuButton_);
toolbar_.addLeftSeparator();
if (isConnected(connection.getId()) && connection.getActions() != null)
{
// if we have any actions, create a toolbar button for each one
for (int i = 0; i < connection.getActions().length(); i++)
{
final ConnectionAction action = connection.getActions().get(i);
// use the supplied base64 icon data if it was provided
Image icon = StringUtil.isNullOrEmpty(action.getIconData()) ?
null :
new Image(action.getIconData());
// force to 20x18
if (icon != null)
{
icon.setWidth("20px");
icon.setHeight("18px");
}
ToolbarButton button = new ToolbarButton(action.getName(),
icon, // left image
null, // right image
// invoke the action when the button is clicked
new ClickHandler()
{
@Override
public void onClick(ClickEvent arg0)
{
fireEvent(new ExecuteConnectionActionEvent(
connection.getId(), action.getName()));
}
});
// move the toolbar button up 5px to account for missing icon if
// none was supplied
if (StringUtil.isNullOrEmpty(action.getIconData()))
button.getElement().getStyle().setMarginTop(-5, Unit.PX);
toolbar_.addLeftWidget(button);
toolbar_.addLeftSeparator();
}
}
toolbar_.addLeftWidget(commands_.disconnectConnection().createToolbarButton());
toolbar_.addRightWidget(commands_.removeConnection().createToolbarButton());
ToolbarButton refreshButton = commands_.refreshConnection().createToolbarButton();
refreshButton.addStyleName(ThemeStyles.INSTANCE.refreshToolbarButton());
toolbar_.addRightWidget(refreshButton);
connectionName_.setText(connection.getDisplayName());
connectionIcon_.setUrl(connection.getIconData());
connectionType_.setText(connection.getId().getType());
setSecondaryToolbarVisible(true);
}
private void sortConnections()
{
// order the list
List<Connection> connections = dataProvider_.getList();
Collections.sort(connections, new Comparator<Connection>() {
@Override
public int compare(Connection conn1, Connection conn2)
{
// values to use in comparison
boolean conn1Connected = isConnected(conn1.getId());
boolean conn2Connected = isConnected(conn2.getId());
if (conn1Connected && !conn2Connected)
return -1;
else if (conn2Connected && !conn1Connected)
return 1;
else
return -1 * Double.compare(conn1.getLastUsed(), conn2.getLastUsed());
}
});
}
private Toolbar toolbar_;
private final LayoutPanel mainPanel_;
private final DataGrid<Connection> connectionsDataGrid_;
private final SingleSelectionModel<Connection> selectionModel_;
private final ConnectionExplorer connectionExplorer_;
private Connection exploredConnection_ = null;
private final Column<Connection, String> typeColumn_;
private final TextColumn<Connection> hostColumn_;
private final TextColumn<Connection> statusColumn_;
private final ProvidesKey<Connection> keyProvider_;
private final ListDataProvider<Connection> dataProvider_;
private List<ConnectionId> activeConnections_ = new ArrayList<ConnectionId>();
private SearchWidget searchWidget_;
private ToolbarButton backToConnectionsButton_;
private ToolbarButton connectMenuButton_;
private SecondaryToolbar secondaryToolbar_;
private ToolbarLabel connectionName_;
private Image connectionIcon_;
private ToolbarLabel connectionType_;
private final Commands commands_;
private final EventBus eventBus_;
// Resources, etc ----
public interface Resources extends RStudioDataGridResources
{
@Source({RStudioDataGridStyle.RSTUDIO_DEFAULT_CSS, "ConnectionsListDataGridStyle.css"})
Styles dataGridStyle();
@Source("connectionExploreButton_2x.png")
ImageResource connectionExploreButton2x();
}
public interface Styles extends RStudioDataGridStyle
{
String statusColumn();
}
private static final Resources RES = GWT.create(Resources.class);
static {
RES.dataGridStyle().ensureInjected();
}
@Override
public void onActiveConnectionsChanged(ActiveConnectionsChangedEvent event)
{
activeConnections_.clear();
JsArray<ConnectionId> connections = event.getActiveConnections();
for (int idxConn = 0; idxConn < connections.length(); idxConn++) {
activeConnections_.add(connections.get(idxConn));
}
sortConnections();
installConnectionExplorerToolbar(exploredConnection_);
}
}