From a89aa5c206ea0db296dd97bf7d723e955b518877 Mon Sep 17 00:00:00 2001 From: vesp Date: Wed, 24 Dec 2025 01:56:40 +0100 Subject: [PATCH] WIP: Refactoring window.py for per-tab widgets --- src/window.py | 249 +++++++++----------------------------------------- 1 file changed, 41 insertions(+), 208 deletions(-) diff --git a/src/window.py b/src/window.py index 05aef8f..a2784a7 100644 --- a/src/window.py +++ b/src/window.py @@ -26,12 +26,11 @@ from .history_manager import HistoryManager from .project_manager import ProjectManager from .tab_manager import TabManager from .icon_picker_dialog import IconPickerDialog -from .widgets.header_row import HeaderRow +from .request_tab_widget import RequestTabWidget from .widgets.history_item import HistoryItem from .widgets.project_item import ProjectItem from datetime import datetime import json -import xml.dom.minidom import uuid @@ -40,31 +39,19 @@ class RosterWindow(Adw.ApplicationWindow): __gtype_name__ = 'RosterWindow' # Top bar widgets - method_dropdown = Gtk.Template.Child() - url_entry = Gtk.Template.Child() - send_button = Gtk.Template.Child() new_request_button = Gtk.Template.Child() tab_view = Gtk.Template.Child() tab_bar = Gtk.Template.Child() # Panes main_pane = Gtk.Template.Child() - split_pane = Gtk.Template.Child() # Sidebar widgets projects_listbox = Gtk.Template.Child() add_project_button = Gtk.Template.Child() save_request_button = Gtk.Template.Child() - # Containers for tabs - request_tabs_container = Gtk.Template.Child() - response_tabs_container = Gtk.Template.Child() - - # Status bar - status_label = Gtk.Template.Child() - time_label = Gtk.Template.Child() - - # History + # History (hidden but kept for compatibility) history_listbox = Gtk.Template.Child() def __init__(self, **kwargs): @@ -75,7 +62,8 @@ class RosterWindow(Adw.ApplicationWindow): self.project_manager = ProjectManager() self.tab_manager = TabManager() - # Map AdwTabPage to RequestTab - stores our tab data + # Map AdwTabPage to tab data + self.page_to_widget = {} # AdwTabPage -> RequestTabWidget self.page_to_tab = {} # AdwTabPage -> RequestTab self.current_tab_id = None @@ -90,48 +78,24 @@ class RosterWindow(Adw.ApplicationWindow): self._setup_custom_css() # Setup UI - self._setup_method_dropdown() - self._setup_request_tabs() - self._setup_response_tabs() self._setup_tab_system() - self._load_history() self._load_projects() - # Add initial header row - self._add_header_row() - - # Set split pane position to center after window is shown - self.connect("map", self._on_window_mapped) - # Connect to close-request to warn about unsaved changes self.connect("close-request", self._on_close_request) # Create first tab self._create_new_tab() - def _on_window_mapped(self, widget): - """Set split pane position to center when window is mapped.""" - # Use idle_add to ensure the widget is fully allocated - GLib.idle_add(self._center_split_pane) - - def _center_split_pane(self): - """Center the split pane divider.""" - # Get the allocated width of the paned widget - allocation = self.split_pane.get_allocation() - if allocation.width > 0: - self.split_pane.set_position(allocation.width // 2) - return False # Don't repeat - def _on_close_request(self, window): """Handle window close request - warn if there are unsaved changes.""" - # Save current tab state first - if self.current_tab_id: - current_tab = self.tab_manager.get_tab_by_id(self.current_tab_id) - if current_tab: - current_tab.request = self._build_request_from_ui() - # Check if any tabs have unsaved changes - modified_tabs = [tab for tab in self.tab_manager.tabs if tab.is_modified()] + modified_tabs = [] + for page, widget in self.page_to_widget.items(): + if widget.modified: + tab = self.page_to_tab.get(page) + if tab: + modified_tabs.append(tab) if modified_tabs: # Show warning dialog @@ -194,82 +158,11 @@ class RosterWindow(Adw.ApplicationWindow): Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ) - def _setup_sourceview_theme(self): - """Set up GtkSourceView theme based on system color scheme.""" - # Get the style manager to detect dark mode - style_manager = Adw.StyleManager.get_default() - is_dark = style_manager.get_dark() - - # Get the appropriate color scheme (using classic for more subtle colors) - # You can change these to customize the color theme: - # - 'classic' / 'classic-dark' (subtle, muted colors) - # - 'Adwaita' / 'Adwaita-dark' (GNOME default) - # - 'tango' (colorful) - # - 'solarized-light' / 'solarized-dark' (popular alternative) - # - 'kate' / 'kate-dark' - style_scheme_manager = GtkSource.StyleSchemeManager.get_default() - scheme_name = 'Adwaita-dark' if is_dark else 'Adwaita' - scheme = style_scheme_manager.get_scheme(scheme_name) - - if scheme: - self.response_body_sourceview.get_buffer().set_style_scheme(scheme) - - # Listen for theme changes - style_manager.connect('notify::dark', self._on_theme_changed) - - def _on_theme_changed(self, style_manager, param): - """Handle system theme changes.""" - is_dark = style_manager.get_dark() - style_scheme_manager = GtkSource.StyleSchemeManager.get_default() - scheme_name = 'Adwaita-dark' if is_dark else 'Adwaita' - scheme = style_scheme_manager.get_scheme(scheme_name) - - if scheme: - self.response_body_sourceview.get_buffer().set_style_scheme(scheme) - # Also update request body theme - self.body_sourceview.get_buffer().set_style_scheme(scheme) - - def _setup_request_body_theme(self): - """Set up GtkSourceView theme for request body.""" - style_manager = Adw.StyleManager.get_default() - is_dark = style_manager.get_dark() - - style_scheme_manager = GtkSource.StyleSchemeManager.get_default() - scheme_name = 'Adwaita-dark' if is_dark else 'Adwaita' - scheme = style_scheme_manager.get_scheme(scheme_name) - - if scheme: - self.body_sourceview.get_buffer().set_style_scheme(scheme) - - def _on_request_body_language_changed(self, dropdown, param): - """Handle request body language selection change.""" - selected = dropdown.get_selected() - language_manager = GtkSource.LanguageManager.get_default() - - buffer = self.body_sourceview.get_buffer() - - if selected == 0: # RAW - buffer.set_language(None) - elif selected == 1: # JSON - language = language_manager.get_language('json') - buffer.set_language(language) - elif selected == 2: # XML - language = language_manager.get_language('xml') - buffer.set_language(language) - def _setup_tab_system(self): """Set up the tab system.""" # Connect new request button self.new_request_button.connect("clicked", self._on_new_request_clicked) - # Connect change tracking to detect modifications - self.url_entry.connect("changed", self._on_request_changed) - self.method_dropdown.connect("notify::selected", self._on_request_changed) - self.body_language_dropdown.connect("notify::selected", self._on_request_changed) - # Track body changes - body_buffer = self.body_sourceview.get_buffer() - body_buffer.connect("changed", self._on_request_changed) - def _on_tab_selected(self, tab_view, param): """Handle tab selection change.""" page = tab_view.get_selected_page() @@ -280,31 +173,18 @@ class RosterWindow(Adw.ApplicationWindow): tab = self.page_to_tab.get(page) if tab: self.current_tab_id = tab.id - self._load_request_to_ui(tab.request) - if tab.response: - self._display_response(tab.response) - else: - # Clear response display - self.status_label.set_text("Ready") - self.time_label.set_text("") - buffer = self.response_headers_textview.get_buffer() - buffer.set_text("") - buffer = self.response_body_sourceview.get_buffer() - buffer.set_text("") def _on_tab_close_page(self, tab_view, page): """Handle tab close request.""" - # Get the RequestTab for this page + # Get the RequestTab and widget for this page tab = self.page_to_tab.get(page) - if not tab: + widget = self.page_to_widget.get(page) + + if not tab or not widget: return True # Allow close - # Save current tab state if this is the active tab - if self.current_tab_id == tab.id: - tab.request = self._build_request_from_ui() - # Check if tab has unsaved changes - if tab.is_modified(): + if widget.modified: # Show warning dialog dialog = Adw.AlertDialog() dialog.set_heading("Close Unsaved Request?") @@ -320,6 +200,7 @@ class RosterWindow(Adw.ApplicationWindow): # Remove from our tracking self.tab_manager.close_tab(tab.id) del self.page_to_tab[page] + del self.page_to_widget[page] # Close the page tab_view.close_page_finish(page, True) else: @@ -333,6 +214,7 @@ class RosterWindow(Adw.ApplicationWindow): # No unsaved changes, allow close self.tab_manager.close_tab(tab.id) del self.page_to_tab[page] + del self.page_to_widget[page] # If no tabs left, create a new one if self.tab_view.get_n_pages() == 1: # This page is still counted @@ -340,33 +222,29 @@ class RosterWindow(Adw.ApplicationWindow): return False # Allow close - def _on_request_changed(self, widget, *args): - """Track changes to mark tab as modified.""" - if self.current_tab_id: - current_tab = self.tab_manager.get_tab_by_id(self.current_tab_id) - if current_tab: - # Update the current request in the tab - current_tab.request = self._build_request_from_ui() - # Check if modified - current_tab.modified = current_tab.is_modified() - # Update tab indicator - self._update_tab_indicator(current_tab) def _is_empty_new_request_tab(self): """Check if current tab is an empty 'New Request' tab.""" if not self.current_tab_id: return False - current_tab = self.tab_manager.get_tab_by_id(self.current_tab_id) - if not current_tab: + # Find the current page + page = self.tab_view.get_selected_page() + if not page: + return False + + tab = self.page_to_tab.get(page) + widget = self.page_to_widget.get(page) + + if not tab or not widget: return False # Check if it's a "New Request" (not from saved request) - if current_tab.saved_request_id: + if tab.saved_request_id: return False - # Check if it's empty (no URL, no body, no headers) - request = self._build_request_from_ui() + # Check if it's empty + request = widget.get_request() is_empty = ( not request.url.strip() and not request.body.strip() and @@ -398,52 +276,36 @@ class RosterWindow(Adw.ApplicationWindow): return f"{base_name} (copy {counter})" - def _create_new_tab(self, name="New Request", request=None, saved_request_id=None): - """Create a new tab using AdwTabView.""" + def _create_new_tab(self, name="New Request", request=None, saved_request_id=None, response=None): + """Create a new tab with RequestTabWidget.""" # Create tab in tab manager if not request: request = HttpRequest(method="GET", url="", headers={}, body="", syntax="RAW") - tab = self.tab_manager.create_tab(name, request, saved_request_id) + tab = self.tab_manager.create_tab(name, request, saved_request_id, response) self.current_tab_id = tab.id - # Create a placeholder widget for the tab page - placeholder = Gtk.Box() + # Create RequestTabWidget for this tab + widget = RequestTabWidget(tab.id, request, response) + widget.original_request = tab.original_request + + # Connect to send button + widget.send_button.connect("clicked", lambda btn: self._on_send_clicked(widget)) # Create AdwTabPage - page = self.tab_view.append(placeholder) + page = self.tab_view.append(widget) page.set_title(name) page.set_indicator_activatable(False) - # Store mapping + # Store mappings self.page_to_tab[page] = tab - - # Update indicator if modified - if tab.is_modified(): - page.set_indicator_icon(Gio.ThemedIcon.new("dot-symbolic")) + self.page_to_widget[page] = widget # Select this new page self.tab_view.set_selected_page(page) - # Load request into UI (will be triggered by selection change signal) - return tab.id - def _update_tab_indicator(self, tab): - """Update the indicator for a tab based on its modified state.""" - # Find the page for this tab - page = None - for p, t in self.page_to_tab.items(): - if t.id == tab.id: - page = p - break - - if page: - if tab.is_modified(): - page.set_indicator_icon(Gio.ThemedIcon.new("dot-symbolic")) - else: - page.set_indicator_icon(None) - def _switch_to_tab(self, tab_id): """Switch to a tab by its ID.""" # Find the page for this tab @@ -458,35 +320,6 @@ class RosterWindow(Adw.ApplicationWindow): if page: self.tab_view.close_page(page) - def _load_request_to_ui(self, request): - """Load a request into the UI.""" - # Set method - methods = ["GET", "POST", "PUT", "DELETE"] - if request.method in methods: - self.method_dropdown.set_selected(methods.index(request.method)) - - # Set URL - self.url_entry.set_text(request.url) - - # Clear and set headers - while child := self.headers_listbox.get_first_child(): - self.headers_listbox.remove(child) - - for key, value in request.headers.items(): - self._add_header_row(key, value) - - if not request.headers: - self._add_header_row() - - # Set body - buffer = self.body_sourceview.get_buffer() - buffer.set_text(request.body) - - # Set syntax - syntax_options = ["RAW", "JSON", "XML"] - if request.syntax in syntax_options: - self.body_language_dropdown.set_selected(syntax_options.index(request.syntax)) - def _on_new_request_clicked(self, button): """Handle New Request button click.""" self._create_new_tab()