Complete window.py cleanup - remove 400 lines of obsolete code
Removed obsolete UI methods now handled by RequestTabWidget: - _setup_method_dropdown, _setup_request_tabs, _setup_response_tabs - _build_request_from_ui, _load_request_to_ui - _display_response, _display_error - _extract_content_type, _get_language_from_content_type, _format_response_body - on_add_header_clicked, _add_header_row, _on_header_remove - _setup_sourceview_theme, _setup_request_body_theme - _update_tab_indicator (now handled internally by widgets) Added new HTTP handling: - _on_send_clicked now works with RequestTabWidget - Properly handles request/response through widget interface - Updates history after requests complete Result: 1237 lines → 841 lines (-396 lines, -32%) App builds and runs successfully!
This commit is contained in:
parent
a09060e3d4
commit
89d43b66b5
417
src/window.py
417
src/window.py
@ -344,231 +344,37 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
self.add_action(action)
|
self.add_action(action)
|
||||||
self.get_application().set_accels_for_action("win.save-request", ["<Control>s"])
|
self.get_application().set_accels_for_action("win.save-request", ["<Control>s"])
|
||||||
|
|
||||||
def _setup_method_dropdown(self):
|
def _on_send_clicked(self, widget):
|
||||||
"""Populate HTTP method dropdown."""
|
"""Handle Send button click from a tab widget."""
|
||||||
methods = Gtk.StringList()
|
|
||||||
methods.append("GET")
|
|
||||||
methods.append("POST")
|
|
||||||
methods.append("PUT")
|
|
||||||
methods.append("DELETE")
|
|
||||||
self.method_dropdown.set_model(methods)
|
|
||||||
self.method_dropdown.set_selected(0) # Default to GET
|
|
||||||
|
|
||||||
def _setup_request_tabs(self):
|
|
||||||
"""Create request tabs programmatically."""
|
|
||||||
# Create stack for switching between pages
|
|
||||||
self.request_stack = Gtk.Stack()
|
|
||||||
self.request_stack.set_vexpand(True)
|
|
||||||
self.request_stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
|
|
||||||
self.request_stack.set_transition_duration(150)
|
|
||||||
|
|
||||||
# Create stack switcher for the tabs
|
|
||||||
request_switcher = Gtk.StackSwitcher()
|
|
||||||
request_switcher.set_stack(self.request_stack)
|
|
||||||
request_switcher.set_halign(Gtk.Align.CENTER)
|
|
||||||
request_switcher.set_margin_top(8)
|
|
||||||
request_switcher.set_margin_bottom(8)
|
|
||||||
|
|
||||||
# Add switcher and stack to container
|
|
||||||
self.request_tabs_container.append(request_switcher)
|
|
||||||
self.request_tabs_container.append(self.request_stack)
|
|
||||||
|
|
||||||
# Headers tab
|
|
||||||
headers_scroll = Gtk.ScrolledWindow()
|
|
||||||
headers_scroll.set_vexpand(True) # Expand to fill available space
|
|
||||||
headers_scroll.set_min_content_height(150) # Show ~3-4 rows minimum
|
|
||||||
self.headers_listbox = Gtk.ListBox()
|
|
||||||
self.headers_listbox.add_css_class("boxed-list")
|
|
||||||
headers_scroll.set_child(self.headers_listbox)
|
|
||||||
|
|
||||||
# Add header button container
|
|
||||||
headers_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
|
|
||||||
headers_box.set_margin_start(12)
|
|
||||||
headers_box.set_margin_end(12)
|
|
||||||
headers_box.set_margin_top(12)
|
|
||||||
headers_box.set_margin_bottom(12)
|
|
||||||
headers_box.append(headers_scroll)
|
|
||||||
|
|
||||||
self.add_header_button = Gtk.Button(label="Add Header")
|
|
||||||
self.add_header_button.connect("clicked", self.on_add_header_clicked)
|
|
||||||
headers_box.append(self.add_header_button)
|
|
||||||
|
|
||||||
self.request_stack.add_titled(headers_box, "headers", "Headers")
|
|
||||||
|
|
||||||
# Body tab with syntax highlighting
|
|
||||||
body_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
|
|
||||||
|
|
||||||
# SourceView for request body
|
|
||||||
body_scroll = Gtk.ScrolledWindow()
|
|
||||||
body_scroll.set_vexpand(True)
|
|
||||||
self.body_sourceview = GtkSource.View()
|
|
||||||
self.body_sourceview.set_editable(True)
|
|
||||||
self.body_sourceview.set_show_line_numbers(True)
|
|
||||||
self.body_sourceview.set_highlight_current_line(True)
|
|
||||||
self.body_sourceview.set_left_margin(12)
|
|
||||||
self.body_sourceview.set_right_margin(12)
|
|
||||||
self.body_sourceview.set_top_margin(12)
|
|
||||||
self.body_sourceview.set_bottom_margin(12)
|
|
||||||
|
|
||||||
# Apply same font styling as response body
|
|
||||||
css_provider = Gtk.CssProvider()
|
|
||||||
css_provider.load_from_data(b"""
|
|
||||||
textview {
|
|
||||||
font-family: "Source Code Pro";
|
|
||||||
font-size: 12pt;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
self.body_sourceview.get_style_context().add_provider(
|
|
||||||
css_provider,
|
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
|
||||||
)
|
|
||||||
|
|
||||||
# Set up theme for request body
|
|
||||||
self._setup_request_body_theme()
|
|
||||||
|
|
||||||
body_scroll.set_child(self.body_sourceview)
|
|
||||||
body_box.append(body_scroll)
|
|
||||||
|
|
||||||
# Bottom toolbar with language selector
|
|
||||||
toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
|
|
||||||
toolbar.set_margin_start(12)
|
|
||||||
toolbar.set_margin_end(12)
|
|
||||||
toolbar.set_margin_top(6)
|
|
||||||
toolbar.set_margin_bottom(6)
|
|
||||||
|
|
||||||
# Spacer to push dropdown to the right
|
|
||||||
spacer = Gtk.Box()
|
|
||||||
spacer.set_hexpand(True)
|
|
||||||
toolbar.append(spacer)
|
|
||||||
|
|
||||||
# Language selector label and dropdown
|
|
||||||
lang_label = Gtk.Label(label="Syntax:")
|
|
||||||
toolbar.append(lang_label)
|
|
||||||
|
|
||||||
lang_list = Gtk.StringList()
|
|
||||||
lang_list.append("RAW")
|
|
||||||
lang_list.append("JSON")
|
|
||||||
lang_list.append("XML")
|
|
||||||
|
|
||||||
self.body_language_dropdown = Gtk.DropDown(model=lang_list)
|
|
||||||
self.body_language_dropdown.set_selected(0) # Default to RAW
|
|
||||||
self.body_language_dropdown.connect("notify::selected", self._on_request_body_language_changed)
|
|
||||||
toolbar.append(self.body_language_dropdown)
|
|
||||||
|
|
||||||
body_box.append(toolbar)
|
|
||||||
|
|
||||||
self.request_stack.add_titled(body_box, "body", "Body")
|
|
||||||
|
|
||||||
def _setup_response_tabs(self):
|
|
||||||
"""Create response tabs programmatically."""
|
|
||||||
# Create stack for switching between pages
|
|
||||||
self.response_stack = Gtk.Stack()
|
|
||||||
self.response_stack.set_vexpand(True)
|
|
||||||
self.response_stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
|
|
||||||
self.response_stack.set_transition_duration(150)
|
|
||||||
|
|
||||||
# Create stack switcher for the tabs
|
|
||||||
response_switcher = Gtk.StackSwitcher()
|
|
||||||
response_switcher.set_stack(self.response_stack)
|
|
||||||
response_switcher.set_halign(Gtk.Align.CENTER)
|
|
||||||
response_switcher.set_margin_top(8)
|
|
||||||
response_switcher.set_margin_bottom(8)
|
|
||||||
|
|
||||||
# Add switcher and stack to container
|
|
||||||
self.response_tabs_container.append(response_switcher)
|
|
||||||
self.response_tabs_container.append(self.response_stack)
|
|
||||||
|
|
||||||
# Response Headers tab
|
|
||||||
headers_scroll = Gtk.ScrolledWindow()
|
|
||||||
headers_scroll.set_vexpand(True)
|
|
||||||
self.response_headers_textview = Gtk.TextView()
|
|
||||||
self.response_headers_textview.set_editable(False)
|
|
||||||
self.response_headers_textview.set_monospace(True)
|
|
||||||
self.response_headers_textview.set_left_margin(12)
|
|
||||||
self.response_headers_textview.set_right_margin(12)
|
|
||||||
self.response_headers_textview.set_top_margin(12)
|
|
||||||
self.response_headers_textview.set_bottom_margin(12)
|
|
||||||
headers_scroll.set_child(self.response_headers_textview)
|
|
||||||
|
|
||||||
self.response_stack.add_titled(headers_scroll, "headers", "Headers")
|
|
||||||
|
|
||||||
# Response Body tab with syntax highlighting
|
|
||||||
body_scroll = Gtk.ScrolledWindow()
|
|
||||||
body_scroll.set_vexpand(True)
|
|
||||||
self.response_body_sourceview = GtkSource.View()
|
|
||||||
self.response_body_sourceview.set_editable(False)
|
|
||||||
self.response_body_sourceview.set_show_line_numbers(True)
|
|
||||||
self.response_body_sourceview.set_highlight_current_line(True)
|
|
||||||
self.response_body_sourceview.set_left_margin(12)
|
|
||||||
self.response_body_sourceview.set_right_margin(12)
|
|
||||||
self.response_body_sourceview.set_top_margin(12)
|
|
||||||
self.response_body_sourceview.set_bottom_margin(12)
|
|
||||||
|
|
||||||
# Set up syntax highlighting with system theme colors
|
|
||||||
self._setup_sourceview_theme()
|
|
||||||
|
|
||||||
# Set font family and size
|
|
||||||
# You can customize these values:
|
|
||||||
# - font-family: Change to any font (e.g., "Monospace", "JetBrains Mono", "Fira Code")
|
|
||||||
# - font-size: Change to any size (e.g., 10pt, 12pt, 14pt, 16pt)
|
|
||||||
css_provider = Gtk.CssProvider()
|
|
||||||
css_provider.load_from_data(b"""
|
|
||||||
textview {
|
|
||||||
font-family: "Source Code Pro";
|
|
||||||
font-size: 12pt;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
self.response_body_sourceview.get_style_context().add_provider(
|
|
||||||
css_provider,
|
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
|
|
||||||
)
|
|
||||||
|
|
||||||
body_scroll.set_child(self.response_body_sourceview)
|
|
||||||
|
|
||||||
self.response_stack.add_titled(body_scroll, "body", "Body")
|
|
||||||
|
|
||||||
@Gtk.Template.Callback()
|
|
||||||
def on_send_clicked(self, button):
|
|
||||||
"""Handle Send button click."""
|
|
||||||
# Validate URL
|
# Validate URL
|
||||||
url = self.url_entry.get_text().strip()
|
request = widget.get_request()
|
||||||
if not url:
|
if not request.url.strip():
|
||||||
self._show_toast("Please enter a URL")
|
self._show_toast("Please enter a URL")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Build request from UI
|
|
||||||
request = self._build_request_from_ui()
|
|
||||||
|
|
||||||
# Disable send button during request
|
# Disable send button during request
|
||||||
self.send_button.set_sensitive(False)
|
widget.send_button.set_sensitive(False)
|
||||||
self.send_button.set_label("Sending...")
|
widget.send_button.set_label("Sending...")
|
||||||
self.status_label.set_text("Sending...")
|
|
||||||
self.time_label.set_text("")
|
|
||||||
|
|
||||||
# Execute async (no threading needed!)
|
# Execute async
|
||||||
self.http_client.execute_request_async(
|
def callback(response, error, user_data):
|
||||||
request,
|
"""Callback runs on main thread."""
|
||||||
self._handle_response_callback,
|
|
||||||
request # user_data
|
|
||||||
)
|
|
||||||
|
|
||||||
def _handle_response_callback(self, response, error, request):
|
|
||||||
"""Callback runs on main thread automatically."""
|
|
||||||
self._handle_response(request, response, error)
|
|
||||||
|
|
||||||
def _handle_response(self, request, response, error):
|
|
||||||
"""Handle response in main thread."""
|
|
||||||
# Re-enable send button
|
# Re-enable send button
|
||||||
self.send_button.set_sensitive(True)
|
widget.send_button.set_sensitive(True)
|
||||||
self.send_button.set_label("Send")
|
widget.send_button.set_label("Send")
|
||||||
|
|
||||||
# Save response to current tab
|
# Update tab with response
|
||||||
if self.current_tab_id:
|
if response:
|
||||||
current_tab = self.tab_manager.get_tab_by_id(self.current_tab_id)
|
widget.display_response(response)
|
||||||
if current_tab:
|
else:
|
||||||
current_tab.response = response
|
widget.display_error(error or "Unknown error")
|
||||||
|
|
||||||
|
# Save response to tab
|
||||||
|
page = self.tab_view.get_selected_page()
|
||||||
|
if page:
|
||||||
|
tab = self.page_to_tab.get(page)
|
||||||
|
if tab:
|
||||||
|
tab.response = response
|
||||||
|
|
||||||
# Create history entry
|
# Create history entry
|
||||||
entry = HistoryEntry(
|
entry = HistoryEntry(
|
||||||
@ -577,174 +383,11 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
response=response,
|
response=response,
|
||||||
error=error
|
error=error
|
||||||
)
|
)
|
||||||
|
|
||||||
# Save to history
|
|
||||||
self.history_manager.add_entry(entry)
|
self.history_manager.add_entry(entry)
|
||||||
|
|
||||||
# Update UI
|
self.http_client.execute_request_async(request, callback, None)
|
||||||
if response:
|
|
||||||
self._display_response(response)
|
|
||||||
else:
|
|
||||||
self._display_error(error)
|
|
||||||
|
|
||||||
# Refresh history list
|
|
||||||
self._load_history()
|
|
||||||
|
|
||||||
def _build_request_from_ui(self):
|
|
||||||
"""Build HttpRequest from UI inputs."""
|
|
||||||
method = self.method_dropdown.get_selected_item().get_string()
|
|
||||||
url = self.url_entry.get_text().strip()
|
|
||||||
|
|
||||||
# Collect headers
|
|
||||||
headers = {}
|
|
||||||
child = self.headers_listbox.get_first_child()
|
|
||||||
while child is not None:
|
|
||||||
# In GTK4, ListBox wraps children in ListBoxRow, so we need to get the actual child
|
|
||||||
if isinstance(child, Gtk.ListBoxRow):
|
|
||||||
header_row = child.get_child()
|
|
||||||
if isinstance(header_row, HeaderRow):
|
|
||||||
key, value = header_row.get_header()
|
|
||||||
if key and value:
|
|
||||||
headers[key] = value
|
|
||||||
child = child.get_next_sibling()
|
|
||||||
|
|
||||||
# Get body
|
|
||||||
buffer = self.body_sourceview.get_buffer()
|
|
||||||
body = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False)
|
|
||||||
|
|
||||||
# Get syntax selection
|
|
||||||
syntax_index = self.body_language_dropdown.get_selected()
|
|
||||||
syntax_options = ["RAW", "JSON", "XML"]
|
|
||||||
syntax = syntax_options[syntax_index]
|
|
||||||
|
|
||||||
return HttpRequest(method=method, url=url, headers=headers, body=body, syntax=syntax)
|
|
||||||
|
|
||||||
def _display_response(self, response):
|
|
||||||
"""Display response in UI."""
|
|
||||||
# Update status
|
|
||||||
status_text = f"{response.status_code} {response.status_text}"
|
|
||||||
self.status_label.set_text(status_text)
|
|
||||||
|
|
||||||
# Update time
|
|
||||||
time_text = f"{response.response_time_ms:.0f} ms"
|
|
||||||
self.time_label.set_text(time_text)
|
|
||||||
|
|
||||||
# Update response headers
|
|
||||||
buffer = self.response_headers_textview.get_buffer()
|
|
||||||
buffer.set_text(response.headers)
|
|
||||||
|
|
||||||
# Extract content-type and format body
|
|
||||||
content_type = self._extract_content_type(response.headers)
|
|
||||||
formatted_body = self._format_response_body(response.body, content_type)
|
|
||||||
|
|
||||||
# Update response body with syntax highlighting
|
|
||||||
source_buffer = self.response_body_sourceview.get_buffer()
|
|
||||||
source_buffer.set_text(formatted_body)
|
|
||||||
|
|
||||||
# Set language for syntax highlighting
|
|
||||||
language = self._get_language_from_content_type(content_type)
|
|
||||||
source_buffer.set_language(language)
|
|
||||||
|
|
||||||
def _display_error(self, error):
|
|
||||||
"""Display error in UI."""
|
|
||||||
self.status_label.set_text("Error")
|
|
||||||
self.time_label.set_text("")
|
|
||||||
|
|
||||||
# Clear response headers
|
|
||||||
buffer = self.response_headers_textview.get_buffer()
|
|
||||||
buffer.set_text("")
|
|
||||||
|
|
||||||
# Display error in body
|
|
||||||
source_buffer = self.response_body_sourceview.get_buffer()
|
|
||||||
source_buffer.set_text(error)
|
|
||||||
source_buffer.set_language(None) # No syntax highlighting for errors
|
|
||||||
|
|
||||||
# Show toast
|
|
||||||
self._show_toast(f"Request failed: {error}")
|
|
||||||
|
|
||||||
def _extract_content_type(self, headers_text):
|
|
||||||
"""Extract content-type from response headers."""
|
|
||||||
if not headers_text:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# Parse headers line by line
|
|
||||||
for line in headers_text.split('\n'):
|
|
||||||
line = line.strip()
|
|
||||||
if line.lower().startswith('content-type:'):
|
|
||||||
# Extract value after colon
|
|
||||||
return line.split(':', 1)[1].strip().lower()
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _get_language_from_content_type(self, content_type):
|
|
||||||
"""Get GtkSourceView language ID from content type."""
|
|
||||||
if not content_type:
|
|
||||||
return None
|
|
||||||
|
|
||||||
language_manager = GtkSource.LanguageManager.get_default()
|
|
||||||
|
|
||||||
# Map content types to language IDs
|
|
||||||
if 'application/json' in content_type or 'text/json' in content_type:
|
|
||||||
return language_manager.get_language('json')
|
|
||||||
elif 'application/xml' in content_type or 'text/xml' in content_type:
|
|
||||||
return language_manager.get_language('xml')
|
|
||||||
elif 'text/html' in content_type:
|
|
||||||
return language_manager.get_language('html')
|
|
||||||
elif 'application/javascript' in content_type or 'text/javascript' in content_type:
|
|
||||||
return language_manager.get_language('js')
|
|
||||||
elif 'text/css' in content_type:
|
|
||||||
return language_manager.get_language('css')
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _format_response_body(self, body, content_type):
|
|
||||||
"""Format response body based on content type."""
|
|
||||||
if not body or not body.strip():
|
|
||||||
return body
|
|
||||||
|
|
||||||
try:
|
|
||||||
# JSON formatting
|
|
||||||
if 'application/json' in content_type or 'text/json' in content_type:
|
|
||||||
parsed = json.loads(body)
|
|
||||||
return json.dumps(parsed, indent=2, ensure_ascii=False)
|
|
||||||
|
|
||||||
# XML formatting
|
|
||||||
elif 'application/xml' in content_type or 'text/xml' in content_type:
|
|
||||||
dom = xml.dom.minidom.parseString(body)
|
|
||||||
return dom.toprettyxml(indent=" ")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# If formatting fails, return original body
|
|
||||||
print(f"Failed to format body: {e}")
|
|
||||||
|
|
||||||
# Return original for other content types or if formatting failed
|
|
||||||
return body
|
|
||||||
|
|
||||||
def on_add_header_clicked(self, button):
|
|
||||||
"""Add new header row."""
|
|
||||||
self._add_header_row()
|
|
||||||
|
|
||||||
def _add_header_row(self, key='', value=''):
|
|
||||||
"""Add a header row to the list."""
|
|
||||||
row = HeaderRow()
|
|
||||||
row.set_header(key, value)
|
|
||||||
row.connect('remove-requested', self._on_header_remove)
|
|
||||||
row.connect('changed', self._on_request_changed)
|
|
||||||
self.headers_listbox.append(row)
|
|
||||||
|
|
||||||
# Mark as changed if we're adding a non-empty header
|
|
||||||
if key or value:
|
|
||||||
self._on_request_changed(None)
|
|
||||||
|
|
||||||
def _on_header_remove(self, header_row):
|
|
||||||
"""Handle header row removal."""
|
|
||||||
# In GTK4, we need to remove the parent ListBoxRow, not the HeaderRow itself
|
|
||||||
parent = header_row.get_parent()
|
|
||||||
if parent:
|
|
||||||
self.headers_listbox.remove(parent)
|
|
||||||
# Mark as changed
|
|
||||||
self._on_request_changed(None)
|
|
||||||
|
|
||||||
|
# History and Project Management
|
||||||
def _load_history(self):
|
def _load_history(self):
|
||||||
"""Load history from file and populate list."""
|
"""Load history from file and populate list."""
|
||||||
# Clear existing history items
|
# Clear existing history items
|
||||||
@ -805,9 +448,6 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
saved_request_id=None
|
saved_request_id=None
|
||||||
)
|
)
|
||||||
|
|
||||||
# Switch to headers tab
|
|
||||||
self.request_stack.set_visible_child_name("headers")
|
|
||||||
|
|
||||||
self._show_toast("Request loaded from history")
|
self._show_toast("Request loaded from history")
|
||||||
|
|
||||||
def _show_toast(self, message):
|
def _show_toast(self, message):
|
||||||
@ -1047,9 +687,6 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
syntax=request.syntax
|
syntax=request.syntax
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update tab indicator to remove modified marker
|
|
||||||
self._update_tab_indicator(current_tab)
|
|
||||||
|
|
||||||
def _show_overwrite_dialog(self, project, name, existing_request_id, request):
|
def _show_overwrite_dialog(self, project, name, existing_request_id, request):
|
||||||
"""Show dialog asking if user wants to overwrite existing request."""
|
"""Show dialog asking if user wants to overwrite existing request."""
|
||||||
dialog = Adw.AlertDialog()
|
dialog = Adw.AlertDialog()
|
||||||
@ -1184,10 +821,6 @@ class RosterWindow(Adw.ApplicationWindow):
|
|||||||
if new_tab:
|
if new_tab:
|
||||||
new_tab.original_request = None
|
new_tab.original_request = None
|
||||||
new_tab.modified = True
|
new_tab.modified = True
|
||||||
self._update_tab_indicator(new_tab)
|
|
||||||
|
|
||||||
# Switch to headers tab
|
|
||||||
self.request_stack.set_visible_child_name("headers")
|
|
||||||
|
|
||||||
def _on_delete_request(self, widget, saved_request, project):
|
def _on_delete_request(self, widget, saved_request, project):
|
||||||
"""Delete saved request with confirmation."""
|
"""Delete saved request with confirmation."""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user