// Copyright 2012 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.collide.client.workspace; import com.google.collide.client.AppContext; import com.google.collide.client.bootstrap.BootstrapSession; import com.google.collide.client.search.FileNameSearch; import com.google.collide.client.search.SearchContainer; import com.google.collide.client.search.awesomebox.AwesomeBox; import com.google.collide.client.search.awesomebox.host.AwesomeBoxComponent; import com.google.collide.client.search.awesomebox.host.AwesomeBoxComponentHost; import com.google.collide.client.search.awesomebox.shared.AwesomeBoxResources; import com.google.collide.client.testing.DebugAttributeSetter; import com.google.collide.client.testing.DebugId; import com.google.collide.client.ui.button.ImageButton; import com.google.collide.client.ui.menu.PositionController.HorizontalAlign; import com.google.collide.client.ui.menu.PositionController.Positioner; import com.google.collide.client.ui.menu.PositionController.VerticalAlign; import com.google.collide.client.ui.tooltip.Tooltip; import com.google.collide.client.util.CssUtils; import com.google.collide.client.util.Elements; import com.google.collide.clientlibs.model.Workspace; import com.google.collide.dto.UserDetails; import com.google.collide.mvp.CompositeView; import com.google.collide.mvp.UiComponent; import com.google.gwt.core.client.GWT; import com.google.gwt.dom.client.AnchorElement; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Element; import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiTemplate; import elemental.events.Event; import elemental.events.EventListener; import elemental.html.ImageElement; /** * The Header for the workspace Shell. * */ public class Header extends UiComponent<Header.View> { /** * Creates the default version of the header to be used in the editor shell. */ public static Header create(View view, WorkspaceShell.View workspaceShellView, WorkspacePlace currentPlace, final AppContext appContext, FileNameSearch fileNameSearch, FileTreeModel fileTreeModel) { AwesomeBoxComponentHost awesomeBoxHost = new AwesomeBoxComponentHost( view.awesomeBoxComponentHostView, appContext.getAwesomeBoxComponentHostModel()); // TODO: Sigh, due to tooltip being part of the client glob this // lives outside of the component host, need to resolve this when I get a // chance should be easy to make tooltip and autohide crud its own thing. // TODO: This should never be shown (its a fallback just in // case we hit a case where it could in the future). final String defaultText = "Access the AwesomeBox for more options"; Positioner tooltipPositioner = new Tooltip.TooltipPositionerBuilder().buildAnchorPositioner( view.awesomeBoxComponentHostView.getElement()); Tooltip tooltip = new Tooltip.Builder(appContext.getResources(), view.awesomeBoxComponentHostView.getElement(), tooltipPositioner).setTooltipRenderer( new Tooltip.TooltipRenderer() { @Override public elemental.html.Element renderDom() { elemental.html.DivElement element = Elements.createDivElement(); AwesomeBoxComponent component = appContext.getAwesomeBoxComponentHostModel().getActiveComponent(); if (component == null || component.getTooltipText() == null) { element.setTextContent(defaultText); } else { element.setTextContent(component.getTooltipText()); } return element; } }).build(); AwesomeBox awesomeBoxComponent = AwesomeBox.create(appContext); appContext.getAwesomeBoxComponentHostModel().setDefaultComponent(awesomeBoxComponent); RunButtonController runButtonController = RunButtonController.create(appContext, view.runButton, view.runDropdownButton, currentPlace, fileNameSearch, fileTreeModel); Header header = new Header(view, currentPlace, appContext, runButtonController, awesomeBoxHost); return header; } /** * Style names associated with elements in the header. */ public interface Css extends CssResource { String gray(); String leftButtonGroup(); String runButtonContainer(); String runButton(); String runDropdownButton(); String runIcon(); String syncButtons(); String readOnlyMessage(); String newWorkspaceButton(); String selectArrows(); String selectBg(); String triangle(); String awesomeBoxContainer(); String feedbackButton(); String shareButton(); String rightButtonGroup(); String profileImage(); } /** * Images and CssResources consumed by the Header. */ public interface Resources extends AwesomeBox.Resources, AwesomeBoxResources, PopupBlockedInstructionalPopup.Resources, RunButtonTargetPopup.Resources, SearchContainer.Resources { @Source("play.png") ImageResource runIcon(); @Source("play_dropdown.png") ImageResource runDropdownIcon(); @Source("select_control.png") ImageResource selectArrows(); @Source("read_only_message_icon.png") ImageResource readOnlyMessageIcon(); @Source("trunk_branch_icon.png") ImageResource trunkBranchIcon(); @Source({"Header.css", "constants.css", "com/google/collide/client/common/constants.css"}) Css workspaceHeaderCss(); } /** * The View for the Header. */ public static class View extends CompositeView<ViewEvents> { @UiTemplate("Header.ui.xml") interface MyBinder extends UiBinder<DivElement, View> { } static MyBinder binder = GWT.create(MyBinder.class); @UiField(provided = true) final Resources res; final Css css; @UiField DivElement headerMenuElem; @UiField AnchorElement runButton; @UiField AnchorElement runDropdownButton; @UiField DivElement leftButtonGroup; @UiField DivElement syncButtonsDiv; @UiField DivElement readOnlyMessage; @UiField AnchorElement newWorkspaceButton; @UiField DivElement awesomeBoxContainer; @UiField AnchorElement feedbackButton; @UiField AnchorElement shareButton; @UiField DivElement profileImage; private final AwesomeBoxComponentHost.View awesomeBoxComponentHostView; private final ImageButton runImageButton; private final ImageButton runDropdownImageButton; private final ImageButton newWorkspaceImageButton; private final ImageButton shareImageButton; private final Tooltip newWorkspaceTooltip; public View(Resources res) { this.res = res; this.css = res.workspaceHeaderCss(); setElement(Elements.asJsElement(binder.createAndBindUi(this))); // Determine if we should use the awesome box awesomeBoxComponentHostView = new AwesomeBoxComponentHost.View(Elements.asJsElement( awesomeBoxContainer), res.awesomeBoxHostCss()); // Create the run button. runImageButton = new ImageButton.Builder(res).setImage(res.runIcon()) .setElement((elemental.html.AnchorElement) runButton).build(); runImageButton.getView().getImageElement().addClassName(res.workspaceHeaderCss().runIcon()); newWorkspaceImageButton = new ImageButton.Builder(res).setImage(res.trunkBranchIcon()) .setElement((elemental.html.AnchorElement) newWorkspaceButton).setText("Branch & Edit") .build(); newWorkspaceImageButton.getView() .getImageElement().addClassName(res.workspaceHeaderCss().newWorkspaceButton()); new ImageButton.Builder(res).setImage(res.readOnlyMessageIcon()) .setElement((elemental.html.AnchorElement) readOnlyMessage).setText("Read Only").build(); // Create the run drop down button. runDropdownImageButton = new ImageButton.Builder(res).setImage(res.runDropdownIcon()) .setElement((elemental.html.AnchorElement) runDropdownButton).build(); // Create the share button. Wait to set the icon until we know the // workspace's visibility // settings. shareImageButton = new ImageButton.Builder(res).setText("Share") .setElement((elemental.html.AnchorElement) shareButton).build(); setShareButtonVisible(false); new DebugAttributeSetter().setId(DebugId.WORKSPACE_HEADER_SHARE_BUTTON) .on(shareImageButton.getView().getElement()); // Tooltips Tooltip.create(res, Elements.asJsElement(runButton), VerticalAlign.BOTTOM, HorizontalAlign.MIDDLE, "Preview file or application"); Tooltip.create(res, Elements.asJsElement(runDropdownButton), VerticalAlign.BOTTOM, HorizontalAlign.MIDDLE, "Set custom preview target"); newWorkspaceTooltip = Tooltip.create(res, Elements.asJsElement(newWorkspaceButton), VerticalAlign.BOTTOM, HorizontalAlign.MIDDLE, "In Collide, code changes happen in branches.", "Click here to create your own editable branch of the top-level project source code."); newWorkspaceTooltip.setMaxWidth("150px"); // Wire up event handlers. attachHandlers(); } public AwesomeBoxComponentHost.View getAwesomeBoxView() { return awesomeBoxComponentHostView; } public void createReadOnlyMessageTooltip(Workspace workspace) { // boolean isTrunk = WorkspaceType.TRUNK == workspace.getWorkspaceType(); // String text; // if (isTrunk) { // text = "Click 'Branch & Edit' to make changes to Trunk"; // } else if (WorkspaceType.SUBMITTED == workspace.getWorkspaceType()) { // text = "This branch has been submitted to trunk and cannot be modified"; // } else { // text = "You have read access to this branch. Contact the owner to gain contributor access."; // } // Tooltip readOnlyMessageTooltip = Tooltip.create( // res, Elements.asJsElement(readOnlyMessage), VerticalAlign.BOTTOM, HorizontalAlign.MIDDLE, // text); // if (isTrunk) { // readOnlyMessageTooltip.setMaxWidth("150px"); // } } protected void attachHandlers() { runImageButton.setListener(new ImageButton.Listener() { @Override public void onClick() { if (getDelegate() != null) { getDelegate().onRunButtonClicked(); } } }); runDropdownImageButton.setListener(new ImageButton.Listener() { @Override public void onClick() { if (getDelegate() != null) { getDelegate().onRunDropdownButtonClicked(); } } }); shareImageButton.setListener(new ImageButton.Listener() { @Override public void onClick() { if (getDelegate() != null) { getDelegate().onShareButtonClicked(); } } }); getElement().setOnClick(new EventListener() { @Override public void handleEvent(Event evt) { ViewEvents delegate = getDelegate(); if (delegate == null) { return; } Element target = (Element) evt.getTarget(); if (feedbackButton.isOrHasChild(target)) { delegate.onFeedbackButtonClicked(); } else if (newWorkspaceButton.isOrHasChild(target)) { delegate.onNewWorkspaceButtonClicked(); } } }); } void setReadOnly(boolean isReadOnly) { if (isReadOnly) { CssUtils.setDisplayVisibility2( Elements.asJsElement(readOnlyMessage), true, false, "inline-block"); } else { CssUtils.setDisplayVisibility2(Elements.asJsElement(readOnlyMessage), false); } } void setShareButtonVisible(boolean isVisible) { CssUtils.setDisplayVisibility2(Elements.asJsElement(shareButton), isVisible); } void setRunButtonVisible(boolean isVisible) { CssUtils.setDisplayVisibility2(Elements.asJsElement(runButton), isVisible); CssUtils.setDisplayVisibility2(Elements.asJsElement(runDropdownButton), isVisible); } public Element getProfileImage() { return profileImage; } } /** * Events reported by the Header's View. */ private interface ViewEvents { void onRunButtonClicked(); void onRunDropdownButtonClicked(); void onFeedbackButtonClicked(); void onShareButtonClicked(); void onNewWorkspaceButtonClicked(); } /** * The delegate implementation for handling events reported by the View. */ private class ViewEventsImpl implements ViewEvents { @Override public void onRunButtonClicked() { runButtonController.onRunButtonClicked(); } @Override public void onRunDropdownButtonClicked() { runButtonController.onRunButtonDropdownClicked(); } @Override public void onFeedbackButtonClicked() { // TODO: something. } @Override public void onShareButtonClicked() { // was show manage membership } @Override public void onNewWorkspaceButtonClicked() { // was new workspace overlay } } private final AwesomeBoxComponentHost awesomeBoxComponentHost; private final RunButtonController runButtonController; private static final int PORTRAIT_SIZE_HEADER = 28; private Header(View view, WorkspacePlace currentPlace, AppContext appContext, RunButtonController runButtonController, AwesomeBoxComponentHost awesomeBoxComponentHost) { super(view); this.runButtonController = runButtonController; this.awesomeBoxComponentHost = awesomeBoxComponentHost; setProfileImage(); view.setDelegate(new ViewEventsImpl()); } public AwesomeBoxComponentHost getAwesomeBoxComponentHost() { return awesomeBoxComponentHost; } private void setProfileImage() { ImageElement pic = Elements.createImageElement(getView().css.profileImage()); pic.setSrc(UserDetails.Utils.getSizeSpecificPortraitUrl( BootstrapSession.getBootstrapSession().getProfileImageUrl(), PORTRAIT_SIZE_HEADER)); Elements.asJsElement(getView().getProfileImage()).appendChild(pic); } }