content: /*****************************************************************
   *   Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you under the Apache License, Version 2.0 (the
   *  "License"); you may not use this file except in compliance
   *  with the License.  You may obtain a copy of the License at
   *
   *    https://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing,
   *  software distributed under the License is distributed on an
   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   *  KIND, either express or implied.  See the License for the
   *  specific language governing permissions and limitations
   *  under the License.
   ****************************************************************/
  
  package org.apache.cayenne.modeler.util;
  
  import org.apache.cayenne.modeler.Application;
  import org.apache.cayenne.modeler.pref.TableColumnPreferences;
  
  import javax.swing.BorderFactory;
  import javax.swing.DefaultCellEditor;
  import javax.swing.DefaultListSelectionModel;
  import javax.swing.JTable;
  import javax.swing.JTextField;
  import javax.swing.UIManager;
  import javax.swing.event.ChangeEvent;
  import javax.swing.event.TableModelEvent;
  import javax.swing.table.DefaultTableCellRenderer;
  import javax.swing.table.DefaultTableModel;
  import javax.swing.table.JTableHeader;
  import javax.swing.table.TableCellEditor;
  import javax.swing.table.TableColumn;
  import javax.swing.table.TableColumnModel;
  import javax.swing.table.TableModel;
  import javax.swing.text.JTextComponent;
  import java.awt.Color;
  import java.awt.Component;
  import java.util.EventObject;
  
  /**
   * Common superclass of tables used in Cayenne. Contains some common configuration
   * settings and utility methods.
   *
   */
  public class CayenneTable extends JTable {
  
      private SortButtonRenderer renderer = new SortButtonRenderer();
      protected TableHeaderListener tableHeaderListener;
      private boolean isColumnWidthChanged;
  
      public CayenneTable() {
          super();
          this.setRowHeight(25);
          this.setRowMargin(3);
          JTableHeader header = getTableHeader();
          tableHeaderListener = new TableHeaderListener(header, renderer);
          header.addMouseListener(tableHeaderListener);
          setSelectionModel(new CayenneListSelectionModel());
      }
  
      @Override
      public void setModel(TableModel dataModel) {
          super.setModel(dataModel);
  
          if (!(dataModel instanceof DefaultTableModel)) {
              TableColumnModel model = getColumnModel();
  
              for (int i = 0; i < getColumnCount(); i++) {
                  model.getColumn(i).setHeaderRenderer(renderer);
              }
          }
      }
  
      @Override
      protected void createDefaultEditors() {
          super.createDefaultEditors();
  
          JTextField textField = new JTextField(20);
          final DefaultCellEditor textEditor = Application.getWidgetFactory().createCellEditor(textField);
          textEditor.setClickCountToStart(1);
  
          setDefaultEditor(Object.class, textEditor);
          setDefaultEditor(String.class, textEditor);
      }
  
      /**
       * @return CayenneTableModel, or null if model can't be casted to CayenneTableModel.
       */
      public CayenneTableModel getCayenneModel() {
          TableModel model = getModel();
          if(model instanceof CayenneTableModel) {
              return (CayenneTableModel) model;
          }
          return null;
      }
  
      /**
       * Cancels editing of any cells that maybe currently edited. This method should be
       * called before updating any selections.
       */
      public void cancelEditing() {
          editingCanceled(new ChangeEvent(this));
      }
  
      public void select(Object row) {
          if (row == null) {
              return;
          }
  
          CayenneTableModel model = getCayenneModel();
          int ind = model.getObjectList().indexOf(row);
  
          if (ind >= 0) {
              getSelectionModel().setSelectionInterval(ind, ind);
          }
      }
  
      public void select(int index) {
  
          CayenneTableModel model = getCayenneModel();
          if (index >= model.getObjectList().size()) {
              index = model.getObjectList().size() - 1;
          }
  
          if (index >= 0) {
              getSelectionModel().setSelectionInterval(index, index);
          }
      }
  
      /**
       * Selects multiple rows at once. Fires not more than only one ListSelectionEvent
       */
      public void select(int[] rows) {
          ((CayenneListSelectionModel) getSelectionModel()).setSelection(rows);
      }
  
      public JTextComponent getSelectedTextComponent() {
          int row = getSelectedRow();
          int column = getSelectedColumn();
          if (row < 0 || column < 0) {
              return null;
          }
  
          TableCellEditor editor = this.getCellEditor(row, column);
          if (editor instanceof DefaultCellEditor) {
              Component comp = ((DefaultCellEditor) editor).getComponent();
              if (comp instanceof JTextComponent) {
                  return (JTextComponent) comp;
              }
          }
          return null;
      }
  
      @Override
      public void tableChanged(TableModelEvent e) {
          cancelEditing();
          super.tableChanged(e);
      }
  
      /**
       * ListSelectionModel for Cayenne table. Has a method to set multiple rows selection at once.
       */
      class CayenneListSelectionModel extends DefaultListSelectionModel {
  
          boolean fireForbidden = false;
  
          /**
           * Selects selection on multiple rows at once. Fires no more than one
           * ListSelectionEvent
           */
          public void setSelection(int[] rows) {
              // First check if we must do anything at all
              boolean selectionChanged = false;
              for (int row : rows) {
                  if (!isRowSelected(row)) {
                      selectionChanged = true;
                      break;
                  }
              }
  
              if (!selectionChanged) {
                  for (int i = getMinSelectionIndex(); i < getMaxSelectionIndex(); i++) {
                      if (isSelectedIndex(i)) {
                          boolean inNewSelection = false;
                          for (int row : rows) {
                              if (row == i) {
                                  inNewSelection = true;
                                  break;
                              }
                          }
  
                          if (!inNewSelection) {
                              selectionChanged = true;
                              break;
                          }
                      }
                  }
              }
  
              if (!selectionChanged) {
                  return;
              }
  
              fireForbidden = true;
  
              clearSelection();
              for (int row : rows) {
                  if (row >= 0 && row < getRowCount()) {
                      addRowSelectionInterval(row, row);
                  }
              }
  
              fireForbidden = false;
  
              fireValueChanged(getValueIsAdjusting());
          }
  
          @Override
          protected void fireValueChanged(int firstIndex, int lastIndex, boolean isAdjusting) {
              if (!fireForbidden) {
                  super.fireValueChanged(firstIndex, lastIndex, isAdjusting);
              }
          }
      }
  
      public boolean editCellAt(int row, int column, EventObject e) {
  
          boolean result = false;
          if (isCellEditable(row, column)) {
              result = super.editCellAt(row, column, null);
              editorComp.requestFocus();
          }
          return result;
      }
  
      public void changeSelection(final int row, final int column, boolean toggle, boolean extend) {
          super.changeSelection(row, column, toggle, extend);
      }
  
      public void sort(int column, boolean isAscend) {
          tableHeaderListener.sortByDefinedColumn(
                  convertColumnIndexToView(column),
                  column,
                  isAscend);
      }
  
      public void setSortPreferenceSaver(TableColumnPreferences tableColumnPreferences) {
          tableHeaderListener.setPreferences(tableColumnPreferences);
      }
      
      public boolean getColumnWidthChanged() {
          return isColumnWidthChanged;
      }
  
      public void setColumnWidthChanged(boolean widthChanged) {
          isColumnWidthChanged = widthChanged;
      }
  
      /**
       * @since 4.0
       */
      public void setSortable(boolean sortable) {
          renderer.setSortingEnabled(sortable);
      }
  }
  