Repository navigation
Conversation
- Move action configuration into completed handler so connections are established after the KPart is fully loaded - Use dynamic property guard (_kpw_connected) to prevent duplicate lambda connections (Qt::UniqueConnection doesn't work with lambdas) - Persist checkable action states to QSettings INI file - Restore settings using trigger() to change Gwenview's internal zoom state, not just checkbox visual state - Handle zoom radio pair specially during restore to avoid Gwenview fighting back when we manipulate checkbox states directly - Add Ctrl+Shift+S shortcut routing for Save As - Remove aggressive uncheck→check-other radio logic that conflicted with manual zoom in/out (custom zoom is a valid state)
Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Focus/input: - Add lc_focus (5) to the shared wlxplugin.h SDK header, previously missing entirely on this branch, so ListSendCommand could handle DC's focus hand-off signal at all. - Port QtWlPlugin::FocusManager (from wayland_qt_base) into officeview for both the x2t/PdfViewerWidget and LibreOfficeKit/LOKContainerWidget paths, replacing ad-hoc focus handling that left keyboard shortcuts and panel switching unreliable. - Fix a click-activation bug where the ancestry check used the wrong QObject in the propagation chain (QWidgetWindow isn't a QWidget), causing some documents to require two clicks before Ctrl+C worked. - Add a grace-period guard (setActiveFromHost) so DC's own lc_focus(0) can't immediately undo a legitimate click-based activation. - Retry LOK's getTextSelection after SelectAll instead of a single fixed wait, fixing intermittent empty-clipboard copies. - Scope LOK copy/context-menu actions to the specific sheet/slide clicked or currently visible, instead of whatever part LOK's internal cursor last happened to be on. Rendering: - Give ODF documents their own LibreOfficeKit-side zoom, copy, and context menu instead of silently borrowing x2t's, since ODF stays on LibreOffice deliberately (better fidelity than x2t for this format family). - Fix spreadsheet sheet-tab extraction to exclude hidden sheets, which otherwise mismatched x2t's actual visible-sheet PDF output and silently hid the tab bar. - Add per-sheet pagination for xlsx/xlsm: convert each sheet separately (patching its own temp copy's activeTab so x2t paginates it fully, instead of squeezing every sheet onto a single page via printPages:all) and merge with qpdf, tracking real per-sheet start pages instead of assuming one page per sheet. - Switch the PDF view to always use MultiPage mode; SinglePage mode hid a sheet's later pages behind the tab bar with no obvious way to reach them. - Apply 2x supersampled LOK tile rendering to reduce font aliasing. Config: - Add a per-extension file size limit (officeview.conf), skipping conversion entirely for files over the limit; 0 disables an extension. - Restructure officeview.conf into [Paths]/[Engines]/[FileSizeLimits] sections (previously a flat list that only grew entries lazily, so it never had all supported extensions) with paths first and a comment explaining the size-limit semantics. Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
QPdfView's page-layout/coordinate-mapping internals are private API with no public hook to map a click to a page + point-within-page, which meant copy was limited to whole-page/whole-selection text with no way to highlight or select an arbitrary run of text. MuPDF's structured-text API (fz_highlight_selection / fz_copy_selection) exposes that directly. - Add MuPdfWidget/MuPdfContainerWidget, mirroring LOKWidget/ LOKContainerWidget's structure (continuous page stacking, only visible pages rendered, same FocusManager/zoom/wheel-event wiring) so all three rendering paths share one interface for ListSendCommand. - Remove PdfViewerWidget and the Qt6::Pdf/PdfWidgets dependency entirely; replace the one QPdfDocument use (page counting for the per-sheet merge pipeline) with a small MuPDF-based helper. - Fix tab-bar desync: sheetStartPages holds page *indices*, but three places compared it directly against the scrollbar's pixel position, so a few pixels of scroll could exceed a page-index number like 14 or 15 and snap the active tab to the last sheet. Convert through pageYOffset()/pageAtY() so both sides of every comparison share units. - Fix font aliasing on fractional-scaling displays: page rendering had no devicePixelRatio handling, so Qt stretched the under-resolution image up to fill the widget's real physical pixels. Render at zoom*devicePixelRatioF() and mark the QImage's devicePixelRatio so Qt draws it 1:1 instead of scaling it. Links dynamically against the system libmupdf for now; static linking is a separate follow-up. Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Also update the compiled binary to match the current MuPDF-based build. Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
rclone's Google Drive VFS mount leaves native Google Docs/Sheets/Slides as 0-byte stub files on disk, since it can't materialize their content transparently on read like a regular file. When ListLoad hits a 0-byte file for a supported extension, it now checks /proc/mounts for a covering fuse.rclone mount, and if found runs `rclone copyto` to export the real content before handing it to the usual OOXML/ODF rendering path. If the file isn't under an rclone mount, it fails gracefully with a short message instead of trying to render nothing. Google Drive exports get their own independently configurable engine preference (EngineForGDrive), and any of the four Engines keys can now be set to Disabled to skip that format family entirely. The file-size-limit disable sentinel changes from 0 to -1, since 0 is now a legitimate size for an unmaterialized rclone stub rather than something to reject outright. README updated to document all of the above. Signed-off-by: Peter P. Lupo <pplupo@gmail.com>
Drag-and-drop never started. ArchiveModel did not override flags(), so items lacked Qt::ItemIsDragEnabled -- which QAbstractItemView checks before it will call startDrag() at all. The drag implementation was therefore unreachable code, however the view was configured. Confirmed by asserting the flag on a populated model: NO before, YES after. Extraction could freeze Double Commander. Three compounding causes, all addressed rather than guessing which one fired: * The wait was a `while (!finished) processEvents()` spin with no exit if the terminal signal never arrived, so any failure to emit it presented as a frozen file manager instead of an error -- the worst failure mode for the one operation here that writes to disk. Replaced with a real QEventLoop that quits on the signal. * The progress dialog was Qt::WindowModal against a top-level window that belongs to Double Commander, not to Qt. Qt's modality cannot reason about a foreign LCL window; blocking on it is a good way to wedge the host. It is now non-modal, and the view is disabled for the duration instead, which is the containment actually wanted. * The directory chooser was the native one, which goes out to the desktop portal. A portal dialog raised from a plugin inside a non-Qt host is a known hang. Now uses Qt's own dialog. Also adds a re-entrancy guard: extraction runs a nested event loop, so a second request from a menu click or a drag while one is in flight would have nested two loops over one extractor. Detect string: drop JAR and EPUB, add EAR and ACE as requested. Note libarchive has no ACE reader, so .ace files are probed, declined, and handed back to DC for another viewer to handle. Removes the right-hand detail panel and its ini key; the window is now just the contents grid, filter box and status bar.
Adds a GTK3 build of the lister alongside the Qt6 one. Both are driven by the same archiveview::Scanner and the same archiveview::EntryTree, so the decisions about what the user is told -- hostile member names shown verbatim, duplicate paths given their own rows, synthesised directories, packed sizes and CRCs from the ZIP central directory, encryption indicators -- exist once and cannot drift apart. Two pieces moved into the core to make that true rather than aspirational: * EntryTree, the member hierarchy, previously built inside the Qt model. It brackets each group of sibling insertions through a listener because Qt needs beginInsertRows() *before* the mutation while GTK emits row-inserted *after*; one traversal satisfies both. The Qt model is now a view onto it and still passes all 61 existing checks, which is what establishes the shared tree behaves as the verified one did. * EntryFormat, the cell text. Sizes and timestamps stay locale-aware via QLocale on the Qt side; everything where a wrong string would mislead -- mode bits, ratio, CRC, blank-versus-zero -- comes from the core. The GTK model is a hand-written GtkTreeModel over EntryTree, not a GtkTreeStore. A GtkTreeStore copies every cell into itself, which for a 100k-entry archive duplicates everything the tree already holds -- the same mistake the predecessor made with QTableWidget, in another toolkit. Node pointers are stable for the tree's lifetime, so ITERS_PERSIST is honest, and the view runs in fixed-height mode so row heights are not measured 100k times. Scanner callbacks arrive on a worker thread and GTK may only be touched from the main loop, so each one is bounced through g_idle_add. A queued handler can outlive the view, so the payload holds a weak_ptr and a destroyed view makes the handler a no-op rather than a use-after-free. Verified by tests/gtk_host.cpp, the GTK counterpart to wlx_host: it dlopen()s the built .wlx and walks the model through the public GtkTreeModel API as GtkTreeView would, checking get_path/get_iter round-trip -- the pair a hand-written model most easily gets wrong. It lists 100100 rows from many.zip in 1.75s, reloads through ListLoadNext, declines non-archives, and survives null handles and teardown. Suite is now 62 checks. Scope is listing: extraction, drag-out and the filter box remain Qt-only for now. Neither variant links the other's toolkit -- verified with ldd.
Brings the GTK3 variant level with the Qt6 one: live filter box, context menu, extract-selection-to-directory, open-with-default-application, and drag-out as text/uri-list. Extraction and drag go through the same archiveview::Extractor, so the three layers of path-containment checks are the ones already tested, not a second implementation. Nested extraction uses a GMainLoop rather than a `while (...) gtk_main_iteration()` spin, for the reason the Qt variant now does the same: a spin has no exit if the terminal callback never arrives, so a failure to report presents as a frozen file manager instead of an error. The same re-entrancy guard is here too. Two bugs found by the GTK host harness, both real: * Row duplication. EntryTree attached a whole batch and announced it afterwards, which suits Qt (beginInsertRows must precede the mutation) but violates GtkTreeModel: a model must not expose a row before announcing it, because GtkTreeModelFilter builds its level cache from row-inserted *and* independently enumerates what the model already holds. A 110-row fixture reported 217 through the filter, then 321 when the emission was made recursive. EntryTree now has two modes -- Grouped for Qt, Immediate for GTK, attaching and announcing one node at a time, parent before child. 110 = 110. * Quadratic scan cost. With the filter permanently between the view and the model, every insertion reindexed a filter level: ~1.1 s of overhead at 10k rows, extrapolating past ten minutes at 100k, which is how it was found -- the suite timed out. The filter is now attached only while something is typed in the box. 100k rows list in 2.8 s. Also drops EXT="ACE" from both variants. libarchive ships no ACE reader, so the extension only bought a probe that always declined. Reading ACE would mean the unace decoder: a binary-only tool rather than a library, proprietary-licensed, unmaintained since ~2005, and the codebase behind CVE-2018-20250 -- the traversal RCE that made WinRAR drop ACE support outright. Not something to statically link into a file manager's process. Suite is 62 checks; the GTK case now also asserts the filter narrows and restores, and that the drag source advertises text/uri-list.
Selecting a directory and extracting produced only the directory itself. The Qt widget found children by walking the view's rows, which looks equivalent to walking the tree and is not. Qt returns selections in the name column, and only column-0 indexes have children -- rowCount() on a column-1 parent is 0 by contract, and ArchiveModel honours that. So the descent terminated immediately. Demonstrated on a model holding one directory with three children: rowCount is 3 at column 0 and 0 at column 1, which is the index selectedRows(NameColumn) hands back. Normalising the column would have fixed the reported symptom and left two siblings of it in place, because a view walk is the wrong source of truth here: it also sees nothing beneath a row in flat mode, and silently skips children hidden by an active filter. What gets written to disk should not depend on what happens to be on screen. Resolution moved to EntryTree::membersUnder(), which expands the selected paths against the whole tree, and both variants now call it. Prefix matching respects the path boundary, so selecting "dir" does not drag in "dirty.txt". Adds tests/select_smoke.cpp and four suite cases over real fixtures: 5000 files under one directory, a directory with a stored entry of its own (1001, not 1000 -- zip -r writes dir7/ alongside its files), a 300-level chain, and the boundary case. End to end, extracting the 5000-member selection puts 5000 files on disk. Suite is 66 checks.
Double-clicking a member now opens it with the default application, in both variants. Directories keep the view's expand/collapse behaviour -- the test is on the entry type rather than on whether an entry record exists, because a directory can carry one. The GTK variant hooks row-activated, so Enter works the same way. The context menu's "Open with default application" becomes a plain "Open", joined by a real "Open with…" chooser. For the chooser the Qt variant calls org.freedesktop.portal.OpenURI.OpenFile with ask=true. That is the portable way to get the *desktop's own* dialog: the portal frontend is a freedesktop standard and each desktop ships its own backend (xdg-desktop-portal-kde, -gtk, -gnome, -hyprland). Qt has no app chooser API, and the alternative -- parsing .desktop files and launching them -- means reimplementing Exec= field expansion and needs QProcess, which cmake/CheckNoSubprocess.cmake forbids for good reason. The file goes over D-Bus as a file descriptor, which is what the interface takes and what lets the portal reach a file in our scratch directory. The call is bounded at two seconds: a portal frontend with no backend installed can leave a request unanswered, and blocking a file manager on that is the failure mode this plugin exists to avoid. When no portal answers, the plugin says so instead of silently launching the default application -- that is not what "Open with…" was asked to do. Verified: OpenURI version 5 responds here, and a call to an absent service is detected as ServiceUnknown, which is the fallback path. The GTK variant uses GtkAppChooserDialog, which is already the desktop's chooser on a GTK desktop and needs no portal at all. Adds Qt6::DBus to the Qt variant. No subprocess is introduced; the guard target still passes.
The columns were laid out at fixed widths and, when DC passed lcp_fittowindow, resized to their contents -- so a listing docked in a wide panel stopped partway across it and left the rest empty. Reported with a screenshot: roughly 580px of a 1370px pane unused, with member names ellipsized to four characters while all that space sat idle. The name column now takes whatever width the others leave over. It is the right column to widen: it is the one that gets ellipsized, and the only one whose useful length varies per archive. A 200px floor keeps it readable when the metadata columns alone overflow a narrow pane, in which case the view scrolls horizontally as before -- hiding columns through the HiddenColumns ini key is the fix for that. Refitting happens on three occasions, and the third is the subtle one: the widget resizing, the first batch of entries arriving, and the viewport resizing. The viewport narrows on its own when the vertical scrollbar appears partway through a large scan, after the widget itself has stopped resizing; without watching for that the columns overshoot by the scrollbar's width and the view scrolls for no reason. Measured on a 100k-entry archive: 1368 used against a 1356px viewport before, exact after. The GTK variant gets the same behaviour from gtk_tree_view_column_set_expand on the name column. wlx_host now sizes the plugin widget to its container the way DC does -- without that the widget kept its default size and any width assertion was meaningless -- and reports used-versus-available width. The suite fails if a listing leaves more than a few pixels of the pane unused.
…ne edition Squashed merge of markdownview-vegalite. Squashed rather than merged so that the branch's intermediate Matplot++ step (5709943, which vendored ~1.07M lines under wlx/markdownview/3rdparty/matplotplusplus/) does not become reachable from master. Later commits on the branch removed that renderer again, so the tree here is identical to a normal merge -- only the vendored blob's ancestry is dropped. Squashed commits: dad317a markdownview: render figures from Vega-Lite specs 5f83f76 markdownview: fix GTK3 build -- missing fcfreetype.h include 4c24c39 markdownview: ship light and vlcharts editions 7c592a4 markdownview: document diagram network dependency, drop unused md4qt 4229777 markdownview: handover notes for the online/offline edition split 106d9bb markdownview: per-notation diagram services, offline edition, HiDPI fix
The repository had no .gitignore, which is how wlx/archiveview/build_tsan (815 files, 23.4 MB of ThreadSanitizer build output) ended up committed. Rules are scoped deliberately rather than by broad extension globs, since several checked-in fixtures would otherwise be caught: wfx/rclone/build/ holds build scripts, plugman/testdata carries a sample .wlx, wlx/logview ships sample.log, and MicroTeX keeps real sources under lib/.
The plugin's .gitignore covered build/ and build_asan/ but not build_tsan/, which is how that build tree came to be committed on this branch. Carried over from PR #3; the build output itself is no longer in this branch's history, so only the ignore rule is needed here.
ListLoad returned null whenever a file failed to parse, so Double Commander silently passed it to the next viewer in the chain with no indication of what was wrong. Every engine already computed a diagnostic and discarded it. Expose it through TextFormatEngine as errorMessage()/errorLine()/errorColumn() and populate it in all six engines: JSON's byte offset is converted to a line/column, XML/YAML/TOML report their own positions, CBOR and INI report a message only. On failure the widget is now built anyway: a banner names the problem and the Text tab shows the raw bytes with the offending line selected. An empty file, or binary content in a text format, still returns null so the host can hand it to another plugin (a .json holding JSON Lines keeps reaching logview). CBOR is exempt from that binary test, being binary. Saving and the Text tab needed guarding as a result: serialize() would have written an empty document over the user's file, and switching tabs would have replaced the raw bytes with a serialization of nothing.
A malformed CSV loads and simply looks wrong, so rather than refusing it the grid now shows a dismissable banner naming what is off and the first few line numbers to go look at: rows whose field count differs from the header, rows with an unclosed quote, and rows containing NUL bytes. Reporting unclosed quotes required fixing the reader first. Both the Qt and GTK loaders parsed one physical line per row, so a quoted field containing newlines was split across several rows and any per-line quote heuristic flagged it. CsvCore gains continuesQuotedField(), and both loaders now fold physical lines into logical records before parsing; the Qt loader tracks how many lines each record spans so the banner still cites real file line numbers. A record left mid-quote only happens at end of file, which is precisely an unterminated quote. escapeField() quoted a field only when it was already quoted or held the separator, so a field containing a newline or a bare quote was written unquoted and split the record in two on the next read. Quote those too. Also drop src/Makefile. It builds plugin.cpp as a lone translation unit, which cannot work since the CsvCore and wlxbase_wlqt extractions: it knows about neither, and ten wlxbase_wlqt headers carry Q_OBJECT and so need moc. Its enca download moved into wlxbase_wlqt's CMake, its dist target writes to a directory that does not exist, and no other CMake-based wlx plugin keeps one. The real build is CMakeLists.txt, which is what build.sh invokes.
…es to pane Standalone images rendered as <p><img></p> with no line-height override, leaving the body stylesheet's full 1.6x line-height as dead space below every image; the paragraph is now centered with line-height:1, matching the treatment diagram/equation figures already had. Oversized images now fit the pane (width, height, or both, whichever is more constraining) while preserving aspect ratio, computed against the image's real on-disk size and spliced directly into the <img> tag's width/height attributes before the HTML is ever handed to setHtml() -- mutating an already-laid-out plain image's QTextImageFormat in place (the first two approaches tried) corrupted real documents containing tables/blockquotes/code-blocks, silently dropping blocks of body text. The pre-existing zoom code only ever mutates images that already carry an explicit size from diagrams/equations for the same reason. Also fixes printDocument() leaving the paint context's palette at the ambient system default instead of this widget's theme-correct one, which could print invisible text on a light document under a dark system theme.
…hr, wrapping) printDocument() painted the page background inside the printer's default inset coordinate space instead of the true physical page, leaving a white border around dark-theme prints; fixed via setFullPage(true) plus a QPageLayout-based fill sized off the printer's own paint-device metrics (a plain pageRect()-derived fill rounded short on the far edges). Also: - clamp plain images to the real print content width (they previously kept their on-screen device-pixel size, which can exceed the printer's own device-pixel scale) - drop the blockquote table's redundant margin-left (already duplicated by its own leading spacer columns), which pushed it past the right margin - override code/quote-block table widths to an explicit fixed value at print time; Qt was resolving their width:100% against the wrong reference once the cloned document got relaid out for the print page - give a <hr> that's the very first block in the document a zero-height spacer to sit below, instead of directly on the page-0 clip boundary, where it wasn't painting at all - wrap long code lines (white-space + word-wrap, set inline since these <pre> tags already carry other inline styles Qt wasn't cascading a stylesheet rule onto) instead of letting them run off and get clipped once the table-width fix above stopped the box from overshooting - add a real half-inch print margin (previously near-zero), filled with the same theme background color as the rest of the page
…Printer printer.setPageMargins() turned out to be a no-op once setFullPage(true) is active -- full-page mode makes the printable area equal the whole physical page, so the printer's own margin concept doesn't apply in that mode. Computing the half-inch inset directly against fullPageSize instead keeps it in the exact same coordinate space the background fill already covers, so it can't reintroduce the white-border bug that motivated setFullPage(true) in the first place.
The Azure apt mirror's package files can lag behind its index by a release, causing 404s on packages like libglib2.0-dev that block the build before any plugin code compiles.
…low flicker Qt::ScrollBarAsNeeded (the default) toggles the vertical scrollbar based on content height, but showing/hiding it changes the viewport's width, which rewraps the text, which can change the total height enough to toggle the scrollbar again -- a reflow oscillation. Documents made of a few long, densely-wrapped paragraphs reflow a lot from that width delta and can land right on the threshold, visibly flickering the scrollbar thumb between two positions from the moment the document opens; a table/bullet/code-heavy document barely reflows from the same delta, which is why this was only visible on some files. Always reserving the scrollbar's width makes the viewport width constant, removing the oscillation.
…e no images resizeEvent() unconditionally debounced into a full reapplyImageSizing() -> setHtml() document rebuild, but that path only exists to re-fit plain <img> tags to the pane's size -- for an image-less document it did real, visible work for zero benefit. Double Commander settling its quick-view panel into its final size fires several resizeEvent()s in quick succession right on load, so an image-less document paid for a full rebuild on each one, landing in the same fraction of a second the scrollbar was still settling after the ScrollBarAlwaysOn fix. Skipping the reload when the raw HTML has no <img> at all removes that work entirely for the documents where it was pure overhead.
… MuPDF resource store
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
I've built GTK versions of almost all the plugins I built for Qt. I don't use them, only QT. Why did I do it? Because the GTK community is large, doublemd deserves to be the most popular file manager out there!!! However, there's such a powerful capability in plugins that I believe you are severely underselling it if you don't advertise them properly and don't stimulate people to build plugins for it. Total Commander has a huge page where you can search for plugins and all. Finding plugins for Double Commander is tough. Please merge into my fork.
Look at what my fork does when you run the CI (check for yourself here https://github.com/pplupo/doublecmd_plugins/releases/tag/v0.01):
Plugins in this release
Each plugin is packaged as its own zip below -- grab only the ones you need.