Recipe 10: How to remove a filter that returned no rows from an OutlineView?

March 2, 2014 Leave a comment

Problem

By right-clicking inside an outline view, a popup menu is displayed where you can select the action Show only rows where you can define your criteria depending under which column you clicked. However, if the filter resulted in no rows, how can you display all rows again since the action is displayed only when there are visible rows in the outline view?

Solution

An easy solution is to add a toolbar next to your outline view inside the top component and add the following button:

public class RemoveFilterAction extends AbstractAction {
  /** The outline view to sort. */
  private final OutlineView outlineView;
  /** PropertyChangeListener to set the enabled state. */
  private final PropertyChangeListener pcl = new PropertyChangeListener() {
     @Override
     public void propertyChange(PropertyChangeEvent evt) {
       setEnabled(RemoveFilterAction.this.outlineView.getOutline().getQuickFilterColumn() != -1);
     }
  };

  /**
    *
    * @param view the outline view to remove filter from.
    */
  public RemoveFilterAction(OutlineView view) {
     super(NbBundle.getMessage(RemoveFilterAction.class, "HINT_RemoveFilter"),
           ImageUtilities.loadImageIcon("deleteFilter.png", false));
     this.outlineView = view;
     view.getOutline().addPropertyChangeListener(pcl);
     pcl.propertyChange(null);
     putValue(SHORT_DESCRIPTION, org.openide.util.NbBundle.getMessage(RemoveFilterAction.class, "HINT_RemoveFilter"));
  }

  @Override
  public void actionPerformed(ActionEvent e) {
     outlineView.getOutline().unsetQuickFilter();
     pcl.propertyChange(null);
  }
}

where

HINT_RemoveFilter=Remove the applied filter from view

Whenever a filter is applied to the outline view, the button becomes enabled, and by clicking on it the filter is removed.

Categories: Filter, OutlineView

Recipe 9: How to filter an OutlineView?

February 26, 2014 1 comment

Problem

How can I filter an OutlineView programmatically?

Solution

By right-clicking inside an outline view, a popup menu is displayed where you can select the action Show only rows where you can define your criteria depending under which column you clicked.

To do filtering programmatically, ETable defines two methods:

outlineView.getOutline().setQuickFilter(column, filter);
outlineView.getOutline().unsetQuickFilter();

where column is 1 for first column, 2 for second etc. and filter is of type Object and can be one of two types:

  • a value that is matched directly to the values of the column, e.g.
outlineView.getOutline().setQuickFilter(3, Boolean.TRUE);
  • a QuickFilter e.g.
private final QuickFilter filter = new QuickFilter() {
   @Override
   public boolean accept(Object aValue) {
      if (aValue instanceof TaskNode) {
         TaskNode taskNode = (TaskNode) aValue;
         Task task = taskNode.getLookup().lookup(Task.class);
         return task.isCompleted();
      }
     return true;
   }
};
outlineView.getOutline().setQuickFilter(0, filter);

In the previous example we pass the filter to column 0. This is the node column which gives us access to all fields of the node, i.e. the bean that is wrapped, which allows to filter the outline view even for fields that are not displayed in the outline view. E.g. you have a class :

public class Task implements Serializable {
  private int id;
  private String description;
  private int priority;
  private Date dueDate;
  private boolean alert = false;
  private int daysBefore;
  private String obs;
  private boolean completed = false;

but the outline view displays only: priority, description, alert and due date and you want to filter the tasks that are completed, then the above filter will do the job.

Categories: Filter, OutlineView

Recipe 1: Loose coupling

May 19, 2013 Leave a comment

Problem:
You would like to create a loose coupled design, i.e. depend on interface instead of implementation. Netbeans uses a component-based way of development. Modules or components developed by independent groups or individuals must be able to communicate with each other in a loose coupled way.


Fig. 1 – Loose coupling

However, the big question is how does the client find the implementation? Spring uses dependency injection or inversion of control via its xml files. Java 6 uses a Query-based approach, the ServiceLoader:

ServiceLoader<Provider> serviceLoader = ServiceLoader.load(Provider.class);
for (Provider provider : serviceLoader) { return provider; }

However, the ServiceLoader has a number of problems:

  • it isn’t dynamic (you cannot install/uninstall a plugin/service at runtime)
  • it does all service loading at startup (as a result it requires longer startup time and more memory usage)
  • it cannot be configured; there is a standard constructor and it doesn’t support factory methods
  • it doesn’t allow for ranking, i.e. we cannot choose which service to load first

Solution:
Netbeans introduces a new way to accomplish loose coupling, the ServiceProvider:

@ServiceProvider(service = Provider.class)
public class ProviderImpl implements Provider { }

The magic line is the first line which tells Netbeans that this class is an implementation of the service Provider.class. Netbeans creates a text file package.Provider inside build/classes/META-INF/services/ folder of the module which contains the fully qualified names of the implementation classes, e.g. package.ProviderImpl. If you have worked with ServiceLoader, then this is not new to you.

However, the big question has not been answered yet. How does the client find the implementation? In Netbeans this is done with the use of lookups! The client looks in the default lookup for the interface. The default Lookup is a Lookup that evaluates the service declarations in the META-INF/services folder. It is callable through the Lookup.getDefault() method. By asking for a service interface in this way, you receive instances of implementing classes
registered in the META-INF/services folder.

A lookup is a map with class objects as keys and sets of instances of these class objects as values, i.e. Lookup = Map<Class, Set<Class>>, e.g. Map<String, Set<String>> or Map<Provider, Set<Provider>>. Netbeans provides a number of methods to access a lookup:

Provider provider = Lookup.getDefault().lookup(Provider.class);
provider.aMethod();

or if you have more than one implementations of Provider:

Collection <? extends Provider> providers = Lookup.getDefault().lookupAll(Provider.class);
for (Provider provider : providers) { }

As you can see from the above code examples, the client has no clue about which implementation it uses; it only knows the interface. Loose coupling!

Imagine the lookup as a map in memory which stores implementations of all the services of your application. You put the service implementation in the lookup when you define it with the @ServiceProvider annotation and then you search for it using the above commands.

ServiceProvider does not have the drawbacks of ServiceLoader, mentioned above. It is dynamic, so you can plugin/unplug modules while your application is running, it doesn’t load all services at startup and allows you to set priorities (with the position attribute), e.g.:

@ServiceProvider(service = Provider.class, position=1)

NetBeans orders instances by ascending positions, i.e. instances with smaller numbers are returned before instances with larger numbers.

There are other lookups in Netbeans apart from the default lookup which is used to store services implementations, and this often brings confusions. E.g. each OutlineView or TopComponent associates with it a lookup to store the nodes that are selected at a specific time. You should not confuse this lookup with the default lookup.
More on lookups on future posts.

References:

  1. Epple A. (2009), “NetBeans Lookups Explained!“, NetBeans DZone.
  2. Lof Nicklas (2010), “That other Lookup“.
  3. Lof Nicklas (2010), “Power of Lookup“.
  4. Antonios lookup articles.
Categories: Lookup

Recipe 8: Hide node column in OutlineView

April 14, 2013 Leave a comment

Problem

The OutlineView is a combined tree/table structure. As a consequence, when using it to display tabular data, the node column is displayed as the first column of the table in addition to the data columns. How can I remove it?

Solution

In older (< 7.0) versions of NetBeans, it was recommended to use this trick:

outlineView.getOutline().setRootVisible(false);
outlineView.getOutline().getColumnModel().removeColumn(outlineView.getOutline().getColumnModel().getColumn(0));

However, it is not a good idea to remove this column because NetBeans is using it. The recommended way, that works in 7.0 (there seems to be a bug in previous versions), is:

Outline outline = outlineView.getOutline();
outline.setRootVisible(false);
TableColumnModel columnModel = outline.getColumnModel();
ETableColumn column = (ETableColumn) columnModel.getColumn(0);
((ETableColumnModel) columnModel).setColumnHidden(column, true);
Categories: OutlineView

Recipe 7: Decorate an OutlineView

November 28, 2011 1 comment

Problem

When an OutlineView is not editable, its cells appear grey and the property editor button is shown. How do I decorate a read-only OutlineView?

Solution

Use the following custom cell renderer in NetBeans 7.0 and above:

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import org.netbeans.swing.outline.DefaultOutlineCellRenderer;
import org.openide.awt.HtmlRenderer;
import org.openide.nodes.Node.Property;
import org.openide.util.Exceptions;
/**
 * Renderer used to remove the property editor button and the grey appearance of the cells of an outline view.
 * It uses the &lt;@link PropertyTextRenderer/&gt; of the property if any was set.
 */
public class CustomOutlineCellRenderer extends DefaultOutlineCellRenderer {

 /** Gray Color for the odd lines in the view. */
 private static final Color VERY_LIGHT_GRAY = new Color(236, 236, 236);
 /** Center the content of the cells displaying text. */
 protected boolean centered = System.getProperty(&quot;os.name&quot;).toLowerCase().indexOf(&quot;windows&quot;) &lt; 0;
 /** Highlight the non editable cells making the foreground lighter.*/
 protected boolean lighterEditableFields = false;
 @Override
 @SuppressWarnings(&quot;unchecked&quot;)
 public Component getTableCellRendererComponent(final JTable table,
                                                final Object value,
                                                final boolean isSelected,
                                                final boolean hasFocus,
                                                final int row,
                                                final int column) {
  Component cell = null;
  Object valueToDisplay = value;
  if (value instanceof Property) {
     try {
        valueToDisplay = ((Property) value).getValue();
     } catch (IllegalAccessException ex) {
        Exceptions.printStackTrace(ex);
     } catch (InvocationTargetException ex) {
        Exceptions.printStackTrace(ex);
     }
  }
  if (valueToDisplay != null) {
    TableCellRenderer renderer = table.getDefaultRenderer(valueToDisplay.getClass());
    if (renderer != null) {
       cell = renderer.getTableCellRendererComponent(table, valueToDisplay, isSelected,
                                                     hasFocus, row, column);
    }
  } else {
       cell = super.getTableCellRendererComponent(table, valueToDisplay, isSelected, hasFocus, row, column);
  }
  if (cell != null) {
     if (centered) {
        if (cell instanceof HtmlRenderer.Renderer) {
           ((HtmlRenderer.Renderer) cell).setCentered(centered);
        } else if (cell instanceof DefaultTableCellRenderer.UIResource) {
           ((DefaultTableCellRenderer.UIResource) cell).setHorizontalAlignment(JLabel.CENTER);
        }
     }
     Color foregroundColor = table.getForeground();
     int modelRow = table.convertRowIndexToModel(row);
     int modelColumn = table.convertColumnIndexToModel(column);
     final boolean cellEditable = table.getModel().isCellEditable(modelRow, modelColumn);
     if (lighterEditableFields &amp;&amp; cellEditable) {
        foregroundColor = Color.BLUE;
     }
     cell.setForeground(foregroundColor);
     cell.setBackground(row % 2 == 0 ? Color.WHITE : VERY_LIGHT_GRAY);
     if (isSelected) {
        if (lighterEditableFields &amp;&amp; cellEditable) {
           cell.setFont(cell.getFont().deriveFont(Font.BOLD));
        }
        cell.setBackground(table.getSelectionBackground());
     }
  }
  return cell;
 }
 /**
   * @return true if the text rendered in labels is centered.
   */
 public boolean isCentered() {
    return centered;
 }

 /**
  * Center the content of the cells displaying text.
  *
  * @param value true to center, false for default alignment.
  */
 public void setCentered(final boolean value) {
    this.centered = value;
 }

 /**
  * @return true if non editable cells have a lighter foreground.
  */
 public boolean isLighterEditableFields() {
    return lighterEditableFields;
 }

 /**
  * Highlight the non editable cells making the foreground lighter.
  *
  * @param value true to activate this feature.
  */
 public void setLighterEditableFields(final boolean value) {
    this.lighterEditableFields = value;
 }
}

Then you can use it in your outline view like so:

outlineView.getOutline().setDefaultRenderer(Node.Property.class,
                                            new CustomOutlineCellRenderer());

If you need more customisation:

outlineView.getOutline().setDefaultRenderer(Node.Property.class,
new CustomOutlineCellRenderer() {

 @Override
 public Component getTableCellRendererComponent(final JTable table, final Object value,
                                               final boolean isSelected,
                                               final boolean hasFocus,
                                               final int row, final int column) {
  JLabel cell = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
  int modelRow = table.convertRowIndexToModel(row);
  Node node = EXPLORER_MANAGER.getRootContext().getChildren().getNodeAt(modelRow);
  if (node != null) {
     if (isSelected) {
        cell.setFont(cell.getFont().deriveFont(Font.BOLD));
     } else {
        cell.setFont(cell.getFont().deriveFont(Font.PLAIN));
     }
     cell.setBackground(Color.black);
     cell.setForeground(Color.white);
  }
  return cell;
 }
});

Recipe 6: Sort an OutlineView programmatically

November 27, 2011 Leave a comment

Problem

How can I sort a column of an OutlineView programmatically?

Solution

You can click on the header of a column in an OutlineView to sort it in ascending/descending order. But how can you do this programmatically? E.g. when you click on a button you wish to have the outline view sorted by one column, and when you click another button to have it sorted based on another column:

/**
 * Sort the outline view {@code ov} on the given {@code field}.
 * @param ov outline view to sort
 * @param field to sort upon
 * @param ascending if {@code true} then the list is sorted in ascending order, if {@code false} in descending order.
 */
public static void sortBy(final OutlineView ov, final String field, final boolean ascending) {
  ETableColumnModel columnModel = (ETableColumnModel) ov.getOutline().getColumnModel();
  int columnCount = columnModel.getColumnCount();
  columnModel.clearSortedColumns();
  for (int i = 0; i < columnCount; i++) {
    ETableColumn column = (ETableColumn)columnModel.getColumn(i);
    if (column.getHeaderValue().equals(field)) {
      columnModel.setColumnSorted(column, ascending, 1);
    }
  }
  TableModel model = ov.getOutline().getModel();
  ov.getOutline().tableChanged(new TableModelEvent(model, 0, model.getRowCount()));
}
Categories: OutlineView Tags: ,

Recipe 5: Notifications

October 11, 2011 Leave a comment

Problem

How can I display a notification (e.g. ) in a form of ‘bubble’ that notifies the user e.g. that something went wrong?

Solution

Since NetBeans 6.7 a new class was introduced which displays a notification on the right side of the status bar:

NotificationDisplayer.getDefault().notify(String title, Icon icon, String detailsText, ActionListener detailsAction);
NotificationDisplayer.getDefault().notify(String title, Icon icon, String detailsText, ActionListener detailsAction, Priority priority);
NotificationDisplayer.getDefault().notify(String title, Icon icon, JComponent balloonDetails, JComponent popupDetails, Priority priority);
NotificationDisplayer.getDefault().notify();
Categories: Uncategorized