Fix typo in manual page
[pipeglade.git] / pipeglade.c
bloba1dd9d315159e53fa36b40ba32b5192df64fc4a4
1 /*
2 * Copyright (c) 2014-2016 Bert Burgemeister <trebbu@googlemail.com>
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <gtk/gtk.h>
27 #include <gtk/gtkunixprint.h>
28 #include <gtk/gtkx.h>
29 #include <inttypes.h>
30 #include <libxml/xpath.h>
31 #include <locale.h>
32 #include <math.h>
33 #include <pthread.h>
34 #include <stdio.h>
35 #include <stdarg.h>
36 #include <stdbool.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <sys/select.h>
40 #include <sys/stat.h>
41 #include <time.h>
42 #include <unistd.h>
44 #define VERSION "4.7.0"
45 #define BUFLEN 256
46 #define WHITESPACE " \t\n"
47 #define MAIN_WIN "main"
48 #define USAGE \
49 "usage: pipeglade [[-i in-fifo] " \
50 "[-o out-fifo] " \
51 "[-b] " \
52 "[-u glade-file.ui] " \
53 "[-e xid]\n" \
54 " [-l log-file] " \
55 "[-O err-file] " \
56 "[--display X-server]] | " \
57 "[-h |" \
58 "-G |" \
59 "-V]\n"
61 #define ABORT \
62 { \
63 fprintf(stderr, \
64 "In %s (%s:%d): ", \
65 __func__, __FILE__, __LINE__); \
66 abort(); \
69 #define OOM_ABORT \
70 { \
71 fprintf(stderr, \
72 "Out of memory in %s (%s:%d): ", \
73 __func__, __FILE__, __LINE__); \
74 abort(); \
77 static FILE *out; /* UI feedback messages */
78 static FILE *save; /* saving user data */
79 static FILE *log_out; /* logging output */
80 static GtkBuilder *builder; /* to be read from .ui file */
84 * ============================================================
85 * Helper functions
86 * ============================================================
90 * Check if s1 and s2 are equal strings
92 static bool
93 eql(const char *s1, const char *s2)
95 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
99 * Print a formatted message to stream s and give up with status
101 static void
102 bye(int status, FILE *s, const char *fmt, ...)
104 va_list ap;
106 va_start(ap, fmt);
107 vfprintf(s, fmt, ap);
108 va_end(ap);
109 exit(status);
112 static void
113 show_lib_versions(void)
115 bye(EXIT_SUCCESS, stdout,
116 "GTK+ v%d.%d.%d (running v%d.%d.%d)\n"
117 "cairo v%s (running v%s)\n",
118 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
119 gtk_get_major_version(), gtk_get_minor_version(),
120 gtk_get_micro_version(),
121 CAIRO_VERSION_STRING, cairo_version_string());
125 * XEmbed us if xid_s is given, or show a standalone window; give up
126 * on errors
128 static void
129 xembed_if(char *xid_s, GObject *main_window)
131 GtkWidget *plug, *body;
132 Window xid;
133 char xid_s2[BUFLEN];
135 if (xid_s == NULL) { /* standalone */
136 gtk_widget_show(GTK_WIDGET(main_window));
137 return;
139 /* We're being XEmbedded */
140 xid = strtoul(xid_s, NULL, 10);
141 snprintf(xid_s2, BUFLEN, "%lu", xid);
142 if (!eql(xid_s, xid_s2))
143 bye(EXIT_FAILURE, stderr,
144 "%s is not a valid XEmbed socket id\n", xid_s);
145 body = gtk_bin_get_child(GTK_BIN(main_window));
146 gtk_container_remove(GTK_CONTAINER(main_window), body);
147 plug = gtk_plug_new(xid);
148 if (!gtk_plug_get_embedded(GTK_PLUG(plug)))
149 bye(EXIT_FAILURE, stderr,
150 "unable to embed into XEmbed socket %s\n", xid_s);
151 gtk_container_add(GTK_CONTAINER(plug), body);
152 gtk_widget_show(plug);
156 * If requested, redirect stderr to file name
158 static void
159 redirect_stderr(const char *name)
161 if (name && (freopen(name, "a", stderr)) == NULL)
162 /* complaining on stdout since stderr is closed now */
163 bye(EXIT_FAILURE, stdout, "couldn't redirect stderr to %s: %s\n",
164 name, strerror(errno));
168 * fork() if requested in bg; give up on errors
170 static void
171 go_bg_if(bool bg, FILE *in, FILE *out, char *err_file)
173 pid_t pid = 0;
175 if (!bg)
176 return;
177 if (in == stdin || out == stdout)
178 bye(EXIT_FAILURE, stderr,
179 "parameter -b requires both -i and -o\n");
180 pid = fork();
181 if (pid < 0)
182 bye(EXIT_FAILURE, stderr,
183 "going to background: %s\n", strerror(errno));
184 if (pid > 0)
185 bye(EXIT_SUCCESS, stdout, "%d\n", pid);
186 /* We're the child */
187 close(fileno(stdin)); /* making certain not-so-smart */
188 close(fileno(stdout)); /* system/run-shell commands happy */
189 if (err_file == NULL)
190 redirect_stderr("/dev/null");
194 * Return the current locale and set it to "C". Should be free()d if
195 * done.
197 static char *
198 lc_numeric()
200 char *lc_orig;
201 char *lc = setlocale(LC_NUMERIC, NULL);
203 if ((lc_orig = malloc(strlen(lc) + 1)) == NULL)
204 OOM_ABORT;
205 strcpy(lc_orig, lc);
206 setlocale(LC_NUMERIC, "C");
207 return lc_orig;
211 * Set locale (back) to lc; free lc
213 static void
214 lc_numeric_free(char *lc)
216 setlocale(LC_NUMERIC, lc);
217 free(lc);
221 * Print a warning about a malformed command to stderr
223 static void
224 ign_cmd(GType type, const char *msg)
226 const char *name, *pad = " ";
228 if (type == G_TYPE_INVALID) {
229 name = "";
230 pad = "";
231 } else
232 name = g_type_name(type);
233 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
237 * Check if n is, or can be made, the name of a fifo. Give up if n
238 * exists but is not a fifo.
240 static void
241 find_fifo(const char *n)
243 struct stat sb;
245 stat(n, &sb);
246 if (S_ISFIFO(sb.st_mode)) {
247 if (chmod(n, 0600) != 0)
248 bye(EXIT_FAILURE, stderr, "using pre-existing fifo %s: %s\n",
249 n, strerror(errno));
250 } else if (mkfifo(n, 0600) != 0)
251 bye(EXIT_FAILURE, stderr, "making fifo %s: %s\n",
252 n, strerror(errno));
256 * Create a fifo if necessary, and open it. Give up if the file
257 * exists but is not a fifo
259 static FILE *
260 open_in_fifo(const char *name)
262 FILE *s = NULL;
263 int fd;
265 if (name == NULL)
266 s = stdin;
267 else {
268 find_fifo(name);
269 if ((fd = open(name, O_RDWR | O_NONBLOCK)) < 0)
270 bye(EXIT_FAILURE, stderr, "opening fifo %s (r): %s\n",
271 name, strerror(errno));
272 if ((s = fdopen(fd, "r")) == NULL)
273 bye(EXIT_FAILURE, stderr, "opening fifo %s (r): %s\n",
274 name, strerror(errno));
276 setvbuf(s, NULL, _IONBF, 0);
277 return s;
280 static FILE *
281 open_out_fifo(const char *name)
283 FILE *s = NULL;
285 if (name == NULL)
286 s = stdout;
287 else {
288 find_fifo(name);
289 if ((s = fopen(name, "w+")) == NULL)
290 bye(EXIT_FAILURE, stderr, "opening fifo %s (w): %s\n",
291 name, strerror(errno));
293 setvbuf(s, NULL, _IOLBF, 0);
294 return s;
298 * Create a log file if necessary, and open it. A name of "-"
299 * requests use of stderr.
301 static FILE *
302 open_log(const char *name)
304 FILE *s = NULL;
306 if (eql(name, "-"))
307 s = stderr;
308 else if (name && (s = fopen(name, "a")) == NULL)
309 bye(EXIT_FAILURE, stderr, "opening log file %s: %s\n",
310 name, strerror(errno));
311 return s;
314 static void
315 rm_unless(FILE *forbidden, FILE *s, char *name)
317 if (s == forbidden)
318 return;
319 fclose(s);
320 unlink(name);
324 * Microseconds elapsed since start
326 static long int
327 usec_since(struct timespec *start)
329 struct timespec now;
331 clock_gettime(CLOCK_MONOTONIC, &now);
332 return (now.tv_sec - start->tv_sec) * 1e6 +
333 (now.tv_nsec - start->tv_nsec) / 1e3;
337 * Write log file
339 static void
340 log_msg(char *msg)
342 static char *old_msg;
343 static struct timespec start;
345 if (log_out == NULL) /* no logging */
346 return;
347 if (msg == NULL && old_msg == NULL)
348 fprintf(log_out,
349 "##########\t##### (New Pipeglade session) #####\n");
350 else if (msg == NULL && old_msg != NULL) { /* command done; start idle */
351 fprintf(log_out,
352 "%10ld\t%s\n", usec_since(&start), old_msg);
353 free(old_msg);
354 old_msg = NULL;
355 } else if (msg != NULL && old_msg == NULL) { /* idle done; start command */
356 fprintf(log_out,
357 "%10ld\t### (Idle) ###\n", usec_since(&start));
358 if ((old_msg = malloc(strlen(msg) + 1)) == NULL)
359 OOM_ABORT;
360 strcpy(old_msg, msg);
361 } else
362 ABORT;
363 clock_gettime(CLOCK_MONOTONIC, &start);
367 * Remove suffix from name; find the object named like this
369 static GObject *
370 obj_sans_suffix(const char *suffix, const char *name)
372 char str[BUFLEN + 1] = {'\0'};
373 int str_l;
375 str_l = suffix - name;
376 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
377 return gtk_builder_get_object(builder, str);
381 * Read UI definition from ui_file; give up on errors
383 static GtkBuilder *
384 builder_from_file(char *ui_file)
386 GError *error = NULL;
387 GtkBuilder *b;
389 b = gtk_builder_new();
390 if (gtk_builder_add_from_file(b, ui_file, &error) == 0)
391 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
392 return b;
395 static const char *
396 widget_name(GtkBuildable *obj)
398 return gtk_buildable_get_name(obj);
402 * Get the main window; give up on errors
404 static GObject *
405 find_main_window(void)
407 GObject *mw;
409 if (GTK_IS_WINDOW(mw = gtk_builder_get_object(builder, MAIN_WIN)))
410 return mw;
411 bye(EXIT_FAILURE, stderr, "no toplevel window named \'" MAIN_WIN "\'\n");
412 return NULL; /* NOT REACHED */
416 * Store a line from stream s into buf, which should have been malloc'd
417 * to bufsize. Enlarge buf and bufsize if necessary.
419 static size_t
420 read_buf(FILE *s, char **buf, size_t *bufsize)
422 bool esc = false;
423 fd_set rfds;
424 int c;
425 int ifd = fileno(s);
426 size_t i = 0;
428 FD_ZERO(&rfds);
429 FD_SET(ifd, &rfds);
430 for (;;) {
431 select(ifd + 1, &rfds, NULL, NULL, NULL);
432 c = getc(s);
433 if (c == '\n' || feof(s))
434 break;
435 if (i >= *bufsize - 1)
436 if ((*buf = realloc(*buf, *bufsize *= 2)) == NULL)
437 OOM_ABORT;
438 if (esc) {
439 esc = false;
440 switch (c) {
441 case 'n': (*buf)[i++] = '\n'; break;
442 case 'r': (*buf)[i++] = '\r'; break;
443 default: (*buf)[i++] = c; break;
445 } else if (c == '\\')
446 esc = true;
447 else
448 (*buf)[i++] = c;
450 (*buf)[i] = '\0';
451 return i;
456 * ============================================================
457 * Receiving feedback from the GUI
458 * ============================================================
461 static void
462 send_msg_to(FILE* o, GtkBuildable *obj, const char *tag, va_list ap)
464 char *data;
465 const char *w_name = widget_name(obj);
466 fd_set wfds;
467 int ofd = fileno(o);
468 struct timeval timeout = {1, 0};
470 FD_ZERO(&wfds);
471 FD_SET(ofd, &wfds);
472 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
473 fprintf(o, "%s:%s ", w_name, tag);
474 while ((data = va_arg(ap, char *)) != NULL) {
475 size_t i = 0;
476 char c;
478 while ((c = data[i++]) != '\0')
479 if (c == '\\')
480 fprintf(o, "\\\\");
481 else if (c == '\n')
482 fprintf(o, "\\n");
483 else
484 putc(c, o);
486 putc('\n', o);
487 } else
488 fprintf(stderr,
489 "send error; discarding feedback message %s:%s\n",
490 w_name, tag);
494 * Send GUI feedback to global stream "out". The message format is
495 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
496 * last argument must be NULL.
498 static void
499 send_msg(GtkBuildable *obj, const char *tag, ...)
501 va_list ap;
503 va_start(ap, tag);
504 send_msg_to(out, obj, tag, ap);
505 va_end(ap);
509 * Send message from GUI to global stream "save". The message format
510 * is "<origin>:<tag> <data ...>". The variadic arguments are strings;
511 * last argument must be NULL.
513 static void
514 save_msg(GtkBuildable *obj, const char *tag, ...)
516 va_list ap;
518 va_start(ap, tag);
519 send_msg_to(save, obj, tag, ap);
520 va_end(ap);
524 * Send message from GUI to global stream "save". The message format
525 * is "<origin>:set <data ...>". The variadic arguments are strings;
526 * last argument must be NULL.
528 static void
529 save_action_set_msg(GtkBuildable *obj, const char *tag, ...)
531 va_list ap;
533 va_start(ap, tag);
534 send_msg_to(save, obj, "set", ap);
535 va_end(ap);
539 * Use msg_sender() to send a message describing a particular cell
541 static void
542 send_tree_cell_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
543 GtkTreeModel *model, const char *path_s,
544 GtkTreeIter *iter, int col, GtkBuildable *obj)
546 GType col_type;
547 GValue value = G_VALUE_INIT;
548 char str[BUFLEN], *lc = lc_numeric();
550 gtk_tree_model_get_value(model, iter, col, &value);
551 col_type = gtk_tree_model_get_column_type(model, col);
552 switch (col_type) {
553 case G_TYPE_INT:
554 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
555 msg_sender(obj, "gint", path_s, str, NULL);
556 break;
557 case G_TYPE_LONG:
558 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
559 msg_sender(obj, "glong", path_s, str, NULL);
560 break;
561 case G_TYPE_INT64:
562 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
563 msg_sender(obj, "gint64", path_s, str, NULL);
564 break;
565 case G_TYPE_UINT:
566 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
567 msg_sender(obj, "guint", path_s, str, NULL);
568 break;
569 case G_TYPE_ULONG:
570 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
571 msg_sender(obj, "gulong", path_s, str, NULL);
572 break;
573 case G_TYPE_UINT64:
574 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
575 msg_sender(obj, "guint64", path_s, str, NULL);
576 break;
577 case G_TYPE_BOOLEAN:
578 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
579 msg_sender(obj, "gboolean", path_s, str, NULL);
580 break;
581 case G_TYPE_FLOAT:
582 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
583 msg_sender(obj, "gfloat", path_s, str, NULL);
584 break;
585 case G_TYPE_DOUBLE:
586 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
587 msg_sender(obj, "gdouble", path_s, str, NULL);
588 break;
589 case G_TYPE_STRING:
590 snprintf(str, BUFLEN, " %d ", col);
591 msg_sender(obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
592 break;
593 default:
594 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
595 break;
597 g_value_unset(&value);
598 lc_numeric_free(lc);
602 * Use msg_sender() to send one message per column for a single row
604 static void
605 send_tree_row_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
606 GtkTreeModel *model, char *path_s,
607 GtkTreeIter *iter, GtkBuildable *obj)
609 int col;
611 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++)
612 send_tree_cell_msg_by(msg_sender, model, path_s, iter, col, obj);
616 * send_tree_row_msg serves as an argument for
617 * gtk_tree_selection_selected_foreach()
619 static gboolean
620 send_tree_row_msg(GtkTreeModel *model,
621 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
623 char *path_s = gtk_tree_path_to_string(path);
625 send_tree_row_msg_by(send_msg, model, path_s, iter, obj);
626 g_free(path_s);
627 return FALSE;
631 * save_tree_row_msg serves as an argument for
632 * gtk_tree_model_foreach().
633 * Send message from GUI to global stream "save".
635 static gboolean
636 save_tree_row_msg(GtkTreeModel *model,
637 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
639 char *path_s = gtk_tree_path_to_string(path);
641 (void) path;
642 send_tree_row_msg_by(save_action_set_msg, model, path_s, iter, obj);
643 g_free(path_s);
644 return FALSE;
647 static void
648 cb_calendar(GtkBuildable *obj, const char *tag)
650 char str[BUFLEN];
651 unsigned int year = 0, month = 0, day = 0;
653 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
654 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
655 send_msg(obj, tag, str, NULL);
658 static void
659 cb_color_button(GtkBuildable *obj, const char *tag)
661 GdkRGBA color;
663 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
664 send_msg(obj, tag, gdk_rgba_to_string(&color), NULL);
667 static void
668 cb_editable(GtkBuildable *obj, const char *tag)
670 send_msg(obj, tag, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
674 * Callback that sends a message about a pointer device button press
675 * in a GtkEventBox
677 static bool
678 cb_event_box_button(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
680 char data[BUFLEN], *lc = lc_numeric();
682 snprintf(data, BUFLEN, "%d %.1lf %.1lf",
683 e->button.button, e->button.x, e->button.y);
684 send_msg(obj, user_data, data, NULL);
685 lc_numeric_free(lc);
686 return true;
690 * Callback that sends in a message the name of the key pressed when
691 * a GtkEventBox is focused
693 static bool
694 cb_event_box_key(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
696 send_msg(obj, user_data, gdk_keyval_name(e->key.keyval), NULL);
697 return true;
701 * Callback that sends a message about pointer device motion in a
702 * GtkEventBox
704 static bool
705 cb_event_box_motion(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
707 char data[BUFLEN], *lc = lc_numeric();
709 snprintf(data, BUFLEN, "%.1lf %.1lf", e->button.x, e->button.y);
710 send_msg(obj, user_data, data, NULL);
711 lc_numeric_free(lc);
712 return true;
716 * Callback that only sends "name:tag" and returns false
718 static bool
719 cb_event_simple(GtkBuildable *obj, GdkEvent *e, const char *tag)
721 (void) e;
722 send_msg(obj, tag, NULL);
723 return false;
726 static void
727 cb_file_chooser_button(GtkBuildable *obj, const char *tag)
729 send_msg(obj, tag, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
732 static void
733 cb_font_button(GtkBuildable *obj, const char *tag)
735 send_msg(obj, tag, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
738 static void
739 cb_menu_item(GtkBuildable *obj, const char *tag)
741 send_msg(obj, tag, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
744 static void
745 cb_range(GtkBuildable *obj, const char *tag)
747 char str[BUFLEN], *lc = lc_numeric();
749 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
750 send_msg(obj, tag, str, NULL);
751 lc_numeric_free(lc);
755 * Callback that sends user's selection from a file dialog
757 static void
758 cb_send_file_chooser_dialog_selection(gpointer user_data)
760 send_msg(user_data, "file",
761 gtk_file_chooser_get_filename(user_data), NULL);
762 send_msg(user_data, "folder",
763 gtk_file_chooser_get_current_folder(user_data), NULL);
767 * Callback that sends in a message the content of the text buffer
768 * passed in user_data
770 static void
771 cb_send_text(GtkBuildable *obj, gpointer user_data)
773 GtkTextIter a, b;
775 gtk_text_buffer_get_bounds(user_data, &a, &b);
776 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
780 * Callback that sends in a message the highlighted text from the text
781 * buffer which was passed in user_data
783 static void
784 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
786 GtkTextIter a, b;
788 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
789 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
793 * Callback that only sends "name:tag" and returns true
795 static bool
796 cb_simple(GtkBuildable *obj, const char *tag)
798 send_msg(obj, tag, NULL);
799 return true;
802 static void
803 cb_switch(GtkBuildable *obj, void *pspec, void *user_data)
805 (void) pspec;
806 (void) user_data;
807 send_msg(obj, gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0", NULL);
810 static void
811 cb_toggle_button(GtkBuildable *obj, const char *tag)
813 (void) tag;
814 send_msg(obj, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0", NULL);
817 static void
818 cb_tree_selection(GtkBuildable *obj, const char *tag)
820 GtkTreeView *view;
822 view = gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj));
823 send_msg(GTK_BUILDABLE(view), tag, NULL);
824 gtk_tree_selection_selected_foreach(GTK_TREE_SELECTION(obj),
825 (GtkTreeSelectionForeachFunc) send_tree_row_msg,
826 view);
831 * ============================================================
832 * cb_draw() maintains a drawing on a GtkDrawingArea; it needs a few
833 * helper functions
834 * ============================================================
838 * The set of supported drawing operations
840 enum cairo_fn {
841 ARC,
842 ARC_NEGATIVE,
843 CLOSE_PATH,
844 CURVE_TO,
845 FILL,
846 FILL_PRESERVE,
847 LINE_TO,
848 MOVE_TO,
849 RECTANGLE,
850 REL_CURVE_TO,
851 REL_LINE_TO,
852 REL_MOVE_TO,
853 REL_MOVE_FOR,
854 RESET_CTM,
855 SET_DASH,
856 SET_FONT_FACE,
857 SET_FONT_SIZE,
858 SET_LINE_CAP,
859 SET_LINE_JOIN,
860 SET_LINE_WIDTH,
861 SET_SOURCE_RGBA,
862 SHOW_TEXT,
863 STROKE,
864 STROKE_PRESERVE,
865 TRANSFORM,
869 * Text placement mode for rel_move_for()
871 enum ref_point {
884 * One single element of a drawing
886 struct draw_op {
887 struct draw_op *next;
888 struct draw_op *prev;
889 unsigned int id;
890 enum cairo_fn op;
891 void *op_args;
895 * Argument sets for the various drawing operations
897 struct arc_args {
898 double x;
899 double y;
900 double radius;
901 double angle1;
902 double angle2;
905 struct curve_to_args {
906 double x1;
907 double y1;
908 double x2;
909 double y2;
910 double x3;
911 double y3;
914 struct move_to_args {
915 double x;
916 double y;
919 struct rectangle_args {
920 double x;
921 double y;
922 double width;
923 double height;
926 struct rel_move_for_args {
927 enum ref_point ref;
928 int len;
929 char text[];
932 struct set_dash_args {
933 int num_dashes;
934 double dashes[];
937 struct set_font_face_args {
938 cairo_font_slant_t slant;
939 cairo_font_weight_t weight;
940 char family[];
943 struct set_font_size_args {
944 double size;
947 struct set_line_cap_args {
948 cairo_line_cap_t line_cap;
951 struct set_line_join_args {
952 cairo_line_join_t line_join;
955 struct set_line_width_args {
956 double width;
959 struct set_source_rgba_args {
960 GdkRGBA color;
963 struct show_text_args {
964 int len;
965 char text[];
968 struct transform_args {
969 cairo_matrix_t matrix;
972 static void
973 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
975 switch (op) {
976 case LINE_TO: {
977 struct move_to_args *args = op_args;
979 cairo_line_to(cr, args->x, args->y);
980 break;
982 case REL_LINE_TO: {
983 struct move_to_args *args = op_args;
985 cairo_rel_line_to(cr, args->x, args->y);
986 break;
988 case MOVE_TO: {
989 struct move_to_args *args = op_args;
991 cairo_move_to(cr, args->x, args->y);
992 break;
994 case REL_MOVE_TO: {
995 struct move_to_args *args = op_args;
997 cairo_rel_move_to(cr, args->x, args->y);
998 break;
1000 case ARC: {
1001 struct arc_args *args = op_args;
1003 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1004 break;
1006 case ARC_NEGATIVE: {
1007 struct arc_args *args = op_args;
1009 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1010 break;
1012 case CURVE_TO: {
1013 struct curve_to_args *args = op_args;
1015 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1016 break;
1018 case REL_CURVE_TO: {
1019 struct curve_to_args *args = op_args;
1021 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1022 break;
1024 case RECTANGLE: {
1025 struct rectangle_args *args = op_args;
1027 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
1028 break;
1030 case CLOSE_PATH:
1031 cairo_close_path(cr);
1032 break;
1033 case SHOW_TEXT: {
1034 struct show_text_args *args = op_args;
1036 cairo_show_text(cr, args->text);
1037 break;
1039 case REL_MOVE_FOR: {
1040 cairo_text_extents_t e;
1041 double dx = 0.0, dy = 0.0;
1042 struct rel_move_for_args *args = op_args;
1044 cairo_text_extents(cr, args->text, &e);
1045 switch (args->ref) {
1046 case C: dx = -e.width / 2; dy = e.height / 2; break;
1047 case E: dx = -e.width; dy = e.height / 2; break;
1048 case N: dx = -e.width / 2; dy = e.height; break;
1049 case NE: dx = -e.width; dy = e.height; break;
1050 case NW: dy = e.height; break;
1051 case S: dx = -e.width / 2; break;
1052 case SE: dx = -e.width; break;
1053 case SW: break;
1054 case W: dy = e.height / 2; break;
1055 default: ABORT; break;
1057 cairo_rel_move_to(cr, dx, dy);
1058 break;
1060 case RESET_CTM:
1061 cairo_identity_matrix(cr);
1062 break;
1063 case STROKE:
1064 cairo_stroke(cr);
1065 break;
1066 case STROKE_PRESERVE:
1067 cairo_stroke_preserve(cr);
1068 break;
1069 case FILL:
1070 cairo_fill(cr);
1071 break;
1072 case FILL_PRESERVE:
1073 cairo_fill_preserve(cr);
1074 break;
1075 case SET_DASH: {
1076 struct set_dash_args *args = op_args;
1078 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
1079 break;
1081 case SET_FONT_FACE: {
1082 struct set_font_face_args *args = op_args;
1084 cairo_select_font_face(cr, args->family, args->slant, args->weight);
1085 break;
1087 case SET_FONT_SIZE: {
1088 struct set_font_size_args *args = op_args;
1090 cairo_set_font_size(cr, args->size);
1091 break;
1093 case SET_LINE_CAP: {
1094 struct set_line_cap_args *args = op_args;
1096 cairo_set_line_cap(cr, args->line_cap);
1097 break;
1099 case SET_LINE_JOIN: {
1100 struct set_line_join_args *args = op_args;
1102 cairo_set_line_join(cr, args->line_join);
1103 break;
1105 case SET_LINE_WIDTH: {
1106 struct set_line_width_args *args = op_args;
1108 cairo_set_line_width(cr, args->width);
1109 break;
1111 case SET_SOURCE_RGBA: {
1112 struct set_source_rgba_args *args = op_args;
1114 gdk_cairo_set_source_rgba(cr, &args->color);
1115 break;
1117 case TRANSFORM: {
1118 struct transform_args *args = op_args;
1120 cairo_transform(cr, &args->matrix);
1121 break;
1123 default:
1124 ABORT;
1125 break;
1130 * Callback that draws on a GtkDrawingArea
1132 static gboolean
1133 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
1135 struct draw_op *op;
1137 (void) data;
1138 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
1139 op != NULL;
1140 op = op->next)
1141 draw(cr, op->op, op->op_args);
1142 return FALSE;
1147 * ============================================================
1148 * Manipulating the GUI
1149 * ============================================================
1152 static void
1153 update_button(GObject *obj, const char *action,
1154 const char *data, const char *whole_msg, GType type)
1156 if (eql(action, "set_label"))
1157 gtk_button_set_label(GTK_BUTTON(obj), data);
1158 else
1159 ign_cmd(type, whole_msg);
1162 static void
1163 update_calendar(GObject *obj, const char *action,
1164 const char *data, const char *whole_msg, GType type)
1166 GtkCalendar *calendar = GTK_CALENDAR(obj);
1167 char dummy;
1168 int year = 0, month = 0, day = 0;
1170 if (eql(action, "select_date") &&
1171 sscanf(data, "%d-%d-%d %c", &year, &month, &day, &dummy) == 3) {
1172 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
1173 gtk_calendar_select_month(calendar, --month, year);
1174 gtk_calendar_select_day(calendar, day);
1175 } else
1176 ign_cmd(type, whole_msg);
1177 } else if (eql(action, "mark_day") &&
1178 sscanf(data, "%d %c", &day, &dummy) == 1) {
1179 if (day > 0 && day <= 31)
1180 gtk_calendar_mark_day(calendar, day);
1181 else
1182 ign_cmd(type, whole_msg);
1183 } else if (eql(action, "clear_marks") && sscanf(data, " %c", &dummy) < 1)
1184 gtk_calendar_clear_marks(calendar);
1185 else
1186 ign_cmd(type, whole_msg);
1190 * Common actions for various kinds of window. Return false if
1191 * command is ignored
1193 static bool
1194 update_class_window(GObject *obj, const char *action,
1195 const char *data, const char *whole_msg, GType type)
1197 GtkWindow *window = GTK_WINDOW(obj);
1198 bool result = true;
1199 char dummy;
1200 int x, y;
1202 if (eql(action, "set_title"))
1203 gtk_window_set_title(window, data);
1204 else if (eql(action, "fullscreen") && sscanf(data, " %c", &dummy) < 1)
1205 gtk_window_fullscreen(window);
1206 else if (eql(action, "unfullscreen") && sscanf(data, " %c", &dummy) < 1)
1207 gtk_window_unfullscreen(window);
1208 else if (eql(action, "resize")) {
1209 if (sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
1210 gtk_window_resize(window, x, y);
1211 else if (sscanf(data, " %c", &dummy) < 1) {
1212 gtk_window_get_default_size(window, &x, &y);
1213 gtk_window_resize(window, x, y);
1214 } else
1215 ign_cmd(type, whole_msg);
1216 } else if (eql(action, "move")) {
1217 if (sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
1218 gtk_window_move(window, x, y);
1219 else
1220 ign_cmd(type, whole_msg);
1221 } else
1222 result = false;
1223 return result;
1226 static void
1227 update_color_button(GObject *obj, const char *action,
1228 const char *data, const char *whole_msg, GType type)
1230 GdkRGBA color;
1232 if (eql(action, "set_color")) {
1233 gdk_rgba_parse(&color, data);
1234 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
1235 } else
1236 ign_cmd(type, whole_msg);
1239 static void
1240 update_combo_box_text(GObject *obj, const char *action,
1241 const char *data, const char *whole_msg, GType type)
1243 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(obj);
1244 char data1[strlen(data) + 1];
1245 char dummy;
1246 int val;
1248 strcpy(data1, data);
1249 if (eql(action, "prepend_text"))
1250 gtk_combo_box_text_prepend_text(combobox, data1);
1251 else if (eql(action, "append_text"))
1252 gtk_combo_box_text_append_text(combobox, data1);
1253 else if (eql(action, "remove") && sscanf(data, "%d %c", &val, &dummy) == 1)
1254 gtk_combo_box_text_remove(combobox, strtol(data1, NULL, 10));
1255 else if (eql(action, "insert_text")) {
1256 char *position = strtok(data1, WHITESPACE);
1257 char *text = strtok(NULL, WHITESPACE);
1258 gtk_combo_box_text_insert_text(combobox,
1259 strtol(position, NULL, 10), text);
1260 } else
1261 ign_cmd(type, whole_msg);
1265 * Maintaining a list of drawing operations. It is the responsibility
1266 * of cb_draw() to actually draw them. update_drawing_area() needs a
1267 * few helper functions.
1271 * Fill structure *op with the drawing operation according to action
1272 * and with the appropriate set of arguments
1274 static bool
1275 set_draw_op(struct draw_op *op, const char *action, const char *data)
1277 char dummy;
1279 if (eql(action, "line_to")) {
1280 struct move_to_args *args;
1282 if ((args = malloc(sizeof(*args))) == NULL)
1283 OOM_ABORT;
1284 op->op = LINE_TO;
1285 op->op_args = args;
1286 if (sscanf(data, "%u %lf %lf %c", &op->id, &args->x, &args->y, &dummy) != 3)
1287 return false;
1288 } else if (eql(action, "rel_line_to")) {
1289 struct move_to_args *args;
1291 if ((args = malloc(sizeof(*args))) == NULL)
1292 OOM_ABORT;
1293 op->op = REL_LINE_TO;
1294 op->op_args = args;
1295 if (sscanf(data, "%u %lf %lf %c", &op->id, &args->x, &args->y, &dummy) != 3)
1296 return false;
1297 } else if (eql(action, "move_to")) {
1298 struct move_to_args *args;
1300 if ((args = malloc(sizeof(*args))) == NULL)
1301 OOM_ABORT;
1302 op->op = MOVE_TO;
1303 op->op_args = args;
1304 if (sscanf(data, "%u %lf %lf %c", &op->id, &args->x, &args->y, &dummy) != 3)
1305 return false;
1306 } else if (eql(action, "rel_move_to")) {
1307 struct move_to_args *args;
1309 if ((args = malloc(sizeof(*args))) == NULL)
1310 OOM_ABORT;
1311 op->op = REL_MOVE_TO;
1312 op->op_args = args;
1313 if (sscanf(data, "%u %lf %lf %c", &op->id, &args->x, &args->y, &dummy) != 3)
1314 return false;
1315 } else if (eql(action, "arc")) {
1316 struct arc_args *args;
1317 double deg1, deg2;
1319 if ((args = malloc(sizeof(*args))) == NULL)
1320 OOM_ABORT;
1321 op->op = ARC;
1322 op->op_args = args;
1323 if (sscanf(data, "%u %lf %lf %lf %lf %lf %c", &op->id,
1324 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 6)
1325 return false;
1326 args->angle1 = deg1 * (M_PI / 180.L);
1327 args->angle2 = deg2 * (M_PI / 180.L);
1328 } else if (eql(action, "arc_negative")) {
1329 double deg1, deg2;
1330 struct arc_args *args;
1332 if ((args = malloc(sizeof(*args))) == NULL)
1333 OOM_ABORT;
1334 op->op = ARC_NEGATIVE;
1335 op->op_args = args;
1336 if (sscanf(data, "%u %lf %lf %lf %lf %lf %c", &op->id,
1337 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 6)
1338 return false;
1339 args->angle1 = deg1 * (M_PI / 180.L);
1340 args->angle2 = deg2 * (M_PI / 180.L);
1341 } else if (eql(action, "curve_to")) {
1342 struct curve_to_args *args;
1344 if ((args = malloc(sizeof(*args))) == NULL)
1345 OOM_ABORT;
1346 op->op = CURVE_TO;
1347 op->op_args = args;
1348 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf %c", &op->id,
1349 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 7)
1350 return false;
1351 } else if (eql(action, "rel_curve_to")) {
1352 struct curve_to_args *args;
1354 if ((args = malloc(sizeof(*args))) == NULL)
1355 OOM_ABORT;
1356 op->op = REL_CURVE_TO;
1357 op->op_args = args;
1358 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf %c", &op->id,
1359 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 7)
1360 return false;
1361 } else if (eql(action, "rectangle")) {
1362 struct rectangle_args *args;
1364 if ((args = malloc(sizeof(*args))) == NULL)
1365 OOM_ABORT;
1366 op->op = RECTANGLE;
1367 op->op_args = args;
1368 if (sscanf(data, "%u %lf %lf %lf %lf %c", &op->id,
1369 &args->x, &args->y, &args->width, &args->height, &dummy) != 5)
1370 return false;
1371 } else if (eql(action, "close_path")) {
1372 op->op = CLOSE_PATH;
1373 if (sscanf(data, "%u %c", &op->id, &dummy) != 1)
1374 return false;
1375 op->op_args = NULL;
1376 } else if (eql(action, "show_text")) {
1377 struct show_text_args *args;
1378 int start, len;
1380 if (sscanf(data, "%u %n", &op->id, &start) < 1)
1381 return false;
1382 len = strlen(data + start) + 1;
1383 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1384 OOM_ABORT;
1385 op->op = SHOW_TEXT;
1386 op->op_args = args;
1387 args->len = len; /* not used */
1388 strncpy(args->text, (data + start), len);
1389 } else if (eql(action, "rel_move_for")) {
1390 char ref_point[2 + 1];
1391 int start, len;
1392 struct rel_move_for_args *args;
1394 if (sscanf(data, "%u %2s %n", &op->id, ref_point, &start) < 2)
1395 return false;
1396 len = strlen(data + start) + 1;
1397 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1398 OOM_ABORT;
1399 if (eql(ref_point, "c"))
1400 args->ref = C;
1401 else if (eql(ref_point, "e"))
1402 args->ref = E;
1403 else if (eql(ref_point, "n"))
1404 args->ref = N;
1405 else if (eql(ref_point, "ne"))
1406 args->ref = NE;
1407 else if (eql(ref_point, "nw"))
1408 args->ref = NW;
1409 else if (eql(ref_point, "s"))
1410 args->ref = S;
1411 else if (eql(ref_point, "se"))
1412 args->ref = SE;
1413 else if (eql(ref_point, "sw"))
1414 args->ref = SW;
1415 else if (eql(ref_point, "w"))
1416 args->ref = W;
1417 else
1418 return false;
1419 op->op = REL_MOVE_FOR;
1420 op->op_args = args;
1421 args->len = len; /* not used */
1422 strncpy(args->text, (data + start), len);
1423 } else if (eql(action, "stroke")) {
1424 op->op = STROKE;
1425 if (sscanf(data, "%u %s", &op->id, &dummy) != 1)
1426 return false;
1427 op->op_args = NULL;
1428 } else if (eql(action, "stroke_preserve")) {
1429 op->op = STROKE_PRESERVE;
1430 if (sscanf(data, "%u %c", &op->id, &dummy) != 1)
1431 return false;
1432 op->op_args = NULL;
1433 } else if (eql(action, "fill")) {
1434 op->op = FILL;
1435 if (sscanf(data, "%u %c", &op->id, &dummy) != 1)
1436 return false;
1437 op->op_args = NULL;
1438 } else if (eql(action, "fill_preserve")) {
1439 op->op = FILL_PRESERVE;
1440 if (sscanf(data, "%u %c", &op->id, &dummy) != 1)
1441 return false;
1442 op->op_args = NULL;
1443 } else if (eql(action, "set_dash")) {
1444 char *next, *end;
1445 char data1[strlen(data) + 1];
1446 int d_start, n, i;
1447 struct set_dash_args *args;
1449 strcpy(data1, data);
1450 if (sscanf(data1, "%u %n", &op->id, &d_start) < 1)
1451 return false;
1452 next = end = data1 + d_start;
1453 n = -1;
1454 do {
1455 n++;
1456 next = end;
1457 strtod(next, &end);
1458 } while (next != end);
1459 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
1460 OOM_ABORT;
1461 op->op = SET_DASH;
1462 op->op_args = args;
1463 args->num_dashes = n;
1464 for (i = 0, next = data1 + d_start; i < n; i++, next = end) {
1465 args->dashes[i] = strtod(next, &end);
1467 } else if (eql(action, "set_font_face")) {
1468 char slant[7 + 1];
1469 char weight[6 + 1];
1470 int family_start, family_len;
1471 struct set_font_face_args *args;
1473 if (sscanf(data, "%u %s %s %n%*s", &op->id, slant, weight, &family_start) != 3)
1474 return false;
1475 family_len = strlen(data + family_start) + 1;
1476 if ((args = malloc(sizeof(*args) + family_len * sizeof(args->family[0]))) == NULL)
1477 OOM_ABORT;
1478 op->op = SET_FONT_FACE;
1479 op->op_args = args;
1480 strncpy(args->family, data + family_start, family_len);
1481 if (eql(slant, "normal"))
1482 args->slant = CAIRO_FONT_SLANT_NORMAL;
1483 else if (eql(slant, "italic"))
1484 args->slant = CAIRO_FONT_SLANT_ITALIC;
1485 else if (eql(slant, "oblique"))
1486 args->slant = CAIRO_FONT_SLANT_OBLIQUE;
1487 else
1488 return false;
1489 if (eql(weight, "normal"))
1490 args->weight = CAIRO_FONT_WEIGHT_NORMAL;
1491 else if (eql(weight, "bold"))
1492 args->weight = CAIRO_FONT_WEIGHT_BOLD;
1493 else
1494 return false;
1495 } else if (eql(action, "set_font_size")) {
1496 struct set_font_size_args *args;
1498 if ((args = malloc(sizeof(*args))) == NULL)
1499 OOM_ABORT;
1500 op->op = SET_FONT_SIZE;
1501 op->op_args = args;
1502 if (sscanf(data, "%u %lf %c", &op->id, &args->size, &dummy) != 2)
1503 return false;
1504 } else if (eql(action, "set_line_cap")) {
1505 char str[6 + 1];
1506 struct set_line_cap_args *args;
1508 if ((args = malloc(sizeof(*args))) == NULL)
1509 OOM_ABORT;
1510 op->op = SET_LINE_CAP;
1511 op->op_args = args;
1512 if (sscanf(data, "%u %6s %c", &op->id, str, &dummy) != 2)
1513 return false;
1514 if (eql(str, "butt"))
1515 args->line_cap = CAIRO_LINE_CAP_BUTT;
1516 else if (eql(str, "round"))
1517 args->line_cap = CAIRO_LINE_CAP_ROUND;
1518 else if (eql(str, "square"))
1519 args->line_cap = CAIRO_LINE_CAP_SQUARE;
1520 else
1521 return false;
1522 } else if (eql(action, "set_line_join")) {
1523 char str[5 + 1];
1524 struct set_line_join_args *args;
1526 if ((args = malloc(sizeof(*args))) == NULL)
1527 OOM_ABORT;
1528 op->op = SET_LINE_JOIN;
1529 op->op_args = args;
1530 if (sscanf(data, "%u %5s %c", &op->id, str, &dummy) != 2)
1531 return false;
1532 if (eql(str, "miter"))
1533 args->line_join = CAIRO_LINE_JOIN_MITER;
1534 else if (eql(str, "round"))
1535 args->line_join = CAIRO_LINE_JOIN_ROUND;
1536 else if (eql(str, "bevel"))
1537 args->line_join = CAIRO_LINE_JOIN_BEVEL;
1538 else
1539 return false;
1540 } else if (eql(action, "set_line_width")) {
1541 struct set_line_width_args *args;
1543 if ((args = malloc(sizeof(*args))) == NULL)
1544 OOM_ABORT;
1545 op->op = SET_LINE_WIDTH;
1546 op->op_args = args;
1547 if (sscanf(data, "%u %lf %c", &op->id, &args->width, &dummy) != 2)
1548 return false;
1549 } else if (eql(action, "set_source_rgba")) {
1550 int c_start;
1551 struct set_source_rgba_args *args;
1553 if ((args = malloc(sizeof(*args))) == NULL)
1554 OOM_ABORT;
1555 op->op = SET_SOURCE_RGBA;
1556 op->op_args = args;
1557 if ((sscanf(data, "%u %n", &op->id, &c_start) < 1))
1558 return false;;
1559 gdk_rgba_parse(&args->color, data + c_start);
1560 } else if (eql(action, "transform")) {
1561 char dummy;
1562 double xx, yx, xy, yy, x0, y0;
1564 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf %c",
1565 &op->id, &xx, &yx, &xy, &yy, &x0, &y0, &dummy) == 7) {
1566 struct transform_args *args;
1568 if ((args = malloc(sizeof(*args))) == NULL)
1569 OOM_ABORT;
1570 op->op_args = args;
1571 op->op = TRANSFORM;
1572 cairo_matrix_init(&args->matrix, xx, yx, xy, yy, x0, y0);
1573 } else if (sscanf(data, "%u, %c", &op->id, &dummy))
1574 return false;
1575 else if (sscanf(data, "%u %c", &op->id, &dummy) == 1) {
1576 op->op = RESET_CTM;
1577 op->op_args = NULL;
1578 } else
1579 return false;
1580 } else if (eql(action, "translate")) {
1581 double tx, ty;
1582 struct transform_args *args;
1584 if ((args = malloc(sizeof(*args))) == NULL)
1585 OOM_ABORT;
1586 op->op = TRANSFORM;
1587 op->op_args = args;
1588 if (sscanf(data, "%u %lf %lf %c", &op->id, &tx, &ty, &dummy) != 3)
1589 return false;
1590 cairo_matrix_init_translate(&args->matrix, tx, ty);
1591 } else if (eql(action, "scale")) {
1592 double sx, sy;
1593 struct transform_args *args;
1595 if ((args = malloc(sizeof(*args))) == NULL)
1596 OOM_ABORT;
1597 op->op = TRANSFORM;
1598 op->op_args = args;
1599 if (sscanf(data, "%u %lf %lf %c", &op->id, &sx, &sy, &dummy) != 3)
1600 return false;
1601 cairo_matrix_init_scale(&args->matrix, sx, sy);
1602 } else if (eql(action, "rotate")) {
1603 double angle;
1604 struct transform_args *args;
1606 if ((args = malloc(sizeof(*args))) == NULL)
1607 OOM_ABORT;
1608 op->op = TRANSFORM;
1609 op->op_args = args;
1610 if (sscanf(data, "%u %lf %c", &op->id, &angle, &dummy) != 2)
1611 return false;
1612 cairo_matrix_init_rotate(&args->matrix, angle * (M_PI / 180.L));
1613 } else
1614 return false;
1615 return true;
1619 * Append another element to widget's "draw_ops" list
1621 static bool
1622 ins_draw_op(GObject *widget, const char *action, const char *data)
1624 struct draw_op *op, *draw_ops, *last_op;
1626 if ((op = malloc(sizeof(*op))) == NULL)
1627 OOM_ABORT;
1628 op->op_args = NULL;
1629 op->next = NULL;
1630 if (!set_draw_op(op, action, data)) {
1631 free(op->op_args);
1632 free(op);
1633 return false;
1635 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL)
1636 g_object_set_data(widget, "draw_ops", op);
1637 else {
1638 for (last_op = draw_ops;
1639 last_op->next != NULL;
1640 last_op = last_op->next);
1641 last_op->next = op;
1643 return true;
1647 * Remove all elements with the given id from widget's "draw_ops" list
1649 static bool
1650 rem_draw_op(GObject *widget, const char *data)
1652 char dummy;
1653 struct draw_op *op, *next_op, *prev_op = NULL;
1654 unsigned int id;
1656 if (sscanf(data, "%u %c", &id, &dummy) != 1)
1657 return false;
1658 op = g_object_get_data(widget, "draw_ops");
1659 while (op != NULL) {
1660 next_op = op->next;
1661 if (op->id == id) {
1662 if (prev_op == NULL) /* list head */
1663 g_object_set_data(widget, "draw_ops", op->next);
1664 else
1665 prev_op->next = op->next;
1666 free(op->op_args);
1667 free(op);
1668 } else
1669 prev_op = op;
1670 op = next_op;
1672 return true;
1675 static gboolean
1676 refresh_widget(GtkWidget *widget)
1678 gint height = gtk_widget_get_allocated_height(widget);
1679 gint width = gtk_widget_get_allocated_width(widget);
1681 gtk_widget_queue_draw_area(widget, 0, 0, width, height);
1682 return G_SOURCE_REMOVE;
1685 static void
1686 update_drawing_area(GObject *obj, const char *action,
1687 const char *data, const char *whole_msg, GType type)
1689 if (eql(action, "remove")) {
1690 if (!rem_draw_op(obj, data))
1691 ign_cmd(type, whole_msg);
1692 } else if (ins_draw_op(obj, action, data));
1693 else
1694 ign_cmd(type, whole_msg);
1695 if (eql(action, "stroke") ||
1696 eql(action, "stroke_preserve") ||
1697 eql(action, "fill") ||
1698 eql(action, "fill_preserve") ||
1699 eql(action, "show_text") ||
1700 eql(action, "remove"))
1701 gdk_threads_add_idle_full(G_PRIORITY_LOW,
1702 (GSourceFunc) refresh_widget,
1703 GTK_WIDGET(obj), NULL);
1706 static void
1707 update_entry(GObject *obj, const char *action,
1708 const char *data, const char *whole_msg, GType type)
1710 GtkEntry *entry = GTK_ENTRY(obj);
1712 if (eql(action, "set_text"))
1713 gtk_entry_set_text(entry, data);
1714 else if (eql(action, "set_placeholder_text"))
1715 gtk_entry_set_placeholder_text(entry, data);
1716 else
1717 ign_cmd(type, whole_msg);
1720 static void
1721 update_expander(GObject *obj, const char *action,
1722 const char *data, const char *whole_msg, GType type)
1724 GtkExpander *expander = GTK_EXPANDER(obj);
1725 char dummy;
1726 unsigned int val;
1728 if (eql(action, "set_expanded") &&
1729 sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
1730 gtk_expander_set_expanded(expander, val);
1731 else if (eql(action, "set_label"))
1732 gtk_expander_set_label(expander, data);
1733 else
1734 ign_cmd(type, whole_msg);
1737 static void
1738 update_file_chooser_button(GObject *obj, const char *action,
1739 const char *data, const char *whole_msg, GType type)
1741 if (eql(action, "set_filename"))
1742 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
1743 else
1744 ign_cmd(type, whole_msg);
1747 static void
1748 update_file_chooser_dialog(GObject *obj, const char *action,
1749 const char *data, const char *whole_msg, GType type)
1751 GtkFileChooser *chooser = GTK_FILE_CHOOSER(obj);
1753 if (eql(action, "set_filename"))
1754 gtk_file_chooser_set_filename(chooser, data);
1755 else if (eql(action, "set_current_name"))
1756 gtk_file_chooser_set_current_name(chooser, data);
1757 else if (update_class_window(obj, action, data, whole_msg, type));
1758 else
1759 ign_cmd(type, whole_msg);
1762 static void
1763 update_focus(GObject *obj, const char *action,
1764 const char *data, const char *whole_msg, GType type)
1766 char dummy;
1768 (void) action;
1769 (void) data;
1770 if (sscanf(data, " %c", &dummy) < 1 &&
1771 gtk_widget_get_can_focus(GTK_WIDGET(obj)))
1772 gtk_widget_grab_focus(GTK_WIDGET(obj));
1773 else
1774 ign_cmd(type, whole_msg);
1777 static void
1778 update_font_button(GObject *obj, const char *action,
1779 const char *data, const char *whole_msg, GType type)
1781 GtkFontButton *font_button = GTK_FONT_BUTTON(obj);
1783 if (eql(action, "set_font_name"))
1784 gtk_font_button_set_font_name(font_button, data);
1785 else
1786 ign_cmd(type, whole_msg);
1789 static void
1790 update_frame(GObject *obj, const char *action,
1791 const char *data, const char *whole_msg, GType type)
1793 if (eql(action, "set_label"))
1794 gtk_frame_set_label(GTK_FRAME(obj), data);
1795 else
1796 ign_cmd(type, whole_msg);
1799 static void
1800 update_image(GObject *obj, const char *action,
1801 const char *data, const char *whole_msg, GType type)
1803 GtkIconSize size;
1804 GtkImage *image = GTK_IMAGE(obj);
1806 gtk_image_get_icon_name(image, NULL, &size);
1807 if (eql(action, "set_from_file"))
1808 gtk_image_set_from_file(image, data);
1809 else if (eql(action, "set_from_icon_name"))
1810 gtk_image_set_from_icon_name(image, data, size);
1811 else
1812 ign_cmd(type, whole_msg);
1815 static void
1816 update_label(GObject *obj, const char *action,
1817 const char *data, const char *whole_msg, GType type)
1819 if (eql(action, "set_text"))
1820 gtk_label_set_text(GTK_LABEL(obj), data);
1821 else
1822 ign_cmd(type, whole_msg);
1825 static void
1826 update_notebook(GObject *obj, const char *action,
1827 const char *data, const char *whole_msg, GType type)
1829 char dummy;
1830 unsigned int val;
1832 if (eql(action, "set_current_page") && sscanf(data, "%u %c", &val, &dummy) == 1)
1833 gtk_notebook_set_current_page(GTK_NOTEBOOK(obj), val);
1834 else
1835 ign_cmd(type, whole_msg);
1838 static void
1839 update_nothing(GObject *obj, const char *action,
1840 const char *data, const char *whole_msg, GType type)
1842 (void) obj;
1843 (void) action;
1844 (void) data;
1845 (void) whole_msg;
1846 (void) type;
1849 static void
1850 update_print_dialog(GObject *obj, const char *action,
1851 const char *data, const char *whole_msg, GType type)
1853 GtkPageSetup *page_setup;
1854 GtkPrintJob *job;
1855 GtkPrintSettings *settings;
1856 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(obj);
1857 GtkPrinter *printer;
1858 gint response_id;
1860 if (eql(action, "print")) {
1861 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
1862 switch (response_id) {
1863 case GTK_RESPONSE_OK:
1864 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
1865 settings = gtk_print_unix_dialog_get_settings(dialog);
1866 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
1867 job = gtk_print_job_new(data, printer, settings, page_setup);
1868 if (gtk_print_job_set_source_file(job, data, NULL))
1869 gtk_print_job_send(job, NULL, NULL, NULL);
1870 else
1871 ign_cmd(type, whole_msg);
1872 g_clear_object(&settings);
1873 g_clear_object(&job);
1874 break;
1875 case GTK_RESPONSE_CANCEL:
1876 case GTK_RESPONSE_DELETE_EVENT:
1877 break;
1878 default:
1879 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
1880 widget_name(GTK_BUILDABLE(dialog)), response_id);
1881 break;
1883 gtk_widget_hide(GTK_WIDGET(dialog));
1884 } else
1885 ign_cmd(type, whole_msg);
1888 static void
1889 update_progress_bar(GObject *obj, const char *action,
1890 const char *data, const char *whole_msg, GType type)
1892 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(obj);
1893 char dummy;
1894 double frac;
1896 if (eql(action, "set_text"))
1897 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
1898 else if (eql(action, "set_fraction") &&
1899 (sscanf(data, "%lf %c", &frac, &dummy) == 1))
1900 gtk_progress_bar_set_fraction(progressbar, frac);
1901 else
1902 ign_cmd(type, whole_msg);
1905 static void
1906 update_scale(GObject *obj, const char *action,
1907 const char *data, const char *whole_msg, GType type)
1909 char dummy;
1910 double val;
1912 if (eql(action, "set_value") && (sscanf(data, "%lf %c", &val, &dummy) == 1))
1913 gtk_range_set_value(GTK_RANGE(obj), val);
1914 else
1915 ign_cmd(type, whole_msg);
1918 static void
1919 update_scrolled_window(GObject *obj, const char *action,
1920 const char *data, const char *whole_msg, GType type)
1922 GtkScrolledWindow *window = GTK_SCROLLED_WINDOW(obj);
1923 GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(window);
1924 GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(window);
1925 char dummy;
1926 double d0, d1;
1928 if (eql(action, "hscroll") && sscanf(data, "%lf %c", &d0, &dummy) == 1)
1929 gtk_adjustment_set_value(hadj, d0);
1930 else if (eql(action, "vscroll") && sscanf(data, "%lf %c", &d0, &dummy) == 1)
1931 gtk_adjustment_set_value(vadj, d0);
1932 else if (eql(action, "hscroll_to_range") &&
1933 sscanf(data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
1934 gtk_adjustment_clamp_page(hadj, d0, d1);
1935 else if (eql(action, "vscroll_to_range") &&
1936 sscanf(data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
1937 gtk_adjustment_clamp_page(vadj, d0, d1);
1938 else
1939 ign_cmd(type, whole_msg);
1942 static void
1943 update_sensitivity(GObject *obj, const char *action,
1944 const char *data, const char *whole_msg, GType type)
1946 char dummy;
1947 unsigned int val;
1949 (void) action;
1950 (void) whole_msg;
1951 (void) type;
1952 if (sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
1953 gtk_widget_set_sensitive(GTK_WIDGET(obj), val);
1954 else
1955 ign_cmd(type, whole_msg);
1958 static void
1959 update_size_request(GObject *obj, const char *action,
1960 const char *data, const char *whole_msg, GType type)
1962 char dummy;
1963 int x, y;
1965 (void) action;
1966 (void) whole_msg;
1967 (void) type;
1968 if (sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
1969 gtk_widget_set_size_request(GTK_WIDGET(obj), x, y);
1970 else if (sscanf(data, " %c", &dummy) < 1)
1971 gtk_widget_set_size_request(GTK_WIDGET(obj), -1, -1);
1972 else
1973 ign_cmd(type, whole_msg);
1976 static void
1977 update_socket(GObject *obj, const char *action,
1978 const char *data, const char *whole_msg, GType type)
1980 GtkSocket *socket = GTK_SOCKET(obj);
1981 Window id;
1982 char str[BUFLEN], dummy;
1984 (void) data;
1985 if (eql(action, "id") && sscanf(data, " %c", &dummy) < 1) {
1986 id = gtk_socket_get_id(socket);
1987 snprintf(str, BUFLEN, "%lu", id);
1988 send_msg(GTK_BUILDABLE(socket), "id", str, NULL);
1989 } else
1990 ign_cmd(type, whole_msg);
1993 static void
1994 update_spinner(GObject *obj, const char *action,
1995 const char *data, const char *whole_msg, GType type)
1997 GtkSpinner *spinner = GTK_SPINNER(obj);
1998 char dummy;
2000 (void) data;
2001 if (eql(action, "start") && sscanf(data, " %c", &dummy) < 1)
2002 gtk_spinner_start(spinner);
2003 else if (eql(action, "stop") && sscanf(data, " %c", &dummy) < 1)
2004 gtk_spinner_stop(spinner);
2005 else
2006 ign_cmd(type, whole_msg);
2009 static void
2010 update_statusbar(GObject *obj, const char *action,
2011 const char *data, const char *whole_msg, GType type)
2013 GtkStatusbar *statusbar = GTK_STATUSBAR(obj);
2014 char *ctx_msg, dummy;
2015 const char *msg;
2016 int ctx_len, t;
2018 if ((ctx_msg = malloc(strlen(data) + 1)) == NULL)
2019 OOM_ABORT;
2020 t = sscanf(data, "%s %n%c", ctx_msg, &ctx_len, &dummy);
2021 msg = data + ctx_len;
2022 if (eql(action, "push"))
2023 gtk_statusbar_push(statusbar,
2024 gtk_statusbar_get_context_id(statusbar, "0"),
2025 data);
2026 else if (eql(action, "push_id") && t >= 1)
2027 gtk_statusbar_push(statusbar,
2028 gtk_statusbar_get_context_id(statusbar, ctx_msg),
2029 msg);
2030 else if (eql(action, "pop") && t < 1)
2031 gtk_statusbar_pop(statusbar,
2032 gtk_statusbar_get_context_id(statusbar, "0"));
2033 else if (eql(action, "pop_id") && t == 1)
2034 gtk_statusbar_pop(statusbar,
2035 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2036 else if (eql(action, "remove_all") && t < 1)
2037 gtk_statusbar_remove_all(statusbar,
2038 gtk_statusbar_get_context_id(statusbar, "0"));
2039 else if (eql(action, "remove_all_id") && t == 1)
2040 gtk_statusbar_remove_all(statusbar,
2041 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2042 else
2043 ign_cmd(type, whole_msg);
2044 free(ctx_msg);
2047 static void
2048 update_switch(GObject *obj, const char *action,
2049 const char *data, const char *whole_msg, GType type)
2051 char dummy;
2052 unsigned int val;
2054 if (eql(action, "set_active") &&
2055 sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2056 gtk_switch_set_active(GTK_SWITCH(obj), val);
2057 else
2058 ign_cmd(type, whole_msg);
2061 static void
2062 update_text_view(GObject *obj, const char *action,
2063 const char *data, const char *whole_msg, GType type)
2065 GtkTextView *view = GTK_TEXT_VIEW(obj);
2066 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
2067 GtkTextIter a, b;
2068 char dummy;
2069 int val;
2071 if (eql(action, "set_text"))
2072 gtk_text_buffer_set_text(textbuf, data, -1);
2073 else if (eql(action, "delete") && sscanf(data, " %c", &dummy) < 1) {
2074 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2075 gtk_text_buffer_delete(textbuf, &a, &b);
2076 } else if (eql(action, "insert_at_cursor"))
2077 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
2078 else if (eql(action, "place_cursor") && eql(data, "end")) {
2079 gtk_text_buffer_get_end_iter(textbuf, &a);
2080 gtk_text_buffer_place_cursor(textbuf, &a);
2081 } else if (eql(action, "place_cursor") &&
2082 sscanf(data, "%d %c", &val, &dummy) == 1) {
2083 gtk_text_buffer_get_iter_at_offset(textbuf, &a, val);
2084 gtk_text_buffer_place_cursor(textbuf, &a);
2085 } else if (eql(action, "place_cursor_at_line") &&
2086 sscanf(data, "%d %c", &val, &dummy) == 1) {
2087 gtk_text_buffer_get_iter_at_line(textbuf, &a, val);
2088 gtk_text_buffer_place_cursor(textbuf, &a);
2089 } else if (eql(action, "scroll_to_cursor") &&
2090 sscanf(data, " %c", &dummy) < 1)
2091 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
2092 0., 0, 0., 0.);
2093 else if (eql(action, "save") && data != NULL &&
2094 (save = fopen(data, "w")) != NULL) {
2095 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2096 save_msg(GTK_BUILDABLE(view), "insert_at_cursor",
2097 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
2098 fclose(save);
2099 } else
2100 ign_cmd(type, whole_msg);
2103 static void
2104 update_toggle_button(GObject *obj, const char *action,
2105 const char *data, const char *whole_msg, GType type)
2107 char dummy;
2108 unsigned int val;
2110 if (eql(action, "set_label"))
2111 gtk_button_set_label(GTK_BUTTON(obj), data);
2112 else if (eql(action, "set_active") &&
2113 sscanf(data, "%u %c", &val, &dummy) == 1 &&
2114 val < 2)
2115 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), val);
2116 else
2117 ign_cmd(type, whole_msg);
2120 static void
2121 update_tooltip_text(GObject *obj, const char *action,
2122 const char *data, const char *whole_msg, GType type)
2124 (void) action;
2125 (void) whole_msg;
2126 (void) type;
2127 gtk_widget_set_tooltip_text(GTK_WIDGET(obj), data);
2131 * update_tree_view() needs a few helper functions
2135 * Check if s is a valid string representation of a GtkTreePath
2137 static bool
2138 is_path_string(char *s)
2140 return s != NULL &&
2141 strlen(s) == strspn(s, ":0123456789") &&
2142 strstr(s, "::") == NULL &&
2143 strcspn(s, ":") > 0;
2146 static void
2147 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
2148 GtkTreeIter *parent, GtkTreeIter *sibling)
2150 if (GTK_IS_TREE_STORE(model))
2151 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
2152 iter, parent, sibling);
2153 else if (GTK_IS_LIST_STORE(model))
2154 gtk_list_store_insert_before(GTK_LIST_STORE(model),
2155 iter, sibling);
2156 else
2157 ABORT;
2160 static void
2161 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
2162 GtkTreeIter *parent, GtkTreeIter *sibling)
2164 if (GTK_IS_TREE_STORE(model))
2165 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
2166 iter, parent, sibling);
2167 else if (GTK_IS_LIST_STORE(model))
2168 gtk_list_store_insert_after(GTK_LIST_STORE(model),
2169 iter, sibling);
2170 else
2171 ABORT;
2174 static void
2175 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
2176 GtkTreeIter *position)
2178 if (GTK_IS_TREE_STORE(model))
2179 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
2180 else if (GTK_IS_LIST_STORE(model))
2181 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
2182 else
2183 ABORT;
2186 static void
2187 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
2189 if (GTK_IS_TREE_STORE(model))
2190 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
2191 else if (GTK_IS_LIST_STORE(model))
2192 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
2193 else
2194 ABORT;
2197 static void
2198 tree_model_clear(GtkTreeModel *model)
2200 if (GTK_IS_TREE_STORE(model))
2201 gtk_tree_store_clear(GTK_TREE_STORE(model));
2202 else if (GTK_IS_LIST_STORE(model))
2203 gtk_list_store_clear(GTK_LIST_STORE(model));
2204 else
2205 ABORT;
2208 static void
2209 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
2211 va_list ap;
2213 va_start(ap, iter);
2214 if (GTK_IS_TREE_STORE(model))
2215 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
2216 else if (GTK_IS_LIST_STORE(model))
2217 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
2218 else
2219 ABORT;
2220 va_end(ap);
2224 * Create an empty row at path if it doesn't yet exist. Create older
2225 * siblings and parents as necessary.
2227 static void
2228 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
2230 GtkTreeIter iter_1; /* iter's predecessor */
2231 GtkTreePath *path_1; /* path's predecessor */
2233 if (gtk_tree_model_get_iter(model, iter, path))
2234 return;
2235 path_1 = gtk_tree_path_copy(path);
2236 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
2237 create_subtree(model, path_1, iter);
2238 iter_1 = *iter;
2239 tree_model_insert_after(model, iter, NULL, &iter_1);
2240 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
2241 create_subtree(model, path_1, iter);
2242 if (gtk_tree_path_get_depth(path_1) == 0)
2243 /* first toplevel row */
2244 tree_model_insert_after(model, iter, NULL, NULL);
2245 else { /* first row in a lower level */
2246 iter_1 = *iter;
2247 tree_model_insert_after(model, iter, &iter_1, NULL);
2249 } /* neither prev nor up mean we're at the root of an empty tree */
2250 gtk_tree_path_free(path_1);
2253 static bool
2254 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
2255 const char *path_s, int col, const char *new_text)
2257 GType col_type = gtk_tree_model_get_column_type(model, col);
2258 GtkTreePath *path;
2259 bool ok = false;
2260 char *endptr;
2261 double d;
2262 long long int n;
2264 path = gtk_tree_path_new_from_string(path_s);
2265 create_subtree(model, path, iter);
2266 gtk_tree_path_free(path);
2267 switch (col_type) {
2268 case G_TYPE_BOOLEAN:
2269 case G_TYPE_INT:
2270 case G_TYPE_LONG:
2271 case G_TYPE_INT64:
2272 case G_TYPE_UINT:
2273 case G_TYPE_ULONG:
2274 case G_TYPE_UINT64:
2275 errno = 0;
2276 endptr = NULL;
2277 n = strtoll(new_text, &endptr, 10);
2278 if (!errno && endptr != new_text) {
2279 tree_model_set(model, iter, col, n, -1);
2280 ok = true;
2282 break;
2283 case G_TYPE_FLOAT:
2284 case G_TYPE_DOUBLE:
2285 errno = 0;
2286 endptr = NULL;
2287 d = strtod(new_text, &endptr);
2288 if (!errno && endptr != new_text) {
2289 tree_model_set(model, iter, col, d, -1);
2290 ok = true;
2292 break;
2293 case G_TYPE_STRING:
2294 tree_model_set(model, iter, col, new_text, -1);
2295 ok = true;
2296 break;
2297 default:
2298 fprintf(stderr, "column %d: %s not implemented\n",
2299 col, g_type_name(col_type));
2300 ok = true;
2301 break;
2303 return ok;
2306 static void
2307 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
2309 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
2310 /* is just fine and this function need not exist. */
2311 if (path == NULL)
2312 path = gtk_tree_path_new();
2313 gtk_tree_view_set_cursor(view, path, col, false);
2316 static void
2317 update_tree_view(GObject *obj, const char *action,
2318 const char *data, const char *whole_msg, GType type)
2320 GtkTreeView *view = GTK_TREE_VIEW(obj);
2321 GtkTreeIter iter0, iter1;
2322 GtkTreeModel *model = gtk_tree_view_get_model(view);
2323 GtkTreePath *path = NULL;
2324 bool iter0_valid, iter1_valid;
2325 char *tokens, *arg0, *arg1, *arg2;
2326 int col = -1; /* invalid column number */
2328 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
2330 fprintf(stderr, "missing model/");
2331 ign_cmd(type, whole_msg);
2332 return;
2334 if ((tokens = malloc(strlen(data) + 1)) == NULL)
2335 OOM_ABORT;
2336 strcpy(tokens, data);
2337 arg0 = strtok(tokens, WHITESPACE);
2338 arg1 = strtok(NULL, WHITESPACE);
2339 arg2 = strtok(NULL, "");
2340 iter0_valid = is_path_string(arg0) &&
2341 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
2342 iter1_valid = is_path_string(arg1) &&
2343 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
2344 if (is_path_string(arg1))
2345 col = strtol(arg1, NULL, 10);
2346 if (eql(action, "set") &&
2347 col > -1 &&
2348 col < gtk_tree_model_get_n_columns(model) &&
2349 is_path_string(arg0)) {
2350 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
2351 ign_cmd(type, whole_msg);
2352 } else if (eql(action, "scroll") && iter0_valid && iter1_valid &&
2353 arg2 == NULL) {
2354 path = gtk_tree_path_new_from_string(arg0);
2355 gtk_tree_view_scroll_to_cell (view,
2356 path,
2357 gtk_tree_view_get_column(view, col),
2358 0, 0., 0.);
2359 } else if (eql(action, "expand") && iter0_valid && arg1 == NULL) {
2360 path = gtk_tree_path_new_from_string(arg0);
2361 gtk_tree_view_expand_row(view, path, false);
2362 } else if (eql(action, "expand_all") && iter0_valid && arg1 == NULL) {
2363 path = gtk_tree_path_new_from_string(arg0);
2364 gtk_tree_view_expand_row(view, path, true);
2365 } else if (eql(action, "expand_all") && arg0 == NULL)
2366 gtk_tree_view_expand_all(view);
2367 else if (eql(action, "collapse") && iter0_valid && arg1 == NULL) {
2368 path = gtk_tree_path_new_from_string(arg0);
2369 gtk_tree_view_collapse_row(view, path);
2370 } else if (eql(action, "collapse") && arg0 == NULL)
2371 gtk_tree_view_collapse_all(view);
2372 else if (eql(action, "set_cursor") && iter0_valid && arg1 == NULL) {
2373 path = gtk_tree_path_new_from_string(arg0);
2374 tree_view_set_cursor(view, path, NULL);
2375 } else if (eql(action, "set_cursor") && arg0 == NULL) {
2376 tree_view_set_cursor(view, NULL, NULL);
2377 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2378 } else if (eql(action, "insert_row") &&
2379 eql(arg0, "end") && arg1 == NULL)
2380 tree_model_insert_before(model, &iter1, NULL, NULL);
2381 else if (eql(action, "insert_row") && iter0_valid &&
2382 eql(arg1, "as_child") && arg2 == NULL)
2383 tree_model_insert_after(model, &iter1, &iter0, NULL);
2384 else if (eql(action, "insert_row") && iter0_valid && arg1 == NULL)
2385 tree_model_insert_before(model, &iter1, NULL, &iter0);
2386 else if (eql(action, "move_row") && iter0_valid &&
2387 eql(arg1, "end") && arg2 == NULL)
2388 tree_model_move_before(model, &iter0, NULL);
2389 else if (eql(action, "move_row") && iter0_valid && iter1_valid && arg2 == NULL)
2390 tree_model_move_before(model, &iter0, &iter1);
2391 else if (eql(action, "remove_row") && iter0_valid && arg1 == NULL)
2392 tree_model_remove(model, &iter0);
2393 else if (eql(action, "clear") && arg0 == NULL) {
2394 tree_view_set_cursor(view, NULL, NULL);
2395 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2396 tree_model_clear(model);
2397 } else if (eql(action, "save") && arg0 != NULL &&
2398 (save = fopen(arg0, "w")) != NULL) {
2399 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc) save_tree_row_msg, view);
2400 fclose(save);
2401 } else
2402 ign_cmd(type, whole_msg);
2403 free(tokens);
2404 gtk_tree_path_free(path);
2407 static void
2408 update_visibility(GObject *obj, const char *action,
2409 const char *data, const char *whole_msg, GType type)
2411 char dummy;
2412 unsigned int val;
2414 (void) action;
2415 (void) whole_msg;
2416 (void) type;
2417 if (sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2418 gtk_widget_set_visible(GTK_WIDGET(obj), val);
2419 else
2420 ign_cmd(type, whole_msg);
2424 * Change the style of the widget passed
2426 static void
2427 update_widget_style(GObject *obj, const char *name,
2428 const char *data, const char *whole_msg, GType type)
2430 GtkStyleContext *context;
2431 GtkStyleProvider *style_provider;
2432 char *style_decl;
2433 const char *prefix = "* {", *suffix = "}";
2434 size_t sz;
2436 (void) name;
2437 (void) whole_msg;
2438 (void) type;
2439 style_provider = g_object_get_data(obj, "style_provider");
2440 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
2441 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2442 gtk_style_context_remove_provider(context, style_provider);
2443 if ((style_decl = malloc(sz)) == NULL)
2444 OOM_ABORT;
2445 strcpy(style_decl, prefix);
2446 strcat(style_decl, data);
2447 strcat(style_decl, suffix);
2448 gtk_style_context_add_provider(context, style_provider,
2449 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2450 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
2451 style_decl, -1, NULL);
2452 free(style_decl);
2455 static void
2456 update_window(GObject *obj, const char *action,
2457 const char *data, const char *whole_msg, GType type)
2459 if (!update_class_window(obj, action, data, whole_msg, type))
2460 ign_cmd(type, whole_msg);
2464 * Simulate user activity on various widgets
2466 static void
2467 fake_ui_activity(GObject *obj, const char *action,
2468 const char *data, const char *whole_msg, GType type)
2470 char dummy;
2472 (void) action;
2473 (void) data;
2474 if (!GTK_IS_WIDGET(obj) || sscanf(data, " %c", &dummy) > 0)
2475 ign_cmd(type, whole_msg);
2476 else if (GTK_IS_ENTRY(obj) || GTK_IS_SPIN_BUTTON(obj))
2477 cb_editable(GTK_BUILDABLE(obj), "text");
2478 else if (GTK_IS_SCALE(obj))
2479 cb_range(GTK_BUILDABLE(obj), "value");
2480 else if (GTK_IS_CALENDAR(obj))
2481 cb_calendar(GTK_BUILDABLE(obj), "clicked");
2482 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
2483 cb_file_chooser_button(GTK_BUILDABLE(obj), "file");
2484 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
2485 ign_cmd(type, whole_msg);
2489 * The final UI update
2491 static void
2492 main_quit(GObject *obj, const char *action,
2493 const char *data, const char *whole_msg, GType type)
2495 char dummy;
2497 (void) obj;
2498 (void) action;
2499 (void) data;
2500 (void) whole_msg;
2501 (void) type;
2502 if (sscanf(data, " %c", &dummy) < 1)
2503 gtk_main_quit();
2504 else
2505 ign_cmd(type, whole_msg);
2509 * Don't update anything; just complain
2511 static void
2512 complain(GObject *obj, const char *action,
2513 const char *data, const char *whole_msg, GType type)
2515 (void) obj;
2516 (void) action;
2517 (void) data;
2518 ign_cmd(type, whole_msg);
2522 * Data to be passed to and from the GTK main loop
2524 struct ui_data {
2525 void (*fn)(GObject *, const char *action,
2526 const char *data, const char *msg, GType type);
2527 GObject *obj;
2528 char *action;
2529 char *data;
2530 char *msg;
2531 char *msg_tokens;
2532 GType type;
2536 * Parse command pointed to by ud, and act on ui accordingly. Runs
2537 * once per command inside gtk_main_loop().
2539 static gboolean
2540 update_ui(struct ui_data *ud)
2542 char *lc = lc_numeric();
2544 (ud->fn)(ud->obj, ud->action, ud->data, ud->msg, ud->type);
2545 free(ud->msg_tokens);
2546 free(ud->msg);
2547 free(ud);
2548 lc_numeric_free(lc);
2549 return G_SOURCE_REMOVE;
2553 * Keep track of loading files to avoid recursive loading of the same
2554 * file. If filename = NULL, forget the most recently remembered file.
2556 static bool
2557 remember_loading_file(char *filename)
2559 static char *filenames[BUFLEN];
2560 static size_t latest = 0;
2561 size_t i;
2563 if (filename == NULL) { /* pop */
2564 if (latest < 1)
2565 ABORT;
2566 latest--;
2567 return false;
2568 } else { /* push */
2569 for (i = 1; i <= latest; i++)
2570 if (eql(filename, filenames[i]))
2571 return false;
2572 if (latest > BUFLEN -2)
2573 return false;
2574 filenames[++latest] = filename;
2575 return true;
2580 * Read lines from stream cmd and perform appropriate actions on the
2581 * GUI
2583 static void *
2584 digest_msg(FILE *cmd)
2586 FILE *load; /* restoring user data */
2587 char *name;
2588 static int recursion = -1; /* > 0 means this is a recursive call */
2590 recursion++;
2591 for (;;) {
2592 struct ui_data *ud;
2593 char first_char = '\0';
2594 size_t msg_size = 32;
2595 int name_start = 0, name_end = 0;
2596 int action_start = 0, action_end = 0;
2597 int data_start;
2599 if (feof(cmd))
2600 break;
2601 if ((ud = malloc(sizeof(*ud))) == NULL)
2602 OOM_ABORT;
2603 if ((ud->msg = malloc(msg_size)) == NULL)
2604 OOM_ABORT;
2605 ud->type = G_TYPE_INVALID;
2606 pthread_testcancel();
2607 if (recursion == 0)
2608 log_msg(NULL);
2609 read_buf(cmd, &ud->msg, &msg_size);
2610 if (recursion == 0)
2611 log_msg(ud->msg);
2612 data_start = strlen(ud->msg);
2613 if ((ud->msg_tokens = malloc(strlen(ud->msg) + 1)) == NULL)
2614 OOM_ABORT;
2615 strcpy(ud->msg_tokens, ud->msg);
2616 sscanf(ud->msg, " %c", &first_char);
2617 if (strlen(ud->msg) == 0 || first_char == '#') { /* comment */
2618 ud->fn = update_nothing;
2619 goto exec;
2621 sscanf(ud->msg_tokens,
2622 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2623 &name_start, &name_end, &action_start, &action_end, &data_start);
2624 ud->msg_tokens[name_end] = ud->msg_tokens[action_end] = '\0';
2625 name = ud->msg_tokens + name_start;
2626 ud->action = ud->msg_tokens + action_start;
2627 ud->data = ud->msg_tokens + data_start;
2628 if (eql(ud->action, "main_quit")) {
2629 ud->fn = main_quit;
2630 goto exec;
2632 if (eql(ud->action, "load") && strlen(ud->data) > 0 &&
2633 (load = fopen(ud->data, "r")) != NULL &&
2634 remember_loading_file(ud->data)) {
2635 digest_msg(load);
2636 fclose(load);
2637 remember_loading_file(NULL);
2638 ud->fn = update_nothing;
2639 goto exec;
2641 if ((ud->obj = (gtk_builder_get_object(builder, name))) == NULL) {
2642 ud->fn = complain;
2643 goto exec;
2645 ud->type = G_TYPE_FROM_INSTANCE(ud->obj);
2646 if (eql(ud->action, "force"))
2647 ud->fn = fake_ui_activity;
2648 else if (eql(ud->action, "set_sensitive"))
2649 ud->fn = update_sensitivity;
2650 else if (eql(ud->action, "set_visible"))
2651 ud->fn = update_visibility;
2652 else if (eql(ud->action, "set_size_request"))
2653 ud->fn = update_size_request;
2654 else if (eql(ud->action, "set_tooltip_text"))
2655 ud->fn = update_tooltip_text;
2656 else if (eql(ud->action, "grab_focus"))
2657 ud->fn = update_focus;
2658 else if (eql(ud->action, "style")) {
2659 ud->action = name;
2660 ud->fn = update_widget_style;
2661 } else if (ud->type == GTK_TYPE_DRAWING_AREA)
2662 ud->fn = update_drawing_area;
2663 else if (ud->type == GTK_TYPE_TREE_VIEW)
2664 ud->fn = update_tree_view;
2665 else if (ud->type == GTK_TYPE_COMBO_BOX_TEXT)
2666 ud->fn = update_combo_box_text;
2667 else if (ud->type == GTK_TYPE_LABEL)
2668 ud->fn = update_label;
2669 else if (ud->type == GTK_TYPE_IMAGE)
2670 ud->fn = update_image;
2671 else if (ud->type == GTK_TYPE_TEXT_VIEW)
2672 ud->fn = update_text_view;
2673 else if (ud->type == GTK_TYPE_NOTEBOOK)
2674 ud->fn = update_notebook;
2675 else if (ud->type == GTK_TYPE_EXPANDER)
2676 ud->fn = update_expander;
2677 else if (ud->type == GTK_TYPE_FRAME)
2678 ud->fn = update_frame;
2679 else if (ud->type == GTK_TYPE_SCROLLED_WINDOW)
2680 ud->fn = update_scrolled_window;
2681 else if (ud->type == GTK_TYPE_BUTTON)
2682 ud->fn = update_button;
2683 else if (ud->type == GTK_TYPE_FILE_CHOOSER_DIALOG)
2684 ud->fn = update_file_chooser_dialog;
2685 else if (ud->type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2686 ud->fn = update_file_chooser_button;
2687 else if (ud->type == GTK_TYPE_COLOR_BUTTON)
2688 ud->fn = update_color_button;
2689 else if (ud->type == GTK_TYPE_FONT_BUTTON)
2690 ud->fn = update_font_button;
2691 else if (ud->type == GTK_TYPE_PRINT_UNIX_DIALOG)
2692 ud->fn = update_print_dialog;
2693 else if (ud->type == GTK_TYPE_SWITCH)
2694 ud->fn = update_switch;
2695 else if (ud->type == GTK_TYPE_TOGGLE_BUTTON ||
2696 ud->type == GTK_TYPE_RADIO_BUTTON ||
2697 ud->type == GTK_TYPE_CHECK_BUTTON)
2698 ud->fn = update_toggle_button;
2699 else if (ud->type == GTK_TYPE_SPIN_BUTTON ||
2700 ud->type == GTK_TYPE_ENTRY)
2701 ud->fn = update_entry;
2702 else if (ud->type == GTK_TYPE_SCALE)
2703 ud->fn = update_scale;
2704 else if (ud->type == GTK_TYPE_PROGRESS_BAR)
2705 ud->fn = update_progress_bar;
2706 else if (ud->type == GTK_TYPE_SPINNER)
2707 ud->fn = update_spinner;
2708 else if (ud->type == GTK_TYPE_STATUSBAR)
2709 ud->fn = update_statusbar;
2710 else if (ud->type == GTK_TYPE_CALENDAR)
2711 ud->fn = update_calendar;
2712 else if (ud->type == GTK_TYPE_SOCKET)
2713 ud->fn = update_socket;
2714 else if (ud->type == GTK_TYPE_WINDOW ||
2715 ud->type == GTK_TYPE_DIALOG)
2716 ud->fn = update_window;
2717 else
2718 ud->fn = complain;
2719 exec:
2720 pthread_testcancel();
2721 gdk_threads_add_timeout(0, (GSourceFunc) update_ui, ud);
2723 recursion--;
2724 return NULL;
2729 * ============================================================
2730 * Initialization
2731 * ============================================================
2735 * Attach to renderer key "col_number". Associate "col_number" with
2736 * the corresponding column number in the underlying model.
2737 * Due to what looks like a gap in the GTK API, renderer id and column
2738 * number are taken directly from the XML .ui file.
2740 static bool
2741 tree_view_column_get_renderer_column(const char *ui_file, GtkTreeViewColumn *t_col,
2742 int n, GtkCellRenderer **renderer)
2744 bool r = false;
2745 char *xpath_base1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
2746 char *xpath_base2 = "\"]/child[";
2747 char *xpath_base3 = "]/object[@class=\"GtkCellRendererText\""
2748 " or @class=\"GtkCellRendererToggle\"]/";
2749 char *xpath_renderer_id = "/@id";
2750 char *xpath_text_col = "../attributes/attribute[@name=\"text\""
2751 " or @name=\"active\"]";
2752 const char *xpath_id = widget_name(GTK_BUILDABLE(t_col));
2753 int i;
2754 size_t xpath_len;
2755 size_t xpath_n_len = 3; /* Big Enough (TM) */
2756 xmlChar *xpath, *renderer_name = NULL, *m_col_s = NULL;
2757 xmlDocPtr doc;
2758 xmlNodePtr cur;
2759 xmlNodeSetPtr nodes;
2760 xmlXPathContextPtr xpath_ctx;
2761 xmlXPathObjectPtr xpath_obj;
2763 if ((doc = xmlParseFile(ui_file)) == NULL)
2764 return false;
2765 if ((xpath_ctx = xmlXPathNewContext(doc)) == NULL) {
2766 xmlFreeDoc(doc);
2767 return false;
2769 xpath_len = 2 * (strlen(xpath_base1) + strlen(xpath_id) +
2770 strlen(xpath_base2) + xpath_n_len +
2771 strlen(xpath_base3))
2772 + 1 /* "|" */
2773 + strlen(xpath_text_col) + strlen(xpath_renderer_id)
2774 + 1; /* '\0' */
2775 if ((xpath = malloc(xpath_len)) == NULL) {
2776 xmlFreeDoc(doc);
2777 return false;
2779 snprintf((char *) xpath, xpath_len, "%s%s%s%d%s%s|%s%s%s%d%s%s",
2780 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_text_col,
2781 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_renderer_id);
2782 if ((xpath_obj = xmlXPathEvalExpression(xpath, xpath_ctx)) == NULL) {
2783 xmlXPathFreeContext(xpath_ctx);
2784 free(xpath);
2785 xmlFreeDoc(doc);
2786 return false;
2788 if ((nodes = xpath_obj->nodesetval) != NULL) {
2789 for (i = 0; i < nodes->nodeNr; ++i) {
2790 if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
2791 cur = nodes->nodeTab[i];
2792 m_col_s = xmlNodeGetContent(cur);
2793 } else {
2794 cur = nodes->nodeTab[i];
2795 renderer_name = xmlNodeGetContent(cur);
2799 if (renderer_name) {
2800 *renderer = GTK_CELL_RENDERER(
2801 gtk_builder_get_object(builder, (char *) renderer_name));
2802 if (m_col_s) {
2803 g_object_set_data(G_OBJECT(*renderer), "col_number",
2804 GINT_TO_POINTER(strtol((char *) m_col_s,
2805 NULL, 10)));
2806 xmlFree(m_col_s);
2807 r = true;
2809 xmlFree(renderer_name);
2811 xmlXPathFreeObject(xpath_obj);
2812 xmlXPathFreeContext(xpath_ctx);
2813 free(xpath);
2814 xmlFreeDoc(doc);
2815 return r;
2819 * Callbacks that forward a modification of a tree view cell to the
2820 * underlying model
2822 static void
2823 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
2824 const gchar *new_text, gpointer model)
2826 GtkTreeIter iter;
2827 GtkTreeView *view;
2828 void *col;
2830 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2831 view = g_object_get_data(G_OBJECT(renderer), "tree_view");
2832 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2833 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2834 new_text);
2835 send_tree_cell_msg_by(send_msg, model, path_s, &iter, GPOINTER_TO_INT(col),
2836 GTK_BUILDABLE(view));
2839 static void
2840 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, gpointer model)
2842 GtkTreeIter iter;
2843 bool toggle_state;
2844 void *col;
2846 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2847 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2848 gtk_tree_model_get(model, &iter, col, &toggle_state, -1);
2849 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2850 toggle_state? "0" : "1");
2853 static void
2854 connect_widget_signals(gpointer *obj, char *ui_file)
2856 GObject *obj2;
2857 GType type = G_TYPE_INVALID;
2858 char *suffix = NULL;
2859 const char *name = NULL;
2861 type = G_TYPE_FROM_INSTANCE(obj);
2862 if (GTK_IS_BUILDABLE(obj))
2863 name = widget_name(GTK_BUILDABLE(obj));
2864 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
2865 GtkCellRenderer *renderer;
2866 GtkTreeModel *model;
2867 GtkTreeView *view;
2868 gboolean editable = FALSE;
2869 int i;
2871 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
2872 view = GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(GTK_TREE_VIEW_COLUMN(obj)));
2873 model = gtk_tree_view_get_model(view);
2874 for (i = 1;; i++) {
2875 if (!tree_view_column_get_renderer_column(ui_file, GTK_TREE_VIEW_COLUMN(obj), i, &renderer))
2876 break;
2877 g_object_set_data(G_OBJECT(renderer), "tree_view", view);
2878 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
2879 g_object_get(renderer, "editable", &editable, NULL);
2880 if (editable)
2881 g_signal_connect(renderer, "edited", G_CALLBACK(cb_tree_model_edit), model);
2882 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
2883 g_object_get(renderer, "activatable", &editable, NULL);
2884 if (editable)
2885 g_signal_connect(renderer, "toggled", G_CALLBACK(cb_tree_model_toggle), model);
2888 } else if (type == GTK_TYPE_BUTTON) {
2889 /* Button associated with a GtkTextView. */
2890 if ((suffix = strstr(name, "_send_text")) != NULL &&
2891 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2892 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
2893 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2894 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
2895 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2896 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
2897 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2898 else {
2899 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
2900 /* Buttons associated with (and part of) a GtkDialog.
2901 * (We shun response ids which could be returned from
2902 * gtk_dialog_run() because that would require the
2903 * user to define those response ids in Glade,
2904 * numerically */
2905 if ((suffix = strstr(name, "_cancel")) != NULL &&
2906 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2907 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
2908 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2909 else
2910 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2911 else if ((suffix = strstr(name, "_ok")) != NULL &&
2912 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name))) {
2913 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
2914 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), GTK_FILE_CHOOSER(obj2));
2915 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
2916 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2917 else
2918 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2919 } else if ((suffix = strstr(name, "_apply")) != NULL &&
2920 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2921 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
2923 } else if (GTK_IS_MENU_ITEM(obj))
2924 if ((suffix = strstr(name, "_invoke")) != NULL &&
2925 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2926 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
2927 else
2928 g_signal_connect(obj, "activate", G_CALLBACK(cb_menu_item), "active");
2929 else if (GTK_IS_WINDOW(obj)) {
2930 g_signal_connect(obj, "delete-event", G_CALLBACK(cb_event_simple), "closed");
2931 if (eql(name, MAIN_WIN))
2932 g_signal_connect_swapped(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
2933 else
2934 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
2935 } else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2936 g_signal_connect(obj, "file-set", G_CALLBACK(cb_file_chooser_button), "file");
2937 else if (type == GTK_TYPE_COLOR_BUTTON)
2938 g_signal_connect(obj, "color-set", G_CALLBACK(cb_color_button), "color");
2939 else if (type == GTK_TYPE_FONT_BUTTON)
2940 g_signal_connect(obj, "font-set", G_CALLBACK(cb_font_button), "font");
2941 else if (type == GTK_TYPE_SWITCH)
2942 g_signal_connect(obj, "notify::active", G_CALLBACK(cb_switch), NULL);
2943 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
2944 g_signal_connect(obj, "toggled", G_CALLBACK(cb_toggle_button), NULL);
2945 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
2946 g_signal_connect(obj, "changed", G_CALLBACK(cb_editable), "text");
2947 else if (type == GTK_TYPE_SCALE)
2948 g_signal_connect(obj, "value-changed", G_CALLBACK(cb_range), "value");
2949 else if (type == GTK_TYPE_CALENDAR) {
2950 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb_calendar), "doubleclicked");
2951 g_signal_connect(obj, "day-selected", G_CALLBACK(cb_calendar), "clicked");
2952 } else if (type == GTK_TYPE_TREE_SELECTION)
2953 g_signal_connect(obj, "changed", G_CALLBACK(cb_tree_selection), "clicked");
2954 else if (type == GTK_TYPE_SOCKET) {
2955 g_signal_connect(obj, "plug-added", G_CALLBACK(cb_simple), "plug-added");
2956 g_signal_connect(obj, "plug-removed", G_CALLBACK(cb_simple), "plug-removed");
2957 } else if (type == GTK_TYPE_DRAWING_AREA)
2958 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
2959 else if (type == GTK_TYPE_EVENT_BOX) {
2960 gtk_widget_set_can_focus(GTK_WIDGET(obj), true);
2961 g_signal_connect(obj, "button-press-event", G_CALLBACK(cb_event_box_button), "button_press");
2962 g_signal_connect(obj, "button-release-event", G_CALLBACK(cb_event_box_button), "button_release");
2963 g_signal_connect(obj, "motion-notify-event", G_CALLBACK(cb_event_box_motion), "motion");
2964 g_signal_connect(obj, "key-press-event", G_CALLBACK(cb_event_box_key), "key_press");
2969 * We keep a style provider with each widget
2971 static void
2972 add_widget_style_provider(gpointer *obj, void *data)
2974 GtkCssProvider *style_provider;
2975 GtkStyleContext *context;
2977 (void) data;
2978 if (!GTK_IS_WIDGET(obj))
2979 return;
2980 style_provider = gtk_css_provider_new();
2981 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2982 gtk_style_context_add_provider(context,
2983 GTK_STYLE_PROVIDER(style_provider),
2984 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2985 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
2988 static void
2989 prepare_widgets(char *ui_file)
2991 GSList *objects = NULL;
2993 objects = gtk_builder_get_objects(builder);
2994 g_slist_foreach(objects, (GFunc) connect_widget_signals, ui_file);
2995 g_slist_foreach(objects, (GFunc) add_widget_style_provider, NULL);
2996 g_slist_free(objects);
3000 main(int argc, char *argv[])
3002 FILE *in = NULL; /* command input */
3003 GObject *main_window = NULL;
3004 bool bg = false;
3005 char *in_fifo = NULL, *out_fifo = NULL;
3006 char *ui_file = "pipeglade.ui", *log_file = NULL, *err_file = NULL;
3007 char *xid = NULL;
3008 char opt;
3009 pthread_t receiver;
3011 /* Disable runtime GLIB deprecation warnings: */
3012 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
3013 out = NULL;
3014 save = NULL;
3015 log_out = NULL;
3016 gtk_init(&argc, &argv);
3017 while ((opt = getopt(argc, argv, "bGhe:i:l:o:O:u:V")) != -1) {
3018 switch (opt) {
3019 case 'b': bg = true; break;
3020 case 'e': xid = optarg; break;
3021 case 'G': show_lib_versions(); break;
3022 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
3023 case 'i': in_fifo = optarg; break;
3024 case 'l': log_file = optarg; break;
3025 case 'o': out_fifo = optarg; break;
3026 case 'O': err_file = optarg; break;
3027 case 'u': ui_file = optarg; break;
3028 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
3029 case '?':
3030 default: bye(EXIT_FAILURE, stderr, USAGE); break;
3033 if (argv[optind] != NULL)
3034 bye(EXIT_FAILURE, stderr,
3035 "illegal parameter '%s'\n" USAGE, argv[optind]);
3036 redirect_stderr(err_file);
3037 in = open_in_fifo(in_fifo);
3038 out = open_out_fifo(out_fifo);
3039 go_bg_if(bg, in, out, err_file);
3040 builder = builder_from_file(ui_file);
3041 log_out = open_log(log_file);
3042 pthread_create(&receiver, NULL, (void *(*)(void *)) digest_msg, in);
3043 main_window = find_main_window();
3044 xmlInitParser();
3045 LIBXML_TEST_VERSION;
3046 prepare_widgets(ui_file);
3047 xembed_if(xid, main_window);
3048 gtk_main();
3049 rm_unless(stdin, in, in_fifo);
3050 rm_unless(stdout, out, out_fifo);
3051 pthread_cancel(receiver);
3052 pthread_join(receiver, NULL);
3053 xmlCleanupParser();
3054 exit(EXIT_SUCCESS);