/*******************************************************************************
* Copyright (c) 2006 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
* IBM Research
*******************************************************************************/
package com.ibm.research.tagging.core.ui.waypoints;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.JFaceColors;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.forms.widgets.FormText;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.ui.part.ViewPart;
import com.ibm.research.tagging.core.ITag;
import com.ibm.research.tagging.core.IWaypoint;
import com.ibm.research.tagging.core.IWaypointListener;
import com.ibm.research.tagging.core.IWaypointModelListener;
import com.ibm.research.tagging.core.TagCorePlugin;
import com.ibm.research.tagging.core.ui.ITagSelectionListener;
import com.ibm.research.tagging.core.ui.TagUIPlugin;
import com.ibm.research.tagging.core.ui.controls.ExpressionFilteredTable;
import com.ibm.research.tagging.core.ui.controls.ExpressionPatternFilter;
import com.ibm.research.tagging.core.ui.controls.FilteredTable;
import com.ibm.research.tagging.core.ui.dnd.WaypointTableDragListener;
import com.ibm.research.tagging.core.ui.dnd.WaypointTransfer;
import com.ibm.research.tagging.core.ui.fieldassist.ExpressionProposalProvider;
/**
*
* @author mdesmond
*
*/
public class WaypointView extends ViewPart
{
public static final String ID = "com.ibm.research.tagging.core.ui.waypoints.WaypointView";
private static final long REFRESH_DELAY = 500; //msecs
private class WaypointListener implements IWaypointListener
{
public void tagAdded(IWaypoint waypoint, ITag tag)
{
Display.getDefault().asyncExec(new Runnable() {
public void run()
{
fWaypointViewer.setSelection(fWaypointViewer.getSelection());
}
});
}
public void tagRemoved(IWaypoint waypoint, ITag tag)
{
Display.getDefault().asyncExec(new Runnable() {
public void run()
{
fWaypointViewer.setSelection(fWaypointViewer.getSelection());
}
});
}
public void waypointChanged(IWaypoint waypoint)
{
Display.getDefault().asyncExec(new Runnable() {
public void run()
{
fWaypointViewer.setSelection(fWaypointViewer.getSelection());
scheduleViewerRefresh();
}
});
}
}
private class WaypointCollectionListener implements IWaypointModelListener
{
/*
* (non-Javadoc)
* @see com.ibm.research.tagging.core.IWaypointCollectionListener#waypointAdded(com.ibm.research.tagging.core.IWaypoint)
*/
public void waypointAdded(IWaypoint waypoint)
{
waypoint.addWaypointListener(waypointListener);
scheduleViewerRefresh();
}
/*
* (non-Javadoc)
* @see com.ibm.research.tagging.core.IWaypointCollectionListener#waypointRemoved(com.ibm.research.tagging.core.IWaypoint)
*/
public void waypointRemoved(IWaypoint waypoint)
{
waypoint.removeWaypointListener(waypointListener);
scheduleViewerRefresh();
}
}
private class TagSelectionListener implements ITagSelectionListener
{
/*
* (non-Javadoc)
* @see com.ibm.research.tagging.core.ui.ITagSelectionListener#tagsSelected(com.ibm.research.tagging.core.ITag[])
*/
public void tagsSelected(ITag[] tags)
{
Display.getDefault().asyncExec(new Runnable() {
public void run()
{
fWaypointViewer.refresh();
}
});
}
}
private FilteredTable fFilteredTable;
private TableViewer fWaypointViewer;
private Composite fRoot;
private List<IWaypointViewListener> fListeners;
private FormToolkit fFormToolkit;
private TagHyperLinkListener fTagHyperLinkListener;
private Section fPropertiesSection;
private Section fTagsSection;
private Button fEditPropertiesButton;
private Composite fFormTextComposite;
private Composite fPropertiesComposite;
private Timer fRefreshTimer;
private boolean fScheduleRefresh;
private Composite fTagsSectionComposite;
private FormText fTagsFormText;
private Action deleteAction;
IWaypointListener waypointListener = new WaypointListener();
private TableColumn fNameColumn;
private TableColumn fDescriptionColumn;
@Override
public void createPartControl(Composite parent)
{
fFormToolkit = new FormToolkit(Display.getCurrent());
// create the root with a 1 col gridlayout
createRootComposite(parent);
//create the waypoint viewer
createWaypointTableViewer(fRoot);
//create the collapsable tags box
createWaypointTagsSection(fRoot);
//create the collapsable properties box
createWaypointPropertiesSection(fRoot);
makeActions();
hookContextMenu();
contributeToActionBars();
// refresh the waypoint table whenever a waypoint is added
TagCorePlugin.getDefault().getTagCore().getWaypointModel().addWaypointModelListener(new WaypointCollectionListener());
// refresh the waypoint table whenever a tag is selected in the tag view
TagUIPlugin.getDefault().getTagUI().addTagSelectionListener(new TagSelectionListener());
addWaypointViewListener(new WaypointViewListener());
fRefreshTimer = new Timer();
fRefreshTimer.schedule(new TimerTask()
{
@Override
public void run()
{
if(fScheduleRefresh)
{
fScheduleRefresh = false;
Display.getDefault().asyncExec(new Runnable()
{
public void run()
{
if(!fWaypointViewer.getTable().isDisposed())
fWaypointViewer.refresh();
}
});
}
}
},0, REFRESH_DELAY);
}
private void createWaypointPropertiesSection(Composite parent)
{
fPropertiesSection = fFormToolkit.createSection(parent,Section.SHORT_TITLE_BAR |Section.TWISTIE);
fPropertiesSection.setText("Properties");
fPropertiesSection.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
fPropertiesComposite = fFormToolkit.createComposite(fPropertiesSection);
GridLayout propertiesLayout = new GridLayout(2,false);
propertiesLayout.marginWidth = 0;
propertiesLayout.marginHeight = 0;
propertiesLayout.verticalSpacing = 0;
propertiesLayout.marginTop = 0;
propertiesLayout.marginBottom = 3;
propertiesLayout.marginLeft = 0;
propertiesLayout.marginRight = 0;
propertiesLayout.horizontalSpacing = 0;
fPropertiesComposite.setLayout(propertiesLayout);
fPropertiesComposite.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
fFormTextComposite = fFormToolkit.createComposite(fPropertiesComposite);
fFormTextComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
fFormTextComposite.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
GridLayout textCompositeLayout = new GridLayout();
textCompositeLayout.marginWidth = 0;
textCompositeLayout.marginTop = 0;
textCompositeLayout.marginBottom = 0;
fFormTextComposite.setLayout(textCompositeLayout);
fEditPropertiesButton = fFormToolkit.createButton(fPropertiesComposite, "Edit...", SWT.PUSH);
fEditPropertiesButton.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
fEditPropertiesButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
IStructuredSelection selection = (IStructuredSelection) fWaypointViewer.getSelection();
if ( selection!=null && selection.size()==1 )
{
final IWaypoint waypoint = (IWaypoint) selection.getFirstElement();
if ( TagUIPlugin.getDefault().getTagUI().editProperties(waypoint) )
{
setSelectedWaypoint(waypoint);
Display.getDefault().asyncExec(new Runnable() {
public void run()
{
fWaypointViewer.refresh();
}
});
}
}
}
});
fPropertiesSection.setClient(fPropertiesComposite);
fPropertiesSection.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
fPropertiesSection.setExpanded(true);
clearSelectedWaypoint();
}
private void createWaypointTagsSection(Composite parent)
{
fTagsSection = fFormToolkit.createSection(parent,Section.SHORT_TITLE_BAR |Section.TWISTIE);
fTagsSection.setText("Tags");
fTagsSection.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
fTagsSectionComposite = fFormToolkit.createComposite(fTagsSection,SWT.NONE);
fTagsSectionComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
fTagsSectionComposite.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
GridLayout tagsCompositeLayout = new GridLayout();
tagsCompositeLayout.marginWidth = 0;
tagsCompositeLayout.marginHeight = 0;
tagsCompositeLayout.verticalSpacing = 0;
tagsCompositeLayout.marginTop = 0;
tagsCompositeLayout.marginBottom = 3;
tagsCompositeLayout.marginLeft = 0;
tagsCompositeLayout.marginRight = 0;
tagsCompositeLayout.horizontalSpacing = 0;
fTagsSectionComposite.setLayout(tagsCompositeLayout);
fTagsFormText = fFormToolkit.createFormText(fTagsSectionComposite, true);
fTagsFormText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
fTagHyperLinkListener = new TagHyperLinkListener();
fTagsFormText.addHyperlinkListener(fTagHyperLinkListener);
fTagsFormText.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
fTagsSection.setClient(fTagsSectionComposite);
GridData tagSectionData = new GridData(GridData.FILL_HORIZONTAL);
fTagsSection.setLayoutData(tagSectionData);
fTagsSection.setExpanded(true);
clearTags();
}
private void createRootComposite(Composite parent)
{
fRoot = new Composite(parent,SWT.NONE);
GridLayout layout = new GridLayout();
layout.marginWidth = 0;
layout.marginHeight = 0;
layout.verticalSpacing = 0;
layout.marginTop = 0;
layout.marginBottom = 0;
layout.marginLeft = 0;
layout.marginRight = 0;
layout.horizontalSpacing = 0;
fRoot.setLayout(layout);
fRoot.setBackground(JFaceColors.getBannerBackground(Display.getDefault()));
}
private void createWaypointTableViewer(Composite parent)
{
Composite composite = new Composite(parent,SWT.NONE);
composite.setLayoutData(new GridData(GridData.FILL_BOTH));
composite.setBackground(JFaceColors.getBannerBackground(Display.getDefault()));
GridLayout layout = new GridLayout();
layout.marginWidth = 3;
layout.marginHeight = 3;
layout.verticalSpacing = 0;
layout.marginTop = 0;
layout.marginBottom = 0;
layout.marginLeft = 0;
layout.marginRight = 0;
layout.horizontalSpacing = 0;
composite.setLayout(layout);
fFilteredTable = new ExpressionFilteredTable(composite,SWT.MULTI | SWT.V_SCROLL, new ExpressionPatternFilter());
fFilteredTable.setBackground(JFaceColors.getBannerBackground(Display.getDefault()));
fWaypointViewer = fFilteredTable.getViewer();
fWaypointViewer.setContentProvider(new WaypointTableContentProvider());
fWaypointViewer.setLabelProvider(new WaypointTableLabelProvider());
fWaypointViewer.setSorter(new WaypointTableSorter());
fWaypointViewer.addDragSupport(DND.DROP_MOVE|DND.DROP_COPY, new Transfer[]{WaypointTransfer.getInstance()/*, PluginTransfer.getInstance()*/}, new WaypointTableDragListener(fWaypointViewer));
fWaypointViewer.addSelectionChangedListener(new WaypointTableSelectionChangedListener());
fWaypointViewer.addDoubleClickListener(new WaypointTableDoubleClickListener());
Table table = fWaypointViewer.getTable();
table.setHeaderVisible(false);
table.setLinesVisible(true);
fNameColumn = new TableColumn(table,SWT.LEFT,0);
fNameColumn.setMoveable(false);
fNameColumn.setWidth(240);
fDescriptionColumn = new TableColumn(table,SWT.LEFT,1);
fDescriptionColumn.setMoveable(false);
fDescriptionColumn.setWidth(640);
// auto-resize columns on any change to the table
table.addPaintListener(new PaintListener()
{
public void paintControl(PaintEvent e)
{
Table table = (Table) e.widget;
Rectangle clientSize = table.getClientArea();
int newWidth = clientSize.width - fNameColumn.getWidth();
// this check prevents recursion
if ( fDescriptionColumn.getWidth()!=newWidth )
fDescriptionColumn.setWidth(newWidth);
}
});
fWaypointViewer.setInput(new Object());
}
private void makeActions()
{
deleteAction = new Action()
{
@Override
public void run()
{
TagCorePlugin.getDefault().getEventDispatcher().dispatch(new Runnable()
{
public void run()
{
for(IWaypointViewListener listener : getViewListeners())
listener.deleteWaypoint(WaypointView.this);
}
});
}
};
deleteAction.setText("Delete");
deleteAction.setToolTipText("Delete selected waypoints(s)");
deleteAction.setImageDescriptor(TagUIPlugin.getDefault().getImageRegistry().getDescriptor(TagUIPlugin.IMG_DELETE));
}
private void hookContextMenu()
{
MenuManager menuMgr = new MenuManager("#PopupMenu");
menuMgr.setRemoveAllWhenShown(true);
menuMgr.addMenuListener(new IMenuListener()
{
public void menuAboutToShow(IMenuManager manager)
{
WaypointView.this.fillContextMenu(manager);
}
});
Menu menu = menuMgr.createContextMenu(fWaypointViewer.getControl());
fWaypointViewer.getControl().setMenu(menu);
getSite().registerContextMenu(menuMgr, fWaypointViewer);
}
private void contributeToActionBars()
{
IActionBars bars = getViewSite().getActionBars();
fillLocalPullDown(bars.getMenuManager());
fillLocalToolBar(bars.getToolBarManager());
}
private void fillLocalPullDown(IMenuManager manager)
{
manager.add(deleteAction);
manager.add(new Separator());
}
private void fillContextMenu(IMenuManager manager)
{
manager.add(deleteAction);
manager.add(new Separator());
// Other plug-ins can contribute there actions here
manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}
private void fillLocalToolBar(IToolBarManager manager)
{
manager.add(deleteAction);
manager.add(new Separator());
}
@Override
public void setFocus()
{
}
/**
* Get the tag table viewer
* @return the tag table viewer
*/
public TableViewer getWaypointTableViewer()
{
return fWaypointViewer;
}
/**
* @param waypoint
*/
public void setSelectedWaypoint(IWaypoint waypoint)
{
if(fPropertiesSection.isExpanded())
{
Control[] children = fFormTextComposite.getChildren();
for(Control child : children)
child.dispose();
FormText text = TagUIPlugin.getDefault().getTagUI().getProperties(fFormTextComposite, waypoint);
fFormToolkit.adapt(text);
text.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
GridData wrapAround = new GridData(GridData.FILL_HORIZONTAL);
wrapAround.widthHint = fFormTextComposite.getSize().x;
text.setLayoutData(wrapAround);
fEditPropertiesButton.setEnabled(true);
}
}
/**
*
*/
public void clearSelectedWaypoint()
{
if(fPropertiesSection.isExpanded())
{
Control[] children = fFormTextComposite.getChildren();
for(Control child : children)
child.dispose();
Label label = fFormToolkit.createLabel(fFormTextComposite, "Select a single waypoint to see its properties.");
label.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
fEditPropertiesButton.setEnabled(false);
}
}
protected void setTags(ITag[] tags)
{
if(fTagsSection.isExpanded())
{
StringBuffer buffer = new StringBuffer();
buffer.append("<form>");
buffer.append("<p>");
if(tags.length > 0)
{
boolean bold = true;
for(ITag tag : tags)
{
// @tag hack tagsea urlencoding : currently using URLEncoder as stopgap to deal with tags with illegal chars that would bomb HTML formatting - this will render ok, but the hyperlinking may not work
buffer.append("<a href=\"" + URLEncoder.encode(tag.getName()) + "\">" + tag.getName() + "</a> ");
bold = !bold;
}
}
else
buffer.append("This waypoint contains no tags. ");
buffer.append("</p>");
buffer.append("</form>");
GridData wrapAround = new GridData(GridData.FILL_HORIZONTAL);
wrapAround.widthHint = fTagsSectionComposite.getSize().x;
fTagsFormText.setLayoutData(wrapAround);
fTagsFormText.setText(buffer.toString(), true, true);
}
}
public void clearTags()
{
if(fTagsSection.isExpanded())
fTagsFormText.setText("Select a waypoint to view its tags.", false, false);
}
// protected void setTags(ITag[] tags)
// {
// if(fTagsSection.isExpanded())
// {
// Control[] children = fDisposabeComposite.getChildren();
// Boolean labelShowing = false;
//
// // remove the existing controls
// for(Control child : children)
// {
// if(child instanceof Hyperlink)
// {
// Hyperlink link = (Hyperlink)child;
// link.removeHyperlinkListener(fTagHyperLinkListener);
// link.dispose();
// }
// else if(child instanceof Label)
// child.dispose();
// }
//
// if(tags.length != 0)
// {
// // add the new hyperlinks
// for(ITag tag : tags)
// {
// Hyperlink link = fFormToolkit.createHyperlink(fDisposabeComposite, tag.getName(), SWT.NONE);
// link.addHyperlinkListener(fTagHyperLinkListener);
// link.setUnderlined(true);
// }
// }
// else
// {
// fFormToolkit.createLabel(fDisposabeComposite, "Select a waypoint to see its tags.");
// labelShowing = true;
// }
//
// int numberOfChildren = fDisposabeComposite.getChildren().length;
//
// if(numberOfChildren > 0 && !labelShowing)
// {
// fDisposabeComposite.layout(true, true);
// children = fDisposabeComposite.getChildren();
// Hyperlink firstlink = (Hyperlink)children[0];
// Hyperlink lastLink = (Hyperlink)children[numberOfChildren - 1];
//
// int top = firstlink.getBounds().y;
// int bottom = lastLink.getBounds().y + lastLink.getBounds().height;
// int height = bottom - top;
//
// GridData data = new GridData();
// data.heightHint = height;
// fExpandButton.setLayoutData(data);
// }
// else
// {
// GridData data = new GridData();
// data.heightHint = fExpandButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).y;
// fExpandButton.setLayoutData(data);
// }
//
// fRoot.layout(true, true);
// //fRoot.redraw();
// }
// }
/**
* Add a TagViewListener
* @param listener
*/
public synchronized void addWaypointViewListener(IWaypointViewListener listener)
{
if(!getViewListeners().contains(listener))
getViewListeners().add(listener);
}
/**
* Remove a TagViewListener
* @param listener
*/
public synchronized void removeWaypointViewListener(IWaypointViewListener listener)
{
getViewListeners().remove(listener);
}
/**
* Get the listeners
* @return
*/
private List<IWaypointViewListener> getViewListeners()
{
if(fListeners == null)
fListeners = new ArrayList<IWaypointViewListener>();
return fListeners;
}
public void scheduleViewerRefresh()
{
fScheduleRefresh = true;
}
protected void refreshSections()
{
fRoot.layout(true,true);
fRoot.redraw();
}
@Override
public void dispose()
{
fRefreshTimer.cancel();
super.dispose();
}
}