Add "Undo close tab" feature#1342
Conversation
ea4bacf to
b5849ef
Compare
|
@kelson42 I have implemented the feature of undo Close Tab. Kindly review. |
a4064f4 to
dfcc19b
Compare
|
@etude11 Sorry, somehow this PR got forgotten. I will make the review. |
dfcc19b to
9f32e23
Compare
9f32e23 to
5b00ea8
Compare
There was a problem hiding this comment.
Pull request overview
This PR implements an "undo close tab" feature (#1154) for the Kiwix desktop application. When users close tabs, the tab information (URL and title) is saved to a stack, allowing them to reopen recently closed tabs via a keyboard shortcut (Ctrl+Shift+T) or context menu. The implementation uses a QStack to maintain the history of closed tabs and enables the reopen action when tabs are available.
Key Changes
- Adds a stack-based history of closed tabs with URL and title information
- Enables the previously hidden "Reopen Closed Tab" action and connects it to functionality
- Implements a context menu on the tab bar's empty area to access the reopen action
- Saves tab information before closing ZimView tabs
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| src/tabbar.h | Adds contextMenuEvent override declaration to handle right-click context menu |
| src/tabbar.cpp | Implements tab info saving before closure and context menu for reopening; simplifies comment |
| src/kiwixapp.h | Adds ClosedTabInfo struct, m_closedTabs stack, and public methods for managing closed tabs |
| src/kiwixapp.cpp | Enables ReopenClosedTabAction, implements pushClosedTab/reopenLastClosedTab methods |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| { | ||
| int tabIndex = tabAt(event->pos()); | ||
| if (tabIndex == -1) { // Clicked outside tabs | ||
| QMenu menu; |
There was a problem hiding this comment.
[nitpick] The QMenu menu is created on the stack and will be properly destroyed when it goes out of scope after menu.exec() returns. However, consider using a parent pointer (e.g., QMenu menu(this)) to ensure proper cleanup even if exceptions occur, following Qt best practices for object lifetime management.
| QMenu menu; | |
| QMenu menu(this); |
| { | ||
| // The first and last tabs (i.e. the library tab and the + (new tab) button) | ||
| // cannot be closed | ||
| // First and last tabs cannot be closed |
There was a problem hiding this comment.
[nitpick] The comment was simplified but lost helpful context. The original comment explained what the first and last tabs are (library tab and new tab button), which aids code comprehension. Consider restoring the original comment or keeping at least: // First and last tabs (library tab and + button) cannot be closed
| // First and last tabs cannot be closed | |
| // First and last tabs (library tab and + button) cannot be closed |
| struct ClosedTabInfo { | ||
| QString url; | ||
| QString title; | ||
| }; | ||
| QStack<ClosedTabInfo> m_closedTabs; | ||
|
|
||
| public: | ||
| void pushClosedTab(const QString& url, const QString& title); | ||
| bool hasClosedTabs() const { return !m_closedTabs.isEmpty(); } | ||
| void reopenLastClosedTab(); |
There was a problem hiding this comment.
Avoid adding a new public: access specifier in the middle of the private: section. This breaks the class organization pattern. Move the struct definition and member variable to stay within the private: section, and relocate the three public methods to the existing public: section (before line 116). Following the codebase pattern seen in other files (e.g., contentmanager.h, downloadmanagement.h), you could organize the private section with comments like // types and // data for better clarity.
| } | ||
|
|
||
| void KiwixApp::pushClosedTab(const QString& url, const QString& title) { | ||
| if (url.isEmpty() || title.isEmpty()) |
There was a problem hiding this comment.
The check title.isEmpty() may reject valid tabs with empty titles. Consider saving tabs even with empty titles, as some pages may legitimately have no title yet, or remove only the URL check if that's the critical validation. This could prevent users from reopening legitimately closed tabs.
| if (url.isEmpty() || title.isEmpty()) | |
| if (url.isEmpty()) |
| void KiwixApp::pushClosedTab(const QString& url, const QString& title) { | ||
| if (url.isEmpty() || title.isEmpty()) | ||
| return; | ||
| m_closedTabs.push({url, title}); |
There was a problem hiding this comment.
The m_closedTabs stack has no size limit, which could lead to unbounded memory growth if users close many tabs. Consider implementing a maximum size (e.g., 10-20 tabs) and removing the oldest entries when the limit is reached to prevent memory issues during long sessions.
| if (tabIndex == -1) { // Clicked outside tabs | ||
| QMenu menu; | ||
| menu.addAction(KiwixApp::instance()->getAction(KiwixApp::ReopenClosedTabAction)); | ||
| menu.exec(event->globalPos()); |
There was a problem hiding this comment.
[nitpick] The context menu is only shown when clicking outside tabs (tabIndex == -1), but no context menu is shown when clicking on a tab. Consider adding a context menu for tabs themselves (when tabIndex >= 0) with options like "Close Tab", "Close Other Tabs", etc., which would provide better UX consistency with other tabbed applications.
| menu.exec(event->globalPos()); | |
| menu.exec(event->globalPos()); | |
| } else { // Clicked on a tab | |
| QMenu menu; | |
| QAction* closeTabAction = menu.addAction(tr("Close Tab")); | |
| QAction* closeOtherTabsAction = menu.addAction(tr("Close Other Tabs")); | |
| QAction* closeTabsToRightAction = menu.addAction(tr("Close Tabs to the Right")); | |
| QAction* selectedAction = menu.exec(event->globalPos()); | |
| if (selectedAction == closeTabAction) { | |
| // Close the clicked tab | |
| emit tabCloseRequested(tabIndex); | |
| } else if (selectedAction == closeOtherTabsAction) { | |
| // Close all tabs except the clicked one | |
| for (int i = count() - 1; i >= 0; --i) { | |
| if (i != tabIndex) { | |
| emit tabCloseRequested(i); | |
| } | |
| } | |
| } else if (selectedAction == closeTabsToRightAction) { | |
| // Close all tabs to the right of the clicked one | |
| for (int i = count() - 1; i > tabIndex; --i) { | |
| emit tabCloseRequested(i); | |
| } | |
| } |
kelson42
left a comment
There was a problem hiding this comment.
@etude11 Thank you very much for your PR and sorry again for the very long time before review. To me, the basic feature, it works fine and as requested in the issue.
@veloman-yunkan Can you please do the code review?
veloman-yunkan
left a comment
There was a problem hiding this comment.
From what I get from reviewing the code
- The tab is reopened without restoring its scroll state,
- The reopened tab appears in the end of the tab list rather than at its old position.
Also Copilot has made quite reasonable comments.
Other than that (and an unused public method introduced by the code change) the PR looks good to me.
|
|
||
| public: | ||
| void pushClosedTab(const QString& url, const QString& title); | ||
| bool hasClosedTabs() const { return !m_closedTabs.isEmpty(); } |
|
@etude11 Hi, do you plan to look at this PR again? |
|
@etude11 Any feedback? Do you plan to improve this PR later? |
Fixes #1154