WIP: Refactoring window.py for per-tab widgets

This commit is contained in:
Pavel Baksy 2025-12-24 01:56:40 +01:00
parent 99faf14983
commit a09060e3d4

View File

@ -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()