Thursday, November 1, 2007

AbstractRulerColumn text inset

Recently, I've been trying to learn about Eclipse's text processing and display APIs (org.eclipse.text and org.eclipse.jface.text). I wanted to determine how eclipse dynamically placed its line numbers beside its text editors. Then, I wanted to determine how I could customize the text in that area.

For non-graphical text processing, Eclipse provides the IDocument interface. I've yet to do much exploration into its capabilities, but for now it gives me the ability to determine line counts.

For text display, Eclipse provides the TextViewer and the SourceViewer. The TextViewer is to a StyledText what a TableViewer is to a Table. The SourceViewer extends the TextViewer and adds an IVerticalRuler and IOverviewRuler. The IVerticalRuler allows you to draw arbitrary content on the left of an ISourceViewer; the IOverviewRuler does the same on the right. In terms of the JDT, it looks like the line numbers are an IVerticalRuler and the source annotations (the red/yellow bars on the right for problems/warnings) are an IOverviewRuler.

Experienced JDT users will remember that the JDT has more than one column to the left of its source code editors. Eclipse provides reusable code for a multi-column IVerticalRuler: the CompositeRuler. It allows clients to compose a single IVerticalRuler from many IVerticalRulerColumns, and the JDT uses it to hold its line number column and its code folding column.

Eclipse provides the AbstractRulerColumn for simple columns like mine. I just want to replicate eclipse's LineNumberRulerColumn. (The LineNumberRulerColumn has fancy features like right alignment, double-buffering, and performance tweaks that I don't need; I'm just learning here.) All I need to do is extend AbstractRulerColumn#computeText(int) to create text for a given line of my line number column. Additionally, I must use AbstractRulerColumn#setWidth(int) to update the width requirements when my lines grow by a power of 10. Finally, I must ensure to set my AbstractRulerColumn's textInset to zero. It took me 2 hours to track down why my line numbers were getting chopped off.

Conveniently, the AbstractRulerColumn is a PaintListener to its underlying Canvas, and will query its subclass for text on every repaint. However, just because my line numbers changed doesn't mean my AbstractRulerColumn's Canvas will be asked to paint itself, so I need to listen for text changes. Because SourceViewer subclasses TextViewer, I can listen for its TextEvents with an ITextListener. Then, when the SourceViewer's text changes, I can ask my AbstractRulerColumn to update itself with a call to AbstractRulerColumn#redraw().

Monday, August 27, 2007

Bolding Eclipse RCP Tabs on Content Change

Let's say you have a view whose content can update when it is not in the foreground of its view stack. You need to indicate to the user that your view's content has changed. To do that, use IWorkbenchSiteProgressService#warnOfContentChange().

For instance, in the Eclipse Java Development Tools (JDT), the Problems view can change after each build of a project (the project might gain or add some errors/warnings). If the Problems view is in the foreground of its stack, the change is immediately obvious to the user. If not (let's say you have the Console in front of the Problems view), the view needs to indicate to the user that its content has changed, and it does this by changing its title to bold.

Thursday, March 1, 2007

Show View Shortcuts

Show View Shortcuts:
In Eclipse, you can click the 'Window->Show View' menu, and you see a bunch of views available for quick showing within the current perspective.

Where I started:
From my research in how to get the 'Window->Show View' menu, I knew that org.eclipse.ui.internal.ShowViewMenu was used by the IContributionItem created by the VIEWS_SHORTLIST ContributionFactory in the org.eclipse.ui.workbench plug-in. Thus, my search began there.

Inside the ShowViewMenu#fillMenu(IMenuManager) method, the Menu is populated with view ids retrieved from the active org.eclipse.ui.IWorkbenchPage. Tracing references to this method should aid us. Unfortunately, ShowViewMenu#fillMenu(IMenuManager) is the only reference. Thus, we must dig into the implementation class, org.eclipse.ui.internal.WorkbenchPage.

org.eclipse.ui.internal.WorkbenchPage#getShowViewShortcuts() retrieves its data from the active org.eclipse.ui.internal.Perspective with the org.eclipse.ui.internal.Perspective#getShowViewShortcuts() method. Inside this method, we see a java.util.ArrayList of view ids stored internally. Searching for references within the class turns up the org.eclipse.ui.internal.Perspective#loadPredefinedPesp(PerspectiveDescriptor) method. It sets the view ids based on the value given by the org.eclipse.ui.internal.PageLayout#getShowViewShortcuts() method. This method maintains its own java.util.ArrayList of view ids. Searching for its references, we find we can add view ids with the org.eclipse.ui.internal.PageLayout#addShowViewShortcut(String) method. Encouragingly, this method is also part of the org.eclipse.ui.IPageLayout public API.

Now, we have the method to call to add a view to the top-level "Show View" menu. We just need to find where it is used. Searching for references to org.eclipse.ui.IPageLayout#addShowViewShortcut(String) yields the org.eclipse.ui.internal.ide.ResourcePerspective#defineActions(IPageLayout) method in the org.eclipse.ui.ide plug-in. Again, this method is not part of the public API, but it is only used inside the org.eclipse.ui.internal.ide.ResourcePerspective#createInitialLayout(IPageLayout) method. This method implements the org.eclipse.ui.IPerspectiveFactory#createInitialLayout(IPageLayout) method, and provides a good example for how to exercise the public API.

Most Likely Public API:
To define which views should be available as shortcuts under the 'Show View' Menu, add the view ids with the org.eclipse.ui.IPageLayout#addShowViewShortcut(String) method on the org.eclipse.ui.IPageLayout that is passed to the org.eclipse.ui.IPerspectiveFactory#createInitialLayout(IPageLayout) method. The IPerspectiveFactory is responsible for defining the layout of your perspective.

Show View

Show View:
In the IDE, you can use the 'Window->Show View' menu to open any view available. Additionally, there are some views that have shortcuts, and they appear to change with the selected perspective.

Where I Started:
I guessed that putting a breakpoint in a view that shows up in the default perspective would lead me to the IAction that represented the 'Window->Show View' menu.

My target platform is Eclipse 3.2.1, and I debugged the org.eclipse.sdk.ide product with the following required plug-ins (select these plug-ins and then click the 'Add Required Plug-ins' button):
org.eclipse.sdk
org.eclipse.ui.ide

This starts up a minimal Eclipse IDE with only the Resource perspective. I placed a breakpoint in the org.eclipse.ui.views.markers.internal.BookmarkView#createPartControl(Composite) method
and clicked 'Window->Show View->Bookmarks' menu item.

This lead to the following call stack:
BookmarkView.createPartControl(Composite) line: 96
ViewReference.createPartHelper() line: 332
ViewReference.createPart() line: 197
ViewReference(WorkbenchPartReference).getPart(boolean) line: 566
Perspective.showView(String, String) line: 1675
WorkbenchPage.busyShowView(String, String, int) line: 987
WorkbenchPage.access$13(WorkbenchPage, String, String, int) line: 968
WorkbenchPage$13.run() line: 3514
BusyIndicator.showWhile(Display, Runnable) line: 67
WorkbenchPage.showView(String, String, int) line: 3511
WorkbenchPage.showView(String) line: 3487
ShowViewAction.run() line: 76
ShowViewAction(Action).runWithEvent(Event) line: 499
...

org.eclipse.ui.internal.ShowViewAction was a likely candidate, so I opened that it and searched for references, which returned only org.eclipse.ui.internal.ShowViewMenu#getAction(String). Since I'm looking for a public API, and any package with 'internal' in the name is not for public use, I kept searching, this time for references to ShowViewMenu. This seach returned org.eclipse.ui.actions.ContributionItemFactory#VIEWS_SHORTLIST. This is a public API, and is probably the API I want to use to get an IContributionItem for my 'Show View' menu item.

As an aside, you may have noticed that the ContributionItemFactory isn't dealing with IActions. IMenuManagers really need IContributionItems to be added to them, not IActions. The IMenuManager is in fact an IContributionManager, which is an interface for anything that manages IContributionItems, including the ICoolBarManager, IStatusLineManager, and IToolBarManager. The IContributionManager#add(IAction) method is really just a convenience method that wraps the IAction in an IContributionItem.

So, now I had a public API for my 'Show View' menu item, but I didn't know the best way to use it. So, I did a search for references to ContributionItemFactory#VIEWS_SHORTLIST, and found org.eclipse.internal.ide.WorkbenchActionBuilder. On line 670 in the addPerspectiveActions(MenuManager) method, the 'Show View' IContributionItem, IContributionItem returned from ContributionItemFactory#VIEWS_SHORTLIST, is added to a new MenuManager, which is added to the IDE's 'Window' MenuManager.

I didn't understand why the IContributionItem needed to be in its own IMenuManager, since it seemed like the best option is just to add it to the 'Window' MenuManager directly. To experiment, I did just that. In my ActionBarAdvisor (the class responsible for adding IActions to the IWorkbenchWindow), I added the
'Show View' IContributionItem to my 'Window' MenuManager, and I only saw a separator and 'Other...' menu items. So, the 'Show View' IContributionItem is really only responsible for populating the sub-menu beneath an already existing 'Show View' menu.

Another result of my experiment was that the 'Show View' submenu only had the separator and 'Other...' menu items. It didn't have any of my application's views. If I clicked the 'Other...' menu item, I received the same dialog I'm used to in the IDE that showed all views broken into categories. All my views were there, so how'd Eclipse do that? We'll save that for the next (hopefully shorter) post.

Most Likely Public API:
Googling "Eclipse 'show view'" turned up the following nothing of interest other than an eclipse news post asking the same question I did. Hopefully, that person finds this blog.

Thus, my guess is that if you want the 'Show View' menu inside your RCP application, create a MenuManager for it and add to that MenuManager the IContributionItem returned by ContributionItemFactory#VIEWS_SHORTLIST#create(IWorkbenchWindow),
which exists in the org.eclipse.ui.workbench plug-in. Then, add your MenuManager to any existing MenuManager, usually one you created in ActionBarAdvisor#fillMenuBar(IMenuManager).

Intro

I'm an Eclipse RCP developer, and am constantly searching the eclipse source code for the best way to implement featues on an Eclipse RCP application. Usually, I find a similar feature in the Eclipse IDE; set a few breakpoints in the Eclipse source; and follow the code until a decipher the public API.

This blog will be a record of my findings. I'm going to try the following format:

Feature:
The behavior in the Eclipse IDE I want to emulate.

Where I started:
The Eclipse IDE source files and line numbers I started with. At the end of this section, I'll detail where Eclipse does the real work.

Most Likely Public API:
The API available to implement the feature, and the plug-ins in which it is located. Additionally, I'll post some links to real documentation if I can find it.