Thursday, July 25, 2013

Editing TreeViewer's cell with two clicks

With introduction of ColumnViewerEditorActivationStrategy in Eclipse 3.3 there is an ability to control when cell of a viewer gets activated for editing. Unfortunately the events for cell selection is limited to ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION and ColumnViewerEditorActivationEvent.MOUSE_DOUBLE_CLICK_SELECTION as far mouse events go. MOUSE_CLICK_SELECTION was not something I wanted to do because I didn't want cell editing to be activated each time selection changes in TreeViewer. I did not want to resort to MOUSE_DOUBLE_CLICK_SELECTION either because something else was suppose to happen on double-click in my case. I needed a way to enable cell editing when user click on a cell once and then again to edit. The solution I found was suggested by David Green (http://greensopinion.blogspot.com/2009/11/treeviewer-two-clicks-to-edit.html). However it did not work in my case. I am not sure exactly why, may be because I was using Nebula's Grid Widget (although it uses the same interfaces as TreeViewer) or custom EditingSupport. I've spend some time figuring out what was the problem, but then decided to take a different approach. Below is the code for my ColumnViewerEditorActivationStrategy implementation.
import org.eclipse.jface.viewers.ColumnViewer;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent;
import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.events.MouseEvent;

class SecondClickColumnViewerEditorActivationStrategy extends ColumnViewerEditorActivationStrategy implements ISelectionChangedListener {

 private int counter = 0;

 public SecondClickColumnViewerEditorActivationStrategy(ColumnViewer viewer) {
  super(viewer);
  viewer.addSelectionChangedListener(this);
 }

 @Override
 protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) {
  counter++;
  
  if(counter == 1){
   return false;
  }
  // this is in case when user clicked on tree node once, but second time activated context menu by clicking on right button,
  // we don't want to activate cell editing in this case
  else if(counter > 1 && isRightMouseClick(event)){
   return false;
  }
  else{
   return true;
  }
 }

 public void selectionChanged(SelectionChangedEvent event) {
  counter = 0;
 }
 
 /**
  * Determine if right mouse button was clicked
  * @param event
  * @return true if right mouse button was clicked, false otherwise.
  */
 private boolean isRightMouseClick(ColumnViewerEditorActivationEvent event){
  return event.eventType == ColumnViewerEditorActivationEvent.MOUSE_CLICK_SELECTION && ((MouseEvent)event.sourceEvent).button == 3;
 }
}
As you can see the editing enablement is controlled by counters. The important thing here is chain of calls between selectionChanged() and isEditorActivationEvent() methods. When user clicks on cell selectionChanged() gets called, and all subsequential clicks are handled by isEditorActivationEvent() method where counter is being incremented and appropriate return value is set. Note use of isRightMouseClick() call in isEditorActivationEvent(), I did not want to activate both context menu on the cell and editing when user clicked on a tree node and then activated context menu by clicking on right mouse button.

Friday, July 12, 2013

Scrolling draw2d ScrollPane using mouse wheel

Today I was working on a project where there was a need to scroll draw2d ScrollPane using only mouse wheel. Why? Because in this particular project there is a tree drawn using draw2d Figures and to the left of it a Nebula Grid. I won't go in details of why that is, but both trees needed to be scrolled using Grid's scroll bar. However, since draw2d tree had no scroll bar visible (by design), I still wanted to give user ability to scroll the tree using mouse wheel since the wheel is pretty much standard on all modern mice. There is no mouse wheel listener for ScrollPane or Viewport, but I found a nice little helper interface MouseWheelHelper. This interface has just one method:
void handleMouseWheelScrolled(Event event);
From there it was just a matter of finding the right variable that stores scroll value:
public class MyEditPart extends TreeEditPart implements MouseWheelHelper{
 private static final int SCROLL_OFFSET = 10;
 ...
        ...
 @Override
 // subtracting or adding values below controls direction of the scrolling
 public void handleMouseWheelScrolled(Event event) {
  pane.scrollVerticalTo(pane.getViewport().getVerticalRangeModel().getValue() - (event.count * SCROLL_OFFSET));
 }
}
That's it. Now when user clicks into the tree and uses scroll wheel the tree with scroll. What's neat is that by changing value of SCROLL_OFFSET you can speed up or slow down the scrolling. Of course this would not work for draw2d only case, because my ScrollPane was inside an EditPart that was already part of gef stack and that's why I could take advantage of MouseWheelHelper interface.

Wednesday, April 10, 2013

Adding additional composites to Eclipse editor

I've been working on DSL editor that we have in our product. One of the features we wanted to add is a way to examine AST generated based on expression. This AST viewer would allow user to examine parts of language statement and see what's going in the tree so he can of sort of debug each step of the execution.

 On the UI side we needed a way to present the AST tree and synchronize it with the statement that is being entered. One way to do it is add a view that would synchronized with editor, however drawback in this approach is not seeing the view when editor is maximized. What I wanted to achieve is something that Eclipse Compare editor offers - and editor which is split into two parts. I started looking at
org.eclipse.compare code. It's pretty involved and had a lot things that I really didn't need. I took a closer look at our DSL editor that extends TextEditor and decided to play with createPartControl(Composite parent) method since this is where UI elements were build. The result was just what I needed.

What I ended up doing is shown here:

public void createPartControl(Composite parent) {
   SashForm sashForm = new SashForm(parent, SWT.VERTICAL);
   sashForm.setLayout(new FillLayout());

   super.createPartControl(sashForm);
 
   new ASTComposite(sashForm, SWT.BORDER);
}
As you can see SashForm is used to achieve split pane. Top pane is where editor is added, and bottom is where any Composite goes, in my case it's a specialized Composite that has TreeViewer holding AST of the expression. Pretty simple and effective.

Monday, April 8, 2013

Making location in target definition version agnostic

When adding custom target definition location make version of the software site "0.0.0" in order to pick up latest version. This works just like when defining feature that your feature depends on to pick up latest version.

Thursday, August 30, 2012

Performing compare in Eclipse with resources that are not under version control

I had an interesting problem at hand. For a particular plugin that has GEF editor every time model of the editor would get build a special '.compiledFlow' file would get generated. This file should not be managed by svn and should not be committed with the rest of the project. This was double by using Team.setAllIgnores(). However, as a by-product of this whenever I tried to compare a file that was under version control, .compiledFlow would also be considered for comparison, this is because model file and .compiledFlow file were bulked in this special virtual folder. Whenever I tried to compare my model file I would get the following error
org.tigris.subversion.javahl.ClientException: svn: '/home/akravets/dev/workspaces/runtime-trunk/test1/Flows/flow1.iwp/.compiledFlow' is not under version control at org.tigris.subversion.svnclientadapter.javahl.AbstractJhlClientAdapter.diff(AbstractJhlClientAdapter.java:2462) at org.tigris.subversion.subclipse.ui.operations.ShowDifferencesAsUnifiedDiffOperationWC.execute(ShowDifferencesAsUnifiedDiffOperationWC.java:50) at org.tigris.subversion.subclipse.ui.operations.SVNOperation.run(SVNOperation.java:90) at org.eclipse.team.internal.ui.actions.ProgressDialogRunnableContext$3.run(ProgressDialogRunnableContext.java:100) at org.eclipse.core.internal.resources.Workspace.run(Workspace.java:1800) at org.eclipse.team.internal.ui.actions.ProgressDialogRunnableContext$2.run(ProgressDialogRunnableContext.java:97) at org.eclipse.jface.operation.ModalContext$ModalContextThread.run(ModalContext.java:121) Caused by: org.tigris.subversion.javahl.ClientException: svn: '/home/akravets/dev/workspaces/runtime-trunk/test1/Flows/flow1.iwp/.compiledFlow' is not under version control at org.tigris.subversion.javahl.JavaHLObjectFactory.throwException(JavaHLObjectFactory.java:778) at org.tmatesoft.svn.core.javahl.SVNClientImpl.throwException(SVNClientImpl.java:1850) at org.tmatesoft.svn.core.javahl.SVNClientImpl.diff(SVNClientImpl.java:2035) at org.tmatesoft.svn.core.javahl.SVNClientImpl.diff(SVNClientImpl.java:1990) at org.tmatesoft.svn.core.javahl.SVNClientImpl.diff(SVNClientImpl.java:1985) at org.tigris.subversion.svnclientadapter.javahl.AbstractJhlClientAdapter.diff(AbstractJhlClientAdapter.java:2459) ... 6 more Caused by: org.tmatesoft.svn.core.SVNException: svn: '/home/akravets/dev/workspaces/runtime-trunk/test1/Flows/flow1.iwp/.compiledFlow' is not under version control at org.tmatesoft.svn.core.internal.wc.SVNErrorManager.error(SVNErrorManager.java:64) at org.tmatesoft.svn.core.internal.wc.SVNErrorManager.error(SVNErrorManager.java:51) at org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess.getVersionedEntry(SVNWCAccess.java:621) at org.tmatesoft.svn.core.wc.SVNBasicClient.getRevisionNumber(SVNBasicClient.java:499) at org.tmatesoft.svn.core.wc.SVNBasicClient.getRevisionNumber(SVNBasicClient.java:465) at org.tmatesoft.svn.core.wc.SVNDiffClient.doDiffURLWC(SVNDiffClient.java:2725) at org.tmatesoft.svn.core.wc.SVNDiffClient.doDiff(SVNDiffClient.java:685) at org.tmatesoft.svn.core.javahl.SVNClientImpl.diff(SVNClientImpl.java:2024) ... 9 more
Obviously the problem here is that .compiledFlow was not under version control. However, I don't want it to be there, and I also want to compare this file's siblings. The solution is set unversioned diff flag:
SVNDiffClient diffClient = SVNClientManager.newInstance().getDiffClient(); 
diffClient.getDiffGenerator().setDiffUnversioned(true);

Monday, August 27, 2012

Meld wrapper script

A script to call meld with comparison of local and remote file:
#!/bin/sh

# if you simply want to see what command arguments are passed by subversion,
# simply uncomment following line, and comment rest of the script:
# echo "$@"

# http://pida.co.uk/wiki/UsingExternalDiffTools

# Configure your favorite diff program here.
DIFF="/usr/bin/meld"

# Subversion provides the paths we need as the sixth and seventh
# parameters.
LEFT=${3} # 'MINE' - was 6
RIGHT=${2} # 'THEIRS' (online) - was 7

# Call the diff command (change the following line to make sense for
# your merge program).
$DIFF $LEFT $RIGHT

# Return an errorcode of 0 if no differences were detected, 1 if some were.
# Any other errorcode will be treated as fatal.

Blogger Syntax Highliter