X

Geertjan's Blog

  • February 19, 2011

Enabling/Disabling Checkboxes in a NetBeans Platform Toolbar

Geertjan Wielenga
Product Manager
I'm working on a situation where, if no image panel is in the selection, i.e., because another window is selected, as below:

The "Zoom In", "Zoom Out", and "Pan" checkboxes in the toolbar should be disabled, which is the case above. On the other hand, when, as is the case below, the window that contains the image panel is selected, the "Zoom In", "Zoom Out", and "Pan" checkboxes in the toolbar should be enabled, as can be seen below:

The above, in itself, is a reasonably tricky thing to achieve. See this blog entry as a starting point.

I copied the "ButtonGroupHelper" from the above. Then I created a "ZoomInAction" that listens to the global selection for an "ImagePanel". That "ImagePanel" is added to the "TopComponent" that contains it, whenever that "TopComponent" is selected. At that point, the radio button is enabled. When there's no "ImagePanel" available (i.e., some other "TopComponent" is selected), the radio button is deselected and disabled, while the cursor is also returned to its default.

Here's the relevant code:

public class ZoomInAction extends AbstractAction implements Presenter.Toolbar, LookupListener {
private double zoomFactor = 1.1;
private final Lookup.Result result;
private ImagePanel imagePanel;
private JRadioButtonMenuItem zoomInRadioButton;
private final ZoomInMouseListener mouseListener;
public ZoomInAction() {
mouseListener = new ZoomInMouseListener(zoomFactor);
result = Utilities.actionsGlobalContext().lookupResult(ImagePanel.class);
result.addLookupListener(this);
}
public double getZoomFactor() {
return zoomFactor;
}
public void setZoomFactor(double factor) {
this.zoomFactor = factor;
}
@Override
public void actionPerformed(ActionEvent e) {
//not needed
}
@Override
public Component getToolbarPresenter() {
final ImageIcon ICON = ImageUtilities.loadImageIcon("toolbarButtonGraphics/general/ZoomIn16.gif", true);
zoomInRadioButton = new JRadioButtonMenuItem("Zoom In");
zoomInRadioButton.setIcon(ICON);
ButtonGroup local = ButtonGroupHelper.returnGroup();
local.add(zoomInRadioButton);
zoomInRadioButton.setEnabled(false);
zoomInRadioButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Cursor c = Toolkit.getDefaultToolkit().createCustomCursor(ICON.getImage(), new Point(0, 0), "ZOOMIN");
imagePanel.setCursor(c);
imagePanel.addMouseListener(mouseListener);
StatusDisplayer.getDefault().setStatusText("Zoom in enabled...");
}
});
return zoomInRadioButton;
}
@Override
public void resultChanged(LookupEvent ev) {
if (result.allInstances().iterator().hasNext()) {
imagePanel = (ImagePanel) result.allInstances().iterator().next();
zoomInRadioButton.setEnabled(true);
} else {
zoomInRadioButton.setSelected(false);
zoomInRadioButton.setEnabled(false);
zoomInRadioButton.removeMouseListener(mouseListener);
Cursor c = Cursor.getDefaultCursor();
imagePanel.setCursor(c);
}
}
public class ZoomInMouseListener implements MouseListener {
private final double zoomFactor;
public ZoomInMouseListener(double zoomFactor) {
this.zoomFactor = zoomFactor;
}
@Override
public void mousePressed(MouseEvent evt) {
if (SwingUtilities.isLeftMouseButton(evt)) {
ImagePanel imgpan = (ImagePanel) evt.getSource();
ZoomUtility.zoom(imgpan, evt.getPoint(), zoomFactor);
}
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
}
}

I have one thing left to do, which is, I need to share the "MouseListener" between multiple actions, so that selecting a different radio button will remove/add a "MouseListener".

Be ware of annotations in complex scenarios such as the above. I.e., do not use annotations. Instead, register the actions like this:

<folder name="Actions">
<folder name="NDVisActions">
<file name="com-visitrend-ndvis-actions-ZoomInAction.instance"/>
<file name="com-visitrend-ndvis-actions-ZoomOutAction.instance"/>
<file name="com-visitrend-ndvis-actions-PanAction.instance"/>
</folder>
</folder>
<folder name="Toolbars">
<folder name="Zoom">
<file name="com-visitrend-ndvis-actions-ZoomInAction.shadow">
<attr name="originalFile" stringvalue="Actions/NDVisActions/com-visitrend-ndvis-actions-ZoomInAction.instance"/>
<attr intvalue="60" name="position"/>
</file>
<file name="com-visitrend-ndvis-actions-ZoomOutAction.shadow">
<attr name="originalFile" stringvalue="Actions/NDVisActions/com-visitrend-ndvis-actions-ZoomOutAction.instance"/>
<attr intvalue="70" name="position"/>
</file>
<file name="com-visitrend-ndvis-actions-PanAction.shadow">
<attr name="originalFile" stringvalue="Actions/NDVisActions/com-visitrend-ndvis-actions-PanAction.instance"/>
<attr intvalue="80" name="position"/>
</file>
</folder>
</folder>

Simple and to the point.

Join the discussion

Comments ( 1 )
  • Jesse Glick Monday, February 21, 2011

    You should be able to use @ActionRegistration here. The fact that you are implementing Presenter.Toolbar means that the annotation processor will automatically create an eager registration like the one you did manually.

    result should be a Lookup.Result<ImagePanel>. Turn on the compiler warning about raw types!

    Better decoupling would probably be for the image panel to handle setting its own cursor and responding to clicks (perhaps with a glass pane). Since the radio buttons still need to know something about the global selection (to disable themselves when applicable), they cannot be completely passive. But instead of looking for an actual ImagePanel in the context, define an abstraction:

    public interface Zoomable {

    enum Mode {IN, OUT, PAN}

    void setMode(Mode);

    }

    The ImagePanel's lookup would contain an instance of Zoomable whose setMode impl would change its own cursor and prepare it for clicks. The radio button pseudo-actions would merely listen to a Lookup.Result<Zoomable>, and be enabled iff results.allItems().size() == 1.


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.