I have an Eclipse RCP application with a sort of three column layout:
The editor area is at the extreme right. Now, when you get an IPageLayout
to work with, the editor area is already added in. That's fine: we add area B to the left of the editor, and area A to the left of B, and the layout is exactly what we need.
The issue is that when you move the sash between A and B, views A and B change without resizing the editor area (good;) but when you move the other sash between B and the editor area, all three views are resized; the layout manager acts to maintain the ratio of the widths of A and B, and that's not what we want. We want the user to be able to move each sash independently, and have it influence only the two views it touches.
It seems like the root cause of this is that the editor is in place when you get your IPageView
, and therefore you have to position the IFolderLayout
s relative to it. If you could position the editor relative to B, instead, then resize would do the right thing.
So my questions:
IPageView
to position the editor relative to a view, instead of the other way around? I don't think, it's possible to achieve exactly what you want (so the answers to your questions would be 1. no, 2. no). But there it a 3rd alternative, which IMO behaves quite nicely.
When trying in Eclipse: Start with viewA on left and Editor on right. Then when you drag viewB to the right side of viewA, you get the (wrong) setup you describe. But then you drag it to the left part of the Editor, then you get different configuration, where dragging right sash behaves as you want. Dragging of left sash resizes viewA and Editor and MOVES viewB.
I would say that the code to achieve this would be:
IFolderLayout areaA = layout.createFolder("A", IPageLayout.LEFT, 0.33f, editorArea);
IFolderLayout areaB = layout.createFolder("B", IPageLayout.LEFT, 0.5f, editorArea);
I know of no way to alter the layout tree of IPageLayout
in Eclipse 3.x. In Eclipse 4.2, however, the Application Model can be changed dynamically at runtime.
So, if you would consider migrating your application to Eclipse 4, this solution could be an option. To keep the original application and UI code as untouched as possible, this solution will
I started with the regular RCP Mail template of Eclipse 3 and altered the perspective to recreate the problem. This is the Perspective
class I used in my test application:
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IPerspectiveFactory;
public class Perspective implements IPerspectiveFactory {
public static final String ID = "wag.perspective";
public void createInitialLayout(IPageLayout layout) {
String editorArea = layout.getEditorArea();
layout.setEditorAreaVisible(true);
layout.addStandaloneView(AView.ID, false, IPageLayout.LEFT,
0.25f, editorArea);
layout.addStandaloneView(BView.ID, false, IPageLayout.LEFT,
0.25f, editorArea);
layout.getViewLayout(AView.ID).setCloseable(false);
layout.getViewLayout(BView.ID).setCloseable(false);
}
}
It basically creates the scenario you described: a three column layout where one sash effects all three parts and the other one only two.
I then proceeded to migrate the application and alter the Application Model.
Migrate the Eclipse 3 based RCP application to Eclipse 4
There are online tutorials available for this process. I found Eclipse 4.1: Run your 3.x RCP in 4.1 and Eclipse 4 and the Compatibility Layer - Tutorial to be very helpful.
I recommend including the org.eclipse.e4.tools.emf.liveeditor
and its required plug-ins in your product dependencies. With the live editor, you can take a look at the Application Model that is created by the compatibility layer.
Once the application starts, thet sashes will still behave the same way. Open the live editor on your application window and take a look at your model.
You can see that the PartSashContainer
including the placeholder for the AView
contains another PartSashContainer
. Moving the sash between AView
and that container will update the rest of the layout tree, while moving the sash between BView
and the editor does not effect other parts of the layout.
You could now drag the placeholder for the AView
to the container where the BView
and the editor are located. This would instantly create the effect you desire: The sashes will only affect their direct neighbours. But these changes will only be saved in one's own runtime workspace. Something else is needed to alter the layout structure automatically.
Altering the Application Model at runtime
Since I didn't want to touch the original code if possible, I created another plugin to make a contribution to the Application Model.
Create a Plug-In Project without an Activator
without using a template.
Add an Addon
class: select New->Other->Eclipse 4->Classes->New Addon Class
Add a Model Fragment
: select New->Other-Eclipse 4->Model->New Model Fragment. Open the created fragment.e4xmi
file and add a Model Fragment
. For the Element Id, put org.eclipse.e4.legacy.ide.application
(this is the standard id of legacy applications) and for the Featurename addons
. Add an Addon
to the Model Fragment
. Enter an ID and set the Class URI
to your addon class.
Now add your fragment.e4xmi
to your org.eclipse.e4.workbench.model
extension point:
<extension
id="id1"
point="org.eclipse.e4.workbench.model">
<fragment
uri="fragment.e4xmi">
</fragment>
</extension>
Add your contribution plugin to the dependencies of your application product. When you start your application and look at the model with the live editor, you should see your Addon
listed in the model.
Now we can implement the Addon
. This is the code of my Addon
class:
package wag.contribution.addons;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.e4.ui.model.application.MApplication;
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.advanced.MPlaceholder;
import org.eclipse.e4.ui.workbench.modeling.EModelService;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
public class LayoutSorter {
@Inject private IEventBroker broker;
private EventHandler handler;
// The part IDs we are interested in, sorted in the sequence they should be
// shown
private static List<String> PART_IDS = Arrays.asList(new String[] {
"wag.aView", "wag.bView", "org.eclipse.ui.editorss" });
// Listen to the e4 core service's event broker to find the magical time
// when the application is created and try to sort the layout.
@PostConstruct
void hookListeners(final MApplication application,
final EModelService service) {
if (handler == null) {
handler = new EventHandler() {
// Try to sort the layout. Unsubscribe from event broker if
// successful.
@Override
public void handleEvent(Event event) {
try {
sort(application, service);
// sort did finish: stop listening to the broker.
broker.unsubscribe(handler);
} catch (Exception e) {
// Something went wrong, the application model was not ready yet.
// Keep on listening.
}
}
};
// Subscribe "ServiceEvent.MODIFIED" to grab the application.STARTED
// event. Does anybody know how to do this in a better way?
broker.subscribe("org/osgi/framework/ServiceEvent/MODIFIED",
handler);
}
}
private void sort(MApplication application, EModelService service) {
// find all placeholders
List<MPlaceholder> placeholders = service.findElements(application,
null, MPlaceholder.class, null);
// only keep the ones we are interested in
for (int i = placeholders.size() - 1; i > -1; i--) {
if (!PART_IDS.contains(placeholders.get(i).getElementId())) {
placeholders.remove(i);
}
}
// find the parents of the placeholders
List<MElementContainer<MUIElement>> parents = new ArrayList<>(
placeholders.size());
for (MPlaceholder placeholder : placeholders) {
parents.add(placeholder.getParent());
}
// find the parent that is "deepest down" in the tree
MElementContainer<MUIElement> targetParent = null;
for (MElementContainer<MUIElement> parent : parents) {
for (MUIElement child : parent.getChildren()) {
if (parents.contains(child)) {
continue;
}
targetParent = parent;
}
}
// move all parts to the target parent
if (targetParent != null) {
for (int i = 0; i < placeholders.size(); i++) {
if (targetParent != placeholders.get(i).getParent()) {
service.move(placeholders.get(i), targetParent, i);
}
}
}
}
@PreDestroy
void unhookListeners() {
if (handler != null) {
// in case it wasn't unhooked earlier
broker.unsubscribe(handler);
}
}
}
(Please note that the code above is a bit of a hack because it is only really suited for this specific problem.)
After a restart, the application should now behave in the desired way. Take a look at the Application Model to see your changes.
One thing to be aware of is that local changes are saved in the runtime workspace in the file .metadata\.plugins\org.eclipse.e4.workbench\workbench.xmi
if saving is switched on, so for recreating the unaltered model for testing this file has to be deleted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With