Sanitize two cases of unrestricted %s in sscanf()
[pipeglade.git] / pipeglade.c
blob6982023a94de125ffefce17f6f336a121d6f2dac
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_spin_button(GtkBuildable *obj, const char *tag)
805 char str[BUFLEN], *lc = lc_numeric();
807 snprintf(str, BUFLEN, "%f", gtk_spin_button_get_value(GTK_SPIN_BUTTON(obj)));
808 send_msg(obj, tag, str, NULL);
809 lc_numeric_free(lc);
812 static void
813 cb_switch(GtkBuildable *obj, void *pspec, void *user_data)
815 (void) pspec;
816 (void) user_data;
817 send_msg(obj, gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0", NULL);
820 static void
821 cb_toggle_button(GtkBuildable *obj, const char *tag)
823 (void) tag;
824 send_msg(obj, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0", NULL);
827 static void
828 cb_tree_selection(GtkBuildable *obj, const char *tag)
830 GtkTreeSelection *sel = GTK_TREE_SELECTION(obj);
831 GtkTreeView *view = gtk_tree_selection_get_tree_view(sel);
834 send_msg(GTK_BUILDABLE(view), tag, NULL);
835 gtk_tree_selection_selected_foreach(
836 sel, (GtkTreeSelectionForeachFunc) send_tree_row_msg, view);
841 * ============================================================
842 * cb_draw() maintains a drawing on a GtkDrawingArea; it needs a few
843 * helper functions
844 * ============================================================
848 * The set of supported drawing operations
850 enum cairo_fn {
851 ARC,
852 ARC_NEGATIVE,
853 CLOSE_PATH,
854 CURVE_TO,
855 FILL,
856 FILL_PRESERVE,
857 LINE_TO,
858 MOVE_TO,
859 RECTANGLE,
860 REL_CURVE_TO,
861 REL_LINE_TO,
862 REL_MOVE_TO,
863 REL_MOVE_FOR,
864 RESET_CTM,
865 SET_DASH,
866 SET_FONT_FACE,
867 SET_FONT_SIZE,
868 SET_LINE_CAP,
869 SET_LINE_JOIN,
870 SET_LINE_WIDTH,
871 SET_SOURCE_RGBA,
872 SHOW_TEXT,
873 STROKE,
874 STROKE_PRESERVE,
875 TRANSFORM,
879 * Text placement mode for rel_move_for()
881 enum ref_point {
893 enum draw_op_policy {
894 APPEND,
895 BEFORE,
896 REPLACE,
900 * One single element of a drawing
902 struct draw_op {
903 struct draw_op *next;
904 struct draw_op *prev;
905 unsigned long long int id;
906 unsigned long long int before;
907 enum draw_op_policy policy;
908 enum cairo_fn op;
909 void *op_args;
913 * Argument sets for the various drawing operations
915 struct arc_args {
916 double x;
917 double y;
918 double radius;
919 double angle1;
920 double angle2;
923 struct curve_to_args {
924 double x1;
925 double y1;
926 double x2;
927 double y2;
928 double x3;
929 double y3;
932 struct move_to_args {
933 double x;
934 double y;
937 struct rectangle_args {
938 double x;
939 double y;
940 double width;
941 double height;
944 struct rel_move_for_args {
945 enum ref_point ref;
946 int len;
947 char text[];
950 struct set_dash_args {
951 int num_dashes;
952 double dashes[];
955 struct set_font_face_args {
956 cairo_font_slant_t slant;
957 cairo_font_weight_t weight;
958 char family[];
961 struct set_font_size_args {
962 double size;
965 struct set_line_cap_args {
966 cairo_line_cap_t line_cap;
969 struct set_line_join_args {
970 cairo_line_join_t line_join;
973 struct set_line_width_args {
974 double width;
977 struct set_source_rgba_args {
978 GdkRGBA color;
981 struct show_text_args {
982 int len;
983 char text[];
986 struct transform_args {
987 cairo_matrix_t matrix;
990 static void
991 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
993 switch (op) {
994 case LINE_TO: {
995 struct move_to_args *args = op_args;
997 cairo_line_to(cr, args->x, args->y);
998 break;
1000 case REL_LINE_TO: {
1001 struct move_to_args *args = op_args;
1003 cairo_rel_line_to(cr, args->x, args->y);
1004 break;
1006 case MOVE_TO: {
1007 struct move_to_args *args = op_args;
1009 cairo_move_to(cr, args->x, args->y);
1010 break;
1012 case REL_MOVE_TO: {
1013 struct move_to_args *args = op_args;
1015 cairo_rel_move_to(cr, args->x, args->y);
1016 break;
1018 case ARC: {
1019 struct arc_args *args = op_args;
1021 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1022 break;
1024 case ARC_NEGATIVE: {
1025 struct arc_args *args = op_args;
1027 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1028 break;
1030 case CURVE_TO: {
1031 struct curve_to_args *args = op_args;
1033 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1034 break;
1036 case REL_CURVE_TO: {
1037 struct curve_to_args *args = op_args;
1039 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1040 break;
1042 case RECTANGLE: {
1043 struct rectangle_args *args = op_args;
1045 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
1046 break;
1048 case CLOSE_PATH:
1049 cairo_close_path(cr);
1050 break;
1051 case SHOW_TEXT: {
1052 struct show_text_args *args = op_args;
1054 cairo_show_text(cr, args->text);
1055 break;
1057 case REL_MOVE_FOR: {
1058 cairo_text_extents_t e;
1059 double dx = 0.0, dy = 0.0;
1060 struct rel_move_for_args *args = op_args;
1062 cairo_text_extents(cr, args->text, &e);
1063 switch (args->ref) {
1064 case C: dx = -e.width / 2; dy = e.height / 2; break;
1065 case E: dx = -e.width; dy = e.height / 2; break;
1066 case N: dx = -e.width / 2; dy = e.height; break;
1067 case NE: dx = -e.width; dy = e.height; break;
1068 case NW: dy = e.height; break;
1069 case S: dx = -e.width / 2; break;
1070 case SE: dx = -e.width; break;
1071 case SW: break;
1072 case W: dy = e.height / 2; break;
1073 default: ABORT; break;
1075 cairo_rel_move_to(cr, dx, dy);
1076 break;
1078 case RESET_CTM:
1079 cairo_identity_matrix(cr);
1080 break;
1081 case STROKE:
1082 cairo_stroke(cr);
1083 break;
1084 case STROKE_PRESERVE:
1085 cairo_stroke_preserve(cr);
1086 break;
1087 case FILL:
1088 cairo_fill(cr);
1089 break;
1090 case FILL_PRESERVE:
1091 cairo_fill_preserve(cr);
1092 break;
1093 case SET_DASH: {
1094 struct set_dash_args *args = op_args;
1096 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
1097 break;
1099 case SET_FONT_FACE: {
1100 struct set_font_face_args *args = op_args;
1102 cairo_select_font_face(cr, args->family, args->slant, args->weight);
1103 break;
1105 case SET_FONT_SIZE: {
1106 struct set_font_size_args *args = op_args;
1108 cairo_set_font_size(cr, args->size);
1109 break;
1111 case SET_LINE_CAP: {
1112 struct set_line_cap_args *args = op_args;
1114 cairo_set_line_cap(cr, args->line_cap);
1115 break;
1117 case SET_LINE_JOIN: {
1118 struct set_line_join_args *args = op_args;
1120 cairo_set_line_join(cr, args->line_join);
1121 break;
1123 case SET_LINE_WIDTH: {
1124 struct set_line_width_args *args = op_args;
1126 cairo_set_line_width(cr, args->width);
1127 break;
1129 case SET_SOURCE_RGBA: {
1130 struct set_source_rgba_args *args = op_args;
1132 gdk_cairo_set_source_rgba(cr, &args->color);
1133 break;
1135 case TRANSFORM: {
1136 struct transform_args *args = op_args;
1138 cairo_transform(cr, &args->matrix);
1139 break;
1141 default:
1142 ABORT;
1143 break;
1148 * Callback that draws on a GtkDrawingArea
1150 static gboolean
1151 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
1153 struct draw_op *op;
1155 (void) data;
1156 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
1157 op != NULL;
1158 op = op->next)
1159 draw(cr, op->op, op->op_args);
1160 return FALSE;
1165 * ============================================================
1166 * Manipulating the GUI
1167 * ============================================================
1170 static void
1171 update_button(GObject *obj, const char *action,
1172 const char *data, const char *whole_msg, GType type)
1174 if (eql(action, "set_label"))
1175 gtk_button_set_label(GTK_BUTTON(obj), data);
1176 else
1177 ign_cmd(type, whole_msg);
1180 static void
1181 update_calendar(GObject *obj, const char *action,
1182 const char *data, const char *whole_msg, GType type)
1184 GtkCalendar *calendar = GTK_CALENDAR(obj);
1185 char dummy;
1186 int year = 0, month = 0, day = 0;
1188 if (eql(action, "select_date") &&
1189 sscanf(data, "%d-%d-%d %c", &year, &month, &day, &dummy) == 3) {
1190 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
1191 gtk_calendar_select_month(calendar, --month, year);
1192 gtk_calendar_select_day(calendar, day);
1193 } else
1194 ign_cmd(type, whole_msg);
1195 } else if (eql(action, "mark_day") &&
1196 sscanf(data, "%d %c", &day, &dummy) == 1) {
1197 if (day > 0 && day <= 31)
1198 gtk_calendar_mark_day(calendar, day);
1199 else
1200 ign_cmd(type, whole_msg);
1201 } else if (eql(action, "clear_marks") && sscanf(data, " %c", &dummy) < 1)
1202 gtk_calendar_clear_marks(calendar);
1203 else
1204 ign_cmd(type, whole_msg);
1208 * Common actions for various kinds of window. Return false if
1209 * command is ignored
1211 static bool
1212 update_class_window(GObject *obj, const char *action,
1213 const char *data, const char *whole_msg, GType type)
1215 GtkWindow *window = GTK_WINDOW(obj);
1216 char dummy;
1217 int x, y;
1219 (void) type;
1220 (void) whole_msg;
1221 if (eql(action, "set_title"))
1222 gtk_window_set_title(window, data);
1223 else if (eql(action, "fullscreen") && sscanf(data, " %c", &dummy) < 1)
1224 gtk_window_fullscreen(window);
1225 else if (eql(action, "unfullscreen") && sscanf(data, " %c", &dummy) < 1)
1226 gtk_window_unfullscreen(window);
1227 else if (eql(action, "resize") &&
1228 sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
1229 gtk_window_resize(window, x, y);
1230 else if (eql(action, "resize") && sscanf(data, " %c", &dummy) < 1) {
1231 gtk_window_get_default_size(window, &x, &y);
1232 gtk_window_resize(window, x, y);
1233 } else if (eql(action, "move") &&
1234 sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
1235 gtk_window_move(window, x, y);
1236 else
1237 return false;
1238 return true;
1241 static void
1242 update_color_button(GObject *obj, const char *action,
1243 const char *data, const char *whole_msg, GType type)
1245 GdkRGBA color;
1247 if (eql(action, "set_color")) {
1248 gdk_rgba_parse(&color, data);
1249 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
1250 } else
1251 ign_cmd(type, whole_msg);
1254 static void
1255 update_combo_box_text(GObject *obj, const char *action,
1256 const char *data, const char *whole_msg, GType type)
1258 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(obj);
1259 char data1[strlen(data) + 1];
1260 char dummy;
1261 int val;
1263 strcpy(data1, data);
1264 if (eql(action, "prepend_text"))
1265 gtk_combo_box_text_prepend_text(combobox, data1);
1266 else if (eql(action, "append_text"))
1267 gtk_combo_box_text_append_text(combobox, data1);
1268 else if (eql(action, "remove") && sscanf(data, "%d %c", &val, &dummy) == 1)
1269 gtk_combo_box_text_remove(combobox, strtol(data1, NULL, 10));
1270 else if (eql(action, "insert_text")) {
1271 char *position = strtok(data1, WHITESPACE);
1272 char *text = strtok(NULL, WHITESPACE);
1274 gtk_combo_box_text_insert_text(combobox,
1275 strtol(position, NULL, 10), text);
1276 } else
1277 ign_cmd(type, whole_msg);
1281 * Maintaining a list of drawing operations. It is the responsibility
1282 * of cb_draw() to actually draw them. update_drawing_area() needs a
1283 * few helper functions.
1286 enum draw_op_stat {
1287 FAILURE,
1288 SUCCESS,
1289 NEED_REDRAW,
1293 * Fill structure *op with the drawing operation according to action
1294 * and with the appropriate set of arguments
1296 static enum draw_op_stat
1297 set_draw_op(struct draw_op *op, const char *action, const char *data)
1299 char dummy;
1300 const char *raw_args = data;
1301 enum draw_op_stat result = SUCCESS;
1302 int args_start = 0;
1304 if (sscanf(data, "=%llu %n", &op->id, &args_start) == 1) {
1305 op->policy = REPLACE;
1306 result = NEED_REDRAW;
1307 } else if (sscanf(data, "%llu<%llu %n", &op->id, &op->before, &args_start) == 2) {
1308 op->policy = BEFORE;
1309 result = NEED_REDRAW;
1310 } else if (sscanf(data, "%llu %n", &op->id, &args_start) == 1)
1311 op->policy = APPEND;
1312 else
1313 return FAILURE;
1314 raw_args += args_start;
1315 if (eql(action, "line_to")) {
1316 struct move_to_args *args;
1318 if ((args = malloc(sizeof(*args))) == NULL)
1319 OOM_ABORT;
1320 op->op = LINE_TO;
1321 op->op_args = args;
1322 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1323 return FAILURE;
1324 } else if (eql(action, "rel_line_to")) {
1325 struct move_to_args *args;
1327 if ((args = malloc(sizeof(*args))) == NULL)
1328 OOM_ABORT;
1329 op->op = REL_LINE_TO;
1330 op->op_args = args;
1331 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1332 return FAILURE;
1333 } else if (eql(action, "move_to")) {
1334 struct move_to_args *args;
1336 if ((args = malloc(sizeof(*args))) == NULL)
1337 OOM_ABORT;
1338 op->op = MOVE_TO;
1339 op->op_args = args;
1340 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1341 return FAILURE;
1342 } else if (eql(action, "rel_move_to")) {
1343 struct move_to_args *args;
1345 if ((args = malloc(sizeof(*args))) == NULL)
1346 OOM_ABORT;
1347 op->op = REL_MOVE_TO;
1348 op->op_args = args;
1349 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1350 return FAILURE;
1351 } else if (eql(action, "arc")) {
1352 struct arc_args *args;
1353 double deg1, deg2;
1355 if ((args = malloc(sizeof(*args))) == NULL)
1356 OOM_ABORT;
1357 op->op = ARC;
1358 op->op_args = args;
1359 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %c",
1360 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 5)
1361 return FAILURE;
1362 args->angle1 = deg1 * (M_PI / 180.L);
1363 args->angle2 = deg2 * (M_PI / 180.L);
1364 } else if (eql(action, "arc_negative")) {
1365 double deg1, deg2;
1366 struct arc_args *args;
1368 if ((args = malloc(sizeof(*args))) == NULL)
1369 OOM_ABORT;
1370 op->op = ARC_NEGATIVE;
1371 op->op_args = args;
1372 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %c",
1373 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 5)
1374 return FAILURE;
1375 args->angle1 = deg1 * (M_PI / 180.L);
1376 args->angle2 = deg2 * (M_PI / 180.L);
1377 } else if (eql(action, "curve_to")) {
1378 struct curve_to_args *args;
1380 if ((args = malloc(sizeof(*args))) == NULL)
1381 OOM_ABORT;
1382 op->op = CURVE_TO;
1383 op->op_args = args;
1384 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1385 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 6)
1386 return FAILURE;
1387 } else if (eql(action, "rel_curve_to")) {
1388 struct curve_to_args *args;
1390 if ((args = malloc(sizeof(*args))) == NULL)
1391 OOM_ABORT;
1392 op->op = REL_CURVE_TO;
1393 op->op_args = args;
1394 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1395 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 6)
1396 return FAILURE;
1397 } else if (eql(action, "rectangle")) {
1398 struct rectangle_args *args;
1400 if ((args = malloc(sizeof(*args))) == NULL)
1401 OOM_ABORT;
1402 op->op = RECTANGLE;
1403 op->op_args = args;
1404 if (sscanf(raw_args, "%lf %lf %lf %lf %c",
1405 &args->x, &args->y, &args->width, &args->height, &dummy) != 4)
1406 return FAILURE;
1407 } else if (eql(action, "close_path")) {
1408 op->op = CLOSE_PATH;
1409 if (sscanf(raw_args, " %c", &dummy) > 0)
1410 return FAILURE;
1411 op->op_args = NULL;
1412 } else if (eql(action, "show_text")) {
1413 struct show_text_args *args;
1414 int len;
1416 len = strlen(raw_args) + 1;
1417 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1418 OOM_ABORT;
1419 op->op = SHOW_TEXT;
1420 op->op_args = args;
1421 args->len = len; /* not used */
1422 strncpy(args->text, raw_args, len);
1423 result = NEED_REDRAW;
1424 } else if (eql(action, "rel_move_for")) {
1425 char ref_point[2 + 1];
1426 int start, len;
1427 struct rel_move_for_args *args;
1429 if (sscanf(raw_args, "%2s %n", ref_point, &start) < 1)
1430 return FAILURE;
1431 len = strlen(raw_args + start) + 1;
1432 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1433 OOM_ABORT;
1434 if (eql(ref_point, "c"))
1435 args->ref = C;
1436 else if (eql(ref_point, "e"))
1437 args->ref = E;
1438 else if (eql(ref_point, "n"))
1439 args->ref = N;
1440 else if (eql(ref_point, "ne"))
1441 args->ref = NE;
1442 else if (eql(ref_point, "nw"))
1443 args->ref = NW;
1444 else if (eql(ref_point, "s"))
1445 args->ref = S;
1446 else if (eql(ref_point, "se"))
1447 args->ref = SE;
1448 else if (eql(ref_point, "sw"))
1449 args->ref = SW;
1450 else if (eql(ref_point, "w"))
1451 args->ref = W;
1452 else
1453 return FAILURE;
1454 op->op = REL_MOVE_FOR;
1455 op->op_args = args;
1456 args->len = len; /* not used */
1457 strncpy(args->text, (raw_args + start), len);
1458 } else if (eql(action, "stroke")) {
1459 op->op = STROKE;
1460 if (sscanf(raw_args, " %c", &dummy) > 0)
1461 return FAILURE;
1462 op->op_args = NULL;
1463 result = NEED_REDRAW;
1464 } else if (eql(action, "stroke_preserve")) {
1465 op->op = STROKE_PRESERVE;
1466 if (sscanf(raw_args, " %c", &dummy) > 0)
1467 return FAILURE;
1468 op->op_args = NULL;
1469 result = NEED_REDRAW;
1470 } else if (eql(action, "fill")) {
1471 op->op = FILL;
1472 if (sscanf(raw_args, " %c", &dummy) > 0)
1473 return FAILURE;
1474 op->op_args = NULL;
1475 result = NEED_REDRAW;
1476 } else if (eql(action, "fill_preserve")) {
1477 op->op = FILL_PRESERVE;
1478 if (sscanf(raw_args, " %c", &dummy) > 0)
1479 return FAILURE;
1480 op->op_args = NULL;
1481 result = NEED_REDRAW;
1482 } else if (eql(action, "set_dash")) {
1483 char *next, *end;
1484 char data1[strlen(raw_args) + 1];
1485 int n, i;
1486 struct set_dash_args *args;
1488 strcpy(data1, raw_args);
1489 next = end = data1;
1490 n = -1;
1491 do {
1492 n++;
1493 next = end;
1494 strtod(next, &end);
1495 } while (next != end);
1496 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
1497 OOM_ABORT;
1498 op->op = SET_DASH;
1499 op->op_args = args;
1500 args->num_dashes = n;
1501 for (i = 0, next = data1; i < n; i++, next = end) {
1502 args->dashes[i] = strtod(next, &end);
1504 } else if (eql(action, "set_font_face")) {
1505 char slant[7 + 1]; /* "oblique" */
1506 char weight[6 + 1]; /* "normal" */
1507 int family_start, family_len;
1508 struct set_font_face_args *args;
1510 if (sscanf(raw_args, "%7s %6s %n%*s", slant, weight, &family_start) != 2)
1511 return FAILURE;
1512 family_len = strlen(raw_args + family_start) + 1;
1513 if ((args = malloc(sizeof(*args) + family_len * sizeof(args->family[0]))) == NULL)
1514 OOM_ABORT;
1515 op->op = SET_FONT_FACE;
1516 op->op_args = args;
1517 strncpy(args->family, raw_args + family_start, family_len);
1518 if (eql(slant, "normal"))
1519 args->slant = CAIRO_FONT_SLANT_NORMAL;
1520 else if (eql(slant, "italic"))
1521 args->slant = CAIRO_FONT_SLANT_ITALIC;
1522 else if (eql(slant, "oblique"))
1523 args->slant = CAIRO_FONT_SLANT_OBLIQUE;
1524 else
1525 return FAILURE;
1526 if (eql(weight, "normal"))
1527 args->weight = CAIRO_FONT_WEIGHT_NORMAL;
1528 else if (eql(weight, "bold"))
1529 args->weight = CAIRO_FONT_WEIGHT_BOLD;
1530 else
1531 return FAILURE;
1532 } else if (eql(action, "set_font_size")) {
1533 struct set_font_size_args *args;
1535 if ((args = malloc(sizeof(*args))) == NULL)
1536 OOM_ABORT;
1537 op->op = SET_FONT_SIZE;
1538 op->op_args = args;
1539 if (sscanf(raw_args, "%lf %c", &args->size, &dummy) != 1)
1540 return FAILURE;
1541 } else if (eql(action, "set_line_cap")) {
1542 char str[6 + 1]; /* "square" */
1543 struct set_line_cap_args *args;
1545 if ((args = malloc(sizeof(*args))) == NULL)
1546 OOM_ABORT;
1547 op->op = SET_LINE_CAP;
1548 op->op_args = args;
1549 if (sscanf(raw_args, "%6s %c", str, &dummy) != 1)
1550 return FAILURE;
1551 if (eql(str, "butt"))
1552 args->line_cap = CAIRO_LINE_CAP_BUTT;
1553 else if (eql(str, "round"))
1554 args->line_cap = CAIRO_LINE_CAP_ROUND;
1555 else if (eql(str, "square"))
1556 args->line_cap = CAIRO_LINE_CAP_SQUARE;
1557 else
1558 return FAILURE;
1559 } else if (eql(action, "set_line_join")) {
1560 char str[5 + 1]; /* "miter" */
1561 struct set_line_join_args *args;
1563 if ((args = malloc(sizeof(*args))) == NULL)
1564 OOM_ABORT;
1565 op->op = SET_LINE_JOIN;
1566 op->op_args = args;
1567 if (sscanf(raw_args, "%5s %c", str, &dummy) != 1)
1568 return FAILURE;
1569 if (eql(str, "miter"))
1570 args->line_join = CAIRO_LINE_JOIN_MITER;
1571 else if (eql(str, "round"))
1572 args->line_join = CAIRO_LINE_JOIN_ROUND;
1573 else if (eql(str, "bevel"))
1574 args->line_join = CAIRO_LINE_JOIN_BEVEL;
1575 else
1576 return FAILURE;
1577 } else if (eql(action, "set_line_width")) {
1578 struct set_line_width_args *args;
1580 if ((args = malloc(sizeof(*args))) == NULL)
1581 OOM_ABORT;
1582 op->op = SET_LINE_WIDTH;
1583 op->op_args = args;
1584 if (sscanf(raw_args, "%lf %c", &args->width, &dummy) != 1)
1585 return FAILURE;
1586 } else if (eql(action, "set_source_rgba")) {
1587 struct set_source_rgba_args *args;
1589 if ((args = malloc(sizeof(*args))) == NULL)
1590 OOM_ABORT;
1591 op->op = SET_SOURCE_RGBA;
1592 op->op_args = args;
1593 gdk_rgba_parse(&args->color, raw_args);
1594 } else if (eql(action, "transform")) {
1595 char dummy;
1596 double xx, yx, xy, yy, x0, y0;
1598 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1599 &xx, &yx, &xy, &yy, &x0, &y0, &dummy) == 6) {
1600 struct transform_args *args;
1602 if ((args = malloc(sizeof(*args))) == NULL)
1603 OOM_ABORT;
1604 op->op_args = args;
1605 op->op = TRANSFORM;
1606 cairo_matrix_init(&args->matrix, xx, yx, xy, yy, x0, y0);
1607 } else if (sscanf(raw_args, " %c", &dummy) < 1) {
1608 op->op = RESET_CTM;
1609 op->op_args = NULL;
1610 } else
1611 return FAILURE;
1612 } else if (eql(action, "translate")) {
1613 double tx, ty;
1614 struct transform_args *args;
1616 if ((args = malloc(sizeof(*args))) == NULL)
1617 OOM_ABORT;
1618 op->op = TRANSFORM;
1619 op->op_args = args;
1620 if (sscanf(raw_args, "%lf %lf %c", &tx, &ty, &dummy) != 2)
1621 return FAILURE;
1622 cairo_matrix_init_translate(&args->matrix, tx, ty);
1623 } else if (eql(action, "scale")) {
1624 double sx, sy;
1625 struct transform_args *args;
1627 if ((args = malloc(sizeof(*args))) == NULL)
1628 OOM_ABORT;
1629 op->op = TRANSFORM;
1630 op->op_args = args;
1631 if (sscanf(raw_args, "%lf %lf %c", &sx, &sy, &dummy) != 2)
1632 return FAILURE;
1633 cairo_matrix_init_scale(&args->matrix, sx, sy);
1634 } else if (eql(action, "rotate")) {
1635 double angle;
1636 struct transform_args *args;
1638 if ((args = malloc(sizeof(*args))) == NULL)
1639 OOM_ABORT;
1640 op->op = TRANSFORM;
1641 op->op_args = args;
1642 if (sscanf(raw_args, "%lf %c", &angle, &dummy) != 1)
1643 return FAILURE;
1644 cairo_matrix_init_rotate(&args->matrix, angle * (M_PI / 180.L));
1645 } else
1646 return FAILURE;
1647 return result;
1651 * Add another element to widget's "draw_ops" list
1653 static enum draw_op_stat
1654 ins_draw_op(GObject *widget, const char *action, const char *data)
1656 enum draw_op_stat result;
1657 struct draw_op *new_op = NULL, *draw_ops = NULL, *prev_op = NULL;
1659 if ((new_op = malloc(sizeof(*new_op))) == NULL)
1660 OOM_ABORT;
1661 new_op->op_args = NULL;
1662 new_op->next = NULL;
1663 if ((result = set_draw_op(new_op, action, data)) == FAILURE) {
1664 free(new_op->op_args);
1665 free(new_op);
1666 return FAILURE;
1668 switch (new_op->policy) {
1669 case APPEND:
1670 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL)
1671 g_object_set_data(widget, "draw_ops", new_op);
1672 else {
1673 for (prev_op = draw_ops;
1674 prev_op->next != NULL;
1675 prev_op = prev_op->next);
1676 prev_op->next = new_op;
1678 break;
1679 case BEFORE:
1680 for (prev_op = NULL, draw_ops = g_object_get_data(widget, "draw_ops");
1681 draw_ops != NULL && draw_ops->id != new_op->before;
1682 prev_op = draw_ops, draw_ops = draw_ops->next);
1683 if (prev_op == NULL) { /* prepend a new first element */
1684 g_object_set_data(widget, "draw_ops", new_op);
1685 new_op->next = draw_ops;
1686 } else if (draw_ops == NULL) /* append */
1687 prev_op->next = new_op;
1688 else { /* insert */
1689 new_op->next = draw_ops;
1690 prev_op->next = new_op;
1692 break;
1693 case REPLACE:
1694 for (prev_op = NULL, draw_ops = g_object_get_data(widget, "draw_ops");
1695 draw_ops != NULL && draw_ops->id != new_op->id;
1696 prev_op = draw_ops, draw_ops = draw_ops->next);
1697 if (draw_ops == NULL && prev_op == NULL) /* start a new list */
1698 g_object_set_data(widget, "draw_ops", new_op);
1699 else if (prev_op == NULL) { /* replace the first element */
1700 g_object_set_data(widget, "draw_ops", new_op);
1701 new_op->next = draw_ops->next;
1702 free(draw_ops->op_args);
1703 free(draw_ops);
1704 } else if (draw_ops == NULL) /* append */
1705 prev_op->next = new_op;
1706 else { /* replace some other element */
1707 new_op->next = draw_ops->next;
1708 prev_op->next = new_op;
1709 free(draw_ops->op_args);
1710 free(draw_ops);
1712 break;
1713 default:
1714 ABORT;
1715 break;
1717 return result;
1721 * Remove all elements with the given id from widget's "draw_ops" list
1723 static enum draw_op_stat
1724 rem_draw_op(GObject *widget, const char *data)
1726 char dummy;
1727 struct draw_op *op, *next_op, *prev_op = NULL;
1728 unsigned long long int id;
1730 if (sscanf(data, "%llu %c", &id, &dummy) != 1)
1731 return FAILURE;
1732 op = g_object_get_data(widget, "draw_ops");
1733 while (op != NULL) {
1734 next_op = op->next;
1735 if (op->id == id) {
1736 if (prev_op == NULL) /* list head */
1737 g_object_set_data(widget, "draw_ops", op->next);
1738 else
1739 prev_op->next = op->next;
1740 free(op->op_args);
1741 free(op);
1742 } else
1743 prev_op = op;
1744 op = next_op;
1746 return NEED_REDRAW;
1749 static gboolean
1750 refresh_widget(GtkWidget *widget)
1752 gint height = gtk_widget_get_allocated_height(widget);
1753 gint width = gtk_widget_get_allocated_width(widget);
1755 gtk_widget_queue_draw_area(widget, 0, 0, width, height);
1756 return G_SOURCE_REMOVE;
1759 static void
1760 update_drawing_area(GObject *obj, const char *action,
1761 const char *data, const char *whole_msg, GType type)
1763 enum draw_op_stat dos;
1765 if (eql(action, "remove"))
1766 dos = rem_draw_op(obj, data);
1767 else
1768 dos = ins_draw_op(obj, action, data);
1769 switch (dos) {
1770 case NEED_REDRAW:
1771 gdk_threads_add_idle_full(G_PRIORITY_LOW,
1772 (GSourceFunc) refresh_widget,
1773 GTK_WIDGET(obj), NULL);
1774 break;
1775 case FAILURE:
1776 ign_cmd(type, whole_msg);
1777 break;
1778 case SUCCESS:
1779 break;
1780 default:
1781 ABORT;
1782 break;
1786 static void
1787 update_entry(GObject *obj, const char *action,
1788 const char *data, const char *whole_msg, GType type)
1790 GtkEntry *entry = GTK_ENTRY(obj);
1792 if (eql(action, "set_text"))
1793 gtk_entry_set_text(entry, data);
1794 else if (eql(action, "set_placeholder_text"))
1795 gtk_entry_set_placeholder_text(entry, data);
1796 else
1797 ign_cmd(type, whole_msg);
1800 static void
1801 update_expander(GObject *obj, const char *action,
1802 const char *data, const char *whole_msg, GType type)
1804 GtkExpander *expander = GTK_EXPANDER(obj);
1805 char dummy;
1806 unsigned int val;
1808 if (eql(action, "set_expanded") &&
1809 sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
1810 gtk_expander_set_expanded(expander, val);
1811 else if (eql(action, "set_label"))
1812 gtk_expander_set_label(expander, data);
1813 else
1814 ign_cmd(type, whole_msg);
1817 static void
1818 update_file_chooser_button(GObject *obj, const char *action,
1819 const char *data, const char *whole_msg, GType type)
1821 if (eql(action, "set_filename"))
1822 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
1823 else
1824 ign_cmd(type, whole_msg);
1827 static void
1828 update_file_chooser_dialog(GObject *obj, const char *action,
1829 const char *data, const char *whole_msg, GType type)
1831 GtkFileChooser *chooser = GTK_FILE_CHOOSER(obj);
1833 if (eql(action, "set_filename"))
1834 gtk_file_chooser_set_filename(chooser, data);
1835 else if (eql(action, "set_current_name"))
1836 gtk_file_chooser_set_current_name(chooser, data);
1837 else if (update_class_window(obj, action, data, whole_msg, type));
1838 else
1839 ign_cmd(type, whole_msg);
1842 static void
1843 update_focus(GObject *obj, const char *action,
1844 const char *data, const char *whole_msg, GType type)
1846 char dummy;
1848 (void) action;
1849 (void) data;
1850 if (sscanf(data, " %c", &dummy) < 1 &&
1851 gtk_widget_get_can_focus(GTK_WIDGET(obj)))
1852 gtk_widget_grab_focus(GTK_WIDGET(obj));
1853 else
1854 ign_cmd(type, whole_msg);
1857 static void
1858 update_font_button(GObject *obj, const char *action,
1859 const char *data, const char *whole_msg, GType type)
1861 GtkFontButton *font_button = GTK_FONT_BUTTON(obj);
1863 if (eql(action, "set_font_name"))
1864 gtk_font_button_set_font_name(font_button, data);
1865 else
1866 ign_cmd(type, whole_msg);
1869 static void
1870 update_frame(GObject *obj, const char *action,
1871 const char *data, const char *whole_msg, GType type)
1873 if (eql(action, "set_label"))
1874 gtk_frame_set_label(GTK_FRAME(obj), data);
1875 else
1876 ign_cmd(type, whole_msg);
1879 static void
1880 update_image(GObject *obj, const char *action,
1881 const char *data, const char *whole_msg, GType type)
1883 GtkIconSize size;
1884 GtkImage *image = GTK_IMAGE(obj);
1886 gtk_image_get_icon_name(image, NULL, &size);
1887 if (eql(action, "set_from_file"))
1888 gtk_image_set_from_file(image, data);
1889 else if (eql(action, "set_from_icon_name"))
1890 gtk_image_set_from_icon_name(image, data, size);
1891 else
1892 ign_cmd(type, whole_msg);
1895 static void
1896 update_label(GObject *obj, const char *action,
1897 const char *data, const char *whole_msg, GType type)
1899 if (eql(action, "set_text"))
1900 gtk_label_set_text(GTK_LABEL(obj), data);
1901 else
1902 ign_cmd(type, whole_msg);
1905 static void
1906 update_notebook(GObject *obj, const char *action,
1907 const char *data, const char *whole_msg, GType type)
1909 char dummy;
1910 unsigned int val;
1912 if (eql(action, "set_current_page") &&
1913 sscanf(data, "%u %c", &val, &dummy) == 1)
1914 gtk_notebook_set_current_page(GTK_NOTEBOOK(obj), val);
1915 else
1916 ign_cmd(type, whole_msg);
1919 static void
1920 update_nothing(GObject *obj, const char *action,
1921 const char *data, const char *whole_msg, GType type)
1923 (void) obj;
1924 (void) action;
1925 (void) data;
1926 (void) whole_msg;
1927 (void) type;
1930 static void
1931 update_print_dialog(GObject *obj, const char *action,
1932 const char *data, const char *whole_msg, GType type)
1934 GtkPageSetup *page_setup;
1935 GtkPrintJob *job;
1936 GtkPrintSettings *settings;
1937 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(obj);
1938 GtkPrinter *printer;
1939 gint response_id;
1941 if (eql(action, "print")) {
1942 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
1943 switch (response_id) {
1944 case GTK_RESPONSE_OK:
1945 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
1946 settings = gtk_print_unix_dialog_get_settings(dialog);
1947 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
1948 job = gtk_print_job_new(data, printer, settings, page_setup);
1949 if (gtk_print_job_set_source_file(job, data, NULL))
1950 gtk_print_job_send(job, NULL, NULL, NULL);
1951 else
1952 ign_cmd(type, whole_msg);
1953 g_clear_object(&settings);
1954 g_clear_object(&job);
1955 break;
1956 case GTK_RESPONSE_CANCEL:
1957 case GTK_RESPONSE_DELETE_EVENT:
1958 break;
1959 default:
1960 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
1961 widget_name(GTK_BUILDABLE(dialog)), response_id);
1962 break;
1964 gtk_widget_hide(GTK_WIDGET(dialog));
1965 } else
1966 ign_cmd(type, whole_msg);
1969 static void
1970 update_progress_bar(GObject *obj, const char *action,
1971 const char *data, const char *whole_msg, GType type)
1973 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(obj);
1974 char dummy;
1975 double frac;
1977 if (eql(action, "set_text"))
1978 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
1979 else if (eql(action, "set_fraction") &&
1980 sscanf(data, "%lf %c", &frac, &dummy) == 1)
1981 gtk_progress_bar_set_fraction(progressbar, frac);
1982 else
1983 ign_cmd(type, whole_msg);
1986 static void
1987 update_scale(GObject *obj, const char *action,
1988 const char *data, const char *whole_msg, GType type)
1990 GtkRange *range = GTK_RANGE(obj);
1991 char dummy;
1992 double val1, val2;
1994 if (eql(action, "set_value") && sscanf(data, "%lf %c", &val1, &dummy) == 1)
1995 gtk_range_set_value(range, val1);
1996 else if (eql(action, "set_fill_level") &&
1997 sscanf(data, "%lf %c", &val1, &dummy) == 1) {
1998 gtk_range_set_fill_level(range, val1);
1999 gtk_range_set_show_fill_level(range, TRUE);
2000 } else if (eql(action, "set_fill_level") &&
2001 sscanf(data, " %c", &dummy) < 1)
2002 gtk_range_set_show_fill_level(range, FALSE);
2003 else if (eql(action, "set_range") &&
2004 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2005 gtk_range_set_range(range, val1, val2);
2006 else if (eql(action, "set_increments") &&
2007 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2008 gtk_range_set_increments(range, val1, val2);
2009 else
2010 ign_cmd(type, whole_msg);
2013 static void
2014 update_scrolled_window(GObject *obj, const char *action,
2015 const char *data, const char *whole_msg, GType type)
2017 GtkScrolledWindow *window = GTK_SCROLLED_WINDOW(obj);
2018 GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(window);
2019 GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(window);
2020 char dummy;
2021 double d0, d1;
2023 if (eql(action, "hscroll") && sscanf(data, "%lf %c", &d0, &dummy) == 1)
2024 gtk_adjustment_set_value(hadj, d0);
2025 else if (eql(action, "vscroll") && sscanf(data, "%lf %c", &d0, &dummy) == 1)
2026 gtk_adjustment_set_value(vadj, d0);
2027 else if (eql(action, "hscroll_to_range") &&
2028 sscanf(data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
2029 gtk_adjustment_clamp_page(hadj, d0, d1);
2030 else if (eql(action, "vscroll_to_range") &&
2031 sscanf(data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
2032 gtk_adjustment_clamp_page(vadj, d0, d1);
2033 else
2034 ign_cmd(type, whole_msg);
2037 static void
2038 update_sensitivity(GObject *obj, const char *action,
2039 const char *data, const char *whole_msg, GType type)
2041 char dummy;
2042 unsigned int val;
2044 (void) action;
2045 (void) whole_msg;
2046 (void) type;
2047 if (sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2048 gtk_widget_set_sensitive(GTK_WIDGET(obj), val);
2049 else
2050 ign_cmd(type, whole_msg);
2053 static void
2054 update_size_request(GObject *obj, const char *action,
2055 const char *data, const char *whole_msg, GType type)
2057 char dummy;
2058 int x, y;
2060 (void) action;
2061 (void) whole_msg;
2062 (void) type;
2063 if (sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
2064 gtk_widget_set_size_request(GTK_WIDGET(obj), x, y);
2065 else if (sscanf(data, " %c", &dummy) < 1)
2066 gtk_widget_set_size_request(GTK_WIDGET(obj), -1, -1);
2067 else
2068 ign_cmd(type, whole_msg);
2071 static void
2072 update_socket(GObject *obj, const char *action,
2073 const char *data, const char *whole_msg, GType type)
2075 GtkSocket *socket = GTK_SOCKET(obj);
2076 Window id;
2077 char str[BUFLEN], dummy;
2079 (void) data;
2080 if (eql(action, "id") && sscanf(data, " %c", &dummy) < 1) {
2081 id = gtk_socket_get_id(socket);
2082 snprintf(str, BUFLEN, "%lu", id);
2083 send_msg(GTK_BUILDABLE(socket), "id", str, NULL);
2084 } else
2085 ign_cmd(type, whole_msg);
2088 static void
2089 update_spin_button(GObject *obj, const char *action,
2090 const char *data, const char *whole_msg, GType type)
2092 GtkSpinButton *spinbutton = GTK_SPIN_BUTTON(obj);
2093 char dummy;
2094 double val1, val2;
2096 if (eql(action, "set_text") && /* TODO: rename to "set_value" */
2097 sscanf(data, "%lf %c", &val1, &dummy) == 1)
2098 gtk_spin_button_set_value(spinbutton, val1);
2099 else if (eql(action, "set_range") &&
2100 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2101 gtk_spin_button_set_range(spinbutton, val1, val2);
2102 else if (eql(action, "set_increments") &&
2103 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2104 gtk_spin_button_set_increments(spinbutton, val1, val2);
2105 else
2106 ign_cmd(type, whole_msg);
2109 static void
2110 update_spinner(GObject *obj, const char *action,
2111 const char *data, const char *whole_msg, GType type)
2113 GtkSpinner *spinner = GTK_SPINNER(obj);
2114 char dummy;
2116 (void) data;
2117 if (eql(action, "start") && sscanf(data, " %c", &dummy) < 1)
2118 gtk_spinner_start(spinner);
2119 else if (eql(action, "stop") && sscanf(data, " %c", &dummy) < 1)
2120 gtk_spinner_stop(spinner);
2121 else
2122 ign_cmd(type, whole_msg);
2125 static void
2126 update_statusbar(GObject *obj, const char *action,
2127 const char *data, const char *whole_msg, GType type)
2129 GtkStatusbar *statusbar = GTK_STATUSBAR(obj);
2130 char *ctx_msg, dummy;
2131 const char *msg;
2132 int ctx_len, t;
2134 /* TODO: remove "push", "pop", "remove_all"; rename "push_id" to "push", etc. */
2135 if ((ctx_msg = malloc(strlen(data) + 1)) == NULL)
2136 OOM_ABORT;
2137 t = sscanf(data, "%s %n%c", ctx_msg, &ctx_len, &dummy);
2138 msg = data + ctx_len;
2139 if (eql(action, "push"))
2140 gtk_statusbar_push(statusbar,
2141 gtk_statusbar_get_context_id(statusbar, "0"),
2142 data);
2143 else if (eql(action, "push_id") && t >= 1)
2144 gtk_statusbar_push(statusbar,
2145 gtk_statusbar_get_context_id(statusbar, ctx_msg),
2146 msg);
2147 else if (eql(action, "pop") && t < 1)
2148 gtk_statusbar_pop(statusbar,
2149 gtk_statusbar_get_context_id(statusbar, "0"));
2150 else if (eql(action, "pop_id") && t == 1)
2151 gtk_statusbar_pop(statusbar,
2152 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2153 else if (eql(action, "remove_all") && t < 1)
2154 gtk_statusbar_remove_all(statusbar,
2155 gtk_statusbar_get_context_id(statusbar, "0"));
2156 else if (eql(action, "remove_all_id") && t == 1)
2157 gtk_statusbar_remove_all(statusbar,
2158 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2159 else
2160 ign_cmd(type, whole_msg);
2161 free(ctx_msg);
2164 static void
2165 update_switch(GObject *obj, const char *action,
2166 const char *data, const char *whole_msg, GType type)
2168 char dummy;
2169 unsigned int val;
2171 if (eql(action, "set_active") &&
2172 sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2173 gtk_switch_set_active(GTK_SWITCH(obj), val);
2174 else
2175 ign_cmd(type, whole_msg);
2178 static void
2179 update_text_view(GObject *obj, const char *action,
2180 const char *data, const char *whole_msg, GType type)
2182 GtkTextView *view = GTK_TEXT_VIEW(obj);
2183 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
2184 GtkTextIter a, b;
2185 char dummy;
2186 int val;
2188 if (eql(action, "set_text"))
2189 gtk_text_buffer_set_text(textbuf, data, -1);
2190 else if (eql(action, "delete") && sscanf(data, " %c", &dummy) < 1) {
2191 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2192 gtk_text_buffer_delete(textbuf, &a, &b);
2193 } else if (eql(action, "insert_at_cursor"))
2194 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
2195 else if (eql(action, "place_cursor") && eql(data, "end")) {
2196 gtk_text_buffer_get_end_iter(textbuf, &a);
2197 gtk_text_buffer_place_cursor(textbuf, &a);
2198 } else if (eql(action, "place_cursor") &&
2199 sscanf(data, "%d %c", &val, &dummy) == 1) {
2200 gtk_text_buffer_get_iter_at_offset(textbuf, &a, val);
2201 gtk_text_buffer_place_cursor(textbuf, &a);
2202 } else if (eql(action, "place_cursor_at_line") &&
2203 sscanf(data, "%d %c", &val, &dummy) == 1) {
2204 gtk_text_buffer_get_iter_at_line(textbuf, &a, val);
2205 gtk_text_buffer_place_cursor(textbuf, &a);
2206 } else if (eql(action, "scroll_to_cursor") &&
2207 sscanf(data, " %c", &dummy) < 1)
2208 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
2209 0., 0, 0., 0.);
2210 else if (eql(action, "save") && data != NULL &&
2211 (save = fopen(data, "w")) != NULL) {
2212 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2213 save_msg(GTK_BUILDABLE(view), "insert_at_cursor",
2214 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
2215 fclose(save);
2216 } else
2217 ign_cmd(type, whole_msg);
2220 static void
2221 update_toggle_button(GObject *obj, const char *action,
2222 const char *data, const char *whole_msg, GType type)
2224 char dummy;
2225 unsigned int val;
2227 if (eql(action, "set_label"))
2228 gtk_button_set_label(GTK_BUTTON(obj), data);
2229 else if (eql(action, "set_active") &&
2230 sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2231 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), val);
2232 else
2233 ign_cmd(type, whole_msg);
2236 static void
2237 update_tooltip_text(GObject *obj, const char *action,
2238 const char *data, const char *whole_msg, GType type)
2240 (void) action;
2241 (void) whole_msg;
2242 (void) type;
2243 gtk_widget_set_tooltip_text(GTK_WIDGET(obj), data);
2247 * update_tree_view() needs a few helper functions
2251 * Check if s is a valid string representation of a GtkTreePath
2253 static bool
2254 is_path_string(char *s)
2256 return s != NULL &&
2257 strlen(s) == strspn(s, ":0123456789") &&
2258 strstr(s, "::") == NULL &&
2259 strcspn(s, ":") > 0;
2262 static void
2263 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
2264 GtkTreeIter *parent, GtkTreeIter *sibling)
2266 if (GTK_IS_TREE_STORE(model))
2267 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
2268 iter, parent, sibling);
2269 else if (GTK_IS_LIST_STORE(model))
2270 gtk_list_store_insert_before(GTK_LIST_STORE(model),
2271 iter, sibling);
2272 else
2273 ABORT;
2276 static void
2277 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
2278 GtkTreeIter *parent, GtkTreeIter *sibling)
2280 if (GTK_IS_TREE_STORE(model))
2281 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
2282 iter, parent, sibling);
2283 else if (GTK_IS_LIST_STORE(model))
2284 gtk_list_store_insert_after(GTK_LIST_STORE(model),
2285 iter, sibling);
2286 else
2287 ABORT;
2290 static void
2291 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
2292 GtkTreeIter *position)
2294 if (GTK_IS_TREE_STORE(model))
2295 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
2296 else if (GTK_IS_LIST_STORE(model))
2297 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
2298 else
2299 ABORT;
2302 static void
2303 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
2305 if (GTK_IS_TREE_STORE(model))
2306 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
2307 else if (GTK_IS_LIST_STORE(model))
2308 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
2309 else
2310 ABORT;
2313 static void
2314 tree_model_clear(GtkTreeModel *model)
2316 if (GTK_IS_TREE_STORE(model))
2317 gtk_tree_store_clear(GTK_TREE_STORE(model));
2318 else if (GTK_IS_LIST_STORE(model))
2319 gtk_list_store_clear(GTK_LIST_STORE(model));
2320 else
2321 ABORT;
2324 static void
2325 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
2327 va_list ap;
2329 va_start(ap, iter);
2330 if (GTK_IS_TREE_STORE(model))
2331 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
2332 else if (GTK_IS_LIST_STORE(model))
2333 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
2334 else
2335 ABORT;
2336 va_end(ap);
2340 * Create an empty row at path if it doesn't yet exist. Create older
2341 * siblings and parents as necessary.
2343 static void
2344 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
2346 GtkTreeIter iter_1; /* iter's predecessor */
2347 GtkTreePath *path_1; /* path's predecessor */
2349 if (gtk_tree_model_get_iter(model, iter, path))
2350 return;
2351 path_1 = gtk_tree_path_copy(path);
2352 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
2353 create_subtree(model, path_1, iter);
2354 iter_1 = *iter;
2355 tree_model_insert_after(model, iter, NULL, &iter_1);
2356 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
2357 create_subtree(model, path_1, iter);
2358 if (gtk_tree_path_get_depth(path_1) == 0)
2359 /* first toplevel row */
2360 tree_model_insert_after(model, iter, NULL, NULL);
2361 else { /* first row in a lower level */
2362 iter_1 = *iter;
2363 tree_model_insert_after(model, iter, &iter_1, NULL);
2365 } /* neither prev nor up mean we're at the root of an empty tree */
2366 gtk_tree_path_free(path_1);
2369 static bool
2370 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
2371 const char *path_s, int col, const char *new_text)
2373 GType col_type = gtk_tree_model_get_column_type(model, col);
2374 GtkTreePath *path;
2375 bool ok = false;
2376 char *endptr;
2377 double d;
2378 long long int n;
2380 path = gtk_tree_path_new_from_string(path_s);
2381 create_subtree(model, path, iter);
2382 gtk_tree_path_free(path);
2383 switch (col_type) {
2384 case G_TYPE_BOOLEAN:
2385 case G_TYPE_INT:
2386 case G_TYPE_LONG:
2387 case G_TYPE_INT64:
2388 case G_TYPE_UINT:
2389 case G_TYPE_ULONG:
2390 case G_TYPE_UINT64:
2391 errno = 0;
2392 endptr = NULL;
2393 n = strtoll(new_text, &endptr, 10);
2394 if (!errno && endptr != new_text) {
2395 tree_model_set(model, iter, col, n, -1);
2396 ok = true;
2398 break;
2399 case G_TYPE_FLOAT:
2400 case G_TYPE_DOUBLE:
2401 errno = 0;
2402 endptr = NULL;
2403 d = strtod(new_text, &endptr);
2404 if (!errno && endptr != new_text) {
2405 tree_model_set(model, iter, col, d, -1);
2406 ok = true;
2408 break;
2409 case G_TYPE_STRING:
2410 tree_model_set(model, iter, col, new_text, -1);
2411 ok = true;
2412 break;
2413 default:
2414 fprintf(stderr, "column %d: %s not implemented\n",
2415 col, g_type_name(col_type));
2416 ok = true;
2417 break;
2419 return ok;
2422 static void
2423 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
2425 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
2426 /* is just fine and this function need not exist. */
2427 if (path == NULL)
2428 path = gtk_tree_path_new();
2429 gtk_tree_view_set_cursor(view, path, col, false);
2432 static void
2433 update_tree_view(GObject *obj, const char *action,
2434 const char *data, const char *whole_msg, GType type)
2436 GtkTreeView *view = GTK_TREE_VIEW(obj);
2437 GtkTreeIter iter0, iter1;
2438 GtkTreeModel *model = gtk_tree_view_get_model(view);
2439 GtkTreePath *path = NULL;
2440 bool iter0_valid, iter1_valid;
2441 char *tokens, *arg0, *arg1, *arg2;
2442 int col = -1; /* invalid column number */
2444 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
2446 fprintf(stderr, "missing model/");
2447 ign_cmd(type, whole_msg);
2448 return;
2450 if ((tokens = malloc(strlen(data) + 1)) == NULL)
2451 OOM_ABORT;
2452 strcpy(tokens, data);
2453 arg0 = strtok(tokens, WHITESPACE);
2454 arg1 = strtok(NULL, WHITESPACE);
2455 arg2 = strtok(NULL, "");
2456 iter0_valid = is_path_string(arg0) &&
2457 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
2458 iter1_valid = is_path_string(arg1) &&
2459 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
2460 if (is_path_string(arg1))
2461 col = strtol(arg1, NULL, 10);
2462 if (eql(action, "set") &&
2463 col > -1 &&
2464 col < gtk_tree_model_get_n_columns(model) &&
2465 is_path_string(arg0)) {
2466 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
2467 ign_cmd(type, whole_msg);
2468 } else if (eql(action, "scroll") && iter0_valid && iter1_valid &&
2469 arg2 == NULL) {
2470 path = gtk_tree_path_new_from_string(arg0);
2471 gtk_tree_view_scroll_to_cell (view,
2472 path,
2473 gtk_tree_view_get_column(view, col),
2474 0, 0., 0.);
2475 } else if (eql(action, "expand") && iter0_valid && arg1 == NULL) {
2476 path = gtk_tree_path_new_from_string(arg0);
2477 gtk_tree_view_expand_row(view, path, false);
2478 } else if (eql(action, "expand_all") && iter0_valid && arg1 == NULL) {
2479 path = gtk_tree_path_new_from_string(arg0);
2480 gtk_tree_view_expand_row(view, path, true);
2481 } else if (eql(action, "expand_all") && arg0 == NULL)
2482 gtk_tree_view_expand_all(view);
2483 else if (eql(action, "collapse") && iter0_valid && arg1 == NULL) {
2484 path = gtk_tree_path_new_from_string(arg0);
2485 gtk_tree_view_collapse_row(view, path);
2486 } else if (eql(action, "collapse") && arg0 == NULL)
2487 gtk_tree_view_collapse_all(view);
2488 else if (eql(action, "set_cursor") && iter0_valid && arg1 == NULL) {
2489 path = gtk_tree_path_new_from_string(arg0);
2490 tree_view_set_cursor(view, path, NULL);
2491 } else if (eql(action, "set_cursor") && arg0 == NULL) {
2492 tree_view_set_cursor(view, NULL, NULL);
2493 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2494 } else if (eql(action, "insert_row") &&
2495 eql(arg0, "end") && arg1 == NULL)
2496 tree_model_insert_before(model, &iter1, NULL, NULL);
2497 else if (eql(action, "insert_row") && iter0_valid &&
2498 eql(arg1, "as_child") && arg2 == NULL)
2499 tree_model_insert_after(model, &iter1, &iter0, NULL);
2500 else if (eql(action, "insert_row") && iter0_valid && arg1 == NULL)
2501 tree_model_insert_before(model, &iter1, NULL, &iter0);
2502 else if (eql(action, "move_row") && iter0_valid &&
2503 eql(arg1, "end") && arg2 == NULL)
2504 tree_model_move_before(model, &iter0, NULL);
2505 else if (eql(action, "move_row") && iter0_valid && iter1_valid && arg2 == NULL)
2506 tree_model_move_before(model, &iter0, &iter1);
2507 else if (eql(action, "remove_row") && iter0_valid && arg1 == NULL)
2508 tree_model_remove(model, &iter0);
2509 else if (eql(action, "clear") && arg0 == NULL) {
2510 tree_view_set_cursor(view, NULL, NULL);
2511 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2512 tree_model_clear(model);
2513 } else if (eql(action, "save") && arg0 != NULL &&
2514 (save = fopen(arg0, "w")) != NULL) {
2515 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc) save_tree_row_msg, view);
2516 fclose(save);
2517 } else
2518 ign_cmd(type, whole_msg);
2519 free(tokens);
2520 gtk_tree_path_free(path);
2523 static void
2524 update_visibility(GObject *obj, const char *action,
2525 const char *data, const char *whole_msg, GType type)
2527 char dummy;
2528 unsigned int val;
2530 (void) action;
2531 (void) whole_msg;
2532 (void) type;
2533 if (sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2534 gtk_widget_set_visible(GTK_WIDGET(obj), val);
2535 else
2536 ign_cmd(type, whole_msg);
2540 * Change the style of the widget passed
2542 static void
2543 update_widget_style(GObject *obj, const char *name,
2544 const char *data, const char *whole_msg, GType type)
2546 GtkStyleContext *context;
2547 GtkStyleProvider *style_provider;
2548 char *style_decl;
2549 const char *prefix = "* {", *suffix = "}";
2550 size_t sz;
2552 (void) name;
2553 (void) whole_msg;
2554 (void) type;
2555 style_provider = g_object_get_data(obj, "style_provider");
2556 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
2557 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2558 gtk_style_context_remove_provider(context, style_provider);
2559 if ((style_decl = malloc(sz)) == NULL)
2560 OOM_ABORT;
2561 strcpy(style_decl, prefix);
2562 strcat(style_decl, data);
2563 strcat(style_decl, suffix);
2564 gtk_style_context_add_provider(context, style_provider,
2565 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2566 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
2567 style_decl, -1, NULL);
2568 free(style_decl);
2571 static void
2572 update_window(GObject *obj, const char *action,
2573 const char *data, const char *whole_msg, GType type)
2575 if (!update_class_window(obj, action, data, whole_msg, type))
2576 ign_cmd(type, whole_msg);
2580 * Simulate user activity on various widgets
2582 static void
2583 fake_ui_activity(GObject *obj, const char *action,
2584 const char *data, const char *whole_msg, GType type)
2586 char dummy;
2588 (void) action;
2589 (void) data;
2590 if (!GTK_IS_WIDGET(obj) || sscanf(data, " %c", &dummy) > 0)
2591 ign_cmd(type, whole_msg);
2592 else if (GTK_IS_SPIN_BUTTON(obj))
2593 cb_spin_button(GTK_BUILDABLE(obj), "text"); /* TODO: rename to "value" */
2594 else if (GTK_IS_SCALE(obj))
2595 cb_range(GTK_BUILDABLE(obj), "value");
2596 else if (GTK_IS_ENTRY(obj))
2597 cb_editable(GTK_BUILDABLE(obj), "text");
2598 else if (GTK_IS_CALENDAR(obj))
2599 cb_calendar(GTK_BUILDABLE(obj), "clicked");
2600 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
2601 cb_file_chooser_button(GTK_BUILDABLE(obj), "file");
2602 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
2603 ign_cmd(type, whole_msg);
2607 * The final UI update
2609 static void
2610 main_quit(GObject *obj, const char *action,
2611 const char *data, const char *whole_msg, GType type)
2613 char dummy;
2615 (void) obj;
2616 (void) action;
2617 (void) data;
2618 (void) whole_msg;
2619 (void) type;
2620 if (sscanf(data, " %c", &dummy) < 1)
2621 gtk_main_quit();
2622 else
2623 ign_cmd(type, whole_msg);
2627 * Don't update anything; just complain
2629 static void
2630 complain(GObject *obj, const char *action,
2631 const char *data, const char *whole_msg, GType type)
2633 (void) obj;
2634 (void) action;
2635 (void) data;
2636 ign_cmd(type, whole_msg);
2640 * Data to be passed to and from the GTK main loop
2642 struct ui_data {
2643 void (*fn)(GObject *, const char *action,
2644 const char *data, const char *msg, GType type);
2645 GObject *obj;
2646 char *action;
2647 char *data;
2648 char *msg;
2649 char *msg_tokens;
2650 GType type;
2654 * Parse command pointed to by ud, and act on ui accordingly. Runs
2655 * once per command inside gtk_main_loop().
2657 static gboolean
2658 update_ui(struct ui_data *ud)
2660 char *lc = lc_numeric();
2662 (ud->fn)(ud->obj, ud->action, ud->data, ud->msg, ud->type);
2663 free(ud->msg_tokens);
2664 free(ud->msg);
2665 free(ud);
2666 lc_numeric_free(lc);
2667 return G_SOURCE_REMOVE;
2671 * Keep track of loading files to avoid recursive loading of the same
2672 * file. If filename = NULL, forget the most recently remembered file.
2674 static bool
2675 remember_loading_file(char *filename)
2677 static char *filenames[BUFLEN];
2678 static size_t latest = 0;
2679 size_t i;
2681 if (filename == NULL) { /* pop */
2682 if (latest < 1)
2683 ABORT;
2684 latest--;
2685 return false;
2686 } else { /* push */
2687 for (i = 1; i <= latest; i++)
2688 if (eql(filename, filenames[i]))
2689 return false;
2690 if (latest > BUFLEN -2)
2691 return false;
2692 filenames[++latest] = filename;
2693 return true;
2698 * Read lines from stream cmd and perform appropriate actions on the
2699 * GUI
2701 static void *
2702 digest_msg(FILE *cmd)
2704 FILE *load; /* restoring user data */
2705 char *name;
2706 static int recursion = -1; /* > 0 means this is a recursive call */
2708 recursion++;
2709 for (;;) {
2710 struct ui_data *ud;
2711 char first_char = '\0';
2712 size_t msg_size = 32;
2713 int name_start = 0, name_end = 0;
2714 int action_start = 0, action_end = 0;
2715 int data_start;
2717 if (feof(cmd))
2718 break;
2719 if ((ud = malloc(sizeof(*ud))) == NULL)
2720 OOM_ABORT;
2721 if ((ud->msg = malloc(msg_size)) == NULL)
2722 OOM_ABORT;
2723 ud->type = G_TYPE_INVALID;
2724 pthread_testcancel();
2725 if (recursion == 0)
2726 log_msg(NULL);
2727 read_buf(cmd, &ud->msg, &msg_size);
2728 if (recursion == 0)
2729 log_msg(ud->msg);
2730 data_start = strlen(ud->msg);
2731 if ((ud->msg_tokens = malloc(strlen(ud->msg) + 1)) == NULL)
2732 OOM_ABORT;
2733 strcpy(ud->msg_tokens, ud->msg);
2734 sscanf(ud->msg, " %c", &first_char);
2735 if (strlen(ud->msg) == 0 || first_char == '#') { /* comment */
2736 ud->fn = update_nothing;
2737 goto exec;
2739 sscanf(ud->msg_tokens,
2740 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2741 &name_start, &name_end, &action_start, &action_end, &data_start);
2742 ud->msg_tokens[name_end] = ud->msg_tokens[action_end] = '\0';
2743 name = ud->msg_tokens + name_start;
2744 ud->action = ud->msg_tokens + action_start;
2745 ud->data = ud->msg_tokens + data_start;
2746 if (eql(ud->action, "main_quit")) {
2747 ud->fn = main_quit;
2748 goto exec;
2750 if (eql(ud->action, "load") && strlen(ud->data) > 0 &&
2751 (load = fopen(ud->data, "r")) != NULL &&
2752 remember_loading_file(ud->data)) {
2753 digest_msg(load);
2754 fclose(load);
2755 remember_loading_file(NULL);
2756 ud->fn = update_nothing;
2757 goto exec;
2759 if ((ud->obj = (gtk_builder_get_object(builder, name))) == NULL) {
2760 ud->fn = complain;
2761 goto exec;
2763 ud->type = G_TYPE_FROM_INSTANCE(ud->obj);
2764 if (eql(ud->action, "force"))
2765 ud->fn = fake_ui_activity;
2766 else if (eql(ud->action, "set_sensitive"))
2767 ud->fn = update_sensitivity;
2768 else if (eql(ud->action, "set_visible"))
2769 ud->fn = update_visibility;
2770 else if (eql(ud->action, "set_size_request"))
2771 ud->fn = update_size_request;
2772 else if (eql(ud->action, "set_tooltip_text"))
2773 ud->fn = update_tooltip_text;
2774 else if (eql(ud->action, "grab_focus"))
2775 ud->fn = update_focus;
2776 else if (eql(ud->action, "style")) {
2777 ud->action = name;
2778 ud->fn = update_widget_style;
2779 } else if (ud->type == GTK_TYPE_DRAWING_AREA)
2780 ud->fn = update_drawing_area;
2781 else if (ud->type == GTK_TYPE_TREE_VIEW)
2782 ud->fn = update_tree_view;
2783 else if (ud->type == GTK_TYPE_COMBO_BOX_TEXT)
2784 ud->fn = update_combo_box_text;
2785 else if (ud->type == GTK_TYPE_LABEL)
2786 ud->fn = update_label;
2787 else if (ud->type == GTK_TYPE_IMAGE)
2788 ud->fn = update_image;
2789 else if (ud->type == GTK_TYPE_TEXT_VIEW)
2790 ud->fn = update_text_view;
2791 else if (ud->type == GTK_TYPE_NOTEBOOK)
2792 ud->fn = update_notebook;
2793 else if (ud->type == GTK_TYPE_EXPANDER)
2794 ud->fn = update_expander;
2795 else if (ud->type == GTK_TYPE_FRAME)
2796 ud->fn = update_frame;
2797 else if (ud->type == GTK_TYPE_SCROLLED_WINDOW)
2798 ud->fn = update_scrolled_window;
2799 else if (ud->type == GTK_TYPE_BUTTON)
2800 ud->fn = update_button;
2801 else if (ud->type == GTK_TYPE_FILE_CHOOSER_DIALOG)
2802 ud->fn = update_file_chooser_dialog;
2803 else if (ud->type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2804 ud->fn = update_file_chooser_button;
2805 else if (ud->type == GTK_TYPE_COLOR_BUTTON)
2806 ud->fn = update_color_button;
2807 else if (ud->type == GTK_TYPE_FONT_BUTTON)
2808 ud->fn = update_font_button;
2809 else if (ud->type == GTK_TYPE_PRINT_UNIX_DIALOG)
2810 ud->fn = update_print_dialog;
2811 else if (ud->type == GTK_TYPE_SWITCH)
2812 ud->fn = update_switch;
2813 else if (ud->type == GTK_TYPE_TOGGLE_BUTTON ||
2814 ud->type == GTK_TYPE_RADIO_BUTTON ||
2815 ud->type == GTK_TYPE_CHECK_BUTTON)
2816 ud->fn = update_toggle_button;
2817 else if (ud->type == GTK_TYPE_ENTRY)
2818 ud->fn = update_entry;
2819 else if (ud->type == GTK_TYPE_SPIN_BUTTON)
2820 ud->fn = update_spin_button;
2821 else if (ud->type == GTK_TYPE_SCALE)
2822 ud->fn = update_scale;
2823 else if (ud->type == GTK_TYPE_PROGRESS_BAR)
2824 ud->fn = update_progress_bar;
2825 else if (ud->type == GTK_TYPE_SPINNER)
2826 ud->fn = update_spinner;
2827 else if (ud->type == GTK_TYPE_STATUSBAR)
2828 ud->fn = update_statusbar;
2829 else if (ud->type == GTK_TYPE_CALENDAR)
2830 ud->fn = update_calendar;
2831 else if (ud->type == GTK_TYPE_SOCKET)
2832 ud->fn = update_socket;
2833 else if (ud->type == GTK_TYPE_WINDOW ||
2834 ud->type == GTK_TYPE_DIALOG)
2835 ud->fn = update_window;
2836 else
2837 ud->fn = complain;
2838 exec:
2839 pthread_testcancel();
2840 gdk_threads_add_timeout(0, (GSourceFunc) update_ui, ud);
2842 recursion--;
2843 return NULL;
2848 * ============================================================
2849 * Initialization
2850 * ============================================================
2854 * Attach to renderer key "col_number". Associate "col_number" with
2855 * the corresponding column number in the underlying model.
2856 * Due to what looks like a gap in the GTK API, renderer id and column
2857 * number are taken directly from the XML .ui file.
2859 static bool
2860 tree_view_column_get_renderer_column(const char *ui_file, GtkTreeViewColumn *t_col,
2861 int n, GtkCellRenderer **renderer)
2863 bool r = false;
2864 char *xpath_base1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
2865 char *xpath_base2 = "\"]/child[";
2866 char *xpath_base3 = "]/object[@class=\"GtkCellRendererText\""
2867 " or @class=\"GtkCellRendererToggle\"]/";
2868 char *xpath_renderer_id = "/@id";
2869 char *xpath_text_col = "../attributes/attribute[@name=\"text\""
2870 " or @name=\"active\"]";
2871 const char *xpath_id = widget_name(GTK_BUILDABLE(t_col));
2872 int i;
2873 size_t xpath_len;
2874 size_t xpath_n_len = 3; /* Big Enough (TM) */
2875 xmlChar *xpath, *renderer_name = NULL, *m_col_s = NULL;
2876 xmlDocPtr doc;
2877 xmlNodePtr cur;
2878 xmlNodeSetPtr nodes;
2879 xmlXPathContextPtr xpath_ctx;
2880 xmlXPathObjectPtr xpath_obj;
2882 if ((doc = xmlParseFile(ui_file)) == NULL)
2883 return false;
2884 if ((xpath_ctx = xmlXPathNewContext(doc)) == NULL) {
2885 xmlFreeDoc(doc);
2886 return false;
2888 xpath_len = 2 * (strlen(xpath_base1) + strlen(xpath_id) +
2889 strlen(xpath_base2) + xpath_n_len +
2890 strlen(xpath_base3))
2891 + 1 /* "|" */
2892 + strlen(xpath_text_col) + strlen(xpath_renderer_id)
2893 + 1; /* '\0' */
2894 if ((xpath = malloc(xpath_len)) == NULL) {
2895 xmlFreeDoc(doc);
2896 return false;
2898 snprintf((char *) xpath, xpath_len, "%s%s%s%d%s%s|%s%s%s%d%s%s",
2899 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_text_col,
2900 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_renderer_id);
2901 if ((xpath_obj = xmlXPathEvalExpression(xpath, xpath_ctx)) == NULL) {
2902 xmlXPathFreeContext(xpath_ctx);
2903 free(xpath);
2904 xmlFreeDoc(doc);
2905 return false;
2907 if ((nodes = xpath_obj->nodesetval) != NULL) {
2908 for (i = 0; i < nodes->nodeNr; ++i) {
2909 if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
2910 cur = nodes->nodeTab[i];
2911 m_col_s = xmlNodeGetContent(cur);
2912 } else {
2913 cur = nodes->nodeTab[i];
2914 renderer_name = xmlNodeGetContent(cur);
2918 if (renderer_name) {
2919 *renderer = GTK_CELL_RENDERER(
2920 gtk_builder_get_object(builder, (char *) renderer_name));
2921 if (m_col_s) {
2922 g_object_set_data(G_OBJECT(*renderer), "col_number",
2923 GINT_TO_POINTER(strtol((char *) m_col_s,
2924 NULL, 10)));
2925 xmlFree(m_col_s);
2926 r = true;
2928 xmlFree(renderer_name);
2930 xmlXPathFreeObject(xpath_obj);
2931 xmlXPathFreeContext(xpath_ctx);
2932 free(xpath);
2933 xmlFreeDoc(doc);
2934 return r;
2938 * Callbacks that forward a modification of a tree view cell to the
2939 * underlying model
2941 static void
2942 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
2943 const gchar *new_text, gpointer model)
2945 GtkTreeIter iter;
2946 GtkTreeView *view;
2947 void *col;
2949 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2950 view = g_object_get_data(G_OBJECT(renderer), "tree_view");
2951 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2952 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2953 new_text);
2954 send_tree_cell_msg_by(send_msg, model, path_s, &iter, GPOINTER_TO_INT(col),
2955 GTK_BUILDABLE(view));
2958 static void
2959 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, gpointer model)
2961 GtkTreeIter iter;
2962 bool toggle_state;
2963 void *col;
2965 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2966 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2967 gtk_tree_model_get(model, &iter, col, &toggle_state, -1);
2968 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2969 toggle_state? "0" : "1");
2972 static void
2973 connect_widget_signals(gpointer *obj, char *ui_file)
2975 GObject *obj2;
2976 GType type = G_TYPE_INVALID;
2977 char *suffix = NULL;
2978 const char *name = NULL;
2980 type = G_TYPE_FROM_INSTANCE(obj);
2981 if (GTK_IS_BUILDABLE(obj))
2982 name = widget_name(GTK_BUILDABLE(obj));
2983 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
2984 GtkCellRenderer *renderer;
2985 GtkTreeModel *model;
2986 GtkTreeView *view;
2987 gboolean editable = FALSE;
2988 int i;
2990 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
2991 view = GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(GTK_TREE_VIEW_COLUMN(obj)));
2992 model = gtk_tree_view_get_model(view);
2993 for (i = 1;; i++) {
2994 if (!tree_view_column_get_renderer_column(ui_file, GTK_TREE_VIEW_COLUMN(obj), i, &renderer))
2995 break;
2996 g_object_set_data(G_OBJECT(renderer), "tree_view", view);
2997 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
2998 g_object_get(renderer, "editable", &editable, NULL);
2999 if (editable)
3000 g_signal_connect(renderer, "edited", G_CALLBACK(cb_tree_model_edit), model);
3001 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
3002 g_object_get(renderer, "activatable", &editable, NULL);
3003 if (editable)
3004 g_signal_connect(renderer, "toggled", G_CALLBACK(cb_tree_model_toggle), model);
3007 } else if (type == GTK_TYPE_BUTTON) {
3008 /* Button associated with a GtkTextView. */
3009 if ((suffix = strstr(name, "_send_text")) != NULL &&
3010 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
3011 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
3012 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
3013 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
3014 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
3015 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
3016 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
3017 else {
3018 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
3019 /* Buttons associated with (and part of) a GtkDialog.
3020 * (We shun response ids which could be returned from
3021 * gtk_dialog_run() because that would require the
3022 * user to define those response ids in Glade,
3023 * numerically */
3024 if ((suffix = strstr(name, "_cancel")) != NULL &&
3025 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
3026 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
3027 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
3028 else
3029 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
3030 else if ((suffix = strstr(name, "_ok")) != NULL &&
3031 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name))) {
3032 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
3033 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), GTK_FILE_CHOOSER(obj2));
3034 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
3035 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
3036 else
3037 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
3038 } else if ((suffix = strstr(name, "_apply")) != NULL &&
3039 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
3040 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
3042 } else if (GTK_IS_MENU_ITEM(obj))
3043 if ((suffix = strstr(name, "_invoke")) != NULL &&
3044 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
3045 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
3046 else
3047 g_signal_connect(obj, "activate", G_CALLBACK(cb_menu_item), "active");
3048 else if (GTK_IS_WINDOW(obj)) {
3049 g_signal_connect(obj, "delete-event", G_CALLBACK(cb_event_simple), "closed");
3050 if (eql(name, MAIN_WIN))
3051 g_signal_connect_swapped(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
3052 else
3053 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
3054 } else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
3055 g_signal_connect(obj, "file-set", G_CALLBACK(cb_file_chooser_button), "file");
3056 else if (type == GTK_TYPE_COLOR_BUTTON)
3057 g_signal_connect(obj, "color-set", G_CALLBACK(cb_color_button), "color");
3058 else if (type == GTK_TYPE_FONT_BUTTON)
3059 g_signal_connect(obj, "font-set", G_CALLBACK(cb_font_button), "font");
3060 else if (type == GTK_TYPE_SWITCH)
3061 g_signal_connect(obj, "notify::active", G_CALLBACK(cb_switch), NULL);
3062 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
3063 g_signal_connect(obj, "toggled", G_CALLBACK(cb_toggle_button), NULL);
3064 else if (type == GTK_TYPE_ENTRY)
3065 g_signal_connect(obj, "changed", G_CALLBACK(cb_editable), "text");
3066 else if (type == GTK_TYPE_SPIN_BUTTON)
3067 g_signal_connect(obj, "value_changed", G_CALLBACK(cb_spin_button), "text"); /* TODO: rename to "value" */
3068 else if (type == GTK_TYPE_SCALE)
3069 g_signal_connect(obj, "value-changed", G_CALLBACK(cb_range), "value");
3070 else if (type == GTK_TYPE_CALENDAR) {
3071 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb_calendar), "doubleclicked");
3072 g_signal_connect(obj, "day-selected", G_CALLBACK(cb_calendar), "clicked");
3073 } else if (type == GTK_TYPE_TREE_SELECTION)
3074 g_signal_connect(obj, "changed", G_CALLBACK(cb_tree_selection), "clicked");
3075 else if (type == GTK_TYPE_SOCKET) {
3076 g_signal_connect(obj, "plug-added", G_CALLBACK(cb_simple), "plug-added");
3077 g_signal_connect(obj, "plug-removed", G_CALLBACK(cb_simple), "plug-removed");
3078 } else if (type == GTK_TYPE_DRAWING_AREA)
3079 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
3080 else if (type == GTK_TYPE_EVENT_BOX) {
3081 gtk_widget_set_can_focus(GTK_WIDGET(obj), true);
3082 g_signal_connect(obj, "button-press-event", G_CALLBACK(cb_event_box_button), "button_press");
3083 g_signal_connect(obj, "button-release-event", G_CALLBACK(cb_event_box_button), "button_release");
3084 g_signal_connect(obj, "motion-notify-event", G_CALLBACK(cb_event_box_motion), "motion");
3085 g_signal_connect(obj, "key-press-event", G_CALLBACK(cb_event_box_key), "key_press");
3090 * We keep a style provider with each widget
3092 static void
3093 add_widget_style_provider(gpointer *obj, void *data)
3095 GtkCssProvider *style_provider;
3096 GtkStyleContext *context;
3098 (void) data;
3099 if (!GTK_IS_WIDGET(obj))
3100 return;
3101 style_provider = gtk_css_provider_new();
3102 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
3103 gtk_style_context_add_provider(context,
3104 GTK_STYLE_PROVIDER(style_provider),
3105 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
3106 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
3109 static void
3110 prepare_widgets(char *ui_file)
3112 GSList *objects = NULL;
3114 objects = gtk_builder_get_objects(builder);
3115 g_slist_foreach(objects, (GFunc) connect_widget_signals, ui_file);
3116 g_slist_foreach(objects, (GFunc) add_widget_style_provider, NULL);
3117 g_slist_free(objects);
3121 main(int argc, char *argv[])
3123 FILE *in = NULL; /* command input */
3124 GObject *main_window = NULL;
3125 bool bg = false;
3126 char *in_fifo = NULL, *out_fifo = NULL;
3127 char *ui_file = "pipeglade.ui", *log_file = NULL, *err_file = NULL;
3128 char *xid = NULL;
3129 char opt;
3130 pthread_t receiver;
3132 /* Disable runtime GLIB deprecation warnings: */
3133 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
3134 out = NULL;
3135 save = NULL;
3136 log_out = NULL;
3137 gtk_init(&argc, &argv);
3138 while ((opt = getopt(argc, argv, "bGhe:i:l:o:O:u:V")) != -1) {
3139 switch (opt) {
3140 case 'b': bg = true; break;
3141 case 'e': xid = optarg; break;
3142 case 'G': show_lib_versions(); break;
3143 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
3144 case 'i': in_fifo = optarg; break;
3145 case 'l': log_file = optarg; break;
3146 case 'o': out_fifo = optarg; break;
3147 case 'O': err_file = optarg; break;
3148 case 'u': ui_file = optarg; break;
3149 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
3150 case '?':
3151 default: bye(EXIT_FAILURE, stderr, USAGE); break;
3154 if (argv[optind] != NULL)
3155 bye(EXIT_FAILURE, stderr,
3156 "illegal parameter '%s'\n" USAGE, argv[optind]);
3157 redirect_stderr(err_file);
3158 in = open_in_fifo(in_fifo);
3159 out = open_out_fifo(out_fifo);
3160 go_bg_if(bg, in, out, err_file);
3161 builder = builder_from_file(ui_file);
3162 log_out = open_log(log_file);
3163 pthread_create(&receiver, NULL, (void *(*)(void *)) digest_msg, in);
3164 main_window = find_main_window();
3165 xmlInitParser();
3166 LIBXML_TEST_VERSION;
3167 prepare_widgets(ui_file);
3168 xembed_if(xid, main_window);
3169 gtk_main();
3170 rm_unless(stdin, in, in_fifo);
3171 rm_unless(stdout, out, out_fifo);
3172 pthread_cancel(receiver);
3173 pthread_join(receiver, NULL);
3174 xmlCleanupParser();
3175 exit(EXIT_SUCCESS);