+G_MODULE_EXPORT void
+switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
+ gint page_num, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ pref_changed_cb (GTK_WIDGET (notebook), user_data);
+
+ /* If we're switching to page 0, schedule the current hack to be run.
+ Otherwise, schedule it to stop. */
+ if (page_num == 0)
+ populate_demo_window (s, selected_list_element (s));
+ else
+ schedule_preview (s, 0);
+}
+
+#ifdef HAVE_GTK2
+static void
+list_activated_cb (GtkTreeView *list,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ gpointer data)
+{
+ state *s = data;
+ char *str;
+ int list_elt;
+
+ g_return_if_fail (!gdk_pointer_is_grabbed ());
+
+ str = gtk_tree_path_to_string (path);
+ list_elt = strtol (str, NULL, 10);
+ g_free (str);
+
+ if (list_elt >= 0)
+ run_hack (s, list_elt, True);
+}
+
+static void
+list_select_changed_cb (GtkTreeSelection *selection, gpointer data)
+{
+ state *s = (state *)data;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkTreePath *path;
+ char *str;
+ int list_elt;
+
+ if (!gtk_tree_selection_get_selected (selection, &model, &iter))
+ return;
+
+ path = gtk_tree_model_get_path (model, &iter);
+ str = gtk_tree_path_to_string (path);
+ list_elt = strtol (str, NULL, 10);
+
+ gtk_tree_path_free (path);
+ g_free (str);
+
+ populate_demo_window (s, list_elt);
+ flush_dialog_changes_and_save (s);
+
+ /* Re-populate the Settings window any time a new item is selected
+ in the list, in case both windows are currently visible.
+ */
+ populate_popup_window (s);
+}
+
+#else /* !HAVE_GTK2 */
+
+static time_t last_doubleclick_time = 0; /* FMH! This is to suppress the
+ list_select_cb that comes in
+ *after* we've double-clicked.
+ */
+
+static gint
+list_doubleclick_cb (GtkWidget *button, GdkEventButton *event,
+ gpointer data)
+{
+ state *s = (state *) data;
+ if (event->type == GDK_2BUTTON_PRESS)
+ {
+ GtkList *list = GTK_LIST (name_to_widget (s, "list"));
+ int list_elt = gtk_list_child_position (list, GTK_WIDGET (button));
+
+ last_doubleclick_time = time ((time_t *) 0);
+
+ if (list_elt >= 0)
+ run_hack (s, list_elt, True);
+ }
+
+ return FALSE;
+}
+
+
+static void
+list_select_cb (GtkList *list, GtkWidget *child, gpointer data)
+{
+ state *s = (state *) data;
+ time_t now = time ((time_t *) 0);
+
+ if (now >= last_doubleclick_time + 2)
+ {
+ int list_elt = gtk_list_child_position (list, GTK_WIDGET (child));
+ populate_demo_window (s, list_elt);
+ flush_dialog_changes_and_save (s);
+ }
+}
+
+static void
+list_unselect_cb (GtkList *list, GtkWidget *child, gpointer data)
+{
+ state *s = (state *) data;
+ populate_demo_window (s, -1);
+ flush_dialog_changes_and_save (s);
+}
+
+#endif /* !HAVE_GTK2 */
+
+
+/* Called when the checkboxes that are in the left column of the
+ scrolling list are clicked. This both populates the right pane
+ (just as clicking on the label (really, listitem) does) and
+ also syncs this checkbox with the right pane Enabled checkbox.
+ */
+static void
+list_checkbox_cb (
+#ifdef HAVE_GTK2
+ GtkCellRendererToggle *toggle,
+ gchar *path_string,
+#else /* !HAVE_GTK2 */
+ GtkWidget *cb,
+#endif /* !HAVE_GTK2 */
+ gpointer data)
+{
+ state *s = (state *) data;
+
+#ifdef HAVE_GTK2
+ GtkScrolledWindow *scroller =
+ GTK_SCROLLED_WINDOW (name_to_widget (s, "scroller"));
+ GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
+ GtkTreeModel *model = gtk_tree_view_get_model (list);
+ GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
+ GtkTreeIter iter;
+ gboolean active;
+#else /* !HAVE_GTK2 */
+ GtkWidget *line_hbox = GTK_WIDGET (cb)->parent;
+ GtkWidget *line = GTK_WIDGET (line_hbox)->parent;
+
+ GtkList *list = GTK_LIST (GTK_WIDGET (line)->parent);
+ GtkViewport *vp = GTK_VIEWPORT (GTK_WIDGET (list)->parent);
+ GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW (GTK_WIDGET (vp)->parent);
+#endif /* !HAVE_GTK2 */
+ GtkAdjustment *adj;
+ double scroll_top;
+
+ int list_elt;
+
+#ifdef HAVE_GTK2
+ if (!gtk_tree_model_get_iter (model, &iter, path))
+ {
+ g_warning ("bad path: %s", path_string);
+ return;
+ }
+ gtk_tree_path_free (path);
+
+ gtk_tree_model_get (model, &iter,
+ COL_ENABLED, &active,
+ -1);
+
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+ COL_ENABLED, !active,
+ -1);
+
+ list_elt = strtol (path_string, NULL, 10);
+#else /* !HAVE_GTK2 */
+ list_elt = gtk_list_child_position (list, line);
+#endif /* !HAVE_GTK2 */
+
+ /* remember previous scroll position of the top of the list */
+ adj = gtk_scrolled_window_get_vadjustment (scroller);
+ scroll_top = GET_ADJ_VALUE (adj);
+
+ flush_dialog_changes_and_save (s);
+ force_list_select_item (s, GTK_WIDGET (list), list_elt, False);
+ populate_demo_window (s, list_elt);
+
+ /* restore the previous scroll position of the top of the list.
+ this is weak, but I don't really know why it's moving... */
+ gtk_adjustment_set_value (adj, scroll_top);
+}
+
+
+typedef struct {
+ state *state;
+ GtkFileSelection *widget;
+} file_selection_data;
+
+
+
+static void
+store_image_directory (GtkWidget *button, gpointer user_data)
+{
+ file_selection_data *fsd = (file_selection_data *) user_data;
+ state *s = fsd->state;
+ GtkFileSelection *selector = fsd->widget;
+ GtkWidget *top = s->toplevel_widget;
+ saver_preferences *p = &s->prefs;
+ const char *path = gtk_file_selection_get_filename (selector);
+
+ if (p->image_directory && !strcmp(p->image_directory, path))
+ return; /* no change */
+
+ /* No warning for URLs. */
+ if ((!directory_p (path)) && strncmp(path, "http://", 6))
+ {
+ char b[255];
+ sprintf (b, _("Error:\n\n" "Directory does not exist: \"%s\"\n"), path);
+ warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
+ return;
+ }
+
+ if (p->image_directory) free (p->image_directory);
+ p->image_directory = normalize_directory (path);
+
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
+ (p->image_directory ? p->image_directory : ""));
+ demo_write_init_file (s, p);
+}
+
+
+static void
+store_text_file (GtkWidget *button, gpointer user_data)
+{
+ file_selection_data *fsd = (file_selection_data *) user_data;
+ state *s = fsd->state;
+ GtkFileSelection *selector = fsd->widget;
+ GtkWidget *top = s->toplevel_widget;
+ saver_preferences *p = &s->prefs;
+ const char *path = gtk_file_selection_get_filename (selector);
+
+ if (p->text_file && !strcmp(p->text_file, path))
+ return; /* no change */
+
+ if (!file_p (path))
+ {
+ char b[255];
+ sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
+ warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
+ return;
+ }
+
+ if (p->text_file) free (p->text_file);
+ p->text_file = normalize_directory (path);
+
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
+ (p->text_file ? p->text_file : ""));
+ demo_write_init_file (s, p);
+}
+
+
+static void
+store_text_program (GtkWidget *button, gpointer user_data)
+{
+ file_selection_data *fsd = (file_selection_data *) user_data;
+ state *s = fsd->state;
+ GtkFileSelection *selector = fsd->widget;
+ /*GtkWidget *top = s->toplevel_widget;*/
+ saver_preferences *p = &s->prefs;
+ const char *path = gtk_file_selection_get_filename (selector);
+
+ if (p->text_program && !strcmp(p->text_program, path))
+ return; /* no change */
+
+# if 0
+ if (!file_p (path))
+ {
+ char b[255];
+ sprintf (b, _("Error:\n\n" "File does not exist: \"%s\"\n"), path);
+ warning_dialog (GTK_WIDGET (top), b, D_NONE, 100);
+ return;
+ }
+# endif
+
+ if (p->text_program) free (p->text_program);
+ p->text_program = normalize_directory (path);
+
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
+ (p->text_program ? p->text_program : ""));
+ demo_write_init_file (s, p);
+}
+
+
+
+static void
+browse_image_dir_cancel (GtkWidget *button, gpointer user_data)
+{
+ file_selection_data *fsd = (file_selection_data *) user_data;
+ gtk_widget_hide (GTK_WIDGET (fsd->widget));
+}
+
+static void
+browse_image_dir_ok (GtkWidget *button, gpointer user_data)
+{
+ browse_image_dir_cancel (button, user_data);
+ store_image_directory (button, user_data);
+}
+
+static void
+browse_text_file_ok (GtkWidget *button, gpointer user_data)
+{
+ browse_image_dir_cancel (button, user_data);
+ store_text_file (button, user_data);
+}
+
+static void
+browse_text_program_ok (GtkWidget *button, gpointer user_data)
+{
+ browse_image_dir_cancel (button, user_data);
+ store_text_program (button, user_data);
+}
+
+static void
+browse_image_dir_close (GtkWidget *widget, GdkEvent *event, gpointer user_data)
+{
+ browse_image_dir_cancel (widget, user_data);
+}
+
+
+G_MODULE_EXPORT void
+browse_image_dir_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ saver_preferences *p = &s->prefs;
+ static file_selection_data *fsd = 0;
+
+ GtkFileSelection *selector = GTK_FILE_SELECTION(
+ gtk_file_selection_new ("Please select the image directory."));
+
+ if (!fsd)
+ fsd = (file_selection_data *) malloc (sizeof (*fsd));
+
+ fsd->widget = selector;
+ fsd->state = s;
+
+ if (p->image_directory && *p->image_directory)
+ gtk_file_selection_set_filename (selector, p->image_directory);
+
+ gtk_signal_connect (GTK_OBJECT (selector->ok_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_image_dir_ok),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
+ GTK_SIGNAL_FUNC (browse_image_dir_close),
+ (gpointer *) fsd);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (selector->file_list), False);
+
+ gtk_window_set_modal (GTK_WINDOW (selector), True);
+ gtk_widget_show (GTK_WIDGET (selector));
+}
+
+
+G_MODULE_EXPORT void
+browse_text_file_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ saver_preferences *p = &s->prefs;
+ static file_selection_data *fsd = 0;
+
+ GtkFileSelection *selector = GTK_FILE_SELECTION(
+ gtk_file_selection_new ("Please select a text file."));
+
+ if (!fsd)
+ fsd = (file_selection_data *) malloc (sizeof (*fsd));
+
+ fsd->widget = selector;
+ fsd->state = s;
+
+ if (p->text_file && *p->text_file)
+ gtk_file_selection_set_filename (selector, p->text_file);
+
+ gtk_signal_connect (GTK_OBJECT (selector->ok_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_text_file_ok),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
+ GTK_SIGNAL_FUNC (browse_image_dir_close),
+ (gpointer *) fsd);
+
+ gtk_window_set_modal (GTK_WINDOW (selector), True);
+ gtk_widget_show (GTK_WIDGET (selector));
+}
+
+
+G_MODULE_EXPORT void
+browse_text_program_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ saver_preferences *p = &s->prefs;
+ static file_selection_data *fsd = 0;
+
+ GtkFileSelection *selector = GTK_FILE_SELECTION(
+ gtk_file_selection_new ("Please select a text-generating program."));
+
+ if (!fsd)
+ fsd = (file_selection_data *) malloc (sizeof (*fsd));
+
+ fsd->widget = selector;
+ fsd->state = s;
+
+ if (p->text_program && *p->text_program)
+ gtk_file_selection_set_filename (selector, p->text_program);
+
+ gtk_signal_connect (GTK_OBJECT (selector->ok_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_text_program_ok),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector->cancel_button),
+ "clicked", GTK_SIGNAL_FUNC (browse_image_dir_cancel),
+ (gpointer *) fsd);
+ gtk_signal_connect (GTK_OBJECT (selector), "delete_event",
+ GTK_SIGNAL_FUNC (browse_image_dir_close),
+ (gpointer *) fsd);
+
+ gtk_window_set_modal (GTK_WINDOW (selector), True);
+ gtk_widget_show (GTK_WIDGET (selector));
+}
+
+
+
+
+
+G_MODULE_EXPORT void
+settings_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ int list_elt = selected_list_element (s);
+
+ populate_demo_window (s, list_elt); /* reset the widget */
+ populate_popup_window (s); /* create UI on popup window */
+ gtk_widget_show (s->popup_widget);
+}
+
+static void
+settings_sync_cmd_text (state *s)
+{
+# ifdef HAVE_XML
+ GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
+ char *cmd_line = get_configurator_command_line (s->cdata, False);
+ gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
+ gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
+ free (cmd_line);
+# endif /* HAVE_XML */
+}
+
+G_MODULE_EXPORT void
+settings_adv_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ GtkNotebook *notebook =
+ GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
+
+ settings_sync_cmd_text (s);
+ gtk_notebook_set_page (notebook, 1);
+}
+
+G_MODULE_EXPORT void
+settings_std_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ GtkNotebook *notebook =
+ GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
+
+ /* Re-create UI to reflect the in-progress command-line settings. */
+ populate_popup_window (s);
+
+ gtk_notebook_set_page (notebook, 0);
+}
+
+G_MODULE_EXPORT void
+settings_reset_cb (GtkButton *button, gpointer user_data)
+{
+# ifdef HAVE_XML
+ state *s = global_state_kludge; /* I hate C so much... */
+ GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
+ char *cmd_line = get_configurator_command_line (s->cdata, True);
+ gtk_entry_set_text (GTK_ENTRY (cmd), cmd_line);
+ gtk_entry_set_position (GTK_ENTRY (cmd), strlen (cmd_line));
+ free (cmd_line);
+ populate_popup_window (s);
+# endif /* HAVE_XML */
+}
+
+G_MODULE_EXPORT void
+settings_switch_page_cb (GtkNotebook *notebook, GtkNotebookPage *page,
+ gint page_num, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ GtkWidget *adv = name_to_widget (s, "adv_button");
+ GtkWidget *std = name_to_widget (s, "std_button");
+
+ if (page_num == 0)
+ {
+ gtk_widget_show (adv);
+ gtk_widget_hide (std);
+ }
+ else if (page_num == 1)
+ {
+ gtk_widget_hide (adv);
+ gtk_widget_show (std);
+ }
+ else
+ abort();
+}
+
+
+
+G_MODULE_EXPORT void
+settings_cancel_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ gtk_widget_hide (s->popup_widget);
+}
+
+G_MODULE_EXPORT void
+settings_ok_cb (GtkButton *button, gpointer user_data)
+{
+ state *s = global_state_kludge; /* I hate C so much... */
+ GtkNotebook *notebook = GTK_NOTEBOOK (name_to_widget (s, "opt_notebook"));
+ int page = gtk_notebook_get_current_page (notebook);
+
+ if (page == 0)
+ /* Regenerate the command-line from the widget contents before saving.
+ But don't do this if we're looking at the command-line page already,
+ or we will blow away what they typed... */
+ settings_sync_cmd_text (s);
+
+ flush_popup_changes_and_save (s);
+ gtk_widget_hide (s->popup_widget);
+}
+
+static gboolean
+wm_popup_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+ state *s = (state *) data;
+ settings_cancel_cb (0, (gpointer) s);
+ return TRUE;
+}
+
+
+\f
+/* Populating the various widgets
+ */
+
+
+/* Returns the number of the last hack run by the server.
+ */
+static int
+server_current_hack (void)
+{
+ Atom type;
+ int format;
+ unsigned long nitems, bytesafter;
+ unsigned char *dataP = 0;
+ Display *dpy = GDK_DISPLAY();
+ int hack_number = -1;
+
+ if (XGetWindowProperty (dpy, RootWindow (dpy, 0), /* always screen #0 */
+ XA_SCREENSAVER_STATUS,
+ 0, 3, False, XA_INTEGER,
+ &type, &format, &nitems, &bytesafter,
+ &dataP)
+ == Success
+ && type == XA_INTEGER
+ && nitems >= 3
+ && dataP)
+ {
+ PROP32 *data = (PROP32 *) dataP;
+ hack_number = (int) data[2] - 1;
+ }
+
+ if (dataP) XFree (dataP);
+
+ return hack_number;
+}
+
+
+/* Finds the number of the last hack that was run, and makes that item be
+ selected by default.
+ */
+static void
+scroll_to_current_hack (state *s)
+{
+ saver_preferences *p = &s->prefs;
+ int hack_number = -1;
+
+ if (p->mode == ONE_HACK) /* in "one" mode, use the one */
+ hack_number = p->selected_hack;
+ if (hack_number < 0) /* otherwise, use the last-run */
+ hack_number = server_current_hack ();
+ if (hack_number < 0) /* failing that, last "one mode" */
+ hack_number = p->selected_hack;
+ if (hack_number < 0) /* failing that, newest hack. */
+ {
+ /* We should only get here if the user does not have a .xscreensaver
+ file, and the screen has not been blanked with a hack since X
+ started up: in other words, this is probably a fresh install.
+
+ Instead of just defaulting to hack #0 (in either "programs" or
+ "alphabetical" order) let's try to default to the last runnable
+ hack in the "programs" list: this is probably the hack that was
+ most recently added to the xscreensaver distribution (and so
+ it's probably the currently-coolest one!)
+ */
+ hack_number = p->screenhacks_count-1;
+ while (hack_number > 0 &&
+ ! (s->hacks_available_p[hack_number] &&
+ p->screenhacks[hack_number]->enabled_p))
+ hack_number--;
+ }
+
+ if (hack_number >= 0 && hack_number < p->screenhacks_count)
+ {
+ int list_elt = s->hack_number_to_list_elt[hack_number];
+ GtkWidget *list = name_to_widget (s, "list");
+ force_list_select_item (s, list, list_elt, True);
+ populate_demo_window (s, list_elt);
+ }
+}
+
+
+static void
+populate_hack_list (state *s)
+{
+ Display *dpy = GDK_DISPLAY();
+#ifdef HAVE_GTK2
+ saver_preferences *p = &s->prefs;
+ GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
+ GtkListStore *model;
+ GtkTreeSelection *selection;
+ GtkCellRenderer *ren;
+ GtkTreeIter iter;
+ int i;
+
+ g_object_get (G_OBJECT (list),
+ "model", &model,
+ NULL);
+ if (!model)
+ {
+ model = gtk_list_store_new (COL_LAST, G_TYPE_BOOLEAN, G_TYPE_STRING);
+ g_object_set (G_OBJECT (list), "model", model, NULL);
+ g_object_unref (model);
+
+ ren = gtk_cell_renderer_toggle_new ();
+ gtk_tree_view_insert_column_with_attributes (list, COL_ENABLED,
+ _("Use"), ren,
+ "active", COL_ENABLED,
+ NULL);
+
+ g_signal_connect (ren, "toggled",
+ G_CALLBACK (list_checkbox_cb),
+ s);
+
+ ren = gtk_cell_renderer_text_new ();
+ gtk_tree_view_insert_column_with_attributes (list, COL_NAME,
+ _("Screen Saver"), ren,
+ "markup", COL_NAME,
+ NULL);
+
+ g_signal_connect_after (list, "row_activated",
+ G_CALLBACK (list_activated_cb),
+ s);
+
+ selection = gtk_tree_view_get_selection (list);
+ g_signal_connect (selection, "changed",
+ G_CALLBACK (list_select_changed_cb),
+ s);
+
+ }
+
+ for (i = 0; i < s->list_count; i++)
+ {
+ int hack_number = s->list_elt_to_hack_number[i];
+ screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
+ char *pretty_name;
+ Bool available_p = (hack && s->hacks_available_p [hack_number]);
+
+ if (!hack) continue;
+
+ /* If we're to suppress uninstalled hacks, check $PATH now. */
+ if (p->ignore_uninstalled_p && !available_p)
+ continue;
+
+ pretty_name = (hack->name
+ ? strdup (hack->name)
+ : make_hack_name (dpy, hack->command));
+
+ if (!available_p)
+ {
+ /* Make the text foreground be the color of insensitive widgets
+ (but don't actually make it be insensitive, since we still
+ want to be able to click on it.)
+ */
+ GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (list));
+ GdkColor *fg = &style->fg[GTK_STATE_INSENSITIVE];
+ /* GdkColor *bg = &style->bg[GTK_STATE_INSENSITIVE]; */
+ char *buf = (char *) malloc (strlen (pretty_name) + 100);
+
+ sprintf (buf, "<span foreground=\"#%02X%02X%02X\""
+ /* " background=\"#%02X%02X%02X\"" */
+ ">%s</span>",
+ fg->red >> 8, fg->green >> 8, fg->blue >> 8,
+ /* bg->red >> 8, bg->green >> 8, bg->blue >> 8, */
+ pretty_name);
+ free (pretty_name);
+ pretty_name = buf;
+ }
+
+ gtk_list_store_append (model, &iter);
+ gtk_list_store_set (model, &iter,
+ COL_ENABLED, hack->enabled_p,
+ COL_NAME, pretty_name,
+ -1);
+ free (pretty_name);
+ }
+
+#else /* !HAVE_GTK2 */
+
+ saver_preferences *p = &s->prefs;
+ GtkList *list = GTK_LIST (name_to_widget (s, "list"));
+ int i;
+ for (i = 0; i < s->list_count; i++)
+ {
+ int hack_number = s->list_elt_to_hack_number[i];
+ screenhack *hack = (hack_number < 0 ? 0 : p->screenhacks[hack_number]);
+
+ /* A GtkList must contain only GtkListItems, but those can contain
+ an arbitrary widget. We add an Hbox, and inside that, a Checkbox
+ and a Label. We handle single and double click events on the
+ line itself, for clicking on the text, but the interior checkbox
+ also handles its own events.
+ */
+ GtkWidget *line;
+ GtkWidget *line_hbox;
+ GtkWidget *line_check;
+ GtkWidget *line_label;
+ char *pretty_name;
+ Bool available_p = (hack && s->hacks_available_p [hack_number]);
+
+ if (!hack) continue;
+
+ /* If we're to suppress uninstalled hacks, check $PATH now. */
+ if (p->ignore_uninstalled_p && !available_p)
+ continue;
+
+ pretty_name = (hack->name
+ ? strdup (hack->name)
+ : make_hack_name (hack->command));
+
+ line = gtk_list_item_new ();
+ line_hbox = gtk_hbox_new (FALSE, 0);
+ line_check = gtk_check_button_new ();
+ line_label = gtk_label_new (pretty_name);
+
+ gtk_container_add (GTK_CONTAINER (line), line_hbox);
+ gtk_box_pack_start (GTK_BOX (line_hbox), line_check, FALSE, FALSE, 0);
+ gtk_box_pack_start (GTK_BOX (line_hbox), line_label, FALSE, FALSE, 0);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (line_check),
+ hack->enabled_p);
+ gtk_label_set_justify (GTK_LABEL (line_label), GTK_JUSTIFY_LEFT);
+
+ gtk_widget_show (line_check);
+ gtk_widget_show (line_label);
+ gtk_widget_show (line_hbox);
+ gtk_widget_show (line);
+
+ free (pretty_name);
+
+ gtk_container_add (GTK_CONTAINER (list), line);
+ gtk_signal_connect (GTK_OBJECT (line), "button_press_event",
+ GTK_SIGNAL_FUNC (list_doubleclick_cb),
+ (gpointer) s);
+
+ gtk_signal_connect (GTK_OBJECT (line_check), "toggled",
+ GTK_SIGNAL_FUNC (list_checkbox_cb),
+ (gpointer) s);
+
+ gtk_widget_show (line);
+
+ if (!available_p)
+ {
+ /* Make the widget be colored like insensitive widgets
+ (but don't actually make it be insensitive, since we
+ still want to be able to click on it.)
+ */
+ GtkRcStyle *rc_style;
+ GdkColor fg, bg;
+
+ gtk_widget_realize (GTK_WIDGET (line_label));
+
+ fg = GTK_WIDGET (line_label)->style->fg[GTK_STATE_INSENSITIVE];
+ bg = GTK_WIDGET (line_label)->style->bg[GTK_STATE_INSENSITIVE];
+
+ rc_style = gtk_rc_style_new ();
+ rc_style->fg[GTK_STATE_NORMAL] = fg;
+ rc_style->bg[GTK_STATE_NORMAL] = bg;
+ rc_style->color_flags[GTK_STATE_NORMAL] |= GTK_RC_FG|GTK_RC_BG;
+
+ gtk_widget_modify_style (GTK_WIDGET (line_label), rc_style);
+ gtk_rc_style_unref (rc_style);
+ }
+ }
+
+ gtk_signal_connect (GTK_OBJECT (list), "select_child",
+ GTK_SIGNAL_FUNC (list_select_cb),
+ (gpointer) s);
+ gtk_signal_connect (GTK_OBJECT (list), "unselect_child",
+ GTK_SIGNAL_FUNC (list_unselect_cb),
+ (gpointer) s);
+#endif /* !HAVE_GTK2 */
+}
+
+static void
+update_list_sensitivity (state *s)
+{
+ saver_preferences *p = &s->prefs;
+ Bool sensitive = (p->mode == RANDOM_HACKS ||
+ p->mode == RANDOM_HACKS_SAME ||
+ p->mode == ONE_HACK);
+ Bool checkable = (p->mode == RANDOM_HACKS ||
+ p->mode == RANDOM_HACKS_SAME);
+ Bool blankable = (p->mode != DONT_BLANK);
+
+#ifndef HAVE_GTK2
+ GtkWidget *head = name_to_widget (s, "col_head_hbox");
+ GtkWidget *use = name_to_widget (s, "use_col_frame");
+#endif /* HAVE_GTK2 */
+ GtkWidget *scroller = name_to_widget (s, "scroller");
+ GtkWidget *buttons = name_to_widget (s, "next_prev_hbox");
+ GtkWidget *blanker = name_to_widget (s, "blanking_table");
+
+#ifdef HAVE_GTK2
+ GtkTreeView *list = GTK_TREE_VIEW (name_to_widget (s, "list"));
+ GtkTreeViewColumn *use = gtk_tree_view_get_column (list, COL_ENABLED);
+#else /* !HAVE_GTK2 */
+ GtkList *list = GTK_LIST (name_to_widget (s, "list"));
+ GList *kids = gtk_container_children (GTK_CONTAINER (list));
+
+ gtk_widget_set_sensitive (GTK_WIDGET (head), sensitive);
+#endif /* !HAVE_GTK2 */
+ gtk_widget_set_sensitive (GTK_WIDGET (scroller), sensitive);
+ gtk_widget_set_sensitive (GTK_WIDGET (buttons), sensitive);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (blanker), blankable);
+
+#ifdef HAVE_GTK2
+ gtk_tree_view_column_set_visible (use, checkable);
+#else /* !HAVE_GTK2 */
+ if (checkable)
+ gtk_widget_show (use); /* the "Use" column header */
+ else
+ gtk_widget_hide (use);
+
+ while (kids)
+ {
+ GtkBin *line = GTK_BIN (kids->data);
+ GtkContainer *line_hbox = GTK_CONTAINER (line->child);
+ GtkWidget *line_check =
+ GTK_WIDGET (gtk_container_children (line_hbox)->data);
+
+ if (checkable)
+ gtk_widget_show (line_check);
+ else
+ gtk_widget_hide (line_check);
+
+ kids = kids->next;
+ }
+#endif /* !HAVE_GTK2 */
+}
+
+
+static void
+populate_prefs_page (state *s)
+{
+ saver_preferences *p = &s->prefs;
+
+ Bool can_lock_p = True;
+
+ /* Disable all the "lock" controls if locking support was not provided
+ at compile-time, or if running on MacOS. */
+# if defined(NO_LOCKING) || defined(__APPLE__)
+ can_lock_p = False;
+# endif
+
+
+ /* If there is only one screen, the mode menu contains
+ "random" but not "random-same".
+ */
+ if (s->nscreens <= 1 && p->mode == RANDOM_HACKS_SAME)
+ p->mode = RANDOM_HACKS;
+
+
+ /* The file supports timeouts of less than a minute, but the GUI does
+ not, so throttle the values to be at least one minute (since "0" is
+ a bad rounding choice...)
+ */
+# define THROTTLE(NAME) if (p->NAME != 0 && p->NAME < 60000) p->NAME = 60000
+ THROTTLE (timeout);
+ THROTTLE (cycle);
+ /* THROTTLE (passwd_timeout); */ /* GUI doesn't set this; leave it alone */
+# undef THROTTLE
+
+# define FMT_MINUTES(NAME,N) \
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) + 59) / (60 * 1000))
+
+# define FMT_SECONDS(NAME,N) \
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (name_to_widget (s, (NAME))), (double)((N) / 1000))
+
+ FMT_MINUTES ("timeout_spinbutton", p->timeout);
+ FMT_MINUTES ("cycle_spinbutton", p->cycle);
+ FMT_MINUTES ("lock_spinbutton", p->lock_timeout);
+ FMT_MINUTES ("dpms_standby_spinbutton", p->dpms_standby);
+ FMT_MINUTES ("dpms_suspend_spinbutton", p->dpms_suspend);
+ FMT_MINUTES ("dpms_off_spinbutton", p->dpms_off);
+ FMT_SECONDS ("fade_spinbutton", p->fade_seconds);
+
+# undef FMT_MINUTES
+# undef FMT_SECONDS
+
+# define TOGGLE_ACTIVE(NAME,ACTIVEP) \
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (name_to_widget (s,(NAME))),\
+ (ACTIVEP))
+
+ TOGGLE_ACTIVE ("lock_button", p->lock_p);
+#if 0
+ TOGGLE_ACTIVE ("verbose_button", p->verbose_p);
+ TOGGLE_ACTIVE ("capture_button", p->capture_stderr_p);
+ TOGGLE_ACTIVE ("splash_button", p->splash_p);
+#endif
+ TOGGLE_ACTIVE ("dpms_button", p->dpms_enabled_p);
+ TOGGLE_ACTIVE ("dpms_quickoff_button", p->dpms_quickoff_p);
+ TOGGLE_ACTIVE ("grab_desk_button", p->grab_desktop_p);
+ TOGGLE_ACTIVE ("grab_video_button", p->grab_video_p);
+ TOGGLE_ACTIVE ("grab_image_button", p->random_image_p);
+ TOGGLE_ACTIVE ("install_button", p->install_cmap_p);
+ TOGGLE_ACTIVE ("fade_button", p->fade_p);
+ TOGGLE_ACTIVE ("unfade_button", p->unfade_p);
+
+ switch (p->tmode)
+ {
+ case TEXT_LITERAL: TOGGLE_ACTIVE ("text_radio", True); break;
+ case TEXT_FILE: TOGGLE_ACTIVE ("text_file_radio", True); break;
+ case TEXT_PROGRAM: TOGGLE_ACTIVE ("text_program_radio", True); break;
+ case TEXT_URL: TOGGLE_ACTIVE ("text_url_radio", True); break;
+ default: TOGGLE_ACTIVE ("text_host_radio", True); break;
+ }
+
+# undef TOGGLE_ACTIVE
+
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "image_text")),
+ (p->image_directory ? p->image_directory : ""));
+ gtk_widget_set_sensitive (name_to_widget (s, "image_text"),
+ p->random_image_p);
+ gtk_widget_set_sensitive (name_to_widget (s, "image_browse_button"),
+ p->random_image_p);
+
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_entry")),
+ (p->text_literal ? p->text_literal : ""));
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_file_entry")),
+ (p->text_file ? p->text_file : ""));
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_program_entry")),
+ (p->text_program ? p->text_program : ""));
+ gtk_entry_set_text (GTK_ENTRY (name_to_widget (s, "text_url_entry")),
+ (p->text_url ? p->text_url : ""));
+
+ gtk_widget_set_sensitive (name_to_widget (s, "text_entry"),
+ p->tmode == TEXT_LITERAL);
+ gtk_widget_set_sensitive (name_to_widget (s, "text_file_entry"),
+ p->tmode == TEXT_FILE);
+ gtk_widget_set_sensitive (name_to_widget (s, "text_file_browse"),
+ p->tmode == TEXT_FILE);
+ gtk_widget_set_sensitive (name_to_widget (s, "text_program_entry"),
+ p->tmode == TEXT_PROGRAM);
+ gtk_widget_set_sensitive (name_to_widget (s, "text_program_browse"),
+ p->tmode == TEXT_PROGRAM);
+ gtk_widget_set_sensitive (name_to_widget (s, "text_url_entry"),
+ p->tmode == TEXT_URL);
+
+
+ /* Map the `saver_mode' enum to mode menu to values. */
+ {
+ GtkOptionMenu *opt = GTK_OPTION_MENU (name_to_widget (s, "mode_menu"));
+
+ int i;
+ for (i = 0; i < countof(mode_menu_order); i++)
+ if (mode_menu_order[i] == p->mode)
+ break;
+ gtk_option_menu_set_history (opt, i);
+ update_list_sensitivity (s);
+ }
+
+ {
+ Bool found_any_writable_cells = False;
+ Bool fading_possible = False;
+ Bool dpms_supported = False;
+
+ Display *dpy = GDK_DISPLAY();
+ int nscreens = ScreenCount(dpy); /* real screens, not Xinerama */
+ int i;
+ for (i = 0; i < nscreens; i++)
+ {
+ Screen *s = ScreenOfDisplay (dpy, i);
+ if (has_writable_cells (s, DefaultVisualOfScreen (s)))
+ {
+ found_any_writable_cells = True;
+ break;
+ }
+ }
+
+ fading_possible = found_any_writable_cells;
+#ifdef HAVE_XF86VMODE_GAMMA
+ fading_possible = True;
+#endif
+
+#ifdef HAVE_DPMS_EXTENSION
+ {
+ int op = 0, event = 0, error = 0;
+ if (XQueryExtension (dpy, "DPMS", &op, &event, &error))
+ dpms_supported = True;
+ }
+#endif /* HAVE_DPMS_EXTENSION */
+
+
+# define SENSITIZE(NAME,SENSITIVEP) \
+ gtk_widget_set_sensitive (name_to_widget (s, (NAME)), (SENSITIVEP))
+
+ /* Blanking and Locking
+ */
+ SENSITIZE ("lock_button", can_lock_p);
+ SENSITIZE ("lock_spinbutton", can_lock_p && p->lock_p);
+ SENSITIZE ("lock_mlabel", can_lock_p && p->lock_p);
+
+ /* DPMS
+ */
+ SENSITIZE ("dpms_frame", dpms_supported);
+ SENSITIZE ("dpms_button", dpms_supported);
+ SENSITIZE ("dpms_quickoff_button", dpms_supported);
+
+ SENSITIZE ("dpms_standby_label", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_standby_mlabel", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_standby_spinbutton", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_suspend_label", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_suspend_mlabel", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_suspend_spinbutton", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_off_label", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_off_mlabel", dpms_supported && p->dpms_enabled_p);
+ SENSITIZE ("dpms_off_spinbutton", dpms_supported && p->dpms_enabled_p);
+
+ /* Colormaps
+ */
+ SENSITIZE ("cmap_frame", found_any_writable_cells || fading_possible);
+ SENSITIZE ("install_button", found_any_writable_cells);
+ SENSITIZE ("fade_button", fading_possible);
+ SENSITIZE ("unfade_button", fading_possible);
+
+ SENSITIZE ("fade_label", (fading_possible &&
+ (p->fade_p || p->unfade_p)));
+ SENSITIZE ("fade_spinbutton", (fading_possible &&
+ (p->fade_p || p->unfade_p)));
+
+# undef SENSITIZE
+ }
+}
+
+
+static void
+populate_popup_window (state *s)
+{
+ GtkLabel *doc = GTK_LABEL (name_to_widget (s, "doc"));
+ char *doc_string = 0;
+
+ /* #### not in Gtk 1.2
+ gtk_label_set_selectable (doc);
+ */
+
+# ifdef HAVE_XML
+ if (s->cdata)
+ {
+ free_conf_data (s->cdata);
+ s->cdata = 0;
+ }
+
+ {
+ saver_preferences *p = &s->prefs;
+ int list_elt = selected_list_element (s);
+ int hack_number = (list_elt >= 0 && list_elt < s->list_count
+ ? s->list_elt_to_hack_number[list_elt]
+ : -1);
+ screenhack *hack = (hack_number >= 0 ? p->screenhacks[hack_number] : 0);
+ if (hack)
+ {
+ GtkWidget *parent = name_to_widget (s, "settings_vbox");
+ GtkWidget *cmd = GTK_WIDGET (name_to_widget (s, "cmd_text"));
+ const char *cmd_line = gtk_entry_get_text (GTK_ENTRY (cmd));
+ s->cdata = load_configurator (cmd_line, s->debug_p);
+ if (s->cdata && s->cdata->widget)
+ gtk_box_pack_start (GTK_BOX (parent), s->cdata->widget,
+ TRUE, TRUE, 0);
+ }
+ }
+
+ doc_string = (s->cdata
+ ? s->cdata->description
+ : 0);
+# else /* !HAVE_XML */
+ doc_string = _("Descriptions not available: no XML support compiled in.");
+# endif /* !HAVE_XML */
+
+ gtk_label_set_text (doc, (doc_string
+ ? _(doc_string)
+ : _("No description available.")));
+}
+
+
+static void
+sensitize_demo_widgets (state *s, Bool sensitive_p)
+{
+ const char *names[] = { "demo", "settings",
+ "cmd_label", "cmd_text", "manual",
+ "visual", "visual_combo" };
+ int i;
+ for (i = 0; i < countof(names); i++)
+ {
+ GtkWidget *w = name_to_widget (s, names[i]);
+ gtk_widget_set_sensitive (GTK_WIDGET(w), sensitive_p);
+ }
+}
+
+
+static void
+sensitize_menu_items (state *s, Bool force_p)
+{
+ static Bool running_p = False;
+ static time_t last_checked = 0;
+ time_t now = time ((time_t *) 0);
+ const char *names[] = { "activate_menu", "lock_menu", "kill_menu",
+ /* "demo" */ };
+ int i;
+
+ if (force_p || now > last_checked + 10) /* check every 10 seconds */
+ {
+ running_p = xscreensaver_running_p (s);
+ last_checked = time ((time_t *) 0);
+ }
+
+ for (i = 0; i < countof(names); i++)
+ {
+ GtkWidget *w = name_to_widget (s, names[i]);
+ gtk_widget_set_sensitive (GTK_WIDGET(w), running_p);
+ }
+}
+
+
+/* When the File menu is de-posted after a "Restart Daemon" command,
+ the window underneath doesn't repaint for some reason. I guess this
+ is a bug in exposure handling in GTK or GDK. This works around it.
+ */
+static void
+force_dialog_repaint (state *s)
+{
+#if 1
+ /* Tell GDK to invalidate and repaint the whole window.
+ */
+ GdkWindow *w = GET_WINDOW (s->toplevel_widget);
+ GdkRegion *region = gdk_region_new ();
+ GdkRectangle rect;
+ rect.x = rect.y = 0;
+ rect.width = rect.height = 32767;
+ gdk_region_union_with_rect (region, &rect);
+ gdk_window_invalidate_region (w, region, True);
+ gdk_region_destroy (region);
+ gdk_window_process_updates (w, True);
+#else
+ /* Force the server to send an exposure event by creating and then
+ destroying a window as a child of the top level shell.
+ */
+ Display *dpy = GDK_DISPLAY();
+ Window parent = GDK_WINDOW_XWINDOW (s->toplevel_widget->window);
+ Window w;
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (dpy, parent, &xgwa);
+ w = XCreateSimpleWindow (dpy, parent, 0, 0, xgwa.width, xgwa.height, 0,0,0);
+ XMapRaised (dpy, w);
+ XDestroyWindow (dpy, w);
+ XSync (dpy, False);
+#endif
+}
+
+
+/* Even though we've given these text fields a maximum number of characters,
+ their default size is still about 30 characters wide -- so measure out
+ a string in their font, and resize them to just fit that.
+ */
+static void
+fix_text_entry_sizes (state *s)
+{
+ GtkWidget *w;
+
+# if 0 /* appears no longer necessary with Gtk 1.2.10 */
+ const char * const spinbuttons[] = {
+ "timeout_spinbutton", "cycle_spinbutton", "lock_spinbutton",
+ "dpms_standby_spinbutton", "dpms_suspend_spinbutton",
+ "dpms_off_spinbutton",
+ "-fade_spinbutton" };
+ int i;
+ int width = 0;
+
+ for (i = 0; i < countof(spinbuttons); i++)
+ {
+ const char *n = spinbuttons[i];
+ int cols = 4;
+ while (*n == '-') n++, cols--;
+ w = GTK_WIDGET (name_to_widget (s, n));
+ width = gdk_text_width (w->style->font, "MMMMMMMM", cols);
+ gtk_widget_set_usize (w, width, -2);
+ }
+
+ /* Now fix the width of the combo box.
+ */
+ w = GTK_WIDGET (name_to_widget (s, "visual_combo"));
+ w = GTK_COMBO (w)->entry;
+ width = gdk_string_width (w->style->font, "PseudoColor___");
+ gtk_widget_set_usize (w, width, -2);
+
+ /* Now fix the width of the file entry text.
+ */
+ w = GTK_WIDGET (name_to_widget (s, "image_text"));
+ width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmm");
+ gtk_widget_set_usize (w, width, -2);
+
+ /* Now fix the width of the command line text.
+ */
+ w = GTK_WIDGET (name_to_widget (s, "cmd_text"));
+ width = gdk_string_width (w->style->font, "mmmmmmmmmmmmmmmmmmmm");
+ gtk_widget_set_usize (w, width, -2);
+
+# endif /* 0 */
+
+ /* Now fix the height of the list widget:
+ make it default to being around 10 text-lines high instead of 4.
+ */
+ w = GTK_WIDGET (name_to_widget (s, "list"));
+ {
+ int lines = 10;
+ int height;
+ int leading = 3; /* approximate is ok... */
+ int border = 2;
+
+#ifdef HAVE_GTK2
+ PangoFontMetrics *pain =
+ pango_context_get_metrics (gtk_widget_get_pango_context (w),
+ gtk_widget_get_style (w)->font_desc,
+ gtk_get_default_language ());
+ height = PANGO_PIXELS (pango_font_metrics_get_ascent (pain) +
+ pango_font_metrics_get_descent (pain));
+#else /* !HAVE_GTK2 */
+ height = w->style->font->ascent + w->style->font->descent;
+#endif /* !HAVE_GTK2 */
+
+ height += leading;
+ height *= lines;
+ height += border * 2;
+ w = GTK_WIDGET (name_to_widget (s, "scroller"));
+ gtk_widget_set_usize (w, -2, height);
+ }
+}
+
+
+#ifndef HAVE_GTK2
+\f
+/* Pixmaps for the up and down arrow buttons (yeah, this is sleazy...)
+ */
+
+static char *up_arrow_xpm[] = {
+ "15 15 4 1",
+ " c None",
+ "- c #FFFFFF",
+ "+ c #D6D6D6",
+ "@ c #000000",
+
+ " @ ",
+ " @ ",
+ " -+@ ",
+ " -+@ ",
+ " -+++@ ",
+ " -+++@ ",
+ " -+++++@ ",
+ " -+++++@ ",
+ " -+++++++@ ",
+ " -+++++++@ ",
+ " -+++++++++@ ",
+ " -+++++++++@ ",
+ " -+++++++++++@ ",
+ " @@@@@@@@@@@@@ ",
+ " ",
+
+ /* Need these here because gdk_pixmap_create_from_xpm_d() walks off
+ the end of the array (Gtk 1.2.5.) */
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000",
+ "\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"
+};
+
+static char *down_arrow_xpm[] = {
+ "15 15 4 1",