/*******************************************************************************
* Copyright 2012 Geoscience Australia
*
* 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 au.gov.ga.earthsci.bookmark.ui;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.beans.IBeanListProperty;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.map.IMapChangeListener;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.map.MapChangeEvent;
import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.e4.ui.internal.workbench.swt.AbstractPartRenderer;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.model.application.commands.MCommand;
import org.eclipse.e4.ui.model.application.commands.MCommandsFactory;
import org.eclipse.e4.ui.model.application.commands.MParameter;
import org.eclipse.e4.ui.model.application.ui.MElementContainer;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.model.application.ui.menu.ItemType;
import org.eclipse.e4.ui.model.application.ui.menu.MHandledMenuItem;
import org.eclipse.e4.ui.model.application.ui.menu.MMenu;
import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement;
import org.eclipse.e4.ui.model.application.ui.menu.MMenuFactory;
import org.eclipse.e4.ui.model.application.ui.menu.MPopupMenu;
import org.eclipse.e4.ui.services.EMenuService;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
import org.eclipse.e4.ui.workbench.swt.factories.IRendererFactory;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.databinding.viewers.ObservableMapCellLabelProvider;
import org.eclipse.jface.layout.TableColumnLayout;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.ColumnLayoutData;
import org.eclipse.jface.viewers.ColumnViewerEditor;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.jface.viewers.TableViewerEditor;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.jface.viewers.ViewerCell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.Clipboard;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import au.gov.ga.earthsci.application.ImageRegistry;
import au.gov.ga.earthsci.bookmark.model.Bookmarks;
import au.gov.ga.earthsci.bookmark.model.IBookmark;
import au.gov.ga.earthsci.bookmark.model.IBookmarkList;
import au.gov.ga.earthsci.bookmark.model.IBookmarks;
import au.gov.ga.earthsci.bookmark.ui.handlers.CopyToListHandler;
import au.gov.ga.earthsci.bookmark.ui.handlers.MoveToListHandler;
import au.gov.ga.earthsci.worldwind.common.util.Util;
/**
* A part used to display current bookmarks, and to allow the user to interact
* with them.
*
* @author James Navin (james.navin@ga.gov.au)
*/
public class BookmarksPart
{
private static final String POPUP_MENU_ID = "au.gov.ga.earthsci.application.bookmarks.popupmenu"; //$NON-NLS-1$
private static final String MOVE_TO_MENU_ID = "au.gov.ga.earthsci.application.bookmarks.movetomenu"; //$NON-NLS-1$
private static final String COPY_TO_MENU_ID = "au.gov.ga.earthsci.application.bookmarks.copytomenu"; //$NON-NLS-1$
@Inject
private Bookmarks bookmarks;
@Inject
private ESelectionService selectionService;
@Inject
private EModelService modelService;
@Inject
private EMenuService menuService;
@Inject
private MApplication application;
@Inject
private IEclipseContext context;
@Inject
private IBookmarksController controller;
private TableViewer bookmarkListTableViewer;
private ComboViewer bookmarkListsComboViewer;
private MPopupMenu popupMenu;
private MMenu copyToMenu;
private MMenu moveToMenu;
@PostConstruct
public void init(final Composite parent, final MPart part)
{
controller.setView(this);
parent.setLayout(new GridLayout(1, true));
initCombo(parent);
initList(parent);
context.set(TableViewer.class, bookmarkListTableViewer);
setupClipboardDnD();
setupBookmarkListInput();
setupPopupMenus(part);
}
/**
* Highlight the given bookmark in the part
*
* @param bookmark
* The bookmark to highlight. If <code>null</code>, clears any
* highlighting
*/
public void highlight(final IBookmark bookmark)
{
Display.getDefault().asyncExec(new Runnable()
{
@Override
public void run()
{
bookmarkListTableViewer.setSelection(bookmark == null ? null : new StructuredSelection(bookmark));
}
});
}
public void refreshDropdown()
{
Display.getDefault().asyncExec(new Runnable()
{
@Override
public void run()
{
if (controller.getCurrentList() == getSelectedBookmarkList())
{
return;
}
bookmarkListsComboViewer.setSelection(new StructuredSelection(controller.getCurrentList()), true);
}
});
}
private void setupClipboardDnD()
{
bookmarkListTableViewer.addDropSupport(DND.DROP_MOVE | DND.DROP_COPY, new Transfer[] { BookmarkTransfer
.getInstance() }, new BookmarksDropAdapter(bookmarkListTableViewer, controller));
bookmarkListTableViewer.addDragSupport(DND.DROP_MOVE | DND.DROP_COPY, new Transfer[] { BookmarkTransfer
.getInstance() }, new BookmarksDragSourceListener(bookmarkListTableViewer));
Clipboard clipboard = new Clipboard(Display.getCurrent());
context.set(Clipboard.class, clipboard);
}
private void setupBookmarkListInput()
{
IBeanListProperty<IBookmarkList, IBookmark> bookmarksProperty =
BeanProperties.list(IBookmarkList.class, "bookmarks", IBookmark.class); //$NON-NLS-1$
IObservableList<IBookmark> observableList = bookmarksProperty.observe(getSelectedBookmarkList());
bookmarkListTableViewer.setInput(observableList);
bookmarkListsComboViewer.addSelectionChangedListener(new ISelectionChangedListener()
{
@Override
public void selectionChanged(SelectionChangedEvent event)
{
bookmarkListTableViewer.setInput(BeanProperties
.list(IBookmarkList.class, "bookmarks").observe(getSelectedBookmarkList())); //$NON-NLS-1$
}
});
}
private void initCombo(Composite parent)
{
bookmarkListsComboViewer = new ComboViewer(parent, SWT.READ_ONLY | SWT.DROP_DOWN);
GridData gd = new GridData(GridData.FILL_HORIZONTAL);
gd.grabExcessHorizontalSpace = true;
gd.grabExcessVerticalSpace = false;
bookmarkListsComboViewer.getCombo().setLayoutData(gd);
bookmarkListsComboViewer.setLabelProvider(new LabelProvider()
{
@Override
public String getText(Object element)
{
if (!(element instanceof IBookmarkList))
{
return super.getText(element);
}
return ((IBookmarkList) element).getName();
}
});
// Trigger a label refresh if a list name changes etc.
ObservableListContentProvider<IBookmarkList> contentProvider =
new ObservableListContentProvider<IBookmarkList>(IBookmarkList.class);
IObservableSet<IBookmarkList> knownElements = contentProvider.getKnownElements();
IObservableMap<IBookmarkList, String> nameMap =
BeanProperties.value(IBookmarkList.class, "name", String.class).observeDetail(knownElements); //$NON-NLS-1$
nameMap.addMapChangeListener(new IMapChangeListener<IBookmarkList, String>()
{
@Override
public void handleMapChange(MapChangeEvent<IBookmarkList, String> event)
{
for (IBookmarkList key : event.diff.getChangedKeys())
{
bookmarkListsComboViewer.refresh(key, true);
}
}
});
IBeanListProperty<IBookmarks, IBookmarkList> listsProperty =
BeanProperties.list(IBookmarks.class, "lists", IBookmarkList.class); //$NON-NLS-1$
IObservableList<IBookmarkList> observableList = listsProperty.observe(bookmarks);
bookmarkListsComboViewer.setContentProvider(contentProvider);
bookmarkListsComboViewer.setInput(observableList);
bookmarkListsComboViewer.setSelection(new StructuredSelection(bookmarks.getDefaultList()));
bookmarkListsComboViewer.addSelectionChangedListener(new ISelectionChangedListener()
{
@Override
public void selectionChanged(SelectionChangedEvent event)
{
controller.setCurrentList(getSelectedBookmarkList());
}
});
}
private void initList(Composite parent)
{
Composite tableHolder = new Composite(parent, SWT.NONE);
GridData gd = new GridData(GridData.FILL_BOTH);
gd.grabExcessHorizontalSpace = true;
gd.grabExcessVerticalSpace = true;
gd.horizontalAlignment = GridData.FILL;
tableHolder.setLayoutData(gd);
TableColumnLayout layout = new TableColumnLayout();
tableHolder.setLayout(layout);
bookmarkListTableViewer =
new TableViewer(tableHolder, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION);
bookmarkListTableViewer.getTable().setHeaderVisible(false);
bookmarkListTableViewer.getTable().setLinesVisible(false);
bookmarkListTableViewer.getTable().setLayoutData(gd);
ObservableListContentProvider<IBookmark> contentProvider =
new ObservableListContentProvider<IBookmark>(IBookmark.class);
bookmarkListTableViewer.setContentProvider(contentProvider);
IObservableSet<IBookmark> knownElements = contentProvider.getKnownElements();
IObservableMap<IBookmark, String> nameMap =
BeanProperties.value(IBookmark.class, "name", String.class).observeDetail(knownElements); //$NON-NLS-1$
TableViewerColumn column = new TableViewerColumn(bookmarkListTableViewer, SWT.LEFT);
column.setEditingSupport(new BookmarkNameEditingSupport(bookmarkListTableViewer));
column.setLabelProvider(new ObservableMapCellLabelProvider<IBookmark, String>(nameMap)
{
@Override
public void update(ViewerCell cell)
{
super.update(cell);
cell.setText(" " + ((IBookmark) cell.getElement()).getName()); //$NON-NLS-1$
cell.setImage(ImageRegistry.getInstance().get(ImageRegistry.ICON_BOOKMARKS));
}
});
ColumnLayoutData cld = new ColumnWeightData(12);
layout.setColumnData(column.getColumn(), cld);
// Allow edit (rename) only via programmatic access (rename command)
ColumnViewerEditorActivationStrategy activationStrategy =
new ColumnViewerEditorActivationStrategy(bookmarkListTableViewer)
{
@Override
protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event)
{
return event.eventType == ColumnViewerEditorActivationEvent.PROGRAMMATIC;
}
};
TableViewerEditor.create(bookmarkListTableViewer, activationStrategy, ColumnViewerEditor.KEYBOARD_ACTIVATION);
// Populate the current selection with the actual bookmark items
bookmarkListTableViewer.addSelectionChangedListener(new ISelectionChangedListener()
{
@Override
public void selectionChanged(SelectionChangedEvent event)
{
IStructuredSelection selection = (IStructuredSelection) bookmarkListTableViewer.getSelection();
List<?> list = selection.toList();
selectionService.setSelection(list.toArray(new IBookmark[list.size()]));
}
});
// Apply the bookmark on double click
bookmarkListTableViewer.addDoubleClickListener(new IDoubleClickListener()
{
@Override
public void doubleClick(DoubleClickEvent event)
{
controller.apply((IBookmark) ((IStructuredSelection) event.getSelection()).getFirstElement());
}
});
// Deselect when clicking outside a valid row
bookmarkListTableViewer.getTable().addMouseListener(new MouseAdapter()
{
@Override
public void mouseUp(MouseEvent e)
{
if (bookmarkListTableViewer.getTable().getItem(new Point(e.x, e.y)) == null)
{
bookmarkListTableViewer.setSelection(null);
}
}
});
}
private void setupPopupMenus(MPart part)
{
menuService.registerContextMenu(bookmarkListTableViewer.getTable(), POPUP_MENU_ID);
//XXX since Luna, the above no longer returns a MPopupMenu
//TODO find out a better way to get the MPopupMenu created above
//XXX this is a dirty hack:
Menu menu = bookmarkListTableViewer.getTable().getMenu();
popupMenu = (MPopupMenu) menu.getData(AbstractPartRenderer.OWNING_ME);
//XXX this doesn't work:
//popupMenu = (MPopupMenu) modelService.find(POPUP_MENU_ID, part);
copyToMenu = (MMenu) modelService.find(COPY_TO_MENU_ID, popupMenu);
moveToMenu = (MMenu) modelService.find(MOVE_TO_MENU_ID, popupMenu);
updateCopyToMenu();
updateMoveToMenu();
bookmarks.addPropertyChangeListener("lists", new PropertyChangeListener() //$NON-NLS-1$
{
@Override
public void propertyChange(PropertyChangeEvent evt)
{
updateCopyToMenu();
updateMoveToMenu();
}
});
}
private void updateCopyToMenu()
{
updateMenu(copyToMenu, CopyToListHandler.COMMAND_ID, CopyToListHandler.LIST_PARAMETER_ID);
}
private void updateMoveToMenu()
{
updateMenu(moveToMenu, MoveToListHandler.COMMAND_ID, MoveToListHandler.LIST_PARAMETER_ID);
}
@SuppressWarnings("unchecked")
private void updateMenu(MMenu menu, String commandId, String paramId)
{
// Remove existing menu items
for (MMenuElement child : menu.getChildren())
{
child.setToBeRendered(false);
child.setVisible(false);
}
menu.getChildren().clear();
// Find the appropriate command
MCommand command = null;
for (MCommand c : application.getCommands())
{
if (commandId.equals(c.getElementId()))
{
command = c;
break;
}
}
// Add a menu item for each available list
for (IBookmarkList b : bookmarks.getLists())
{
MHandledMenuItem menuItem = MMenuFactory.INSTANCE.createHandledMenuItem();
menuItem.setLabel(b.getName());
menuItem.setCommand(command);
menuItem.setElementId(menu.getElementId() + "." + b.getId()); //$NON-NLS-1$
menuItem.setType(ItemType.PUSH);
menuItem.setToBeRendered(true);
menuItem.setVisible(true);
MParameter parameter = MCommandsFactory.INSTANCE.createParameter();
parameter.setName(paramId);
parameter.setValue(b.getId());
menuItem.getParameters().add(parameter);
menu.getChildren().add(menuItem);
}
menu.setToBeRendered(true);
menu.setVisible(true);
// This is a workaround for Bug 391340 to force the UI to re-sync with the application model
IRendererFactory rendererFactory = context.get(IRendererFactory.class);
AbstractPartRenderer renderer = rendererFactory.getRenderer(popupMenu, bookmarkListTableViewer.getTable());
renderer.processContents((MElementContainer<MUIElement>) (Object) popupMenu);
}
/**
* @return The currently selected bookmark list from the combo box
*/
private IBookmarkList getSelectedBookmarkList()
{
return (IBookmarkList) ((StructuredSelection) bookmarkListsComboViewer.getSelection()).getFirstElement();
}
/**
* A simple {@link EditingSupport} implementation that provides in-place
* editing of bookmark names within the list
*/
private static class BookmarkNameEditingSupport extends EditingSupport
{
private TableViewer viewer;
public BookmarkNameEditingSupport(TableViewer viewer)
{
super(viewer);
this.viewer = viewer;
}
@Override
protected CellEditor getCellEditor(Object element)
{
return new TextCellEditor(viewer.getTable(), SWT.BORDER);
}
@Override
protected boolean canEdit(Object element)
{
return true;
}
@Override
protected Object getValue(Object element)
{
return ((IBookmark) element).getName();
}
@Override
protected void setValue(Object element, Object value)
{
String stringValue = (String) value;
if (!Util.isBlank(stringValue))
{
((IBookmark) element).setName(stringValue);
}
viewer.update(element, null);
}
}
}