|
18 | 18 |
|
19 | 19 |
|
20 | 20 | #include "DockedEditor.h" |
| 21 | +#include "DockAreaSanitizer.h" |
21 | 22 | #include "DockAreaTabBar.h" |
22 | 23 | #include "DockAreaWidget.h" |
23 | 24 | #include "DockWidgetTab.h" |
@@ -92,6 +93,17 @@ DockedEditor::DockedEditor(QWidget *parent) : QObject(parent) |
92 | 93 | emit editorActivated(editor); |
93 | 94 | }); |
94 | 95 |
|
| 96 | + // Fire lastTabClosed() once the area is fully empty. dockWidgetRemoved is |
| 97 | + // emitted by CDockManager ONLY from CDockWidget::deleteDockWidget() (the |
| 98 | + // delete-on-close path) — never during drag/split/float — and AFTER the |
| 99 | + // widget and any now-empty dock area are removed, so totalTabCount() reads |
| 100 | + // the settled state here. Both tab kinds use DockWidgetDeleteOnClose, so |
| 101 | + // neither lingers as a hidden phantom in the layout count. |
| 102 | + connect(dockManager, &ads::CDockManager::dockWidgetRemoved, this, [this](ads::CDockWidget *) { |
| 103 | + if (totalTabCount() == 0) |
| 104 | + emit lastTabClosed(); |
| 105 | + }); |
| 106 | + |
95 | 107 | connect(dockManager, &ads::CDockManager::dockAreaCreated, this, [=](ads::CDockAreaWidget* DockArea) { |
96 | 108 | DockedEditorTitleBar *titleBar = qobject_cast<DockedEditorTitleBar *>(DockArea->titleBar()); |
97 | 109 | connect(titleBar, &DockedEditorTitleBar::doubleClicked, this, &DockedEditor::titleBarDoubleClicked); |
@@ -133,6 +145,60 @@ int DockedEditor::count() const |
133 | 145 | return total; |
134 | 146 | } |
135 | 147 |
|
| 148 | +int DockedEditor::totalTabCount() const |
| 149 | +{ |
| 150 | + // Counts every tab of every kind — editors AND nn_previewTab tabs (preview, |
| 151 | + // browser, mini-apps, future kinds) — so it answers "is the editor area |
| 152 | + // empty?" with no per-type branching: a new tab kind is counted for free. |
| 153 | + // |
| 154 | + // We iterate dockContainers() (the main dock manager container PLUS every |
| 155 | + // floating container), NOT just the main container's dockAreaCount(). Today |
| 156 | + // all tab types clear DockWidgetFloatable so none can be torn out into a |
| 157 | + // floating window — but counting only the main container would silently |
| 158 | + // undercount the instant any future tab type is made floatable, reopening |
| 159 | + // the spurious-"New 1" bug in a way that's painful to trace. Iterating all |
| 160 | + // containers makes the count correct regardless of the floatable flag. |
| 161 | + // |
| 162 | + // dockWidgetsCount() (the raw layout count) is correct here rather than |
| 163 | + // openDockWidgetsCount() (which filters !isClosed()): both our tab kinds set |
| 164 | + // DockWidgetDeleteOnClose, so a closed tab is REMOVED from the layout, never |
| 165 | + // left hidden-but-present. There is therefore no closed-but-undeleted |
| 166 | + // phantom to overcount, and this avoids a per-widget isClosed() scan. Each |
| 167 | + // dockWidgetsCount() is O(1) (a layout count), so this stays a cheap walk. |
| 168 | + int total = 0; |
| 169 | + |
| 170 | + for (const ads::CDockContainerWidget *container : dockManager->dockContainers()) { |
| 171 | + for (int i = 0; i < container->dockAreaCount(); ++i) |
| 172 | + total += container->dockArea(i)->dockWidgetsCount(); |
| 173 | + } |
| 174 | + |
| 175 | + return total; |
| 176 | +} |
| 177 | + |
| 178 | +ScintillaNext *DockedEditor::initialEditor() const |
| 179 | +{ |
| 180 | + // Only a reusable scratch tab if it is the sole editor. count() is |
| 181 | + // editors-only (skips nn_previewTab), matching the historic semantics: |
| 182 | + // a lone "New X" alongside e.g. a browser tab is still replaceable. |
| 183 | + if (count() != 1) |
| 184 | + return nullptr; |
| 185 | + |
| 186 | + ScintillaNext *editor = getCurrentEditor(); |
| 187 | + |
| 188 | + // getCurrentEditor() can be null mid-close: the cached pointer is auto- |
| 189 | + // nulled by QPointer when its editor is destroyed. Treat as "none". |
| 190 | + if (editor == nullptr) |
| 191 | + return nullptr; |
| 192 | + |
| 193 | + // Reject anything the user might care about: a temporary buffer, a real or |
| 194 | + // missing file, or a buffer with undo/redo history (i.e. it was edited). |
| 195 | + // Only a truly pristine "New X" survives and may be transparently closed. |
| 196 | + if (editor->isTemporary() || editor->isFile() || editor->canUndo() || editor->canRedo()) |
| 197 | + return nullptr; |
| 198 | + |
| 199 | + return editor; |
| 200 | +} |
| 201 | + |
136 | 202 | QVector<ScintillaNext *> DockedEditor::editors() const |
137 | 203 | { |
138 | 204 | QVector<ScintillaNext *> editors; |
@@ -172,7 +238,13 @@ void DockedEditor::dockWidgetCloseRequested() |
172 | 238 |
|
173 | 239 | ads::CDockAreaWidget *DockedEditor::currentDockArea() const |
174 | 240 | { |
175 | | - return dockManager->focusedDockWidget() ? dockManager->focusedDockWidget()->dockAreaWidget() : latestDockArea.data(); |
| 241 | + ads::CDockWidget *focused = dockManager->focusedDockWidget(); |
| 242 | + ads::CDockAreaWidget *area = focused ? focused->dockAreaWidget() : latestDockArea.data(); |
| 243 | + |
| 244 | + // Collapse a detached-but-not-yet-destroyed area to nullptr (see |
| 245 | + // sanitizeDockArea / DockAreaSanitizer.h for why this prevents the |
| 246 | + // null-container crash in CDockManager::addDockWidget). |
| 247 | + return sanitizeDockArea(area); |
176 | 248 | } |
177 | 249 |
|
178 | 250 | void DockedEditor::addEditor(ScintillaNext *editor) |
|
0 commit comments