Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Highlights subString in the TableCell(s) which is using for JTable filetering

how is possible to synchonize text typed into JTextField (then by DocumentListener passed String to the RowFilter) with TableCell if contains same as String value in JTextField/Document,

and then highlights (meaning f.e. change text Color.Red) for identical text in synchronized TableCell(s)

I know that by usage of some hacks is that possible by using

1/ by using getTableCellRendererComponent

2/ by using prepareRenderer

is there another and maybe correct way(s)

little bit modified code from JTable tutorial

import java.awt.*;
import java.util.regex.PatternSyntaxException;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

public class TableFilterSorter extends JPanel {

    private boolean DEBUG = false;
    private static final long serialVersionUID = 1L;

    public TableFilterSorter() {
        super(new BorderLayout(5, 5));
        final JTextField filterCpText = new JTextField();
        filterCpText.setFont(new Font("Serif", Font.BOLD, 28));
        filterCpText.setForeground(Color.BLUE);
        filterCpText.setBackground(Color.LIGHT_GRAY);
        JPanel filterCpPanel = new JPanel();
        filterCpPanel.setLayout(new BorderLayout(5, 5));
        filterCpPanel.setBorder(BorderFactory.createEmptyBorder(1, 1, 1, 1));
        filterCpPanel.setBackground(Color.LIGHT_GRAY);
        filterCpPanel.setPreferredSize(new Dimension(300, 30));
        filterCpPanel.add(filterCpText, BorderLayout.CENTER);
        add(filterCpPanel, BorderLayout.NORTH);
        final JTable table = new JTable(new MyTableModel());
        table.setPreferredScrollableViewportSize(new Dimension(500, 160));
        table.setFillsViewportHeight(true);
        JScrollPane scrollPane = new JScrollPane(table);
        add(scrollPane, BorderLayout.CENTER);
        TableModel myTableModel = table.getModel();
        final TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(myTableModel);
        table.setRowSorter(sorter);
        filterCpText.getDocument().addDocumentListener(new DocumentListener() {

            private void searchFieldChangedUpdate(DocumentEvent evt) {
                String text = filterCpText.getText();
                if (text.length() == 0) {
                    sorter.setRowFilter(null);
                    table.clearSelection();
                } else {
                    try {
                        sorter.setRowFilter(RowFilter.regexFilter("(?i)" + text, 4));
                        table.clearSelection();
                    } catch (PatternSyntaxException pse) {
                        JOptionPane.showMessageDialog(null, "Bad regex pattern",
                                "Bad regex pattern", JOptionPane.ERROR_MESSAGE);
                    }
                }
            }

            @Override
            public void insertUpdate(DocumentEvent evt) {
                searchFieldChangedUpdate(evt);
            }

            @Override
            public void removeUpdate(DocumentEvent evt) {
                searchFieldChangedUpdate(evt);
            }

            @Override
            public void changedUpdate(DocumentEvent evt) {
                searchFieldChangedUpdate(evt);
            }
        });
    }

    private class MyTableModel extends AbstractTableModel {

        private static final long serialVersionUID = 1L;
        private String[] columnNames = {"First Name", "Last Name", "Sport", "# of Years", "Vegetarian"};
        private Object[][] data = {
            {"Mary", "Campione", "Snowboarding", new Integer(5), false},
            {"Alison", "Huml", "Rowing", new Integer(3), true},
            {"Kathy", "Walrath", "Knitting", new Integer(2), false},
            {"Sharon", "Zakhour", "Speed reading", new Integer(20), true},
            {"Philip", "Milne", "Pool", new Integer(10), false},
            {"Mary", "Campione", "Snowboarding", new Integer(5), false},
            {"Alison", "Huml", "Rowing", new Integer(3), true},
            {"Kathy", "Walrath", "Knitting", new Integer(2), false},
            {"Sharon", "Zakhour", "Speed reading", new Integer(20), true},
            {"Philip", "Milne", "Pool", new Integer(10), false},};

        @Override
        public int getColumnCount() {
            return columnNames.length;
        }

        @Override
        public int getRowCount() {
            return data.length;
        }

        @Override
        public String getColumnName(int col) {
            return columnNames[col];
        }

        @Override
        public Object getValueAt(int row, int col) {
            return data[row][col];
        }

        @Override
        public Class<?> getColumnClass(int c) {
            return getValueAt(0, c).getClass();
        }

        @Override
        public boolean isCellEditable(int row, int col) {
            if (col < 2) {
                return false;
            } else {
                return true;
            }
        }

        @Override
        public void setValueAt(Object value, int row, int col) {
            if (DEBUG) {
                System.out.println("Setting value at " + row + "," + col
                        + " to " + value + " (an instance of " + value.getClass() + ")");
            }
            data[row][col] = value;
            fireTableCellUpdated(row, col);
            if (DEBUG) {
                System.out.println("New value of data:");
                printDebugData();
            }
        }

        private void printDebugData() {
            int numRows = getRowCount();
            int numCols = getColumnCount();
            for (int i = 0; i < numRows; i++) {
                System.out.print("    row " + i + ":");
                for (int j = 0; j < numCols; j++) {
                    System.out.print("  " + data[i][j]);
                }
                System.out.println();
            }
            System.out.println("--------------------------");
        }
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("TableDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        TableFilterSorter newContentPane = new TableFilterSorter();
        newContentPane.setOpaque(true);
        frame.setContentPane(newContentPane);
        frame.setLocation(150, 150);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }
}
like image 206
mKorbel Avatar asked Jun 20 '11 12:06

mKorbel


1 Answers

JXTable can do so via a Highlighter - see swinglabs-demos for an example (MatchingTextHighlighter in the search demo) - there a background highlight is applied by a Painter.

You can do something like that manually somewhere in your renderer. If using a JLabel as renderingComponent, you basically have to find parts of the text that need the background highlight and paint the background for that region (below is a code snippet for finding the areas, c&p from the demos example). Alternatively you might consider using a JTextField as rendering component: the adv is that Highlighter (from javax.swing.text) is built-in, the disadv are the usual issues with text comps as renderers ;-)

/**
 * Finds the rectangles that contain rendered characters that match the
 * pattern.
 * 
 * @param object an optional configuration parameter. This may be null.
 * @param width width of the area to paint.
 * @param height height of the area to paint.
 * @return a <code>List</code> of <code>Rectangle</code>s marking characters
 *         to highlight
 */
protected List<Rectangle> findHighlightAreas(JLabel object, int width,
        int height) {
    String text = object.getText();

    insets = object.getInsets(insets);

    viewR.x = 0 + insets.left;
    viewR.y = 0 + insets.bottom;
    viewR.width = width - insets.right;
    viewR.height = height - insets.top;

    // Reset the text and view rectangle x any y coordinates.
    // These are not set to 0 in SwingUtilities.layoutCompoundLabel
    iconR.x = iconR.y = 0;
    textR.x = textR.y = 0;

    FontMetrics fm = object.getFontMetrics(object.getFont());
    String clippedText = SwingUtilities.layoutCompoundLabel(object, fm,
            text, object.getIcon(), object.getVerticalAlignment(), object
                    .getHorizontalAlignment(), object
                    .getVerticalTextPosition(), object
                    .getHorizontalTextPosition(), viewR, iconR, textR,
            object.getIconTextGap());

    int xOffset = calculateXOffset(object, viewR, textR);

    String textToSearch = clippedText;
    // Check to see if the text will be clipped
    if (!object.getText().equals(clippedText)) {
        // TODO There has to be a better way that assuming ellipses
        // are the last characters of the text
        textToSearch = clippedText.substring(0, clippedText.length() - 3);
    }

    return createHighlightAreas(textToSearch, fm, xOffset, height);
}

/**
 * Creates the rectangles that contain matched characters in the given text.
 * 
 * @param text the text to search
 * @param fm the font metrics of the rendered font
 * @param xOffset the x offset at which text rendering starts
 * @param height the height of painted highlights
 * @return a <code>List</code> of highlight areas to paint
 */
protected List<Rectangle> createHighlightAreas(String text, FontMetrics fm,
        int xOffset, int height) {
    SearchPredicate predicate = (SearchPredicate) getHighlightPredicate();
    Matcher matcher = predicate.getPattern().matcher(text);

    List<Rectangle> highlightAreas = null;
    int startFrom = 0;
    while (startFrom < text.length() && matcher.find(startFrom)) {
        if (highlightAreas == null) {
            highlightAreas = new ArrayList<Rectangle>();
        }

        int start = matcher.start();
        int end = matcher.end();

        if (start == end) {
            // empty matcher will cause infinite loop
            break;
        }

        startFrom = end;

        int highlightx;
        int highlightWidth;

        if (start == 0) {
            // start highlight from the start of the field
            highlightx = textR.x + xOffset;
        } else {
            // Calculate the width of the unhighlighted text to
            // get the start of the highlighted region.
            String strToStart = text.substring(0, start);
            highlightx = textR.x + fm.stringWidth(strToStart) + xOffset;
        }

        // Get the width of the highlighted region
        String highlightText = text.substring(start, end);
        highlightWidth = fm.stringWidth(highlightText);

        highlightAreas.add(new Rectangle(highlightx, 0, highlightWidth,
                height));
    }

    if (highlightAreas == null) {
        highlightAreas = Collections.emptyList();
    } else {
        coalesceHighlightAreas(highlightAreas);
    }
    return highlightAreas;
}

/**
 * Joins highlight rectangles that mark adjacent horizontal areas into
 * single rectangles. This is useful to renderers that vary horizontally,
 * such a horizontal gradient - the gradient will not restart when there are
 * two adjacent highlight areas.
 * 
 * @param highlightAreas a <code>List</code> of <code>Rectangle</code>s.
 */
protected void coalesceHighlightAreas(List<Rectangle> highlightAreas) {
    Collections.sort(highlightAreas, X_AXIS_RECTANGLE_COMPARATOR);

    int i = 0;
    while (i < highlightAreas.size() - 1) {
        Rectangle r1 = highlightAreas.get(i);
        Rectangle r2 = highlightAreas.get(i + 1);

        if (r1.x + r1.width == r2.x) {
            r1.width += r2.width;
            highlightAreas.remove(i + 1);
        } else {
            i++;
        }
    }
}

/**
 * Calculates the x offset of highlights based on component orientation and
 * text direction.
 * 
 * @param component the renderer component
 * @param viewR the view rectangle of the renderer component
 * @param textR the text rectangle of the renderer component
 * @return the number of pixels to offset the highlight from the left edge
 *         of the component
 */
protected int calculateXOffset(JLabel component, Rectangle viewR,
        Rectangle textR) {
    int horizAlignment = component.getHorizontalAlignment();
    boolean leftToRight = component.getComponentOrientation()
            .isLeftToRight();

    if (horizAlignment == SwingConstants.LEFT
            || (horizAlignment == SwingConstants.LEADING && leftToRight)
            || (horizAlignment == SwingConstants.TRAILING && !leftToRight)) {
        return 0;
    } else if (horizAlignment == SwingConstants.RIGHT
            || (horizAlignment == SwingConstants.TRAILING && !leftToRight)
            || (horizAlignment == SwingConstants.LEADING && leftToRight)) {
        return viewR.width - textR.width;
    } else if (horizAlignment == SwingConstants.CENTER) {
        return (viewR.width - textR.width) / 2;
    }
    throw new AssertionError("Unknown horizonal alignment "
            + horizAlignment);
}
like image 162
kleopatra Avatar answered Sep 18 '22 11:09

kleopatra