CHE-5735 Provide consistent "new tab" behaviour (#6156)

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour

* CHE-5735 Provide consistent "new tab" behaviour
6.19.x
Vitaliy Guliy 2017-09-06 18:15:00 +03:00 committed by GitHub
parent 1ec08d1cff
commit dcdab14f73
29 changed files with 793 additions and 83 deletions

View File

@ -875,6 +875,13 @@ public interface Theme {
*/
String getMainMenuBkgColor();
/**
* Delimiter background color of main menu
*
* @return the color
*/
String mainMenuDelimiterBackground();
/**
* Background color of selected menu items
*

View File

@ -211,6 +211,7 @@
/*Main Menu*/
@eval mainMenuBkgColor org.eclipse.che.ide.api.theme.Style.getMainMenuBkgColor();
@eval mainMenuDelimiterBackground org.eclipse.che.ide.api.theme.Style.theme.mainMenuDelimiterBackground();
@eval mainMenuSelectedBkgColor org.eclipse.che.ide.api.theme.Style.getMainMenuSelectedBkgColor();
@eval mainMenuSelectedBorderColor org.eclipse.che.ide.api.theme.Style.getMainMenuSelectedBorderColor();
@eval mainMenuFontColor org.eclipse.che.ide.api.theme.Style.getMainMenuFontColor();

View File

@ -50,8 +50,8 @@ public class ToolbarControllerViewImpl extends Composite implements ToolbarContr
* @return element left position
*/
private native int getAbsoluteLeft(JavaScriptObject element) /*-{
return element.getBoundingClientRect().left;
}-*/;
return element.getBoundingClientRect().left;
}-*/;
/**
* Returns absolute top position of the element.
@ -60,8 +60,8 @@ public class ToolbarControllerViewImpl extends Composite implements ToolbarContr
* @return element top position
*/
private native int getAbsoluteTop(JavaScriptObject element) /*-{
return element.getBoundingClientRect().top;
}-*/;
return element.getBoundingClientRect().top;
}-*/;
@Override
public void setDelegate(ActionDelegate delegate) {

View File

@ -44,6 +44,7 @@ import org.eclipse.che.ide.editor.synchronization.workingCopy.EditorWorkingCopyS
import org.eclipse.che.ide.editor.synchronization.workingCopy.EditorWorkingCopySynchronizerImpl;
import org.eclipse.che.ide.editor.texteditor.TextEditorPartViewImpl;
import org.eclipse.che.ide.editor.texteditor.infopanel.InfoPanel;
import org.eclipse.che.ide.part.editor.AddEditorTabMenuFactory;
import org.eclipse.che.ide.part.editor.EditorPartStackView;
import org.eclipse.che.ide.part.editor.EditorTabContextMenuFactory;
import org.eclipse.che.ide.part.editor.recent.RecentFileActionFactory;
@ -115,6 +116,7 @@ public class EditorApiModule extends AbstractGinModule {
install(new GinFactoryModuleBuilder().build(QuickAssistWidgetFactory.class));
install(new GinFactoryModuleBuilder().build(EditorTabContextMenuFactory.class));
install(new GinFactoryModuleBuilder().build(AddEditorTabMenuFactory.class));
install(new GinFactoryModuleBuilder().build(RecentFileActionFactory.class));
bind(RecentFileList.class).to(RecentFileStore.class).in(Singleton.class);

View File

@ -44,9 +44,9 @@ import org.eclipse.che.ide.ui.toolbar.PresentationFactory;
@Singleton
public class ContextMenu implements CloseMenuHandler, ActionSelectedHandler {
private final ActionManager actionManager;
private final KeyBindingAgent keyBindingAgent;
private final Provider<PerspectiveManager> managerProvider;
protected final ActionManager actionManager;
protected final KeyBindingAgent keyBindingAgent;
protected final Provider<PerspectiveManager> managerProvider;
private PopupMenu popupMenu;
private MenuLockLayer lockLayer;

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.part.editor;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.che.ide.api.action.Action;
import org.eclipse.che.ide.api.action.ActionGroup;
import org.eclipse.che.ide.api.action.ActionManager;
import org.eclipse.che.ide.api.action.DefaultActionGroup;
import org.eclipse.che.ide.api.action.IdeActions;
import org.eclipse.che.ide.api.keybinding.KeyBindingAgent;
import org.eclipse.che.ide.api.parts.PerspectiveManager;
import org.eclipse.che.ide.menu.ContextMenu;
/** Menu appeared after clicking on Add editor tab button. */
public class AddEditorTabMenu extends ContextMenu {
@Inject
public AddEditorTabMenu(
ActionManager actionManager,
KeyBindingAgent keyBindingAgent,
Provider<PerspectiveManager> managerProvider) {
super(actionManager, keyBindingAgent, managerProvider);
}
protected ActionGroup updateActions() {
DefaultActionGroup defaultGroup = new DefaultActionGroup(actionManager);
final ActionGroup actionGroup =
(ActionGroup) actionManager.getAction(IdeActions.GROUP_FILE_NEW);
for (Action action : actionGroup.getChildren(null)) {
defaultGroup.add(action);
}
return defaultGroup;
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.part.editor;
/** Factory for creating instances of {@link AddEditorTabMenu} */
public interface AddEditorTabMenuFactory {
/**
* Creates new Add editor tab menu.
*
* @return editor tab menu
*/
AddEditorTabMenu newAddEditorTabMenu();
}

View File

@ -84,7 +84,9 @@ public class EditorPartStackPresenter extends PartStackPresenter
implements EditorPartStack,
EditorTab.ActionDelegate,
CloseNonPinnedEditorsHandler,
ResourceChangedHandler {
ResourceChangedHandler,
EditorPartStackView.AddTabButtonClickListener {
private final PresentationFactory presentationFactory;
private final AppContext appContext;
private final EditorPaneMenuItemFactory editorPaneMenuItemFactory;
@ -95,6 +97,7 @@ public class EditorPartStackPresenter extends PartStackPresenter
private final CloseAllTabsPaneAction closeAllTabsPaneAction;
private final EditorAgent editorAgent;
private final Map<EditorPaneMenuItem, TabItem> items;
private final AddEditorTabMenuFactory addEditorTabMenuFactory;
//this list need to save order of added parts
private final LinkedList<EditorPartPresenter> partsOrder;
@ -121,7 +124,8 @@ public class EditorPartStackPresenter extends PartStackPresenter
ActionManager actionManager,
ClosePaneAction closePaneAction,
CloseAllTabsPaneAction closeAllTabsPaneAction,
EditorAgent editorAgent) {
EditorAgent editorAgent,
AddEditorTabMenuFactory addEditorTabMenuFactory) {
super(eventBus, partMenu, partStackEventHandler, tabItemFactory, partsComparator, view, null);
this.appContext = appContext;
this.editorPaneMenuItemFactory = editorPaneMenuItemFactory;
@ -136,7 +140,9 @@ public class EditorPartStackPresenter extends PartStackPresenter
this.items = new HashMap<>();
this.partsOrder = new LinkedList<>();
this.closedParts = new LinkedList<>();
this.addEditorTabMenuFactory = addEditorTabMenuFactory;
view.setAddTabButtonClickListener(this);
initializePaneMenu();
view.addPaneMenuButton(editorPaneMenu);
}
@ -322,6 +328,18 @@ public class EditorPartStackPresenter extends PartStackPresenter
eventBus.fireEvent(new MaximizePartEvent(parts.get(tab)));
}
@Override
public void onAddTabButtonClicked(final int mouseX, final int mouseY) {
Scheduler.get()
.scheduleDeferred(
new Scheduler.ScheduledCommand() {
@Override
public void execute() {
addEditorTabMenuFactory.newAddEditorTabMenu().show(mouseX, mouseY);
}
});
}
/** {@inheritDoc} */
@Override
public void removePart(PartPresenter part) {

View File

@ -11,16 +11,18 @@
package org.eclipse.che.ide.part.editor;
import static com.google.gwt.dom.client.Style.Display.BLOCK;
import static com.google.gwt.dom.client.Style.Display.NONE;
import static com.google.gwt.dom.client.Style.Unit.PCT;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.gwt.user.client.ui.DeckLayoutPanel;
import com.google.gwt.user.client.ui.DockLayoutPanel;
@ -33,6 +35,7 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.validation.constraints.NotNull;
import org.eclipse.che.ide.FontAwesome;
import org.eclipse.che.ide.api.editor.texteditor.TextEditor;
import org.eclipse.che.ide.api.parts.PartPresenter;
import org.eclipse.che.ide.api.parts.PartStackView;
@ -48,21 +51,42 @@ public class EditorPartStackView extends ResizeComposite
interface PartStackUiBinder extends UiBinder<Widget, EditorPartStackView> {}
/** Listener to handle clicking on Add tab button. */
interface AddTabButtonClickListener {
/**
* Called when clicking on Add tab button.
*
* @param mouseX absolute mouse left
* @param mouseY absolute mouse top
*/
void onAddTabButtonClicked(int mouseX, int mouseY);
}
private static final PartStackUiBinder UI_BINDER = GWT.create(PartStackUiBinder.class);
private static final int POPUP_OFFSET = 15;
@UiField DockLayoutPanel parent;
@UiField FlowPanel tabsPanel;
@UiField FlowPanel plusPanel;
@UiField DeckLayoutPanel contentPanel;
@UiField FlowPanel menuPanel;
private final Map<PartPresenter, TabItem> tabs;
private final AcceptsOneWidget partViewContainer;
private final LinkedList<PartPresenter> contents;
private int tabsPanelWidth = 0;
private ActionDelegate delegate;
private EditorPaneMenu editorPaneMenu;
private TabItem activeTab;
private AddTabButtonClickListener addTabButtonClickListener;
public EditorPartStackView() {
this.tabs = new HashMap<>();
@ -70,6 +94,20 @@ public class EditorPartStackView extends ResizeComposite
initWidget(UI_BINDER.createAndBindUi(this));
plusPanel.getElement().setInnerHTML(FontAwesome.PLUS);
plusPanel.addDomHandler(
new ClickHandler() {
@Override
public void onClick(ClickEvent clickEvent) {
if (addTabButtonClickListener != null) {
addTabButtonClickListener.onAddTabButtonClicked(
getAbsoluteLeft(plusPanel.getElement()) + POPUP_OFFSET,
getAbsoluteTop(plusPanel.getElement()) + POPUP_OFFSET);
}
}
},
ClickEvent.getType());
partViewContainer =
new AcceptsOneWidget() {
@Override
@ -83,6 +121,30 @@ public class EditorPartStackView extends ResizeComposite
setMaximized(false);
}
/**
* Returns absolute left position of the element.
*
* @param element element
* @return element left position
*/
private native int getAbsoluteLeft(JavaScriptObject element) /*-{
return element.getBoundingClientRect().left;
}-*/;
/**
* Returns absolute top position of the element.
*
* @param element element
* @return element top position
*/
private native int getAbsoluteTop(JavaScriptObject element) /*-{
return element.getBoundingClientRect().top;
}-*/;
public void setAddTabButtonClickListener(AddTabButtonClickListener listener) {
addTabButtonClickListener = listener;
}
/** {@inheritDoc} */
@Override
protected void onAttach() {
@ -124,7 +186,7 @@ public class EditorPartStackView extends ResizeComposite
}
/** Add editor tab to tab panel */
tabsPanel.add(tabItem.getView());
tabsPanel.insert(tabItem.getView(), tabsPanel.getWidgetIndex(plusPanel));
/** Process added editor tab */
tabs.put(partPresenter, tabItem);
@ -132,30 +194,106 @@ public class EditorPartStackView extends ResizeComposite
partPresenter.go(partViewContainer);
}
/** Makes active tab visible. */
/** Ensures active tab and plus button are visible. */
private void ensureActiveTabVisible() {
// do nothing if selected tab is null
if (activeTab == null) {
return;
}
for (int i = 0; i < tabsPanel.getWidgetCount(); i++) {
if (editorPaneMenu != null && editorPaneMenu != tabsPanel.getWidget(i)) {
tabsPanel.getWidget(i).setVisible(true);
}
// do nothing if selected tab is visible and plus button is visible
if (getAbsoluteTop(activeTab.getView().asWidget().getElement())
== getAbsoluteTop(tabsPanel.getElement())
&& getAbsoluteTop(plusPanel.getElement()) == getAbsoluteTop(tabsPanel.getElement())
&& tabsPanelWidth == tabsPanel.getOffsetWidth()) {
return;
}
tabsPanelWidth = tabsPanel.getOffsetWidth();
// determine whether all widgets are visible
boolean allWidgetVisible = true;
for (int i = 0; i < tabsPanel.getWidgetCount(); i++) {
Widget currentWidget = tabsPanel.getWidget(i);
Widget activeTabWidget = activeTab.getView().asWidget();
if (editorPaneMenu != null && editorPaneMenu == currentWidget) {
Widget w = tabsPanel.getWidget(i);
if (plusPanel == w) {
continue;
}
if (activeTabWidget.getAbsoluteTop() > tabsPanel.getAbsoluteTop()
&& activeTabWidget != currentWidget) {
currentWidget.setVisible(false);
if (!w.isVisible()) {
allWidgetVisible = false;
break;
}
}
// do nothing if all widgets are visible and sum of children width less then panel width
if (allWidgetVisible) {
int childrenWidth = 0;
for (int i = 0; i < tabsPanel.getWidgetCount(); i++) {
Widget w = tabsPanel.getWidget(i);
childrenWidth += w.getOffsetWidth();
}
if (childrenWidth < tabsPanelWidth) {
return;
}
}
// hide all widgets except plus button
for (int i = 0; i < tabsPanel.getWidgetCount(); i++) {
Widget w = tabsPanel.getWidget(i);
if (plusPanel == w) {
continue;
}
w.setVisible(false);
}
// determine selected tab index
int selectedTabIndex = tabsPanel.getWidgetIndex(activeTab.getView().asWidget());
// show all possible tabs before selected tab
for (int i = selectedTabIndex; i >= 0; i--) {
Widget w = tabsPanel.getWidget(i);
// skip for plus button
if (plusPanel == w) {
continue;
}
// set tab visible
w.setVisible(true);
// continue cycle if plus button visible
if (getAbsoluteTop(plusPanel.getElement()) == getAbsoluteTop(tabsPanel.getElement())) {
continue;
}
// otherwise hide tab and break
w.setVisible(false);
break;
}
// show all possible tabs after selected tab
for (int i = selectedTabIndex + 1; i < tabsPanel.getWidgetCount(); i++) {
Widget w = tabsPanel.getWidget(i);
// skip for plus button
if (plusPanel == w) {
continue;
}
// set tab visible
w.setVisible(true);
// continue cycle if plus button visible
if (getAbsoluteTop(plusPanel.getElement()) == getAbsoluteTop(tabsPanel.getElement())) {
continue;
}
// otherwise hide tab and break
w.setVisible(false);
break;
}
}
/** {@inheritDoc} */
@ -171,11 +309,6 @@ public class EditorPartStackView extends ResizeComposite
if (!contents.isEmpty()) {
selectTab(contents.getLast());
}
//this hack need to force redraw dom element to apply correct styles
tabsPanel.getElement().getStyle().setDisplay(NONE);
tabsPanel.getElement().getOffsetHeight();
tabsPanel.getElement().getStyle().setDisplay(BLOCK);
}
/** {@inheritDoc} */
@ -221,7 +354,9 @@ public class EditorPartStackView extends ResizeComposite
delegate.onRequestFocus();
Scheduler.get().scheduleDeferred(this::ensureActiveTabVisible);
// reset timer and schedule it again
ensureActiveTabVisibleTimer.cancel();
ensureActiveTabVisibleTimer.schedule(200);
}
/** {@inheritDoc} */
@ -260,13 +395,21 @@ public class EditorPartStackView extends ResizeComposite
@Override
public void onResize() {
super.onResize();
Scheduler.get()
.scheduleDeferred(
new Scheduler.ScheduledCommand() {
@Override
public void execute() {
ensureActiveTabVisible();
}
});
// reset timer and schedule it again
ensureActiveTabVisibleTimer.cancel();
ensureActiveTabVisibleTimer.schedule(200);
}
/**
* Timer to prevent updating tabs visibility while resizing. It needs to update tabs once when
* resizing has stopped.
*/
private Timer ensureActiveTabVisibleTimer =
new Timer() {
@Override
public void run() {
ensureActiveTabVisible();
}
};
}

View File

@ -14,25 +14,70 @@
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style>
<ui:style src="org/eclipse/che/ide/api/ui/style.css">
@eval editorPanelBackgroundColor org.eclipse.che.ide.api.theme.Style.theme.editorPanelBackgroundColor();
@eval editorPanelBorderColor org.eclipse.che.ide.api.theme.Style.theme.editorPanelBorderColor();
.tabsPanel {
.topPanel {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
background-color: editorPanelBackgroundColor;
border-bottom: 1px solid editorPanelBorderColor;
overflow: hidden;
background-color: editorPanelBackgroundColor;
}
.tabsPanel {
position: absolute;
top: 0px;
height: 23px;
left: 0px;
right: 34px;
overflow: hidden;
}
.tabsPanel > div {
max-width: 300px;
}
.plusPanel {
float: left;
width: 23px;
height: 23px;
cursor: pointer;
overflow: hidden;
line-height: 23px;
text-align: center;
font-size: 10px;
opacity: 0.6;
color: editorTabIconColor;
}
.plusPanel:HOVER {
opacity: 1;
}
.menuPanel {
position: absolute;
top: 0px;
height: 23px;
right: 0px;
width: 32px;
}
.menuPanel > div {
position: absolute;
}
</ui:style>
<g:DockLayoutPanel ui:field="parent" width="100%" height="100%">
<g:north size="23">
<g:FlowPanel ui:field="tabsPanel" addStyleNames="{style.tabsPanel}" debugId="editorPartStack-tabsPanel"/>
<g:FlowPanel styleName="{style.topPanel}">
<g:FlowPanel ui:field="tabsPanel" styleName="{style.tabsPanel}" debugId="multiSplitPanel-tabsPanel">
<g:FlowPanel ui:field="plusPanel" styleName="{style.plusPanel}" debugId="plusPanel"></g:FlowPanel>
</g:FlowPanel>
<g:FlowPanel ui:field="menuPanel" styleName="{style.menuPanel}"></g:FlowPanel>
</g:FlowPanel>
</g:north>
<g:center>
<g:DeckLayoutPanel ui:field="contentPanel" debugId="editorPartStack-contentPanel"/>

View File

@ -18,6 +18,7 @@ import com.google.gwt.inject.client.AbstractGinModule;
import com.google.gwt.inject.client.assistedinject.GinFactoryModuleBuilder;
import com.google.gwt.inject.client.multibindings.GinMapBinder;
import com.google.inject.Singleton;
import org.eclipse.che.ide.processes.actions.AddTabMenuFactory;
import org.eclipse.che.ide.processes.actions.ConsoleTreeContextMenuFactory;
import org.eclipse.che.ide.processes.panel.ProcessesPanelView;
import org.eclipse.che.ide.processes.panel.ProcessesPanelViewImpl;
@ -28,6 +29,7 @@ public class ProcessesGinModule extends AbstractGinModule {
protected void configure() {
bind(ProcessesPanelView.class).to(ProcessesPanelViewImpl.class).in(Singleton.class);
install(new GinFactoryModuleBuilder().build(ConsoleTreeContextMenuFactory.class));
install(new GinFactoryModuleBuilder().build(AddTabMenuFactory.class));
GinMapBinder.newMapBinder(binder(), String.class, ProcessTreeNodeRenderStrategy.class)
.addBinding(COMMAND_NODE.getStringValue())

View File

@ -0,0 +1,134 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.processes.actions;
import static org.eclipse.che.api.workspace.shared.Constants.SERVER_SSH_REFERENCE;
import static org.eclipse.che.api.workspace.shared.Constants.SERVER_TERMINAL_REFERENCE;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Map;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.FontAwesome;
import org.eclipse.che.ide.api.action.Action;
import org.eclipse.che.ide.api.action.ActionEvent;
import org.eclipse.che.ide.api.action.ActionGroup;
import org.eclipse.che.ide.api.action.ActionManager;
import org.eclipse.che.ide.api.action.DefaultActionGroup;
import org.eclipse.che.ide.api.action.IdeActions;
import org.eclipse.che.ide.api.action.Separator;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.keybinding.KeyBindingAgent;
import org.eclipse.che.ide.api.parts.PerspectiveManager;
import org.eclipse.che.ide.api.workspace.model.MachineImpl;
import org.eclipse.che.ide.machine.MachineResources;
import org.eclipse.che.ide.menu.ContextMenu;
import org.eclipse.che.ide.processes.panel.ProcessesPanelPresenter;
/**
* Menu for adding new tab in processes panel.
*
* @author Vitaliy Guliy
*/
public class AddTabMenu extends ContextMenu {
private AppContext appContext;
private ProcessesPanelPresenter processesPanelPresenter;
private CoreLocalizationConstant coreLocalizationConstant;
private MachineResources machineResources;
@Inject
public AddTabMenu(
ActionManager actionManager,
KeyBindingAgent keyBindingAgent,
Provider<PerspectiveManager> managerProvider,
AppContext appContext,
ProcessesPanelPresenter processesPanelPresenter,
CoreLocalizationConstant coreLocalizationConstant,
MachineResources machineResources) {
super(actionManager, keyBindingAgent, managerProvider);
this.appContext = appContext;
this.processesPanelPresenter = processesPanelPresenter;
this.coreLocalizationConstant = coreLocalizationConstant;
this.machineResources = machineResources;
}
/** {@inheritDoc} */
@Override
protected String getGroupMenu() {
return IdeActions.GROUP_CONSOLES_TREE_CONTEXT_MENU;
}
@Override
protected ActionGroup updateActions() {
final DefaultActionGroup actionGroup = new DefaultActionGroup(actionManager);
Map<String, MachineImpl> machines = appContext.getWorkspace().getRuntime().getMachines();
for (MachineImpl machine : machines.values()) {
Separator separ = new Separator(machine.getName() + ":");
actionGroup.add(separ);
if (machine.getServerByName(SERVER_TERMINAL_REFERENCE).isPresent()) {
NewTerminalMenuAction newTerminalMenuAction =
new NewTerminalMenuAction(
coreLocalizationConstant, machineResources, machine.getName());
actionGroup.add(newTerminalMenuAction);
}
if (machine.getServerByName(SERVER_SSH_REFERENCE).isPresent()) {
AddSSHMenuAction addSSHMenuAction = new AddSSHMenuAction(machine.getName());
actionGroup.add(addSSHMenuAction);
}
}
return actionGroup;
}
/** Action to add new Terminal tab. */
public class NewTerminalMenuAction extends Action {
private String machineName;
public NewTerminalMenuAction(
CoreLocalizationConstant locale, MachineResources machineResources, String machineName) {
super(
locale.newTerminal(),
locale.newTerminalDescription(),
null,
machineResources.addTerminalIcon());
this.machineName = machineName;
}
@Override
public void actionPerformed(ActionEvent e) {
processesPanelPresenter.onAddTerminal(machineName, this);
}
}
/** Action to add new SSH tab. */
public class AddSSHMenuAction extends Action {
private String machineName;
public AddSSHMenuAction(String machineName) {
super("SSH", "SSH", null, null, FontAwesome.RETWEET);
this.machineName = machineName;
}
@Override
public void actionPerformed(ActionEvent e) {
processesPanelPresenter.onPreviewSsh(machineName);
}
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2012-2017 Red Hat, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package org.eclipse.che.ide.processes.actions;
/** Factory to create menu for adding new tab */
public interface AddTabMenuFactory {
/**
* Creates new menu for adding new tab in processes panel.
*
* @return menu
*/
AddTabMenu newAddTabMenu();
}

View File

@ -90,6 +90,7 @@ import org.eclipse.che.ide.console.DefaultOutputConsole;
import org.eclipse.che.ide.machine.MachineResources;
import org.eclipse.che.ide.processes.ProcessTreeNode;
import org.eclipse.che.ide.processes.ProcessTreeNodeSelectedEvent;
import org.eclipse.che.ide.processes.actions.AddTabMenuFactory;
import org.eclipse.che.ide.processes.actions.ConsoleTreeContextMenu;
import org.eclipse.che.ide.processes.actions.ConsoleTreeContextMenuFactory;
import org.eclipse.che.ide.terminal.TerminalFactory;
@ -136,6 +137,7 @@ public class ProcessesPanelPresenter extends BasePresenter
private final CommandConsoleFactory commandConsoleFactory;
private final DialogFactory dialogFactory;
private final ConsoleTreeContextMenuFactory consoleTreeContextMenuFactory;
private final AddTabMenuFactory addTabMenuFactory;
private final CommandTypeRegistry commandTypeRegistry;
private final ExecAgentCommandManager execAgentCommandManager;
private final Provider<MacroProcessor> macroProcessorProvider;
@ -158,6 +160,7 @@ public class ProcessesPanelPresenter extends BasePresenter
CommandConsoleFactory commandConsoleFactory,
DialogFactory dialogFactory,
ConsoleTreeContextMenuFactory consoleTreeContextMenuFactory,
AddTabMenuFactory addTabMenuFactory,
CommandManager commandManager,
CommandTypeRegistry commandTypeRegistry,
SshServiceClient sshServiceClient,
@ -175,6 +178,7 @@ public class ProcessesPanelPresenter extends BasePresenter
this.commandConsoleFactory = commandConsoleFactory;
this.dialogFactory = dialogFactory;
this.consoleTreeContextMenuFactory = consoleTreeContextMenuFactory;
this.addTabMenuFactory = addTabMenuFactory;
this.eventBus = eventBus;
this.commandTypeRegistry = commandTypeRegistry;
this.execAgentCommandManager = execAgentCommandManager;
@ -1283,6 +1287,18 @@ public class ProcessesPanelPresenter extends BasePresenter
});
}
@Override
public void onAddTabButtonClicked(final int mouseX, final int mouseY) {
Scheduler.get()
.scheduleDeferred(
new Scheduler.ScheduledCommand() {
@Override
public void execute() {
addTabMenuFactory.newAddTabMenu().show(mouseX, mouseY);
}
});
}
@Override
public void onDownloadWorkspaceOutput(DownloadWorkspaceOutputEvent event) {
WorkspaceImpl workspace = appContext.getWorkspace();
@ -1324,15 +1340,15 @@ public class ProcessesPanelPresenter extends BasePresenter
* @param text file content
*/
private native void download(String fileName, String text) /*-{
var element = $doc.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', fileName);
var element = $doc.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', fileName);
element.style.display = 'none';
$doc.body.appendChild(element);
element.style.display = 'none';
$doc.body.appendChild(element);
element.click();
element.click();
$doc.body.removeChild(element);
}-*/;
$doc.body.removeChild(element);
}-*/;
}

View File

@ -141,10 +141,16 @@ public interface ProcessesPanelView extends View<ProcessesPanelView.ActionDelega
*/
void onCloseCommandOutputClick(ProcessTreeNode node);
/**
* Will be called when user is going to close command tab.
*
* @param node node of process to stop with closing output
* @param removeCallback remove callback
*/
void onCommandTabClosing(ProcessTreeNode node, SubPanel.RemoveCallback removeCallback);
/**
* Is called when user has clicked right mouse button.
* Will be called when click right mouse button.
*
* @param mouseX mouse x coordinate
* @param mouseY mouse y coordinate
@ -152,7 +158,15 @@ public interface ProcessesPanelView extends View<ProcessesPanelView.ActionDelega
*/
void onContextMenu(int mouseX, int mouseY, ProcessTreeNode node);
/** Is called when user has double clicked on console tab to maximize/restore the console. */
/** Will be called when double click on console tab to maximize/restore the console. */
void onToggleMaximizeConsole();
/**
* Will be called when click on Add tab button.
*
* @param mouseX absolute mouse left position
* @param mouseY absolute mouse top position
*/
void onAddTabButtonClicked(int mouseX, int mouseY);
}
}

View File

@ -60,6 +60,7 @@ public class ProcessesPanelViewImpl extends BaseView<ProcessesPanelView.ActionDe
implements ProcessesPanelView,
SubPanel.FocusListener,
SubPanel.DoubleClickListener,
SubPanel.AddTabButtonClickListener,
RequiresResize {
@UiField(provided = true)
@ -176,6 +177,7 @@ public class ProcessesPanelViewImpl extends BaseView<ProcessesPanelView.ActionDe
final SubPanel subPanel = subPanelFactory.newPanel();
subPanel.setFocusListener(this);
subPanel.setDoubleClickListener(this);
subPanel.setAddTabButtonClickListener(this);
splitLayoutPanel.add(subPanel.getView());
focusedSubPanel = subPanel;
@ -505,6 +507,12 @@ public class ProcessesPanelViewImpl extends BaseView<ProcessesPanelView.ActionDe
((RequiresResize) widget).onResize();
}
}
for (SubPanel panel : widget2Panels.values()) {
if (panel.getView() instanceof RequiresResize) {
((RequiresResize) panel.getView()).onResize();
}
}
}
@Override
@ -513,6 +521,11 @@ public class ProcessesPanelViewImpl extends BaseView<ProcessesPanelView.ActionDe
navigationPanelVisible = visible;
}
@Override
public void onAddTabButtonClicked(int mouseX, int mouseY) {
delegate.onAddTabButtonClicked(mouseX, mouseY);
}
@Override
public boolean isProcessesTreeVisible() {
return navigationPanelVisible;

View File

@ -722,6 +722,11 @@ public class DarkTheme implements Theme {
return this.getMenuBackgroundColor();
}
@Override
public String mainMenuDelimiterBackground() {
return "#383f53";
}
@Override
public String getMainMenuSelectedBkgColor() {
return "#2e3a45";

View File

@ -694,6 +694,11 @@ public class LightTheme implements Theme {
return "#cacacc";
}
@Override
public String mainMenuDelimiterBackground() {
return "#ececec";
}
@Override
public String getMainMenuSelectedBkgColor() {
return "#ffffff";

View File

@ -97,6 +97,7 @@ public class EditorPartStackPresenterTest {
@Mock private CloseAllTabsPaneAction closeAllTabsPaneAction;
@Mock private EditorPaneMenuItemFactory editorPaneMenuItemFactory;
@Mock private EditorAgent editorAgent;
@Mock private AddEditorTabMenuFactory addEditorTabMenuFactory;
//additional mocks
@Mock private SplitHorizontallyAction splitHorizontallyAction;
@ -183,7 +184,8 @@ public class EditorPartStackPresenterTest {
actionManager,
closePaneAction,
closeAllTabsPaneAction,
editorAgent);
editorAgent,
addEditorTabMenuFactory);
when(tabItemFactory.createEditorPartButton(partPresenter1, presenter)).thenReturn(editorTab1);
when(tabItemFactory.createEditorPartButton(partPresenter2, presenter)).thenReturn(editorTab2);

View File

@ -14,22 +14,22 @@
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<ui:style>
.innerPanel {
width: 12px;
height: 12px;
float: left;
cursor: pointer;
margin-top: 3px;
width: 12px;
height: 12px;
float: left;
cursor: pointer;
margin-top: 3px;
}
.innerPanel:active {
opacity: 0.4;
opacity: 0.4;
}
.outerPanel {
position: relative;
width: 18px;
height: 18px;
margin: 6px 0 6px 3px;
position: relative;
width: 18px;
height: 18px;
margin: 6px 0 6px 3px;
}
</ui:style>

View File

@ -78,28 +78,32 @@ public interface SubPanel {
*/
void setDoubleClickListener(DoubleClickListener listener);
interface WidgetRemovingListener {
/** Set the listener to be notified when Add Tab button has been clicked. */
void setAddTabButtonClickListener(AddTabButtonClickListener listener);
interface WidgetRemovingListener {
/** Invoked when a widget is going to be removed. */
void onWidgetRemoving(RemoveCallback removeCallback);
}
/** Callback that may be used for actual removing widget. */
interface RemoveCallback {
/** Tells panel to remove widget. */
void remove();
}
interface FocusListener {
/** Invoked when a {@code widget} on a {@code panel} gains the focus. */
void focusGained(SubPanel panel, IsWidget widget);
}
interface DoubleClickListener {
/** Invoked when a {@code widget} on a {@code panel} has been double clicked. */
void onDoubleClicked(SubPanel panel, IsWidget widget);
}
interface AddTabButtonClickListener {
/** Invoked when `Add Tab` button has been clicked. */
void onAddTabButtonClicked(int mouseX, int mouseY);
}
}

View File

@ -40,6 +40,7 @@ public class SubPanelPresenter implements SubPanel, SubPanelView.ActionDelegate
private FocusListener focusListener;
private DoubleClickListener doubleClickListener;
private AddTabButtonClickListener addTabButtonClickListener;
@AssistedInject
public SubPanelPresenter(
@ -147,6 +148,11 @@ public class SubPanelPresenter implements SubPanel, SubPanelView.ActionDelegate
doubleClickListener = listener;
}
@Override
public void setAddTabButtonClickListener(AddTabButtonClickListener listener) {
addTabButtonClickListener = listener;
}
@Override
public void onWidgetFocused(IsWidget widget) {
focusListener.focusGained(this, widget);
@ -164,4 +170,11 @@ public class SubPanelPresenter implements SubPanel, SubPanelView.ActionDelegate
listener.onWidgetRemoving(removeCallback);
}
}
@Override
public void onAddTabButtonClicked(int mouseX, int mouseY) {
if (addTabButtonClickListener != null) {
addTabButtonClickListener.onAddTabButtonClicked(mouseX, mouseY);
}
}
}

View File

@ -68,5 +68,8 @@ public interface SubPanelView extends View<SubPanelView.ActionDelegate> {
/** Called when the {@code widget} is going to be removed from the panel. */
void onWidgetRemoving(IsWidget widget, SubPanel.RemoveCallback removeCallback);
/** Called when the `Add Tab` button has been clicked. */
void onAddTabButtonClicked(int mouseX, int mouseY);
}
}

View File

@ -12,9 +12,12 @@ package org.eclipse.che.ide.ui.multisplitpanel.panel;
import static com.google.gwt.user.client.ui.DockLayoutPanel.Direction.CENTER;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DeckLayoutPanel;
import com.google.gwt.user.client.ui.DockLayoutPanel;
@ -30,6 +33,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.che.commons.annotation.Nullable;
import org.eclipse.che.ide.FontAwesome;
import org.eclipse.che.ide.api.action.Action;
import org.eclipse.che.ide.ui.multisplitpanel.SubPanel;
import org.eclipse.che.ide.ui.multisplitpanel.WidgetToShow;
@ -52,6 +56,10 @@ import org.eclipse.che.ide.ui.multisplitpanel.tab.TabItemFactory;
public class SubPanelViewImpl extends Composite
implements SubPanelView, Menu.ActionDelegate, Tab.ActionDelegate, RequiresResize {
interface SubPanelViewImplUiBinder extends UiBinder<Widget, SubPanelViewImpl> {}
private static final int POPUP_OFFSET = 15;
private final TabItemFactory tabItemFactory;
private final Menu menu;
private final Map<Tab, WidgetToShow> tabs2Widgets;
@ -66,6 +74,10 @@ public class SubPanelViewImpl extends Composite
@UiField FlowPanel tabsPanel;
@UiField FlowPanel plusPanel;
@UiField FlowPanel menuPanel;
@UiField DeckLayoutPanel widgetsPanel;
private ActionDelegate delegate;
@ -73,6 +85,9 @@ public class SubPanelViewImpl extends Composite
private List<SubPanelView> eastSubPanels;
private List<SubPanelView> southSubPanels;
private Tab selectedTab;
private int tabsPanelWidth = 0;
@Inject
public SubPanelViewImpl(
SubPanelViewImplUiBinder uiBinder,
@ -103,13 +118,45 @@ public class SubPanelViewImpl extends Composite
initWidget(uiBinder.createAndBindUi(this));
tabsPanel.add(menu);
menuPanel.add(menu);
plusPanel.getElement().setInnerHTML(FontAwesome.PLUS);
plusPanel.addDomHandler(
new ClickHandler() {
@Override
public void onClick(ClickEvent clickEvent) {
delegate.onAddTabButtonClicked(
getAbsoluteLeft(plusPanel.getElement()) + POPUP_OFFSET,
getAbsoluteTop(plusPanel.getElement()) + POPUP_OFFSET);
}
},
ClickEvent.getType());
widgetsPanel.ensureDebugId("process-output-panel-holder");
widgetsPanel.addDomHandler(
event -> delegate.onWidgetFocused(widgetsPanel.getVisibleWidget()), ClickEvent.getType());
}
/**
* Returns absolute left position of the element.
*
* @param element element
* @return element left position
*/
private native int getAbsoluteLeft(JavaScriptObject element) /*-{
return element.getBoundingClientRect().left;
}-*/;
/**
* Returns absolute top position of the element.
*
* @param element element
* @return element top position
*/
private native int getAbsoluteTop(JavaScriptObject element) /*-{
return element.getBoundingClientRect().top;
}-*/;
@Override
public void setDelegate(ActionDelegate delegate) {
this.delegate = delegate;
@ -149,7 +196,7 @@ public class SubPanelViewImpl extends Composite
tabs2Widgets.put(tab, widget);
widgets2Tabs.put(widget, tab);
tabsPanel.add(tab);
tabsPanel.insert(tab, tabsPanel.getWidgetIndex(plusPanel));
widgetsPanel.setWidget(widget.getWidget());
// add item to drop-down menu
@ -325,6 +372,9 @@ public class SubPanelViewImpl extends Composite
}
tab.select();
selectedTab = tab;
ensureActiveTabVisible();
}
@Override
@ -339,7 +389,96 @@ public class SubPanelViewImpl extends Composite
((RequiresResize) widgetToShow.getWidget()).onResize();
}
}
// reset timer and schedule it again
ensureActiveTabVisibleTimer.cancel();
ensureActiveTabVisibleTimer.schedule(200);
}
interface SubPanelViewImplUiBinder extends UiBinder<Widget, SubPanelViewImpl> {}
/**
* Timer to prevent updating tabs visibility while resizing. It needs to update tabs once when
* resizing has stopped.
*/
private Timer ensureActiveTabVisibleTimer =
new Timer() {
@Override
public void run() {
ensureActiveTabVisible();
}
};
/** Ensures active tab and plus button are visible */
private void ensureActiveTabVisible() {
// do nothing if selected tab is null
if (selectedTab == null) {
return;
}
// do nothing if selected tab is visible and plus button is visible
if (selectedTab.asWidget().getElement().getAbsoluteTop()
== tabsPanel.getElement().getAbsoluteTop()
&& plusPanel.getElement().getAbsoluteTop() == tabsPanel.getElement().getAbsoluteTop()
&& tabsPanelWidth == tabsPanel.getOffsetWidth()) {
return;
}
tabsPanelWidth = tabsPanel.getOffsetWidth();
// hide all widgets except plus button
for (int i = 0; i < tabsPanel.getWidgetCount(); i++) {
Widget w = tabsPanel.getWidget(i);
if (plusPanel == w) {
continue;
}
w.setVisible(false);
}
// determine selected tab index
int selectedTabIndex = tabsPanel.getWidgetIndex(selectedTab.asWidget());
// show all possible tabs before selected tab
for (int i = selectedTabIndex; i >= 0; i--) {
Widget w = tabsPanel.getWidget(i);
// skip for plus button
if (plusPanel == w) {
continue;
}
// set tab visible
w.setVisible(true);
// continue cycle if plus button visible
if (plusPanel.getElement().getAbsoluteTop() == tabsPanel.getElement().getAbsoluteTop()) {
continue;
}
// otherwise hide tab and break
w.setVisible(false);
break;
}
// show all possible tabs after selected tab
for (int i = selectedTabIndex + 1; i < tabsPanel.getWidgetCount(); i++) {
Widget w = tabsPanel.getWidget(i);
// skip for plus button
if (plusPanel == w) {
continue;
}
// set tab visible
w.setVisible(true);
// continue cycle if plus button visible
if (plusPanel.getElement().getAbsoluteTop() == tabsPanel.getElement().getAbsoluteTop()) {
continue;
}
// otherwise hide tab and break
w.setVisible(false);
break;
}
}
}

View File

@ -14,17 +14,54 @@
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style>
@eval editorPanelBackgroundColor org.eclipse.che.ide.api.theme.Style.theme.editorPanelBackgroundColor();
@eval editorPanelBorderColor org.eclipse.che.ide.api.theme.Style.theme.editorPanelBorderColor();
<ui:style src="org/eclipse/che/ide/api/ui/style.css">
.tabsPanel {
.topPanel {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
background-color: editorPanelBackgroundColor;
border-bottom: 1px solid editorPanelBorderColor;
overflow: hidden;
background-color: editorPanelBackgroundColor;
}
.tabsPanel {
position: absolute;
top: 0px;
height: 23px;
left: 0px;
right: 34px;
overflow: hidden;
}
.plusPanel {
float: left;
width: 23px;
height: 23px;
cursor: pointer;
overflow: hidden;
line-height: 23px;
text-align: center;
font-size: 10px;
opacity: 0.6;
color: editorTabIconColor;
}
.plusPanel:HOVER {
opacity: 1;
}
.menuPanel {
position: absolute;
top: 0px;
height: 23px;
right: 0px;
width: 32px;
}
.menuPanel > div {
position: absolute;
}
</ui:style>
@ -33,7 +70,12 @@
<g:center>
<g:DockLayoutPanel ui:field="mainPanel" width="100%" height="100%">
<g:north size="23">
<g:FlowPanel ui:field="tabsPanel" addStyleNames="{style.tabsPanel}" debugId="multiSplitPanel-tabsPanel"/>
<g:FlowPanel styleName="{style.topPanel}">
<g:FlowPanel ui:field="tabsPanel" styleName="{style.tabsPanel}" debugId="multiSplitPanel-tabsPanel">
<g:FlowPanel ui:field="plusPanel" styleName="{style.plusPanel}" debugId="plusPanel"></g:FlowPanel>
</g:FlowPanel>
<g:FlowPanel ui:field="menuPanel" styleName="{style.menuPanel}"></g:FlowPanel>
</g:FlowPanel>
</g:north>
<g:center>
<g:DeckLayoutPanel ui:field="widgetsPanel"/>

View File

@ -70,13 +70,14 @@
}
.popupMenuTextDelimiter {
background: editorPanelBackgroundColor;
background: mainMenuDelimiterBackground;
font-weight: bold;
}
.popupMenuTextDelimiter > div {
display: inline-block;
line-height: 20px;
margin-left: 14px;
margin-left: 5px;
margin-bottom: 0;
}

View File

@ -60,11 +60,17 @@ public class FontAwesome {
public static final String MINUS = "<i class=\"fa fa-minus-circle\"></i>";
/** http://fortawesome.github.io/Font-Awesome/icon/plus-circle/ */
public static final String PLUS = "<i class=\"fa fa-plus-circle\"></i>";
public static final String PLUS_CIRCLE = "<i class=\"fa fa-plus-circle\"></i>";
/** http://fortawesome.github.io/Font-Awesome/icon/plus/ */
public static final String PLUS = "<i class=\"fa fa-plus\"></i>";
/** http://fortawesome.github.io/Font-Awesome/icon/refresh/ */
public static final String REFRESH = "<i class=\"fa fa-refresh\"></i>";
/** http://fortawesome.github.io/Font-Awesome/icon/retweet/ */
public static final String RETWEET = "<i class=\"fa fa-retweet\" aria-hidden=\"true\"></i>";
/** http://fortawesome.github.io/Font-Awesome/icon/exchange/ */
public static final String EXCHANGE = "<i class=\"fa fa-exchange\"></i>";

View File

@ -55,7 +55,11 @@ public class AddToIndexAction extends GitAction {
ProcessesPanelPresenter consolesPanelPresenter,
GitServiceClient service,
NotificationManager notificationManager) {
super(constant.addToIndexTitle(), constant.addToIndexTitle(), FontAwesome.PLUS, appContext);
super(
constant.addToIndexTitle(),
constant.addToIndexTitle(),
FontAwesome.PLUS_CIRCLE,
appContext);
this.presenter = presenter;
this.constant = constant;
this.gitOutputConsoleFactory = gitOutputConsoleFactory;

View File

@ -43,7 +43,7 @@ public final class ChainExecutor {
public void execute(final WorkflowExecutor workflow, final Context context) {
if (chainIt.hasNext()) {
currentStep = chainIt.next();
Log.info(
Log.debug(
getClass(),
"Executing :: " + context.getProject().getName() + " :: => " + currentStep.getClass());
currentStep.execute(workflow, context);