Collapsing groups in GroupTable using the keyboard

This forum is used by users to request and discuss new product features. Please do not use this forum for technical support including bug reports.

Moderator: JIDE Support

Forum rules
Product suggestions only. Please do not use this forum for technical support including bug reports.

Collapsing groups in GroupTable using the keyboard

Postby grmo » Fri Aug 10, 2007 3:02 am

It seems that if you have the first column of a group heading row selected/focussed and you press the left arrow key it collapses that group, which is very useful. However, if you happen to be on the second column, and yet still on a group heading row, pressing the left arrow key moves the focus to the first column, though you cannot see this change of focus (at least not on the look and feel I have?). I would argue it would make better sense to instead collapse the group and not move the focus. Otherwise, to collapse and expand the groups from the keyboard gets a little confusing, and requires you to make sure you are in the first column.
grmo
 
Posts: 33
Joined: Fri Jul 27, 2007 1:44 am

Postby JIDE Support » Fri Aug 10, 2007 7:17 am

People still want to use key to navigate. We observe other native windows application. That's what they do too. If the L&F you are using doesn't show the focus, it's the L&F problem that should be fixed.

Thanks,
JIDE Software Technical Support Team
JIDE Support
Site Admin
 
Posts: 37219
Joined: Sun Sep 14, 2003 10:49 am

Postby grmo » Mon Aug 13, 2007 12:53 am

Looking at this further I don't think it's the fault of the L&F (eg. none of the available L&Fs on your demo show up the column focus on a group heading row).

Further, the issue only seems to happen when I sort by a column. If I have set the GroupTableModel to be sorted, when I select a group heading row and call myTable.getSelectedColumns() I get the correct result before an update (the array {0} in my case) and the incorrect result after an update (an empty array).

I quote a unit test for this below. I find that testColumnWithoutSort passes and that testColumnWithSort fails on the second assertion.

Am I sorting correctly? Note that I have a separate topic posted on a forum here that asks how to sort a GroupTable without sorting one of the non-grouping columns. I'm not sure whether this issue is related to this question also.

Code: Select all
import java.util.*;

import javax.swing.*;
import javax.swing.table.*;

import ca.odell.glazedlists.*;
import ca.odell.glazedlists.gui.*;
import ca.odell.glazedlists.swing.*;

import com.jidesoft.converter.*;
import com.jidesoft.grid.*;

import junit.framework.*;

public class GroupTableColumnSelectionTest extends TestCase
{
    private DefaultTableModel myTableModel;
    private GroupTable myTable;

    public void testColumnWithoutSort()
    {
        selectColumnAndAdd();
    }
   
    public void testColumnWithSort()
    {
        myTable.sortColumn(0);
        selectColumnAndAdd();
    }

    private void selectColumnAndAdd()
    {
        myTable.setRowSelectionInterval(2, 2);
        myTable.setColumnSelectionInterval(1, 1);
        assertTrue(myTable.getSelectedColumns().length > 0);
       
        myTableModel.addRow(new Integer[] {7,8,9});
        assertTrue(myTable.getSelectedColumns().length > 0);
    }
     
    protected void setUp()
    {
        myTableModel = new DefaultTableModel(new Integer[][] {{1,2,3},{4,5,6}}, new String[] {"col0", "col1", "col2"});
        DefaultGroupTableModel myGroupTableModel = new DefaultGroupTableModel(myTableModel);
        myGroupTableModel.addGroupColumn(0);
        myGroupTableModel.groupAndRefresh();
        myTable = new GroupTable(myGroupTableModel);
    }

}
grmo
 
Posts: 33
Joined: Fri Jul 27, 2007 1:44 am

Postby JIDE Support » Mon Aug 13, 2007 9:29 am

I saw the focus rectangle (a dotted rectangle) in GroupTableDemo.

The selections are not guranteed to be kept if you change the data. You will have to use methods on TableUtils to load and save selection in certain cases. We have code to restore the selection if user clicks the header to sort. But if you write code to do it, you have to restore the selection yourself.

Thanks,
JIDE Software Technical Support Team
JIDE Support
Site Admin
 
Posts: 37219
Joined: Sun Sep 14, 2003 10:49 am

Postby grmo » Tue Aug 14, 2007 8:37 am

Just to clarify: If I create a GroupTable, sort on a column, then later add a single row to the underlying table model, you do not guarantee anything about the selection being preserved once the update has been processed by the GroupTable?

For reference, I see that the column selection is lost completely (hence the loss of the focus rectangle once an update is applied), and in certain cases the row selection is incorrect.

How would I save and restore the selection over a change to the underlying model? Is it possible to listen to all changes to the underlying model and save and restore selection before/after the update is processed by the GroupTable? As I understand it, I would be able to listen to the changes, but I would not know whether the change has yet been processed by the GroupTable.
grmo
 
Posts: 33
Joined: Fri Jul 27, 2007 1:44 am

Postby JIDE Support » Tue Aug 14, 2007 9:14 am

If you fire the correct table insert event, the selection should be kept. At least that's what I noticed in the demo below.

Code: Select all
/*
 * @(#)GroupTableDemo.java 4/4/2007
 *
 * Copyright 2002 - 2007 JIDE Software Inc. All rights reserved.
 */

import com.jidesoft.converter.ConverterContext;
import com.jidesoft.converter.ObjectConverter;
import com.jidesoft.converter.ObjectConverterManager;
import com.jidesoft.dialog.ButtonPanel;
import com.jidesoft.grid.*;
import com.jidesoft.plaf.LookAndFeelFactory;
import com.jidesoft.swing.JideBoxLayout;
import com.jidesoft.swing.JideSplitPane;

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Vector;

/**
 * Demoed Component: {@link com.jidesoft.grid.JideTable}
 * <br>
 * Required jar files: jide-common.jar, jide-grids.jar
 * <br>
 * Required L&F: any L&F
 */
public class AdvancedGroupTableDemo extends AbstractDemo {
    public TreeTable _table;
    public JLabel _message;
    protected DefaultTableModel _tableModel;
    private DefaultGroupTableModel _groupTableModel;

    public AdvancedGroupTableDemo() {
    }

    public String getName() {
        return "GroupTableDemo Demo";
    }

    public String getProduct() {
        return PRODUCT_NAME_GRIDS;
    }


    public int getAttributes() {
        return ATTRIBUTE_BETA;
    }

    static CellStyle cellStyle = new CellStyle();

    static {
        cellStyle.setHorizontalAlignment(SwingConstants.CENTER);
    }

    protected final Color BACKGROUND1 = new Color(253, 253, 244);
    protected final Color BACKGROUND2 = new Color(255, 255, 255);

    public Component getOptionsPanel() {
        JPanel checkBoxPanel = new JPanel(new GridLayout(0, 1));
        JCheckBox singleLevel = new JCheckBox("Single Level");
        singleLevel.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                _groupTableModel.setSingleLevelGrouping(e.getStateChange() == ItemEvent.SELECTED);
                _groupTableModel.groupAndRefresh();
                _groupTableModel.fireTableStructureChanged();
                _table.expandAll();
            }
        });
        singleLevel.setSelected(_groupTableModel.isSingleLevelGrouping());

        JCheckBox showGroupColumns = new JCheckBox("Show Group Columns");
        showGroupColumns.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                _groupTableModel.setDisplayGroupColumns(e.getStateChange() == ItemEvent.SELECTED);
                _groupTableModel.groupAndRefresh();
                _groupTableModel.fireTableStructureChanged();
                _table.expandAll();
            }
        });
        showGroupColumns.setSelected(_groupTableModel.isDisplayGroupColumns());

        JCheckBox showCountColumn = new JCheckBox("Show Count Columns");
        showCountColumn.addItemListener(new ItemListener() {
            public void itemStateChanged(ItemEvent e) {
                _groupTableModel.setDisplayCountColumn(e.getStateChange() == ItemEvent.SELECTED);
                _groupTableModel.groupAndRefresh();
                _groupTableModel.fireTableStructureChanged();
                _table.expandAll();
            }
        });
        showCountColumn.setSelected(_groupTableModel.isDisplayCountColumn());

        checkBoxPanel.add(singleLevel);
        checkBoxPanel.add(showGroupColumns);
        checkBoxPanel.add(showCountColumn);
        return checkBoxPanel;
    }

    public Component getDemoPanel() {
        JideSplitPane panel = new JideSplitPane(JideSplitPane.VERTICAL_SPLIT);

        final Object[] columnNames = {"ID", "Time", "Source", "Destination", "Description"};
        final Object[][] data = {
                {1, "12:00", "A", "X", "desciptions"},
                {2, "1:00", "B", "Z", "desciptions"},
                {1, "1:00", "A", "Y", "desciptions"},
                {3, "12:00", "A", "Y", "desciptions"},
                {2, "1:00", "A", "Z", "desciptions"},
                {2, "1:05", "A", "Z", "desciptions"},
                {6, "1:00", "B", "Y", "desciptions"},
                {3, "1:00", "A", "Y", "desciptions"},
                {3, "1:05", "C", "X", "desciptions"},
        };

        _tableModel = new StyledDefaultTableModel(data, columnNames);

//        for (int i = 0; i < 1000; i++) {
//            for (int j = 0; j < data.length; j++) {
//                Object[] objects = data[j];
//                objects[0] = (int) (Math.random() * 100);
//                _tableModel.addRow(objects);
//            }
//        }

        TableRowFilter filterForRow = new AbstractTableRowFilter() {
            public boolean isValueFiltered(Object value) {
                // value will be an instance of Row
                return false;
            }
        };

        QuickTableFilterField beforeField = new QuickTableFilterField(_tableModel);

        _groupTableModel = new DefaultGroupTableModel(beforeField.getDisplayTableModel());
        _groupTableModel.addGroupColumn(2);
        _groupTableModel.addGroupColumn(3);
        _groupTableModel.groupAndRefresh();

        QuickTableFilterField afterField = new QuickTableFilterField(_groupTableModel);

        afterField.getDisplayTableModel().addFilter(filterForRow);

        _table = new GroupTable(afterField.getDisplayTableModel());
        AutoFilterTableHeader header = new AutoFilterTableHeader(_table);
        header.setAutoFilterEnabled(true);
        _table.setTableHeader(header);
        _table.setRowHeight(18);
        _table.setOptimized(true);
        _table.setShowLeafNodeTreeLines(true);

        _table.expandAll();

        panel.add(new JScrollPane(_table));
        TableModelMonitor.monitor(_table);

        JPanel tableModelPanel = new JPanel(new BorderLayout(6, 6));
        final SortableTable table = new SortableTable(_tableModel);
        tableModelPanel.add(new JScrollPane(table));
        ButtonPanel buttonPanel = new ButtonPanel();
        JButton delete = new JButton("Delete");
        delete.addActionListener(new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                int[] rows = table.getSelectedRows();
                for (int i = rows.length - 1; i >= 0; i--) {
                    int row = rows[i];
                    _tableModel.removeRow(row);
                }
            }
        });
        JButton insert = new JButton("Insert");
        insert.addActionListener(new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                Vector rowData = _tableModel.getDataVector();
                int index = table.getSelectedRow();
                if (index != -1) {
                    Vector v = (Vector) rowData.get(index);
                    Vector clone = new Vector();
                    for (int i = 0; i < v.size(); i++) {
                        if (i == 0) {
                            clone.add((int) (Math.random() * 10));
                        }
                        else {
                            clone.add("" + v.get(i));
                        }
                    }
                    _tableModel.insertRow(index, clone);
                }
                else {
                    Vector clone = new Vector();
                    for (int i = 0; i < columnNames.length; i++) {
                        if (i == 0) {
                            clone.add((int) (Math.random() * 10));
                        }
                        else {
                            clone.add(data[0][i]);
                        }
                    }
                    _tableModel.addRow(clone);
                }
            }
        });
        buttonPanel.add(insert);
        buttonPanel.add(delete);
        JPanel fieldPanel = new JPanel(new GridLayout(2, 1, 4, 4));
        fieldPanel.add(createTitledComponent("Before Grouping: ", "This filter field filters before the group table model", beforeField));
        fieldPanel.add(createTitledComponent("After Grouping: ", "This filter field filters after the group table model", afterField));

        tableModelPanel.add(fieldPanel, BorderLayout.BEFORE_FIRST_LINE);
        tableModelPanel.add(buttonPanel, BorderLayout.AFTER_LAST_LINE);
        panel.add(tableModelPanel);

        return panel;
    }

    private JPanel createTitledComponent(String label, String toolTip, JComponent component) {
        JPanel panel = new JPanel();
        panel.setLayout(new JideBoxLayout(panel, JideBoxLayout.Y_AXIS, 3));
        component.setToolTipText(toolTip);
        panel.add(new JLabel(label));
        panel.add(component);
        panel.add(Box.createGlue(), JideBoxLayout.VARY);
        return panel;
    }

    public String getDemoFolder() {
        return "G16. GroupTable";
    }

    public static void main(String[] args) {
        ObjectConverterManager.initDefaultConverter();
        ObjectConverterManager.registerConverter(DefaultGroupRow.class, new ObjectConverter() {
            public String toString(Object object, ConverterContext context) {
                if (object instanceof DefaultGroupRow) {
                    DefaultGroupRow row = (DefaultGroupRow) object;
                    StringBuffer buf = new StringBuffer();
                    DefaultGroupTableModel defaultGroupTableModel = (DefaultGroupTableModel) row.getTreeTableModel();
                    for (int i = 0; i < row.getNumberOfConditions(); i++) {
                        buf.append(" ");

                        if (defaultGroupTableModel.isSingleLevelGrouping() || i == row.getNumberOfConditions() - 1) {
                            buf.append(defaultGroupTableModel.getActualModel().getColumnName(row.getConditionColumn(i)));
                            buf.append(": ");
                            buf.append(row.getConditionValue(i));
                        }
                    }
                    int allVisibleChildrenCount = row.getAllVisibleChildrenCount();
                    buf.append(" (").append(allVisibleChildrenCount).append(" items)");
                    return buf.toString();
                }
                return null;
            }

            public boolean supportToString(Object object, ConverterContext context) {
                return true;
            }

            public Object fromString(String string, ConverterContext context) {
                return null;
            }

            public boolean supportFromString(String string, ConverterContext context) {
                return false;
            }
        });
        LookAndFeelFactory.installDefaultLookAndFeelAndExtension();
        showAsFrame(new AdvancedGroupTableDemo());
    }

    private static class StyledDefaultTableModel extends DefaultTableModel implements StyleModel {
        public StyledDefaultTableModel(Object[][] data, Object[] columnNames) {
            super(data, columnNames);
        }

        public boolean isCellEditable(int row, int column) {
            return true;
        }

        public CellStyle getCellStyleAt(int rowIndex, int columnIndex) {
            if (new Integer(2).equals(getValueAt(rowIndex, columnIndex))) {
                CellStyle cs = new CellStyle();
                cs.setForeground(Color.RED);
                return cs;
            }
            return null;
        }

        public boolean isCellStyleOn() {
            return true;
        }
    }
}
JIDE Software Technical Support Team
JIDE Support
Site Admin
 
Posts: 37219
Joined: Sun Sep 14, 2003 10:49 am

Postby grmo » Wed Aug 15, 2007 4:01 am

I observe the selection is preserved by the demo you give too. However if you make the following changes:

* Add "_table.sortColumn(0);" to force a sort
* Comment out "_table.setOptimized(true);"
* Add "System.out.println("Column selection now: " + java.util.Arrays.toString(_table.getSelectedColumns()));" to the start of the insert listener's actionPerformed to record the column selection before each insert

then start the demo, select a cell in, say, column 1, then press insert twice. On the first insert you will see on stdout

Column selection now: [1]

and on the second insert you will see

Column selection now: []

Now uncomment the call to setOptimized and you will observe the selection is preserved, ie. stdout will show

Column selection now: [1]
Column selection now: [1]

Somehow the optimization is preserving column selection. Setting the optimization to true actually solves all of the selection problems I've seen so far! (Both the column selection problems and the occasional row selection problem.)
grmo
 
Posts: 33
Joined: Fri Jul 27, 2007 1:44 am

Postby JIDE Support » Wed Aug 15, 2007 8:17 am

If you turned optimized off, we will fire tableDataChanged event when adding a row. This event will cause the selection lost. Then the only way to restore the selection is to use TableUtils methods. BTW, why do you turn optimized off?

Thanks,
JIDE Software Technical Support Team
JIDE Support
Site Admin
 
Posts: 37219
Joined: Sun Sep 14, 2003 10:49 am

Postby grmo » Wed Aug 15, 2007 8:17 am

Since my last post I have found some problems with using the optimized sort. Although it says it should work if the elements are already sorted and the underlying model fires the right events, I can't get it to work in all cases. For example, this "unit test" below fails for the optimized sort but not for the unoptimized sort, using a DefaultTableModel from Swing (which must surely fire the right events?). You can also see the failure to order the elements correctly from looking at the table displayed by the tests.

What's going wrong here?

PS. Optimization is off by default so I don't explicitly turn if off, I wasn't even aware of it till I stumbled on it from investigating the selection problem solved by your demo above. The JavaDoc says "Default is false. The performance will be not as good but it's more stable."

Code: Select all
import javax.swing.*;
import javax.swing.table.*;

import junit.framework.*;

import com.jidesoft.grid.*;

public class SortableTableOptimizedTest extends TestCase
{
    private DefaultTableModel myTableModel;
    private SortableTable myTable;

    public void testOrderUnoptimized() throws InterruptedException
    {
        update();
        Thread.sleep(5000);
        assertOrder();
    }
   
    public void testOrderOptimized() throws InterruptedException
    {
        myTable.setOptimized(true);
        Thread.sleep(1000);
        update();
        Thread.sleep(5000);
        assertOrder();
    }

    private void update()
    {
        myTableModel.setValueAt(0, 2, 0);
    }

    private void assertOrder()
    {
        SortableTableModel sortableTableModel = (SortableTableModel) myTable.getModel();
        for (int r = 0; r < sortableTableModel.getRowCount() - 1; r++)
        {
            int current = (Integer) sortableTableModel.getValueAt(r, 0);
            int next = (Integer) sortableTableModel.getValueAt(r+1, 0);
            assertTrue("row = " + r + ", current = " + current + ", next = " + next, current <= next);   
        }
    }

    protected void setUp()
    {
        myTableModel = new DefaultTableModel(new Integer[][] {{1},{2},{3}}, new String[] {"col0"});
        myTable = new SortableTable(myTableModel);
        myTable.sortColumn(0);
       
        JFrame myFrame = new JFrame();
        myFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        myFrame.getContentPane().add(new JScrollPane(myTable));
        myFrame.setVisible(true);
        myFrame.pack();
    }
}
grmo
 
Posts: 33
Joined: Fri Jul 27, 2007 1:44 am

Postby JIDE Support » Wed Aug 15, 2007 8:39 am

I will fix it and let you know when the patch is available. It looks like an optimizatoin we did is not correct.

Thanks,
JIDE Software Technical Support Team
JIDE Support
Site Admin
 
Posts: 37219
Joined: Sun Sep 14, 2003 10:49 am

Postby JIDE Support » Tue Aug 28, 2007 9:20 am

Just so you know, this is fixed in 2.1.3 release.

Thanks,
JIDE Software Technical Support Team
JIDE Support
Site Admin
 
Posts: 37219
Joined: Sun Sep 14, 2003 10:49 am


Return to Product Suggestions

Who is online

Users browsing this forum: No registered users and 12 guests

cron