From bf47ce7678eab211f51af93a2eaeb9bca29f2d95 Mon Sep 17 00:00:00 2001 From: Stan Persoons <147701137+stan-at-work@users.noreply.github.com> Date: Fri, 6 Dec 2024 18:32:34 +0100 Subject: [PATCH 1/2] Fix for https://github.com/doonfrs/pluto_grid_plus/issues/88 and added autoFitAllColumns --- lib/src/manager/state/column_state.dart | 2184 ++++++++++++----------- 1 file changed, 1104 insertions(+), 1080 deletions(-) diff --git a/lib/src/manager/state/column_state.dart b/lib/src/manager/state/column_state.dart index 525198431..52b579dcf 100644 --- a/lib/src/manager/state/column_state.dart +++ b/lib/src/manager/state/column_state.dart @@ -1,1080 +1,1104 @@ -import 'dart:collection'; -import 'dart:math' as math; - -import 'package:flutter/material.dart'; - -import '../../../pluto_grid_plus.dart'; -import '../../ui/cells/pluto_default_cell.dart'; - -abstract class IColumnState { - /// Columns provided at grid start. - List get columns; - - FilteredList get refColumns; - - /// Column index list. - List get columnIndexes; - - /// List of column indexes in which the sequence is maintained - /// while the frozen column is visible. - List get columnIndexesForShowFrozen; - - /// Width of the entire column. - double get columnsWidth; - - /// Left frozen columns. - List get leftFrozenColumns; - - /// Left frozen column Index List. - List get leftFrozenColumnIndexes; - - /// Width of the left frozen column. - double get leftFrozenColumnsWidth; - - /// Right frozen columns. - List get rightFrozenColumns; - - /// Right frozen column Index List. - List get rightFrozenColumnIndexes; - - /// Width of the right frozen column. - double get rightFrozenColumnsWidth; - - /// Body columns. - List get bodyColumns; - - /// Body column Index List. - List get bodyColumnIndexes; - - /// Width of the body column. - double get bodyColumnsWidth; - - /// Column of currently selected cell. - PlutoColumn? get currentColumn; - - /// Column field name of currently selected cell. - String? get currentColumnField; - - bool get hasSortedColumn; - - PlutoColumn? get getSortedColumn; - - /// Column Index List by frozen Column - List get columnIndexesByShowFrozen; - - /// Toggle whether the column is frozen or not. - /// - /// When [column] is changed to a frozen column, - /// the [PlutoColumn.frozen] is not changed if the frozen column width constraint is insufficient. - /// Unfreeze the existing frozen or widen the entire grid width - /// to set it wider than the frozen column width constraint. - void toggleFrozenColumn(PlutoColumn column, PlutoColumnFrozen frozen); - - /// Toggle column sorting. - /// - /// It works when you tap the title area of a column. - /// When called, [PlutoGrid.onSorted] callback is called. (If registered) - /// - /// [sortAscending], [sortDescending], [sortBySortIdx] also sort the column, - /// but do not call the [PlutoGrid.onSorted] callback. - void toggleSortColumn(PlutoColumn column); - - /// Index of [column] in [columns] - /// - /// Depending on the state of the frozen column, the column order index - /// must be referenced with the columnIndexesByShowFrozen function. - int? columnIndex(PlutoColumn column); - - /// Insert [columns] at [columnIdx] position. - /// - /// If there is a [PlutoColumn.frozen.isFrozen] column in [columns], - /// If the width constraint of the frozen column is greater than the range, - /// the columns are unfreeze in order. - void insertColumns(int columnIdx, List columns); - - void removeColumns(List columns); - - /// Move [column] position to [targetColumn]. - /// - /// In case of [column.frozen.isNone] and [targetColumn.frozen.isFroze], - /// If the width constraint of a frozen column is narrow, it cannot be moved. - void moveColumn({ - required PlutoColumn column, - required PlutoColumn targetColumn, - }); - - /// Resize column size - /// - /// In case of [column.frozen.isFrozen], - /// it is not changed if the width constraint of the frozen column is narrow. - void resizeColumn(PlutoColumn column, double offset); - - void autoFitColumn(BuildContext context, PlutoColumn column); - - /// Hide or show the [column] with [hide] value. - /// - /// When [column.frozen.isFrozen] and [hide] is false, - /// [column.frozen] is changed to [PlutoColumnFrozen.none] - /// if the frozen column width constraint is narrow. - void hideColumn( - PlutoColumn column, - bool hide, { - bool notify = true, - }); - - /// Hide or show the [columns] with [hide] value. - /// - /// When [column.frozen.isFrozen] in [columns] and [hide] is false, - /// [column.frozen] is changed to [PlutoColumnFrozen.none] - /// if the frozen column width constraint is narrow. - void hideColumns( - List columns, - bool hide, { - bool notify = true, - }); - - void sortAscending(PlutoColumn column, {bool notify = true}); - - void sortDescending(PlutoColumn column, {bool notify = true}); - - void sortBySortIdx(PlutoColumn column, {bool notify = true}); - - void showSetColumnsPopup(BuildContext context); - - /// When expanding the width of the freeze column, - /// check the width constraint of the freeze column. - bool limitResizeColumn(PlutoColumn column, double offset); - - /// When moving from a non-frozen column to a frozen column area, - /// check the frozen column width constraint. - bool limitMoveColumn({ - required PlutoColumn column, - required PlutoColumn targetColumn, - }); - - /// When changing the value of [PlutoColumn.frozen], - /// check the frozen column width constraint. - /// - /// [frozen] is the value you want to change. - bool limitToggleFrozenColumn(PlutoColumn column, PlutoColumnFrozen frozen); - - /// When changing a column from hidden state to unhidden state, - /// Check the constraint on the frozen column. - /// If the hidden column is a frozen column - /// The width of the currently frozen column is limited. - bool limitHideColumn( - PlutoColumn column, - bool hide, { - double accumulateWidth = 0, - }); -} - -mixin ColumnState implements IPlutoGridState { - @override - List get columns => List.from(refColumns, growable: false); - - @override - List get columnIndexes => List.generate( - refColumns.length, - (index) => index, - growable: false, - ); - - @override - List get columnIndexesForShowFrozen { - final leftIndexes = []; - final bodyIndexes = []; - final rightIndexes = []; - final length = refColumns.length; - - for (int i = 0; i < length; i += 1) { - refColumns[i].frozen.isNone - ? bodyIndexes.add(i) - : refColumns[i].frozen.isStart - ? leftIndexes.add(i) - : rightIndexes.add(i); - } - - return leftIndexes + bodyIndexes + rightIndexes; - } - - @override - double get columnsWidth { - double width = 0; - - for (final column in refColumns) { - width += column.width; - } - - return width; - } - - @override - List get leftFrozenColumns { - return refColumns.where((e) => e.frozen.isStart).toList(growable: false); - } - - @override - List get leftFrozenColumnIndexes { - final indexes = []; - final length = refColumns.length; - - for (int i = 0; i < length; i += 1) { - if (refColumns[i].frozen.isStart) { - indexes.add(i); - } - } - - return indexes; - } - - @override - double get leftFrozenColumnsWidth { - double width = 0; - - for (final column in refColumns) { - if (column.frozen.isStart) { - width += column.width; - } - } - - return width; - } - - @override - List get rightFrozenColumns { - return refColumns.where((e) => e.frozen.isEnd).toList(); - } - - @override - List get rightFrozenColumnIndexes { - final indexes = []; - final length = refColumns.length; - - for (int i = 0; i < length; i += 1) { - if (refColumns[i].frozen.isEnd) { - indexes.add(i); - } - } - - return indexes; - } - - @override - double get rightFrozenColumnsWidth { - double width = 0; - - for (final column in refColumns) { - if (column.frozen.isEnd) { - width += column.width; - } - } - - return width; - } - - @override - List get bodyColumns { - return refColumns.where((e) => e.frozen.isNone).toList(); - } - - @override - List get bodyColumnIndexes { - final indexes = []; - final length = refColumns.length; - - for (int i = 0; i < length; i += 1) { - if (refColumns[i].frozen.isNone) { - indexes.add(i); - } - } - - return indexes; - } - - @override - double get bodyColumnsWidth { - double width = 0; - - for (final column in refColumns) { - if (column.frozen.isNone) { - width += column.width; - } - } - - return width; - } - - @override - PlutoColumn? get currentColumn { - return currentCell?.column; - } - - @override - String? get currentColumnField { - return currentCell?.column.field; - } - - @override - bool get hasSortedColumn { - for (final column in refColumns) { - if (column.sort.isNone == false) { - return true; - } - } - - return false; - } - - @override - PlutoColumn? get getSortedColumn { - for (final column in refColumns) { - if (column.sort.isNone == false) { - return column; - } - } - - return null; - } - - @override - List get columnIndexesByShowFrozen { - return showFrozenColumn ? columnIndexesForShowFrozen : columnIndexes; - } - - @override - void toggleFrozenColumn(PlutoColumn column, PlutoColumnFrozen frozen) { - if (limitToggleFrozenColumn(column, frozen)) { - return; - } - - column.frozen = column.frozen.isFrozen ? PlutoColumnFrozen.none : frozen; - - resetCurrentState(notify: false); - - resetShowFrozenColumn(); - - if (!columnSizeConfig.restoreAutoSizeAfterFrozenColumn) { - deactivateColumnsAutoSize(); - } - - updateVisibilityLayout(); - - if (onColumnsMoved != null) { - onColumnsMoved!(PlutoGridOnColumnsMovedEvent( - idx: refColumns.indexOf(column), - visualIdx: columnIndex(column)!, - columns: [column], - )); - } - - notifyListeners(true, toggleFrozenColumn.hashCode); - } - - @override - void toggleSortColumn(PlutoColumn column) { - final oldSort = column.sort; - - if (column.sort.isNone) { - sortAscending(column, notify: false); - } else if (column.sort.isAscending) { - sortDescending(column, notify: false); - } else { - sortBySortIdx(column, notify: false); - } - - _callOnSorted(column, oldSort); - - notifyListeners(true, toggleSortColumn.hashCode); - } - - @override - int? columnIndex(PlutoColumn column) { - final columnIndexes = columnIndexesByShowFrozen; - final length = columnIndexes.length; - - for (int i = 0; i < length; i += 1) { - if (refColumns[columnIndexes[i]].field == column.field) { - return i; - } - } - - return null; - } - - @override - void insertColumns(int columnIdx, List columns) { - if (columns.isEmpty) { - return; - } - - if (columnIdx < 0 || refColumns.length < columnIdx) { - return; - } - - _updateLimitedFrozenColumns(columns); - - if (columnIdx >= refColumns.originalLength) { - refColumns.addAll(columns); - } else { - refColumns.insertAll(columnIdx, columns); - } - - _fillCellsInRows(columns); - - resetCurrentState(notify: false); - - resetShowFrozenColumn(); - - if (!columnSizeConfig.restoreAutoSizeAfterInsertColumn) { - deactivateColumnsAutoSize(); - } - - updateVisibilityLayout(); - - notifyListeners(true, insertColumns.hashCode); - } - - @override - void removeColumns(List columns) { - if (columns.isEmpty) { - return; - } - - removeColumnsInColumnGroup(columns, notify: false); - - removeColumnsInFilterRows(columns, notify: false); - - removeColumnsInRowGroupByColumn(columns, notify: false); - - _removeCellsInRows(columns); - - final removeKeys = Set.from(columns.map((e) => e.key)); - - refColumns.removeWhereFromOriginal( - (column) => removeKeys.contains(column.key), - ); - - resetShowFrozenColumn(); - - if (!columnSizeConfig.restoreAutoSizeAfterRemoveColumn) { - deactivateColumnsAutoSize(); - } - - updateVisibilityLayout(); - - resetCurrentState(notify: false); - - notifyListeners(true, removeColumns.hashCode); - } - - @override - void moveColumn({ - required PlutoColumn column, - required PlutoColumn targetColumn, - }) { - if (limitMoveColumn(column: column, targetColumn: targetColumn)) { - return; - } - - final foundIndexes = _findIndexOfColumns([column, targetColumn]); - - if (foundIndexes.length != 2) { - return; - } - - int index = foundIndexes[0]; - - int targetIndex = foundIndexes[1]; - - final frozen = refColumns[index].frozen; - - final targetFrozen = refColumns[targetIndex].frozen; - - if (frozen != targetFrozen) { - if (targetFrozen.isEnd && index > targetIndex) { - targetIndex += 1; - } else if (targetFrozen.isStart && index < targetIndex) { - targetIndex -= 1; - } else if (frozen.isStart && index > targetIndex) { - targetIndex += 1; - } else if (frozen.isEnd && index < targetIndex) { - targetIndex -= 1; - } - } - - refColumns[index].frozen = targetFrozen; - - var columnToMove = refColumns[index]; - - refColumns.removeAt(index); - - refColumns.insert(targetIndex, columnToMove); - - updateCurrentCellPosition(notify: false); - - resetShowFrozenColumn(); - - if (!columnSizeConfig.restoreAutoSizeAfterMoveColumn) { - deactivateColumnsAutoSize(); - } - - updateVisibilityLayout(); - - if (onColumnsMoved != null) { - onColumnsMoved!(PlutoGridOnColumnsMovedEvent( - idx: targetIndex, - visualIdx: columnIndex(columnToMove)!, - columns: [columnToMove], - )); - } - - notifyListeners(true, moveColumn.hashCode); - } - - @override - void resizeColumn(PlutoColumn column, double offset) { - if (columnsResizeMode.isNone || !column.enableDropToResize) { - return; - } - - if (limitResizeColumn(column, offset)) { - return; - } - - bool updated = false; - - if (columnsResizeMode.isNormal) { - final setWidth = column.width + offset; - - column.width = setWidth > column.minWidth ? setWidth : column.minWidth; - - updated = setWidth == column.width; - } else { - updated = _updateResizeColumns(column: column, offset: offset); - } - - if (updated == false) { - return; - } - - deactivateColumnsAutoSize(); - - notifyResizingListeners(); - - scrollByDirection( - PlutoMoveDirection.right, - correctHorizontalOffset, - ); - - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - activateColumnsAutoSize(); - }); - } - - @override - void autoFitColumn(BuildContext context, PlutoColumn column) { - String maxValue = ''; - bool hasExpandableRowGroup = false; - for (final row in refRows) { - final cell = row.cells.entries - .firstWhere((element) => element.key == column.field) - .value; - var value = column.formattedValueForDisplay(cell.value); - if (hasRowGroups) { - if (PlutoDefaultCell.showGroupCount(rowGroupDelegate!, cell)) { - final groupCountValue = - PlutoDefaultCell.groupCountText(rowGroupDelegate!, row); - if (groupCountValue.isNotEmpty) { - value = '$value $groupCountValue'; - } - } - - hasExpandableRowGroup |= - PlutoDefaultCell.canExpand(rowGroupDelegate!, cell); - } - if (maxValue.length < value.length) { - maxValue = value; - } - } - - // Get size after rendering virtually - // https://stackoverflow.com/questions/54351655/flutter-textfield-width-should-match-width-of-contained-text - final titleTextWidth = - _visualTextWidth(column.title, style.columnTextStyle); - final maxValueTextWidth = _visualTextWidth(maxValue, style.cellTextStyle); - - // todo : Handle (renderer) width - - final calculatedTileWidth = titleTextWidth - - column.width + - [ - (column.titlePadding ?? style.defaultColumnTitlePadding).horizontal, - if (column.enableRowChecked) - _getEffectiveButtonWidth(context, checkBox: true), - if (column.isShowRightIcon) style.iconSize, - 8, - ].reduce((acc, a) => acc + a); - - final calculatedCellWidth = maxValueTextWidth - - column.width + - [ - (column.cellPadding ?? style.defaultCellPadding).horizontal, - if (hasExpandableRowGroup) _getEffectiveButtonWidth(context), - if (column.enableRowChecked) - _getEffectiveButtonWidth(context, checkBox: true), - if (column.isShowRightIcon) style.iconSize, - 2, - ].reduce((acc, a) => acc + a); - - resizeColumn(column, math.max(calculatedTileWidth, calculatedCellWidth)); - } - - double _visualTextWidth(String text, TextStyle style) { - if (text.isEmpty) return 0; - final painter = TextPainter( - text: TextSpan( - style: style, - text: text, - ), - textDirection: isRTL ? TextDirection.rtl : TextDirection.ltr, - )..layout(); - return painter.width; - } - - @override - void hideColumn( - PlutoColumn column, - bool hide, { - bool notify = true, - }) { - if (column.hide == hide) { - return; - } - - if (limitHideColumn(column, hide)) { - column.frozen = PlutoColumnFrozen.none; - } - - column.hide = hide; - - _updateAfterHideColumn(columns: [column], notify: notify); - } - - @override - void hideColumns( - List columns, - bool hide, { - bool notify = true, - }) { - if (columns.isEmpty) { - return; - } - - _updateLimitedHideColumns(columns, hide); - - _updateAfterHideColumn(columns: columns, notify: notify); - } - - @override - void sortAscending(PlutoColumn column, {bool notify = true}) { - _updateBeforeColumnSort(); - - column.sort = PlutoColumnSort.ascending; - - if (sortOnlyEvent) return; - - compare(a, b) => column.type.compare( - a.cells[column.field]!.valueForSorting, - b.cells[column.field]!.valueForSorting, - ); - - if (enabledRowGroups) { - sortRowGroup(column: column, compare: compare); - } else { - refRows.sort(compare); - } - - notifyListeners(notify, sortAscending.hashCode); - } - - @override - void sortDescending(PlutoColumn column, {bool notify = true}) { - _updateBeforeColumnSort(); - - column.sort = PlutoColumnSort.descending; - - if (sortOnlyEvent) return; - - compare(b, a) => column.type.compare( - a.cells[column.field]!.valueForSorting, - b.cells[column.field]!.valueForSorting, - ); - - if (enabledRowGroups) { - sortRowGroup(column: column, compare: compare); - } else { - refRows.sort(compare); - } - - notifyListeners(notify, sortDescending.hashCode); - } - - @override - void sortBySortIdx(PlutoColumn column, {bool notify = true}) { - _updateBeforeColumnSort(); - - if (sortOnlyEvent) return; - - int compare(a, b) { - if (a.sortIdx == null || b.sortIdx == null) { - if (a.sortIdx == null && b.sortIdx == null) { - return 0; - } - - return a.sortIdx == null ? -1 : 1; - } - - return a.sortIdx!.compareTo(b.sortIdx!); - } - - if (enabledRowGroups) { - sortRowGroup(column: column, compare: compare); - } else { - refRows.sort(compare); - } - - notifyListeners(notify, sortBySortIdx.hashCode); - } - - @override - void showSetColumnsPopup(BuildContext context) { - const titleField = 'title'; - const columnField = 'field'; - - final columns = [ - PlutoColumn( - title: configuration.localeText.setColumnsTitle, - field: titleField, - type: PlutoColumnType.text(), - enableRowChecked: true, - enableEditingMode: false, - enableDropToResize: true, - enableContextMenu: false, - enableColumnDrag: false, - backgroundColor: configuration.style.filterHeaderColor), - PlutoColumn( - title: 'hidden column', - field: columnField, - type: PlutoColumnType.text(), - hide: true, - ), - ]; - - final toRow = _toRowByColumnField( - titleField: titleField, - columnField: columnField, - ); - - final rows = refColumns.originalList.map(toRow).toList(growable: false); - - void handleOnRowChecked(PlutoGridOnRowCheckedEvent event) { - if (event.isAll) { - hideColumns(refColumns.originalList, event.isChecked != true); - } else { - final checkedField = event.row!.cells[columnField]!.value.toString(); - final checkedColumn = refColumns.originalList.firstWhere( - (column) => column.field == checkedField, - ); - hideColumn(checkedColumn, event.isChecked != true); - } - } - - PlutoGridPopup( - context: context, - configuration: configuration.copyWith( - style: configuration.style.copyWith( - gridBorderRadius: configuration.style.gridPopupBorderRadius, - enableRowColorAnimation: false, - oddRowColor: const PlutoOptional(null), - evenRowColor: const PlutoOptional(null), - ), - ), - columns: columns, - rows: rows, - width: 200, - height: 500, - mode: PlutoGridMode.popup, - onLoaded: (e) { - e.stateManager.setSelectingMode(PlutoGridSelectingMode.none); - }, - onRowChecked: handleOnRowChecked, - ); - } - - @override - bool limitResizeColumn(PlutoColumn column, double offset) { - if (offset <= 0) { - return false; - } - - return _limitFrozenColumn(column.frozen, offset); - } - - @override - bool limitMoveColumn({ - required PlutoColumn column, - required PlutoColumn targetColumn, - }) { - if (column.frozen.isFrozen) { - return false; - } - - return _limitFrozenColumn(targetColumn.frozen, column.width); - } - - @override - bool limitToggleFrozenColumn(PlutoColumn column, PlutoColumnFrozen frozen) { - if (column.frozen.isFrozen) { - return false; - } - - return _limitFrozenColumn(frozen, column.width); - } - - @override - bool limitHideColumn( - PlutoColumn column, - bool hide, { - double accumulateWidth = 0, - }) { - if (hide == true) { - return false; - } - - return _limitFrozenColumn( - column.frozen, - column.width + accumulateWidth, - ); - } - - /// Check the width limit before changing the PlutoColumnFrozen value. - /// - /// In the following situations, need to check the frozen column width limit. - /// 1. Change the width of the frozen column - /// 2. Set a non-frozen column to a frozen column - /// 3. If the column to be unhidden in the hidden state is a frozen column - /// 4. Add a frozen column - /// - /// [frozen] The value to be changed. - /// - /// [offsetWidth] The size to be changed. Usually [PlutoColumn.width]. - /// Check the width limit of the frozen column - /// by subtracting the offsetWidth value from the total width of the grid. - /// Assume that a column has been added by subtracting the [offsetWidth] value - /// from the total width while no column has been added yet. - bool _limitFrozenColumn( - PlutoColumnFrozen frozen, - double offsetWidth, - ) { - if (frozen.isNone) { - return false; - } - - return !enoughFrozenColumnsWidth(maxWidth! - offsetWidth); - } - - void _updateBeforeColumnSort() { - clearCurrentCell(notify: false); - - clearCurrentSelecting(notify: false); - - // Reset column sort to none. - for (var i = 0; i < refColumns.originalList.length; i += 1) { - refColumns.originalList[i].sort = PlutoColumnSort.none; - } - } - - List _findIndexOfColumns(List findColumns) { - SplayTreeMap found = SplayTreeMap(); - - for (int i = 0; i < refColumns.length; i += 1) { - for (int j = 0; j < findColumns.length; j += 1) { - if (findColumns[j].key == refColumns[i].key) { - found[j] = i; - continue; - } - } - - if (findColumns.length == found.length) { - break; - } - } - - return found.values.toList(); - } - - PlutoRow Function(PlutoColumn column) _toRowByColumnField({ - required String titleField, - required String columnField, - }) { - return (PlutoColumn column) { - return PlutoRow( - cells: { - titleField: PlutoCell(value: column.titleWithGroup), - columnField: PlutoCell(value: column.field), - }, - checked: !column.hide, - ); - }; - } - - /// [PlutoGrid.onSorted] Called when a callback is registered. - void _callOnSorted(PlutoColumn column, PlutoColumnSort oldSort) { - if (sortOnlyEvent) { - eventManager!.addEvent( - PlutoGridChangeColumnSortEvent(column: column, oldSort: oldSort), - ); - } - - if (onSorted == null) { - return; - } - - onSorted!(PlutoGridOnSortedEvent(column: column, oldSort: oldSort)); - } - - /// Add [PlutoCell] to the whole [PlutoRow.cells]. - /// Called when a new column is added. - void _fillCellsInRows(List columns) { - for (var row in iterateAllRowAndGroup) { - final List> cells = []; - - for (var column in columns) { - final cell = PlutoCell(value: column.type.defaultValue) - ..setRow(row) - ..setColumn(column); - - cells.add(MapEntry(column.field, cell)); - } - - row.cells.addEntries(cells); - } - } - - /// Delete [PlutoCell] with matching [columns.field] from [PlutoRow.cells]. - /// When a column is deleted, the corresponding [PlutoCell] is also called to be deleted. - void _removeCellsInRows(List columns) { - for (var row in iterateAllRowAndGroup) { - for (var column in columns) { - row.cells.remove(column.field); - } - } - } - - /// If there is a [PlutoColumn.frozen] column in [columns], - /// check the width limit of the frozen column. - /// If there are more frozen columns in [columns] than the width limit, - /// they are updated in order to unfreeze them. - void _updateLimitedFrozenColumns(List columns) { - double accumulateWidth = 0; - - for (final column in columns) { - if (_limitFrozenColumn( - column.frozen, - column.width + accumulateWidth, - )) { - column.frozen = PlutoColumnFrozen.none; - } - - if (column.frozen.isFrozen) { - accumulateWidth += column.width; - } - } - } - - /// Change the value of [PlutoColumn.hide] of [columns] to [hide]. - /// - /// When there is a frozen column when it is unhidden in a hidden state, - /// it is limited to the width of the frozen column area. - /// Updated to unfreeze [PlutoColumn.frozen]. - void _updateLimitedHideColumns(List columns, bool hide) { - double accumulateWidth = 0; - - for (final column in columns) { - if (hide == column.hide) { - continue; - } - - if (limitHideColumn(column, hide, accumulateWidth: accumulateWidth)) { - column.frozen = PlutoColumnFrozen.none; - } - - if (column.frozen.isFrozen) { - accumulateWidth += column.width; - } - - column.hide = hide; - } - } - - void _updateAfterHideColumn({ - required List columns, - required bool notify, - }) { - refColumns.update(); - - resetCurrentState(notify: false); - - resetShowFrozenColumn(); - - if (!columnSizeConfig.restoreAutoSizeAfterHideColumn) { - deactivateColumnsAutoSize(); - } - - updateRowGroupByHideColumn(columns); - - updateVisibilityLayout(); - - notifyListeners(notify, hideColumn.hashCode); - } - - bool _updateResizeColumns({ - required PlutoColumn column, - required double offset, - }) { - if (offset == 0 || columnsResizeMode.isNone || columnsResizeMode.isNormal) { - return false; - } - - final columns = showFrozenColumn - ? leftFrozenColumns + bodyColumns + rightFrozenColumns - : refColumns; - - final resizeHelper = getColumnsResizeHelper( - columns: columns, - column: column, - offset: offset, - ); - - return resizeHelper.update(); - } - - double _getEffectiveButtonWidth(BuildContext context, - {bool checkBox = false}) { - final theme = Theme.of(context); - late double width; - switch (theme.materialTapTargetSize) { - case MaterialTapTargetSize.padded: - width = kMinInteractiveDimension; - break; - case MaterialTapTargetSize.shrinkWrap: - width = kMinInteractiveDimension - 8.0; - break; - } - if (!checkBox) { - return width; - } - return width + theme.visualDensity.baseSizeAdjustment.dx; - } -} +import 'dart:collection'; +import 'dart:math' as math; + +import 'package:flutter/material.dart'; + +import '../../../pluto_grid_plus.dart'; +import '../../ui/cells/pluto_default_cell.dart'; + +abstract class IColumnState { + /// Columns provided at grid start. + List get columns; + + FilteredList get refColumns; + + /// Column index list. + List get columnIndexes; + + /// List of column indexes in which the sequence is maintained + /// while the frozen column is visible. + List get columnIndexesForShowFrozen; + + /// Width of the entire column. + double get columnsWidth; + + /// Left frozen columns. + List get leftFrozenColumns; + + /// Left frozen column Index List. + List get leftFrozenColumnIndexes; + + /// Width of the left frozen column. + double get leftFrozenColumnsWidth; + + /// Right frozen columns. + List get rightFrozenColumns; + + /// Right frozen column Index List. + List get rightFrozenColumnIndexes; + + /// Width of the right frozen column. + double get rightFrozenColumnsWidth; + + /// Body columns. + List get bodyColumns; + + /// Body column Index List. + List get bodyColumnIndexes; + + /// Width of the body column. + double get bodyColumnsWidth; + + /// Column of currently selected cell. + PlutoColumn? get currentColumn; + + /// Column field name of currently selected cell. + String? get currentColumnField; + + bool get hasSortedColumn; + + PlutoColumn? get getSortedColumn; + + /// Column Index List by frozen Column + List get columnIndexesByShowFrozen; + + /// Toggle whether the column is frozen or not. + /// + /// When [column] is changed to a frozen column, + /// the [PlutoColumn.frozen] is not changed if the frozen column width constraint is insufficient. + /// Unfreeze the existing frozen or widen the entire grid width + /// to set it wider than the frozen column width constraint. + void toggleFrozenColumn(PlutoColumn column, PlutoColumnFrozen frozen); + + /// Toggle column sorting. + /// + /// It works when you tap the title area of a column. + /// When called, [PlutoGrid.onSorted] callback is called. (If registered) + /// + /// [sortAscending], [sortDescending], [sortBySortIdx] also sort the column, + /// but do not call the [PlutoGrid.onSorted] callback. + void toggleSortColumn(PlutoColumn column); + + /// Index of [column] in [columns] + /// + /// Depending on the state of the frozen column, the column order index + /// must be referenced with the columnIndexesByShowFrozen function. + int? columnIndex(PlutoColumn column); + + /// Insert [columns] at [columnIdx] position. + /// + /// If there is a [PlutoColumn.frozen.isFrozen] column in [columns], + /// If the width constraint of the frozen column is greater than the range, + /// the columns are unfreeze in order. + void insertColumns(int columnIdx, List columns); + + void removeColumns(List columns); + + /// Move [column] position to [targetColumn]. + /// + /// In case of [column.frozen.isNone] and [targetColumn.frozen.isFroze], + /// If the width constraint of a frozen column is narrow, it cannot be moved. + void moveColumn({ + required PlutoColumn column, + required PlutoColumn targetColumn, + }); + + /// Resize column size + /// + /// In case of [column.frozen.isFrozen], + /// it is not changed if the width constraint of the frozen column is narrow. + void resizeColumn(PlutoColumn column, double offset); + + /// {@template autoFitColumn} + /// Auto fit a specific column. + /// {@endtemplate} + void autoFitColumn(BuildContext context, PlutoColumn column); + + /// {@template autoFitAllColumns} + /// Auto fit all columns. + /// + /// If no columns are specified, use the column from the state. + /// {@endtemplate} + void autoFitAllColumns(BuildContext context, + [Iterable? columns]); + + /// Hide or show the [column] with [hide] value. + /// + /// When [column.frozen.isFrozen] and [hide] is false, + /// [column.frozen] is changed to [PlutoColumnFrozen.none] + /// if the frozen column width constraint is narrow. + void hideColumn( + PlutoColumn column, + bool hide, { + bool notify = true, + }); + + /// Hide or show the [columns] with [hide] value. + /// + /// When [column.frozen.isFrozen] in [columns] and [hide] is false, + /// [column.frozen] is changed to [PlutoColumnFrozen.none] + /// if the frozen column width constraint is narrow. + void hideColumns( + List columns, + bool hide, { + bool notify = true, + }); + + void sortAscending(PlutoColumn column, {bool notify = true}); + + void sortDescending(PlutoColumn column, {bool notify = true}); + + void sortBySortIdx(PlutoColumn column, {bool notify = true}); + + void showSetColumnsPopup(BuildContext context); + + /// When expanding the width of the freeze column, + /// check the width constraint of the freeze column. + bool limitResizeColumn(PlutoColumn column, double offset); + + /// When moving from a non-frozen column to a frozen column area, + /// check the frozen column width constraint. + bool limitMoveColumn({ + required PlutoColumn column, + required PlutoColumn targetColumn, + }); + + /// When changing the value of [PlutoColumn.frozen], + /// check the frozen column width constraint. + /// + /// [frozen] is the value you want to change. + bool limitToggleFrozenColumn(PlutoColumn column, PlutoColumnFrozen frozen); + + /// When changing a column from hidden state to unhidden state, + /// Check the constraint on the frozen column. + /// If the hidden column is a frozen column + /// The width of the currently frozen column is limited. + bool limitHideColumn( + PlutoColumn column, + bool hide, { + double accumulateWidth = 0, + }); +} + +mixin ColumnState implements IPlutoGridState { + @override + List get columns => List.from(refColumns, growable: false); + + @override + List get columnIndexes => List.generate( + refColumns.length, + (index) => index, + growable: false, + ); + + @override + List get columnIndexesForShowFrozen { + final leftIndexes = []; + final bodyIndexes = []; + final rightIndexes = []; + final length = refColumns.length; + + for (int i = 0; i < length; i += 1) { + refColumns[i].frozen.isNone + ? bodyIndexes.add(i) + : refColumns[i].frozen.isStart + ? leftIndexes.add(i) + : rightIndexes.add(i); + } + + return leftIndexes + bodyIndexes + rightIndexes; + } + + @override + double get columnsWidth { + double width = 0; + + for (final column in refColumns) { + width += column.width; + } + + return width; + } + + @override + List get leftFrozenColumns { + return refColumns.where((e) => e.frozen.isStart).toList(growable: false); + } + + @override + List get leftFrozenColumnIndexes { + final indexes = []; + final length = refColumns.length; + + for (int i = 0; i < length; i += 1) { + if (refColumns[i].frozen.isStart) { + indexes.add(i); + } + } + + return indexes; + } + + @override + double get leftFrozenColumnsWidth { + double width = 0; + + for (final column in refColumns) { + if (column.frozen.isStart) { + width += column.width; + } + } + + return width; + } + + @override + List get rightFrozenColumns { + return refColumns.where((e) => e.frozen.isEnd).toList(); + } + + @override + List get rightFrozenColumnIndexes { + final indexes = []; + final length = refColumns.length; + + for (int i = 0; i < length; i += 1) { + if (refColumns[i].frozen.isEnd) { + indexes.add(i); + } + } + + return indexes; + } + + @override + double get rightFrozenColumnsWidth { + double width = 0; + + for (final column in refColumns) { + if (column.frozen.isEnd) { + width += column.width; + } + } + + return width; + } + + @override + List get bodyColumns { + return refColumns.where((e) => e.frozen.isNone).toList(); + } + + @override + List get bodyColumnIndexes { + final indexes = []; + final length = refColumns.length; + + for (int i = 0; i < length; i += 1) { + if (refColumns[i].frozen.isNone) { + indexes.add(i); + } + } + + return indexes; + } + + @override + double get bodyColumnsWidth { + double width = 0; + + for (final column in refColumns) { + if (column.frozen.isNone) { + width += column.width; + } + } + + return width; + } + + @override + PlutoColumn? get currentColumn { + return currentCell?.column; + } + + @override + String? get currentColumnField { + return currentCell?.column.field; + } + + @override + bool get hasSortedColumn { + for (final column in refColumns) { + if (column.sort.isNone == false) { + return true; + } + } + + return false; + } + + @override + PlutoColumn? get getSortedColumn { + for (final column in refColumns) { + if (column.sort.isNone == false) { + return column; + } + } + + return null; + } + + @override + List get columnIndexesByShowFrozen { + return showFrozenColumn ? columnIndexesForShowFrozen : columnIndexes; + } + + @override + void toggleFrozenColumn(PlutoColumn column, PlutoColumnFrozen frozen) { + if (limitToggleFrozenColumn(column, frozen)) { + return; + } + + column.frozen = column.frozen.isFrozen ? PlutoColumnFrozen.none : frozen; + + resetCurrentState(notify: false); + + resetShowFrozenColumn(); + + if (!columnSizeConfig.restoreAutoSizeAfterFrozenColumn) { + deactivateColumnsAutoSize(); + } + + updateVisibilityLayout(); + + if (onColumnsMoved != null) { + onColumnsMoved!(PlutoGridOnColumnsMovedEvent( + idx: refColumns.indexOf(column), + visualIdx: columnIndex(column)!, + columns: [column], + )); + } + + notifyListeners(true, toggleFrozenColumn.hashCode); + } + + @override + void toggleSortColumn(PlutoColumn column) { + final oldSort = column.sort; + + if (column.sort.isNone) { + sortAscending(column, notify: false); + } else if (column.sort.isAscending) { + sortDescending(column, notify: false); + } else { + sortBySortIdx(column, notify: false); + } + + _callOnSorted(column, oldSort); + + notifyListeners(true, toggleSortColumn.hashCode); + } + + @override + int? columnIndex(PlutoColumn column) { + final columnIndexes = columnIndexesByShowFrozen; + final length = columnIndexes.length; + + for (int i = 0; i < length; i += 1) { + if (refColumns[columnIndexes[i]].field == column.field) { + return i; + } + } + + return null; + } + + @override + void insertColumns(int columnIdx, List columns) { + if (columns.isEmpty) { + return; + } + + if (columnIdx < 0 || refColumns.length < columnIdx) { + return; + } + + _updateLimitedFrozenColumns(columns); + + if (columnIdx >= refColumns.originalLength) { + refColumns.addAll(columns); + } else { + refColumns.insertAll(columnIdx, columns); + } + + _fillCellsInRows(columns); + + resetCurrentState(notify: false); + + resetShowFrozenColumn(); + + if (!columnSizeConfig.restoreAutoSizeAfterInsertColumn) { + deactivateColumnsAutoSize(); + } + + updateVisibilityLayout(); + + notifyListeners(true, insertColumns.hashCode); + } + + @override + void removeColumns(List columns) { + if (columns.isEmpty) { + return; + } + + removeColumnsInColumnGroup(columns, notify: false); + + removeColumnsInFilterRows(columns, notify: false); + + removeColumnsInRowGroupByColumn(columns, notify: false); + + _removeCellsInRows(columns); + + final removeKeys = Set.from(columns.map((e) => e.key)); + + refColumns.removeWhereFromOriginal( + (column) => removeKeys.contains(column.key), + ); + + resetShowFrozenColumn(); + + if (!columnSizeConfig.restoreAutoSizeAfterRemoveColumn) { + deactivateColumnsAutoSize(); + } + + updateVisibilityLayout(); + + resetCurrentState(notify: false); + + notifyListeners(true, removeColumns.hashCode); + } + + @override + void moveColumn({ + required PlutoColumn column, + required PlutoColumn targetColumn, + }) { + if (limitMoveColumn(column: column, targetColumn: targetColumn)) { + return; + } + + final foundIndexes = _findIndexOfColumns([column, targetColumn]); + + if (foundIndexes.length != 2) { + return; + } + + int index = foundIndexes[0]; + + int targetIndex = foundIndexes[1]; + + final frozen = refColumns[index].frozen; + + final targetFrozen = refColumns[targetIndex].frozen; + + if (frozen != targetFrozen) { + if (targetFrozen.isEnd && index > targetIndex) { + targetIndex += 1; + } else if (targetFrozen.isStart && index < targetIndex) { + targetIndex -= 1; + } else if (frozen.isStart && index > targetIndex) { + targetIndex += 1; + } else if (frozen.isEnd && index < targetIndex) { + targetIndex -= 1; + } + } + + refColumns[index].frozen = targetFrozen; + + var columnToMove = refColumns[index]; + + refColumns.removeAt(index); + + refColumns.insert(targetIndex, columnToMove); + + updateCurrentCellPosition(notify: false); + + resetShowFrozenColumn(); + + if (!columnSizeConfig.restoreAutoSizeAfterMoveColumn) { + deactivateColumnsAutoSize(); + } + + updateVisibilityLayout(); + + if (onColumnsMoved != null) { + onColumnsMoved!(PlutoGridOnColumnsMovedEvent( + idx: targetIndex, + visualIdx: columnIndex(columnToMove)!, + columns: [columnToMove], + )); + } + + notifyListeners(true, moveColumn.hashCode); + } + + @override + void resizeColumn(PlutoColumn column, double offset) { + if (columnsResizeMode.isNone || !column.enableDropToResize) { + return; + } + + if (limitResizeColumn(column, offset)) { + return; + } + + bool updated = false; + + if (columnsResizeMode.isNormal) { + final setWidth = column.width + offset; + + column.width = math.max(setWidth, column.minWidth); + + updated = setWidth == column.width; + } else { + updated = _updateResizeColumns(column: column, offset: offset); + } + + if (updated == false) { + return; + } + + deactivateColumnsAutoSize(); + + notifyResizingListeners(); + + scrollByDirection( + PlutoMoveDirection.right, + correctHorizontalOffset, + ); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + activateColumnsAutoSize(); + }); + } + + @override + void autoFitColumn(BuildContext context, PlutoColumn column) { + String maxValue = ''; + bool hasExpandableRowGroup = false; + for (final row in refRows) { + final cell = row.cells.entries + .firstWhere((element) => element.key == column.field) + .value; + var value = column.formattedValueForDisplay(cell.value); + if (hasRowGroups) { + if (PlutoDefaultCell.showGroupCount(rowGroupDelegate!, cell)) { + final groupCountValue = + PlutoDefaultCell.groupCountText(rowGroupDelegate!, row); + if (groupCountValue.isNotEmpty) { + value = '$value $groupCountValue'; + } + } + + hasExpandableRowGroup |= + PlutoDefaultCell.canExpand(rowGroupDelegate!, cell); + } + if (maxValue.length < value.length) { + maxValue = value; + } + } + + // Get size after rendering virtually + // https://stackoverflow.com/questions/54351655/flutter-textfield-width-should-match-width-of-contained-text + final titleTextWidth = + _visualTextWidth(column.title, style.columnTextStyle); + final maxValueTextWidth = _visualTextWidth(maxValue, style.cellTextStyle); + + // todo : Handle (renderer) width + + final calculatedTileWidth = titleTextWidth - + column.width + + [ + (column.titlePadding ?? style.defaultColumnTitlePadding).horizontal, + if (column.enableRowChecked) + _getEffectiveButtonWidth(context, checkBox: true), + if (column.isShowRightIcon) style.iconSize, + 8, + ].reduce((acc, a) => acc + a); + + final calculatedCellWidth = maxValueTextWidth - + column.width + + [ + (column.cellPadding ?? style.defaultCellPadding).horizontal, + if (hasExpandableRowGroup) _getEffectiveButtonWidth(context), + if (column.enableRowChecked) + _getEffectiveButtonWidth(context, checkBox: true), + if (column.isShowRightIcon) style.iconSize, + 2, + ].reduce((acc, a) => acc + a); + + resizeColumn(column, math.max(calculatedTileWidth, calculatedCellWidth)); + + notifyResizingListeners(); + } + + @override + void autoFitAllColumns(BuildContext context, + [Iterable? columns]) { + // If no columns are specified, use the column from the state. + columns ??= this.columns; + + for (final column in columns) { + autoFitColumn(context, column); + } + } + + double _visualTextWidth(String text, TextStyle style) { + if (text.isEmpty) return 0; + final painter = TextPainter( + text: TextSpan( + style: style, + text: text, + ), + textDirection: isRTL ? TextDirection.rtl : TextDirection.ltr, + )..layout(); + return painter.width; + } + + @override + void hideColumn( + PlutoColumn column, + bool hide, { + bool notify = true, + }) { + if (column.hide == hide) { + return; + } + + if (limitHideColumn(column, hide)) { + column.frozen = PlutoColumnFrozen.none; + } + + column.hide = hide; + + _updateAfterHideColumn(columns: [column], notify: notify); + } + + @override + void hideColumns( + List columns, + bool hide, { + bool notify = true, + }) { + if (columns.isEmpty) { + return; + } + + _updateLimitedHideColumns(columns, hide); + + _updateAfterHideColumn(columns: columns, notify: notify); + } + + @override + void sortAscending(PlutoColumn column, {bool notify = true}) { + _updateBeforeColumnSort(); + + column.sort = PlutoColumnSort.ascending; + + if (sortOnlyEvent) return; + + compare(a, b) => column.type.compare( + a.cells[column.field]!.valueForSorting, + b.cells[column.field]!.valueForSorting, + ); + + if (enabledRowGroups) { + sortRowGroup(column: column, compare: compare); + } else { + refRows.sort(compare); + } + + notifyListeners(notify, sortAscending.hashCode); + } + + @override + void sortDescending(PlutoColumn column, {bool notify = true}) { + _updateBeforeColumnSort(); + + column.sort = PlutoColumnSort.descending; + + if (sortOnlyEvent) return; + + compare(b, a) => column.type.compare( + a.cells[column.field]!.valueForSorting, + b.cells[column.field]!.valueForSorting, + ); + + if (enabledRowGroups) { + sortRowGroup(column: column, compare: compare); + } else { + refRows.sort(compare); + } + + notifyListeners(notify, sortDescending.hashCode); + } + + @override + void sortBySortIdx(PlutoColumn column, {bool notify = true}) { + _updateBeforeColumnSort(); + + if (sortOnlyEvent) return; + + int compare(a, b) { + if (a.sortIdx == null || b.sortIdx == null) { + if (a.sortIdx == null && b.sortIdx == null) { + return 0; + } + + return a.sortIdx == null ? -1 : 1; + } + + return a.sortIdx!.compareTo(b.sortIdx!); + } + + if (enabledRowGroups) { + sortRowGroup(column: column, compare: compare); + } else { + refRows.sort(compare); + } + + notifyListeners(notify, sortBySortIdx.hashCode); + } + + @override + void showSetColumnsPopup(BuildContext context) { + const titleField = 'title'; + const columnField = 'field'; + + final columns = [ + PlutoColumn( + title: configuration.localeText.setColumnsTitle, + field: titleField, + type: PlutoColumnType.text(), + enableRowChecked: true, + enableEditingMode: false, + enableDropToResize: true, + enableContextMenu: false, + enableColumnDrag: false, + backgroundColor: configuration.style.filterHeaderColor), + PlutoColumn( + title: 'hidden column', + field: columnField, + type: PlutoColumnType.text(), + hide: true, + ), + ]; + + final toRow = _toRowByColumnField( + titleField: titleField, + columnField: columnField, + ); + + final rows = refColumns.originalList.map(toRow).toList(growable: false); + + void handleOnRowChecked(PlutoGridOnRowCheckedEvent event) { + if (event.isAll) { + hideColumns(refColumns.originalList, event.isChecked != true); + } else { + final checkedField = event.row!.cells[columnField]!.value.toString(); + final checkedColumn = refColumns.originalList.firstWhere( + (column) => column.field == checkedField, + ); + hideColumn(checkedColumn, event.isChecked != true); + } + } + + PlutoGridPopup( + context: context, + configuration: configuration.copyWith( + style: configuration.style.copyWith( + gridBorderRadius: configuration.style.gridPopupBorderRadius, + enableRowColorAnimation: false, + oddRowColor: const PlutoOptional(null), + evenRowColor: const PlutoOptional(null), + ), + ), + columns: columns, + rows: rows, + width: 200, + height: 500, + mode: PlutoGridMode.popup, + onLoaded: (e) { + e.stateManager.setSelectingMode(PlutoGridSelectingMode.none); + }, + onRowChecked: handleOnRowChecked, + ); + } + + @override + bool limitResizeColumn(PlutoColumn column, double offset) { + if (offset <= 0) { + return false; + } + + return _limitFrozenColumn(column.frozen, offset); + } + + @override + bool limitMoveColumn({ + required PlutoColumn column, + required PlutoColumn targetColumn, + }) { + if (column.frozen.isFrozen) { + return false; + } + + return _limitFrozenColumn(targetColumn.frozen, column.width); + } + + @override + bool limitToggleFrozenColumn(PlutoColumn column, PlutoColumnFrozen frozen) { + if (column.frozen.isFrozen) { + return false; + } + + return _limitFrozenColumn(frozen, column.width); + } + + @override + bool limitHideColumn( + PlutoColumn column, + bool hide, { + double accumulateWidth = 0, + }) { + if (hide == true) { + return false; + } + + return _limitFrozenColumn( + column.frozen, + column.width + accumulateWidth, + ); + } + + /// Check the width limit before changing the PlutoColumnFrozen value. + /// + /// In the following situations, need to check the frozen column width limit. + /// 1. Change the width of the frozen column + /// 2. Set a non-frozen column to a frozen column + /// 3. If the column to be unhidden in the hidden state is a frozen column + /// 4. Add a frozen column + /// + /// [frozen] The value to be changed. + /// + /// [offsetWidth] The size to be changed. Usually [PlutoColumn.width]. + /// Check the width limit of the frozen column + /// by subtracting the offsetWidth value from the total width of the grid. + /// Assume that a column has been added by subtracting the [offsetWidth] value + /// from the total width while no column has been added yet. + bool _limitFrozenColumn( + PlutoColumnFrozen frozen, + double offsetWidth, + ) { + if (frozen.isNone) { + return false; + } + + return !enoughFrozenColumnsWidth(maxWidth! - offsetWidth); + } + + void _updateBeforeColumnSort() { + clearCurrentCell(notify: false); + + clearCurrentSelecting(notify: false); + + // Reset column sort to none. + for (var i = 0; i < refColumns.originalList.length; i += 1) { + refColumns.originalList[i].sort = PlutoColumnSort.none; + } + } + + List _findIndexOfColumns(List findColumns) { + SplayTreeMap found = SplayTreeMap(); + + for (int i = 0; i < refColumns.length; i += 1) { + for (int j = 0; j < findColumns.length; j += 1) { + if (findColumns[j].key == refColumns[i].key) { + found[j] = i; + continue; + } + } + + if (findColumns.length == found.length) { + break; + } + } + + return found.values.toList(); + } + + PlutoRow Function(PlutoColumn column) _toRowByColumnField({ + required String titleField, + required String columnField, + }) { + return (PlutoColumn column) { + return PlutoRow( + cells: { + titleField: PlutoCell(value: column.titleWithGroup), + columnField: PlutoCell(value: column.field), + }, + checked: !column.hide, + ); + }; + } + + /// [PlutoGrid.onSorted] Called when a callback is registered. + void _callOnSorted(PlutoColumn column, PlutoColumnSort oldSort) { + if (sortOnlyEvent) { + eventManager!.addEvent( + PlutoGridChangeColumnSortEvent(column: column, oldSort: oldSort), + ); + } + + if (onSorted == null) { + return; + } + + onSorted!(PlutoGridOnSortedEvent(column: column, oldSort: oldSort)); + } + + /// Add [PlutoCell] to the whole [PlutoRow.cells]. + /// Called when a new column is added. + void _fillCellsInRows(List columns) { + for (var row in iterateAllRowAndGroup) { + final List> cells = []; + + for (var column in columns) { + final cell = PlutoCell(value: column.type.defaultValue) + ..setRow(row) + ..setColumn(column); + + cells.add(MapEntry(column.field, cell)); + } + + row.cells.addEntries(cells); + } + } + + /// Delete [PlutoCell] with matching [columns.field] from [PlutoRow.cells]. + /// When a column is deleted, the corresponding [PlutoCell] is also called to be deleted. + void _removeCellsInRows(List columns) { + for (var row in iterateAllRowAndGroup) { + for (var column in columns) { + row.cells.remove(column.field); + } + } + } + + /// If there is a [PlutoColumn.frozen] column in [columns], + /// check the width limit of the frozen column. + /// If there are more frozen columns in [columns] than the width limit, + /// they are updated in order to unfreeze them. + void _updateLimitedFrozenColumns(List columns) { + double accumulateWidth = 0; + + for (final column in columns) { + if (_limitFrozenColumn( + column.frozen, + column.width + accumulateWidth, + )) { + column.frozen = PlutoColumnFrozen.none; + } + + if (column.frozen.isFrozen) { + accumulateWidth += column.width; + } + } + } + + /// Change the value of [PlutoColumn.hide] of [columns] to [hide]. + /// + /// When there is a frozen column when it is unhidden in a hidden state, + /// it is limited to the width of the frozen column area. + /// Updated to unfreeze [PlutoColumn.frozen]. + void _updateLimitedHideColumns(List columns, bool hide) { + double accumulateWidth = 0; + + for (final column in columns) { + if (hide == column.hide) { + continue; + } + + if (limitHideColumn(column, hide, accumulateWidth: accumulateWidth)) { + column.frozen = PlutoColumnFrozen.none; + } + + if (column.frozen.isFrozen) { + accumulateWidth += column.width; + } + + column.hide = hide; + } + } + + void _updateAfterHideColumn({ + required List columns, + required bool notify, + }) { + refColumns.update(); + + resetCurrentState(notify: false); + + resetShowFrozenColumn(); + + if (!columnSizeConfig.restoreAutoSizeAfterHideColumn) { + deactivateColumnsAutoSize(); + } + + updateRowGroupByHideColumn(columns); + + updateVisibilityLayout(); + + notifyListeners(notify, hideColumn.hashCode); + } + + bool _updateResizeColumns({ + required PlutoColumn column, + required double offset, + }) { + if (offset == 0 || columnsResizeMode.isNone || columnsResizeMode.isNormal) { + return false; + } + + final columns = showFrozenColumn + ? leftFrozenColumns + bodyColumns + rightFrozenColumns + : refColumns; + + final resizeHelper = getColumnsResizeHelper( + columns: columns, + column: column, + offset: offset, + ); + + return resizeHelper.update(); + } + + double _getEffectiveButtonWidth(BuildContext context, + {bool checkBox = false}) { + final theme = Theme.of(context); + late double width; + switch (theme.materialTapTargetSize) { + case MaterialTapTargetSize.padded: + width = kMinInteractiveDimension; + break; + case MaterialTapTargetSize.shrinkWrap: + width = kMinInteractiveDimension - 8.0; + break; + } + if (!checkBox) { + return width; + } + return width + theme.visualDensity.baseSizeAdjustment.dx; + } +} From 0ab15d7c33c212abe83bd16b0a949db9e3c2f434 Mon Sep 17 00:00:00 2001 From: Stan Persoons <147701137+stan-at-work@users.noreply.github.com> Date: Sat, 11 Jan 2025 14:15:00 +0100 Subject: [PATCH 2/2] fix formatting for unrelated file changs --- example/lib/main.dart | 45 +++++++++---------- .../event/pluto_grid_row_hover_event.dart | 3 +- lib/src/manager/state/hovering_state.dart | 12 ++--- lib/src/manager/state/layout_state.dart | 6 +-- .../plugin/pluto_infinity_scroll_rows.dart | 3 +- lib/src/plugin/pluto_lazy_pagination.dart | 2 +- lib/src/pluto_grid_configuration.dart | 7 ++- lib/src/ui/cells/pluto_date_cell.dart | 3 +- .../state/hovering_row_state_test.dart | 8 ++-- 9 files changed, 45 insertions(+), 44 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index dad9d280d..78c17d30a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -170,29 +170,28 @@ class _PlutoGridExamplePageState extends State { body: Container( padding: const EdgeInsets.all(15), child: PlutoGrid( - columns: columns, - rows: rows, - columnGroups: columnGroups, - onLoaded: (PlutoGridOnLoadedEvent event) { - stateManager = event.stateManager; - stateManager.setShowColumnFilter(true); - }, - onChanged: (PlutoGridOnChangedEvent event) { - print(event); - }, - configuration: const PlutoGridConfiguration(), - selectDateCallback: (PlutoCell cell, PlutoColumn column) async { - return showDatePicker( - context: context, - initialDate: PlutoDateTimeHelper.parseOrNullWithFormat( - cell.value, - column.type.date.format, - ) ?? DateTime.now(), - firstDate: column.type.date.startDate ?? DateTime(0), - lastDate: column.type.date.endDate ?? DateTime(9999) - ); - } - ), + columns: columns, + rows: rows, + columnGroups: columnGroups, + onLoaded: (PlutoGridOnLoadedEvent event) { + stateManager = event.stateManager; + stateManager.setShowColumnFilter(true); + }, + onChanged: (PlutoGridOnChangedEvent event) { + print(event); + }, + configuration: const PlutoGridConfiguration(), + selectDateCallback: (PlutoCell cell, PlutoColumn column) async { + return showDatePicker( + context: context, + initialDate: PlutoDateTimeHelper.parseOrNullWithFormat( + cell.value, + column.type.date.format, + ) ?? + DateTime.now(), + firstDate: column.type.date.startDate ?? DateTime(0), + lastDate: column.type.date.endDate ?? DateTime(9999)); + }), ), ); } diff --git a/lib/src/manager/event/pluto_grid_row_hover_event.dart b/lib/src/manager/event/pluto_grid_row_hover_event.dart index 4987d459f..6f9abbd75 100644 --- a/lib/src/manager/event/pluto_grid_row_hover_event.dart +++ b/lib/src/manager/event/pluto_grid_row_hover_event.dart @@ -12,7 +12,8 @@ class PlutoGridRowHoverEvent extends PlutoGridEvent { @override void handler(PlutoGridStateManager stateManager) { - bool enableRowHoverColor = stateManager.configuration.style.enableRowHoverColor; + bool enableRowHoverColor = + stateManager.configuration.style.enableRowHoverColor; // only change current hovered row index // if row hover color effect is enabled diff --git a/lib/src/manager/state/hovering_state.dart b/lib/src/manager/state/hovering_state.dart index 59b30b646..21abfe000 100644 --- a/lib/src/manager/state/hovering_state.dart +++ b/lib/src/manager/state/hovering_state.dart @@ -1,13 +1,9 @@ import 'package:pluto_grid_plus/pluto_grid_plus.dart'; abstract class IHoveringState { - int? get hoveredRowIdx; - void setHoveredRowIdx( - int? rowIdx, - {bool notify = true} - ); + void setHoveredRowIdx(int? rowIdx, {bool notify = true}); bool isRowIdxHovered(int rowIdx); } @@ -24,9 +20,9 @@ mixin HoveringState implements IPlutoGridState { @override void setHoveredRowIdx( - int? rowIdx, - {bool notify = true,} - ) { + int? rowIdx, { + bool notify = true, + }) { if (hoveredRowIdx == rowIdx) { return; } diff --git a/lib/src/manager/state/layout_state.dart b/lib/src/manager/state/layout_state.dart index 339dd710a..f9946e7c9 100644 --- a/lib/src/manager/state/layout_state.dart +++ b/lib/src/manager/state/layout_state.dart @@ -386,10 +386,8 @@ mixin LayoutState implements IPlutoGridState { double offset = 0; if (showFrozenColumn) { - offset += - leftFrozenColumnsWidth > 0 ? gridBorderWidth: 0; - offset += - rightFrozenColumnsWidth > 0 ? gridBorderWidth : 0; + offset += leftFrozenColumnsWidth > 0 ? gridBorderWidth : 0; + offset += rightFrozenColumnsWidth > 0 ? gridBorderWidth : 0; } return offset; diff --git a/lib/src/plugin/pluto_infinity_scroll_rows.dart b/lib/src/plugin/pluto_infinity_scroll_rows.dart index 756d0232f..13f340582 100644 --- a/lib/src/plugin/pluto_infinity_scroll_rows.dart +++ b/lib/src/plugin/pluto_infinity_scroll_rows.dart @@ -203,7 +203,8 @@ class _PlutoInfinityScrollRowsState extends State { if (!_isLast) { WidgetsBinding.instance.addPostFrameCallback((_) { if (scroll.hasClients && scroll.position.maxScrollExtent == 0) { - var lastRow = stateManager.rows.isNotEmpty ? stateManager.rows.last : null; + var lastRow = + stateManager.rows.isNotEmpty ? stateManager.rows.last : null; _update(lastRow); } }); diff --git a/lib/src/plugin/pluto_lazy_pagination.dart b/lib/src/plugin/pluto_lazy_pagination.dart index 6126c8cc4..4dd915d0d 100644 --- a/lib/src/plugin/pluto_lazy_pagination.dart +++ b/lib/src/plugin/pluto_lazy_pagination.dart @@ -180,7 +180,7 @@ class _PlutoLazyPaginationState extends State { ), ) .then((data) { - if(!mounted)return; + if (!mounted) return; stateManager.scroll.bodyRowsVertical!.jumpTo(0); stateManager.refRows.clearFromOriginal(); diff --git a/lib/src/pluto_grid_configuration.dart b/lib/src/pluto_grid_configuration.dart index c3bed1f8b..0c932002c 100644 --- a/lib/src/pluto_grid_configuration.dart +++ b/lib/src/pluto_grid_configuration.dart @@ -642,7 +642,8 @@ class PlutoGridStyleConfig { defaultColumnFilterPadding ?? this.defaultColumnFilterPadding, defaultCellPadding: defaultCellPadding ?? this.defaultCellPadding, columnTextStyle: columnTextStyle ?? this.columnTextStyle, - columnUnselectedColor: columnUnselectedColor ?? this.columnUnselectedColor, + columnUnselectedColor: + columnUnselectedColor ?? this.columnUnselectedColor, columnActiveColor: columnActiveColor ?? this.columnActiveColor, cellUnselectedColor: cellUnselectedColor ?? this.cellUnselectedColor, cellActiveColor: cellActiveColor ?? this.cellActiveColor, @@ -1784,12 +1785,16 @@ class PlutoGridLocaleText { enum PlutoGridRowSelectionCheckBoxBehavior { /// Selecting a row does nothing to its checkbox none, + /// Automatically enables the checkbox of the selected rows checkRow, + /// Automatically toggles the checkbox of the selected rows toggleCheckRow, + /// Automatically enabels the checkbox of a selected row (if another row is checked via select, the previous one is unchecked) singleRowCheck, + /// Automatically toggles the checkbox of a selected row (if another row is checked via select, the previous one is unchecked) toggleSingleRowCheck, } diff --git a/lib/src/ui/cells/pluto_date_cell.dart b/lib/src/ui/cells/pluto_date_cell.dart index cf8d3d628..1d197096b 100644 --- a/lib/src/ui/cells/pluto_date_cell.dart +++ b/lib/src/ui/cells/pluto_date_cell.dart @@ -52,7 +52,8 @@ class PlutoDateCellState extends State final date = await sm.selectDateCallback!(widget.cell, widget.column); isOpenedPopup = false; if (date != null) { - handleSelected(widget.column.type.date.dateFormat.format(date)); // Consider call onSelected + handleSelected(widget.column.type.date.dateFormat + .format(date)); // Consider call onSelected } } else { PlutoGridDatePicker( diff --git a/test/src/manager/state/hovering_row_state_test.dart b/test/src/manager/state/hovering_row_state_test.dart index da52c2540..e5a7ee6ab 100644 --- a/test/src/manager/state/hovering_row_state_test.dart +++ b/test/src/manager/state/hovering_row_state_test.dart @@ -38,7 +38,7 @@ void main() { group('setHoveredRowIdx', () { test( 'If the rowIdx passed as an argument is the same as' - 'hoveredRowIdx, then notifyListeners should not be called.', + 'hoveredRowIdx, then notifyListeners should not be called.', () { // given stateManager.setHoveredRowIdx(1); @@ -55,7 +55,7 @@ void main() { test( 'If the rowIdx passed as an argument is different from ' - 'hoveredRowIdx, notifyListeners should be called.', + 'hoveredRowIdx, notifyListeners should be called.', () { // given stateManager.setHoveredRowIdx(1); @@ -73,8 +73,8 @@ void main() { test( 'If the rowIdx passed as an argument is different from ' - 'hoveredRowIdx, but notify is false,' - 'notifyListeners should not be called.', + 'hoveredRowIdx, but notify is false,' + 'notifyListeners should not be called.', () { // given stateManager.setHoveredRowIdx(1);