blob: 79c993850e49622ee1a4d33e68c5b616ede7ac89 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.internal.ua.ui.editor.toc;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.action.*;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.*;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.pde.core.IModelChangedEvent;
import org.eclipse.pde.internal.core.text.IDocumentElementNode;
import org.eclipse.pde.internal.ua.core.toc.ITocConstants;
import org.eclipse.pde.internal.ua.core.toc.text.*;
import org.eclipse.pde.internal.ua.ui.PDEUserAssistanceUIPlugin;
import org.eclipse.pde.internal.ua.ui.editor.toc.actions.*;
import org.eclipse.pde.internal.ua.ui.wizards.toc.NewTocFileWizard;
import org.eclipse.pde.internal.ua.ui.wizards.toc.TocHTMLWizard;
import org.eclipse.pde.internal.ui.editor.ModelDataTransfer;
import org.eclipse.pde.internal.ui.editor.PDEFormPage;
import org.eclipse.pde.internal.ui.editor.TreeSection;
import org.eclipse.pde.internal.ui.editor.actions.CollapseAction;
import org.eclipse.pde.internal.ui.editor.plugin.FormFilteredTree;
import org.eclipse.pde.internal.ui.parts.TreePart;
import org.eclipse.pde.internal.ui.util.PDELabelUtility;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.ContributionItemFactory;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.dialogs.WizardNewFileCreationPage;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.keys.IBindingService;
/**
* TocTreeSection - The section that displays the TOC
* tree structure and any buttons used to manipulate it.
* This is the main section that the user will interact
* with the TOC through.
*/
public class TocTreeSection extends TreeSection {
private TocModel fModel;
private TreeViewer fTocTree;
private FormFilteredTree fFilteredTree;
/* The indices for each button attached to the Tree Viewer.
* This type of UI form does not permit direct access to each particular
* button. However, using these indices, one can perform any typical SWT
* operation on any button.
*/
private static final int F_BUTTON_ADD_TOPIC = 0;
private static final int F_BUTTON_ADD_LINK = 3;
private static final int F_BUTTON_ADD_ANCHOR = 4;
private static final int F_BUTTON_REMOVE = 5;
private static final int F_BUTTON_UP = 6;
private static final int F_BUTTON_DOWN = 7;
private static final int F_UP_FLAG = -1;
private static final int F_DOWN_FLAG = 1;
private class TocOpenLinkAction extends Action {
private TocObject fOpenTarget;
public TocOpenLinkAction() {
setText(TocMessages.TocTreeSection_open);
}
public void setTarget(TocObject target) {
fOpenTarget = target;
}
public void run() {
if (fOpenTarget != null) {
open(fOpenTarget);
}
}
}
// The action that collapses down the TOC tree
private CollapseAction fCollapseAction;
// The actions that will add each type of TOC object
private TocAddTopicAction fAddTopicAction;
private TocAddLinkAction fAddLinkAction;
private TocAddAnchorAction fAddAnchorAction;
// The object removal action
private TocRemoveObjectAction fRemoveObjectAction;
// The action for opening a link from the context menu
private TocOpenLinkAction fOpenLinkAction;
// The adapter that will listen for drag events in the tree
private TocDragAdapter fDragAdapter;
/* If items are dragged and dropped within this tree, then
* this flag inhibits reselection on the removal (drag) action,
* thus ensuring that the selected objects are the ones that were
* dropped.
*/
private boolean fDragFromHere;
/**
* Constructs a new TOC tree section.
*
* @param formPage The page that will hold this new tree section
* @param parent The parent composite in the page that will contain the section widgets
*/
public TocTreeSection(PDEFormPage formPage, Composite parent) {
/* Create a new section with a description area, and some buttons.
* The null entries in the String array will become blank space
* separators between the buttons.
*/
super(formPage, parent, Section.DESCRIPTION, new String[] {TocMessages.TocTreeSection_addTopic, null, null, TocMessages.TocTreeSection_addLink, TocMessages.TocTreeSection_addAnchor, TocMessages.TocTreeSection_remove, TocMessages.TocTreeSection_up, TocMessages.TocTreeSection_down});
// Initialize all the actions
fAddTopicAction = new TocAddTopicAction();
fAddLinkAction = new TocAddLinkAction();
fAddAnchorAction = new TocAddAnchorAction();
fRemoveObjectAction = new TocRemoveObjectAction();
fOpenLinkAction = new TocOpenLinkAction();
}
/* (non-Javadoc)
* @see org.eclipse.pde.internal.ui.editor.PDESection#createClient(org.eclipse.ui.forms.widgets.Section, org.eclipse.ui.forms.widgets.FormToolkit)
*/
protected void createClient(Section section, FormToolkit toolkit) {
// Get the model
fModel = (TocModel) getPage().getModel();
// Create a container in the section
Composite container = createClientContainer(section, 2, toolkit);
// Create a TOC tree in the new container
createTree(container, toolkit);
toolkit.paintBordersFor(container);
section.setText(TocMessages.TocTreeSection_sectionText);
section.setDescription(TocMessages.TocTreeSection_sectionDesc);
section.setClient(container);
initializeTreeViewer();
createSectionToolbar(section, toolkit);
// Create the adapted listener for the filter entry field
fFilteredTree.createUIListenerEntryFilter(this);
}
/**
* Adds a link (with hand cursor) for tree 'Collapse All' action,
* which collapses the TOC tree down to the second level
*
* @param section The section that the toolbar will belong to
* @param toolkit The toolkit that will be used to make the toolbar
*/
private void createSectionToolbar(Section section, FormToolkit toolkit) {
ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
ToolBar toolbar = toolBarManager.createControl(section);
final Cursor handCursor = new Cursor(Display.getCurrent(), SWT.CURSOR_HAND);
toolbar.setCursor(handCursor);
// Cursor needs to be explicitly disposed
toolbar.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
if ((handCursor != null) && (handCursor.isDisposed() == false)) {
handCursor.dispose();
}
}
});
// Add collapse action to the tool bar
fCollapseAction = new CollapseAction(fTocTree, TocMessages.TocTreeSection_collapseAll, 1, fModel.getToc());
toolBarManager.add(fCollapseAction);
toolBarManager.update(true);
section.setTextClient(toolbar);
}
/**
* Create the tree widget that will contain the TOC
*
* @param container The container of the tree widget
* @param toolkit The toolkit used to create the tree
*/
private void createTree(Composite container, FormToolkit toolkit) {
TreePart treePart = getTreePart();
createViewerPartControl(container, SWT.MULTI, 2, toolkit);
fTocTree = treePart.getTreeViewer();
fTocTree.setContentProvider(new TocContentProvider());
fTocTree.setLabelProvider(PDEUserAssistanceUIPlugin.getDefault().getLabelProvider());
PDEUserAssistanceUIPlugin.getDefault().getLabelProvider().connect(this);
createTreeListeners();
initDragAndDrop();
}
/**
* Initialize the section's drag and drop capabilities
*/
private void initDragAndDrop() {
int ops = DND.DROP_COPY;
if (isEditable()) {
ops |= DND.DROP_MOVE;
}
//Content dragged from the tree viewer can be treated as model objects (TocObjects)
//or as text (XML representation of the TocObjects)
Transfer[] dragTransfers = new Transfer[] {ModelDataTransfer.getInstance(), TextTransfer.getInstance()};
fDragAdapter = new TocDragAdapter(this);
fTocTree.addDragSupport(ops, dragTransfers, fDragAdapter);
if (isEditable()) { //Model objects and files can be dropped onto the viewer
//TODO: Consider allowing drops/pastes of pure XML text
Transfer[] dropTransfers = new Transfer[] {ModelDataTransfer.getInstance(), FileTransfer.getInstance()};
fTocTree.addDropSupport(ops | DND.DROP_DEFAULT, dropTransfers, new TocDropAdapter(fTocTree, this));
}
}
/**
* Create the action listeners for the tree.
*/
private void createTreeListeners() {
// Create listener for the outline view 'link with editor' toggle button
fTocTree.addPostSelectionChangedListener(getPage().getPDEEditor().new PDEFormEditorChangeListener());
}
/**
* Initialize the tree viewer widget and its buttons.
*/
private void initializeTreeViewer() {
if (fModel == null) {
return;
}
// Connect the tree viewer to the TOC model
fTocTree.setInput(fModel);
Toc toc = fModel.getToc();
// Nodes can always be added to the root TOC node
getTreePart().setButtonEnabled(F_BUTTON_ADD_TOPIC, isEditable());
getTreePart().setButtonEnabled(F_BUTTON_ADD_ANCHOR, isEditable());
getTreePart().setButtonEnabled(F_BUTTON_ADD_LINK, isEditable());
// Set to false because initial node selected is the root TOC node
getTreePart().setButtonEnabled(F_BUTTON_REMOVE, false);
// Set to false because initial node selected is the root TOC node
getTreePart().setButtonEnabled(F_BUTTON_UP, false);
// Set to false because initial node selected is the root TOC node
getTreePart().setButtonEnabled(F_BUTTON_DOWN, false);
//Initially, the root TOC element is selected
fTocTree.setSelection(new StructuredSelection(toc), true);
fTocTree.expandToLevel(2);
}
/* (non-Javadoc)
* @see org.eclipse.ui.forms.AbstractFormPart#setFormInput(java.lang.Object)
*/
public boolean setFormInput(Object object) {
// This method allows the outline view to select items in the tree
// (Invoked by org.eclipse.ui.forms.editor.IFormPage.selectReveal(Object object))
if (object instanceof TocObject) { // Select the item in the tree
fTocTree.setSelection(new StructuredSelection(object), true);
// Verify that something was actually selected
ISelection selection = fTocTree.getSelection();
if (selection != null && !selection.isEmpty()) {
return true;
}
}
return false;
}
/**
* @return the selection of the tree section
*/
public ISelection getSelection() {
return fTocTree.getSelection();
}
/**
* @param selection the new selection for the tree section
*/
public void setSelection(ISelection selection) {
fTocTree.setSelection(selection);
}
/**
* Fire a selection change event and refresh the viewer's selection
*/
public void fireSelection() {
fTocTree.setSelection(fTocTree.getSelection());
}
/* (non-Javadoc)
* @see org.eclipse.pde.internal.ui.editor.TreeSection#selectionChanged(org.eclipse.jface.viewers.IStructuredSelection)
*/
protected void selectionChanged(IStructuredSelection selection) {
getPage().getPDEEditor().setSelection(selection);
updateButtons();
}
/**
* Update the buttons in the section based on the current selection
*/
public void updateButtons() {
if (!fModel.isEditable()) {
return;
}
// 'Add' actions are enabled if any object in the selection can
// be added to
boolean canAddObject = false;
// 'Remove' is enabled if any object in the selection is removable
boolean canRemove = false;
IStructuredSelection sel = (IStructuredSelection) fTocTree.getSelection();
//TODO: Implement multi-select move actions from the root TOC element
// 'Up' is disabled if any object in the selection can't be moved up.
boolean canMoveUp = sel.size() == 1;
// 'Down' is disabled if any object in the selection can't be moved down.
boolean canMoveDown = sel.size() == 1;
for (Iterator iter = sel.iterator(); iter.hasNext();) {
TocObject tocObject = (TocObject) iter.next();
if (tocObject != null) {
if (tocObject.canBeRemoved()) {
canRemove = true;
}
TocObject parent = tocObject.getParent();
if (sel.size() == 1 && (tocObject.getType() == ITocConstants.TYPE_TOC || parent.getType() == ITocConstants.TYPE_TOPIC || parent.getType() == ITocConstants.TYPE_TOC)) {
/* Semantic rule:
* As long as the selection is a child of a
* TOC root or a topic, or the selection itself
* is a TOC root, then a new object can be added
* either to the selection or to the parent
*/
canAddObject = true;
}
//Semantic rule:
//You cannot rearrange the TOC root itself
if (tocObject.getType() == ITocConstants.TYPE_TOC) {
canMoveUp = false;
canMoveDown = false;
} else {
if (parent != null) {
TocTopic topic = (TocTopic) parent;
if (topic.isFirstChildObject(tocObject)) {
canMoveUp = false;
}
if (topic.isLastChildObject(tocObject)) {
canMoveDown = false;
}
}
}
} else { // How anyone can select a null object, I don't know.
// However, if it happens, disable all buttons.
canAddObject = false;
canRemove = false;
canMoveUp = false;
canMoveDown = false;
break;
}
}
getTreePart().setButtonEnabled(F_BUTTON_ADD_TOPIC, canAddObject);
getTreePart().setButtonEnabled(F_BUTTON_ADD_LINK, canAddObject);
getTreePart().setButtonEnabled(F_BUTTON_ADD_ANCHOR, canAddObject);
getTreePart().setButtonEnabled(F_BUTTON_REMOVE, canRemove);
getTreePart().setButtonEnabled(F_BUTTON_UP, canMoveUp);
getTreePart().setButtonEnabled(F_BUTTON_DOWN, canMoveDown);
}
/* (non-Javadoc)
* @see org.eclipse.pde.internal.ui.editor.StructuredViewerSection#fillContextMenu(org.eclipse.jface.action.IMenuManager)
*/
protected void fillContextMenu(IMenuManager manager) {
// Get the current selection
ISelection selection = fTocTree.getSelection();
Object object = ((IStructuredSelection) selection).getFirstElement();
// Has to be null or a TOC object
TocObject tocObject = (TocObject) object;
if (tocObject != null) {
boolean emptyMenu = true;
if (tocObject.canBeParent()) { // Create the "New" sub-menu
MenuManager submenu = new MenuManager(TocMessages.TocTreeSection_New);
// Populate the "New" sub-menu
fillContextMenuAddActions(submenu, tocObject);
// Add the "New" sub-menu to the main context menu
manager.add(submenu);
emptyMenu = false;
}
if (tocObject.getPath() != null) {
fOpenLinkAction.setTarget(tocObject);
manager.add(fOpenLinkAction);
emptyMenu = false;
}
if (!emptyMenu) { // Add a separator to the main context menu
manager.add(new Separator());
}
}
// Add clipboard actions
getPage().getPDEEditor().getContributor().contextMenuAboutToShow(manager);
manager.add(new Separator());
if (tocObject != null) { // Add the Remove action and Show In action if an object is selected
fillContextMenuRemoveAction(manager, tocObject);
manager.add(new Separator());
fillContextMenuShowInAction(manager);
manager.add(new Separator());
}
}
private void fillContextMenuShowInAction(IMenuManager manager) {
String showInLabel = TocMessages.TocTreeSection_showIn;
// Add a label for the keybinding for Show In action, if one exists
IBindingService bindingService = (IBindingService) PlatformUI.getWorkbench().getAdapter(IBindingService.class);
if (bindingService != null) {
String keyBinding = bindingService.getBestActiveBindingFormattedFor("org.eclipse.ui.navigate.showInQuickMenu"); //$NON-NLS-1$
if (keyBinding != null) {
showInLabel += '\t' + keyBinding;
}
}
// Add the "Show In" action and its contributions
IMenuManager showInMenu = new MenuManager(showInLabel);
showInMenu.add(ContributionItemFactory.VIEWS_SHOW_IN.create(getPage().getSite().getWorkbenchWindow()));
manager.add(showInMenu);
}
/**
* Add the addition actions (Topic, Link, Anchor) to the specified submenu
*
* @param submenu The submenu to add the addition actions to
* @param tocObject The object that the additions would occur relative to
*/
private void fillContextMenuAddActions(MenuManager submenu, TocObject tocObject) {
if (tocObject != null && tocObject.canBeParent()) { // Add the 'Add Topic' action to the sub-menu
fAddTopicAction.setParentObject(tocObject);
fAddTopicAction.setEnabled(fModel.isEditable());
submenu.add(fAddTopicAction);
// Add the 'Add Link' action to the sub-menu
fAddLinkAction.setParentObject(tocObject);
fAddLinkAction.setEnabled(fModel.isEditable());
submenu.add(fAddLinkAction);
// Add the 'Add Anchor' action to the sub-menu
fAddAnchorAction.setParentObject(tocObject);
fAddAnchorAction.setEnabled(fModel.isEditable());
submenu.add(fAddAnchorAction);
}
}
/**
* Add the remove action to the context menu.
*
* @param manager The context menu to add the remove action to
* @param tocObject The object that would be targetted for removal
*/
private void fillContextMenuRemoveAction(IMenuManager manager, TocObject tocObject) {
// Add to the main context menu
// Delete task object action
fRemoveObjectAction.setToRemove(tocObject);
manager.add(fRemoveObjectAction);
fRemoveObjectAction.setEnabled(tocObject.canBeRemoved() && fModel.isEditable());
}
/* (non-Javadoc)
* @see org.eclipse.pde.internal.ui.editor.StructuredViewerSection#canPaste(java.lang.Object, java.lang.Object[])
*/
protected boolean canPaste(Object targetObject, Object[] sourceObjects) {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.pde.internal.ui.editor.PDESection#doGlobalAction(java.lang.String)
*/
public boolean doGlobalAction(String actionId) {
boolean cutAction = actionId.equals(ActionFactory.CUT.getId());
if (cutAction || actionId.equals(ActionFactory.DELETE.getId())) {
handleDeleteAction();
return !cutAction;
}
if (actionId.equals(ActionFactory.PASTE.getId())) {
doPaste();
return true;
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.pde.internal.ui.editor.StructuredViewerSection#doPaste(java.lang.Object, java.lang.Object[])
*/
protected void doPaste(Object targetObject, Object[] sourceObjects) {
performDrop(targetObject, sourceObjects, ViewerDropAdapter.LOCATION_ON);
}
/* (non-Javadoc)
* @see org.eclipse.pde.internal.ui.editor.TreeSection#handleDoubleClick(org.eclipse.jface.viewers.IStructuredSelection)
*/
protected void handleDoubleClick(IStructuredSelection selection) {
Object selected = selection.getFirstElement();
if (selected instanceof TocObject) {
if (((TocObject) selected).hasXMLChildren()) {
fTocTree.setExpandedState(selected, !fTocTree.getExpandedState(selected));
} else {
open((TocObject) selected);
}
}
}
/**
* Opens a document with the specified path
*
* @param path a path to a resource, relative to this TOC's root project
*/
private void open(TocObject obj) {
String path = obj.getPath();
Path resourcePath = path != null ? new Path(path) : null;
if (!isEditable() || resourcePath == null || resourcePath.isEmpty()) {
MessageDialog.openWarning(PDEUserAssistanceUIPlugin.getActiveWorkbenchShell(), TocMessages.TocTreeSection_openFile, TocMessages.TocTreeSection_openFileMessage);
return;
}
IResource resource = findResource(resourcePath);
if (resource != null && resource instanceof IFile) {
openResource(resource, obj.getType() == ITocConstants.TYPE_LINK);
} else {
MessageDialog.openWarning(PDEUserAssistanceUIPlugin.getActiveWorkbenchShell(), TocMessages.TocTreeSection_openFile, TocMessages.TocTreeSection_openFileMessage2);
}
}
public IFile openFile(String path, boolean isTOCFile) {
Path resourcePath = new Path(path);
if (isEditable()) {
if (!resourcePath.isEmpty()) {
IResource page = findResource(resourcePath);
if (page != null && page instanceof IFile) {
openResource(page, isTOCFile);
return null;
}
}
return showNewWizard(path, isTOCFile);
}
return null;
}
private IFile showNewWizard(String path, boolean tocWizard) {
TocHTMLWizard wizard;
if (tocWizard) {
wizard = new NewTocFileWizard();
} else {
wizard = new TocHTMLWizard();
}
// By default, the file will be created in the same project as the TOC
IResource selectedFolder = fModel.getUnderlyingResource().getProject();
String filename = null;
// Find the folder associated with the specified path
IPath initialFolder = new Path(path.trim());
if (!initialFolder.isEmpty()) {
IPath newPath = selectedFolder.getFullPath().append(initialFolder);
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IResource newFolder = root.findMember(newPath);
if (newFolder == null) {
if (!newPath.hasTrailingSeparator()) {
filename = newPath.lastSegment();
}
}
while (newFolder == null && !newPath.isEmpty()) {
newPath = newPath.removeLastSegments(1);
newFolder = root.findMember(newPath);
}
if (newFolder != null) {
selectedFolder = newFolder;
}
}
// Select the project in the wizard
wizard.init(PlatformUI.getWorkbench(), new StructuredSelection(selectedFolder));
// Create the dialog for the wizard
WizardDialog dialog = new WizardDialog(PDEUserAssistanceUIPlugin.getActiveWorkbenchShell(), wizard);
dialog.create();
// Get the wizard page
IWizardPage wizardPage;
wizardPage = wizard.getStartingPage();
if (!(wizardPage instanceof WizardNewFileCreationPage)) {
return null;
}
WizardNewFileCreationPage page = (WizardNewFileCreationPage) wizardPage;
if (filename != null) {
page.setFileName(filename);
// Inhibit the error message when the wizard is first opened
page.setErrorMessage(null);
}
if (dialog.open() == Window.OK) {
return wizard.getNewResource();
}
return null;
}
private IResource findResource(Path resourcePath) {
IProject pluginProject = fModel.getUnderlyingResource().getProject();
return pluginProject.findMember(resourcePath);
}
private void openResource(IResource resource, boolean tocFile) {
IPath path = resource.getFullPath();
if (isFileValidInContext(tocFile, path)) {
try {
IDE.openEditor(PDEUserAssistanceUIPlugin.getActivePage(), (IFile) resource, true);
} catch (PartInitException e) { //suppress exception
}
}
}
private boolean isFileValidInContext(boolean tocFile, IPath path) {
String message = null;
if (tocFile) {
if (HelpEditorUtil.isTOCFile(path)) {
return true;
}
message = TocMessages.TocTreeSection_errorMessage1;
} else {
if (HelpEditorUtil.hasValidPageExtension(path)) {
return true;
}
message = TocMessages.TocTreeSection_errorMessage2;
}
MessageDialog.openWarning(PDEUserAssistanceUIPlugin.getActiveWorkbenchShell(), TocMessages.TocTreeSection_openFile, message);
return false;
}
/**
* Perform a drop of the specified objects on the target in the widget
*
* @param currentTarget The object that the drop will occur near/on
* @param dropped The dropped objects
* @param location The location of the drop relative to the target
*
* @return true iff the drop was successful
*/
public boolean performDrop(Object currentTarget, Object dropped, int location) {
if (dropped instanceof Object[]) {
TocObject tocTarget = (TocObject) currentTarget;
// Determine the object that the dropped objects will be the
// children of
TocTopic targetParent = determineParent(tocTarget, location);
if (location == TocDropAdapter.LOCATION_JUST_AFTER && targetParent == tocTarget && !tocTarget.getChildren().isEmpty() && fTocTree.getExpandedState(tocTarget)) { // If the drop occurs just after a parentable object
// and it is expanded, then insert the dropped items
// as the first children of the parent
location = ViewerDropAdapter.LOCATION_BEFORE;
tocTarget = (TocObject) tocTarget.getChildren().get(0);
}
if (targetParent != null) { // Get the TocObject versions of the dropped objects
ArrayList objectsToAdd = getObjectsToAdd((Object[]) dropped, targetParent);
if (objectsToAdd != null && !objectsToAdd.isEmpty()) {
if (fDragAdapter.getDraggedElements() != null && fDragAdapter.getDraggedElements().size() == 1 && currentTarget == fDragAdapter.getDraggedElements().get(0)) { // Last-minute check: ignore drops of an object onto/near itself
// to avoid unnecessarily dirtying the page
return false;
}
boolean insertBefore = (location == ViewerDropAdapter.LOCATION_BEFORE);
// Add the objects
handleMultiAddAction(objectsToAdd, tocTarget, insertBefore, targetParent);
return true;
}
}
}
return false;
}
/**
* Determine the parent object that a drop will occur under,
* based on the relative location of the drop and the ability
* of the target to be a parent
*
* @param dropTarget The target that the drop occurs near/on
* @param dropLocation The location of the drop relative to the target
* @return parent
*/
private TocTopic determineParent(TocObject dropTarget, int dropLocation) {
//We must determine what object will be the parent of the
//dropped objects. This is done by looking at the drop location
//and drop target type
if (dropTarget == null || dropTarget.getType() == ITocConstants.TYPE_TOC) { //Since the TOC root has no parent, it must be the target parent
return fModel.getToc();
} else if (!dropTarget.canBeParent()) { //If the object is a leaf, it cannot be the parent
//of the new objects,
//so the target parent must be its parent
return (TocTopic) dropTarget.getParent();
} else { //In all other cases, it depends on the location of the drop
//relative to the drop target
switch (dropLocation) {
case TocDropAdapter.LOCATION_JUST_AFTER : { //if the drop occurred after an expanded node
//and all of its children,
//make the drop target's parent the target parent object
if (!fTocTree.getExpandedState(dropTarget)) {
return (TocTopic) dropTarget.getParent();
}
//otherwise, the target parent is the drop target,
//since the drop occurred between it and its first child
}
case ViewerDropAdapter.LOCATION_ON : { //the drop location is directly on the drop target
//set the parent object to be the drop target
return (TocTopic) dropTarget;
}
case ViewerDropAdapter.LOCATION_BEFORE :
case ViewerDropAdapter.LOCATION_AFTER : { //if the drop is before or after the drop target,
//make the drop target's parent the target parent object
return (TocTopic) dropTarget.getParent();
}
}
}
return null;
}
/**
* Get the TocObject representations of a group of dropped objects.
*
* @param droppings The objects that are dropped; can be file path Strings or
* deserialized TocObjects
*
* @param targetParent The designated parent of the dropped objects
*
* @return a list of the (reconnected) TocObject representations of the dropped objects
*/
private ArrayList getObjectsToAdd(Object[] droppings, TocTopic targetParent) {
ArrayList tocObjects = new ArrayList(droppings.length);
if (fDragAdapter.getDraggedElements() != null) { // If there are items in the drag adapter, then the current drag must be from
// this section
fDragFromHere = fDragAdapter.getDraggedElements().size() == droppings.length;
}
for (int i = 0; i < droppings.length; ++i) {
if (droppings[i] instanceof String) {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
// If the array contains Strings, we treat them as file paths
Path path = new Path((String) droppings[i]);
IFile file = root.getFileForLocation(path);
if (file == null) {
continue;
}
// If the path is to a valid TOC file
// and it isn't the file in this model
// then make a link
if (HelpEditorUtil.isTOCFile(path) && !HelpEditorUtil.isCurrentResource(path, fModel)) {
tocObjects.add(makeNewTocLink(targetParent, file));
}
// If the path is to a file with an HTML page extension, make a topic
else if (HelpEditorUtil.hasValidPageExtension(path)) {
TocTopic topic = makeNewTocTopic(targetParent, file);
String title = generateTitle(targetParent, path);
topic.setFieldLabel(title);
tocObjects.add(topic);
}
} else if (droppings[i] instanceof TocObject) {
ArrayList dragged = fDragAdapter.getDraggedElements();
if (fDragFromHere) {
TocObject draggedObj = (TocObject) dragged.get(i);
//Nesting an object inside itself or its children
//is so stupid and ridiculous that I get a headache
//just thinking about it. Thus, this drag is not going to complete.
if (targetParent.descendsFrom(draggedObj)) {
return null;
}
}
//Reconnect this TocObject, since it was deserialized
((TocObject) droppings[i]).reconnect(targetParent, fModel);
tocObjects.add(droppings[i]);
}
}
return tocObjects;
}
/**
* Generate the title of a Topic created via dragging in an HTML page.
* Use the title of the HTML page, or generate a name based on the target
* parent if no title exists.
*
* @param targetParent The designated parent of this topic
* @param path The path to the HTML file
*
* @return The generated name of the Topic.
*/
private String generateTitle(TocTopic targetParent, Path path) {
String title = TocHTMLTitleUtil.findTitle(path.toFile());
if (title == null) {
int numChildren = targetParent.getChildren().size();
TocObject[] children = (TocObject[]) targetParent.getChildren().toArray(new TocObject[numChildren]);
String[] tocObjectNames = new String[children.length];
for (int j = 0; j < numChildren; ++j) {
tocObjectNames[j] = children[j].getName();
}
title = PDELabelUtility.generateName(tocObjectNames, TocMessages.TocTreeSection_topic);
}
return title;
}
/**
* Create a new Topic node using the model's factory.
*
* @param parent The designated parent for the new topic
* @param path The file that this Topic will link to
*
* @return the newly created topic
*/
private TocTopic makeNewTocTopic(TocObject parent, IFile file) {
return fModel.getFactory().createTocTopic(file);
}
/**
* Create a new Link node using the model's factory.
*
* @param parent The designated parent for the new link
* @param path The file that this link will be associated with
*
* @return the newly created link
*/
private TocLink makeNewTocLink(TocObject parent, IFile file) {
return fModel.getFactory().createTocLink(file);
}
/* (non-Javadoc)
* @see org.eclipse.pde.internal.ui.editor.StructuredViewerSection#buttonSelected(int)
*/
protected void buttonSelected(int index) {
switch (index) {
case F_BUTTON_ADD_TOPIC :
handleAddAction(fAddTopicAction);
break;
case F_BUTTON_ADD_LINK :
handleAddAction(fAddLinkAction);
break;
case F_BUTTON_ADD_ANCHOR :
handleAddAction(fAddAnchorAction);
break;
case F_BUTTON_REMOVE :
handleDeleteAction();
break;
case F_BUTTON_UP :
handleMoveAction(F_UP_FLAG);
break;
case F_BUTTON_DOWN :
handleMoveAction(F_DOWN_FLAG);
break;
}
}
/**
* Handle the addition of an object by preparing and running the
* specified action.
*
* @param action The action to run for the addition
*/
private void handleAddAction(TocAddObjectAction action) {
//Currently, all additions in the TOC editor are semantically similar
//Thus, all addition operations can follow the same procedure
ISelection sel = fTocTree.getSelection();
Object object = ((IStructuredSelection) sel).getFirstElement();
if (object == null) {
return;
}
TocObject tocObject = (TocObject) object;
if (tocObject.canBeParent()) {
// If the selected object can be a parent, then add
// the new object as a child of this object
action.setParentObject(tocObject);
action.run();
} else { // If the selected object cannot be a parent, then add
// the new object as a direct sibling of this object
action.setParentObject(tocObject.getParent());
action.setTargetObject(tocObject);
action.run();
}
}
/**
* Handle the addition of multiple initialized objects to the TOC.
*
* @param objectsToAdd The objects to be added
* @param tocTarget The target to add these objects relative to
* @param insertBefore Whether or not the insertion occurs before the target
* @param targetParent The parent object of the newly added objects
*/
private void handleMultiAddAction(List objectsToAdd, TocObject tocTarget, boolean insertBefore, TocObject targetParent) {
TocObject[] tocObjects = (TocObject[]) objectsToAdd.toArray(new TocObject[objectsToAdd.size()]);
if (tocObjects == null)
return;
for (int i = 0; i < tocObjects.length; ++i) {
if (tocObjects[i] != null) {
if (targetParent != null && targetParent.canBeParent()) {
if (tocTarget != null && tocTarget != targetParent) { // Add the object as a direct sibling of the target
((TocTopic) targetParent).addChild(tocObjects[i], tocTarget, insertBefore);
} else { // Add the object as the last child of the target parent
((TocTopic) targetParent).addChild(tocObjects[i]);
}
}
}
}
}
/**
* Remove the selected objects from the TOC tree
*/
private void handleDeleteAction() {
ArrayList objects = new ArrayList(((IStructuredSelection) fTocTree.getSelection()).toList());
boolean beep = false;
// Iterate through the list of selected objects, removing ones
// that cannot be removed
for (Iterator i = objects.iterator(); i.hasNext();) {
Object object = i.next();
if (object instanceof TocObject) {
TocObject tocObject = (TocObject) object;
if (!tocObject.canBeRemoved()) {
i.remove();
beep = true;
}
}
}
if (beep) { // If any object cannot be removed, beep to notify the user
Display.getCurrent().beep();
}
// Remove the remaining objects
handleRemove(objects);
}
/**
* Remove the items listed from the TOC.
*
* @param itemsToRemove The list of items to remove from the TOC
*/
public void handleRemove(List itemsToRemove) {
if (!itemsToRemove.isEmpty()) { // Target the objects for removal
fRemoveObjectAction.setToRemove((TocObject[]) itemsToRemove.toArray(new TocObject[itemsToRemove.size()]));
// Run the removal action
fRemoveObjectAction.run();
}
}
/**
* Handle the dragging of objects out of this TOC.
*
* @param itemsDragged The items dragged out of the TOC
*/
public void handleDrag(List itemsDragged) {
handleRemove(itemsDragged);
// The drag is finished, so there is no intra-editor DND operation occuring now
fDragFromHere = false;
}
/**
* Move an object within the TOC.
*
* @param positionFlag The direction that the object will move
*/
private void handleMoveAction(int positionFlag) {
IStructuredSelection sel = (IStructuredSelection) fTocTree.getSelection();
for (Iterator iter = sel.iterator(); iter.hasNext();) {
Object object = iter.next();
if (object == null) {
return;
} else if (object instanceof TocObject) {
TocObject tocObject = (TocObject) object;
TocTopic parent = (TocTopic) tocObject.getParent();
// Determine the parent type
if (parent != null) { // Move the object up or down one position
parent.moveChild(tocObject, positionFlag);
}
}
}
}
/* (non-Javadoc)
* @see org.eclipse.pde.internal.ui.editor.PDESection#modelChanged(org.eclipse.pde.core.IModelChangedEvent)
*/
public void modelChanged(IModelChangedEvent event) {
// No need to call super, world changed event handled here
if (event.getChangeType() == IModelChangedEvent.WORLD_CHANGED) {
handleModelEventWorldChanged(event);
} else if (event.getChangeType() == IModelChangedEvent.INSERT) {
handleModelInsertType(event);
} else if (event.getChangeType() == IModelChangedEvent.REMOVE) {
handleModelRemoveType(event);
} else if ((event.getChangeType() == IModelChangedEvent.CHANGE) && (event.getChangedProperty().equals(IDocumentElementNode.F_PROPERTY_CHANGE_TYPE_SWAP))) {
handleModelChangeTypeSwap(event);
} else if (event.getChangeType() == IModelChangedEvent.CHANGE) {
handleModelChangeType(event);
}
}
/**
* @param event
*/
private void handleModelChangeTypeSwap(IModelChangedEvent event) {
// Swap event
// Get the changed object
Object[] objects = event.getChangedObjects();
TocObject object = (TocObject) objects[0];
if (object != null) { // Update the element in the tree viewer
fTocTree.refresh(object);
}
}
/**
* The model is stale, refresh the UI
*
* @param event The world-change event
*/
private void handleModelEventWorldChanged(IModelChangedEvent event) {
markStale();
}
/**
* Handle insertions in the model
* @param event the insertion event
*/
private void handleModelInsertType(IModelChangedEvent event) {
// Insert event
Object[] objects = event.getChangedObjects();
TocObject object = (TocObject) objects[0];
if (object != null) {
if (object.getType() != ITocConstants.TYPE_TOC) {
// Refresh the parent element in the tree viewer
// TODO: Can we get away with an update instead of a refresh here?
fTocTree.refresh(object.getParent());
// Select the new object in the tree
fTocTree.setSelection(new StructuredSelection(object), true);
}
}
}
/**
* Handle removals in the model
*
* @param event the removal event
*/
private void handleModelRemoveType(IModelChangedEvent event) {
// Remove event
Object[] objects = event.getChangedObjects();
TocObject object = (TocObject) objects[0];
if (object != null) {
if (object.getType() != ITocConstants.TYPE_TOC) {
handleTaskObjectRemove(object);
}
}
}
/**
* An object was removed, update the UI to respond to the removal
*
* @param object The object that was removed
*/
private void handleTaskObjectRemove(TocObject object) {
// Remove the item
fTocTree.remove(object);
// Select the appropriate object
TocObject tocObject = fRemoveObjectAction.getNextSelection();
if (tocObject == null) {
tocObject = object.getParent();
}
if (tocObject.equals(object.getParent())) {
fTocTree.refresh(object.getParent());
}
if (!fDragFromHere) {
fTocTree.setSelection(new StructuredSelection(tocObject), true);
}
}
/**
* Handle an update to a TocObject's properties
* @param event the update event
*/
private void handleModelChangeType(IModelChangedEvent event) {
// Get the changed object
Object[] objects = event.getChangedObjects();
TocObject object = (TocObject) objects[0];
if (object != null) { // Update the element in the tree viewer
fTocTree.update(object, null);
}
}
public void refresh() {
TocModel model = (TocModel) getPage().getModel();
fTocTree.setInput(model);
fTocTree.expandToLevel(2);
fTocTree.setSelection(new StructuredSelection(model.getToc()), true);
getManagedForm().fireSelectionChanged(this, fTocTree.getSelection());
super.refresh();
}
/* (non-Javadoc)
* @see org.eclipse.pde.internal.ui.editor.TreeSection#createTreeViewer(org.eclipse.swt.widgets.Composite, int)
*/
protected TreeViewer createTreeViewer(Composite parent, int style) {
fFilteredTree = new FormFilteredTree(parent, style, new PatternFilter());
parent.setData("filtered", Boolean.TRUE); //$NON-NLS-1$
return fFilteredTree.getViewer();
}
/* (non-Javadoc)
* @see org.eclipse.ui.forms.AbstractFormPart#dispose()
*/
public void dispose() {
PDEUserAssistanceUIPlugin.getDefault().getLabelProvider().disconnect(this);
super.dispose();
}
}