Java tutorial
/* * Copyright (c) 2008-2016 Haulmont. * * Licensed 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 * * http://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 com.haulmont.cuba.desktop.gui.components; import com.google.common.collect.Lists; import com.haulmont.bali.util.Preconditions; import com.haulmont.chile.core.model.MetaClass; import com.haulmont.chile.core.model.MetaProperty; import com.haulmont.chile.core.model.MetaPropertyPath; import com.haulmont.cuba.core.entity.Entity; import com.haulmont.cuba.core.global.*; import com.haulmont.cuba.desktop.App; import com.haulmont.cuba.desktop.gui.data.AnyTableModelAdapter; import com.haulmont.cuba.desktop.gui.data.RowSorterImpl; import com.haulmont.cuba.desktop.gui.icons.IconResolver; import com.haulmont.cuba.desktop.sys.FontDialog; import com.haulmont.cuba.desktop.sys.layout.MigLayoutHelper; import com.haulmont.cuba.desktop.sys.vcl.Flushable; import com.haulmont.cuba.desktop.sys.vcl.FocusableTable; import com.haulmont.cuba.desktop.sys.vcl.TableFocusManager; import com.haulmont.cuba.desktop.theme.DesktopTheme; import com.haulmont.cuba.gui.ComponentsHelper; import com.haulmont.cuba.gui.components.*; import com.haulmont.cuba.gui.components.Action; import com.haulmont.cuba.gui.components.Formatter; import com.haulmont.cuba.gui.components.Window; import com.haulmont.cuba.gui.components.formatters.CollectionFormatter; import com.haulmont.cuba.gui.data.*; import com.haulmont.cuba.gui.data.impl.CollectionDsActionsNotifier; import com.haulmont.cuba.gui.data.impl.DatasourceImplementation; import com.haulmont.cuba.gui.data.impl.WeakCollectionChangeListener; import com.haulmont.cuba.gui.data.impl.WeakItemPropertyChangeListener; import com.haulmont.cuba.gui.presentations.Presentations; import net.miginfocom.layout.CC; import net.miginfocom.swing.MigLayout; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.jdesktop.swingx.JXTable; import org.jdesktop.swingx.action.BoundAction; import org.jdesktop.swingx.table.ColumnControlButton; import org.jdesktop.swingx.table.TableColumnExt; import org.jdesktop.swingx.table.TableColumnModelExt; import org.perf4j.StopWatch; import org.perf4j.slf4j.Slf4JStopWatch; import javax.annotation.Nullable; import javax.swing.AbstractAction; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import java.awt.*; import java.awt.Component; import java.awt.event.*; import java.util.*; import java.util.List; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkArgument; import static com.haulmont.bali.util.Preconditions.checkNotNullArgument; import static com.haulmont.cuba.desktop.gui.components.DesktopComponentsHelper.convertKeyCombination; import static java.util.Collections.singletonList; public abstract class DesktopAbstractTable<C extends JXTable, E extends Entity> extends DesktopAbstractActionsHolderComponent<C> implements Table<E>, LookupComponent.LookupSelectionChangeNotifier { protected static final int DEFAULT_ROW_MARGIN = 4; protected boolean contextMenuEnabled = true; protected MigLayout layout; protected JPanel panel; protected JPanel topPanel; protected JScrollPane scrollPane; protected AnyTableModelAdapter tableModel; protected CollectionDatasource datasource; protected ButtonsPanel buttonsPanel; protected RowsCount rowsCount; protected Map<Object, Object> aggregationResult; protected Map<Object, Column> columns = new HashMap<>(); protected List<Table.Column> columnsOrder = new ArrayList<>(); protected boolean sortable = true; protected TableSettings tableSettings; protected boolean editable; protected List<StyleProvider> styleProviders; // lazy initialized list protected IconProvider iconProvider; protected Action itemClickAction; protected Action enterPressAction; protected boolean columnsInitialized = false; protected int generatedColumnsCount = 0; protected boolean columnHeaderVisible = true; protected boolean textSelectionEnabled = false; protected boolean showSelection = true; // Indicates that model is being changed. protected boolean isAdjusting = false; protected DesktopTableFieldFactory tableFieldFactory = new DesktopTableFieldFactory(); protected List<MetaPropertyPath> editableColumns = new LinkedList<>(); protected Map<Table.Column, String> requiredColumns = new HashMap<>(); protected Security security = AppBeans.get(Security.NAME); protected boolean columnAdjustRequired = false; protected boolean fontInitialized = false; protected int defaultRowHeight = 24; protected int defaultEditableRowHeight = 28; protected Set<E> selectedItems = Collections.emptySet(); protected Map<String, Printable> printables = new HashMap<>(); protected Map<Entity, Datasource> fieldDatasources = new WeakHashMap<>(); // Manual control for content repaint process protected boolean contentRepaintEnabled = true; protected Document defaultSettings; protected boolean multiLineCells; protected boolean settingsEnabled = true; protected CollectionDatasource.CollectionChangeListener collectionChangeListener; protected CollectionDatasource.CollectionChangeListener securityCollectionChangeListener; protected Datasource.ItemPropertyChangeListener itemPropertyChangeListener; protected CollectionDsActionsNotifier collectionDsActionsNotifier; protected List<LookupSelectionChangeListener> lookupSelectionChangeListeners = new ArrayList<>(); protected DesktopAbstractTable() { shortcutsDelegate.setAllowEnterShortcut(false); } protected void initComponent() { layout = new MigLayout("flowy, fill, insets 0", "", "[min!][fill]"); panel = new JPanel(layout); topPanel = new JPanel(new BorderLayout()); topPanel.setVisible(false); panel.add(topPanel, "growx"); scrollPane = new JScrollPane(impl); impl.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); impl.setFillsViewportHeight(true); panel.add(scrollPane, "grow"); impl.setShowGrid(true); impl.setGridColor(Color.lightGray); impl.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) { handleClickAction(); } } @Override public void mousePressed(MouseEvent e) { showPopup(e); } @Override public void mouseReleased(MouseEvent e) { showPopup(e); } protected void showPopup(MouseEvent e) { if (e.isPopupTrigger() && contextMenuEnabled) { // select row Point p = e.getPoint(); int viewRowIndex = impl.rowAtPoint(p); int rowNumber; if (viewRowIndex >= 0) { rowNumber = impl.convertRowIndexToModel(viewRowIndex); } else { rowNumber = -1; } ListSelectionModel model = impl.getSelectionModel(); if (!model.isSelectedIndex(rowNumber)) { model.setSelectionInterval(rowNumber, rowNumber); } // show popup menu JPopupMenu popupMenu = createPopupMenu(); if (popupMenu.getComponentCount() > 0) { popupMenu.show(e.getComponent(), e.getX(), e.getY()); } } } }); ColumnControlButton columnControlButton = new ColumnControlButton(impl) { @Override protected ColumnVisibilityAction createColumnVisibilityAction(TableColumn column) { ColumnVisibilityAction columnVisibilityAction = super.createColumnVisibilityAction(column); columnVisibilityAction.addPropertyChangeListener(evt -> { if ("SwingSelectedKey".equals(evt.getPropertyName()) && evt.getNewValue() instanceof Boolean) { ColumnVisibilityAction action = (ColumnVisibilityAction) evt.getSource(); String columnName = action.getActionCommand(); boolean collapsed = !((boolean) evt.getNewValue()); Column col = getColumn(columnName); if (col != null) { col.setCollapsed(collapsed); } } }); return columnVisibilityAction; } }; impl.setColumnControl(columnControlButton); impl.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "enter"); impl.getActionMap().put("enter", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (enterPressAction != null) { enterPressAction.actionPerform(DesktopAbstractTable.this); } else { handleClickAction(); } } }); Messages messages = AppBeans.get(Messages.NAME); // localize default column control actions for (Object actionKey : impl.getActionMap().allKeys()) { if ("column.packAll".equals(actionKey)) { BoundAction action = (BoundAction) impl.getActionMap().get(actionKey); action.setName(messages.getMessage(DesktopTable.class, "DesktopTable.packAll")); } else if ("column.packSelected".equals(actionKey)) { BoundAction action = (BoundAction) impl.getActionMap().get(actionKey); action.setName(messages.getMessage(DesktopTable.class, "DesktopTable.packSelected")); } else if ("column.horizontalScroll".equals(actionKey)) { BoundAction action = (BoundAction) impl.getActionMap().get(actionKey); action.setName(messages.getMessage(DesktopTable.class, "DesktopTable.horizontalScroll")); } } // Ability to configure fonts in table // Add action to column control String configureFontsLabel = messages.getMessage(DesktopTable.class, "DesktopTable.configureFontsLabel"); impl.getActionMap().put(ColumnControlButton.COLUMN_CONTROL_MARKER + "fonts", new AbstractAction(configureFontsLabel) { @Override public void actionPerformed(ActionEvent e) { Component rootComponent = SwingUtilities.getRoot(impl); final FontDialog fontDialog = FontDialog.show(rootComponent, impl.getFont()); fontDialog.addWindowListener(new WindowAdapter() { @Override public void windowClosed(WindowEvent e) { Font result = fontDialog.getResult(); if (result != null) { impl.setFont(result); packRows(); } } }); fontDialog.open(); } }); // Ability to reset settings String resetSettingsLabel = messages.getMessage(DesktopTable.class, "DesktopTable.resetSettings"); impl.getActionMap().put(ColumnControlButton.COLUMN_CONTROL_MARKER + "resetSettings", new AbstractAction(resetSettingsLabel) { @Override public void actionPerformed(ActionEvent e) { resetPresentation(); } }); scrollPane.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { if (!columnsInitialized) { adjustColumnHeaders(); } columnsInitialized = true; } }); // init default row height SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (!fontInitialized) { applyFont(impl, impl.getFont()); } } }); } @Override public void setWidth(String width) { super.setWidth(width); CC cc = new CC().grow(); MigLayoutHelper.applyWidth(cc, (int) widthSize.value, widthSize.unit, false); layout.setComponentConstraints(scrollPane, cc); } protected void handleClickAction() { Action action = getItemClickAction(); if (action == null) { action = getEnterAction(); if (action == null) { action = getAction("edit"); if (action == null) { action = getAction("view"); } } } if (action != null && action.isEnabled() && action.isVisible()) { Window window = ComponentsHelper.getWindow(DesktopAbstractTable.this); if (window instanceof Window.Wrapper) { window = ((Window.Wrapper) window).getWrappedWindow(); } if (!(window instanceof Window.Lookup)) { action.actionPerform(DesktopAbstractTable.this); } else { Window.Lookup lookup = (Window.Lookup) window; com.haulmont.cuba.gui.components.Component lookupComponent = lookup.getLookupComponent(); if (lookupComponent != this) { action.actionPerform(DesktopAbstractTable.this); } else if (action.getId().equals(WindowDelegate.LOOKUP_ITEM_CLICK_ACTION_ID)) { action.actionPerform(DesktopAbstractTable.this); } } } } @Override public void setLookupSelectHandler(Runnable selectHandler) { setEnterPressAction( new com.haulmont.cuba.gui.components.AbstractAction(WindowDelegate.LOOKUP_ENTER_PRESSED_ACTION_ID) { @Override public void actionPerform(com.haulmont.cuba.gui.components.Component component) { selectHandler.run(); } }); setItemClickAction( new com.haulmont.cuba.gui.components.AbstractAction(WindowDelegate.LOOKUP_ITEM_CLICK_ACTION_ID) { @Override public void actionPerform(com.haulmont.cuba.gui.components.Component component) { selectHandler.run(); } }); } @Override public Collection getLookupSelectedItems() { return getSelected(); } protected void refreshActionsState() { for (Action action : getActions()) { action.refreshState(); } } protected Action getEnterAction() { for (Action action : getActions()) { KeyCombination kc = action.getShortcutCombination(); if (kc != null) { if ((kc.getModifiers() == null || kc.getModifiers().length == 0) && kc.getKey() == KeyCombination.Key.ENTER) { return action; } } } return null; } protected void readjustColumns() { if (columnAdjustRequired) { return; } this.columnAdjustRequired = true; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { adjustColumnHeaders(); columnAdjustRequired = false; } }); } protected void adjustColumnHeaders() { List<TableColumn> notInited = new LinkedList<>(); int summaryWidth = 0; int componentWidth = impl.getParent().getWidth(); // take into account only visible columns Enumeration<TableColumn> columnEnumeration = impl.getColumnModel().getColumns(); while (columnEnumeration.hasMoreElements()) { TableColumn tableColumn = columnEnumeration.nextElement(); Column column = (Column) tableColumn.getIdentifier(); Integer width = column.getWidth(); if (width != null) { tableColumn.setPreferredWidth(width); tableColumn.setWidth(width); summaryWidth += width; } else { notInited.add(tableColumn); } } if (notInited.size() != impl.getColumnCount()) { impl.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); if (!notInited.isEmpty() && (componentWidth > summaryWidth)) { int defaultWidth = (componentWidth - summaryWidth) / notInited.size(); for (TableColumn column : notInited) { column.setPreferredWidth(Math.max(defaultWidth, column.getWidth())); } } } } protected abstract void initTableModel(CollectionDatasource datasource); @Override public JComponent getComposition() { return panel; } @Override public List<Column> getColumns() { return Collections.unmodifiableList(columnsOrder); } @Override public Column getColumn(String id) { for (Table.Column column : columnsOrder) { if (column.getId().toString().equals(id)) { return column; } } return null; } @Override public void addColumn(Column column) { checkNotNullArgument(column, "Column must be non null"); Object columnId = column.getId(); columns.put(columnId, column); columnsOrder.add(column); if (tableModel != null) { tableModel.addColumn(column); } if (datasource != null && column.isEditable() && columnId instanceof MetaPropertyPath) { if (!editableColumns.contains(columnId)) { editableColumns.add((MetaPropertyPath) columnId); } } setColumnIdentifiers(); refresh(); column.setOwner(this); if (column.getFormatter() == null && columnId instanceof MetaPropertyPath) { MetaProperty metaProperty = ((MetaPropertyPath) columnId).getMetaProperty(); if (Collection.class.isAssignableFrom(metaProperty.getJavaType())) { final Formatter collectionFormatter = new CollectionFormatter(); column.setFormatter(collectionFormatter); } } } @Override public void removeColumn(Column column) { if (column == null) { return; } String name; if (column.getId() instanceof MetaPropertyPath) { MetaPropertyPath metaPropertyPath = (MetaPropertyPath) column.getId(); name = metaPropertyPath.getMetaProperty().getName(); editableColumns.remove(metaPropertyPath); } else { name = column.getId().toString(); } TableColumn tableColumn = null; Iterator<TableColumn> columnIterator = getAllColumns().iterator(); while (columnIterator.hasNext() && (tableColumn == null)) { TableColumn xColumn = columnIterator.next(); Object identifier = xColumn.getIdentifier(); if (identifier instanceof String && identifier.equals(name)) { tableColumn = xColumn; } else if (column.equals(identifier)) { tableColumn = xColumn; } } if (tableColumn != null) { // store old cell editors / renderers Map<Object, TableCellEditor> cellEditors = new HashMap<>(); Map<Object, TableCellRenderer> cellRenderers = new HashMap<>(); for (int i = 0; i < tableModel.getColumnCount(); i++) { Column tableModelColumn = tableModel.getColumn(i); if (tableModel.isGeneratedColumn(tableModelColumn)) { TableColumn oldColumn = getColumn(tableModelColumn); cellEditors.put(tableModelColumn.getId(), oldColumn.getCellEditor()); cellRenderers.put(tableModelColumn.getId(), oldColumn.getCellRenderer()); } } impl.getColumnModel().removeColumn(tableColumn); impl.removeColumn(tableColumn); columns.remove(column.getId()); columnsOrder.remove(column); if (tableModel != null) { tableModel.removeColumn(column); } // reassign column identifiers setColumnIdentifiers(); // reattach old generated columns for (int i = 0; i < tableModel.getColumnCount(); i++) { Column tableModelColumn = tableModel.getColumn(i); if (tableModel.isGeneratedColumn(tableModelColumn)) { TableColumn oldColumn = getColumn(tableModelColumn); if (cellEditors.containsKey(tableModelColumn.getId())) { oldColumn.setCellEditor(cellEditors.get(tableModelColumn.getId())); } if (cellRenderers.containsKey(tableModelColumn.getId())) { oldColumn.setCellRenderer(cellRenderers.get(tableModelColumn.getId())); } } } packRows(); repaintImplIfNeeded(); } column.setOwner(null); } @Override public void setDatasource(final CollectionDatasource datasource) { Preconditions.checkNotNullArgument(datasource, "datasource is null"); final Collection<Object> properties; if (this.columns.isEmpty()) { MetadataTools metadataTools = AppBeans.get(MetadataTools.NAME); MessageTools messageTools = AppBeans.get(MessageTools.NAME); Collection<MetaPropertyPath> paths = datasource.getView() != null ? // if a view is specified - use view properties metadataTools.getViewPropertyPaths(datasource.getView(), datasource.getMetaClass()) : // otherwise use only string properties from meta-class - the temporary solution for KeyValue datasources metadataTools.getPropertyPaths(datasource.getMetaClass()).stream() .filter(mpp -> mpp.getRangeJavaClass().equals(String.class)) .collect(Collectors.toList()); for (MetaPropertyPath metaPropertyPath : paths) { MetaProperty property = metaPropertyPath.getMetaProperty(); if (!property.getRange().getCardinality().isMany() && !metadataTools.isSystem(property)) { Table.Column column = new Table.Column(metaPropertyPath); String propertyName = property.getName(); MetaClass propertyMetaClass = metadataTools.getPropertyEnclosingMetaClass(metaPropertyPath); column.setCaption(messageTools.getPropertyCaption(propertyMetaClass, propertyName)); column.setType(metaPropertyPath.getRangeJavaClass()); Element element = DocumentHelper.createElement("column"); column.setXmlDescriptor(element); addColumn(column); } } } properties = this.columns.keySet(); this.datasource = datasource; collectionChangeListener = e -> { switch (e.getOperation()) { case CLEAR: case REFRESH: fieldDatasources.clear(); break; case UPDATE: case REMOVE: for (Object entity : e.getItems()) { fieldDatasources.remove(entity); } break; } }; //noinspection unchecked datasource.addCollectionChangeListener( new WeakCollectionChangeListener(datasource, collectionChangeListener)); initTableModel(datasource); initChangeListener(); setColumnIdentifiers(); // Major change in table columns behavior #PL-4853 // We need not to recreate all columns on each table change after initTableModel // impl.setAutoCreateColumnsFromModel(false); if (isSortable()) { impl.setRowSorter(new RowSorterImpl(tableModel)); } initSelectionListener(datasource); List<MetaPropertyPath> editableColumns = null; if (isEditable()) { editableColumns = new LinkedList<>(); } MetaClass metaClass = datasource.getMetaClass(); for (final Object property : properties) { final Table.Column column = this.columns.get(property); // todo implement setColumnHeader // final String caption; // if (column != null) { // caption = StringUtils.capitalize(column.getCaption() != null ? column.getCaption() : getColumnCaption(property)); // } else { // caption = StringUtils.capitalize(getColumnCaption(property)); // } // // setColumnHeader(property, caption); if (column != null) { if (column.isCollapsed() && getColumnControlVisible()) { TableColumn tableColumn = getColumn(column); if (tableColumn instanceof TableColumnExt) { ((TableColumnExt) tableColumn).setVisible(false); } } if (editableColumns != null && column.isEditable() && (property instanceof MetaPropertyPath)) { MetaPropertyPath propertyPath = (MetaPropertyPath) property; if (security.isEntityAttrUpdatePermitted(metaClass, property.toString())) { editableColumns.add(propertyPath); } } } } if (editableColumns != null && !editableColumns.isEmpty()) { setEditableColumns(editableColumns); } List<Object> columnsOrder = new ArrayList<>(); for (Table.Column column : this.columnsOrder) { if (column.getId() instanceof MetaPropertyPath) { MetaPropertyPath metaPropertyPath = (MetaPropertyPath) column.getId(); if (security.isEntityAttrReadPermitted(metaClass, metaPropertyPath.toString())) { columnsOrder.add(column.getId()); } } else { columnsOrder.add(column.getId()); } } setVisibleColumns(columnsOrder); if (security.isSpecificPermitted(ShowInfoAction.ACTION_PERMISSION)) { ShowInfoAction action = (ShowInfoAction) getAction(ShowInfoAction.ACTION_ID); if (action == null) { action = new ShowInfoAction(); addAction(action); } action.setDatasource(datasource); } securityCollectionChangeListener = e -> { onDataChange(); packRows(); // #PL-2035, reload selection from ds Set<E> selectedItems1 = getSelected(); if (selectedItems1 == null) { selectedItems1 = Collections.emptySet(); } Set<E> newSelection = new HashSet<>(); for (E entity : selectedItems1) { if (e.getDs().containsItem(entity.getId())) { newSelection.add(entity); } } if (e.getDs().getState() == Datasource.State.VALID && e.getDs().getItem() != null) { if (e.getDs().containsItem(e.getDs().getItem().getId())) { newSelection.add((E) e.getDs().getItem()); } } if (newSelection.isEmpty()) { setSelected((E) null); } else { setSelected(newSelection); } }; // noinspection unchecked datasource.addCollectionChangeListener( new WeakCollectionChangeListener(datasource, securityCollectionChangeListener)); itemPropertyChangeListener = e -> { List<Column> columns1 = getColumns(); boolean find = false; int i = 0; while ((i < columns1.size()) & !find) { Object columnId = columns1.get(i).getId(); if (columnId instanceof MetaPropertyPath) { String propertyName = ((MetaPropertyPath) columnId).getMetaProperty().getName(); if (propertyName.equals(e.getProperty())) { find = true; } } i++; } if (find) { onDataChange(); } packRows(); }; // noinspection unchecked datasource.addItemPropertyChangeListener( new WeakItemPropertyChangeListener(datasource, itemPropertyChangeListener)); if (rowsCount != null) { rowsCount.setDatasource(datasource); } collectionDsActionsNotifier = new CollectionDsActionsNotifier(this); collectionDsActionsNotifier.bind(datasource); for (Action action : getActions()) { action.refreshState(); } if (!canBeSorted(datasource)) setSortable(false); } protected boolean canBeSorted(CollectionDatasource datasource) { //noinspection SimplifiableConditionalExpression return datasource instanceof PropertyDatasource ? ((PropertyDatasource) datasource).getProperty().getRange().isOrdered() : true; } protected String getColumnCaption(Object columnId) { if (columnId instanceof MetaPropertyPath) { return ((MetaPropertyPath) columnId).getMetaProperty().getName(); } else { return columnId.toString(); } } protected void setColumnIdentifiers() { int i = 0; for (TableColumn tableColumn : getAllColumns()) { Column column = columnsOrder.get(i++); if (!(tableColumn.getIdentifier() instanceof Column)) { tableColumn.setIdentifier(column); } } } protected List<TableColumn> getAllColumns() { return ((TableColumnModelExt) impl.getColumnModel()).getColumns(true); } protected void onDataChange() { for (TableColumn tableColumn : getAllColumns()) { TableCellEditor cellEditor = tableColumn.getCellEditor(); if (cellEditor instanceof DesktopTableCellEditor) { ((DesktopTableCellEditor) cellEditor).clearCache(); } } repaintImplIfNeeded(); } protected void initChangeListener() { tableModel.addChangeListener(new AnyTableModelAdapter.DataChangeListener() { private boolean focused = false; private ThreadLocal<Set<E>> selectionBackup = new ThreadLocal<>(); private int scrollRowIndex = -1; @Override public void beforeChange(boolean structureChanged) { if (!structureChanged) return; isAdjusting = true; focused = impl.isFocusOwner(); selectionBackup.set(selectedItems); JViewport viewport = (JViewport) impl.getParent(); Point scrollPoint = viewport.getViewPosition(); scrollRowIndex = impl.rowAtPoint(scrollPoint); } @Override public void afterChange(boolean structureChanged) { if (!structureChanged) return; isAdjusting = false; applySelection(filterSelection(selectionBackup.get())); selectionBackup.remove(); if (focused) { impl.requestFocus(); } else { if (impl.getCellEditor() != null) { if (!impl.getCellEditor().stopCellEditing()) { impl.getCellEditor().cancelCellEditing(); } } } TableFocusManager focusManager = ((FocusableTable) impl).getFocusManager(); if (focusManager != null && scrollRowIndex >= 0) { focusManager.scrollToSelectedRow(scrollRowIndex); } // reassign identifiers for auto created columns setColumnIdentifiers(); } @SuppressWarnings("unchecked") private Set<E> filterSelection(Set<E> selection) { if (selection == null) return Collections.emptySet(); Set<E> newSelection = new HashSet<>(2 * selection.size()); for (Entity item : selection) { if (datasource.containsItem(item.getId())) { newSelection.add((E) datasource.getItem(item.getId())); } } return newSelection; } private void applySelection(Set<E> selection) { int minimalSelectionRowIndex = Integer.MAX_VALUE; if (!selection.isEmpty()) { for (Entity entity : selection) { int rowIndex = tableModel.getRowIndex(entity); if (rowIndex < minimalSelectionRowIndex && rowIndex >= 0) { minimalSelectionRowIndex = rowIndex; } } } setSelected(selection); if (!selection.isEmpty()) { if (focused) { impl.requestFocus(); } else { if (impl.getCellEditor() != null) { if (!impl.getCellEditor().stopCellEditing()) { impl.getCellEditor().cancelCellEditing(); } } } TableFocusManager focusManager = ((FocusableTable) impl).getFocusManager(); if (focusManager != null) { focusManager.scrollToSelectedRow(minimalSelectionRowIndex); } } } @Override public void dataSorted() { clearGeneratedColumnsCache(); packRows(); } }); } protected void clearGeneratedColumnsCache() { for (Column column : columnsOrder) { if (tableModel.isGeneratedColumn(column)) { TableColumn tableColumn = getColumn(column); if (tableColumn != null) { TableCellEditor tableCellEditor = tableColumn.getCellEditor(); if (tableCellEditor instanceof DesktopTableCellEditor) { ((DesktopTableCellEditor) tableCellEditor).clearCache(); } } } } } // Get cell editor for editable column protected TableCellEditor getCellEditor(int row, int column) { TableColumn tableColumn = impl.getColumnModel().getColumn(column); if (tableColumn.getIdentifier() instanceof Column) { Column columnConf = (Column) tableColumn.getIdentifier(); if (editableColumns != null && columnConf.getId() instanceof MetaPropertyPath && editableColumns.contains(columnConf.getId())) { return tableFieldFactory.createEditComponent(row, columnConf); } } return null; } protected class EditableColumnTableCellEditor extends AbstractCellEditor implements TableCellEditor { protected com.haulmont.cuba.gui.components.Component cellComponent; public EditableColumnTableCellEditor(com.haulmont.cuba.gui.components.Component cellComponent) { this.cellComponent = cellComponent; } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { JComponent composition = DesktopComponentsHelper.getComposition(cellComponent); composition.putClientProperty(DesktopTableCellEditor.CELL_EDITOR_TABLE, table); return composition; } @Override public boolean isCellEditable(EventObject e) { return DesktopAbstractTable.this.isEditable() && cellComponent instanceof Editable && ((Editable) cellComponent).isEditable(); } @Override public Object getCellEditorValue() { flush(DesktopComponentsHelper.getComposition(cellComponent)); impl.requestFocus(); if (cellComponent instanceof HasValue) { return ((Field) cellComponent).getValue(); } return null; } protected void flush(Component component) { if (component instanceof Flushable) { ((Flushable) component).flushValue(); } else if (component instanceof java.awt.Container) { for (Component child : ((java.awt.Container) component).getComponents()) { flush(child); } } } } protected class DesktopTableFieldFactory extends AbstractFieldFactory { public TableCellEditor createEditComponent(int row, Column columnConf) { MetaPropertyPath mpp = (MetaPropertyPath) columnConf.getId(); Datasource fieldDatasource = getItemDatasource(tableModel.getItem(row)); // create lookup final com.haulmont.cuba.gui.components.Component columnComponent = createField(fieldDatasource, mpp.getMetaProperty().getName(), columnConf.getXmlDescriptor()); if (columnComponent instanceof Field) { Field cubaField = (Field) columnComponent; if (columnConf.getDescription() != null) { cubaField.setDescription(columnConf.getDescription()); } if (requiredColumns.containsKey(columnConf)) { cubaField.setRequired(true); cubaField.setRequiredMessage(requiredColumns.get(columnConf)); } } if (columnComponent instanceof DesktopCheckBox) { JCheckBox checkboxImpl = (JCheckBox) ((DesktopCheckBox) columnComponent).getComponent(); checkboxImpl.setHorizontalAlignment(SwingConstants.CENTER); } JComponent composition = DesktopComponentsHelper.getComposition(columnComponent); Color color = UIManager.getColor("Table:\"Table.cellRenderer\".background"); composition.setBackground(new Color(color.getRGB())); composition.setForeground(impl.getForeground()); composition.setFont(impl.getFont()); if (columnConf.getWidth() != null) { columnComponent.setWidth(columnConf.getWidth() + "px"); } else { columnComponent.setWidth("100%"); } if (columnComponent instanceof BelongToFrame) { BelongToFrame belongToFrame = (BelongToFrame) columnComponent; if (belongToFrame.getFrame() == null) { belongToFrame.setFrame(getFrame()); } } applyPermissions(columnComponent); columnComponent.setParent(DesktopAbstractTable.this); return new EditableColumnTableCellEditor(columnComponent); } protected void applyPermissions(com.haulmont.cuba.gui.components.Component columnComponent) { if (columnComponent instanceof DatasourceComponent) { DatasourceComponent dsComponent = (DatasourceComponent) columnComponent; MetaPropertyPath propertyPath = dsComponent.getMetaPropertyPath(); if (propertyPath != null) { MetaClass metaClass = dsComponent.getDatasource().getMetaClass(); dsComponent.setEditable(dsComponent.isEditable() && security.isEntityAttrUpdatePermitted(metaClass, propertyPath.toString())); } } } @Override @Nullable protected CollectionDatasource getOptionsDatasource(Datasource fieldDatasource, String propertyId) { if (datasource == null) throw new IllegalStateException("Table datasource is null"); MetaPropertyPath metaPropertyPath = AppBeans.get(MetadataTools.NAME, MetadataTools.class) .resolveMetaPropertyPath(datasource.getMetaClass(), propertyId); Column columnConf = columns.get(metaPropertyPath); final DsContext dsContext = datasource.getDsContext(); String optDsName = columnConf.getXmlDescriptor() != null ? columnConf.getXmlDescriptor().attributeValue("optionsDatasource") : ""; if (StringUtils.isBlank(optDsName)) { return null; } else { CollectionDatasource ds = (CollectionDatasource) dsContext.get(optDsName); if (ds == null) throw new IllegalStateException("Options datasource not found: " + optDsName); return ds; } } } protected void initSelectionListener(final CollectionDatasource datasource) { impl.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override @SuppressWarnings("unchecked") public void valueChanged(ListSelectionEvent e) { if (e.getValueIsAdjusting() || datasource == null) { return; } selectedItems = getSelected(); if (selectedItems.isEmpty()) { Entity dsItem = datasource.getItemIfValid(); datasource.setItem(null); if (dsItem == null) { // in this case item change event will not be generated refreshActionsState(); } } else { // reset selection and select new item if (isMultiSelect()) { datasource.setItem(null); } Entity newItem = selectedItems.iterator().next(); Entity dsItem = datasource.getItemIfValid(); datasource.setItem(newItem); if (Objects.equals(dsItem, newItem)) { // in this case item change event will not be generated refreshActionsState(); } } LookupSelectionChangeEvent selectionChangeEvent = new LookupSelectionChangeEvent( DesktopAbstractTable.this); for (LookupSelectionChangeListener listener : lookupSelectionChangeListeners) { listener.lookupValueChanged(selectionChangeEvent); } } }); } protected void setVisibleColumns(List<?> columnsOrder) { for (TableColumn tableColumn : getAllColumns()) { Column columnIdentifier = (Column) tableColumn.getIdentifier(); if (!columnsOrder.contains(columnIdentifier.getId())) { impl.removeColumn(tableColumn); } } } @Override public boolean isContextMenuEnabled() { return contextMenuEnabled; } @Override public void setContextMenuEnabled(boolean contextMenuEnabled) { this.contextMenuEnabled = contextMenuEnabled; } protected void setEditableColumns(List<MetaPropertyPath> editableColumns) { this.editableColumns.clear(); this.editableColumns.addAll(editableColumns); } @Override public void setRequired(Table.Column column, boolean required, String message) { if (required) requiredColumns.put(column, message); else requiredColumns.remove(column); } @Override public void addValidator(Column column, Field.Validator validator) { } @Override public void addValidator(Field.Validator validator) { } @Override public void setEnterPressAction(Action action) { enterPressAction = action; } @Override public Action getEnterPressAction() { return enterPressAction; } @Override public void setItemClickAction(Action action) { if (itemClickAction != null) { removeAction(itemClickAction); } itemClickAction = action; if (!getActions().contains(action)) { addAction(action); } } @Override public Action getItemClickAction() { return itemClickAction; } @Override public List<Column> getNotCollapsedColumns() { List<Column> visibleColumns = new LinkedList<>(); for (Column column : columnsOrder) { TableColumnExt columnExt = impl.getColumnExt(column); if (columnExt != null && columnExt.isVisible()) { visibleColumns.add(column); } } return visibleColumns; } @Override public void setSortable(boolean sortable) { this.sortable = sortable && canBeSorted(datasource); if (this.sortable) { if (tableModel != null && impl.getRowSorter() == null) { impl.setRowSorter(new RowSorterImpl(tableModel)); } } else { impl.setRowSorter(null); } } @Override public boolean isSortable() { return sortable; } @Override public void setColumnReorderingAllowed(boolean columnReorderingAllowed) { JTableHeader tableHeader = impl.getTableHeader(); tableHeader.setReorderingAllowed(columnReorderingAllowed); } @Override public boolean getColumnReorderingAllowed() { JTableHeader tableHeader = impl.getTableHeader(); return tableHeader.getReorderingAllowed(); } @Override public void setColumnControlVisible(boolean columnCollapsingAllowed) { impl.setColumnControlVisible(columnCollapsingAllowed); } @Override public boolean getColumnControlVisible() { return impl.isColumnControlVisible(); } @Override public void setAggregatable(boolean aggregatable) { } @Override public Map<Object, Object> getAggregationResults() { return Collections.emptyMap(); } @Override public boolean isAggregatable() { return false; } @Override public void setAggregationStyle(AggregationStyle aggregationStyle) { } @Override public AggregationStyle getAggregationStyle() { return null; } @Override public void setShowTotalAggregation(boolean showAggregation) { } @Override public boolean isShowTotalAggregation() { return false; } @Override public void sortBy(Object propertyId, boolean ascending) { if (isSortable()) { for (int i = 0; i < columnsOrder.size(); i++) { Column column = columnsOrder.get(i); if (column.getId().equals(propertyId)) { SortOrder sortOrder = ascending ? SortOrder.ASCENDING : SortOrder.DESCENDING; tableModel.sort(singletonList(new RowSorter.SortKey(i, sortOrder))); onDataChange(); packRows(); break; } } } } @Override public void sort(String columnId, SortDirection direction) { Column column = getColumn(columnId); if (column == null) { throw new IllegalArgumentException("Unable to find column " + columnId); } if (isSortable()) { SortOrder sortOrder = direction == SortDirection.ASCENDING ? SortOrder.ASCENDING : SortOrder.DESCENDING; int columnIndex = columnsOrder.indexOf(column); tableModel.sort(singletonList(new RowSorter.SortKey(columnIndex, sortOrder))); onDataChange(); packRows(); } } @Nullable @Override public SortInfo getSortInfo() { // SortInfo is returned only for sorting triggered from UI List<? extends RowSorter.SortKey> sortKeys = impl.getRowSorter().getSortKeys(); if (CollectionUtils.isNotEmpty(sortKeys)) { RowSorter.SortKey sortKey = sortKeys.get(0); return new SortInfo(columnsOrder.get(sortKey.getColumn()).getId(), SortOrder.ASCENDING.equals(sortKey.getSortOrder())); } return null; } @Override public void selectAll() { if (isMultiSelect()) { if (impl.getRowCount() > 0) { impl.setRowSelectionInterval(0, impl.getModel().getRowCount() - 1); } } } @Override public RowsCount getRowsCount() { return rowsCount; } @Override public void setRowsCount(RowsCount rowsCount) { if (this.rowsCount != null) { topPanel.remove(DesktopComponentsHelper.getComposition(this.rowsCount)); } this.rowsCount = rowsCount; if (rowsCount != null) { topPanel.add(DesktopComponentsHelper.getComposition(rowsCount), BorderLayout.EAST); topPanel.setVisible(true); } } @Override public void setMultiLineCells(boolean multiLineCells) { this.multiLineCells = multiLineCells; } @Override public boolean isMultiLineCells() { return multiLineCells; } @Override public void setRowHeaderMode(RowHeaderMode mode) { } @Override public void setStyleProvider(StyleProvider<? super E> styleProvider) { if (styleProvider != null) { if (this.styleProviders == null) { this.styleProviders = new LinkedList<>(); } else { this.styleProviders.clear(); } this.styleProviders.add(styleProvider); } else { this.styleProviders = null; } refreshCellStyles(); } @Override public void addStyleProvider(StyleProvider<? super E> styleProvider) { if (this.styleProviders == null) { this.styleProviders = new LinkedList<>(); } if (!this.styleProviders.contains(styleProvider)) { this.styleProviders.add(styleProvider); refreshCellStyles(); } } @Override public void removeStyleProvider(StyleProvider<? super E> styleProvider) { if (this.styleProviders != null) { if (this.styleProviders.remove(styleProvider)) { refreshCellStyles(); } } } protected void refreshCellStyles() { for (Column col : columnsOrder) { // generated column handles styles himself if (!tableModel.isGeneratedColumn(col)) { TableColumn tableColumn = getColumn(col); // If column is not hidden by security if (tableColumn != null) { boolean useStyledCells = styleProviders != null && !styleProviders.isEmpty(); tableColumn.setCellRenderer(useStyledCells ? new StylingCellRenderer() : null); } } } } @Override public void setIconProvider(IconProvider iconProvider) { this.iconProvider = iconProvider; // TODO Kozlov: PL-2411. } @Override public int getRowHeaderWidth() { return 0; // TODO Kozlov: PL-2411. } @Override public void setRowHeaderWidth(int width) { // TODO Kozlov: PL-2411. } @Override public Datasource getItemDatasource(Entity item) { Datasource fieldDatasource = fieldDatasources.get(item); if (fieldDatasource == null) { fieldDatasource = new DsBuilder().setAllowCommit(false).setMetaClass(datasource.getMetaClass()) .setRefreshMode(CollectionDatasource.RefreshMode.NEVER).setViewName("_local").buildDatasource(); ((DatasourceImplementation) fieldDatasource).valid(); fieldDatasource.setItem(item); fieldDatasources.put(item, fieldDatasource); } return fieldDatasource; } @Override public void addGeneratedColumn(String columnId, ColumnGenerator<? super E> generator) { addGeneratedColumn(columnId, generator, null); } @Override public void addGeneratedColumn(String columnId, ColumnGenerator<? super E> generator, Class<? extends com.haulmont.cuba.gui.components.Component> componentClass) { checkArgument(columnId != null, "columnId is null"); checkArgument(generator != null, "generator is null for column id '%s'", columnId); addGeneratedColumnInternal(columnId, generator, componentClass); } protected void addGeneratedColumnInternal(String columnId, ColumnGenerator generator, Class<? extends com.haulmont.cuba.gui.components.Component> componentClass) { Column col = getColumn(columnId); Column associatedRuntimeColumn = null; if (col == null) { col = addRuntimeGeneratedColumn(columnId); associatedRuntimeColumn = col; } tableModel.addGeneratedColumn(col); TableColumn tableColumn = getColumn(col); DesktopTableCellEditor cellEditor = new DesktopTableCellEditor(this, generator, componentClass); tableColumn.setCellEditor(cellEditor); tableColumn.setCellRenderer(cellEditor); cellEditor.setAssociatedRuntimeColumn(associatedRuntimeColumn); generatedColumnsCount++; packRows(); repaintImplIfNeeded(); } protected Column addRuntimeGeneratedColumn(String columnId) { // store old cell editors / renderers TableCellEditor[] cellEditors = new TableCellEditor[tableModel.getColumnCount() + 1]; TableCellRenderer[] cellRenderers = new TableCellRenderer[tableModel.getColumnCount() + 1]; for (int i = 0; i < tableModel.getColumnCount(); i++) { Column tableModelColumn = tableModel.getColumn(i); if (tableModel.isGeneratedColumn(tableModelColumn)) { TableColumn tableColumn = getColumn(tableModelColumn); cellEditors[i] = tableColumn.getCellEditor(); cellRenderers[i] = tableColumn.getCellRenderer(); } } // if column with columnId does not exists then add new to model Column col = new Column(columnId, columnId); col.setEditable(false); columns.put(col.getId(), col); // do not touch columnsOrder, it will be synced from table model if (tableModel != null) { tableModel.addColumn(col); } // reassign column identifiers setColumnIdentifiers(); // reattach old generated columns for (int i = 0; i < tableModel.getColumnCount(); i++) { Column tableModelColumn = tableModel.getColumn(i); if (tableModel.isGeneratedColumn(tableModelColumn)) { TableColumn tableColumn = getColumn(tableModelColumn); if (cellEditors[i] != null) { tableColumn.setCellEditor(cellEditors[i]); } if (cellRenderers[i] != null) { tableColumn.setCellRenderer(cellRenderers[i]); } } } return col; } @Override public void removeGeneratedColumn(String columnId) { checkArgument(columnId != null, "columnId is null"); Column col = getColumn(columnId); if (col != null) { boolean oldContentRepaintEnabled = isContentRepaintEnabled(); setContentRepaintEnabled(false); TableColumn targetTableColumn = getColumn(col); TableCellEditor cellEditor = targetTableColumn.getCellEditor(); if (cellEditor instanceof DesktopTableCellEditor) { Column associatedRuntimeColumn = ((DesktopTableCellEditor) cellEditor).getAssociatedRuntimeColumn(); removeColumn(associatedRuntimeColumn); } tableModel.removeGeneratedColumn(col); generatedColumnsCount--; packRows(); repaintImplIfNeeded(); setContentRepaintEnabled(oldContentRepaintEnabled); } } @Override public void addAggregationProperty(String columnId, AggregationInfo.Type type) { } @Override public void addAggregationProperty(Column columnId, AggregationInfo.Type type) { } @Override public void removeAggregationProperty(String columnId) { } @Override public void setColumnCaption(String columnId, String caption) { Column column = getColumn(columnId); if (column == null) { throw new IllegalStateException(String.format("Column with id '%s' not found", columnId)); } setColumnCaption(column, caption); } @Override public void setColumnCaption(Column column, String caption) { checkNotNullArgument(column, "column must be non null"); if (!Objects.equals(column.getCaption(), caption)) { column.setCaption(caption); } TableColumn tableColumn = getColumn(column); // If column is not hidden by security if (tableColumn != null) { tableColumn.setHeaderValue(caption); } } @Override public void setColumnDescription(String columnId, String description) { Column column = getColumn(columnId); if (column == null) { throw new IllegalStateException(String.format("Column with id '%s' not found", columnId)); } setColumnDescription(column, description); } @Override public void setColumnDescription(Column column, String description) { checkNotNullArgument(column, "column must be non null"); if (!Objects.equals(column.getDescription(), description)) { column.setDescription(description); } // not supported for desktop } @Override public void setTextSelectionEnabled(boolean value) { textSelectionEnabled = value; } @Override public boolean isTextSelectionEnabled() { return textSelectionEnabled; } @Override public void setColumnCollapsed(String columnId, boolean collapsed) { Column column = getColumn(columnId); if (column == null) { throw new IllegalStateException(String.format("Column with id '%s' not found", columnId)); } setColumnCollapsed(column, collapsed); } @Override public void setColumnCollapsed(Column column, boolean collapsed) { if (!getColumnControlVisible()) { return; } checkNotNullArgument(column, "column must be non null"); if (column.isCollapsed() != collapsed) { column.setCollapsed(collapsed); } TableColumn tableColumn = getColumn(column); if (tableColumn instanceof TableColumnExt) { ((TableColumnExt) tableColumn).setVisible(!collapsed); } } @Override public void setColumnAlignment(Column column, ColumnAlignment alignment) { checkNotNullArgument(column, "column must be non null"); if (column.getAlignment() != alignment) { column.setAlignment(alignment); } } @Override public void setColumnAlignment(String columnId, ColumnAlignment alignment) { Column column = getColumn(columnId); if (column == null) { throw new IllegalStateException(String.format("Column with id '%s' not found", columnId)); } setColumnAlignment(column, alignment); } @Override public void setColumnWidth(Column column, int width) { checkNotNullArgument(column, "column must be non null"); if (column.getWidth() == null || column.getWidth() != width) { column.setWidth(width); } readjustColumns(); } @Override public void setColumnWidth(String columnId, int width) { Column column = getColumn(columnId); if (column == null) { throw new IllegalStateException(String.format("Column with id '%s' not found", columnId)); } setColumnWidth(column, width); } @Override public void addPrintable(String columnId, Printable<? super E, ?> printable) { printables.put(columnId, printable); } @Override public void removePrintable(String columnId) { printables.remove(columnId); } @Override @Nullable public Printable getPrintable(Column column) { checkNotNullArgument(column, "column is null"); return getPrintable(String.valueOf(column.getId())); } @Nullable @Override public Printable getPrintable(String columnId) { checkArgument(columnId != null, "columnId is null"); Printable printable = printables.get(columnId); if (printable != null) { return printable; } else { Column column = getColumn(columnId); if (column != null) { TableColumn tableColumn = getColumn(column); TableCellEditor cellEditor = tableColumn.getCellEditor(); if (cellEditor instanceof DesktopTableCellEditor) { ColumnGenerator columnGenerator = ((DesktopTableCellEditor) cellEditor).getColumnGenerator(); if (columnGenerator instanceof Printable) { return (Printable) columnGenerator; } } } return null; } } /** * {@inheritDoc} */ @Override public void repaint() { TableCellEditor cellEditor = impl.getCellEditor(); if (cellEditor instanceof DesktopTableCellEditor) { ((DesktopTableCellEditor) cellEditor).clearCache(); } List<TableColumn> implColumns = impl.getColumns(); for (Column column : getColumns()) { TableColumn tableColumn = null; for (TableColumn implColumn : implColumns) { if (column.equals((implColumn.getIdentifier()))) { tableColumn = implColumn; break; } } // column may be hidden if (tableColumn != null) { TableCellEditor columnCellEditor = tableColumn.getCellEditor(); if (columnCellEditor instanceof DesktopTableCellEditor) { ((DesktopTableCellEditor) columnCellEditor).clearCache(); } } } packRows(); repaintImplIfNeeded(); } protected void repaintImplIfNeeded() { if (contentRepaintEnabled) { impl.repaint(); } } @Override public boolean isEditable() { return editable; } @Override public void setEditable(boolean editable) { this.editable = editable; } @Override public void updateEnabled() { super.updateEnabled(); impl.setEnabled(isEnabledWithParent()); if (buttonsPanel != null) { ((DesktopButtonsPanel) buttonsPanel).setParentEnabled(isEnabledWithParent()); } } @Override public ButtonsPanel getButtonsPanel() { return buttonsPanel; } @Override public void setButtonsPanel(ButtonsPanel panel) { if (buttonsPanel != null) { topPanel.remove(DesktopComponentsHelper.unwrap(buttonsPanel)); buttonsPanel.setParent(null); } buttonsPanel = panel; if (panel != null) { if (panel.getParent() != null && panel.getParent() != this) { throw new IllegalStateException("Component already has parent"); } topPanel.add(DesktopComponentsHelper.unwrap(panel), BorderLayout.WEST); topPanel.setVisible(true); panel.setParent(this); } } @Override public void usePresentations(boolean b) { } @Override public boolean isUsePresentations() { return false; } @Override public void resetPresentation() { if (defaultSettings != null) { applySettings(defaultSettings.getRootElement()); } } @Override public void loadPresentations() { } @Override public Presentations getPresentations() { return null; } @Override public void applyPresentation(Object id) { } @Override public void applyPresentationAsDefault(Object id) { } @Override public Object getDefaultPresentationId() { return null; } @Override public void applySettings(Element element) { if (!isSettingsEnabled()) { return; } if (defaultSettings == null) { // save default view before apply custom defaultSettings = DocumentHelper.createDocument(); defaultSettings.setRootElement(defaultSettings.addElement("presentation")); saveSettings(defaultSettings.getRootElement()); } tableSettings.apply(element, isSortable()); } @Override public boolean saveSettings(Element element) { return isSettingsEnabled() && tableSettings.saveSettings(element); } @Override public boolean isSettingsEnabled() { return settingsEnabled; } @Override public void setSettingsEnabled(boolean settingsEnabled) { this.settingsEnabled = settingsEnabled; } @Override public boolean isMultiSelect() { return impl.getSelectionModel().getSelectionMode() != ListSelectionModel.SINGLE_SELECTION; } @Override public void setMultiSelect(boolean multiselect) { if (multiselect) { impl.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); } else { impl.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION); } } @Override public E getSingleSelected() { Set<E> selected = getSelected(); return selected.isEmpty() ? null : selected.iterator().next(); } @Override public Set<E> getSelected() { Set<E> set = new HashSet<>(); int[] rows = impl.getSelectedRows(); for (int row : rows) { int modelRow = impl.convertRowIndexToModel(row); Object item = tableModel.getItem(modelRow); if (item != null) { //noinspection unchecked set.add((E) item); } } return set; } @Override public void setSelected(E item) { if (item != null) { setSelected(Collections.singleton(item)); } else { setSelected(Collections.emptySet()); } } @Override public void setSelected(Collection<E> items) { if (items == null) { items = Collections.emptyList(); } for (Entity item : items) { // noinspection unchecked if (!datasource.containsItem(item.getId())) { throw new IllegalStateException("Datasource does not contain specified item: " + item.getId()); } } impl.clearSelection(); if (!items.isEmpty()) { List<Integer> indexes = getSelectionIndexes(items); if (!indexes.isEmpty()) { applySelectionIndexes(indexes); } } } @Override protected void attachAction(Action action) { if (action instanceof Action.HasTarget) { ((Action.HasTarget) action).setTarget(this); } super.attachAction(action); } protected void applySelectionIndexes(List<Integer> indexes) { Collections.sort(indexes); ListSelectionModel model = impl.getSelectionModel(); model.setValueIsAdjusting(true); int lastOpened = indexes.get(0); int current = indexes.get(0); for (Integer index : indexes) { if (index > current + 1) { model.addSelectionInterval(lastOpened, current); lastOpened = index; } current = index; } model.addSelectionInterval(lastOpened, current); model.setValueIsAdjusting(false); } protected List<Integer> getSelectionIndexes(Collection<? extends Entity> items) { if (items.isEmpty()) { return Collections.emptyList(); } List<Integer> indexes = Lists.newArrayList(); if (datasource instanceof CollectionDatasource.Ordered) { HashSet<Entity> itemSet = new HashSet<>(items); int itemIndex = 0; CollectionDatasource.Ordered orderedDs = (CollectionDatasource.Ordered) datasource; Object id = orderedDs.firstItemId(); while (id != null && !itemSet.isEmpty()) { int rowIndex = impl.convertRowIndexToView(itemIndex); // noinspection unchecked Entity itemById = datasource.getItem(id); if (itemSet.contains(itemById)) { indexes.add(rowIndex); itemSet.remove(itemById); } // noinspection unchecked id = orderedDs.nextItemId(id); itemIndex++; } } else { for (Entity item : items) { int idx = tableModel.getRowIndex(item); if (idx != -1) { indexes.add(impl.convertColumnIndexToView(idx)); } } } return indexes; } @Override public CollectionDatasource getDatasource() { return datasource; } @Override public void refresh() { if (datasource != null) { datasource.refresh(); packRows(); repaintImplIfNeeded(); } } protected JPopupMenu createPopupMenu() { JPopupMenu popup = new JPopupMenu(); JMenuItem menuItem; for (final Action action : actionList) { if (StringUtils.isNotBlank(action.getCaption()) && action.isVisible()) { menuItem = new JMenuItem(action.getCaption()); if (action.getIcon() != null) { menuItem.setIcon(AppBeans.get(IconResolver.class).getIconResource(action.getIcon())); } if (action.getShortcutCombination() != null) { menuItem.setAccelerator(convertKeyCombination(action.getShortcutCombination())); } menuItem.setEnabled(action.isEnabled()); menuItem.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { action.actionPerform(DesktopAbstractTable.this); } }); popup.add(menuItem); } } return popup; } /** * Returns the preferred height of a row. * The result is equal to the tallest cell in the row. * * @param rowIndex row index * @return row height */ public int getPreferredRowHeight(int rowIndex) { // Get the current default height for all rows int height = impl.getRowHeight(); // Determine highest cell in the row for (int c = 0; c < impl.getColumnCount(); c++) { TableCellRenderer renderer = impl.getCellRenderer(rowIndex, c); Component comp = impl.prepareRenderer(renderer, rowIndex, c); int componentHeight = comp.getPreferredSize().height; height = Math.max(height, componentHeight); } return height; } protected void applyFont(JTable table, Font font) { Graphics graphics = table.getGraphics(); if (graphics != null) { FontMetrics metrics = graphics.getFontMetrics(font); defaultRowHeight = metrics.getHeight() + DEFAULT_ROW_MARGIN; fontInitialized = true; if (impl != null) { packRows(); } } } /** * Sets the height of each row into the preferred height of the tallest cell in that row. */ public void packRows() { if (!contentRepaintEnabled) { return; } impl.setRowHeight(defaultRowHeight); for (Column column : columnsOrder) { if (column.isEditable()) { impl.setRowHeight(defaultEditableRowHeight); break; } } if (allColumnsAreInline()) { return; } int preferredRowHeight = -1; boolean equalsRowHeight = true; StopWatch sw = new Slf4JStopWatch("DAT packRows " + id); for (int r = 0; r < impl.getRowCount(); r++) { int h = getPreferredRowHeight(r); if (preferredRowHeight == -1) { preferredRowHeight = h; } else if (preferredRowHeight != h) { equalsRowHeight = false; } if (impl.getRowHeight(r) != h) { impl.setRowHeight(r, h); } } if (equalsRowHeight && preferredRowHeight > 0) { impl.setRowHeight(preferredRowHeight); } sw.stop(); } protected boolean allColumnsAreInline() { if (generatedColumnsCount <= 0) { return true; } for (Column column : columnsOrder) { if (!tableModel.isGeneratedColumn(column)) { continue; } TableColumn tableColumn = getColumn(column); if (tableColumn != null) { DesktopTableCellEditor cellEditor = (DesktopTableCellEditor) tableColumn.getCellEditor(); if (cellEditor != null) { boolean inline = cellEditor.isInline(); if (!inline) { return false; } } } } return true; } protected TableColumn getColumn(Column column) { List<TableColumn> tableColumns = getAllColumns(); for (TableColumn tableColumn : tableColumns) { if (column.equals(tableColumn.getIdentifier())) { return tableColumn; } } return null; } @Override public void addColumnCollapsedListener(ColumnCollapseListener columnCollapsedListener) { } @Override public void removeColumnCollapseListener(ColumnCollapseListener columnCollapseListener) { } @Override public void setClickListener(String columnId, CellClickListener clickListener) { } @Override public void removeClickListener(String columnId) { } public AnyTableModelAdapter getTableModel() { return tableModel; } public boolean isContentRepaintEnabled() { return contentRepaintEnabled; } public void setContentRepaintEnabled(boolean contentRepaintEnabled) { if (this.contentRepaintEnabled != contentRepaintEnabled) { this.contentRepaintEnabled = contentRepaintEnabled; packRows(); repaintImplIfNeeded(); } } protected void applyStylename(boolean isSelected, boolean hasFocus, Component component, String style) { if (style == null) { return; } DesktopTheme theme = App.getInstance().getTheme(); if (theme != null) { HashSet<String> properties = new HashSet<>(); if (hasFocus) { properties.add("focused"); } else if (isSelected) { properties.add("selected"); } else { properties.add("unselected"); } theme.applyStyle(component, style, properties); } } protected String getStylename(JTable table, int row, int column) { if (styleProviders == null) { return null; } Entity item = tableModel.getItem(row); int modelColumn = table.convertColumnIndexToModel(column); Object property = columnsOrder.get(modelColumn).getId(); String joinedStyle = null; for (StyleProvider styleProvider : styleProviders) { //noinspection unchecked String styleName = styleProvider.getStyleName(item, property.toString()); if (styleName != null) { if (joinedStyle == null) { joinedStyle = styleName; } else { joinedStyle += " " + styleName; } } } return joinedStyle; } protected TableCellEditor getColumnEditor(int column) { TableColumn tableColumn = impl.getColumnModel().getColumn(column); if (tableColumn.getIdentifier() instanceof Table.Column) { Table.Column columnConf = (Table.Column) tableColumn.getIdentifier(); if (columnConf.getId() instanceof MetaPropertyPath && !(isEditable() && columnConf.isEditable()) && !getTableModel().isGeneratedColumn(columnConf)) { MetaPropertyPath propertyPath = (MetaPropertyPath) columnConf.getId(); final CellProvider cellProvider = getCustomCellEditor(propertyPath); if (cellProvider != null) { return new CellProviderEditor(cellProvider); } } } return null; } protected TableCellRenderer getColumnRenderer(int column) { TableColumn tableColumn = impl.getColumnModel().getColumn(column); if (tableColumn.getIdentifier() instanceof Table.Column) { Table.Column columnConf = (Table.Column) tableColumn.getIdentifier(); if (columnConf.getId() instanceof MetaPropertyPath && !(isEditable() && columnConf.isEditable()) && !getTableModel().isGeneratedColumn(columnConf)) { MetaPropertyPath propertyPath = (MetaPropertyPath) columnConf.getId(); final CellProvider cellViewProvider = getCustomCellView(propertyPath); if (cellViewProvider != null) { return new CellProviderRenderer(cellViewProvider); } else if (multiLineCells && String.class == columnConf.getType()) { return new MultiLineTableCellRenderer(); } } } return null; } protected boolean isCustomCellEditable(int row, int column) { TableColumn tableColumn = impl.getColumnModel().getColumn(column); if (tableColumn.getIdentifier() instanceof Table.Column) { Table.Column columnConf = (Table.Column) tableColumn.getIdentifier(); if (columnConf.getId() instanceof MetaPropertyPath && !getTableModel().isGeneratedColumn(columnConf)) { return isCustomCellEditable(tableModel.getItem(row), (MetaPropertyPath) columnConf.getId()); } } return false; } @SuppressWarnings("UnusedParameters") protected CellProvider getCustomCellView(MetaPropertyPath mpp) { return null; } @SuppressWarnings("UnusedParameters") protected CellProvider getCustomCellEditor(MetaPropertyPath mpp) { return null; } @SuppressWarnings("UnusedParameters") protected boolean isCustomCellEditable(Entity e, MetaPropertyPath mpp) { return false; } @Override public void setId(String id) { super.setId(id); if (id != null && App.getInstance().isTestMode()) { getComposition().setName(id + "_composition"); } } @Override public void assignAutoDebugId() { super.assignAutoDebugId(); if (buttonsPanel != null) { for (com.haulmont.cuba.gui.components.Component subComponent : buttonsPanel.getComponents()) { if (subComponent instanceof DesktopAbstractComponent) { ((DesktopAbstractComponent) subComponent).assignAutoDebugId(); } } } } @Override protected String getAlternativeDebugId() { if (id != null) { return id; } if (datasource != null && StringUtils.isNotEmpty(datasource.getId())) { return "table_" + datasource.getId(); } return getClass().getSimpleName(); } @Override public void showCustomPopup(com.haulmont.cuba.gui.components.Component popupComponent) { } @Override public void showCustomPopupActions(List<Action> actions) { } @Override public void setColumnSortable(String columnId, boolean sortable) { } @Override public boolean getColumnSortable(String columnId) { return true; } @Override public void setColumnSortable(Column column, boolean sortable) { } @Override public boolean getColumnSortable(Column column) { return true; } @Override public void setColumnHeaderVisible(boolean visible) { columnHeaderVisible = visible; } @Override public boolean isColumnHeaderVisible() { return columnHeaderVisible; } @Override public void setShowSelection(boolean showSelection) { this.showSelection = showSelection; } @Override public boolean isShowSelection() { return showSelection; } @Override public void requestFocus(E itemId, String columnId) { // unsupported for desktop } @Override public void scrollTo(E item) { Preconditions.checkNotNullArgument(item); int rowIndex = tableModel.getRowIndex(item); if (rowIndex == -1) { throw new IllegalArgumentException("Unable to find item in Table"); } impl.scrollRowToVisible(rowIndex); } @Override public String getDescription() { return impl.getToolTipText(); } @Override public void setDescription(String description) { impl.setToolTipText(description); } @Override public void addLookupValueChangeListener(LookupSelectionChangeListener listener) { if (!lookupSelectionChangeListeners.contains(listener)) { lookupSelectionChangeListeners.add(listener); } } @Override public void removeLookupValueChangeListener(LookupSelectionChangeListener listener) { lookupSelectionChangeListeners.remove(listener); } /** * Uses delegate renderer to create cell component. * Then applies desktop styles to cell component. */ protected class StylingCellRenderer implements TableCellRenderer { private TableCellRenderer delegate; public StylingCellRenderer(TableCellRenderer delegate) { this.delegate = delegate; } public StylingCellRenderer() { } public TableCellRenderer getDelegate() { return delegate; } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { TableCellRenderer renderer = delegate; if (renderer == null) { renderer = table.getDefaultRenderer(value != null ? value.getClass() : Object.class); } java.awt.Component component = renderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (0 <= row) { String style = getStylename(table, row, column); applyStylename(isSelected, hasFocus, component, style); } return component; } } protected class CellProviderEditor extends AbstractCellEditor implements TableCellEditor { private final CellProvider cellProvider; public CellProviderEditor(CellProvider cellProvider) { this.cellProvider = cellProvider; } @Override public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { Entity item = getTableModel().getItem(row); TableColumn tableColumn = impl.getColumnModel().getColumn(column); Column columnConf = (Column) tableColumn.getIdentifier(); Component component = cellProvider.generateCell(item, (MetaPropertyPath) columnConf.getId()); if (component == null) { return new JLabel(""); } if (component instanceof JComponent) { ((JComponent) component).putClientProperty(DesktopTableCellEditor.CELL_EDITOR_TABLE, impl); } return component; } @Override public Object getCellEditorValue() { DesktopComponentsHelper.flushCurrentInputField(); return ""; } } protected class CellProviderRenderer implements TableCellRenderer { private final CellProvider cellViewProvider; public CellProviderRenderer(CellProvider cellViewProvider) { this.cellViewProvider = cellViewProvider; } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Entity item = getTableModel().getItem(row); TableColumn tableColumn = impl.getColumnModel().getColumn(column); Column columnConf = (Column) tableColumn.getIdentifier(); Component component = cellViewProvider.generateCell(item, (MetaPropertyPath) columnConf.getId()); if (component == null) { return new JLabel(""); } if (component instanceof JComponent) { ((JComponent) component).putClientProperty(DesktopTableCellEditor.CELL_EDITOR_TABLE, impl); } String style = getStylename(table, row, column); applyStylename(isSelected, hasFocus, component, style); return component; } } protected class MultiLineTableCellRenderer extends JTextArea implements TableCellRenderer { private List<List<Integer>> rowColHeight = new ArrayList<>(); public MultiLineTableCellRenderer() { setLineWrap(true); setWrapStyleWord(true); setOpaque(true); setBorder(new EmptyBorder(0, 0, 0, 0)); } @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { if (isSelected) { setForeground(table.getSelectionForeground()); setBackground(table.getSelectionBackground()); } else { setForeground(table.getForeground()); Color background = UIManager.getDefaults().getColor("Table:\"Table.cellRenderer\".background"); if (row % 2 == 1) { Color alternateColor = UIManager.getDefaults().getColor("Table.alternateRowColor"); if (alternateColor != null) { background = alternateColor; } } setBackground(background); } setFont(table.getFont()); Border border = null; if (isSelected) { border = UIManager.getDefaults().getBorder("Table.focusSelectedCellHighlightBorder"); } if (border == null) { border = UIManager.getDefaults().getBorder("Table.focusCellHighlightBorder"); } if (hasFocus) { setBorder(border); if (table.isCellEditable(row, column)) { setForeground(UIManager.getColor("Table.focusCellForeground")); setBackground(UIManager.getColor("Table.focusCellBackground")); } } else { setBorder(UIManager.getDefaults().getBorder("Table.cellNoFocusBorder")); } if (value != null) { setText(value.toString()); } else { setText(""); } adjustRowHeight(table, row, column); return this; } /** * Calculate the new preferred height for a given row, and sets the height on the table. */ private void adjustRowHeight(JTable table, int row, int column) { //The trick to get this to work properly is to set the width of the column to the //textarea. The reason for this is that getPreferredSize(), without a width tries //to place all the text in one line. By setting the size with the with of the column, //getPreferredSize() returnes the proper height which the row should have in //order to make room for the text. int cWidth = table.getTableHeader().getColumnModel().getColumn(column).getWidth(); setSize(new Dimension(cWidth, 1000)); int prefH = getPreferredSize().height; while (rowColHeight.size() <= row) { rowColHeight.add(new ArrayList<Integer>(column)); } List<Integer> colHeights = rowColHeight.get(row); while (colHeights.size() <= column) { colHeights.add(0); } colHeights.set(column, prefH); int maxH = prefH; for (Integer colHeight : colHeights) { if (colHeight > maxH) { maxH = colHeight; } } if (table.getRowHeight(row) != maxH) { table.setRowHeight(row, maxH); } } } }