From 90f9ba905152005bdc08d148307e86dfdd148387 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Meyer Date: Mon, 18 May 2026 21:55:31 +0200 Subject: [PATCH 1/2] feat: add close-all / close-others / close-to-left tab strategy (#83) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit File > Close All Tabs (Ctrl+Shift+W) closes every open tab. Each tab gets a context menu (right-click on tab header) with: - Close All Tabs - Close All But This - Close Tabs to the Left Context menu is attached via a ListChangeListener so all tab types (DocumentTab, ImagePreviewTab, WelcomeTab, ThemeTab…) are covered automatically without touching each creation site. requestCloseTab() extracted from closeActiveTab() so the close-request event (unsaved-changes confirmation) is honoured by all strategies. i18n updated in all 6 language files (FR, EN, DE, ES, IT + default). Co-Authored-By: Claude Sonnet 4.6 --- src/main/java/MarkNote.java | 66 +++++++++++++++++-- src/main/resources/i18n/messages.properties | 3 + .../resources/i18n/messages_de.properties | 3 + .../resources/i18n/messages_en.properties | 3 + .../resources/i18n/messages_es.properties | 3 + .../resources/i18n/messages_fr.properties | 3 + .../resources/i18n/messages_it.properties | 3 + 7 files changed, 77 insertions(+), 7 deletions(-) diff --git a/src/main/java/MarkNote.java b/src/main/java/MarkNote.java index 1fe4316..c812d24 100644 --- a/src/main/java/MarkNote.java +++ b/src/main/java/MarkNote.java @@ -46,6 +46,7 @@ import javafx.scene.image.Image; import javafx.scene.control.Alert; import javafx.scene.control.Button; +import javafx.scene.control.ContextMenu; import javafx.scene.control.ButtonType; import javafx.scene.control.CheckMenuItem; import javafx.scene.control.Menu; @@ -169,6 +170,15 @@ public void start(Stage stage) { mainTabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.ALL_TABS); mainTabPane.setTabDragPolicy(TabPane.TabDragPolicy.REORDER); + // Attacher le menu contextuel à chaque onglet dès son ajout (tous types confondus) + mainTabPane.getTabs().addListener((javafx.collections.ListChangeListener) change -> { + while (change.next()) { + if (change.wasAdded()) { + change.getAddedSubList().forEach(this::installTabContextMenu); + } + } + }); + // Panel de prévisualisation previewPanel = new PreviewPanel(); previewPanel.setOnMarkdownLinkClick(this::openFileInTab); @@ -466,9 +476,13 @@ private MenuBar createMenuBar() { closeTabItem.setAccelerator(KeyCombination.keyCombination("Ctrl+W")); closeTabItem.setOnAction(e -> closeActiveTab()); + MenuItem closeAllTabsItem = new MenuItem(messages.getString("menu.file.closeAll")); + closeAllTabsItem.setAccelerator(KeyCombination.keyCombination("Ctrl+Shift+W")); + closeAllTabsItem.setOnAction(e -> closeAllTabs()); + fileMenu.getItems().addAll(newDocItem, new SeparatorMenuItem(), openProjectItem, openItem, new SeparatorMenuItem(), recentMenu, new SeparatorMenuItem(), saveItem, saveAsItem, - new SeparatorMenuItem(), closeTabItem, quitItem); + new SeparatorMenuItem(), closeTabItem, closeAllTabsItem, new SeparatorMenuItem(), quitItem); // == Menu Affichage == Menu viewMenu = new Menu(messages.getString("menu.view")); @@ -593,15 +607,53 @@ private void addNewDocument() { private void closeActiveTab() { var selected = mainTabPane.getSelectionModel().getSelectedItem(); if (selected != null) { - // Déclencher la logique de fermeture existante (onCloseRequest) - var closeEvent = new javafx.event.Event(javafx.scene.control.Tab.TAB_CLOSE_REQUEST_EVENT); - javafx.event.Event.fireEvent(selected, closeEvent); - if (!closeEvent.isConsumed()) { - mainTabPane.getTabs().remove(selected); - } + requestCloseTab(selected); } } + /** Déclenche l'événement de fermeture sur un onglet et le retire s'il n'est pas consommé. */ + private void requestCloseTab(javafx.scene.control.Tab tab) { + var event = new javafx.event.Event(javafx.scene.control.Tab.TAB_CLOSE_REQUEST_EVENT); + javafx.event.Event.fireEvent(tab, event); + if (!event.isConsumed()) { + mainTabPane.getTabs().remove(tab); + } + } + + /** Ferme tous les onglets. */ + private void closeAllTabs() { + List.copyOf(mainTabPane.getTabs()).forEach(this::requestCloseTab); + } + + /** Ferme tous les onglets sauf {@code referenceTab}. */ + private void closeOtherTabs(javafx.scene.control.Tab referenceTab) { + List.copyOf(mainTabPane.getTabs()).stream() + .filter(t -> t != referenceTab) + .forEach(this::requestCloseTab); + } + + /** Ferme tous les onglets situés à gauche de {@code referenceTab}. */ + private void closeTabsToLeft(javafx.scene.control.Tab referenceTab) { + int refIndex = mainTabPane.getTabs().indexOf(referenceTab); + if (refIndex > 0) { + List.copyOf(mainTabPane.getTabs().subList(0, refIndex)).forEach(this::requestCloseTab); + } + } + + /** Installe un menu contextuel sur un onglet avec les actions de fermeture groupée. */ + private void installTabContextMenu(javafx.scene.control.Tab tab) { + MenuItem closeAllItem = new MenuItem(messages.getString("menu.file.closeAll")); + closeAllItem.setOnAction(e -> closeAllTabs()); + + MenuItem closeOthersItem = new MenuItem(messages.getString("menu.tab.closeOthers")); + closeOthersItem.setOnAction(e -> closeOtherTabs(tab)); + + MenuItem closeToLeftItem = new MenuItem(messages.getString("menu.tab.closeToLeft")); + closeToLeftItem.setOnAction(e -> closeTabsToLeft(tab)); + + tab.setContextMenu(new ContextMenu(closeAllItem, new SeparatorMenuItem(), closeOthersItem, closeToLeftItem)); + } + /** * Affiche l'onglet Welcome avec la liste des projets récents. */ diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 37a0518..94f5398 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -23,6 +23,9 @@ menu.view.networkDiagram=Diagramme réseau menu.view.showWelcome=Afficher Welcome menu.view.console=Debug Console menu.file.close=Fermer l'onglet +menu.file.closeAll=Fermer tous les onglets +menu.tab.closeOthers=Fermer tous sauf celui-ci +menu.tab.closeToLeft=Fermer les onglets à gauche # Menu Aide menu.help=Aide diff --git a/src/main/resources/i18n/messages_de.properties b/src/main/resources/i18n/messages_de.properties index 8659f20..1444b56 100644 --- a/src/main/resources/i18n/messages_de.properties +++ b/src/main/resources/i18n/messages_de.properties @@ -23,6 +23,9 @@ menu.view.networkDiagram=Netzwerkdiagramm menu.view.showWelcome=Willkommen anzeigen menu.view.console=Debug-Konsole menu.file.close=Tab schließen +menu.file.closeAll=Alle Tabs schließen +menu.tab.closeOthers=Alle anderen Tabs schließen +menu.tab.closeToLeft=Tabs links schließen # Hilfe-Menü menu.help=Hilfe diff --git a/src/main/resources/i18n/messages_en.properties b/src/main/resources/i18n/messages_en.properties index d55647f..a083b37 100644 --- a/src/main/resources/i18n/messages_en.properties +++ b/src/main/resources/i18n/messages_en.properties @@ -23,6 +23,9 @@ menu.view.networkDiagram=Network diagram menu.view.showWelcome=Show Welcome menu.view.console=Debug Console menu.file.close=Close tab +menu.file.closeAll=Close All Tabs +menu.tab.closeOthers=Close All But This +menu.tab.closeToLeft=Close Tabs to the Left # Help Menu menu.help=Help diff --git a/src/main/resources/i18n/messages_es.properties b/src/main/resources/i18n/messages_es.properties index 6d0cc59..d0eb802 100644 --- a/src/main/resources/i18n/messages_es.properties +++ b/src/main/resources/i18n/messages_es.properties @@ -23,6 +23,9 @@ menu.view.networkDiagram=Diagrama de red menu.view.showWelcome=Mostrar Bienvenida menu.view.console=Consola de depuración menu.file.close=Cerrar pestaña +menu.file.closeAll=Cerrar todas las pestañas +menu.tab.closeOthers=Cerrar todas menos esta +menu.tab.closeToLeft=Cerrar pestañas a la izquierda # Menú Ayuda menu.help=Ayuda diff --git a/src/main/resources/i18n/messages_fr.properties b/src/main/resources/i18n/messages_fr.properties index 4cf0adb..6d86836 100644 --- a/src/main/resources/i18n/messages_fr.properties +++ b/src/main/resources/i18n/messages_fr.properties @@ -23,6 +23,9 @@ menu.view.networkDiagram=Diagramme réseau menu.view.showWelcome=Afficher Welcome menu.view.console=Console de débogage menu.file.close=Fermer l'onglet +menu.file.closeAll=Fermer tous les onglets +menu.tab.closeOthers=Fermer tous sauf celui-ci +menu.tab.closeToLeft=Fermer les onglets à gauche # Menu Aide menu.help=Aide diff --git a/src/main/resources/i18n/messages_it.properties b/src/main/resources/i18n/messages_it.properties index 26ba6dd..c31feef 100644 --- a/src/main/resources/i18n/messages_it.properties +++ b/src/main/resources/i18n/messages_it.properties @@ -23,6 +23,9 @@ menu.view.networkDiagram=Diagramma di rete menu.view.showWelcome=Mostra Benvenuto menu.view.console=Console di debug menu.file.close=Chiudi scheda +menu.file.closeAll=Chiudi tutte le schede +menu.tab.closeOthers=Chiudi tutte tranne questa +menu.tab.closeToLeft=Chiudi schede a sinistra # Menu Aiuto menu.help=Aiuto From adc116d4e996643096b13fcf827907c9a7d89909 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Meyer Date: Mon, 18 May 2026 21:59:13 +0200 Subject: [PATCH 2/2] docs: document close-all / close-others / close-to-left tab shortcuts - README: expand Close Tab bullet with Ctrl+Shift+W and context menu actions - user-guide-en.md: add Ctrl+Shift+W to keyboard shortcuts table and document the three right-click context menu actions in Working with Tabs Co-Authored-By: Claude Sonnet 4.6 --- README.md | 2 +- src/docs/user-guide-en.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e9337c..0cdd1d2 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ A lightweight and modern Markdown editor built with JavaFX. - **Application Icon** - Custom SVG icon with PNG exports (16, 32, 64, 128 px) shown in the title bar, taskbar, and Alt+Tab switcher; on Linux the SVG is installed into `/usr/share/icons/hicolor/scalable/apps/` and the PNG-128 into `/usr/share/icons/hicolor/128x128/apps/` by the DEB post-install script; the `.desktop` entry includes `StartupWMClass=MarkNote` so GNOME Shell correctly associates running windows with the dock icon - **Reading Mode** - Distraction-free fullscreen reading (`Ctrl+Shift+P` or **View → Enter Reading Mode**): the Preview panel fills the whole screen, the editor and all side panels are hidden; the Project Explorer reappears as a compact floating overlay with a **▾/▴ minimize toggle** to collapse it to its title bar; exiting restores all panels and split-divider positions exactly as they were before - **View Menu Controls** - Toggle visibility of Project Explorer (`Ctrl+E`), Preview (`Ctrl+P`), Tag Cloud (`Ctrl+T`), Network Diagram (`Ctrl+L`), and **LLM Chat** (`Ctrl+M`) via the View menu; Show Welcome -- **Close Tab** - Close the active document tab with `Ctrl+W` +- **Close Tab** - Close the active document tab with `Ctrl+W`; close **all** tabs with `Ctrl+Shift+W`; right-click any tab for **Close All**, **Close All But This**, or **Close Tabs to the Left** ## Supported Languages diff --git a/src/docs/user-guide-en.md b/src/docs/user-guide-en.md index 5120263..c261e73 100644 --- a/src/docs/user-guide-en.md +++ b/src/docs/user-guide-en.md @@ -294,6 +294,11 @@ MarkNote automatically remembers which documents you had open when you close a p - Click on a tab to switch to that document - Click the **×** button on a tab to close it - Press `Ctrl+W` to close the active tab +- Press `Ctrl+Shift+W` to close **all** tabs at once +- **Right-click** on any tab for additional close actions: + - **Close All Tabs** — closes every open tab + - **Close All But This** — keeps only the tab you right-clicked + - **Close Tabs to the Left** — closes all tabs to the left of the current one - **Drag tabs** to reorder them within the tab bar - Modified documents show a **\*** prefix in the tab title - Tab names longer than 15 characters are truncated with an ellipsis (`…`); hover for the full name @@ -1463,6 +1468,7 @@ To change the language: | `Ctrl+S` | Save | | `Ctrl+Shift+S` | Save as | | `Ctrl+W` | Close current tab | +| `Ctrl+Shift+W` | Close all tabs | | `Ctrl+Q` | Quit application | ### Editing