/******************************************************************************* * Copyright (c) 2012-2015 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.part.editor; import org.eclipse.che.ide.Resources; import org.eclipse.che.ide.api.parts.PartStackUIResources; import org.eclipse.che.ide.api.parts.PartStackView; import org.eclipse.che.ide.collections.Array; import org.eclipse.che.ide.collections.Collections; import org.eclipse.che.ide.part.FocusManager; import org.eclipse.che.ide.util.loging.Log; import com.google.gwt.core.client.GWT; 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.DoubleClickEvent; import com.google.gwt.event.dom.client.DoubleClickHandler; import com.google.gwt.event.dom.client.MouseDownEvent; import com.google.gwt.event.dom.client.MouseDownHandler; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.DeckLayoutPanel; import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.InlineLabel; import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.ResizeComposite; import com.google.gwt.user.client.ui.Widget; import com.google.inject.Inject; import org.vectomatic.dom.svg.ui.SVGImage; import static com.google.gwt.user.client.ui.InsertPanel.ForIsWidget; /** * @author Evgen Vidolob */ public class EditorPartStackView extends ResizeComposite implements PartStackView { private static final int COUNTING_ERROR = 10; private static PartStackUiBinder uiBinder = GWT.create(PartStackUiBinder.class); private ActionDelegate delegate; private final PartStackUIResources partStackUIResources; private TabButton activeTab; private boolean focused; private Widget focusedWidget; // DOM Handler // private final FocusRequestDOMHandler focusRequestHandler = new FocusRequestDOMHandler(); private HandlerRegistration focusRequestHandlerRegistration; // list of tabs private final Array<TabButton> tabs = Collections.createArray(); @UiField DockLayoutPanel parent; @UiField FlowPanel tabsPanel; @UiField DeckLayoutPanel contentPanel; private ListButton listTabsButton; private ShowListButtonClickHandler showListButtonClickHandler; interface PartStackUiBinder extends UiBinder<Widget, EditorPartStackView> { } /** * Create View * * @param partStackResources */ @Inject public EditorPartStackView(PartStackUIResources partStackResources, Resources resources) { this.partStackUIResources = partStackResources; initWidget(uiBinder.createAndBindUi(this)); setWidth("100%"); setHeight("100%"); parent.setStyleName(partStackResources.partStackCss().idePartStack()); tabsPanel.setStyleName(partStackResources.partStackCss().idePartStackTabs()); contentPanel.setStyleName(partStackResources.partStackCss().idePartStackEditorContent()); listTabsButton = new ListButton(new Image(resources.listOpenedEditors()), "Show list"); listTabsButton.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { listTabsButton.addStyleName(partStackUIResources.partStackCss().idePartStackTabButtonSelected()); int x = listTabsButton.getAbsoluteLeft() + listTabsButton.getOffsetWidth(); int y = listTabsButton.getAbsoluteTop() + listTabsButton.getOffsetHeight(); showListButtonClickHandler.onShowListClicked(x, y, new AsyncCallback<Void>() { @Override public void onSuccess(Void result) { listTabsButton.removeStyleName(partStackUIResources.partStackCss().idePartStackTabButtonSelected()); } @Override public void onFailure(Throwable caught) { listTabsButton.removeStyleName(partStackUIResources.partStackCss().idePartStackTabButtonSelected()); } }); } }); tabsPanel.add(listTabsButton); listTabsButton.setVisible(false); setVisible(false); // addFocusRequestHandler(); addDomHandler(new MouseDownHandler() { @Override public void onMouseDown(MouseDownEvent event) { if (delegate != null) { delegate.onRequestFocus(); } } }, MouseDownEvent.getType()); } @Override protected void onAttach() { super.onAttach(); Style style = getElement().getParentElement().getStyle(); style.setHeight(100, Style.Unit.PCT); style.setWidth(100, Style.Unit.PCT); } /** {@inheritDoc} */ @Override public TabItem addTab(SVGImage icon, String title, String toolTip, IsWidget widget, boolean closable) { setVisible(true); TabButton tabItem = new TabButton(icon, title, toolTip, closable); tabsPanel.add(tabItem); tabs.add(tabItem); return tabItem; } /** {@inheritDoc} */ @Override public ForIsWidget getContentPanel() { return contentPanel; } /** {@inheritDoc} */ @Override public void setTabpositions(Array<Integer> partPositions) { //TODO not implemented. need to add for sorting } /** {@inheritDoc} */ @Override public void removeTab(int index) { if (index < tabs.size()) { TabButton removed = tabs.remove(index); tabsPanel.remove(removed); contentPanel.remove(contentPanel.getWidget(index)); } setVisible(tabs.size() > 0); processPanelSize(); } /** {@inheritDoc} */ @Override public void setActiveTab(int index) { if (activeTab != null) { activeTab.removeStyleName(partStackUIResources.partStackCss().idePartStackTabSelected()); activeTab.getElement().getStyle().clearBackgroundColor(); //This code is necessary to distinguish active from inactive content and tab when testing if (tabs.indexOf(activeTab) != -1) { contentPanel.getWidget(tabs.indexOf(activeTab)).ensureDebugId("inactiveContent"); activeTab.ensureDebugId("inactiveTabButton-" + activeTab.tabItemTittle.getText()); } } if (index >= 0 && index < tabs.size()) { activeTab = tabs.get(index); activeTab.addStyleName(partStackUIResources.partStackCss().idePartStackTabSelected()); contentPanel.showWidget(index); //This code is necessary to distinguish active from inactive content and tab when testing contentPanel.getWidget(index).ensureDebugId("activeContent"); activeTab.ensureDebugId("activeTabButton-" + activeTab.tabItemTittle.getText()); } processPanelSize(); } /** {@inheritDoc} */ @Override public void setDelegate(ActionDelegate delegate) { this.delegate = delegate; } /** {@inheritDoc} */ @Override public void setFocus(boolean focused) { this.focused = focused; // if (focused) { // parent.addStyleName(partStackUIResources.partStackCss().idePartStackFocused()); // removeFocusRequestHandler(); // } else { // parent.removeStyleName(partStackUIResources.partStackCss().idePartStackFocused()); // addFocusRequestHandler(); // } this.focused = focused; if (focused && contentPanel.getVisibleWidget() != null) { contentPanel.getElement().getStyle().setProperty("borderLeftColor", FocusManager.HIGHLIGHT_COLOR); contentPanel.getElement().getStyle().setProperty("borderTopColor", FocusManager.HIGHLIGHT_COLOR); if (activeTab != null) { activeTab.getElement().getStyle().setProperty("backgroundColor", FocusManager.HIGHLIGHT_COLOR); } } else { contentPanel.getElement().getStyle().clearProperty("borderLeftColor"); contentPanel.getElement().getStyle().clearProperty("borderTopColor"); if (activeTab != null) { activeTab.getElement().getStyle().clearProperty("backgroundColor"); } } } // /** Add MouseDown DOM Handler */ // protected void addFocusRequestHandler() { // focusRequestHandlerRegistration = addDomHandler(focusRequestHandler, MouseDownEvent.getType()); // } // /** Remove MouseDown DOM Handler */ // protected void removeFocusRequestHandler() { // if (focusRequestHandlerRegistration != null) { // focusRequestHandlerRegistration.removeHandler(); // focusRequestHandlerRegistration = null; // } // } /** {@inheritDoc} */ @Override public void updateTabItem(int index, SVGImage icon, String title, String toolTip, IsWidget widget) { TabButton tabButton = tabs.get(index); tabButton.tabItemTittle.setText(title); tabButton.setTitle(toolTip); tabButton.update(icon, widget); } /** Special button for tab title. */ private class TabButton extends Composite implements TabItemWithMarks { private Image image; private FlowPanel tabItem; private InlineLabel tabItemTittle; private SVGImage icon; private boolean isVisibilityOfWarningMark; private boolean isVisibilityOfErrorMark; /** * Create button. * * @param icon * @param title * @param toolTip * @param closable */ private TabButton(SVGImage icon, String title, String toolTip, boolean closable) { this.icon = icon; tabItem = new FlowPanel(); tabItem.setTitle(toolTip); initWidget(tabItem); this.setStyleName(partStackUIResources.partStackCss().idePartStackTab()); if (icon != null) { icon.setClassNameBaseVal(partStackUIResources.partStackCss().idePartStackTabIcon()); tabItem.add(icon); } tabItemTittle = new InlineLabel(""); tabItemTittle.getElement().setInnerHTML(title); tabItem.add(tabItemTittle); if (closable) { image = new Image(partStackUIResources.close()); image.setStyleName(partStackUIResources.partStackCss().idePartStackTabCloseButton()); tabItem.add(image); addHandlers(); } this.ensureDebugId("tabButton-" + title); } protected void update(SVGImage icon, IsWidget widget) { if (this.icon != null) { tabItem.remove(this.icon); } this.icon = icon; if (this.icon != null) { icon.setClassNameBaseVal(partStackUIResources.partStackCss().idePartStackTabIcon()); tabItem.add(this.icon); } } @Override public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) { return addDomHandler(handler, MouseDownEvent.getType()); } @Override public HandlerRegistration addClickHandler(ClickHandler handler) { return addDomHandler(handler, ClickEvent.getType()); } @Override public HandlerRegistration addCloseHandler(CloseHandler<TabItem> handler) { return addHandler(handler, CloseEvent.getType()); } private void addHandlers() { tabItem.addDomHandler(new DoubleClickHandler() { @Override public void onDoubleClick(DoubleClickEvent event) { expandEditor(); } }, DoubleClickEvent.getType()); image.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { CloseEvent.fire(TabButton.this, TabButton.this); } }); } /** * Expands or collapses the editor using IDE.eventHandlers.expandEditor(). */ private native void expandEditor() /*-{ try { $wnd.IDE.eventHandlers.expandEditor(); } catch (e) { console.log(e.message); } }-*/; @Override public void setErrorMark(boolean isVisible) { if (isVisibilityOfErrorMark != isVisible) { isVisibilityOfErrorMark = isVisible; if (isVisible) { tabItemTittle.addStyleName(partStackUIResources.partStackCss().lineError()); } else { tabItemTittle.removeStyleName(partStackUIResources.partStackCss().lineError()); } } } @Override public void setWarningMark(boolean isVisible) { if (isVisibilityOfWarningMark != isVisible) { isVisibilityOfWarningMark = isVisible; if (isVisible) { tabItemTittle.addStyleName(partStackUIResources.partStackCss().lineWarning()); } else { tabItemTittle.removeStyleName(partStackUIResources.partStackCss().lineWarning()); } } } } /** Button for listing all opened tabs. */ private class ListButton extends Composite implements PartStackView.TabItem { private FlowPanel tabItem; public ListButton(Image icon, String toolTip) { tabItem = new FlowPanel(); tabItem.setTitle(toolTip); initWidget(tabItem); this.setStyleName(partStackUIResources.partStackCss().idePartStackTabButton()); this.addStyleName(partStackUIResources.partStackCss().idePartStackTabRightButton()); setWidth("32px"); if (icon != null) { tabItem.add(icon); } } @Override public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) { return addDomHandler(handler, MouseDownEvent.getType()); } @Override public HandlerRegistration addClickHandler(ClickHandler handler) { return addDomHandler(handler, ClickEvent.getType()); } /** {@inheritDoc} */ @Override public HandlerRegistration addCloseHandler(CloseHandler<TabItem> handler) { return addHandler(handler, CloseEvent.getType()); } } // /** Notifies delegated handler */ // private final class FocusRequestDOMHandler implements MouseDownHandler { // @Override // public void onMouseDown(MouseDownEvent event) { // if (delegate != null) { // delegate.onRequestFocus(); // } // } // } /** {@inheritDoc} */ @Override public void onResize() { super.onResize(); processPanelSize(); } /** * This method analyzes the tabs panel size and the size of its components and * displays the show list button, when all tabs can not be placed. */ private void processPanelSize() { boolean activeTabIsVisible = true; int width = listTabsButton.isVisible() ? listTabsButton.getOffsetWidth() : COUNTING_ERROR; for (int i = 0; i < tabsPanel.getWidgetCount(); i++) { //Do not count list buttons width if (tabsPanel.getWidget(i) instanceof ListButton) { continue; } width += tabsPanel.getWidget(i).getOffsetWidth(); //Check whether active tab is visible if (tabsPanel.getWidget(i) instanceof TabButton && (tabsPanel.getWidget(i)) == activeTab && width > tabsPanel.getOffsetWidth()) { activeTabIsVisible = false; } } //Move not visible active tab to the first place if (!activeTabIsVisible) { tabsPanel.insert(activeTab, 0); } listTabsButton.setVisible(width > tabsPanel.getOffsetWidth() && tabsPanel.getOffsetWidth() > 0); width = COUNTING_ERROR; if (listTabsButton.isVisible()) { for (int i = 0; i < tabsPanel.getWidgetCount(); i++) { width += tabsPanel.getWidget(i).getOffsetWidth(); if (width > tabsPanel.getOffsetWidth() && i >= 1) { tabsPanel.insert(listTabsButton, i - 1); break; } } } updateTabsAttributes(); } /** * A special method, is needed for issue "IDEUI-40 Create rounded tabs for file in order to be conform to initial designs". * Method appends special attributes to editor tabs to be able to restyle these tabs in future. * Using these attributes we can access these tabs through CSS attribute selectors. */ private void updateTabsAttributes() { try { TabButton left = null; TabButton right = null; int width = listTabsButton.isVisible() ? listTabsButton.getOffsetWidth() : COUNTING_ERROR; for (int i = 0; i < tabsPanel.getWidgetCount(); i++) { if (!(tabsPanel.getWidget(i) instanceof TabButton)) { continue; } TabButton tab = (TabButton)tabsPanel.getWidget(i); width += tab.getOffsetWidth(); if (width > tabsPanel.getOffsetWidth()) { tab.getElement().removeAttribute("visible"); tab.getElement().removeAttribute("left"); tab.getElement().removeAttribute("right"); tab.getElement().removeAttribute("active"); continue; } tab.getElement().setAttribute("visible", ""); if (left == null) { left = tab; tab.getElement().setAttribute("left", ""); } else { tab.getElement().removeAttribute("left"); } if (right == null) { right = tab; tab.getElement().setAttribute("right", ""); } else { right.getElement().removeAttribute("right"); right = tab; tab.getElement().setAttribute("right", ""); } if (tab == activeTab) { tab.getElement().setAttribute("active", ""); } else { tab.getElement().removeAttribute("active"); } } } catch (Exception e) { Log.error(getClass(), "Error occurs while updating tab attributes. " + e.getMessage()); } } /** * Sets the handler for list all tabs button click event. * * @param handler */ public void setShowListButtonHandler(ShowListButtonClickHandler handler) { this.showListButtonClickHandler = handler; } }