247 lines
10 KiB
C
247 lines
10 KiB
C
/*
|
|
* 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;
|
|
}
|