Skip to content

ADD: CSV/TSV Table Grid Lister Plugin with Inline Find & Replace - #37

Open
pplupo wants to merge 236 commits into
doublecmd:masterfrom
pplupo:csvview
Open

ADD: CSV/TSV Table Grid Lister Plugin with Inline Find & Replace#37
pplupo wants to merge 236 commits into
doublecmd:masterfrom
pplupo:csvview

Conversation

@pplupo

@pplupo pplupo commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

This PR updates the CSV plugin for Double Commander built with Qt6 to visualize, navigate, edit, and export CSV and TSV files in an interactive spreadsheet-like grid (QTableWidget). It also includes a fully integrated, focus-safe Find & Replace utility panel.

Key Features

1. Spreadsheet Grid View & Visuals

  • Grid Rendering: Visualizes files in a spreadsheet layout. Supports optional grid lines and auto-resizing column widths to fit content.
  • Header Row Toggle: Treat the first row of a file as column headers (with sorting capability) or as a regular data row.
  • Text / Source View Mode: Toggle to a raw, unparsed text view of the file with optional word wrap.
  • External Application Integration: Launch the default external system spreadsheet editor directly from the toolbar.

2. Auto-Detection & RFC Parsing

  • Encoding Engine: Auto-detects Cyrillic, UTF-8, Latin, etc. via Enca 1.19 (compiled and statically linked to the plugin library).
  • Double-Quote Parsing: Handles double-quoted fields containing separators (commas/tabs) or newlines, adhering to RFC standard specifications.

3. Edit & Undo/Redo Stack

  • Inline Editing: Double-click any cell to modify its content.
  • Row & Column Operations: Insert empty columns/rows, delete selections, or paste content directly from the clipboard.
  • Interactive Column Reordering: Horizontal drag-and-drop column headers to rearrange columns.
  • Full History: All cell edits, column rearrangements, row/column additions, row/column deletions, and sorting actions are fully managed via a QUndoStack for complete Undo (Ctrl+Z) and Redo (Ctrl+shift+ZorCtrl+Y`) capability.
  • Dirty Indicator: Toolbar indicator ( / *) showing unsaved history changes.

4. Inline Find & Replace Panel

Press Ctrl+F, Ctrl+R, or click the toolbar 🔍 Find/Replace button in the grid view to open a collapsible search panel at the bottom of the layout:

  • Interactive Scope Limits: Confines searches and replacements to All Cells, Selected Cells, Current Column, or Current Row.
  • Flexible Querying: Supports Case Sensitivity, exact cell matching (Match Entire Cell), and Regular Expression (QRegularExpression) patterns.
  • Atomic Replace All: Bundles all replacements in a single transaction under a QUndoStack macro so that they can be undone/redone in a single action.
  • Auto-Quoting Protection: If editing or replacing inserts a separator character (e.g. a comma into a CSV), the plugin automatically sets the cell metadata to wrap the value in double quotes and escape internal quotes on text preview or file save. This metadata change is fully reverted if the edit is undone.

5. Focus & DC Pane Coordination

  • Focus Safety: Implemented click-outside event filtering and synchronous deactivation. When focus moves outside the lister, the plugin safely yields focus to the main container, preventing trapped key events and enabling smooth file list navigation and pane switching in Double Commander.
  • Panel Key Bypass: Prevents Double Commander from trapping navigation and delete keys when typing inside the Find/Replace panel text inputs.

🛠 Build & Installation

  • The plugin compiles under Qt6 using standard makefiles.
  • Configured in build.sh to compile and package csvview_qt6.wlx along with translation files into the release archive under release/wlx/csvview/.
  • Configuration options (Enca language hints, double-quoting handling, grid painting, etc.) are managed in j2969719.ini.

pplupo and others added 30 commits May 12, 2026 10:08
This commit introduces the mpv_wayland WLX (Lister) plugin for Double
Commander. It enables native video playback in the Quick View panel
using the libmpv Render API and QOpenGLWidget. It is specifically
designed for seamless integration on Wayland and HiDPI displays.

Committed files:
- Source code (src/) implementing Qt6/mpv integration
- SDK headers (sdk/) for plugin interface
- CMakeLists.txt for build configuration
- README.md with installation instructions and embedded screenshot
- mpv_wayland.png screenshot asset
- Integration into root build.sh for automated release packaging
This commit introduces the MDK WLX plugin, a high-performance media
viewer powered by the MDK SDK and pure Qt6. It allows instant preview
of video and audio files. A key feature is its out-of-process isolation
via dlopen, ensuring that MDK's dependencies do not conflict with
Double Commander.

Committed files:
- C++ source (plugin.cpp) and Pascal wrappers (mdk.pas, etc.)
- Lazarus project files (.lpi, .lpr)
- Makefile for building the C++ shared object
- SDK headers (sdk/)
- README.md and proposal.md documentation
- Integration into root build.sh for automated release packaging
This commit introduces a specialized log viewer WLX plugin built with
Qt6 and C++20. It is designed to handle massive log files without
freezing Double Commander, utilizing zero-copy file loading (mmap),
fast regex searching (RE2), and a robust focus-isolation architecture
for Wayland.

Committed files:
- Source code (src/) for the log model and viewer widget
- CMakeLists.txt for build configuration
- README.md and detailed design doc (double commander wlx log viewer.md)
- logviewer.png screenshot asset
- Integration into root build.sh for automated release packaging
This commit introduces the kpart WLX plugin. It acts as a host for
KDE KParts, effectively allowing Double Commander to leverage any
installed KDE viewer (like Okular for PDFs or LibreOffice for docs)
directly within the Quick View panel natively on Wayland/Qt6.

Committed files:
- Source code (src/) for KPart hosting logic
- SDK headers (sdk/)
- CMakeLists.txt for build configuration
- README.md with screenshots
- test.svg and screenshot assets (kpart_md.png, kpart_svg.png)
- Integration into root build.sh for automated release packaging
…ete row editing, TSV support, and context menu

- Display CSV/TSV files in an interactive QTableWidget grid
- Auto-detect separator (comma, semicolon, tab); fall back to file extension
- Header Row toggle button: interprets first line as header or data row,
  reloads file on toggle; controls whether copies include the header line
- Ctrl+C: copy selection as TSV (includes header when Header Row is on)
- Right-click context menu: Copy as TSV, Copy as CSV, Delete Selected Rows,
  Insert from Clipboard
- Ctrl+V: insert clipboard rows at selected position; validates column count;
  skips clipboard header line when it matches the current header (Header Row on)
- Delete: remove selected rows
- Ctrl+S: save file; correctly handles editing vs non-editing state without
  disturbing Double Commander focus
- Inline cell editing with Save / Save As / Reload toolbar actions
- Enca encoding auto-detection with Glib conversion fallback
- Double-quoted field parsing per CSV RFC
- F7 search support
- Full README with screenshots, feature docs, and configuration reference
- Attribution to original author j2969719
…nd highlighting

- Export ListSetDefaultParams to retrieve and bind the host's INI file path.
- Add HighlightRule structure and fast RE2-based line matching directly on memory-mapped offsets.
- Create Highlighting Rules SettingsDialog modal with an inline-colored rules list.
- Enable ExtendedSelection on the rules list to support deleting or moving multiple rules up/down, maintaining selection state using QItemSelectionModel.
- Connect double-click on any rule row to open the editing window.
- Write Foreground/Background labels above color picker buttons in RuleDialog.
- Implement session persistence under [HighlightRules] in the INI file.
- Prevent default rules from overriding empty rule configurations on restart.
- Add context-aware "Add Default Rules" button to prepend default log levels relative to the current selection.
- Create sample.log matching each level for diagnostics.
This commit introduces a premium editor WLX plugin based on Native Qt6
and KDE's KTextEditor framework. It brings full syntax highlighting,
code folding, and advanced editing capabilities to Double Commander's
viewer panel without LCL compatibility crashes on Wayland.

Committed files:
- Source code (src/) wrapping KTextEditor
- CMakeLists.txt for build configuration
- README.md with screenshots
- defects.md tracking known issues and resolutions
- Screenshot assets (kate_java.png, kate_md.png, kate_py.png)
- Integration into root build.sh for automated release packaging
…yles, preserve selection, and fix Proper/Title Case logic
…nd highlighting

- Export ListSetDefaultParams to retrieve and bind the host's INI file path.
- Add HighlightRule structure and fast RE2-based line matching directly on memory-mapped offsets.
- Create Highlighting Rules SettingsDialog modal with an inline-colored rules list.
- Enable ExtendedSelection on the rules list to support deleting or moving multiple rules up/down, maintaining selection state using QItemSelectionModel.
- Connect double-click on any rule row to open the editing window.
- Write Foreground/Background labels above color picker buttons in RuleDialog.
- Implement session persistence under [HighlightRules] in the INI file.
- Prevent default rules from overriding empty rule configurations on restart.
- Add context-aware "Add Default Rules" button to prepend default log levels relative to the current selection.
- Create sample.log matching each level for diagnostics.
Manually merged unique changes from all editor-related branches:

- editor-features: focus management (NoFocus on menuBar, focus restoration,
  focus-stealing prevention), Save/Save As/Save Copy As actions with toolbar
  button, Print action with toolbar button, font zoom controls (Ctrl++/Ctrl+-/
  Ctrl+0) with native KTE action fallback, Force RTL Direction toggle, Ctrl+Shift+S
  Save As shortcut precedence fix, QTimer-based focus restoration

- kate-case-conversion: Capitalization submenu replacing flat menu items,
  improved Title Case with minor-word handling, fixed Proper Case (toLower
  instead of preserve), renamed snail_case to snake_case, added PascalCase,
  SCREAMING_SNAKE_CASE, kebab-case, Sentence case, SCREAMING-KEBAB-CASE,
  dot.case, path/case, replaceSelectionPreservingRange helper to restore
  selection after text replacement

- fix-redo-and-write-lock: Ctrl+Shift+Z redo shortcut (in addition to Ctrl+Y),
  extracted toggleReadOnly() helper, Alt+Shift+R shortcut for read-only toggle,
  fixed double-trigger bug by switching QAction::toggled to QAction::triggered,
  added shortcut labels to read-only menu action
…agation

- Remove non-standard `lc_focus` definition from `sdk/wlxplugin.h` and its handler in `wlx_plugin.cpp`.

- Remove custom `hostSetFocus` method from `EditorWidget`.

- Set the editor view as the focus proxy for the main widget.

- Use native Qt event filtering and focus change listeners to handle active state transitions.
This makes the plugin standalone by compiling and linking libenca statically, removing the need for users to manually install libenca.so.0.
Adds ability to insert empty rows or clipboard rows above/below current selection or clicked row.
pplupo and others added 30 commits July 15, 2026 16:22
De-Qtify: extracted CsvCore (src/core/CsvCore.{h,cpp}) — the CSV/TSV
tokenizer (was a free function `parse_line()` already, just QString-typed)
and the field-escaping logic that was inline in saveFile() — as
std::string-based, toolkit-neutral functions. Rewired plugin.cpp's
parse_line() and saveFile() to call into CsvCore instead of duplicating
the algorithm inline; encoding conversion (EncodingUtils, still Qt-typed)
stays exactly where it was, only the tokenize/escape steps moved.
Preserves an existing quirk exactly: parse_line() still returns an empty
list when the (persisted) gQuoted setting is false, matching prior
behavior bit-for-bit rather than silently changing it.

Verified with a standalone functional test (not just syntax-checked):
simple fields, quoted fields containing the separator, doubled-quote
escaping, CRLF stripping, and a full parse->escape->join round-trip. All
passed. Also rebuilt csvview_qt6.wlx against the rewired plugin.cpp to
confirm the wiring itself compiles and links clean.

GTK3 port (src/gtk3/plugin_gtk3.cpp): deliberately scoped down from full
feature parity. csvview_qt6 is built on EditableGridWidget (QTableView +
QUndoStack), FocusManager, and ScopedFindReplacePanel from wlxbase_wlqt —
there is no wlxbase_gtk yet providing equivalent GTK primitives, and
building one properly (undo stack, focus/shortcut plumbing, a shared
find/replace panel) is a real separate investment, not something to
improvise inline for one plugin. This GTK3 build is a self-contained
GtkTreeView grid: load, inline cell editing, save, separator/header
toggles — but no undo/redo and no find/replace panel. Tracked as
follow-up, not silently dropped.

Verified: both csvview_qt6.wlx and csvview_gtk3.wlx build clean and
export the required WLX ABI symbols. Confirmed via ldd that
csvview_gtk3.wlx has ZERO Qt dependency and csvview_core carries zero Qt
symbols — third plugin in the suite (after mdk, diagramview) to actually
ship a pure-GTK binary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the self-contained (scoped-down) GTK3 grid from the previous
commit with a full build on wlxbase_gtk, bringing it to parity with the
Qt6 build's use of FocusManager/EditableGridWidget/ScopedFindReplacePanel:

- GtkFocusManager provides Ctrl+Z/Ctrl+Shift+Z/Ctrl+Y undo/redo shortcuts,
  Ctrl+F to toggle the find panel, and Ctrl+S to save.
- GtkEditableGridWidget replaces the plain GtkTreeView grid — every cell
  edit, row insert, and row delete now goes through the shared undo
  stack, and Ctrl+C copies the selection as tab-separated text via
  GtkClipboard.
- GtkScopedFindReplacePanel provides Find/Replace/Replace All with a
  scope selector ("All Cells" / "Current Column"), matching-case/
  entire-cell/regex options (std::regex, ECMAScript syntax) — wired up
  in this file's doFind/doReplace/doReplaceAll.

CsvCore (tokenizer/serializer) is unchanged from the previous commit.

Verified: full build (Qt6 + GTK3 + wlxbase_gtk all together) succeeds.
Confirmed via ldd that csvview_gtk3.wlx still has zero Qt dependency
despite the much larger feature set. wlxbase_gtk itself was verified
against a live GTK runtime (see its own commit) — undo/redo, insert
rows, and cell editing all confirmed working before being wired in here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tainer_add()

DC's GTK3 ResizeWindow (uwlxmodule.pas) calls GtkLayout.move() on the
widget this plugin returns from ListLoad(), which asserts the widget's
parent is exactly that GtkLayout -- a contract only gtk_layout_put()
satisfies. gtk_container_add() doesn't register the child that way, so
DC's later move/resize calls hit 'gtk_layout_move: assertion
gtk_widget_get_parent (child_widget) == GTK_WIDGET (layout) failed'
and, in some cases, a real crash further down the paint/layout path.

Found via real dcgtk testing against a from-source GTK3 Double Commander
build (v1.3.0 alpha, Lazarus 4.99) -- this only ever surfaces against a
genuine GtkLayout parent, not the synthetic dlopen/ListLoad test harness
used earlier this session, which passes a plain GtkWindow.
…lbar)

The GTK3 UI was written from scratch separately from the Qt6 build and
never reached feature parity with it -- confirmed via a side-by-side
screenshot comparison. Concretely, it was missing a row-number gutter
column entirely, had a manual delimiter dropdown that doesn't exist in
the Qt6 toolbar at all (Qt auto-detects the separator from file
content), and was missing most of the Qt6 toolbar: Save As, Undo,
Redo, Print, Reload, Show Text, Line Wrap, Open Externally (only Save
and Find/Replace existed).

Added:
  - Row-number gutter column (leading GtkTreeViewColumn with a cell
    data func rendering the model row index -- doesn't touch
    GtkEditableGridWidget itself, purely cosmetic in csvview's own
    code, since that widget is shared with dbview/structview).
  - Separator auto-detection matching the Qt6 build exactly: try ','
    ';' '\t' on the first line, keep the first yielding >1 columns,
    else fall back on the .tsv/.csv extension. Manual delimiter
    dropdown removed.
  - Separator/extension-mismatch dialog (Ignore / Fix Separator /
    Rename Extension), matching the Qt6 build's behavior when e.g. a
    .csv file turns out to be tab-separated.
  - Save As (GtkFileChooserDialog with CSV/TSV filters).
  - Undo/Redo toolbar buttons wired to GtkFocusManager's undo stack
    (already existed in GtkFocusManager, just wasn't exposed in the
    toolbar), with sensitivity kept in sync via canUndo()/canRedo().
  - Print via GtkPrintOperation, paginating a monospace tab-joined
    rendering of the visible rows -- simpler than the Qt6 build's HTML
    table render, but functionally equivalent (opens the system print
    dialog, prints the data).
  - Reload button.
  - Show Text toggle: a plain GtkTextView shown in place of the grid
    (unlike Qt6's colorized-by-column rendering, kept monochrome here
    -- scope reduction, not a missing feature parity point that
    matters functionally).
  - Line Wrap toggle, applied to the Show Text view (GtkEditableGridWidget
    doesn't expose a cell-wrap mode, so this doesn't affect the grid
    itself, same practical limitation as before).
  - Open Externally via g_app_info_launch_default_for_uri.
  - Dirty indicator label ("✓"/"●"), matching the Qt6 build's checkmark.

Also: reload now explicitly clears the undo stack (st->fm->clearUndoStack()) --
needed because GtkFocusManager owns the undo stack (not the grid), unlike
Qt6 where EditableGridWidget owns its own per-instance QUndoStack that a
reload implicitly replaces.

Verified: compiles clean in the gtk-dev container; dlopen+ListLoad+
ListSearchText smoke test against a real CSV file loads, finds text,
and closes cleanly (the GTK_LAYOUT cast warning from the synthetic
test harness's plain-GtkWindow parent is expected/unrelated, per the
gtk_layout_put commit on this and other plugin branches). Not yet
retested in the real dcgtk GTK3 Double Commander session.
Follow-up to the earlier toolbar-parity pass -- the GTK3 build still
had zero context menu of any kind, unlike the Qt6 build's row/column
right-click menus (EditableGridWidget::showRowContextMenu/
showColumnContextMenu in wlxbase_wlqt).

Row menu (right-click on the grid body, via a button-press-event
handler on the tree view): Copy Row, Copy Selection as TSV/CSV, Delete
Row(s) (GtkEditableGridWidget::deleteSelectedRows(), already existed,
was just never exposed anywhere in this build), Insert Row Above/Below
(insertRows(), same situation).

Column menu (right-click on a column header, via a button-press-event
handler on each GtkTreeViewColumn's own button widget --
gtk_tree_view_column_get_button()): Insert Column Left/Right, Delete
Column. GtkEditableGridWidget has no column insert/delete API at all
(fixed column count set at construction, unlike its Qt6 sibling) --
extending the shared widget would be a cross-cutting change affecting
dbview/structview too, out of scope for a csvview-only pass. Instead
column operations here rebuild the grid in place from the current
in-memory row data with the column list mutated (rebuildGridColumns()),
the same shape loadFile() already uses when the column count changes
on reload, just driven from the live grid's data instead of
re-parsing the file.

Menu-item callbacks and the column-context closures both hit the same
G_CALLBACK()/preprocessor-vs-template-angle-brackets bug fixed earlier
in wlx/cuda's case-conversion menu (a raw comma inside
std::pair<CsvGtkState*, int> used directly as a macro argument gets
misparsed as an extra argument) -- same fix, a local `using ColCtx =
...` alias.

Known, disclosed scope reduction vs. the Qt6 build's row menu: no
"Copy cell" action (only "Copy Row"), and the "N rows"-aware wording
when multiple rows are selected isn't reproduced -- GtkEditableGridWidget
doesn't expose the current TreeView selection range as a queryable API,
so the row menu currently operates on "the row that was right-clicked"
rather than an arbitrary multi-row selection. Not a crash risk, just a
narrower interaction than Qt6's.

Verified: compiles clean; dlopen+ListLoad+ListSearchText smoke test
loads a CSV, finds text, and closes cleanly with the context-menu
handlers attached. Right-click interaction itself (the actual menu
appearing/functioning) needs real mouse input a headless harness can't
provide -- needs retesting in the real dcgtk session.
textScroll had gtk_widget_set_no_show_all(TRUE) so it stays hidden
until "Show Text" is toggled on. But the toggle handler only called
gtk_widget_show() on the outer scroll container, which does not
propagate into its child GtkTextView since show_all was suppressed for
the whole subtree — the panel appeared empty every time.

Use gtk_widget_show_all() on toggle-on instead, so the text view
itself gets shown along with its container.
…all fix was a no-op)

Real-world testing confirmed the earlier C1 fix (task doublecmd#4) didn't
actually work. Root cause fully understood now: gtk_widget_show_all()
skips a widget's ENTIRE subtree when called directly on a widget that
itself has no_show_all set -- not just when recursing into it as
someone else's descendant. st->textScroll has no_show_all set (so an
ancestor's show_all() at ListLoad time doesn't prematurely reveal it),
so gtk_widget_show_all(st->textScroll) was a complete no-op: neither
textScroll nor the GtkTextView inside it (which, as a result, had
never once been individually shown since creation) ever became
visible.

Fix: use plain gtk_widget_show() on both textScroll and textView
explicitly -- gtk_widget_show() ignores no_show_all entirely, unlike
gtk_widget_show_all().

Verified with a purpose-built harness that dlopens the plugin, drives
the real Show Text toggle button via gtk_toggle_button_set_active()
(not just headless load/close), and checks the actual GtkTextView's
visible/mapped state plus buffer content: textview visible=1,
mapped=1, buffer correctly populated with the CSV data (previously
visible=0, mapped=0 despite the buffer being correctly populated all
along -- updateTextView() was never broken, only the visibility).
…eader click-to-select

Real-world testing reported 'no numbered rows to select or resize
them' and 'unable to select columns'. Investigation found a genuine,
concrete bug behind the first complaint, not just a missing feature:

addRowNumberColumn() inserts a column at treeview position 0 (the '#'
gutter). All three call sites called it BEFORE the title-setting loop
(setColumnTitle(c, ...) for c in 0..colCount-1), but setColumnTitle
addresses columns by raw treeview position via
gtk_tree_view_get_column() -- with the gutter already inserted at 0,
every title-setting call was off by one: setColumnTitle(0, ...)
overwrote the gutter's own '#' title with the first data column's
title, and every subsequent title landed one column to the right of
where its actual data lived. The '#' column effectively vanished
(relabeled as the first data column) and every other header was
wrong. Fixed by moving addRowNumberColumn() to run after the
title-setting loop at both affected call sites (loadFile,
rebuildGridColumns).

Once column titles are correctly aligned, clicking the row-number
gutter already selects that row via GTK's own default GtkTreeView
click handling (confirmed with a real synthetic button-press event,
not just a manual selection API call) -- no additional code was
needed for that part; the title-misalignment bug was the actual
reason it looked broken.

Column selection genuinely didn't exist and needed real code: added
setupColumnSelection() making every data column header clickable,
storing the selected column index in CsvGtkState, and highlighting
every cell in that column via a cell-data-func (GtkTreeSelection has
no native column-selection concept to build on). Clicking a header
toggles that column's selection on/off.

Verified: builds cleanly. Confirmed the title fix directly (a
widget-driving harness lists all real column titles/positions post-fix:
'#', then the actual CSV headers in the correct order and position).
Confirmed row-gutter click-to-select via a genuine synthetic
GDK_BUTTON_PRESS/RELEASE event dispatched through gtk_widget_event()
(not a manual gtk_tree_selection_select_path() call) -- selection
count is 1 afterward. Confirmed column-header 'clicked' signal fires
and runs the highlight-toggle logic without crashing via
g_signal_emit_by_name(); the resulting visual rendering itself can't
be asserted headlessly.
…re insert/delete

User feedback after the initial single-column click-to-select landed:
1. Couldn't select multiple columns.
2. Selecting a column didn't unselect rows and vice versa (both could be
   'selected' at once, an inconsistent state).
3. No N-aware insert/remove rows/columns driven by the actual selection
   size.

Changes:

- st->selectedColumn (int) -> st->selectedColumns (std::set<int>) plus
  columnSelectAnchor, matching how row multi-selection already works.
  Column headers now support Ctrl-click (toggle a column in/out of the
  selection) and Shift-click (range from the last-clicked column).
  Switched from GtkTreeViewColumn's 'clicked' signal (no modifier state
  available) to 'button-press-event' on the header's own button widget
  (already reachable via gtk_tree_view_column_get_button(), the same
  widget attachContextMenus() uses for its right-click handler) so
  Ctrl/Shift state is available.

- Selecting a column now clears row selection (gtk_tree_selection_
  unselect_all(), guarded by suppressRowSelectionSync so it doesn't
  immediately re-trigger the reverse sync) and selecting a row clears
  column selection (GtkTreeSelection's own 'changed' signal, checked
  against suppressRowSelectionSync) -- the two selection modes are now
  mutually exclusive instead of independent.

- showRowContextMenu now reads the real multi-row selection directly
  from GtkTreeSelection (gtk_tree_view_get_selection() on the grid's
  own treeview is directly reachable even though GtkEditableGridWidget
  doesn't expose it) instead of falling back to 'just the clicked row'
  -- 'Insert N Above/Below'/'Delete N Rows' now reflect an actual
  multi-row selection. insertRows()/deleteSelectedRows() were already
  N-aware; the previous 'always operates on 1 row' framing was a
  csvview-side limitation, not a GtkEditableGridWidget one.

- Added insertColumnsAt()/deleteColumns() (N-aware) alongside the
  existing single-column rebuildGridColumns() machinery, and rewired
  showColumnContextMenu() to operate on the full selectedColumns set
  when the right-clicked column is part of it (falling back to just
  the clicked column otherwise). Removed the now-orphaned single-
  column insertColumn()/deleteColumn() helpers, replaced by the N-aware
  versions.

Bug found and fixed during verification: the column header's cell-
data-func and its button-press-event handler were sharing a single
heap-allocated ColSelCtx*, each with its own destroy-notify -- GTK
invokes both independently during widget teardown, so it double-freed
on every ListCloseWindow (reproducibly crashed with 'double free
detected in tcache 2' in the plain headless load/close harness, no
interaction needed to trigger it). Fixed with two separate
allocations, one per destroy-notify site.

Verified: builds cleanly. Confirmed via a purpose-built harness
(test_csvview_multisel.c) with real synthetic button-press events
(including a temporary debug print, since removed, that traced
selectedColumns growing correctly across a plain click then a
Ctrl-click) that multi-select accumulates as expected. Re-ran the
general ListLoad/ResizeWindow/ListCloseWindow harness 3x plus a
click-then-reload-then-close harness (test_csvview_reload.c, exercises
setupColumnSelection()'s second call site in rebuildGridColumns()) --
all clean after the double-free fix, all crashing before it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants