package org.xmind.ui.internal.comments;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
import org.eclipse.e4.ui.workbench.modeling.EPartService;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.bindings.Trigger;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.bindings.keys.KeyStroke;
import org.eclipse.jface.bindings.keys.SWTKeySupport;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.PopupDialog;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Color;
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.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchCommandConstants;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.contexts.IContextActivation;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.keys.IBindingService;
import org.xmind.core.Core;
import org.xmind.core.IComment;
import org.xmind.core.ITopic;
import org.xmind.core.event.CoreEvent;
import org.xmind.core.event.CoreEventRegister;
import org.xmind.core.event.ICoreEventListener;
import org.xmind.core.event.ICoreEventRegister;
import org.xmind.core.event.ICoreEventSupport;
import org.xmind.gef.IGraphicalViewer;
import org.xmind.gef.IViewer;
import org.xmind.gef.ZoomManager;
import org.xmind.gef.ui.editor.IGraphicalEditor;
import org.xmind.ui.internal.MindMapMessages;
import org.xmind.ui.internal.MindMapUIPlugin;
import org.xmind.ui.internal.e4models.CommentsPart;
import org.xmind.ui.internal.e4models.IModelConstants;
import org.xmind.ui.internal.utils.E4Utils;
import org.xmind.ui.mindmap.ITopicPart;
import org.xmind.ui.mindmap.MindMapUI;
import org.xmind.ui.resources.ColorUtils;
import org.xmind.ui.resources.FontUtils;
public class CommentsPopup extends PopupDialog
implements ICoreEventListener, ICommentTextViewerContainer {
public static final Color BG_COLOR = ColorUtils.getColor("#f5f5f5"); //$NON-NLS-1$
private static final String CONTEXT_ID = "org.xmind.ui.context.commentsPopup"; //$NON-NLS-1$
private static final String CMD_GOTO_COMMENTS_VIEW = "org.xmind.ui.command.gotoCommentsView"; //$NON-NLS-1$
private static final String CMD_COMMIT_COMMENTS = "org.xmind.ui.command.commitComments"; //$NON-NLS-1$
private class PopupKeyboardListener implements Listener {
private List<TriggerSequence> currentSequences = null;
private DisposeListener disposeListener = new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
if (!e.display.isDisposed()) {
e.display.removeFilter(SWT.KeyDown,
PopupKeyboardListener.this);
}
}
};
private int nextKeyIndex = -1;
public void hook(Control control) {
control.getDisplay().addFilter(SWT.KeyDown, this);
control.getShell().addDisposeListener(disposeListener);
}
public void handleEvent(Event event) {
if (event.type == SWT.KeyDown) {
handleKeyDown(event);
}
}
private void handleKeyDown(Event event) {
if (triggerableCommands.isEmpty())
return;
List<KeyStroke> keys = generateKeyStrokes(event);
if (currentSequences == null) {
nextKeyIndex = -1;
for (TriggerSequence ts : triggerableCommands.keySet()) {
if (matches(keys, ts.getTriggers()[0])) {
if (currentSequences == null)
currentSequences = new ArrayList<TriggerSequence>(
triggerableCommands.size());
currentSequences.add(ts);
}
}
if (currentSequences == null)
return;
}
if (nextKeyIndex < 0)
nextKeyIndex = 0;
Iterator<TriggerSequence> it = currentSequences.iterator();
while (it.hasNext()) {
TriggerSequence ts = it.next();
Trigger[] triggers = ts.getTriggers();
if (nextKeyIndex >= triggers.length) {
it.remove();
} else {
if (matches(keys, triggers[nextKeyIndex])) {
if (nextKeyIndex == triggers.length - 1) {
if (triggerFound(ts)) {
event.doit = false;
}
return;
}
} else {
it.remove();
}
}
}
if (currentSequences != null && currentSequences.isEmpty()) {
nextKeyIndex++;
} else {
currentSequences = null;
nextKeyIndex = -1;
}
}
private boolean triggerFound(TriggerSequence triggerSequence) {
currentSequences = null;
nextKeyIndex = -1;
String commandId = triggerableCommands.get(triggerSequence);
if (commandId != null) {
return handleCommand(commandId);
}
return false;
}
private boolean matches(List<KeyStroke> keys, Trigger expected) {
for (KeyStroke key : keys) {
if (key.equals(expected))
return true;
}
return false;
}
private List<KeyStroke> generateKeyStrokes(Event event) {
final List<KeyStroke> keyStrokes = new ArrayList<KeyStroke>(3);
/*
* If this is not a keyboard event, then there are no key strokes.
* This can happen if we are listening to focus traversal events.
*/
if ((event.stateMask == 0) && (event.keyCode == 0)
&& (event.character == 0)) {
return keyStrokes;
}
// Add each unique key stroke to the list for consideration.
final int firstAccelerator = SWTKeySupport
.convertEventToUnmodifiedAccelerator(event);
keyStrokes.add(SWTKeySupport
.convertAcceleratorToKeyStroke(firstAccelerator));
// We shouldn't allow delete to undergo shift resolution.
if (event.character == SWT.DEL) {
return keyStrokes;
}
final int secondAccelerator = SWTKeySupport
.convertEventToUnshiftedModifiedAccelerator(event);
if (secondAccelerator != firstAccelerator) {
keyStrokes.add(SWTKeySupport
.convertAcceleratorToKeyStroke(secondAccelerator));
}
final int thirdAccelerator = SWTKeySupport
.convertEventToModifiedAccelerator(event);
if ((thirdAccelerator != secondAccelerator)
&& (thirdAccelerator != firstAccelerator)) {
keyStrokes.add(SWTKeySupport
.convertAcceleratorToKeyStroke(thirdAccelerator));
}
return keyStrokes;
}
}
@Inject
private EPartService partService;
private IWorkbenchWindow window;
private ITopicPart topicPart;
private ITopic topic;
private boolean showExtraActions;
private Control control;
private CommentsPopupActionBarContributor contributor;
private ISelectionProvider selectionProvider = new CommentsSelectionProvider();
private PopupKeyboardListener popupKeyBoardListener;
private IBindingService bindingService;
private IContextService contextService;
private IContextActivation contextActivation;
private Map<TriggerSequence, String> triggerableCommands = new HashMap<TriggerSequence, String>(
3);
private ScrolledComposite sc;
private Composite contentComposite;
private TopicCommentsViewer contentViewer;
private ToolBarManager toolBarManager;
private ICoreEventRegister eventRegister;
private ICoreEventRegister globalEventRegister;
private ControlListener controlListener;
private IComment latestCreatedComment;
private IComment selectedComment;
private IComment editingComment;
private boolean modified;
public CommentsPopup(IWorkbenchWindow window, ITopicPart topicPart,
boolean showExtraActions) {
super(window.getShell(), SWT.RESIZE, true, true, true, false, false,
null, null);
this.window = window;
this.topicPart = topicPart;
this.showExtraActions = showExtraActions;
this.topic = topicPart.getTopic();
}
@Override
protected Point getDefaultSize() {
return new Point(350, 250);
}
@Override
protected Color getBackground() {
return BG_COLOR;
}
@Override
protected Point getInitialLocation(Point initialSize) {
IViewer viewer = topicPart.getSite().getViewer();
Rectangle bounds = topicPart.getFigure().getBounds().getCopy();
return calcInitialLocation((IGraphicalViewer) viewer, bounds);
}
private Point calcInitialLocation(IGraphicalViewer viewer,
Rectangle bounds) {
ZoomManager zoom = viewer.getZoomManager();
bounds = bounds.scale(zoom.getScale()).expand(1, 1)
.translate(viewer.getScrollPosition().getNegated());
return viewer.getControl().toDisplay(bounds.x,
bounds.y + bounds.height);
}
@SuppressWarnings("unchecked")
@Override
protected List getForegroundColorExclusions() {
List list = super.getForegroundColorExclusions();
collectColorExclusions(control, list);
return list;
}
@SuppressWarnings("unchecked")
@Override
protected List getBackgroundColorExclusions() {
List list = super.getBackgroundColorExclusions();
collectColorExclusions(control, list);
return list;
}
@SuppressWarnings("unchecked")
private void collectColorExclusions(Control control, List list) {
list.add(control);
if (control instanceof Composite) {
for (Control child : ((Composite) control).getChildren()) {
collectColorExclusions(child, list);
}
}
}
@Override
protected IDialogSettings getDialogSettings() {
return MindMapUIPlugin.getDefault()
.getDialogSettings(MindMapUI.POPUP_DIALOG_SETTINGS_ID);
}
protected Control createDialogArea(Composite parent) {
Composite composite = (Composite) super.createDialogArea(parent);
this.control = composite;
composite.setBackground(getBackground());
composite.setForeground(getForeground());
contributor = new CommentsPopupActionBarContributor(this,
getTargetEditor());
Control control = createControl(composite);
update();
popupKeyBoardListener = new PopupKeyboardListener();
popupKeyBoardListener.hook(control);
setInfoText(null);
hookTopic();
registerGlobalEvent();
initActions();
return composite;
}
private IGraphicalEditor getTargetEditor() {
if (window != null) {
IEditorPart editorPart = window.getActivePage().getActiveEditor();
if (editorPart instanceof IGraphicalEditor) {
return (IGraphicalEditor) editorPart;
}
}
return null;
}
private void registerGlobalEvent() {
globalEventRegister = new CoreEventRegister(
topic.getOwnedWorkbook().getAdapter(ICoreEventSupport.class),
this);
globalEventRegister.register(Core.CommentContent);
}
private void unRegisterGlobalEvent() {
if (globalEventRegister != null) {
globalEventRegister.unregisterAll();
globalEventRegister = null;
}
}
private void hookTopic() {
if (eventRegister == null) {
eventRegister = new CoreEventRegister(topic, this);
}
eventRegister.register(Core.CommentAdd);
eventRegister.register(Core.CommentRemove);
}
private void unhookTopic() {
if (eventRegister != null) {
eventRegister.unregisterAll();
eventRegister = null;
}
}
public void handleCoreEvent(final CoreEvent event) {
final String type = event.getType();
PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
public void run() {
if (!contentComposite.isDisposed()) {
if (Core.CommentAdd.equals(type)
|| Core.CommentRemove.equals(type)) {
update();
} else if (Core.CommentContent.equals(type)) {
IComment comment = (IComment) event.getSource();
if (comment.isOrphan()) {
return;
}
if (comment.getOwnedWorkbook().getElementById(
comment.getObjectId()) == topic) {
update();
}
}
}
}
});
}
private void initActions() {
contributor.selectionChanged(topic);
}
@Override
public boolean close() {
unhookTopic();
unRegisterGlobalEvent();
if (contextActivation != null && contextService != null) {
contextService.deactivateContext(contextActivation);
contextActivation = null;
}
if (contributor != null) {
contributor.dispose();
}
if (sc != null && !sc.isDisposed()) {
sc.removeControlListener(getControlListener());
}
if (getReturnCode() == OK) {
saveComment();
}
//mark with CommentsView
if (partService != null) {
MPart part = partService.findPart(CommentsPart.PART_ID);
if (part.isVisible()) {
Object object = part.getObject();
if (object instanceof CommentsPart) {
Control control = ((CommentsPart) object).getControl();
if (control != null && !control.isDisposed()) {
control.setData(CommentsConstants.COMMENTS_POPUP_SHOWN,
false);
}
}
}
}
return super.close();
}
private Control createControl(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
composite.setBackground(parent.getBackground());
GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
composite.setLayoutData(data);
GridLayout gridLayout = new GridLayout(1, false);
gridLayout.marginWidth = 0;
gridLayout.marginHeight = 0;
gridLayout.verticalSpacing = 0;
gridLayout.horizontalSpacing = 0;
composite.setLayout(gridLayout);
createToolbar(composite);
contentComposite = createContentComposite(composite);
return composite;
}
private void createToolbar(Composite parent) {
if (contributor == null) {
return;
}
Composite composite = new Composite(parent, SWT.NONE);
GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
gridData.heightHint = 30;
composite.setLayoutData(gridData);
composite.setBackground(ColorUtils.getColor("#e0e0e0")); //$NON-NLS-1$
GridLayout layout = new GridLayout(2, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
composite.setLayout(layout);
Label label = new Label(composite, SWT.NONE);
label.setBackground(label.getParent().getBackground());
GridData layoutData = new GridData(SWT.LEFT, SWT.CENTER, false, true);
layoutData.horizontalIndent = 10;
label.setLayoutData(layoutData);
label.setText(MindMapMessages.Comments_lable);
label.setFont(FontUtils.getBold(
FontUtils.getRelativeHeight(JFaceResources.DEFAULT_FONT, 1)));
toolBarManager = new ToolBarManager(SWT.FLAT);
contributor.fillToolBar(toolBarManager);
composite.addListener(SWT.Resize, new Listener() {
public void handleEvent(Event event) {
toolBarManager.update(true);
}
});
ToolBar toolBar = toolBarManager.createControl(composite);
toolBar.setBackground(toolBar.getParent().getBackground());
toolBar.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, true, true));
}
private Composite createContentComposite(Composite parent) {
Composite composite = new Composite(parent, SWT.NONE);
composite.setBackground(parent.getBackground());
composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
composite.setLayout(layout);
sc = new ScrolledComposite(composite, SWT.V_SCROLL);
sc.setBackground(parent.getBackground());
sc.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
sc.setExpandHorizontal(true);
final Composite contentComposite = new Composite(sc, SWT.NONE);
contentComposite.setBackground(parent.getBackground());
GridLayout gridLayout = new GridLayout(1, false);
gridLayout.marginWidth = 0;
gridLayout.marginHeight = 0;
gridLayout.verticalSpacing = 0;
gridLayout.horizontalSpacing = 0;
gridLayout.marginBottom = 29;
contentComposite.setLayout(gridLayout);
sc.setContent(contentComposite);
sc.getVerticalBar().setIncrement(17);
sc.addControlListener(getControlListener());
return contentComposite;
}
private ControlListener getControlListener() {
if (controlListener == null) {
controlListener = new ControlListener() {
public void controlMoved(ControlEvent e) {
}
public void controlResized(ControlEvent e) {
e.widget.getDisplay().asyncExec(new Runnable() {
public void run() {
if (contentComposite != null
&& !contentComposite.isDisposed()) {
contentComposite.pack();
}
}
});
}
};
}
return controlListener;
}
private void update() {
resetSelectedComment();
updateComments();
setModified(false);
setEditingComment(null);
}
private void resetSelectedComment() {
contributor.selectedCommentChanged(null);
}
private void updateComments() {
selectionProvider.setSelection(null);
contentComposite.setRedraw(false);
resetContent();
contentViewer = new TopicCommentsViewer(topic, contributor,
selectionProvider, this, false, getTargetEditor());
contentViewer.create(contentComposite);
contentComposite.pack();
contentComposite.setRedraw(true);
}
private void resetContent() {
Control[] controls = contentComposite.getChildren();
if (controls != null) {
for (Control control : controls) {
if (control != null && !control.isDisposed()) {
control.dispose();
control = null;
}
}
}
}
public int open() {
IWorkbench workbench = window.getWorkbench();
bindingService = (IBindingService) workbench
.getAdapter(IBindingService.class);
contextService = (IContextService) workbench
.getAdapter(IContextService.class);
if (bindingService != null) {
registerWorkbenchCommands();
}
int ret = super.open();
if (ret == OK) {
if (contextService != null) {
contextActivation = contextService.activateContext(CONTEXT_ID);
}
if (bindingService != null) {
registerDialogCommands();
}
}
//mark with CommentsView
if (partService != null) {
MPart part = partService.findPart(CommentsPart.PART_ID);
if (part.isVisible()) {
Object object = part.getObject();
if (object instanceof CommentsPart) {
Control control = ((CommentsPart) object).getControl();
if (control != null && !control.isDisposed()) {
control.setData(CommentsConstants.COMMENTS_POPUP_SHOWN,
true);
}
}
}
}
return ret;
}
private void registerWorkbenchCommands() {
registerCommand(IWorkbenchCommandConstants.FILE_SAVE);
registerCommand(IWorkbenchCommandConstants.EDIT_UNDO);
registerCommand(IWorkbenchCommandConstants.EDIT_REDO);
registerCommand(IWorkbenchCommandConstants.EDIT_CUT);
registerCommand(IWorkbenchCommandConstants.EDIT_COPY);
registerCommand(IWorkbenchCommandConstants.EDIT_PASTE);
registerCommand(IWorkbenchCommandConstants.EDIT_SELECT_ALL);
}
private TriggerSequence registerCommand(String commandId) {
if (bindingService == null)
return null;
TriggerSequence key = bindingService.getBestActiveBindingFor(commandId);
if (key != null) {
triggerableCommands.put(key, commandId);
}
return key;
}
@Override
protected Control getFocusControl() {
return contentComposite;
}
private void registerDialogCommands() {
if (showExtraActions) {
registerCommand(CMD_GOTO_COMMENTS_VIEW);
}
registerCommand(CMD_COMMIT_COMMENTS);
for (String commandId : contributor.getTextCommandIds()) {
registerCommand(commandId);
}
}
private void saveComment() {
if (contentViewer != null) {
MindMapUIPlugin.getDefault().getUsageDataCollector()
.increase("AddCommentCount"); //$NON-NLS-1$
contentViewer.save();
}
}
private boolean handleCommand(String commandId) {
if (CMD_GOTO_COMMENTS_VIEW.equals(commandId)) {
if (showExtraActions) {
gotoCommentsView();
}
return true;
} else if (CMD_COMMIT_COMMENTS.equals(commandId)) {
saveComment();
return true;
} else if (IWorkbenchCommandConstants.FILE_SAVE.equals(commandId)) {
saveComment();
return true;
}
IAction action = contributor.getActionHandler(commandId);
if (action != null && action.isEnabled()) {
if (action.getStyle() == IAction.AS_CHECK_BOX) {
action.setChecked(!action.isChecked());
}
action.run();
return true;
}
return false;
}
public void gotoCommentsView() {
Display.getCurrent().asyncExec(new Runnable() {
public void run() {
if (window == null) {
return;
}
close();
E4Utils.showPart(IModelConstants.COMMAND_SHOW_MODEL_PART,
window, IModelConstants.PART_ID_COMMENTS, null,
IModelConstants.PART_STACK_ID_RIGHT);
}
});
}
public IWorkbenchWindow getWorkbenchWindow() {
return window;
}
public ITopic getTopic() {
return topic;
}
public boolean isShowExtraActions() {
return showExtraActions;
}
public void moveToPreviousTextViewer(CommentTextViewer implementation) {
List<CommentTextViewer> implementations = contentViewer
.getImplementations();
int index = implementations.indexOf(implementation);
if (index <= 0 || index > implementations.size() - 1) {
return;
}
setSelection(new StructuredSelection(implementations.get(index - 1)));
}
public void moveToNextTextViewer(CommentTextViewer implementation) {
List<CommentTextViewer> implementations = contentViewer
.getImplementations();
int index = implementations.indexOf(implementation);
if (index < 0 || index >= implementations.size() - 1) {
return;
}
setSelection(new StructuredSelection(implementations.get(index + 1)));
}
private void setSelection(ISelection selection) {
selectionProvider.setSelection(selection);
}
public Composite getContentComposite() {
return contentComposite;
}
public ScrolledComposite getScrolledComposite() {
return sc;
}
public void setLatestCreatedComment(IComment latestCreatedComment) {
this.latestCreatedComment = latestCreatedComment;
}
public IComment getLatestCreatedComment() {
return latestCreatedComment;
}
public void setSelectedComment(IComment selectedComment) {
this.selectedComment = selectedComment;
}
public IComment getSelectedComment() {
return selectedComment;
}
@Override
public void createComment(String objectId) {
contentViewer.createNewComment();
}
@Override
public void cancelCreateComment() {
contentViewer.cancelCreateNewComment();
}
public void setEditingComment(IComment editingComment) {
this.editingComment = editingComment;
}
public IComment getEditingComment() {
return editingComment;
}
public void setModified(boolean modified) {
this.modified = modified;
}
public boolean isModified() {
return modified;
}
}