// 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.code;
import com.google.collide.client.AppContext;
import com.google.collide.client.code.FileSelectionController.FileOpenedEvent;
import com.google.collide.client.code.errorrenderer.EditorErrorListener;
import com.google.collide.client.collaboration.IncomingDocOpDemultiplexer;
import com.google.collide.client.document.DocumentManager;
import com.google.collide.client.editor.Editor.DocumentListener;
import com.google.collide.client.history.Place;
import com.google.collide.client.history.PlaceNavigationEvent;
import com.google.collide.client.search.FileNameSearch;
import com.google.collide.client.search.SearchPlace;
import com.google.collide.client.search.SearchPlaceNavigationHandler;
import com.google.collide.client.search.awesomebox.AwesomeBoxContext;
import com.google.collide.client.search.awesomebox.FileNameNavigationSection;
import com.google.collide.client.search.awesomebox.GotoActionSection;
import com.google.collide.client.search.awesomebox.OutlineViewAwesomeBoxSection;
import com.google.collide.client.search.awesomebox.PrimaryWorkspaceActionSection;
import com.google.collide.client.search.awesomebox.PrimaryWorkspaceActionSection.FindActionSelectedCallback;
import com.google.collide.client.search.awesomebox.components.FindReplaceComponent;
import com.google.collide.client.search.awesomebox.components.FindReplaceComponent.FindMode;
import com.google.collide.client.search.awesomebox.host.AwesomeBoxComponentHost.AwesomeBoxComponentHiddenListener;
import com.google.collide.client.util.PathUtil;
import com.google.collide.client.util.dom.eventcapture.GlobalHotKey;
import com.google.collide.client.workspace.FileTreeModel;
import com.google.collide.client.workspace.FileTreeNodeMoveController;
import com.google.collide.client.workspace.WorkspacePlace;
import com.google.collide.client.workspace.WorkspaceShell;
import com.google.collide.client.workspace.outline.OutlineModel;
import com.google.collide.client.workspace.outline.OutlineSection;
import com.google.collide.dto.GetWorkspaceMetaDataResponse;
import com.google.collide.shared.document.Document;
import com.google.common.base.Preconditions;
import com.google.gwt.core.client.Scheduler;
import org.waveprotocol.wave.client.common.util.SignalEvent;
import elemental.html.Element;
/**
* Dependencies and Presenter setup code for everything under the workspace header involved with
* code editing.
*/
/*
* TODO: Some cleanup in here now that the this class is created and invoked synchronously.
* Particularly lets extract the AwesomeBox cruft and see if anything else makes sense to bundle
* together into a separate class.
*/
public class CodePanelBundle {
// Dependencies passed in at Handler construction time.
private final AppContext appContext;
private final WorkspaceShell shell;
private final FileTreeModel fileTreeModel;
private final DocumentManager documentManager;
private final ParticipantModel participantModel;
private final IncomingDocOpDemultiplexer docOpReceiver;
// AwesomeBox Related
public final FileNameSearch searchIndex;
public final AwesomeBoxContext awesomeBoxCodeContext;
private final FileNameNavigationSection fileNavSection;
private final PrimaryWorkspaceActionSection primaryWorkspaceActionsSection;
private final GotoActionSection gotoActionSection;
private final OutlineViewAwesomeBoxSection outlineViewAwesomeBoxSection;
private final WorkspacePlace workspacePlace;
// AwesomeBox Components
private final FindReplaceComponent findReplaceComponent;
// Presenters and Controllers that require cleanup.
private EditorBundle editorBundle;
private EditorReloadingFileTreeListener editorReloadingFileTreeListener;
private NoFileSelectedPanel welcomePanel;
private FileSelectionController fileSelectionController;
private FileTreeNodeMoveController treeNodeMoveController;
private FileTreeSection fileTreeSection;
private EditableContentArea contentArea;
public CodePanelBundle(AppContext appContext,
WorkspaceShell shell,
FileTreeModel fileTreeModel,
FileNameSearch searchIndex,
DocumentManager documentManager,
ParticipantModel participantModel,
IncomingDocOpDemultiplexer docOpReceiver,
WorkspacePlace workspacePlace) {
this.appContext = appContext;
this.shell = shell;
this.fileTreeModel = fileTreeModel;
this.searchIndex = searchIndex;
this.documentManager = documentManager;
this.participantModel = participantModel;
this.docOpReceiver = docOpReceiver;
this.workspacePlace = workspacePlace;
awesomeBoxCodeContext = new AwesomeBoxContext(new AwesomeBoxContext.Builder().setWaterMarkText(
"Type to find files and use actions").setPlaceholderText("Actions"));
fileNavSection = new FileNameNavigationSection(appContext.getResources(), searchIndex);
primaryWorkspaceActionsSection = new PrimaryWorkspaceActionSection(appContext.getResources());
gotoActionSection = new GotoActionSection(appContext.getResources());
outlineViewAwesomeBoxSection = new OutlineViewAwesomeBoxSection(appContext.getResources());
// Special Components
findReplaceComponent =
new FindReplaceComponent(new FindReplaceComponent.View(appContext.getResources()));
initializeAwesomeBoxContext();
}
private void initializeAwesomeBoxContext() {
primaryWorkspaceActionsSection.getFindActionSelectionListener()
.add(new FindActionSelectedCallback() {
@Override
public void onSelected(FindMode mode) {
findReplaceComponent.setFindMode(mode);
appContext.getAwesomeBoxComponentHostModel().setActiveComponent(findReplaceComponent);
shell.getHeader().getAwesomeBoxComponentHost().show();
}
});
awesomeBoxCodeContext.addSection(outlineViewAwesomeBoxSection);
awesomeBoxCodeContext.addSection(fileNavSection);
awesomeBoxCodeContext.addSection(gotoActionSection);
awesomeBoxCodeContext.addSection(primaryWorkspaceActionsSection);
}
public void cleanup() {
editorReloadingFileTreeListener.cleanup();
editorBundle.cleanup();
if (welcomePanel != null) {
welcomePanel.detach();
}
treeNodeMoveController.cleanup();
fileTreeSection.cleanup();
GlobalHotKey.unregister(appContext.getKeyBindings().localFind());
GlobalHotKey.unregister(appContext.getKeyBindings().localReplace());
GlobalHotKey.unregister(appContext.getKeyBindings().gotoLine());
GlobalHotKey.unregister(appContext.getKeyBindings().snapshot());
}
public void attach() {
// Construct the Root View for the CodePerspective.
CodePerspective.Resources res = appContext.getResources();
CodePerspective.View codePerspectiveView = new CodePerspective.View(res);
// Then create all the Presenters.
OutlineModel outlineModel = new OutlineModel();
editorBundle = EditorBundle.create(appContext,
workspacePlace,
documentManager,
participantModel,
outlineModel,
fileTreeModel,
EditorErrorListener.NOOP_ERROR_RECEIVER);
UneditableDisplay uneditableDisplay = UneditableDisplay.create(new UneditableDisplay.View(res));
// AwesomeBox Section Setup
appContext.getAwesomeBoxModel().changeContext(awesomeBoxCodeContext);
outlineViewAwesomeBoxSection.setOutlineModelAndEditor(outlineModel, editorBundle.getEditor());
registerAwesomeBoxEvents();
editorReloadingFileTreeListener =
EditorReloadingFileTreeListener.create(workspacePlace, editorBundle, fileTreeModel);
fileTreeSection = FileTreeSection.create(
workspacePlace, appContext, fileTreeModel, editorBundle.getDebuggingModelController());
fileTreeSection.getTree().renderTree(0);
// TODO: The term "Section" is overloaded here. It
// conflates the NavigationSection (the File Tree and the Conflict List)
// with panels in the NavigationToolBar. The Collab and Outline Sections
// here are actually panels on the NavigationToolBar. Clean this up.
CollaborationSection collabSection = CollaborationSection.create(
new CollaborationSection.View(res), participantModel, appContext);
treeNodeMoveController = new FileTreeNodeMoveController(
appContext, fileTreeSection.getFileTreeUiController(), fileTreeModel);
OutlineSection outlineSection = OutlineSection.create(
new OutlineSection.View(res), appContext, outlineModel,
editorBundle.getOutlineController());
final WorkspaceNavigationToolBar navigationToolBar = WorkspaceNavigationToolBar.create(
codePerspectiveView.getNavigationView().getNavigationToolBarView(), collabSection,
outlineSection);
WorkspaceNavigation workspaceNavigation = WorkspaceNavigation.create(
codePerspectiveView.getNavigationView(),
new WorkspaceNavigationSection[] {fileTreeSection},
new WorkspaceNavigationSection[] {collabSection, outlineSection}, navigationToolBar);
navigationToolBar.setWorkspaceNavigation(workspaceNavigation);
contentArea =
EditableContentArea.create(codePerspectiveView.getContentView(), appContext, editorBundle);
CodePerspective codePerspective = CodePerspective.create(
codePerspectiveView, workspacePlace, workspaceNavigation, contentArea, appContext);
// Connect views to the DOM.
Element rightSidebarContentContainer = codePerspectiveView.getSidebarElement();
rightSidebarContentContainer.appendChild(
editorBundle.getDebuggingModelController().getDebuggingSidebarElement());
shell.setPerspective(codePerspectiveView.getElement());
fileSelectionController = new FileSelectionController(documentManager,
editorBundle,
uneditableDisplay,
fileTreeModel,
fileTreeSection.getFileTreeUiController(),
contentArea);
// Creates the welcome panel
welcomePanel = NoFileSelectedPanel.create(workspacePlace, appContext.getResources());
// Register navigation handler for FileSelection.
FileSelectedPlaceNavigationHandler fileSelectionHandler =
new FileSelectedPlaceNavigationHandler(appContext,
codePerspective,
fileSelectionController,
contentArea);
workspacePlace.registerChildHandler(FileSelectedPlace.PLACE, fileSelectionHandler);
// Register navigation handler for searching within files.
SearchPlaceNavigationHandler searchHandler = new SearchPlaceNavigationHandler(appContext,
contentArea, fileTreeSection.getFileTreeUiController(), workspacePlace);
workspacePlace.registerChildHandler(SearchPlace.PLACE, searchHandler);
}
public void enterWorkspace(final PlaceNavigationEvent<? extends Place> navigationEvent,
final GetWorkspaceMetaDataResponse metadata) {
if (!navigationEvent.isActiveLeaf()) {
return;
}
// If there are no previously open files, show the welcome
// panel.
if (metadata.getLastOpenFiles().size() == 0) {
showNoFileSelectedPanel();
return;
}
Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
@Override
public void execute() {
// we check the very first file since that's all we support right now
String file = metadata.getLastOpenFiles().get(0);
Preconditions.checkNotNull(file, "Somehow the file to navigate to was null");
PlaceNavigationEvent<FileSelectedPlace> event =
FileSelectedPlace.PLACE.createNavigationEvent(new PathUtil(file));
navigationEvent.getPlace().fireChildPlaceNavigation(event);
}
});
}
public void showNoFileSelectedPanel() {
editorBundle.getBreadcrumbs().clearPath();
contentArea.setContent(welcomePanel, false);
}
/**
* Register's events that the AwesomeBox context uses.
*/
private void registerAwesomeBoxEvents() {
// If these fail someone called the registerAwesomeBoxEvents method
// to early in enterPlace. Lets notify them clearly.
Preconditions.checkArgument(
editorBundle != null, "Editor must be created before registering awesome box events.");
// Register Hiding Listener
shell.getHeader().getAwesomeBoxComponentHost().getComponentHiddenListener()
.add(new AwesomeBoxComponentHiddenListener() {
@Override
public void onHidden(Reason reason) {
if (reason != Reason.OTHER) {
editorBundle.getEditor().getFocusManager().focus();
}
}
});
// Attach the editor to the editorActionSection
gotoActionSection.attachEditorAndPlace(workspacePlace, editorBundle.getEditor());
// We register the file opened events.
fileNavSection.registerOnFileOpenedHandler(workspacePlace);
primaryWorkspaceActionsSection.registerOnFileOpenedHandler(workspacePlace);
workspacePlace.registerSimpleEventHandler(FileOpenedEvent.TYPE, new FileOpenedEvent.Handler() {
@Override
public void onFileOpened(boolean isEditable, PathUtil filePath) {
shell.getHeader().getAwesomeBoxComponentHost().hide();
}
});
// Hook find/replace upto the editor.
editorBundle.getEditor().getDocumentListenerRegistrar().add(new DocumentListener() {
@Override
public void onDocumentChanged(Document oldDocument, Document newDocument) {
findReplaceComponent.attachEditor(editorBundle.getEditor());
}
});
// Register any hotkeys
GlobalHotKey.Handler findAndReplaceHandler = new GlobalHotKey.Handler() {
@Override
public void onKeyDown(SignalEvent event) {
if (fileSelectionController.isSelectedFileEditable()) {
findReplaceComponent.setFindMode(event.getShiftKey() ? FindMode.REPLACE : FindMode.FIND);
String selectedText = editorBundle.getEditor().getSelection().getSelectedText();
boolean isFindActive = findReplaceComponent.isActive();
boolean shouldReplaceIfInFindMode = isFindActive && !selectedText.isEmpty();
// TODO: Check the AB query once it exists
// && !abQuery.isEmpty();
boolean shouldReplaceIfNotInFindMode = !isFindActive && !selectedText.isEmpty();
if (shouldReplaceIfInFindMode || shouldReplaceIfNotInFindMode) {
findReplaceComponent.setQuery(selectedText);
}
appContext.getAwesomeBoxComponentHostModel().setActiveComponent(findReplaceComponent);
}
shell.getHeader().getAwesomeBoxComponentHost().show();
}
};
GlobalHotKey.register(
appContext.getKeyBindings().localFind(), findAndReplaceHandler, "local find");
GlobalHotKey.register(
appContext.getKeyBindings().localReplace(), findAndReplaceHandler, "local replace");
GlobalHotKey.register(appContext.getKeyBindings().gotoLine(), new GlobalHotKey.Handler() {
@Override
public void onKeyDown(SignalEvent event) {
// TODO: Make a component which handles goto only.
shell.getHeader().getAwesomeBoxComponentHost().show();
}
}, "goto line");
}
}