title: Groupable table header with filter below header
body: <p><strong>Problem</strong></p>
  
  <p>I'd like to create a groupable table header with a filter row below the header.</p>
  
  <p>I guess the straightforward approach would be to include the filter components in the header. The problem is that the components aren't editable.</p>
  
  <p>I searched, but didn't find a good or working approach. The best and working solution I found so far regarding filter components was putting them outside of the table. But when you have a grouped table that just looks and feels ugly. They must be below the header. Putting the filter components in the footer isn't an option either.</p>
  
  <p>I used the groupable table header code form <a href="https://stackoverflow.com/questions/21347647/how-to-combine-two-column-headers-in-jtable-in-swings">this thread</a> and added a filter component.</p>
  
  <p>The problem is now that when I click on the components, I can't access them. Instead the row sorting is triggered. Even adding a mouse listener to the textfield doesn't help.</p>
  
  <p><strong>Question</strong></p>
  
  <p>Does anyone know how to make the textfields in the table header editable? Or does anyone have a better approach about placing a table filter below the table header?</p>
  
  <p><strong>Code</strong></p>
  
  <p>The code so far:</p>
  
  <p>FilterHeader.java</p>
  
  <pre><code>import java.awt.BorderLayout;
  import java.awt.Color;
  import java.awt.Dimension;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  
  import javax.swing.JComboBox;
  import javax.swing.JLabel;
  import javax.swing.JPanel;
  import javax.swing.JTable;
  import javax.swing.JTextField;
  import javax.swing.UIManager;
  import javax.swing.border.EmptyBorder;
  
  public class FilterHeader extends JPanel {
  
      public FilterHeader( JTable table, Object value, int columnIndex) {
  
          setLayout( new BorderLayout());
  
          // header 
          JLabel header = new JLabel();
          header.setForeground(table.getTableHeader().getForeground());
          header.setBackground(table.getTableHeader().getBackground());
          header.setFont(table.getTableHeader().getFont());
  
          header.setHorizontalAlignment(JLabel.CENTER);
          header.setText(value.toString());
          header.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
  
          add( header, BorderLayout.CENTER);
  
          // append filter components to header
          if( columnIndex == 3) {
  
              JComboBox cb = new JComboBox();
              cb.setBackground(Color.yellow);
              cb.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
              cb.setBorder(new EmptyBorder(0, 0, 0, 0));
              cb.setForeground(table.getTableHeader().getForeground());
              cb.setPreferredSize(new Dimension(0,table.getRowHeight() + 4));
  
              add( cb, BorderLayout.SOUTH);
  
          } else {
  
              JTextField tf = new JTextField( "enter filtertext");
              tf.setBackground(Color.yellow);
              tf.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
              tf.setForeground(table.getTableHeader().getForeground());
              tf.setHorizontalAlignment(JLabel.CENTER);
  
              add( tf, BorderLayout.SOUTH);
  
              tf.addMouseListener(new MouseAdapter() {
  
                   @Override
                   public void  mouseClicked(MouseEvent e) {
                       System.out.println("textfield clicked"); // doesn't work
                   }
               });
          }
  
  
      }
  
  }
  </code></pre>
  
  <p>ColumnGroup.java</p>
  
  <pre><code>import java.awt.Component;
  import java.awt.Dimension;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  
  import javax.swing.JTable;
  import javax.swing.table.TableCellRenderer;
  import javax.swing.table.TableColumn;
  
  public class ColumnGroup {
  
      protected TableCellRenderer renderer;
  
      protected List&lt;TableColumn&gt; columns;
      protected List&lt;ColumnGroup&gt; groups;
  
      protected String text;
      protected int margin = 0;
  
      public ColumnGroup(String text) {
          this(text, null);
      }
  
      public ColumnGroup(String text, TableCellRenderer renderer) {
          this.text = text;
          this.renderer = renderer;
          this.columns = new ArrayList&lt;TableColumn&gt;();
          this.groups = new ArrayList&lt;ColumnGroup&gt;();
      }
  
      public void add(TableColumn column) {
          columns.add(column);
      }
  
      public void add(ColumnGroup group) {
          groups.add(group);
      }
  
      /**
       * @param column
       *            TableColumn
       */
      public List&lt;ColumnGroup&gt; getColumnGroups(TableColumn column) {
          if (!contains(column)) {
              return Collections.emptyList();
          }
          List&lt;ColumnGroup&gt; result = new ArrayList&lt;ColumnGroup&gt;();
          result.add(this);
          if (columns.contains(column)) {
              return result;
          }
          for (ColumnGroup columnGroup : groups) {
              result.addAll(columnGroup.getColumnGroups(column));
          }
          return result;
      }
  
      private boolean contains(TableColumn column) {
          if (columns.contains(column)) {
              return true;
          }
          for (ColumnGroup group : groups) {
              if (group.contains(column)) {
                  return true;
              }
          }
          return false;
      }
  
      public TableCellRenderer getHeaderRenderer() {
          return renderer;
      }
  
      public void setHeaderRenderer(TableCellRenderer renderer) {
          this.renderer = renderer;
      }
  
      public String getHeaderValue() {
          return text;
      }
  
      public Dimension getSize(JTable table) {
          TableCellRenderer renderer = this.renderer;
          if (renderer == null) {
              renderer = table.getTableHeader().getDefaultRenderer();
          }
          Component comp = renderer.getTableCellRendererComponent(table, getHeaderValue() == null || getHeaderValue().trim().isEmpty() ? " "
                  : getHeaderValue(), false, false, -1, -1);
          int height = comp.getPreferredSize().height;
          int width = 0;
          for (ColumnGroup columnGroup : groups) {
              width += columnGroup.getSize(table).width;
          }
          for (TableColumn tableColumn : columns) {
              width += tableColumn.getWidth();
              width += margin;
          }
          return new Dimension(width, height);
      }
  
      public void setColumnMargin(int margin) {
          this.margin = margin;
          for (ColumnGroup columnGroup : groups) {
              columnGroup.setColumnMargin(margin);
          }
      }
  
  }
  </code></pre>
  
  <p>GroupableTableHeader.java</p>
  
  <pre><code>import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
  
  import javax.swing.table.JTableHeader;
  import javax.swing.table.TableColumn;
  import javax.swing.table.TableColumnModel;
  
  @SuppressWarnings("serial")
  public class GroupableTableHeader extends JTableHeader {
  
      @SuppressWarnings("unused")
      private static final String uiClassID = "GroupableTableHeaderUI";
  
      protected List&lt;ColumnGroup&gt; columnGroups = new ArrayList&lt;ColumnGroup&gt;();
  
      public GroupableTableHeader(TableColumnModel model) {
          super(model);
          setUI(new GroupableTableHeaderUI());
          setReorderingAllowed(false);
          // setDefaultRenderer(new MultiLineHeaderRenderer());
      }
  
      @Override
      public void updateUI() {
          setUI(new GroupableTableHeaderUI());
      }
  
      @Override
      public void setReorderingAllowed(boolean b) {
          super.setReorderingAllowed(false);
      }
  
      public void addColumnGroup(ColumnGroup g) {
          columnGroups.add(g);
      }
  
      public List&lt;ColumnGroup&gt; getColumnGroups(TableColumn col) {
          for (ColumnGroup group : columnGroups) {
              List&lt;ColumnGroup&gt; groups = group.getColumnGroups(col);
              if (!groups.isEmpty()) {
                  return groups;
              }
          }
          return Collections.emptyList();
      }
  
      public void setColumnMargin() {
          int columnMargin = getColumnModel().getColumnMargin();
          for (ColumnGroup group : columnGroups) {
              group.setColumnMargin(columnMargin);
          }
      }
  
  }
  </code></pre>
  
  <p>GroupableTableHeaderUI.java</p>
  
  <pre><code>import java.awt.Component;
  import java.awt.Dimension;
  import java.awt.Graphics;
  import java.awt.Rectangle;
  import java.util.Enumeration;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import javax.swing.JComponent;
  import javax.swing.JTable;
  import javax.swing.UIManager;
  import javax.swing.plaf.basic.BasicTableHeaderUI;
  import javax.swing.table.DefaultTableCellRenderer;
  import javax.swing.table.TableCellRenderer;
  import javax.swing.table.TableColumn;
  import javax.swing.table.TableColumnModel;
  
  public class GroupableTableHeaderUI extends BasicTableHeaderUI {
  
      protected GroupableTableHeader getHeader() {
          return (GroupableTableHeader) header;
      }
  
      @Override
      public void paint(Graphics g, JComponent c) {
          Rectangle clipBounds = g.getClipBounds();
          if (header.getColumnModel().getColumnCount() == 0) {
              return;
          }
          int column = 0;
          Dimension size = header.getSize();
          Rectangle cellRect = new Rectangle(0, 0, size.width, size.height);
          Map&lt;ColumnGroup, Rectangle&gt; groupSizeMap = new HashMap&lt;ColumnGroup, Rectangle&gt;();
  
          for (Enumeration&lt;TableColumn&gt; enumeration = header.getColumnModel().getColumns(); enumeration.hasMoreElements();) {
              cellRect.height = size.height;
              cellRect.y = 0;
              TableColumn aColumn = enumeration.nextElement();
              List&lt;ColumnGroup&gt; groups = getHeader().getColumnGroups(aColumn);
              int groupHeight = 0;
              for (ColumnGroup group : groups) {
                  Rectangle groupRect = groupSizeMap.get(group);
                  if (groupRect == null) {
                      groupRect = new Rectangle(cellRect);
                      Dimension d = group.getSize(header.getTable());
                      groupRect.width = d.width;
                      groupRect.height = d.height;
                      groupSizeMap.put(group, groupRect);
                  }
                  paintCell(g, groupRect, group);
                  groupHeight += groupRect.height;
                  cellRect.height = size.height - groupHeight;
                  cellRect.y = groupHeight;
              }
              cellRect.width = aColumn.getWidth();
              if (cellRect.intersects(clipBounds)) {
                  paintCell(g, cellRect, column);
              }
              cellRect.x += cellRect.width;
              column++;
          }
      }
  
      private void paintCell(Graphics g, Rectangle cellRect, int columnIndex) {
          TableColumn aColumn = header.getColumnModel().getColumn(columnIndex);
          TableCellRenderer renderer = aColumn.getHeaderRenderer();
          if (renderer == null) {
  
              // original
              renderer = getHeader().getDefaultRenderer();
  
              // modified
              renderer = new DefaultTableCellRenderer() {
                  public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
  
                      FilterHeader header = new FilterHeader( table, value, column);
                      return header;
                  }
  
              };
          }
          Component c = renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false, false,
                  -1, columnIndex);
  
          c.setBackground(UIManager.getColor("control"));
  
          rendererPane.paintComponent(g, c, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
      }
  
      private void paintCell(Graphics g, Rectangle cellRect, ColumnGroup cGroup) {
          TableCellRenderer renderer = cGroup.getHeaderRenderer();
          if (renderer == null) {
              renderer = getHeader().getDefaultRenderer();
          }
  
          Component component = renderer.getTableCellRendererComponent(header.getTable(), cGroup.getHeaderValue(), false,
                  false, -1, -1);
          rendererPane
                  .paintComponent(g, component, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true);
      }
  
      private int getHeaderHeight() {
          int headerHeight = 0;
          TableColumnModel columnModel = header.getColumnModel();
          for (int column = 0; column &lt; columnModel.getColumnCount(); column++) {
              TableColumn aColumn = columnModel.getColumn(column);
              TableCellRenderer renderer = aColumn.getHeaderRenderer();
              if (renderer == null) {
                  renderer = getHeader().getDefaultRenderer();
              }
  
              Component comp = renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false,
                      false, -1, column);
              int cHeight = comp.getPreferredSize().height;
              List&lt;ColumnGroup&gt; groups = getHeader().getColumnGroups(aColumn);
              for (ColumnGroup group : groups) {
                  cHeight += group.getSize(header.getTable()).height;
              }
              headerHeight = Math.max(headerHeight, cHeight);
          }
          return headerHeight;
      }
  
      @Override
      public Dimension getPreferredSize(JComponent c) {
          int width = 0;
          for (Enumeration&lt;TableColumn&gt; enumeration = header.getColumnModel().getColumns(); enumeration.hasMoreElements();) {
              TableColumn aColumn = enumeration.nextElement();
              width += aColumn.getPreferredWidth();
          }
          return createHeaderSize(width);
      }
  
      private Dimension createHeaderSize(int width) {
          TableColumnModel columnModel = header.getColumnModel();
          width += columnModel.getColumnMargin() * columnModel.getColumnCount();
          if (width &gt; Integer.MAX_VALUE) {
              width = Integer.MAX_VALUE;
          }
          return new Dimension(width, getHeaderHeight());
      }
  
  }
  </code></pre>
  
  <p>GroupableHeaderExample.java</p>
  
  <pre><code>import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
  
  import javax.swing.JFrame;
  import javax.swing.JScrollPane;
  import javax.swing.JTable;
  import javax.swing.RowSorter;
  import javax.swing.table.DefaultTableModel;
  import javax.swing.table.JTableHeader;
  import javax.swing.table.TableColumnModel;
  import javax.swing.table.TableModel;
  import javax.swing.table.TableRowSorter;
  
  // original from https://stackoverflow.com/questions/21347647/how-to-combine-two-column-headers-in-jtable-in-swings
  public class GroupableHeaderExample extends JFrame {
  
        GroupableHeaderExample() {
          super( "Groupable Header Example" );
  
          DefaultTableModel dm = new DefaultTableModel();
          dm.setDataVector(new Object[][]{
            {"1","a","b","c","d","e"},
            {"2","f","g","h","i","j"},
            {"3","k","l","m","n","o"},
            {"4","p","q","r","s","t"}
            },
          new Object[]{"SNo.","1","2","Native","2","3"});
  
          JTable table = new JTable( dm ) {
            protected JTableHeader createDefaultTableHeader() {
                return new GroupableTableHeader(columnModel);
            }
          };
  
  
          TableColumnModel cm = table.getColumnModel();
          ColumnGroup g_name = new ColumnGroup("Name");
          g_name.add(cm.getColumn(1));
          g_name.add(cm.getColumn(2));
          ColumnGroup g_lang = new ColumnGroup("Language");
          g_lang.add(cm.getColumn(3));
          ColumnGroup g_other = new ColumnGroup("Others");
          g_other.add(cm.getColumn(4));
          g_other.add(cm.getColumn(5));
          g_lang.add(g_other);
  
          GroupableTableHeader header = (GroupableTableHeader)table.getTableHeader();
          header.addColumnGroup(g_name);
          header.addColumnGroup(g_lang);
          JScrollPane scroll = new JScrollPane( table );
          getContentPane().add( scroll );
          setSize( 400, 120 );   
  
          // allow sorting
          RowSorter&lt;TableModel&gt; sorter = new TableRowSorter&lt;TableModel&gt;(dm);
          table.setRowSorter(sorter);
  
        }
  
        public static void main(String[] args) {
          GroupableHeaderExample frame = new GroupableHeaderExample();
          frame.setSize(1024,768);
          frame.addWindowListener( new WindowAdapter() {
            public void windowClosing( WindowEvent e ) {
        System.exit(0);
            }
          });
          frame.setVisible(true);
        }
      }
  </code></pre>
  
  <p>And a screenshot:</p>
  
  <p><img src="https://i.stack.imgur.com/Zd0Xw.png" alt="enter image description here"></p>
  
  <p>Thank you very much for the help!</p>