Skip to content

Add "Undo close tab" feature#1342

Open
etude11 wants to merge 1 commit intokiwix:mainfrom
etude11:fix-issue-1154
Open

Add "Undo close tab" feature#1342
etude11 wants to merge 1 commit intokiwix:mainfrom
etude11:fix-issue-1154

Conversation

@etude11
Copy link
Copy Markdown
Contributor

@etude11 etude11 commented Feb 11, 2025

Fixes #1154

@etude11
Copy link
Copy Markdown
Contributor Author

etude11 commented Mar 15, 2025

@kelson42 I have implemented the feature of undo Close Tab. Kindly review.

@kelson42
Copy link
Copy Markdown
Collaborator

kelson42 commented Oct 9, 2025

@etude11 Sorry, somehow this PR got forgotten. I will make the review.

Copilot AI review requested due to automatic review settings December 9, 2025 06:48
@kelson42 kelson42 changed the title feat: (#1154) undo close tab Add "Undo close tab" feature Dec 9, 2025
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/tabbar.cpp
{
int tabIndex = tabAt(event->pos());
if (tabIndex == -1) { // Clicked outside tabs
QMenu menu;
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Suggested change
QMenu menu;
QMenu menu(this);

Copilot uses AI. Check for mistakes.
Comment thread src/tabbar.cpp
{
// 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
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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

Suggested change
// First and last tabs cannot be closed
// First and last tabs (library tab and + button) cannot be closed

Copilot uses AI. Check for mistakes.
Comment thread src/kiwixapp.h
Comment on lines +149 to +158
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();
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread src/kiwixapp.cpp
}

void KiwixApp::pushClosedTab(const QString& url, const QString& title) {
if (url.isEmpty() || title.isEmpty())
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
if (url.isEmpty() || title.isEmpty())
if (url.isEmpty())

Copilot uses AI. Check for mistakes.
Comment thread src/kiwixapp.cpp
void KiwixApp::pushClosedTab(const QString& url, const QString& title) {
if (url.isEmpty() || title.isEmpty())
return;
m_closedTabs.push({url, title});
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment thread src/tabbar.cpp
if (tabIndex == -1) { // Clicked outside tabs
QMenu menu;
menu.addAction(KiwixApp::instance()->getAction(KiwixApp::ReopenClosedTabAction));
menu.exec(event->globalPos());
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Suggested change
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);
}
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

@kelson42 kelson42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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?

Copy link
Copy Markdown
Collaborator

@veloman-yunkan veloman-yunkan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I get from reviewing the code

  1. The tab is reopened without restoring its scroll state,
  2. 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.

Comment thread src/kiwixapp.h

public:
void pushClosedTab(const QString& url, const QString& title);
bool hasClosedTabs() const { return !m_closedTabs.isEmpty(); }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused function

@kelson42
Copy link
Copy Markdown
Collaborator

@etude11 Hi, do you plan to look at this PR again?

@kelson42
Copy link
Copy Markdown
Collaborator

@etude11 Any feedback? Do you plan to improve this PR later?

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.

Feature "Undo Close tab"

4 participants