Displaying EventLists in JFace Viewers

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Displaying EventLists in JFace Viewers

Bruce Alspaugh-2
Hi Folks,

Attached is a proof-of-concept for a TableViewerManager that decorates a
JFace TableViewer so it displays an EventList and updates the contents
in response to changes in it.  I modified TableComparatorChooser to sort
the columns and provide iconic visual feedback.  I also threw in a
screenshot for the sorted MP3 example and the view code behind it.

I'm still pretty new at SWT/JFace/RCP (the books I ordered just
arrived). I could use some help and feedback.  In particular, I need a
clean way to detect the OS that is running, and load the appropriate
sort icons directly from the GlazedLists distribution .jar into the
ImageRegistry.  I don't have a good way to do cross-platform testing,
because I only have access to WindowsXP.  Right now, the developer
simply copies the .png files for their OS into an icons directory at the
root of their main plugin and specifies the main plugin id to the
TableComparatorChooser.

Let me know what you think, and any problems you run into.  It's still a
proof-of-concept, but it looks like this should lead to a cleaner
architecture overall than trying to use an SWT Table directly the way we
do now.

Bruce

package ca.odell.glazedlists.jface.test;

import java.util.Random;

import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;

import ca.odell.glazedlists.BasicEventList;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.jface.TableComparatorChooser;
import ca.odell.glazedlists.jface.TableViewerManager;

/**
 * This is an example Eclipse RCP View based on the SortedList example from
 * the screencasts
 *
 * @author <a href="mailto:[hidden email]">James Lemieux</a>
 * @author <a href="mailto:[hidden email]">Bruce Alspaugh</a>
 */
public class SortedListExampleView extends ViewPart {
        public static final String VIEW_ID = "ca.odell.glazedlists.jface.test.sortedListExampleView";

    public static class MP3 {
        private int track;
        private String song;
        private String album;
        private String artist;

        public MP3(String artist, String album, String name, int track) {
            this.song = name;
            this.album = album;
            this.artist = artist;
            this.track = track;
        }

        public String getSong() {
            return song;
        }

        public String getAlbum() {
            return album;
        }

        public String getArtist() {
            return artist;
        }

        public int getTrack() {
            return track;
        }
    }

    private static final String[] musicalStrings =
            {"Seven", "Mary", "Three", "Alice", "In", "Chains",
             "Green", "Day", "Led", "Zeppelin", "Beatles", "Prince",
             "Holy", "Cake", "White", "Black", "Sgt.", "Pepper"};

    private static final Random dice = new Random();

    private static String makeRandomMusicString(int numParts) {
        StringBuffer musicBuffer = new StringBuffer();

        for (int i = 0; i < numParts; i++) {
            if (musicBuffer.length() > 0)
                musicBuffer.append(' ');
            musicBuffer.append(musicalStrings[dice.nextInt(musicalStrings.length)]);
        }

        return musicBuffer.toString();
    }
   
    private TableViewerManager mgr;
   
    // put in the Plugin ID for your main RCP plugin here from the plugin.xml
        public static final String PLUGIN_ID = "ca.odell.glazedlists.jface.test";
       
        public void createPartControl(Composite aParent) {
        // create an EventList of MP3s
        final EventList mp3s = new BasicEventList();

        // populate the MP3 list
        for (int i = 1; i <= 100; i++) {
            String artist = makeRandomMusicString(2 + dice.nextInt(3));
            String album = makeRandomMusicString(2 + dice.nextInt(3));
            String song = makeRandomMusicString(2 + dice.nextInt(3));
            mp3s.add(new MP3(artist, album, song, i));
        }

        SortedList sortedMP3s = new SortedList(mp3s, null);

        // build a TableFormat
        String[] propertyNames = new String[] {"track", "artist", "album", "song"};
        String[] columnLabels = new String[] {"Track", "Artist", "Album", "Song"};
        TableFormat tf = GlazedLists.tableFormat(MP3.class, propertyNames, columnLabels);
       
        // create the JFace TableViewer
                TableViewer tableViewer = new TableViewer(aParent,
                                SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);
               
                // decorate the TableViewer
                mgr = new TableViewerManager(tableViewer, sortedMP3s, tf);
               
                // add sorting capability
                new TableComparatorChooser(mgr, sortedMP3s, true, PLUGIN_ID);
        }

        public void setFocus() {
                mgr.getTableViewer().getTable().setFocus();
        }
}


/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.jface;

// the core Glazed Lists packages
import ca.odell.glazedlists.*;
// the Glazed Lists util and volatile packages for default comparators
import ca.odell.glazedlists.gui.*;
import ca.odell.glazedlists.impl.gui.SortingStrategy;
import ca.odell.glazedlists.impl.gui.MouseOnlySortingStrategy;
// for keeping lists of comparators
import java.util.*;
// SWT toolkit stuff for displaying widgets
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Image;

/**
 * A TableComparatorChooser is a tool that allows the user to sort a ListTable
 * by clicking on the table's headers. It requires that the TableViewerManager
 * has a SortedList as it's source because the sorting on that list is used.
 *
 * To use the TableComparatorChooser, copy the sort image icons for your OS
 * (primary_sorted.png, secondary_sorted.png, etc.) from the GlazedList
 * distribution into an icons directory at the root of your main Eclipse RCP
 * plugin.  You will need to specify the plugin ID for your main plugin as the
 * last parameter for the constructor.  You can obtain this ID from the Overview
 * tab of the plugin.xml file for your main plugin.
 *
 * As an alternative to TableComparatorChooser, the developer could extend a
 * ViewerSorter and put it into the TableViewer by calling TableViewer.setSorter().
 * Using a ViewerSorter and a TableComparatorChooser at the same time is not
 * supported.
 *
  * <p><strong>Warning:</strong> This class is a proof-of-concept and subject to
 * many bugs and API changes.</p>
 *
 * @see <a href="http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet2.java?rev=HEAD">Snippet 2</a>
 *
* @ToDo Find a way to detect the OS at runtime and load the sort images from the
 * appropriate resources subdirectory in the GlazedLists distribution, rather
 * then require the developer to copy the images into an icons directory in
 * their main plugin.
 *
 * @author <a href="mailto:[hidden email]">Jesse Wilson</a>
 * @author <a href="mailto:[hidden email]">Bruce Alspaugh</a>
 */
public final class TableComparatorChooser extends AbstractTableComparatorChooser {
        private ImageRegistry imageRegistry;

    private final SortingStrategy sortingStrategy;

    /** the table being sorted */
    private Table table;

    /** listeners to sort change events */
    private List<Listener> sortListeners = new ArrayList<Listener>();

    /** listeners for column headers */
    private ColumnListener columnListener = new ColumnListener();

    /**
     * Creates a new TableComparatorChooser that responds to clicks
     * on the specified table and uses them to sort the specified list.
     *
     * @param aEventTableViewer the table viewer for the table to be sorted
     * @param aSortedList the sorted list to update.
     * @param aMultipleColumnSort <code>true</code> to sort by multiple columns
     *      at a time, or <code>false</code> to sort by a single column. Although
     *      sorting by multiple columns is more powerful, the user interface is
     *      not as simple and this strategy should only be used where necessary.
     * @param aPluginIn the plugin id for your main RCP plugin.  It should be
     * the same as what is in the plugin.xml file.
     */
    public TableComparatorChooser(TableViewerManager aTableViewerManager,
    SortedList aSortedList, boolean aMultipleColumnSort, String aPluginID) {
        super(aSortedList, aTableViewerManager.getTableFormat());

        // save the SWT-specific state
        this.table = aTableViewerManager.getTableViewer().getTable();

        // listen for events on the specified table
        for (int c = 0; c < table.getColumnCount(); c++) {
            table.getColumn(c).addSelectionListener(columnListener);
        }
       
        // init the image registry that manages the sort icons
        imageRegistry = aTableViewerManager.createImageRegistry(aPluginID);
       
        // sort using the specified approach
        sortingStrategy = new MouseOnlySortingStrategy(aMultipleColumnSort);
       
        // initialize sort images
        setSortImages();
        for (TableColumn col : table.getColumns())
        col.pack();
    }

    /**
     * Registers the specified {@link Listener} to receive notification whenever
     * the {@link Table} is sorted by this {@link TableComparatorChooser}.
     */
    public void addSortListener(final Listener sortListener) {
        sortListeners.add(sortListener);
    }
    /**
     * Deregisters the specified {@link Listener} to no longer receive events.
     */
    public void removeSortActionListener(final Listener sortListener) {
        for(Iterator<Listener> i = sortListeners.iterator(); i.hasNext(); ) {
            if(sortListener == i.next()) {
                i.remove();
                return;
            }
        }
        throw new IllegalArgumentException("Cannot remove nonexistent listener " + sortListener);
    }

    /**
     * Handles column clicks.
     */
    private class ColumnListener implements SelectionListener {
    // handle a single-click
        public void widgetSelected(SelectionEvent aEvent) {
        TableColumn column = (TableColumn)aEvent.widget;
            Table table = column.getParent();
            int columnIndex = table.indexOf(column);
            sortingStrategy.columnClicked(sortingState, columnIndex, 1, false, false);
        }
       
        // handle a double-click
                public void widgetDefaultSelected(SelectionEvent aEvent) {
                        TableColumn column = (TableColumn)aEvent.widget;
            Table table = column.getParent();
            int columnIndex = table.indexOf(column);
            sortingStrategy.columnClicked(sortingState, columnIndex, 2, false, false);
                }
    }

    /**
     * Examines the current {@link Comparator} of the SortedList and
     * adds icons to the table header renderers in response.
     *
     * <p>To do this, clicks are injected into each of the
     * corresponding <code>ColumnClickTracker</code>s.
     */
    protected void redetectComparator(Comparator currentComparator) {
        super.redetectComparator(currentComparator);
       
        setSortImages();
    }

    /**
     * Updates the comparator in use and applies it to the table.
     */
    protected final void rebuildComparator() {
        super.rebuildComparator();
       
        setSortImages();
       
        // notify interested listeners that the sorting has changed
        Event sortEvent = new Event();
        sortEvent.widget = table;
        for(Iterator<Listener> i = sortListeners.iterator(); i.hasNext(); ) {
            i.next().handleEvent(sortEvent);
        }
    }
   
    /**
     * Installs the sort images in the table header based on the
     * sorting style of each column.  Images are retrieved from
     * the image registry.
     *
     */
    private void setSortImages() {
    for (int c = 0; c < table.getColumnCount(); c++) {
        Image colImage = imageRegistry.
        get(ISortImageKeys.IMG_KEYS[getSortingStyle(c)]);
        table.getColumn(c).setImage(colImage);
        }
    }
   
    /**
     * Releases the resources consumed by this {@link TableComparatorChooser} so that it
     * may eventually be garbage collected.
     *
     * <p>A {@link TableComparatorChooser} will be garbage collected without a call to
     * {@link #dispose()}, but not before its source {@link EventList} is garbage
     * collected. By calling {@link #dispose()}, you allow the {@link TableComparatorChooser}
     * to be garbage collected before its source {@link EventList}. This is
     * necessary for situations where an {@link TableComparatorChooser} is short-lived but
     * its source {@link EventList} is long-lived.
     *
     * <p><strong><font color="#FF0000">Warning:</font></strong> It is an error
     * to call any method on a {@link TableComparatorChooser} after it has been disposed.
     */
    public void dispose() {
    // stop listening for events on the specified table
    if (!table.isDisposed()) {
    for (TableColumn col : table.getColumns()) {
    col.removeSelectionListener(columnListener);
    col.setImage(null);
    }
    }
   
    super.dispose();
    }
}
/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.jface;

// Java list utility classes
import java.util.ArrayList;
import java.util.List;

// JFace packages
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;

// SWT packages
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.ui.plugin.AbstractUIPlugin;

// Core GlazedList packages
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.ListSelection;
import ca.odell.glazedlists.event.ListEvent;
import ca.odell.glazedlists.event.ListEventListener;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.swt.GlazedListsSWT;

/**
 * A TableViewerManager is a decorator around a JFace TableViewer that displays
 * an EventList in the TableViewer by installing an appropriate ContentProvider,
 * LabelProvider, EventList input, and change listeners into it.  Developers can
 * read, but should not replace the ContentProvider and LabelProvider installed
 * by the TableViewerManager.
 *
 * @see org.eclipse.jface.viewers.TableViewer
 *
 * This class is not thread-safe.  It must be used exclusively with the SWT
 * event handler thread.
 *
 * <p><strong>Warning:</strong> This class is a proof-of-concept and subject to
 * many bugs and API changes.
 *
 * @todo Provide a hook to allow the developer to do in-cell editing of the
 * JavaBeans displayed in the TableViewer.
 *
 * @author <a href="mailto:[hidden email]">Bruce Alspaugh</a>
 * @author <a href="mailto:[hidden email]">Jesse Wilson</a>
 */
public class TableViewerManager {
        /** the heavyweight TableViewer **/
        private TableViewer tableViewer;
       
        /** the list being displayed in the TableViewer **/
        private EventList sourceList;
       
        /** Specifies how to render table headers and sort */
        private TableFormat tableFormat;
       
        /** ContentProvider installed in the TableViewer */
        private IStructuredContentProvider contentProvider = null;
       
        /** LabelProvider installed in the TableViewer */
        private ITableLabelProvider tableLabelProvider = null;
       
        /** Is the selection being changed? */
        private boolean selectionInProgress = false;
       
        /** Maintains the TableViewer selection as an EventList */
        private ListSelection listSelection = null;
       
        /** Watches for changes in the selection and updates the ListSelection */
        private ISelectionChangedListener selectionManager = null;
       
        /** Maintains the sort images installed in the TableColumns */
        private ImageRegistry imageRegistry = null;
       
        /**
         * Creates a new TableViewerManager that displays the EventList in
         * the provided TableViewer that updates the contents in response to
         * changes in the Eventlist.  The table is formatted according to the
         * provided TableFormat.
         */
        public TableViewerManager(TableViewer aTableViewer, EventList aSourceList,
                        TableFormat aTableFormat) {
                tableViewer = aTableViewer;
                sourceList = aSourceList;
                tableFormat = aTableFormat;
               
                createColumns(tableViewer.getTable());
                tableViewer.setContentProvider(getContentProvider());
                tableViewer.setLabelProvider(getLabelProvider());
                tableViewer.addSelectionChangedListener(getSelectionChangedListener());
                tableViewer.setInput(sourceList);
                tableViewer.getTable().addDisposeListener(new DisposeTableListener());
                packColumns(tableViewer.getTable());
        }
       
        /**
         * Creates a new TableViewerManager that displays the EventList in
         * the provided TableViewer that updates the contents in response to
         * changes in the Eventlist.  The Table is formatted with an automatically
         * generated TableFormat.  It uses JavaBeans and reflection to create a
     * TableFormat as specified.
         */
        public TableViewerManager(TableViewer aTableViewer, EventList aSourceList,
                        String[] aPropertyNames, String[] aColumnLabels) {
                this(aTableViewer, aSourceList,
                                GlazedLists.tableFormat(aPropertyNames, aColumnLabels));
        }
       
        /**
         * Obtain the TableViewer that is being managed.
         * @see org.eclipse.jface.viewers.TableViewer
         *
         * @return TableViewer
         */
        public TableViewer getTableViewer() {
                return tableViewer;
        }
       
        /**
         * Obtain the TableFormat that specifies the column names and labels
         * @see ca.odell.glazedlists.gui.TableFormat
         *
         * @return TableFormat
         */
        public TableFormat getTableFormat() {
                return tableFormat;
        }
       
        /**
         * Lazily constructs and returns a ContentProvider for the TableViewer
         * that listens for changes in the EventList.
         * @see org.eclipse.jface.viewers.IStructuredContentProvider
         *
         * @return ContentProvider
         */
        public IStructuredContentProvider getContentProvider() {
                final class EventContentProvider implements
                        IStructuredContentProvider, ListEventListener {
               
                        private StructuredViewer viewer;
                        private EventList swtSource = null;
                       
                        /**
                         * Obtain an array of beans stored in the inputElement
                         */
                        public Object[] getElements(Object aInputElements) {
                                EventList sourceList = (EventList)aInputElements;
                                sourceList.getReadWriteLock().readLock().lock();
                                try {
                                        return sourceList.toArray();
                                } finally {
                                        sourceList.getReadWriteLock().readLock().unlock();
                                }
                        }
                       
                        /**
                         * Handle changes to the input element.  Stop listening for changes
                         * to the old list (if any), and starts listening for changes to the
                         * new one (if any).
                         */
                        public void inputChanged(Viewer aViewer, Object aOldInput, Object aNewInput) {
                                // update viewer
                                viewer = (StructuredViewer)aViewer;
                               
                                // if not same input
                                if (aOldInput != aNewInput) {
                                       
                                        // remove old listener
                                        if (aOldInput != null) {
                                                swtSource.removeListEventListener(this);
                                        }
                                       
                                        // add new listener
                                        if (aNewInput != null) {
                                                EventList eventList = (EventList)aNewInput;
                                                swtSource = GlazedListsSWT.swtThreadProxyList(eventList,
                                                        viewer.getControl().getDisplay());
                                                swtSource.addListEventListener(this);
                                        }
                                }
                        }
                       
                        /**
                         * Listen for changes in the source EventList, and refresh
                         * or update the Viewer accordingly.   Relies on the swtSource
                         * to dispatch the change events to the SWT thread.
                         *
                         * Note:  I might be able to optimize the refresh a little bit if I
                         * knew which properties were updated, or where the insertion or
                         * deletion took place.
                         */
                        public void listChanged(ListEvent aListEvent) {
                                swtSource.getReadWriteLock().readLock().lock();
                                try {
                                        while(aListEvent.next()) {
                                                switch (aListEvent.getType()) {
                                                        case ListEvent.INSERT:
                                                        case ListEvent.DELETE:
                                                                viewer.refresh();
                                                                break;
                                                        case ListEvent.UPDATE:
                                                                viewer.update(swtSource.get(aListEvent.getIndex()), null);
                                                                break;
                                                }
                                        }
                                } finally {
                                        swtSource.getReadWriteLock().readLock().unlock();
                                }
                        }
               
                        public void dispose() { }
                }

                if (contentProvider == null) {
                        contentProvider = new EventContentProvider();
                }
               
                return contentProvider;
        }
       
        /**
         * Lazily constructs and obtains a LabelProvider for the TableViewer
         * based on the TableFormat of the table
         * @see org.eclipse.jface.viewers.ITableLabelProvider
         *
         * @return TableLabelProvider
         */
        public ITableLabelProvider getLabelProvider() {
                final class EventLabelProvider implements ITableLabelProvider {
                        private List<ILabelProviderListener> listeners;
                        private TableFormat tableFormat;
                       
                        public EventLabelProvider(TableFormat aTableFormat) {
                                listeners = new ArrayList<ILabelProviderListener>();
                                tableFormat = aTableFormat;
                        }
                       
                        public String getColumnText(Object aElement, int aColumnIndex) {
                                Object cellValue = tableFormat.getColumnValue(aElement, aColumnIndex);
                                if (cellValue != null)
                                        return cellValue.toString();
                               
                                return "";
                        }
                       
                        public Image getColumnImage(Object aElement, int aColumnIndex) {
                                return null;
                        }
                       
                        public boolean isLabelProperty(Object aElement, String aProperty) {
                                return true;
                        }
                       
                        public void addListener(ILabelProviderListener aListener) {
                                if (!listeners.contains(aListener)) {
                                        listeners.add(aListener);
                                }
                        }
                       
                        public void removeListener(ILabelProviderListener aListener) {
                                listeners.remove(aListener);
                        }
                                       
                        public void dispose() { }
                }
               
                if (tableLabelProvider == null) {
                        tableLabelProvider = new EventLabelProvider(tableFormat);
                }
               
                return tableLabelProvider;
        }
       
        /**
         * Lazily construct and obtain the TableViewer selection as a
         * ListSelection
         *
         * @see ca.odell.glazedlists.ListSelection
         * @return ListSelection
         */
        public ListSelection getListSelection() {
                /**
                 * This inner class listenes for programmatic changes to the
                 * ListSelection and syncs the TableViewer selection accordingly.
                 */
                final class ListSelectionListener implements ListSelection.Listener {
                        public void selectionChanged(int changeStart, int changeEnd) {
                                if (!selectionInProgress && changeStart != -1) {
                                        selectionInProgress = true;
                                        tableViewer.setSelection(
                                                new StructuredSelection(listSelection.getSelected()));
                                        selectionInProgress = false;
                                }
                        }
                }
               
                if (listSelection == null) {
                        listSelection = new ListSelection(sourceList);
                        listSelection.addSelectionListener(new ListSelectionListener());
                }
               
                return listSelection;
        }
       
        /**
         * Lazily constructs and obtains a listener for changes in the TableViewer
         * selection that syncs those changes with the ListSelection
         */
        public ISelectionChangedListener getSelectionChangedListener() {
                final class SelectionChangedListener implements ISelectionChangedListener {
                        public void selectionChanged(SelectionChangedEvent aEvent) {
                                if (!selectionInProgress) {
                                        IStructuredSelection sSel =
                                                (IStructuredSelection) aEvent.getSelection();
                                        selectionInProgress = true;
                                        getListSelection().deselectAll();
                                        getListSelection().select(sSel.toList());
                                        selectionInProgress = false;
                                }
                        }
                }
               
                if (selectionManager == null)
                        selectionManager = new SelectionChangedListener();
               
                return selectionManager;
        }
       
    /**
     * The ImageRegistry is responsible for loading and releasing the sort image
     * resources.  The images are loaded from an icons directory within the
     * plugin specified by aPluginID.  The plugin id should be the same as the
     * plugin id on the Overview tab of the plugin.xml editor.
     *
     * @see http://www.eclipse.org/articles/Article-Using%20Images%20In%20Eclipse/Using%20Images%20In%20Eclipse.html
     *
     * This function is primarily intended for the use of TableComparatorChooser
     * to obtain

screenshot.png (35K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Displaying EventLists in JFace Viewers

James Lemieux
Nice start Bruce,

   I don't know much about SWT or RCP, but it seems to make some fairly rigid design decisions. After reading one of the links in your attached code, it appears this is probably your major hangup w.r.t . images and RCP:

"Because plug-ins can be stored anywhere, we use URLs to describe their location. The image location will always be the same relative to the plug-in location, so we use that as the base of the image's URL. We then use the factory method defined on the ImageDescriptor class to create our descriptor."

I'm not certain why the image location must be relative to the plug-in? Security? Standardization? You'd like to load it right from the Glazed Lists jar file which will be located somewhere under the plugin directory I guess, right?

James

On 8/24/06, Bruce Alspaugh <[hidden email]> wrote:
Hi Folks,

Attached is a proof-of-concept for a TableViewerManager that decorates a
JFace TableViewer so it displays an EventList and updates the contents
in response to changes in it.  I modified TableComparatorChooser to sort
the columns and provide iconic visual feedback.  I also threw in a
screenshot for the sorted MP3 example and the view code behind it.

I'm still pretty new at SWT/JFace/RCP (the books I ordered just
arrived). I could use some help and feedback.  In particular, I need a
clean way to detect the OS that is running, and load the appropriate
sort icons directly from the GlazedLists distribution .jar into the
ImageRegistry.  I don't have a good way to do cross-platform testing,
because I only have access to WindowsXP.  Right now, the developer
simply copies the .png files for their OS into an icons directory at the
root of their main plugin and specifies the main plugin id to the
TableComparatorChooser.

Let me know what you think, and any problems you run into.  It's still a
proof-of-concept, but it looks like this should lead to a cleaner
architecture overall than trying to use an SWT Table directly the way we
do now.

Bruce


package ca.odell.glazedlists.jface.test;

import java.util.Random;

import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;

import ca.odell.glazedlists.BasicEventList ;
import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.SortedList;
import ca.odell.glazedlists.gui.TableFormat;
import ca.odell.glazedlists.jface.TableComparatorChooser ;
import ca.odell.glazedlists.jface.TableViewerManager;

/**
* This is an example Eclipse RCP View based on the SortedList example from
* the screencasts
*
* @author <a href="mailto:[hidden email]">James Lemieux</a>
* @author <a href="mailto:[hidden email]">Bruce Alspaugh</a>
*/
public class SortedListExampleView extends ViewPart {
        public static final String VIEW_ID = "ca.odell.glazedlists.jface.test.sortedListExampleView";

    public static class MP3 {
        private int track;
        private String song;
        private String album;
        private String artist;

        public MP3(String artist, String album, String name, int track) {
            this.song = name;
            this.album = album;
            this.artist = artist;
            this.track = track;
        }

        public String getSong() {
            return song;
        }

        public String getAlbum() {
            return album;
        }

        public String getArtist() {
            return artist;
        }

        public int getTrack() {
            return track;
        }
    }

    private static final String[] musicalStrings =
            {"Seven", "Mary", "Three", "Alice", "In", "Chains",
             "Green", "Day", "Led", "Zeppelin", "Beatles", "Prince",
             "Holy", "Cake", "White", "Black", "Sgt.", "Pepper"};

    private static final Random dice = new Random();

    private static String makeRandomMusicString(int numParts) {
        StringBuffer musicBuffer = new StringBuffer();

        for (int i = 0; i < numParts; i++) {
            if (musicBuffer.length() > 0)
                musicBuffer.append(' ');
            musicBuffer.append(musicalStrings[dice.nextInt(musicalStrings.length)]);
        }

        return musicBuffer.toString ();
    }

    private TableViewerManager mgr;

    // put in the Plugin ID for your main RCP plugin here from the plugin.xml
        public static final String PLUGIN_ID = "ca.odell.glazedlists.jface.test ";

        public void createPartControl(Composite aParent) {
        // create an EventList of MP3s
        final EventList mp3s = new BasicEventList();

        // populate the MP3 list
        for (int i = 1; i <= 100; i++) {
            String artist = makeRandomMusicString(2 + dice.nextInt(3));
            String album = makeRandomMusicString(2 + dice.nextInt(3));
            String song = makeRandomMusicString(2 + dice.nextInt(3));
            mp3s.add(new MP3(artist, album, song, i));
        }

        SortedList sortedMP3s = new SortedList(mp3s, null);

        // build a TableFormat
        String[] propertyNames = new String[] {"track", "artist", "album", "song"};
        String[] columnLabels = new String[] {"Track", "Artist", "Album", "Song"};
        TableFormat tf = GlazedLists.tableFormat(MP3.class, propertyNames, columnLabels);

        // create the JFace TableViewer
                TableViewer tableViewer = new TableViewer(aParent,
                                SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI | SWT.FULL_SELECTION);

                // decorate the TableViewer
                mgr = new TableViewerManager(tableViewer, sortedMP3s, tf);

                // add sorting capability
                new TableComparatorChooser(mgr, sortedMP3s, true, PLUGIN_ID);
        }

        public void setFocus() {
                mgr.getTableViewer().getTable().setFocus();
        }
}



/* Glazed Lists                                                 (c) 2003-2006 */
/* http://publicobject.com/glazedlists/                      publicobject.com,*/
/*                                                     O'Dell Engineering Ltd.*/
package ca.odell.glazedlists.jface;

// the core Glazed Lists packages
import ca.odell.glazedlists.*;
// the Glazed Lists util and volatile packages for default comparators
import ca.odell.glazedlists.gui.* ;
import ca.odell.glazedlists.impl.gui.SortingStrategy;
import ca.odell.glazedlists.impl.gui.MouseOnlySortingStrategy;
// for keeping lists of comparators
import java.util.*;
// SWT toolkit stuff for displaying widgets
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Event ;
import org.eclipse.swt.events.*;
import org.eclipse.swt.graphics.Image;

/**
* A TableComparatorChooser is a tool that allows the user to sort a ListTable
* by clicking on the table's headers. It requires that the TableViewerManager
* has a SortedList as it's source because the sorting on that list is used.
*
* To use the TableComparatorChooser, copy the sort image icons for your OS
* (primary_sorted.png, secondary_sorted.png, etc.) from the GlazedList
* distribution into an icons directory at the root of your main Eclipse RCP
* plugin.  You will need to specify the plugin ID for your main plugin as the
* last parameter for the constructor.  You can obtain this ID from the Overview
* tab of the plugin.xml file for your main plugin.
*
* As an alternative to TableComparatorChooser, the developer could extend a
* ViewerSorter and put it into the TableViewer by calling TableViewer.setSorter ().
* Using a ViewerSorter and a TableComparatorChooser at the same time is not
* supported.
*
  * <p><strong>Warning:</strong> This class is a proof-of-concept and subject to
* many bugs and API changes.</p>
*
* @see <a href="http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.swt.snippets/src/org/eclipse/swt/snippets/Snippet2.java?rev=HEAD ">Snippet 2</a>
*
* @ToDo Find a way to detect the OS at runtime and load the sort images from the
* appropriate resources subdirectory in the GlazedLists distribution, rather
* then require the developer to copy the images into an icons directory in
* their main plugin.
*
* @author <a href="mailto:[hidden email]">Jesse Wilson</a>
* @author <a href="mailto:[hidden email]">Bruce Alspaugh</a>
*/
public final class TableComparatorChooser extends AbstractTableComparatorChooser {
        private ImageRegistry imageRegistry;

    private final SortingStrategy sortingStrategy;

    /** the table being sorted */
    private Table table;

    /** listeners to sort change events */
    private List<Listener> sortListeners = new ArrayList<Listener>();

    /** listeners for column headers */
    private ColumnListener columnListener = new ColumnListener();

    /**
     * Creates a new TableComparatorChooser that responds to clicks
     * on the specified table and uses them to sort the specified list.
     *
     * @param aEventTableViewer the table viewer for the table to be sorted
     * @param aSortedList the sorted list to update.
     * @param aMultipleColumnSort <code>true</code> to sort by multiple columns
     *      at a time, or <code>false</code> to sort by a single column. Although
     *      sorting by multiple columns is more powerful, the user interface is
     *      not as simple and this strategy should only be used where necessary.
     * @param aPluginIn the plugin id for your main RCP plugin.  It should be
     *          the same as what is in the plugin.xml file.
     */
    public TableComparatorChooser(TableViewerManager aTableViewerManager,
                SortedList aSortedList, boolean aMultipleColumnSort, String aPluginID) {
        super(aSortedList, aTableViewerManager.getTableFormat());

        // save the SWT-specific state
        this.table = aTableViewerManager.getTableViewer().getTable();

        // listen for events on the specified table
        for (int c = 0; c < table.getColumnCount(); c++) {
            table.getColumn(c).addSelectionListener(columnListener);
        }

        // init the image registry that manages the sort icons
        imageRegistry = aTableViewerManager.createImageRegistry(aPluginID);

        // sort using the specified approach
        sortingStrategy = new MouseOnlySortingStrategy(aMultipleColumnSort);

        // initialize sort images
        setSortImages();
        for (TableColumn col : table.getColumns())
                col.pack();
    }

    /**
     * Registers the specified {@link Listener} to receive notification whenever
     * the {@link Table} is sorted by this {@link TableComparatorChooser}.
     */
    public void addSortListener(final Listener sortListener) {
        sortListeners.add(sortListener);
    }
    /**
     * Deregisters the specified {@link Listener} to no longer receive events.
     */
    public void removeSortActionListener(final Listener sortListener) {
        for(Iterator<Listener> i = sortListeners.iterator (); i.hasNext(); ) {
            if(sortListener == i.next()) {
                i.remove();
                return;
            }
        }
        throw new IllegalArgumentException("Cannot remove nonexistent listener " + sortListener);
    }

    /**
     * Handles column clicks.
     */
    private class ColumnListener implements SelectionListener {
        // handle a single-click
        public void widgetSelected(SelectionEvent aEvent) {
                TableColumn column = (TableColumn)aEvent.widget;
            Table table = column.getParent();
            int columnIndex = table.indexOf(column);
            sortingStrategy.columnClicked(sortingState, columnIndex, 1, false, false);
        }

        // handle a double-click
                public void widgetDefaultSelected(SelectionEvent aEvent) {
                        TableColumn column = (TableColumn)aEvent.widget;
            Table table = column.getParent();
            int columnIndex = table.indexOf(column);
            sortingStrategy.columnClicked(sortingState, columnIndex, 2, false, false);
                }
    }

    /**
     * Examines the current {@link Comparator} of the SortedList and
     * adds icons to the table header renderers in response.
     *
     * <p>To do this, clicks are injected into each of the
     * corresponding <code>ColumnClickTracker</code>s.
     */
    protected void redetectComparator(Comparator currentComparator) {
        super.redetectComparator(currentComparator);

        setSortImages();
    }

    /**
     * Updates the comparator in use and applies it to the table.
     */
    protected final void rebuildComparator() {
        super.rebuildComparator();

        setSortImages();

        // notify interested listeners that the sorting has changed
        Event sortEvent = new Event();
        sortEvent.widget = table;
        for(Iterator<Listener> i = sortListeners.iterator(); i.hasNext(); ) {
            i.next().handleEvent(sortEvent);
        }
    }

    /**
     * Installs the sort images in the table header based on the
     * sorting style of each column.  Images are retrieved from
     * the image registry.
     *
     */
    private void setSortImages() {
        for (int c = 0; c < table.getColumnCount(); c++) {
                Image colImage = imageRegistry.
                   &nb
Reply | Threaded
Open this post in threaded view
|

Re: Displaying EventLists in JFace Viewers

Bruce Alspaugh-2
I suppose I should break the problem up into two parts:

1.  Loading images from the distribution .jar files instead of the main plugin.
2.  Detect the OS at runtime to figure out which set of images to load.

Typical RCP applications are built out of plugins.  There are actually two separate communicating plug-ins in the RCP test application:  the Glazed List plug-in which was created by importing the 1.7.0 distribution .jar as a plugin and expanding it, and the main plug-in consisting of boilerplate code created by a wizard and the View that displays the list.  To solve part one, I need to figure out how to tell it to load relative to the Glazed List plugin instead of the main plugin.  I may be able to fall back on some of the techniques you used in SortIconFactory to specify the URL's.  If the distribution .jar could be packaged as a plug-in rather than a plain .jar file, it might be easier.  I think with some further experimentation, I might be able to solve the first problem.

The second part will be harder for me to test because I only have access to WindowsXP.

It would be useful if I had a reserved directory in CVS like ca.odell.glazedlists.jface where I could upload what I am doing, and make all the other areas read-only.  I'm new at using CVS in Eclipse and I don't want to risk messing anything up.

Bruce

On 8/24/06, James Lemieux <[hidden email]> wrote:
Nice start Bruce,

   I don't know much about SWT or RCP, but it seems to make some fairly rigid design decisions. After reading one of the links in your attached code, it appears this is probably your major hangup w.r.t . images and RCP:

"Because plug-ins can be stored anywhere, we use URLs to describe their location. The image location will always be the same relative to the plug-in location, so we use that as the base of the image's URL. We then use the factory method defined on the ImageDescriptor class to create our descriptor."

I'm not certain why the image location must be relative to the plug-in? Security? Standardization? You'd like to load it right from the Glazed Lists jar file which will be located somewhere under the plugin directory I guess, right?

James

On 8/24/06, Bruce Alspaugh <[hidden email]> wrote:
Hi Folks,

Attached is a proof-of-concept for a TableViewerManager that decorates a
JFace TableViewer so it displays an EventList and updates the contents
in response to changes in it.  I modified TableComparatorChooser to sort
the columns and provide iconic visual feedback.  I also threw in a
screenshot for the sorted MP3 example and the view code behind it.

I'm still pretty new at SWT/JFace/RCP (the books I ordered just
arrived). I could use some help and feedback.  In particular, I need a
clean way to detect the OS that is running, and load the appropriate
sort icons directly from the GlazedLists distribution .jar into the
ImageRegistry.  I don't have a good way to do cross-platform testing,
because I only have access to WindowsXP.  Right now, the developer
simply copies the .png files for their OS into an icons directory at the
root of their main plugin and specifies the main plugin id to the
TableComparatorChooser.

Let me know what you think, and any problems you run into.  It's still a
proof-of-concept, but it looks like this should lead to a cleaner
architecture overall than trying to use an SWT Table directly the way we
do now.

Bruce