first commit
This commit is contained in:
246
src/gui/window.c
Normal file
246
src/gui/window.c
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Scallywag-NG: Half the scally, and twice the wag.
|
||||
* Copyright (C) 2025 SILO GROUP LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "window.h"
|
||||
#include "callbacks.h"
|
||||
#include "../app.h"
|
||||
#include <string.h>
|
||||
|
||||
#define LOGO_RESOURCE_PATH "/org/scallywag/logo.svg"
|
||||
|
||||
// Column indices for results store
|
||||
enum {
|
||||
COL_SEEDERS = 0,
|
||||
COL_LEECHERS,
|
||||
COL_SIZE,
|
||||
COL_TITLE,
|
||||
COL_AUTHOR,
|
||||
COL_URL,
|
||||
NUM_COLS
|
||||
};
|
||||
|
||||
// Callback for proxy section toggle
|
||||
static void on_proxy_toggle(GtkToggleButton *toggle, gpointer user_data) {
|
||||
GtkWidget *proxy_box = GTK_WIDGET(user_data);
|
||||
gboolean active = gtk_toggle_button_get_active(toggle);
|
||||
|
||||
if (active) {
|
||||
gtk_widget_show(proxy_box);
|
||||
// Show all children explicitly
|
||||
GList *children = gtk_container_get_children(GTK_CONTAINER(proxy_box));
|
||||
for (GList *l = children; l != NULL; l = l->next) {
|
||||
gtk_widget_show(GTK_WIDGET(l->data));
|
||||
}
|
||||
g_list_free(children);
|
||||
} else {
|
||||
gtk_widget_hide(proxy_box);
|
||||
}
|
||||
gtk_button_set_label(GTK_BUTTON(toggle), active ? "\342\226\274 Proxy" : "\342\226\266 Proxy");
|
||||
}
|
||||
|
||||
// Handle horizontal scroll wheel events
|
||||
static gboolean on_scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) {
|
||||
(void)widget;
|
||||
GtkScrolledWindow *scrolled = GTK_SCROLLED_WINDOW(data);
|
||||
GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(scrolled);
|
||||
gdouble step = gtk_adjustment_get_step_increment(hadj);
|
||||
gdouble value = gtk_adjustment_get_value(hadj);
|
||||
|
||||
// Handle Shift+scroll or horizontal scroll for horizontal movement
|
||||
if (event->state & GDK_SHIFT_MASK ||
|
||||
event->direction == GDK_SCROLL_LEFT ||
|
||||
event->direction == GDK_SCROLL_RIGHT) {
|
||||
|
||||
if (event->direction == GDK_SCROLL_UP || event->direction == GDK_SCROLL_LEFT) {
|
||||
gtk_adjustment_set_value(hadj, value - step * 3);
|
||||
} else if (event->direction == GDK_SCROLL_DOWN || event->direction == GDK_SCROLL_RIGHT) {
|
||||
gtk_adjustment_set_value(hadj, value + step * 3);
|
||||
} else if (event->direction == GDK_SCROLL_SMOOTH) {
|
||||
gtk_adjustment_set_value(hadj, value + event->delta_x * step * 3);
|
||||
}
|
||||
return TRUE; // Event handled
|
||||
}
|
||||
return FALSE; // Let default handler process vertical scroll
|
||||
}
|
||||
|
||||
static void setup_treeview_columns(GtkTreeView *treeview) {
|
||||
const char *titles[] = {"Seeders", "Leechers", "Size", "Title", "Author"};
|
||||
|
||||
// Don't show URL column (COL_URL) - data is still in store for downloads
|
||||
for (int i = 0; i < COL_URL; i++) {
|
||||
GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
|
||||
GtkTreeViewColumn *column;
|
||||
|
||||
// Use markup for Author column to render italic anonymous users
|
||||
if (i == COL_AUTHOR) {
|
||||
column = gtk_tree_view_column_new_with_attributes(
|
||||
titles[i], renderer, "markup", i, NULL);
|
||||
} else {
|
||||
column = gtk_tree_view_column_new_with_attributes(
|
||||
titles[i], renderer, "text", i, NULL);
|
||||
}
|
||||
|
||||
gtk_tree_view_column_set_resizable(column, TRUE);
|
||||
|
||||
// Make title column expand
|
||||
if (i == COL_TITLE) {
|
||||
gtk_tree_view_column_set_expand(column, TRUE);
|
||||
}
|
||||
|
||||
gtk_tree_view_append_column(treeview, column);
|
||||
}
|
||||
}
|
||||
|
||||
GtkWidget *window_create(App *app) {
|
||||
// Set up CSS for alternating row colors
|
||||
GtkCssProvider *css_provider = gtk_css_provider_new();
|
||||
gtk_css_provider_load_from_data(css_provider,
|
||||
"treeview.view row:nth-child(even) { background-color: rgba(0, 26, 45, 0.15); }"
|
||||
"treeview.view row:nth-child(even) cell { background-color: rgba(0, 26, 45, 0.15); }",
|
||||
-1, NULL);
|
||||
gtk_style_context_add_provider_for_screen(gdk_screen_get_default(),
|
||||
GTK_STYLE_PROVIDER(css_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||
g_object_unref(css_provider);
|
||||
|
||||
// Main window
|
||||
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_title(GTK_WINDOW(window), "Scallywag");
|
||||
gtk_window_set_default_size(GTK_WINDOW(window), 600, 500);
|
||||
g_signal_connect(window, "destroy", G_CALLBACK(on_window_destroy), app);
|
||||
|
||||
// Main vertical box with spacing
|
||||
GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
|
||||
gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
|
||||
gtk_container_add(GTK_CONTAINER(window), vbox);
|
||||
|
||||
// Logo (clickable - toggles loading dialog)
|
||||
GError *error = NULL;
|
||||
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_resource_at_scale(
|
||||
LOGO_RESOURCE_PATH, 120, -1, TRUE, &error);
|
||||
if (pixbuf) {
|
||||
GtkWidget *logo = gtk_image_new_from_pixbuf(pixbuf);
|
||||
GtkWidget *event_box = gtk_event_box_new();
|
||||
gtk_container_add(GTK_CONTAINER(event_box), logo);
|
||||
gtk_widget_set_halign(event_box, GTK_ALIGN_CENTER);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), event_box, FALSE, FALSE, 0);
|
||||
g_signal_connect(event_box, "button-press-event", G_CALLBACK(on_logo_clicked), app);
|
||||
g_object_unref(pixbuf);
|
||||
}
|
||||
if (error) g_error_free(error);
|
||||
|
||||
// Program name label
|
||||
GtkWidget *title_label = gtk_label_new(NULL);
|
||||
gtk_label_set_markup(GTK_LABEL(title_label), "<span size='x-large' weight='bold'>Scallywag</span>");
|
||||
gtk_widget_set_halign(title_label, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_margin_bottom(title_label, 12);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), title_label, FALSE, FALSE, 0);
|
||||
|
||||
// Search row
|
||||
GtkWidget *search_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), search_box, FALSE, TRUE, 0);
|
||||
|
||||
GtkWidget *entry_search = gtk_entry_new();
|
||||
gtk_entry_set_placeholder_text(GTK_ENTRY(entry_search), "Enter search terms...");
|
||||
gtk_box_pack_start(GTK_BOX(search_box), entry_search, TRUE, TRUE, 0);
|
||||
g_signal_connect(entry_search, "activate", G_CALLBACK(on_search_activate), app);
|
||||
|
||||
GtkWidget *btn_search = gtk_button_new_with_label("Search");
|
||||
gtk_box_pack_start(GTK_BOX(search_box), btn_search, FALSE, FALSE, 0);
|
||||
g_signal_connect(btn_search, "clicked", G_CALLBACK(on_search_clicked), app);
|
||||
|
||||
// Collapsible proxy section
|
||||
GtkWidget *proxy_toggle = gtk_toggle_button_new_with_label("\342\226\266 Proxy");
|
||||
gtk_widget_set_halign(proxy_toggle, GTK_ALIGN_START);
|
||||
gtk_button_set_relief(GTK_BUTTON(proxy_toggle), GTK_RELIEF_NONE);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), proxy_toggle, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *proxy_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_widget_set_margin_start(proxy_box, 16);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), proxy_box, FALSE, FALSE, 0);
|
||||
|
||||
GtkWidget *combo_proxy = gtk_combo_box_text_new();
|
||||
gtk_box_pack_start(GTK_BOX(proxy_box), combo_proxy, TRUE, TRUE, 0);
|
||||
g_signal_connect(combo_proxy, "changed", G_CALLBACK(on_proxy_changed), app);
|
||||
|
||||
GtkWidget *btn_refresh = gtk_button_new_with_label("Refresh");
|
||||
gtk_box_pack_start(GTK_BOX(proxy_box), btn_refresh, FALSE, FALSE, 0);
|
||||
g_signal_connect(btn_refresh, "clicked", G_CALLBACK(on_refresh_clicked), app);
|
||||
|
||||
// Connect toggle after children are added, then hide
|
||||
g_signal_connect(proxy_toggle, "toggled", G_CALLBACK(on_proxy_toggle), proxy_box);
|
||||
gtk_widget_set_no_show_all(proxy_box, TRUE); // Prevent show_all from main window
|
||||
gtk_widget_hide(proxy_box);
|
||||
|
||||
// Scrolled window for results
|
||||
GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL);
|
||||
gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_IN);
|
||||
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
|
||||
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0);
|
||||
|
||||
// Tree view for results
|
||||
GtkWidget *treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(app->results_store));
|
||||
setup_treeview_columns(GTK_TREE_VIEW(treeview));
|
||||
gtk_container_add(GTK_CONTAINER(scrolled), treeview);
|
||||
g_signal_connect(treeview, "row-activated", G_CALLBACK(on_row_activated), app);
|
||||
g_signal_connect(treeview, "scroll-event", G_CALLBACK(on_scroll_event), scrolled);
|
||||
gtk_widget_add_events(treeview, GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK);
|
||||
|
||||
// Get More Results button (centered, initially disabled)
|
||||
GtkWidget *btn_more = gtk_button_new_with_label("Get More Results");
|
||||
gtk_widget_set_halign(btn_more, GTK_ALIGN_CENTER);
|
||||
gtk_widget_set_margin_top(btn_more, 4);
|
||||
gtk_widget_set_margin_bottom(btn_more, 4);
|
||||
gtk_widget_set_sensitive(btn_more, FALSE);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), btn_more, FALSE, FALSE, 0);
|
||||
g_signal_connect(btn_more, "clicked", G_CALLBACK(on_more_clicked), app);
|
||||
|
||||
// Download button (default action)
|
||||
GtkWidget *btn_download = gtk_button_new_with_label("Download");
|
||||
gtk_widget_set_can_default(btn_download, TRUE);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), btn_download, FALSE, TRUE, 0);
|
||||
g_signal_connect(btn_download, "clicked", G_CALLBACK(on_download_clicked), app);
|
||||
|
||||
// Status bar row with page counter on right
|
||||
GtkWidget *status_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_box_pack_start(GTK_BOX(vbox), status_box, FALSE, TRUE, 0);
|
||||
|
||||
GtkWidget *statusbar = gtk_statusbar_new();
|
||||
gtk_box_pack_start(GTK_BOX(status_box), statusbar, TRUE, TRUE, 0);
|
||||
|
||||
GtkWidget *lbl_page_count = gtk_label_new("pages 0/0");
|
||||
gtk_widget_set_margin_end(lbl_page_count, 8);
|
||||
gtk_box_pack_start(GTK_BOX(status_box), lbl_page_count, FALSE, FALSE, 0);
|
||||
|
||||
// Store widget references in app
|
||||
app->window = window;
|
||||
app->combo_proxy = combo_proxy;
|
||||
app->entry_search = entry_search;
|
||||
app->treeview_results = treeview;
|
||||
app->btn_search = btn_search;
|
||||
app->btn_refresh = btn_refresh;
|
||||
app->btn_download = btn_download;
|
||||
app->btn_more = btn_more;
|
||||
app->lbl_page_count = lbl_page_count;
|
||||
app->statusbar = statusbar;
|
||||
|
||||
// Set download as the default button
|
||||
gtk_window_set_default(GTK_WINDOW(window), btn_download);
|
||||
|
||||
return window;
|
||||
}
|
||||
Reference in New Issue
Block a user