From f1c6dddce848ec91ce8260edeff4bce9a761f422 Mon Sep 17 00:00:00 2001 From: Patrick Ziegler Date: Sun, 11 Jan 2026 21:04:41 +0100 Subject: [PATCH] Cleanup TableModelDialog and migrate away from deprecated JApplet This adapts the model dialog for the JTable to make sure all modifications to the Swing widget are done in the AWT event dispatcher thread and changes the preview to use the `EmbeddedSwingComposite`, which creates an `SWT_AWT` backed by a `JRootPane`, rather than a `JApplet`. The `JApplet` class has been removed with Java 26, so this migration is strictly necessary for WindowBuilder to remain compatible with upcoming Java releases. --- org.eclipse.wb.swing/META-INF/MANIFEST.MF | 2 +- .../property/editor/border/BorderDialog.java | 9 +- .../editor/border/BorderPreviewCanvas.java | 6 +- .../editor/models/table/TableModelDialog.java | 256 ++-------- .../models/table/TableModelPreviewCanvas.java | 204 ++++++++ .../example/EmbeddedSwingComposite2.java | 445 ------------------ 6 files changed, 255 insertions(+), 667 deletions(-) create mode 100644 org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/models/table/TableModelPreviewCanvas.java delete mode 100644 org.eclipse.wb.swing/src/swingintegration/example/EmbeddedSwingComposite2.java diff --git a/org.eclipse.wb.swing/META-INF/MANIFEST.MF b/org.eclipse.wb.swing/META-INF/MANIFEST.MF index 51318ac68..c457d9cae 100644 --- a/org.eclipse.wb.swing/META-INF/MANIFEST.MF +++ b/org.eclipse.wb.swing/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.wb.swing;singleton:=true -Bundle-Version: 1.10.400.qualifier +Bundle-Version: 1.11.0.qualifier Bundle-ClassPath: . Bundle-Activator: org.eclipse.wb.internal.swing.Activator Bundle-Vendor: %providerName diff --git a/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/border/BorderDialog.java b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/border/BorderDialog.java index bdce34ab3..abfe5a945 100644 --- a/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/border/BorderDialog.java +++ b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/border/BorderDialog.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011, 2025 Google, Inc. and others. + * Copyright (c) 2011, 2026 Google, Inc. and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -62,8 +62,6 @@ import javax.swing.border.Border; -import swingintegration.example.EmbeddedSwingComposite2; - /** * Dialog for {@link Border} editing. * @@ -193,11 +191,6 @@ public void handleEvent(Event event) { GridDataFactory.create(previewGroup).spanH(2).grabH().fillH(); GridLayoutFactory.create(previewGroup); previewGroup.setText(ModelMessages.BorderDialog_preview); - // - if (EmbeddedSwingComposite2.canUseAwt()) { - m_previewCanvas = new BorderPreviewCanvas(previewGroup, SWT.NONE); - GridDataFactory.create(m_previewCanvas).grab().fill().hintV(100); - } } // updateGUI(); diff --git a/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/border/BorderPreviewCanvas.java b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/border/BorderPreviewCanvas.java index 4d5ddccf4..68fa5a1cc 100644 --- a/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/border/BorderPreviewCanvas.java +++ b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/border/BorderPreviewCanvas.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011, 2025 Google, Inc. and others. + * Copyright (c) 2011, 2026 Google, Inc. and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -28,7 +28,7 @@ import javax.swing.border.Border; import javax.swing.border.LineBorder; -import swingintegration.example.EmbeddedSwingComposite2; +import swingintegration.example.EmbeddedSwingComposite; /** * {@link Composite} for displaying {@link Border}. @@ -36,7 +36,7 @@ * @author scheglov_ke * @coverage swing.property.editor */ -public final class BorderPreviewCanvas extends EmbeddedSwingComposite2 { +public final class BorderPreviewCanvas extends EmbeddedSwingComposite { private JPanel m_awtRoot; private JPanel m_emptyPanel; private JPanel m_filledPanel_1; diff --git a/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/models/table/TableModelDialog.java b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/models/table/TableModelDialog.java index 53a5b2675..3e32c63f0 100644 --- a/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/models/table/TableModelDialog.java +++ b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/models/table/TableModelDialog.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2011, 2024 Google, Inc. + * Copyright (c) 2011, 2026 Google, Inc. and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at @@ -19,44 +19,24 @@ import org.eclipse.wb.internal.core.utils.ui.UiUtils; import org.eclipse.wb.internal.core.utils.ui.dialogs.ResizableDialog; import org.eclipse.wb.internal.swing.model.ModelMessages; +import org.eclipse.wb.internal.swing.model.property.editor.models.table.TableModelPreviewCanvas.TableSelection; +import org.eclipse.wb.internal.swing.model.property.editor.models.table.TableModelPreviewCanvas.TableOperationRunnable; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; -import org.eclipse.swt.widgets.Widget; import org.apache.commons.collections4.BidiMap; import org.apache.commons.collections4.bidimap.DualHashBidiMap; import org.apache.commons.collections4.bidimap.UnmodifiableBidiMap; -import java.awt.EventQueue; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; - -import javax.swing.JComponent; -import javax.swing.JScrollPane; -import javax.swing.JTable; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import javax.swing.event.TableColumnModelEvent; -import javax.swing.event.TableColumnModelListener; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; -import javax.swing.table.TableModel; - -import swingintegration.example.EmbeddedSwingComposite2; - /** * Dialog for editing {@link TableModelDescription}. * @@ -107,8 +87,7 @@ public TableModelDialog(Shell parentShell, String title, TableModelDescription m private TitledComposite m_columnsComposite; private TitledComposite m_rowsComposite; private TitledComposite m_columnPropertiesComposite; - private EmbeddedSwingComposite2 m_swingComposite; - private JTable m_table; + private TableModelPreviewCanvas m_swingComposite; // columns private CSpinner m_columnCountSpinner; private Button m_insertColumnButton; @@ -158,18 +137,12 @@ private void createJTableComposite(Composite parent) { GridLayoutFactory.create(m_tableComposite).noMargins(); new Label(m_tableComposite, SWT.NONE).setText(ModelMessages.TableModelDialog_itemsLabel); { - m_swingComposite = new EmbeddedSwingComposite2(m_tableComposite, SWT.NONE) { - @Override - protected JComponent createSwingComponent() { - TableModel model = m_model.createTableModel(); - m_table = new JTable(model); - m_table.setCellSelectionEnabled(true); - m_table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); - updateTableModel(); - trackTableSelection(); - return new JScrollPane(m_table); + m_swingComposite = new TableModelPreviewCanvas(m_model, m_tableComposite, SWT.NONE); + m_swingComposite.addPropertyChangeListener(event -> { + if (TableModelPreviewCanvas.PROP_MODEL_CHANGED.equals(event.getPropertyName())) { + updateControls((TableSelection) event.getNewValue()); } - }; + }); GridDataFactory.create(m_swingComposite).grab().fill(); } { @@ -190,7 +163,7 @@ private void createColumnsComposite(Composite parent) { new Label(container, SWT.NONE).setText(ModelMessages.TableModelDialog_columnsCount); m_columnCountSpinner = new CSpinner(container, SWT.BORDER); GridDataFactory.create(m_columnCountSpinner).grabH().fillH().hintHC(5); - addJTableOperationSelectionListener(m_columnCountSpinner, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_columnCountSpinner, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.setColumnCount(m_columnCountSpinner.getSelection()); @@ -202,7 +175,7 @@ public void run(int row, int column) { m_insertColumnButton = new Button(container, SWT.NONE); GridDataFactory.create(m_insertColumnButton).spanH(2).hintHC(13).alignHC(); m_insertColumnButton.setText(ModelMessages.TableModelDialog_columnInsertButton); - addJTableOperationSelectionListener(m_insertColumnButton, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_insertColumnButton, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.insertColumn(getInsertColumnIndex(column)); @@ -210,7 +183,7 @@ public void run(int row, int column) { }, new TableOperationRunnable() { @Override public void run(int row, int column) { - setTableSelection(row, getInsertColumnIndex(column)); + m_swingComposite.setTableSelection(row, getInsertColumnIndex(column)); } }); } @@ -219,7 +192,7 @@ public void run(int row, int column) { m_deleteColumnButton = new Button(container, SWT.NONE); GridDataFactory.create(m_deleteColumnButton).spanH(2).hintHC(13).alignHC(); m_deleteColumnButton.setText(ModelMessages.TableModelDialog_columnDeleteButton); - addJTableOperationSelectionListener(m_deleteColumnButton, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_deleteColumnButton, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.removeColumn(column); @@ -227,10 +200,10 @@ public void run(int row, int column) { }, new TableOperationRunnable() { @Override public void run(int row, int column) { - if (column < m_table.getColumnCount()) { - setTableSelection(row, column); + if (column < m_model.getColumnCount()) { + m_swingComposite.setTableSelection(row, column); } else { - setTableSelection(row, column - 1); + m_swingComposite.setTableSelection(row, column - 1); } } }); @@ -240,7 +213,7 @@ public void run(int row, int column) { m_moveColumnLeftButton = new Button(container, SWT.NONE); GridDataFactory.create(m_moveColumnLeftButton).spanH(2).hintHC(13).alignHC(); m_moveColumnLeftButton.setText(ModelMessages.TableModelDialog_columnMoveLeftButton); - addJTableOperationSelectionListener(m_moveColumnLeftButton, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_moveColumnLeftButton, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.moveColumn(column, column - 1); @@ -248,7 +221,7 @@ public void run(int row, int column) { }, new TableOperationRunnable() { @Override public void run(int row, int column) { - setTableSelection(row, column - 1); + m_swingComposite.setTableSelection(row, column - 1); } }); } @@ -257,7 +230,7 @@ public void run(int row, int column) { m_moveColumnRightButton = new Button(container, SWT.NONE); GridDataFactory.create(m_moveColumnRightButton).spanH(2).hintHC(13).alignHC(); m_moveColumnRightButton.setText(ModelMessages.TableModelDialog_columnMoveRightButton); - addJTableOperationSelectionListener(m_moveColumnRightButton, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_moveColumnRightButton, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.moveColumn(column, column + 1); @@ -265,7 +238,7 @@ public void run(int row, int column) { }, new TableOperationRunnable() { @Override public void run(int row, int column) { - setTableSelection(row, column + 1); + m_swingComposite.setTableSelection(row, column + 1); } }); } @@ -282,7 +255,7 @@ private void createRowsComposite(Composite parent) { new Label(container, SWT.NONE).setText(ModelMessages.TableModelDialog_rowsCount); m_rowCountSpinner = new CSpinner(container, SWT.BORDER); GridDataFactory.create(m_rowCountSpinner).grabH().fillH().hintHC(5); - addJTableOperationSelectionListener(m_rowCountSpinner, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_rowCountSpinner, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.setRowCount(m_rowCountSpinner.getSelection()); @@ -294,7 +267,7 @@ public void run(int row, int column) { m_insertRowButton = new Button(container, SWT.NONE); GridDataFactory.create(m_insertRowButton).spanH(2).hintHC(13).alignHC(); m_insertRowButton.setText(ModelMessages.TableModelDialog_rowInsertButton); - addJTableOperationSelectionListener(m_insertRowButton, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_insertRowButton, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.insertRow(getInsertRowIndex(row)); @@ -302,7 +275,7 @@ public void run(int row, int column) { }, new TableOperationRunnable() { @Override public void run(int row, int column) { - setTableSelection(getInsertRowIndex(row), column); + m_swingComposite.setTableSelection(getInsertRowIndex(row), column); } }); } @@ -311,7 +284,7 @@ public void run(int row, int column) { m_deleteRowButton = new Button(container, SWT.NONE); GridDataFactory.create(m_deleteRowButton).spanH(2).hintHC(13).alignHC(); m_deleteRowButton.setText(ModelMessages.TableModelDialog_rowDeleteButton); - addJTableOperationSelectionListener(m_deleteRowButton, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_deleteRowButton, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.removeRow(row); @@ -319,10 +292,10 @@ public void run(int row, int column) { }, new TableOperationRunnable() { @Override public void run(int row, int column) { - if (row < m_table.getRowCount()) { - setTableSelection(row, column); + if (row < m_model.getRowCount()) { + m_swingComposite.setTableSelection(row, column); } else { - setTableSelection(row - 1, column); + m_swingComposite.setTableSelection(row - 1, column); } } }); @@ -332,7 +305,7 @@ public void run(int row, int column) { m_moveRowUpButton = new Button(container, SWT.NONE); GridDataFactory.create(m_moveRowUpButton).spanH(2).hintHC(13).alignHC(); m_moveRowUpButton.setText(ModelMessages.TableModelDialog_rowMoveUpButton); - addJTableOperationSelectionListener(m_moveRowUpButton, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_moveRowUpButton, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.moveRow(row, row - 1); @@ -340,7 +313,7 @@ public void run(int row, int column) { }, new TableOperationRunnable() { @Override public void run(int row, int column) { - setTableSelection(row - 1, column); + m_swingComposite.setTableSelection(row - 1, column); } }); } @@ -349,7 +322,7 @@ public void run(int row, int column) { m_moveRowDownButton = new Button(container, SWT.NONE); GridDataFactory.create(m_moveRowDownButton).spanH(2).hintHC(13).alignHC(); m_moveRowDownButton.setText(ModelMessages.TableModelDialog_rowMoveDownButton); - addJTableOperationSelectionListener(m_moveRowDownButton, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_moveRowDownButton, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.moveRow(row, row + 1); @@ -357,7 +330,7 @@ public void run(int row, int column) { }, new TableOperationRunnable() { @Override public void run(int row, int column) { - setTableSelection(row + 1, column); + m_swingComposite.setTableSelection(row + 1, column); } }); } @@ -385,7 +358,7 @@ private void createColumnPropertiesComposite(Composite parent) { new Label(container, SWT.NONE).setText(ModelMessages.TableModelDialog_columnPropertiesTitle); m_columnPropertyTitle = new Text(container, SWT.BORDER); GridDataFactory.create(m_columnPropertyTitle).spanH(2).hintHC(40).fillH(); - addJTableOperationListener(m_columnPropertyTitle, SWT.Modify, new TableOperationRunnable() { + m_swingComposite.addJTableOperationListener(m_columnPropertyTitle, SWT.Modify, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.getColumn(column).m_name = m_columnPropertyTitle.getText(); @@ -398,7 +371,7 @@ public void run(int row, int column) { m_columnPropertyPrefWidth = new CSpinner(container, SWT.BORDER); GridDataFactory.create(m_columnPropertyPrefWidth).hintHC(15); m_columnPropertyPrefWidth.setRange(0, Integer.MAX_VALUE); - addJTableOperationSelectionListener(m_columnPropertyPrefWidth, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_columnPropertyPrefWidth, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.getColumn(column).m_preferredWidth = m_columnPropertyPrefWidth.getSelection(); @@ -407,7 +380,7 @@ public void run(int row, int column) { } { ToolItem clearButton = createClearSpinnerButton(container); - addJTableOperationSelectionListener(clearButton, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(clearButton, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.getColumn(column).m_preferredWidth = @@ -426,7 +399,7 @@ public void run(int row, int column) { } m_columnPropertyType.setVisibleItemCount(COLUMN_TYPES.size()); // listener - addJTableOperationSelectionListener(m_columnPropertyType, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_columnPropertyType, new TableOperationRunnable() { @Override public void run(int row, int column) { String typeTitle = m_columnPropertyType.getText(); @@ -459,7 +432,7 @@ public void run(int row, int column) { m_columnPropertyMinWidth = new CSpinner(container, SWT.BORDER); GridDataFactory.create(m_columnPropertyMinWidth).hintHC(15); m_columnPropertyMinWidth.setRange(0, Integer.MAX_VALUE); - addJTableOperationSelectionListener(m_columnPropertyMinWidth, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_columnPropertyMinWidth, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.getColumn(column).m_minWidth = m_columnPropertyMinWidth.getSelection(); @@ -468,7 +441,7 @@ public void run(int row, int column) { } { ToolItem clearButton = createClearSpinnerButton(container); - addJTableOperationSelectionListener(clearButton, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(clearButton, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.getColumn(column).m_minWidth = TableColumnDescription.DEFAULT_MIN_WIDTH; @@ -483,7 +456,7 @@ public void run(int row, int column) { { m_columnPropertyEditable = new Button(booleanContainer, SWT.CHECK); m_columnPropertyEditable.setText(ModelMessages.TableModelDialog_columnPropertiesEditable); - addJTableOperationSelectionListener(m_columnPropertyEditable, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_columnPropertyEditable, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.getColumn(column).m_editable = m_columnPropertyEditable.getSelection(); @@ -493,7 +466,7 @@ public void run(int row, int column) { { m_columnPropertyResizable = new Button(booleanContainer, SWT.CHECK); m_columnPropertyResizable.setText(ModelMessages.TableModelDialog_columnPropertiesResizable); - addJTableOperationSelectionListener( + m_swingComposite.addJTableOperationSelectionListener( m_columnPropertyResizable, new TableOperationRunnable() { @Override @@ -509,7 +482,7 @@ public void run(int row, int column) { m_columnPropertyMaxWidth = new CSpinner(container, SWT.BORDER); GridDataFactory.create(m_columnPropertyMaxWidth).hintHC(15); m_columnPropertyMaxWidth.setRange(0, Integer.MAX_VALUE); - addJTableOperationSelectionListener(m_columnPropertyMaxWidth, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(m_columnPropertyMaxWidth, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.getColumn(column).m_maxWidth = m_columnPropertyMaxWidth.getSelection(); @@ -518,7 +491,7 @@ public void run(int row, int column) { } { ToolItem clearButton = createClearSpinnerButton(container); - addJTableOperationSelectionListener(clearButton, new TableOperationRunnable() { + m_swingComposite.addJTableOperationSelectionListener(clearButton, new TableOperationRunnable() { @Override public void run(int row, int column) { m_model.getColumn(column).m_maxWidth = TableColumnDescription.DEFAULT_MAX_WIDTH; @@ -554,92 +527,17 @@ private static int getInsertRowIndex(int row) { return row != -1 ? row : 0; } - //////////////////////////////////////////////////////////////////////////// - // - // Selection in JTable - // - //////////////////////////////////////////////////////////////////////////// - private int m_tableSelectedColumn; - private int m_tableSelectedRow; - private boolean m_processTableSelectionEvent = true; - - private void trackTableSelection() { - m_tableSelectedRow = -1; - m_tableSelectedColumn = -1; - ListSelectionListener listener = new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - if (m_processTableSelectionEvent) { - m_tableSelectedColumn = m_table.getSelectedColumn(); - m_tableSelectedRow = m_table.getSelectedRow(); - updateControls(); - } - } - }; - m_table.getSelectionModel().addListSelectionListener(listener); - m_table.getColumnModel().getSelectionModel().addListSelectionListener(listener); - updateControls(); - // XXX - final PropertyChangeListener columnWidthListener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (m_processTableSelectionEvent) { - TableColumn column = (TableColumn) evt.getSource(); - int index = column.getModelIndex(); - m_model.getColumn(index).m_preferredWidth = (Integer) evt.getNewValue(); - updateControls(); - } - } - }; - m_table.getColumnModel().addColumnModelListener(new TableColumnModelListener() { - @Override - public void columnAdded(TableColumnModelEvent e) { - TableColumnModel columnModel = (TableColumnModel) e.getSource(); - int columnIndex = e.getToIndex(); - columnModel.getColumn(columnIndex).addPropertyChangeListener(columnWidthListener); - } - - @Override - public void columnSelectionChanged(ListSelectionEvent e) { - } - - @Override - public void columnRemoved(TableColumnModelEvent e) { - } - - @Override - public void columnMoved(TableColumnModelEvent e) { - } - - @Override - public void columnMarginChanged(ChangeEvent e) { - } - }); - } - - private void setTableSelection(int row, int column) { - m_table.changeSelection(row, column, false, false); - } - //////////////////////////////////////////////////////////////////////////// // // Updates // //////////////////////////////////////////////////////////////////////////// - private void updateControls() { - Display.getDefault().asyncExec(new Runnable() { - @Override - public void run() { - updateControlsInDisplay(); - } - }); - } - private void updateControlsInDisplay() { - int row = m_tableSelectedRow; - int column = m_tableSelectedColumn; - int rowCount = m_table.getRowCount(); - int columnCount = m_table.getColumnCount(); + private void updateControls(TableSelection selection) { + int row = selection.row(); + int column = selection.column(); + int rowCount = m_model.getRowCount(); + int columnCount = m_model.getColumnCount(); // m_columnCountSpinner.setSelection(m_model.getColumnCount()); m_rowCountSpinner.setSelection(m_model.getRowCount()); @@ -667,66 +565,4 @@ private void updateControlsInDisplay() { m_columnPropertyResizable.setSelection(columnDescription.m_resizable); } } - - private interface TableOperationRunnable { - void run(int row, int column); - } - - private final TableOperationRunnable DEFAULT_TABLE_UPDATE = new TableOperationRunnable() { - @Override - public void run(int row, int column) { - setTableSelection(row, column); - updateControls(); - } - }; - - private void addJTableOperationListener(Widget eventTarget, - int eventType, - TableOperationRunnable operation) { - addJTableOperationSelectionListener(eventTarget, eventType, operation, DEFAULT_TABLE_UPDATE); - } - - private void addJTableOperationSelectionListener(Widget eventTarget, - TableOperationRunnable operation) { - addJTableOperationSelectionListener(eventTarget, operation, DEFAULT_TABLE_UPDATE); - } - - private void addJTableOperationSelectionListener(Widget eventTarget, - final TableOperationRunnable operation, - final TableOperationRunnable tableUpdateRunnable) { - addJTableOperationSelectionListener(eventTarget, SWT.Selection, operation, tableUpdateRunnable); - } - - private void addJTableOperationSelectionListener(Widget eventTarget, - int eventType, - final TableOperationRunnable operation, - final TableOperationRunnable tableUpdateRunnable) { - eventTarget.addListener(eventType, new Listener() { - @Override - public void handleEvent(Event event) { - final int row = m_table.getSelectedRow(); - final int column = m_table.getSelectedColumn(); - operation.run(row, column); - // update JTable and controls - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - updateTableModel(); - tableUpdateRunnable.run(row, column); - updateControls(); - } - }); - } - }); - } - - private void updateTableModel() { - m_processTableSelectionEvent = false; - try { - m_table.tableChanged(null); - m_model.applyModel(m_table); - } finally { - m_processTableSelectionEvent = true; - } - } } diff --git a/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/models/table/TableModelPreviewCanvas.java b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/models/table/TableModelPreviewCanvas.java new file mode 100644 index 000000000..310f402ee --- /dev/null +++ b/org.eclipse.wb.swing/src/org/eclipse/wb/internal/swing/model/property/editor/models/table/TableModelPreviewCanvas.java @@ -0,0 +1,204 @@ +/******************************************************************************* + * Copyright (c) 2011, 2026 Google, Inc. and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Google, Inc. - initial API and implementation + *******************************************************************************/ +package org.eclipse.wb.internal.swing.model.property.editor.models.table; + +import org.eclipse.core.runtime.Assert; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Widget; + +import java.awt.EventQueue; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; + +import javax.swing.JComponent; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TableColumnModelEvent; +import javax.swing.event.TableColumnModelListener; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import javax.swing.table.TableModel; + +import swingintegration.example.EmbeddedSwingComposite; + +/** + * {@link Composite} for displaying {@link TableModel}. + */ +public class TableModelPreviewCanvas extends EmbeddedSwingComposite { + /* package */ static final String PROP_MODEL_CHANGED = "modify"; + private final PropertyChangeSupport m_propertyChangeSupport; + private final TableModelDescription m_model; + private JTable m_table; + + public TableModelPreviewCanvas(TableModelDescription model, Composite parent, int style) { + super(parent, style); + m_propertyChangeSupport = new PropertyChangeSupport(this); + m_model = model; + } + + @Override + protected JComponent createSwingComponent() { + TableModel model = m_model.createTableModel(); + m_table = new JTable(model); + m_table.setCellSelectionEnabled(true); + m_table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + updateTableModel(); + trackTableSelection(); + return new JScrollPane(m_table); + } + + /* package */ static final record TableSelection(int row, int column) { + + } + + /* package */ static interface TableOperationRunnable { + void run(int row, int column); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Selection in JTable + // + //////////////////////////////////////////////////////////////////////////// + private int m_tableSelectedColumn; + private int m_tableSelectedRow; + private boolean m_processTableSelectionEvent = true; + + private void trackTableSelection() { + m_tableSelectedRow = -1; + m_tableSelectedColumn = -1; + ListSelectionListener listener = e -> { + if (m_processTableSelectionEvent) { + m_tableSelectedColumn = m_table.getSelectedColumn(); + m_tableSelectedRow = m_table.getSelectedRow(); + firePropertyChange(); + } + }; + m_table.getSelectionModel().addListSelectionListener(listener); + m_table.getColumnModel().getSelectionModel().addListSelectionListener(listener); + firePropertyChange(); + // XXX + final PropertyChangeListener columnWidthListener = evt -> { + if (m_processTableSelectionEvent) { + TableColumn column = (TableColumn) evt.getSource(); + int index = column.getModelIndex(); + m_model.getColumn(index).m_preferredWidth = (Integer) evt.getNewValue(); + firePropertyChange(); + } + }; + m_table.getColumnModel().addColumnModelListener(new TableColumnModelListener() { + @Override + public void columnAdded(TableColumnModelEvent e) { + TableColumnModel columnModel = (TableColumnModel) e.getSource(); + int columnIndex = e.getToIndex(); + columnModel.getColumn(columnIndex).addPropertyChangeListener(columnWidthListener); + } + + @Override + public void columnSelectionChanged(ListSelectionEvent e) { + } + + @Override + public void columnRemoved(TableColumnModelEvent e) { + } + + @Override + public void columnMoved(TableColumnModelEvent e) { + } + + @Override + public void columnMarginChanged(ChangeEvent e) { + } + }); + } + + /* package */ void setTableSelection(int row, int column) { + Assert.isTrue(SwingUtilities.isEventDispatchThread(), "Must be called from AWT event dispatcher thread"); + m_table.changeSelection(row, column, false, false); + } + + //////////////////////////////////////////////////////////////////////////// + // + // Updates + // + //////////////////////////////////////////////////////////////////////////// + + private final TableOperationRunnable DEFAULT_TABLE_UPDATE = (row, column) -> { + setTableSelection(row, column); + firePropertyChange(); + }; + + /* package */ void addJTableOperationListener(Widget eventTarget, + int eventType, + TableOperationRunnable operation) { + addJTableOperationSelectionListener(eventTarget, eventType, operation, DEFAULT_TABLE_UPDATE); + } + + /* package */ void addJTableOperationSelectionListener(Widget eventTarget, + TableOperationRunnable operation) { + addJTableOperationSelectionListener(eventTarget, operation, DEFAULT_TABLE_UPDATE); + } + + /* package */ void addJTableOperationSelectionListener(Widget eventTarget, + final TableOperationRunnable operation, + final TableOperationRunnable tableUpdateRunnable) { + addJTableOperationSelectionListener(eventTarget, SWT.Selection, operation, tableUpdateRunnable); + } + + /* package */ void addJTableOperationSelectionListener(Widget eventTarget, + int eventType, + final TableOperationRunnable operation, + final TableOperationRunnable tableUpdateRunnable) { + eventTarget.addListener(eventType, event -> { + final int row = m_table.getSelectedRow(); + final int column = m_table.getSelectedColumn(); + operation.run(row, column); + // update JTable and controls + EventQueue.invokeLater(() -> { + updateTableModel(); + tableUpdateRunnable.run(row, column); + firePropertyChange(); + }); + }); + } + + private void updateTableModel() { + m_processTableSelectionEvent = false; + try { + m_table.tableChanged(null); + m_model.applyModel(m_table); + } finally { + m_processTableSelectionEvent = true; + } + } + //////////////////////////////////////////////////////////////////////////// + // + // PropertyChangeSupport + // + //////////////////////////////////////////////////////////////////////////// + + /* package */ void addPropertyChangeListener(PropertyChangeListener listener) { + m_propertyChangeSupport.addPropertyChangeListener(listener); + } + + private void firePropertyChange() { + TableSelection selection = new TableSelection(m_tableSelectedRow, m_tableSelectedColumn); + Display.getDefault().asyncExec(() -> m_propertyChangeSupport.firePropertyChange(PROP_MODEL_CHANGED, null, selection)); + } +} diff --git a/org.eclipse.wb.swing/src/swingintegration/example/EmbeddedSwingComposite2.java b/org.eclipse.wb.swing/src/swingintegration/example/EmbeddedSwingComposite2.java deleted file mode 100644 index 731e79f03..000000000 --- a/org.eclipse.wb.swing/src/swingintegration/example/EmbeddedSwingComposite2.java +++ /dev/null @@ -1,445 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2007, 2025 SAS Institute. All rights reserved. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * https://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: SAS Institute - initial API and implementation - *******************************************************************************/ -package swingintegration.example; - -import org.eclipse.wb.internal.core.EnvironmentUtils; -import org.eclipse.wb.internal.swing.utils.SwingImageUtils; -import org.eclipse.wb.internal.swing.utils.SwingUtils; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.SWTException; -import org.eclipse.swt.awt.SWT_AWT; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Widget; - -import java.awt.EventQueue; -import java.awt.Frame; -import java.awt.im.InputContext; - -import javax.swing.JApplet; -import javax.swing.JComponent; -import javax.swing.RootPaneContainer; - -/** - * A SWT composite widget for embedding Swing components in a SWT composite within an RCP or - * standalone-SWT application. The Eclipse platform provides limited support for embedding Swing - * components through {@link org.eclipse.swt.awt.SWT_AWT}. This class extends that support by - * - *

- * If, rather than embedding Swing components, you are integrating with Swing by opening Swing - * dialogs, see the {@link AwtEnvironment} class. - *

- * This is an abstract that is normally used by extending it and implementing the - * {@link #createSwingComponent()} method. For example, - * - *

- *        embeddedComposite = new EmbeddedSwingComposite(parent, SWT.NONE) {
- *            protected JComponent createSwingComponent() {
- *                scrollPane = new JScrollPane();
- *                table = new JTable();
- *                scrollPane.setViewportView(table);
- *                return scrollPane;
- *            }
- *        };
- *        embeddedComposite.populate();
- * 
- * - *

- * The Swing component is created inside a standard Swing containment hierarchy, rooted in a - * {@link javax.swing.RootPaneContainer}. The root pane container is placed inside an AWT frame, as - * returned by {@link org.eclipse.swt.awt.SWT_AWT#new_Frame(Composite)} - *

- * Note: When you mix components from Swing/AWT and SWT toolkits, there will be two UI event - * threads, one for AWT, one for SWT. Most SWT APIs require that you call them from the SWT thread. - * Swing has similar restrictions though it does not enforce them as much as SWT. - *

- * Applications need to be aware of the current thread, and, where necessary, schedule tasks to run - * on another thread. This has always been required in the pure Swing or SWT environments, but when - * mixing Swing and SWT, more of this scheduling will be necessary. - *

- * To schedule work on the AWT event thread, you can use: - *

- *

- * (or similar methods in {@link java.awt.EventQueue}) - *

- * To schedule work on the SWT event thread, use: - *

- * - * Of course, as in single-toolkit environments, long-running tasks should be offloaded from either - * UI thread to a background thread. The Eclipse jobs API can be used for this purpose. - */ -@SuppressWarnings("removal") -public abstract class EmbeddedSwingComposite2 extends Composite { - private static class AwtContext { - private final Frame frame; - private JComponent swingComponent; - - AwtContext(Frame frame) { - assert frame != null; - this.frame = frame; - } - - Frame getFrame() { - return frame; - } - - void setSwingComponent(JComponent swingComponent) { - this.swingComponent = swingComponent; - } - - JComponent getSwingComponent() { - return swingComponent; - } - } - - private AwtContext awtContext; - private AwtFocusHandler awtHandler; - // This listener helps ensure that Swing popup menus are properly dismissed when - // a menu item off the SWT main menu bar is shown. - private final Listener menuListener = event -> { - assert awtHandler != null; - awtHandler.postHidePopups(); - }; - - /** - * Constructs a new instance of this class given its parent and a style value describing its - * behavior and appearance. - *

- * This method must be called from the SWT event thread. - *

- * The style value is either one of the style constants defined in class SWT which is - * applicable to instances of this class, or must be built by bitwise OR'ing together - * (that is, using the int "|" operator) two or more of those SWT style - * constants. The class description lists the style constants that are applicable to the class. - * Style bits are also inherited from superclasses. - *

- *

- * The styles SWT.EMBEDDED and SWT.NO_BACKGROUND will be added to the specified style. Usually, no - * other style bits are needed. - * - * @param parent - * a widget which will be the parent of the new instance (cannot be null) - * @param style - * the style of widget to construct - * - * @exception IllegalArgumentException - *

- * @exception SWTException - * - * - * @see Widget#getStyle - */ - public EmbeddedSwingComposite2(Composite parent, int style) { - super(parent, style | SWT.EMBEDDED | SWT.NO_BACKGROUND); - setLayout(new FillLayout()); - } - - /** - * Populates the embedded composite with the Swing component. - *

- * This method must be called from the SWT event thread. - *

- * The Swing component will be created by calling {@link #createSwingComponent()}. The creation is - * scheduled asynchronously on the AWT event thread. This method does not wait for completion of - * this asynchronous task, so it may return before createSwingComponent() is complete. - *

- * The Swing component is created inside a standard Swing containment hierarchy, rooted in a - * {@link javax.swing.RootPaneContainer}. Clients can override - * {@link #addRootPaneContainer(Frame)} to provide their own root pane container implementation. - *

- * This method can be called multiple times for a single instance. If an embedded frame exists - * from a previous call, it is disposed. - * - * @exception SWTException - *

- */ - public void populate() { - checkWidget(); - createFrame(); - scheduleComponentCreation(); - } - - @Override - public Point computeSize(int wHint, int hHint, boolean changed) { - // wait for Swing component creation - while (awtContext.swingComponent == null) { - getDisplay().readAndDispatch(); - } - // return size of Swing component - try { - final java.awt.Dimension prefSize[] = new java.awt.Dimension[1]; - SwingImageUtils.runInDispatchThread(() -> prefSize[0] = awtContext.swingComponent.getPreferredSize()); - // - int width = wHint == SWT.DEFAULT ? prefSize[0].width : wHint; - int height = hHint == SWT.DEFAULT ? prefSize[0].height : hHint; - return new Point(width, height); - } catch (Throwable e) { - } - // if exception, use (0, 0) - return new Point(0, 0); - } - - /** - * Creates the embedded Swing component. This method is called from the AWT event thread. - *

- * Implement this method to provide the Swing component that will be shown inside this composite. - * The returned component will be added to the Swing content pane. At least one component must be - * created by this method; null is not a valid return value. - * - * @return a non-null Swing component - */ - protected abstract JComponent createSwingComponent(); - - /** - * Adds a root pane container to the embedded AWT frame. Override this to provide your own - * {@link javax.swing.RootPaneContainer} implementation. In most cases, it is not necessary to - * override this method. - *

- * This method is called from the AWT event thread. - *

- * If you are defining your own root pane container, make sure that there is at least one - * heavyweight (AWT) component in the frame's containment hierarchy; otherwise, event processing - * will not work correctly. See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4982522 for - * more information. - * - * @param frame - * the frame to which the root pane container is added - * @return a non-null Swing component - */ - protected RootPaneContainer addRootPaneContainer(Frame frame) { - assert EventQueue.isDispatchThread(); // On AWT event thread - assert frame != null; - // It is important to set up the proper top level components in the frame: - // 1) For Swing to work properly, Sun documents that there must be an implementor of - // javax.swing.RootPaneContainer at the top of the component hierarchy. - // 2) For proper event handling there must be a heavyweight - // an AWT frame must contain a heavyweight component (see - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4982522) - // 3) The Swing implementation further narrows the options by expecting that the - // top of the hierarchy be a JFrame, JDialog, JWindow, or JApplet. See javax.swing.PopupFactory. - // All this drives the choice of JApplet for the top level Swing component. It is the - // only single component that satisfies all the above. This does not imply that - // we have a true applet; in particular, there is no notion of an applet lifecycle in this - // context. - // - // We need to intercept call of "getInputContext" because it make native call - // and causes dead-lock in SWT. - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=376561 - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=291326 - JApplet applet = new JApplet() { - private static final long serialVersionUID = 1L; - - @Override - public InputContext getInputContext() { - return null; - } - }; - // In JRE 1.4, the JApplet makes itself a focus cycle root. This - // interferes with the focus handling installed on the parent frame, so - // change it back to a non-root here. - // TODO: consider moving the focus policy from the Frame down to the JApplet - applet.setFocusCycleRoot(false); - frame.add(applet); - return applet; - } - - /** - * Performs custom updates to newly set fonts. This method is called whenever a change to the - * system font through the system settings (i.e. control panel) is detected. - *

- * This method is called from the AWT event thread. - *

- * In most cases it is not necessary to override this method. Normally, the implementation of this - * class will automatically propogate font changes to the embedded Swing components through - * Swing's Look and Feel support. However, if additional special processing is necessary, it can - * be done inside this method. - * - * @param newFont - * New AWT font - */ - protected void updateAwtFont(java.awt.Font newFont) { - } - - /** - * Returns the embedded AWT frame. The returned frame is the root of the AWT containment hierarchy - * for the embedded Swing component. This method can be called from any thread. - * - * @return the embedded frame - */ - public Frame getFrame() { - // Intentionally leaving out checkWidget() call. This may need to be called from within user's - // createSwingComponent() method. Accessing from a non-SWT thread is OK, but we still check - // for disposal - if (getDisplay() == null || isDisposed()) { - SWT.error(SWT.ERROR_WIDGET_DISPOSED); - } - return awtContext != null ? awtContext.getFrame() : null; - } - - private void createFrame() { - assert Display.getCurrent() != null; // On SWT event thread - // Make sure Awt environment is initialized. - //AwtEnvironment.getInstance(getDisplay()); - if (awtContext != null) { - final Frame oldFrame = awtContext.getFrame(); - // Schedule disposal of old frame on AWT thread so that there are no problems with - // already-scheduled operations that have not completed. - // Note: the implementation of Frame.dispose() would schedule the use of the AWT - // thread even if it was not done here, but it uses invokeAndWait() which is - // prone to deadlock (and not necessary for this case). - EventQueue.invokeLater(() -> oldFrame.dispose()); - } - Frame frame = SWT_AWT.new_Frame(this); - frame.getInputContext(); - awtContext = new AwtContext(frame); - // Glue the two frameworks together. Do this before anything is added to the frame - // so that all necessary listeners are in place. - createFocusHandlers(); - // This listener clears garbage during resizing, making it looker much cleaner - addControlListener(new CleanResizeListener()); - } - - private void createFocusHandlers() { - assert awtContext != null; - assert Display.getCurrent() != null; // On SWT event thread - Frame frame = awtContext.getFrame(); - awtHandler = new AwtFocusHandler(frame); - SwtFocusHandler swtHandler = new SwtFocusHandler(this); - awtHandler.setSwtHandler(swtHandler); - swtHandler.setAwtHandler(awtHandler); - // Ensure that AWT popups are dismissed whenever a SWT menu is shown - getDisplay().addFilter(SWT.Show, menuListener); - EmbeddedChildFocusTraversalPolicy policy = new EmbeddedChildFocusTraversalPolicy(awtHandler); - frame.setFocusTraversalPolicy(policy); - } - - private void scheduleComponentCreation() { - assert awtContext != null; - // Create AWT/Swing components on the AWT thread. This is - // especially necessary to avoid an AWT leak bug (6411042). - final AwtContext currentContext = awtContext; - EventQueue.invokeLater(() -> { - RootPaneContainer container = addRootPaneContainer(currentContext.getFrame()); - JComponent swingComponent = createSwingComponent(); - currentContext.setSwingComponent(swingComponent); - container.getRootPane().getContentPane().add(swingComponent); - }); - } - - private boolean isFocusable() { - if (awtContext == null) { - return false; - } - JComponent swingComponent = awtContext.getSwingComponent(); - return swingComponent != null && swingComponent.isFocusable(); - } - - /* (non-Javadoc) - * @see org.eclipse.swt.widgets.Control#setFocus() - */ - @Override - public boolean setFocus() { - checkWidget(); - if (!isFocusable()) { - return false; - } - return super.setFocus(); - } - - /* (non-Javadoc) - * @see org.eclipse.swt.widgets.Control#forceFocus() - */ - @Override - public boolean forceFocus() { - checkWidget(); - if (!isFocusable()) { - return false; - } - return super.forceFocus(); - } - - /* (non-Javadoc) - * @see org.eclipse.swt.widgets.Widget#dispose() - */ - @Override - public void dispose() { - if (!isDisposed()) { - dispose_AWT(); - super.dispose(); - } - } - - private void dispose_AWT() { - // remove listeners - getDisplay().removeFilter(SWT.Show, menuListener); - // dispose frame to avoid lock down in EventQueue.invokeAndWait() later - // must be done in the AWT even thread. See: - // https://github.com/eclipse-windowbuilder/windowbuilder/discussions/937 - if (awtContext != null) { - SwingUtils.runLog(() -> { - Frame oldFrame = awtContext.getFrame(); - oldFrame.dispose(); - }); - } - } - - /** - * There is problem with running SWT_AWT on OSX with Java 1.7 - *

- * https://bugs.eclipse.org/bugs/show_bug.cgi?id=374199 - *

- * It is marked "fixed", but still does not work. - */ - public static boolean canUseAwt() { - if (EnvironmentUtils.IS_MAC) { - Shell shell = new Shell(); - try { - try { - Frame frame = SWT_AWT.new_Frame(shell); - frame.dispose(); - return true; - } catch (Throwable e) { - return false; - } - } finally { - shell.dispose(); - } - } - return true; - } -}