diff --git a/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxRow.java b/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxRow.java index 2c1dab15..376d0bea 100644 --- a/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxRow.java +++ b/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxRow.java @@ -32,9 +32,18 @@ public class FxRow extends IndexedCell { private static final Logger LOG = LogManager.getLogger(FxRow.class); + /** + * The {@code Index} class is a record representing the position of a row within a table + * or sheet-like structure. It encapsulates two distinct pieces of information: + * + * + */ public record Index(int rowIndex, int rowNumber) {} - public static final Affine IDENTITY_TRANSFORMATION = FxUtil.convert(AffineTransformation2f.IDENTITY); + private static final Affine IDENTITY_TRANSFORMATION = FxUtil.convert(AffineTransformation2f.IDENTITY); private final ObservableList rows; @@ -198,7 +207,7 @@ public void dispose() { private void render() { PlatformHelper.checkApplicationThread(); - try (var __ = sheetViewDelegate.readLock()) { + try (var __ = sheetViewDelegate.automaticReadLock()) { switch (getItem().rowIndex()) { case ROW_INDEX_UNUSED -> renderEmpty(); case ROW_INDEX_COLUMN_LABELS -> renderColumnLabels(); diff --git a/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSegmentView.java b/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSegmentView.java index 275fef79..045b3295 100644 --- a/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSegmentView.java +++ b/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSegmentView.java @@ -141,7 +141,7 @@ public FxSegmentView(FxSheetViewDelegate svDelegate, SheetView.Quadrant quadrant this.rows.addListener((ListChangeListener) change -> { PlatformHelper.runLater(() -> { - try (var __ = svDelegate.readLock()) { + try (var __ = svDelegate.automaticReadLock()) { flow.setCellCount(rows.size()); flow.refresh(); } diff --git a/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSheetView.java b/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSheetView.java index dc465e80..2f3af0dd 100644 --- a/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSheetView.java +++ b/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSheetView.java @@ -240,7 +240,7 @@ public void scrollToCurrentCell() { LOG.trace("scrollToCurrentCell()"); Platform.runLater(() -> delegate.getCurrentLogicalCell().ifPresent(cell -> { - try (var __ = delegate.readLock()) { + try (var __ = delegate.automaticReadLock()) { Sheet sheet = delegate.getSheet(); int i = cell.getRowNumber(); int j = cell.getColumnNumber(); @@ -288,7 +288,7 @@ public void repaintCell(Cell cell) { LOG.trace("repaintCell({})", cell); PlatformHelper.runLater(() -> { - try (var __ = delegate.readLock()) { + try (var __ = delegate.automaticReadLock()) { Cell lc = cell.getLogicalCell(); int startRow = lc.getRowNumber(); int endRow = startRow + lc.getVerticalSpan(); @@ -302,37 +302,56 @@ public void repaintCell(Cell cell) { }); } - public void updateLayout() { - PlatformHelper.runAndWait(() -> { - try (var __ = delegate.writeLock()) { - updateDelegate(getSheet()); - topLeftQuadrant.updateLayout(); - topRightQuadrant.updateLayout(); - bottomLeftQuadrant.updateLayout(); - bottomRightQuadrant.updateLayout(); - } - }); + /** + * Updates the layout of the FxSheetView instance and its associated components. + * + * This method performs the following operations: + * + * + * This operation is vital for reflecting changes in layout, scaling, or other graphical + * attributes in the view, particularly after modifications to the underlying {@code Sheet} + * or its configurations. + */ + private void updateLayout() { + LOG.debug("updateLayout()"); + PlatformHelper.checkApplicationThread(); + try (var __ = delegate.automaticWriteLock()) { + updateDelegate(getSheet()); + topLeftQuadrant.updateLayout(); + topRightQuadrant.updateLayout(); + bottomLeftQuadrant.updateLayout(); + bottomRightQuadrant.updateLayout(); + } } @Override public void updateContent() { LOG.debug("updateContent()"); + PlatformHelper.checkApplicationThread(); - PlatformHelper.runAndWait(() -> { - try (var __ = delegate.readLock()) { - updateDelegate(getSheet()); + try (var __ = delegate.automaticReadLock()) { + updateDelegate(getSheet()); - topLeftQuadrant.updateLayout(); - topRightQuadrant.updateLayout(); - bottomLeftQuadrant.updateLayout(); - bottomRightQuadrant.updateLayout(); + topLeftQuadrant.updateLayout(); + topRightQuadrant.updateLayout(); + bottomLeftQuadrant.updateLayout(); + bottomRightQuadrant.updateLayout(); - topLeftQuadrant.refresh(); - topRightQuadrant.refresh(); - bottomLeftQuadrant.refresh(); - bottomRightQuadrant.refresh(); - } - }); + topLeftQuadrant.refresh(); + topRightQuadrant.refresh(); + bottomLeftQuadrant.refresh(); + bottomRightQuadrant.refresh(); + } } private void updateDelegate(Sheet sheet) { diff --git a/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSheetViewDelegate.java b/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSheetViewDelegate.java index f02d313c..e50a3764 100644 --- a/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSheetViewDelegate.java +++ b/meja-fx/src/main/java/com/dua3/meja/ui/fx/FxSheetViewDelegate.java @@ -1,6 +1,7 @@ package com.dua3.meja.ui.fx; import com.dua3.meja.model.Sheet; +import com.dua3.meja.model.SheetEvent; import com.dua3.meja.ui.SheetViewDelegate; import com.dua3.utility.fx.PlatformHelper; @@ -32,4 +33,9 @@ public void updateLayout() { PlatformHelper.checkApplicationThread(); super.updateLayout(); } + + @Override + public void onNext(SheetEvent item) { + PlatformHelper.runLater(() -> super.onNext(item)); + } } diff --git a/meja-fx/src/main/java/com/dua3/meja/ui/fx/ObservableSheet.java b/meja-fx/src/main/java/com/dua3/meja/ui/fx/ObservableSheet.java index 21a18105..e022c2d7 100644 --- a/meja-fx/src/main/java/com/dua3/meja/ui/fx/ObservableSheet.java +++ b/meja-fx/src/main/java/com/dua3/meja/ui/fx/ObservableSheet.java @@ -51,6 +51,13 @@ public int size() { return sheet.getRowCount(); } + /** + * Provides access to the zoom property of the observable sheet. + * The zoom property is a {@link FloatProperty} that allows + * for observing and modifying the zoom level of the underlying sheet. + * + * @return the zoom property of the observable sheet + */ public FloatProperty zoomProperty() { return zoomProperty; } diff --git a/meja-swing/src/main/java/com/dua3/meja/ui/swing/SwingSegmentView.java b/meja-swing/src/main/java/com/dua3/meja/ui/swing/SwingSegmentView.java index ed853198..62421d97 100644 --- a/meja-swing/src/main/java/com/dua3/meja/ui/swing/SwingSegmentView.java +++ b/meja-swing/src/main/java/com/dua3/meja/ui/swing/SwingSegmentView.java @@ -163,7 +163,7 @@ public void mousePressed(MouseEvent e) { protected void paintComponent(Graphics g) { LOG.debug("paintComponent(): ({},{}) - ({},{})", ssvDelegate.getStartRow(), ssvDelegate.getStartColumn(), ssvDelegate.getEndRow(), ssvDelegate.getEndColumn()); - try (var __ = svDelegate.readLock()) { + try (var __ = svDelegate.automaticReadLock()) { // clear background by calling super method super.paintComponent(g); @@ -210,7 +210,7 @@ public void scrollIntoView(Cell cell) { return; } - try (var __ = svDelegate.readLock()) { + try (var __ = svDelegate.automaticReadLock()) { Rectangle2f r = svDelegate.getCellRect(cell); AffineTransformation2f t = ssvDelegate.getTransformation(); Rectangle bounds = SwingGraphics.convert(Rectangle2f.withCorners( diff --git a/meja-swing/src/main/java/com/dua3/meja/ui/swing/SwingSheetView.java b/meja-swing/src/main/java/com/dua3/meja/ui/swing/SwingSheetView.java index 10f74ba1..84393323 100644 --- a/meja-swing/src/main/java/com/dua3/meja/ui/swing/SwingSheetView.java +++ b/meja-swing/src/main/java/com/dua3/meja/ui/swing/SwingSheetView.java @@ -41,7 +41,6 @@ import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.util.Locale; -import java.util.concurrent.locks.Lock; /** * Swing component for displaying instances of {@link Sheet}. @@ -231,15 +230,11 @@ public void updateContent() { LOG.debug("updating content"); Sheet sheet = getSheet(); - Lock lock = delegate.writeLock(); - lock.lock(); - try { + try (var __ = delegate.automaticWriteLock()){ int dpi = Toolkit.getDefaultToolkit().getScreenResolution(); delegate.setDisplayScale(getDisplayScale()); delegate.setScale(new Scale2f(sheet.getZoom() * dpi / 72.0f)); delegate.updateLayout(); - } finally { - lock.unlock(); } SwingUtilities.invokeLater(() -> { delegate.getSheetPainter().update(sheet); diff --git a/meja-ui/src/main/java/com/dua3/meja/ui/AutoLock.java b/meja-ui/src/main/java/com/dua3/meja/ui/AutoLock.java deleted file mode 100644 index 89166cc8..00000000 --- a/meja-ui/src/main/java/com/dua3/meja/ui/AutoLock.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.dua3.meja.ui; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.util.concurrent.locks.Lock; -import java.util.function.Supplier; - -public class AutoLock implements AutoCloseable { - private static final Logger LOG = LogManager.getLogger(AutoLock.class); - - private final Lock lock; - private final Supplier name; - - public AutoLock(Lock lock, Supplier name) { - this.lock = lock; - this.name = name; - - LOG.trace("AutoLock({}): lock [{}]", name::get, () -> System.identityHashCode(this)); - lock.lock(); - } - - @Override - public void close() { - LOG.trace("AutoLock({}): unlock [{}]", name::get, () -> System.identityHashCode(this)); - lock.unlock(); - } -} diff --git a/meja-ui/src/main/java/com/dua3/meja/ui/SheetViewDelegate.java b/meja-ui/src/main/java/com/dua3/meja/ui/SheetViewDelegate.java index 235511d3..113565e9 100644 --- a/meja-ui/src/main/java/com/dua3/meja/ui/SheetViewDelegate.java +++ b/meja-ui/src/main/java/com/dua3/meja/ui/SheetViewDelegate.java @@ -4,6 +4,7 @@ import com.dua3.meja.model.Direction; import com.dua3.meja.model.Sheet; import com.dua3.meja.model.SheetEvent; +import com.dua3.utility.concurrent.AutoLock; import com.dua3.utility.data.Color; import com.dua3.utility.math.MathUtil; import com.dua3.utility.math.geometry.AffineTransformation2f; @@ -19,6 +20,7 @@ import java.util.Locale; import java.util.Optional; import java.util.concurrent.Flow; +import java.util.concurrent.locks.Lock; import java.util.function.IntFunction; /** @@ -83,18 +85,60 @@ public abstract class SheetViewDelegate implements Flow.Subscriber { private float pixelWidthInPoints = 1.0f; private float pixelHeightInPoints = 1.0f; - public AutoLock readLock() { - return new AutoLock(sheet.readLock(), () -> "readLock() [%s]".formatted(sheet.getSheetName())); + /** + * Retrieves the read lock for the underlying sheet. + * + * @return the read lock instance associated with the underlying sheet + */ + public Lock readLock() { + return sheet.readLock(); + } + + /** + * Acquires an automatic read lock on the underlying sheet, ensuring the lock is held while + * the returned {@link AutoLock} instance is in use, and releases it automatically when the + * instance is closed. + * + * @return an {@link AutoLock} instance representing the read lock on the sheet + */ + public AutoLock automaticReadLock() { + return AutoLock.of(sheet.readLock(), () -> "readLock() [%s]".formatted(sheet.getSheetName())); } - public AutoLock writeLock() { - return new AutoLock(sheet.writeLock(), () -> "writeLock() [%s]".formatted(sheet.getSheetName())); + /** + * Retrieves the write lock for the underlying sheet. + * + * @return the write lock instance associated with the underlying sheet + */ + public Lock writeLock() { + return sheet.writeLock(); + } + + /** + * Acquires a write lock on the sheet and returns an AutoLock instance that + * manages the lock with an associated description. + * + * @return an AutoLock instance that wraps the write lock of the sheet and + * provides a textual description of the lock, including the sheet name. + */ + public AutoLock automaticWriteLock() { + return AutoLock.of(sheet.writeLock(), () -> "writeLock() [%s]".formatted(sheet.getSheetName())); } + /** + * Retrieves the height of the sheet in points. + * + * @return the height of the sheet represented in points + */ public float getSheetHeightInPoints() { return sheetHeightInPoints; } + /** + * Retrieves the width of the sheet in points. + * + * @return the width of the sheet in points + */ public float getSheetWidthInPoints() { return sheetWidthInPoints; } @@ -117,14 +161,29 @@ public float getSplitY() { return getRowPos(sheet.getSplitRow()); } + /** + * Retrieves the color used for highlighting the selection in the sheet view. + * + * @return the {@link Color} instance representing the selection highlight color + */ protected Color getSelectionColor() { return selectionColor; } + /** + * Sets the color used to indicate the selection in the sheet view. + * + * @param selectionColor the {@link Color} to be used for the selection highlight + */ protected void setSelectionColor(Color selectionColor) { this.selectionColor = selectionColor; } + /** + * Retrieves the stroke width used for the selection outline. + * + * @return the stroke width of the selection rectangle as a floating-point value + */ public float getSelectionStrokeWidth() { return selectionStrokeWidth; } @@ -300,18 +359,18 @@ public void onNext(SheetEvent item) { switch (item.type()) { case SheetEvent.ZOOM_CHANGED, SheetEvent.LAYOUT_CHANGED, SheetEvent.ROWS_ADDED -> { - try (var __ = readLock()) { + try (var __ = automaticReadLock()) { owner.updateContent(); } } case SheetEvent.SPLIT_CHANGED -> { - try (var __ = readLock()) { + try (var __ = automaticReadLock()) { owner.updateContent(); owner.scrollToCurrentCell(); } } case SheetEvent.ACTIVE_CELL_CHANGED -> { - try (var __ = readLock()) { + try (var __ = automaticReadLock()) { SheetEvent.ActiveCellChanged evt = (SheetEvent.ActiveCellChanged) item; Cell oldCell = evt.oldValue(); @@ -327,7 +386,7 @@ public void onNext(SheetEvent item) { } } case SheetEvent.CELL_VALUE_CHANGED, SheetEvent.CELL_STYLE_CHANGED -> { - try (var __ = readLock()) { + try (var __ = automaticReadLock()) { owner.repaintCell(((SheetEvent.CellChanged) item).cell()); } } @@ -392,7 +451,7 @@ public void updateLayout() { return; } - try (var __ = writeLock()) { + try (var __ = automaticWriteLock()) { this.pixelWidthInPoints = 1.0f / scale.sx(); this.pixelHeightInPoints = 1.0f / scale.sy(); @@ -471,7 +530,7 @@ public void setEditing(boolean editing) { } public void setColumnNames(IntFunction columnNames) { - try (var __ = writeLock()) { + try (var __ = automaticWriteLock()) { this.columnNames = columnNames; markLayoutChanged(); } @@ -486,19 +545,19 @@ public int getSplitRow() { } public boolean setCurrentCell(int i, int j) { - try (var __ = writeLock()) { + try (var __ = automaticWriteLock()) { return sheet.setCurrentCell(i, j); } } public boolean setCurrentCell(Cell cell) { - try (var __ = writeLock()) { + try (var __ = automaticWriteLock()) { return sheet.setCurrentCell(cell); } } public void setRowNames(IntFunction rowNames) { - try (var __ = writeLock()) { + try (var __ = automaticWriteLock()) { this.rowNames = rowNames; markLayoutChanged(); } @@ -509,7 +568,7 @@ public Scale2f getScale() { } public void setScale(Scale2f scale) { - try (var __ = readLock()) { + try (var __ = automaticReadLock()) { if (!scale.equals(this.scale)) { this.scale = scale; markLayoutChanged(); @@ -518,7 +577,7 @@ public void setScale(Scale2f scale) { } public void setDisplayScale(Scale2f displayScale) { - try (var __ = readLock()) { + try (var __ = automaticReadLock()) { if (!displayScale.equals(this.displayScale)) { this.displayScale = displayScale; markLayoutChanged(); @@ -539,7 +598,7 @@ public void setBackground(Color background) { } public void move(Direction d) { - try (var __ = writeLock()) { + try (var __ = automaticWriteLock()) { getCurrentLogicalCell().ifPresent(cell -> { switch (d) { case NORTH -> setCurrentRowNum(cell.getRowNumber() - 1); @@ -556,14 +615,14 @@ public Optional getCurrentLogicalCell() { } public void setCurrentColNum(int colNum) { - try (var __ = writeLock()) { + try (var __ = automaticWriteLock()) { int rowNum = sheet.getCurrentCell().map(Cell::getRowNumber).orElse(0); setCurrentCell(sheet.getCell(rowNum, Math.max(0, colNum))); } } public void setCurrentRowNum(int rowNum) { - try (var __ = writeLock()) { + try (var __ = automaticWriteLock()) { int colNum = sheet.getCurrentCell().map(Cell::getColumnNumber).orElse(0); setCurrentCell(Math.max(0, rowNum), colNum); } @@ -582,7 +641,7 @@ public void moveEnd() { * Move the selection rectangle to the top left cell. */ public void moveHome() { - try (var __ = writeLock()) { + try (var __ = automaticWriteLock()) { int row = sheet.getFirstRowNum(); int col = sheet.getFirstColNum(); setCurrentCell(row, col); @@ -657,7 +716,7 @@ public Font getLabelFont() { } public void setLabelFont(Font labelFont) { - try (var __ = writeLock()) { + try (var __ = automaticWriteLock()) { this.labelFont = labelFont; markLayoutChanged(); }