Complain on invalid page numbers in GtkNotebook
[pipeglade.git] / pipeglade.c
blobffc16248fc91d3dee3d0b1998f5c38562fbed1e5
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 <cairo-pdf.h>
25 #include <cairo-ps.h>
26 #include <cairo-svg.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <gtk/gtk.h>
30 #include <gtk/gtkunixprint.h>
31 #include <gtk/gtkx.h>
32 #include <inttypes.h>
33 #include <libxml/xpath.h>
34 #include <locale.h>
35 #include <math.h>
36 #include <pthread.h>
37 #include <stdio.h>
38 #include <stdarg.h>
39 #include <stdbool.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sys/select.h>
43 #include <sys/stat.h>
44 #include <time.h>
45 #include <unistd.h>
47 #define VERSION "4.7.0"
48 #define BUFLEN 256
49 #define WHITESPACE " \t\n"
50 #define MAIN_WIN "main"
51 #define USAGE \
52 "usage: pipeglade [[-i in-fifo] " \
53 "[-o out-fifo] " \
54 "[-b] " \
55 "[-u glade-file.ui] " \
56 "[-e xid]\n" \
57 " [-l log-file] " \
58 "[-O err-file] " \
59 "[--display X-server]] | " \
60 "[-h |" \
61 "-G |" \
62 "-V]\n"
64 #define ABORT \
65 { \
66 fprintf(stderr, \
67 "In %s (%s:%d): ", \
68 __func__, __FILE__, __LINE__); \
69 abort(); \
72 #define OOM_ABORT \
73 { \
74 fprintf(stderr, \
75 "Out of memory in %s (%s:%d): ", \
76 __func__, __FILE__, __LINE__); \
77 abort(); \
80 static FILE *out; /* UI feedback messages */
81 static FILE *save; /* saving user data */
82 static FILE *log_out; /* logging output */
83 static GtkBuilder *builder; /* to be read from .ui file */
87 * ============================================================
88 * Helper functions
89 * ============================================================
93 * Check if s1 and s2 are equal strings
95 static bool
96 eql(const char *s1, const char *s2)
98 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
102 * Print a formatted message to stream s and give up with status
104 static void
105 bye(int status, FILE *s, const char *fmt, ...)
107 va_list ap;
109 va_start(ap, fmt);
110 vfprintf(s, fmt, ap);
111 va_end(ap);
112 exit(status);
115 static void
116 show_lib_versions(void)
118 bye(EXIT_SUCCESS, stdout,
119 "GTK+ v%d.%d.%d (running v%d.%d.%d)\n"
120 "cairo v%s (running v%s)\n",
121 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
122 gtk_get_major_version(), gtk_get_minor_version(),
123 gtk_get_micro_version(),
124 CAIRO_VERSION_STRING, cairo_version_string());
128 * XEmbed us if xid_s is given, or show a standalone window; give up
129 * on errors
131 static void
132 xembed_if(char *xid_s, GObject *main_window)
134 GtkWidget *plug, *body;
135 Window xid;
136 char xid_s2[BUFLEN];
138 if (xid_s == NULL) { /* standalone */
139 gtk_widget_show(GTK_WIDGET(main_window));
140 return;
142 /* We're being XEmbedded */
143 xid = strtoul(xid_s, NULL, 10);
144 snprintf(xid_s2, BUFLEN, "%lu", xid);
145 if (!eql(xid_s, xid_s2))
146 bye(EXIT_FAILURE, stderr,
147 "%s is not a valid XEmbed socket id\n", xid_s);
148 body = gtk_bin_get_child(GTK_BIN(main_window));
149 gtk_container_remove(GTK_CONTAINER(main_window), body);
150 plug = gtk_plug_new(xid);
151 if (!gtk_plug_get_embedded(GTK_PLUG(plug)))
152 bye(EXIT_FAILURE, stderr,
153 "unable to embed into XEmbed socket %s\n", xid_s);
154 gtk_container_add(GTK_CONTAINER(plug), body);
155 gtk_widget_show(plug);
159 * If requested, redirect stderr to file name
161 static void
162 redirect_stderr(const char *name)
164 if (name == NULL)
165 return;
166 if (freopen(name, "a", stderr) == NULL)
167 /* complaining on stdout since stderr is closed now */
168 bye(EXIT_FAILURE, stdout, "redirecting stderr to %s: %s\n",
169 name, strerror(errno));
170 if (fchmod(fileno(stderr), 0600) < 0)
171 bye(EXIT_FAILURE, stdout, "setting permissions of %s: %s\n",
172 name, strerror(errno));
173 return;
177 * fork() if requested in bg; give up on errors
179 static void
180 go_bg_if(bool bg, FILE *in, FILE *out, char *err_file)
182 pid_t pid = 0;
184 if (!bg)
185 return;
186 if (in == stdin || out == stdout)
187 bye(EXIT_FAILURE, stderr,
188 "parameter -b requires both -i and -o\n");
189 pid = fork();
190 if (pid < 0)
191 bye(EXIT_FAILURE, stderr,
192 "going to background: %s\n", strerror(errno));
193 if (pid > 0)
194 bye(EXIT_SUCCESS, stdout, "%d\n", pid);
195 /* We're the child */
196 close(fileno(stdin)); /* making certain not-so-smart */
197 close(fileno(stdout)); /* system/run-shell commands happy */
198 if (err_file == NULL)
199 freopen("/dev/null", "w", stderr);
203 * Return the current locale and set it to "C". Should be free()d if
204 * done.
206 static char *
207 lc_numeric()
209 char *lc_orig;
210 char *lc = setlocale(LC_NUMERIC, NULL);
212 if ((lc_orig = malloc(strlen(lc) + 1)) == NULL)
213 OOM_ABORT;
214 strcpy(lc_orig, lc);
215 setlocale(LC_NUMERIC, "C");
216 return lc_orig;
220 * Set locale (back) to lc; free lc
222 static void
223 lc_numeric_free(char *lc)
225 setlocale(LC_NUMERIC, lc);
226 free(lc);
230 * Print a warning about a malformed command to stderr
232 static void
233 ign_cmd(GType type, const char *msg)
235 const char *name, *pad = " ";
237 if (type == G_TYPE_INVALID) {
238 name = "";
239 pad = "";
240 } else
241 name = g_type_name(type);
242 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
246 * Check if n is, or can be made, the name of a fifo. Give up if n
247 * exists but is not a fifo.
249 static void
250 find_fifo(const char *n)
252 struct stat sb;
254 stat(n, &sb);
255 if (S_ISFIFO(sb.st_mode)) {
256 if (chmod(n, 0600) != 0)
257 bye(EXIT_FAILURE, stderr, "using pre-existing fifo %s: %s\n",
258 n, strerror(errno));
259 } else if (mkfifo(n, 0600) != 0)
260 bye(EXIT_FAILURE, stderr, "making fifo %s: %s\n",
261 n, strerror(errno));
265 * Create a fifo if necessary, and open it. Give up if the file
266 * exists but is not a fifo
268 static FILE *
269 open_in_fifo(const char *name)
271 FILE *s = NULL;
272 int fd;
274 if (name == NULL)
275 s = stdin;
276 else {
277 find_fifo(name);
278 if ((fd = open(name, O_RDWR | O_NONBLOCK)) < 0)
279 bye(EXIT_FAILURE, stderr, "opening fifo %s (r): %s\n",
280 name, strerror(errno));
281 if ((s = fdopen(fd, "r")) == NULL)
282 bye(EXIT_FAILURE, stderr, "opening fifo %s (r): %s\n",
283 name, strerror(errno));
285 setvbuf(s, NULL, _IONBF, 0);
286 return s;
289 static FILE *
290 open_out_fifo(const char *name)
292 FILE *s = NULL;
294 if (name == NULL)
295 s = stdout;
296 else {
297 find_fifo(name);
298 if ((s = fopen(name, "w+")) == NULL)
299 bye(EXIT_FAILURE, stderr, "opening fifo %s (w): %s\n",
300 name, strerror(errno));
302 setvbuf(s, NULL, _IOLBF, 0);
303 return s;
307 * Create a log file if necessary, and open it. A name of "-"
308 * requests use of stderr.
310 static FILE *
311 open_log(const char *name)
313 FILE *s = NULL;
315 if (name == NULL)
316 return NULL;
317 if (eql(name, "-"))
318 return stderr;
319 if ((s = fopen(name, "a")) == NULL)
320 bye(EXIT_FAILURE, stderr, "opening log file %s: %s\n",
321 name, strerror(errno));
322 if (fchmod(fileno(s), 0600) < 0)
323 bye(EXIT_FAILURE, stderr, "setting permissions of %s: %s\n",
324 name, strerror(errno));
325 return s;
328 static void
329 rm_unless(FILE *forbidden, FILE *s, char *name)
331 if (s == forbidden)
332 return;
333 fclose(s);
334 unlink(name);
338 * Microseconds elapsed since start
340 static long int
341 usec_since(struct timespec *start)
343 struct timespec now;
345 clock_gettime(CLOCK_MONOTONIC, &now);
346 return (now.tv_sec - start->tv_sec) * 1e6 +
347 (now.tv_nsec - start->tv_nsec) / 1e3;
351 * Write log file
353 static void
354 log_msg(char *msg)
356 static char *old_msg;
357 static struct timespec start;
359 if (log_out == NULL) /* no logging */
360 return;
361 if (msg == NULL && old_msg == NULL)
362 fprintf(log_out,
363 "##########\t##### (New Pipeglade session) #####\n");
364 else if (msg == NULL && old_msg != NULL) { /* command done; start idle */
365 fprintf(log_out,
366 "%10ld\t%s\n", usec_since(&start), old_msg);
367 free(old_msg);
368 old_msg = NULL;
369 } else if (msg != NULL && old_msg == NULL) { /* idle done; start command */
370 fprintf(log_out,
371 "%10ld\t### (Idle) ###\n", usec_since(&start));
372 if ((old_msg = malloc(strlen(msg) + 1)) == NULL)
373 OOM_ABORT;
374 strcpy(old_msg, msg);
375 } else
376 ABORT;
377 clock_gettime(CLOCK_MONOTONIC, &start);
380 static bool
381 has_suffix(const char *s, const char *suffix)
383 int s_suf = strlen(s) - strlen(suffix);
385 if (s_suf < 0)
386 return false;
387 return eql(suffix, s + s_suf);
391 * Remove suffix from name; find the object named like this
393 static GObject *
394 obj_sans_suffix(const char *suffix, const char *name)
396 char str[BUFLEN + 1] = {'\0'};
397 int str_l;
399 str_l = suffix - name;
400 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
401 return gtk_builder_get_object(builder, str);
405 * Read UI definition from ui_file; give up on errors
407 static GtkBuilder *
408 builder_from_file(char *ui_file)
410 GError *error = NULL;
411 GtkBuilder *b;
413 b = gtk_builder_new();
414 if (gtk_builder_add_from_file(b, ui_file, &error) == 0)
415 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
416 return b;
419 static const char *
420 widget_name(GtkBuildable *obj)
422 return gtk_buildable_get_name(obj);
426 * Get the main window; give up on errors
428 static GObject *
429 find_main_window(void)
431 GObject *mw;
433 if (GTK_IS_WINDOW(mw = gtk_builder_get_object(builder, MAIN_WIN)))
434 return mw;
435 bye(EXIT_FAILURE, stderr, "no toplevel window named \'" MAIN_WIN "\'\n");
436 return NULL; /* NOT REACHED */
440 * Store a line from stream s into buf, which should have been malloc'd
441 * to bufsize. Enlarge buf and bufsize if necessary.
443 static size_t
444 read_buf(FILE *s, char **buf, size_t *bufsize)
446 bool esc = false;
447 fd_set rfds;
448 int c;
449 int ifd = fileno(s);
450 size_t i = 0;
452 FD_ZERO(&rfds);
453 FD_SET(ifd, &rfds);
454 for (;;) {
455 select(ifd + 1, &rfds, NULL, NULL, NULL);
456 c = getc(s);
457 if (c == '\n' || feof(s))
458 break;
459 if (i >= *bufsize - 1)
460 if ((*buf = realloc(*buf, *bufsize *= 2)) == NULL)
461 OOM_ABORT;
462 if (esc) {
463 esc = false;
464 switch (c) {
465 case 'n': (*buf)[i++] = '\n'; break;
466 case 'r': (*buf)[i++] = '\r'; break;
467 default: (*buf)[i++] = c; break;
469 } else if (c == '\\')
470 esc = true;
471 else
472 (*buf)[i++] = c;
474 (*buf)[i] = '\0';
475 return i;
480 * ============================================================
481 * Receiving feedback from the GUI
482 * ============================================================
485 static void
486 send_msg_to(FILE* o, GtkBuildable *obj, const char *tag, va_list ap)
488 char *data;
489 const char *w_name = widget_name(obj);
490 fd_set wfds;
491 int ofd = fileno(o);
492 struct timeval timeout = {1, 0};
494 FD_ZERO(&wfds);
495 FD_SET(ofd, &wfds);
496 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
497 fprintf(o, "%s:%s ", w_name, tag);
498 while ((data = va_arg(ap, char *)) != NULL) {
499 size_t i = 0;
500 char c;
502 while ((c = data[i++]) != '\0')
503 if (c == '\\')
504 fprintf(o, "\\\\");
505 else if (c == '\n')
506 fprintf(o, "\\n");
507 else
508 putc(c, o);
510 putc('\n', o);
511 } else
512 fprintf(stderr,
513 "send error; discarding feedback message %s:%s\n",
514 w_name, tag);
518 * Send GUI feedback to global stream "out". The message format is
519 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
520 * last argument must be NULL.
522 static void
523 send_msg(GtkBuildable *obj, const char *tag, ...)
525 va_list ap;
527 va_start(ap, tag);
528 send_msg_to(out, obj, tag, ap);
529 va_end(ap);
533 * Send message from GUI to global stream "save". The message format
534 * is "<origin>:<tag> <data ...>". The variadic arguments are strings;
535 * last argument must be NULL.
537 static void
538 save_msg(GtkBuildable *obj, const char *tag, ...)
540 va_list ap;
542 va_start(ap, tag);
543 send_msg_to(save, obj, tag, ap);
544 va_end(ap);
548 * Send message from GUI to global stream "save". The message format
549 * is "<origin>:set <data ...>". The variadic arguments are strings;
550 * last argument must be NULL.
552 static void
553 save_action_set_msg(GtkBuildable *obj, const char *tag, ...)
555 va_list ap;
557 va_start(ap, tag);
558 send_msg_to(save, obj, "set", ap);
559 va_end(ap);
563 * Use msg_sender() to send a message describing a particular cell
565 static void
566 send_tree_cell_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
567 GtkTreeModel *model, const char *path_s,
568 GtkTreeIter *iter, int col, GtkBuildable *obj)
570 GType col_type;
571 GValue value = G_VALUE_INIT;
572 char str[BUFLEN], *lc = lc_numeric();
574 gtk_tree_model_get_value(model, iter, col, &value);
575 col_type = gtk_tree_model_get_column_type(model, col);
576 switch (col_type) {
577 case G_TYPE_INT:
578 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
579 msg_sender(obj, "gint", path_s, str, NULL);
580 break;
581 case G_TYPE_LONG:
582 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
583 msg_sender(obj, "glong", path_s, str, NULL);
584 break;
585 case G_TYPE_INT64:
586 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
587 msg_sender(obj, "gint64", path_s, str, NULL);
588 break;
589 case G_TYPE_UINT:
590 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
591 msg_sender(obj, "guint", path_s, str, NULL);
592 break;
593 case G_TYPE_ULONG:
594 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
595 msg_sender(obj, "gulong", path_s, str, NULL);
596 break;
597 case G_TYPE_UINT64:
598 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
599 msg_sender(obj, "guint64", path_s, str, NULL);
600 break;
601 case G_TYPE_BOOLEAN:
602 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
603 msg_sender(obj, "gboolean", path_s, str, NULL);
604 break;
605 case G_TYPE_FLOAT:
606 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
607 msg_sender(obj, "gfloat", path_s, str, NULL);
608 break;
609 case G_TYPE_DOUBLE:
610 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
611 msg_sender(obj, "gdouble", path_s, str, NULL);
612 break;
613 case G_TYPE_STRING:
614 snprintf(str, BUFLEN, " %d ", col);
615 msg_sender(obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
616 break;
617 default:
618 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
619 break;
621 g_value_unset(&value);
622 lc_numeric_free(lc);
626 * Use msg_sender() to send one message per column for a single row
628 static void
629 send_tree_row_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
630 GtkTreeModel *model, char *path_s,
631 GtkTreeIter *iter, GtkBuildable *obj)
633 int col;
635 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++)
636 send_tree_cell_msg_by(msg_sender, model, path_s, iter, col, obj);
640 * send_tree_row_msg serves as an argument for
641 * gtk_tree_selection_selected_foreach()
643 static gboolean
644 send_tree_row_msg(GtkTreeModel *model,
645 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
647 char *path_s = gtk_tree_path_to_string(path);
649 send_tree_row_msg_by(send_msg, model, path_s, iter, obj);
650 g_free(path_s);
651 return FALSE;
655 * save_tree_row_msg serves as an argument for
656 * gtk_tree_model_foreach().
657 * Send message from GUI to global stream "save".
659 static gboolean
660 save_tree_row_msg(GtkTreeModel *model,
661 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
663 char *path_s = gtk_tree_path_to_string(path);
665 (void) path;
666 send_tree_row_msg_by(save_action_set_msg, model, path_s, iter, obj);
667 g_free(path_s);
668 return FALSE;
671 static void
672 cb_calendar(GtkBuildable *obj, const char *tag)
674 char str[BUFLEN];
675 unsigned int year = 0, month = 0, day = 0;
677 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
678 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
679 send_msg(obj, tag, str, NULL);
682 static void
683 cb_color_button(GtkBuildable *obj, const char *tag)
685 GdkRGBA color;
687 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
688 send_msg(obj, tag, gdk_rgba_to_string(&color), NULL);
691 static void
692 cb_editable(GtkBuildable *obj, const char *tag)
694 send_msg(obj, tag, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
698 * Callback that sends a message about a pointer device button press
699 * in a GtkEventBox
701 static bool
702 cb_event_box_button(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
704 char data[BUFLEN], *lc = lc_numeric();
706 snprintf(data, BUFLEN, "%d %.1lf %.1lf",
707 e->button.button, e->button.x, e->button.y);
708 send_msg(obj, user_data, data, NULL);
709 lc_numeric_free(lc);
710 return true;
714 * Callback that sends in a message the name of the key pressed when
715 * a GtkEventBox is focused
717 static bool
718 cb_event_box_key(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
720 send_msg(obj, user_data, gdk_keyval_name(e->key.keyval), NULL);
721 return true;
725 * Callback that sends a message about pointer device motion in a
726 * GtkEventBox
728 static bool
729 cb_event_box_motion(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
731 char data[BUFLEN], *lc = lc_numeric();
733 snprintf(data, BUFLEN, "%.1lf %.1lf", e->button.x, e->button.y);
734 send_msg(obj, user_data, data, NULL);
735 lc_numeric_free(lc);
736 return true;
740 * Callback that only sends "name:tag" and returns false
742 static bool
743 cb_event_simple(GtkBuildable *obj, GdkEvent *e, const char *tag)
745 (void) e;
746 send_msg(obj, tag, NULL);
747 return false;
750 static void
751 cb_file_chooser_button(GtkBuildable *obj, const char *tag)
753 send_msg(obj, tag, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
756 static void
757 cb_font_button(GtkBuildable *obj, const char *tag)
759 send_msg(obj, tag, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
762 static void
763 cb_menu_item(GtkBuildable *obj, const char *tag)
765 send_msg(obj, tag, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
768 static void
769 cb_range(GtkBuildable *obj, const char *tag)
771 char str[BUFLEN], *lc = lc_numeric();
773 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
774 send_msg(obj, tag, str, NULL);
775 lc_numeric_free(lc);
779 * Callback that sends user's selection from a file dialog
781 static void
782 cb_send_file_chooser_dialog_selection(gpointer user_data)
784 send_msg(user_data, "file",
785 gtk_file_chooser_get_filename(user_data), NULL);
786 send_msg(user_data, "folder",
787 gtk_file_chooser_get_current_folder(user_data), NULL);
791 * Callback that sends in a message the content of the text buffer
792 * passed in user_data
794 static void
795 cb_send_text(GtkBuildable *obj, gpointer user_data)
797 GtkTextIter a, b;
799 gtk_text_buffer_get_bounds(user_data, &a, &b);
800 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
804 * Callback that sends in a message the highlighted text from the text
805 * buffer which was passed in user_data
807 static void
808 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
810 GtkTextIter a, b;
812 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
813 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
817 * Callback that only sends "name:tag" and returns true
819 static bool
820 cb_simple(GtkBuildable *obj, const char *tag)
822 send_msg(obj, tag, NULL);
823 return true;
826 static void
827 cb_spin_button(GtkBuildable *obj, const char *tag)
829 char str[BUFLEN], *lc = lc_numeric();
831 snprintf(str, BUFLEN, "%f", gtk_spin_button_get_value(GTK_SPIN_BUTTON(obj)));
832 send_msg(obj, tag, str, NULL);
833 lc_numeric_free(lc);
836 static void
837 cb_switch(GtkBuildable *obj, void *pspec, void *user_data)
839 (void) pspec;
840 (void) user_data;
841 send_msg(obj, gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0", NULL);
844 static void
845 cb_toggle_button(GtkBuildable *obj, const char *tag)
847 (void) tag;
848 send_msg(obj, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0", NULL);
851 static void
852 cb_tree_selection(GtkBuildable *obj, const char *tag)
854 GtkTreeSelection *sel = GTK_TREE_SELECTION(obj);
855 GtkTreeView *view = gtk_tree_selection_get_tree_view(sel);
858 send_msg(GTK_BUILDABLE(view), tag, NULL);
859 gtk_tree_selection_selected_foreach(
860 sel, (GtkTreeSelectionForeachFunc) send_tree_row_msg, view);
865 * ============================================================
866 * cb_draw() maintains a drawing on a GtkDrawingArea; it needs a few
867 * helper functions
868 * ============================================================
872 * The set of supported drawing operations
874 enum cairo_fn {
875 ARC,
876 ARC_NEGATIVE,
877 CLOSE_PATH,
878 CURVE_TO,
879 FILL,
880 FILL_PRESERVE,
881 LINE_TO,
882 MOVE_TO,
883 RECTANGLE,
884 REL_CURVE_TO,
885 REL_LINE_TO,
886 REL_MOVE_TO,
887 REL_MOVE_FOR,
888 RESET_CTM,
889 SET_DASH,
890 SET_FONT_FACE,
891 SET_FONT_SIZE,
892 SET_LINE_CAP,
893 SET_LINE_JOIN,
894 SET_LINE_WIDTH,
895 SET_SOURCE_RGBA,
896 SHOW_TEXT,
897 STROKE,
898 STROKE_PRESERVE,
899 TRANSFORM,
903 * Text placement mode for rel_move_for()
905 enum ref_point {
917 enum draw_op_policy {
918 APPEND,
919 BEFORE,
920 REPLACE,
924 * One single element of a drawing
926 struct draw_op {
927 struct draw_op *next;
928 struct draw_op *prev;
929 unsigned long long int id;
930 unsigned long long int before;
931 enum draw_op_policy policy;
932 enum cairo_fn op;
933 void *op_args;
937 * Argument sets for the various drawing operations
939 struct arc_args {
940 double x;
941 double y;
942 double radius;
943 double angle1;
944 double angle2;
947 struct curve_to_args {
948 double x1;
949 double y1;
950 double x2;
951 double y2;
952 double x3;
953 double y3;
956 struct move_to_args {
957 double x;
958 double y;
961 struct rectangle_args {
962 double x;
963 double y;
964 double width;
965 double height;
968 struct rel_move_for_args {
969 enum ref_point ref;
970 int len;
971 char text[];
974 struct set_dash_args {
975 int num_dashes;
976 double dashes[];
979 struct set_font_face_args {
980 cairo_font_slant_t slant;
981 cairo_font_weight_t weight;
982 char family[];
985 struct set_font_size_args {
986 double size;
989 struct set_line_cap_args {
990 cairo_line_cap_t line_cap;
993 struct set_line_join_args {
994 cairo_line_join_t line_join;
997 struct set_line_width_args {
998 double width;
1001 struct set_source_rgba_args {
1002 GdkRGBA color;
1005 struct show_text_args {
1006 int len;
1007 char text[];
1010 struct transform_args {
1011 cairo_matrix_t matrix;
1014 static void
1015 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
1017 switch (op) {
1018 case LINE_TO: {
1019 struct move_to_args *args = op_args;
1021 cairo_line_to(cr, args->x, args->y);
1022 break;
1024 case REL_LINE_TO: {
1025 struct move_to_args *args = op_args;
1027 cairo_rel_line_to(cr, args->x, args->y);
1028 break;
1030 case MOVE_TO: {
1031 struct move_to_args *args = op_args;
1033 cairo_move_to(cr, args->x, args->y);
1034 break;
1036 case REL_MOVE_TO: {
1037 struct move_to_args *args = op_args;
1039 cairo_rel_move_to(cr, args->x, args->y);
1040 break;
1042 case ARC: {
1043 struct arc_args *args = op_args;
1045 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1046 break;
1048 case ARC_NEGATIVE: {
1049 struct arc_args *args = op_args;
1051 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1052 break;
1054 case CURVE_TO: {
1055 struct curve_to_args *args = op_args;
1057 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1058 break;
1060 case REL_CURVE_TO: {
1061 struct curve_to_args *args = op_args;
1063 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1064 break;
1066 case RECTANGLE: {
1067 struct rectangle_args *args = op_args;
1069 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
1070 break;
1072 case CLOSE_PATH:
1073 cairo_close_path(cr);
1074 break;
1075 case SHOW_TEXT: {
1076 struct show_text_args *args = op_args;
1078 cairo_show_text(cr, args->text);
1079 break;
1081 case REL_MOVE_FOR: {
1082 cairo_text_extents_t e;
1083 double dx = 0.0, dy = 0.0;
1084 struct rel_move_for_args *args = op_args;
1086 cairo_text_extents(cr, args->text, &e);
1087 switch (args->ref) {
1088 case C: dx = -e.width / 2; dy = e.height / 2; break;
1089 case E: dx = -e.width; dy = e.height / 2; break;
1090 case N: dx = -e.width / 2; dy = e.height; break;
1091 case NE: dx = -e.width; dy = e.height; break;
1092 case NW: dy = e.height; break;
1093 case S: dx = -e.width / 2; break;
1094 case SE: dx = -e.width; break;
1095 case SW: break;
1096 case W: dy = e.height / 2; break;
1097 default: ABORT; break;
1099 cairo_rel_move_to(cr, dx, dy);
1100 break;
1102 case RESET_CTM:
1103 cairo_identity_matrix(cr);
1104 break;
1105 case STROKE:
1106 cairo_stroke(cr);
1107 break;
1108 case STROKE_PRESERVE:
1109 cairo_stroke_preserve(cr);
1110 break;
1111 case FILL:
1112 cairo_fill(cr);
1113 break;
1114 case FILL_PRESERVE:
1115 cairo_fill_preserve(cr);
1116 break;
1117 case SET_DASH: {
1118 struct set_dash_args *args = op_args;
1120 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
1121 break;
1123 case SET_FONT_FACE: {
1124 struct set_font_face_args *args = op_args;
1126 cairo_select_font_face(cr, args->family, args->slant, args->weight);
1127 break;
1129 case SET_FONT_SIZE: {
1130 struct set_font_size_args *args = op_args;
1132 cairo_set_font_size(cr, args->size);
1133 break;
1135 case SET_LINE_CAP: {
1136 struct set_line_cap_args *args = op_args;
1138 cairo_set_line_cap(cr, args->line_cap);
1139 break;
1141 case SET_LINE_JOIN: {
1142 struct set_line_join_args *args = op_args;
1144 cairo_set_line_join(cr, args->line_join);
1145 break;
1147 case SET_LINE_WIDTH: {
1148 struct set_line_width_args *args = op_args;
1150 cairo_set_line_width(cr, args->width);
1151 break;
1153 case SET_SOURCE_RGBA: {
1154 struct set_source_rgba_args *args = op_args;
1156 gdk_cairo_set_source_rgba(cr, &args->color);
1157 break;
1159 case TRANSFORM: {
1160 struct transform_args *args = op_args;
1162 cairo_transform(cr, &args->matrix);
1163 break;
1165 default:
1166 ABORT;
1167 break;
1172 * Callback that draws on a GtkDrawingArea
1174 static gboolean
1175 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
1177 struct draw_op *op;
1179 (void) data;
1180 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
1181 op != NULL;
1182 op = op->next)
1183 draw(cr, op->op, op->op_args);
1184 return FALSE;
1189 * ============================================================
1190 * Manipulating the GUI
1191 * ============================================================
1194 static void
1195 update_button(GObject *obj, const char *action,
1196 const char *data, const char *whole_msg, GType type)
1198 if (eql(action, "set_label"))
1199 gtk_button_set_label(GTK_BUTTON(obj), data);
1200 else
1201 ign_cmd(type, whole_msg);
1204 static void
1205 update_calendar(GObject *obj, const char *action,
1206 const char *data, const char *whole_msg, GType type)
1208 GtkCalendar *calendar = GTK_CALENDAR(obj);
1209 char dummy;
1210 int year = 0, month = 0, day = 0;
1212 if (eql(action, "select_date") &&
1213 sscanf(data, "%d-%d-%d %c", &year, &month, &day, &dummy) == 3) {
1214 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
1215 gtk_calendar_select_month(calendar, --month, year);
1216 gtk_calendar_select_day(calendar, day);
1217 } else
1218 ign_cmd(type, whole_msg);
1219 } else if (eql(action, "mark_day") &&
1220 sscanf(data, "%d %c", &day, &dummy) == 1) {
1221 if (day > 0 && day <= 31)
1222 gtk_calendar_mark_day(calendar, day);
1223 else
1224 ign_cmd(type, whole_msg);
1225 } else if (eql(action, "clear_marks") && sscanf(data, " %c", &dummy) < 1)
1226 gtk_calendar_clear_marks(calendar);
1227 else
1228 ign_cmd(type, whole_msg);
1232 * Common actions for various kinds of window. Return false if
1233 * command is ignored
1235 static bool
1236 update_class_window(GObject *obj, const char *action,
1237 const char *data, const char *whole_msg, GType type)
1239 GtkWindow *window = GTK_WINDOW(obj);
1240 char dummy;
1241 int x, y;
1243 (void) type;
1244 (void) whole_msg;
1245 if (eql(action, "set_title"))
1246 gtk_window_set_title(window, data);
1247 else if (eql(action, "fullscreen") && sscanf(data, " %c", &dummy) < 1)
1248 gtk_window_fullscreen(window);
1249 else if (eql(action, "unfullscreen") && sscanf(data, " %c", &dummy) < 1)
1250 gtk_window_unfullscreen(window);
1251 else if (eql(action, "resize") &&
1252 sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
1253 gtk_window_resize(window, x, y);
1254 else if (eql(action, "resize") && sscanf(data, " %c", &dummy) < 1) {
1255 gtk_window_get_default_size(window, &x, &y);
1256 gtk_window_resize(window, x, y);
1257 } else if (eql(action, "move") &&
1258 sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
1259 gtk_window_move(window, x, y);
1260 else
1261 return false;
1262 return true;
1265 static void
1266 update_color_button(GObject *obj, const char *action,
1267 const char *data, const char *whole_msg, GType type)
1269 GdkRGBA color;
1271 if (eql(action, "set_color")) {
1272 gdk_rgba_parse(&color, data);
1273 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
1274 } else
1275 ign_cmd(type, whole_msg);
1278 static void
1279 update_combo_box_text(GObject *obj, const char *action,
1280 const char *data, const char *whole_msg, GType type)
1282 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(obj);
1283 char data1[strlen(data) + 1];
1284 char dummy;
1285 int val;
1287 strcpy(data1, data);
1288 if (eql(action, "prepend_text"))
1289 gtk_combo_box_text_prepend_text(combobox, data1);
1290 else if (eql(action, "append_text"))
1291 gtk_combo_box_text_append_text(combobox, data1);
1292 else if (eql(action, "remove") && sscanf(data, "%d %c", &val, &dummy) == 1)
1293 gtk_combo_box_text_remove(combobox, strtol(data1, NULL, 10));
1294 else if (eql(action, "insert_text")) {
1295 char *position = strtok(data1, WHITESPACE);
1296 char *text = strtok(NULL, WHITESPACE);
1298 gtk_combo_box_text_insert_text(combobox,
1299 strtol(position, NULL, 10), text);
1300 } else
1301 ign_cmd(type, whole_msg);
1305 * Maintaining a list of drawing operations. It is the responsibility
1306 * of cb_draw() to actually draw them. update_drawing_area() needs a
1307 * few helper functions.
1310 enum draw_op_stat {
1311 FAILURE,
1312 SUCCESS,
1313 NEED_REDRAW,
1317 * Fill structure *op with the drawing operation according to action
1318 * and with the appropriate set of arguments
1320 static enum draw_op_stat
1321 set_draw_op(struct draw_op *op, const char *action, const char *data)
1323 char dummy;
1324 const char *raw_args = data;
1325 enum draw_op_stat result = SUCCESS;
1326 int args_start = 0;
1328 if (sscanf(data, "=%llu %n", &op->id, &args_start) == 1) {
1329 op->policy = REPLACE;
1330 result = NEED_REDRAW;
1331 } else if (sscanf(data, "%llu<%llu %n", &op->id, &op->before, &args_start) == 2) {
1332 op->policy = BEFORE;
1333 result = NEED_REDRAW;
1334 } else if (sscanf(data, "%llu %n", &op->id, &args_start) == 1)
1335 op->policy = APPEND;
1336 else
1337 return FAILURE;
1338 raw_args += args_start;
1339 if (eql(action, "line_to")) {
1340 struct move_to_args *args;
1342 if ((args = malloc(sizeof(*args))) == NULL)
1343 OOM_ABORT;
1344 op->op = LINE_TO;
1345 op->op_args = args;
1346 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1347 return FAILURE;
1348 } else if (eql(action, "rel_line_to")) {
1349 struct move_to_args *args;
1351 if ((args = malloc(sizeof(*args))) == NULL)
1352 OOM_ABORT;
1353 op->op = REL_LINE_TO;
1354 op->op_args = args;
1355 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1356 return FAILURE;
1357 } else if (eql(action, "move_to")) {
1358 struct move_to_args *args;
1360 if ((args = malloc(sizeof(*args))) == NULL)
1361 OOM_ABORT;
1362 op->op = MOVE_TO;
1363 op->op_args = args;
1364 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1365 return FAILURE;
1366 } else if (eql(action, "rel_move_to")) {
1367 struct move_to_args *args;
1369 if ((args = malloc(sizeof(*args))) == NULL)
1370 OOM_ABORT;
1371 op->op = REL_MOVE_TO;
1372 op->op_args = args;
1373 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1374 return FAILURE;
1375 } else if (eql(action, "arc")) {
1376 struct arc_args *args;
1377 double deg1, deg2;
1379 if ((args = malloc(sizeof(*args))) == NULL)
1380 OOM_ABORT;
1381 op->op = ARC;
1382 op->op_args = args;
1383 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %c",
1384 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 5)
1385 return FAILURE;
1386 args->angle1 = deg1 * (M_PI / 180.L);
1387 args->angle2 = deg2 * (M_PI / 180.L);
1388 } else if (eql(action, "arc_negative")) {
1389 double deg1, deg2;
1390 struct arc_args *args;
1392 if ((args = malloc(sizeof(*args))) == NULL)
1393 OOM_ABORT;
1394 op->op = ARC_NEGATIVE;
1395 op->op_args = args;
1396 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %c",
1397 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 5)
1398 return FAILURE;
1399 args->angle1 = deg1 * (M_PI / 180.L);
1400 args->angle2 = deg2 * (M_PI / 180.L);
1401 } else if (eql(action, "curve_to")) {
1402 struct curve_to_args *args;
1404 if ((args = malloc(sizeof(*args))) == NULL)
1405 OOM_ABORT;
1406 op->op = CURVE_TO;
1407 op->op_args = args;
1408 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1409 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 6)
1410 return FAILURE;
1411 } else if (eql(action, "rel_curve_to")) {
1412 struct curve_to_args *args;
1414 if ((args = malloc(sizeof(*args))) == NULL)
1415 OOM_ABORT;
1416 op->op = REL_CURVE_TO;
1417 op->op_args = args;
1418 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1419 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 6)
1420 return FAILURE;
1421 } else if (eql(action, "rectangle")) {
1422 struct rectangle_args *args;
1424 if ((args = malloc(sizeof(*args))) == NULL)
1425 OOM_ABORT;
1426 op->op = RECTANGLE;
1427 op->op_args = args;
1428 if (sscanf(raw_args, "%lf %lf %lf %lf %c",
1429 &args->x, &args->y, &args->width, &args->height, &dummy) != 4)
1430 return FAILURE;
1431 } else if (eql(action, "close_path")) {
1432 op->op = CLOSE_PATH;
1433 if (sscanf(raw_args, " %c", &dummy) > 0)
1434 return FAILURE;
1435 op->op_args = NULL;
1436 } else if (eql(action, "show_text")) {
1437 struct show_text_args *args;
1438 int len;
1440 len = strlen(raw_args) + 1;
1441 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1442 OOM_ABORT;
1443 op->op = SHOW_TEXT;
1444 op->op_args = args;
1445 args->len = len; /* not used */
1446 strncpy(args->text, raw_args, len);
1447 result = NEED_REDRAW;
1448 } else if (eql(action, "rel_move_for")) {
1449 char ref_point[2 + 1];
1450 int start, len;
1451 struct rel_move_for_args *args;
1453 if (sscanf(raw_args, "%2s %n", ref_point, &start) < 1)
1454 return FAILURE;
1455 len = strlen(raw_args + start) + 1;
1456 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1457 OOM_ABORT;
1458 if (eql(ref_point, "c"))
1459 args->ref = C;
1460 else if (eql(ref_point, "e"))
1461 args->ref = E;
1462 else if (eql(ref_point, "n"))
1463 args->ref = N;
1464 else if (eql(ref_point, "ne"))
1465 args->ref = NE;
1466 else if (eql(ref_point, "nw"))
1467 args->ref = NW;
1468 else if (eql(ref_point, "s"))
1469 args->ref = S;
1470 else if (eql(ref_point, "se"))
1471 args->ref = SE;
1472 else if (eql(ref_point, "sw"))
1473 args->ref = SW;
1474 else if (eql(ref_point, "w"))
1475 args->ref = W;
1476 else
1477 return FAILURE;
1478 op->op = REL_MOVE_FOR;
1479 op->op_args = args;
1480 args->len = len; /* not used */
1481 strncpy(args->text, (raw_args + start), len);
1482 } else if (eql(action, "stroke")) {
1483 op->op = STROKE;
1484 if (sscanf(raw_args, " %c", &dummy) > 0)
1485 return FAILURE;
1486 op->op_args = NULL;
1487 result = NEED_REDRAW;
1488 } else if (eql(action, "stroke_preserve")) {
1489 op->op = STROKE_PRESERVE;
1490 if (sscanf(raw_args, " %c", &dummy) > 0)
1491 return FAILURE;
1492 op->op_args = NULL;
1493 result = NEED_REDRAW;
1494 } else if (eql(action, "fill")) {
1495 op->op = FILL;
1496 if (sscanf(raw_args, " %c", &dummy) > 0)
1497 return FAILURE;
1498 op->op_args = NULL;
1499 result = NEED_REDRAW;
1500 } else if (eql(action, "fill_preserve")) {
1501 op->op = FILL_PRESERVE;
1502 if (sscanf(raw_args, " %c", &dummy) > 0)
1503 return FAILURE;
1504 op->op_args = NULL;
1505 result = NEED_REDRAW;
1506 } else if (eql(action, "set_dash")) {
1507 char *next, *end;
1508 char data1[strlen(raw_args) + 1];
1509 int n, i;
1510 struct set_dash_args *args;
1512 strcpy(data1, raw_args);
1513 next = end = data1;
1514 n = -1;
1515 do {
1516 n++;
1517 next = end;
1518 strtod(next, &end);
1519 } while (next != end);
1520 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
1521 OOM_ABORT;
1522 op->op = SET_DASH;
1523 op->op_args = args;
1524 args->num_dashes = n;
1525 for (i = 0, next = data1; i < n; i++, next = end) {
1526 args->dashes[i] = strtod(next, &end);
1528 } else if (eql(action, "set_font_face")) {
1529 char slant[7 + 1]; /* "oblique" */
1530 char weight[6 + 1]; /* "normal" */
1531 int family_start, family_len;
1532 struct set_font_face_args *args;
1534 if (sscanf(raw_args, "%7s %6s %n%*s", slant, weight, &family_start) != 2)
1535 return FAILURE;
1536 family_len = strlen(raw_args + family_start) + 1;
1537 if ((args = malloc(sizeof(*args) + family_len * sizeof(args->family[0]))) == NULL)
1538 OOM_ABORT;
1539 op->op = SET_FONT_FACE;
1540 op->op_args = args;
1541 strncpy(args->family, raw_args + family_start, family_len);
1542 if (eql(slant, "normal"))
1543 args->slant = CAIRO_FONT_SLANT_NORMAL;
1544 else if (eql(slant, "italic"))
1545 args->slant = CAIRO_FONT_SLANT_ITALIC;
1546 else if (eql(slant, "oblique"))
1547 args->slant = CAIRO_FONT_SLANT_OBLIQUE;
1548 else
1549 return FAILURE;
1550 if (eql(weight, "normal"))
1551 args->weight = CAIRO_FONT_WEIGHT_NORMAL;
1552 else if (eql(weight, "bold"))
1553 args->weight = CAIRO_FONT_WEIGHT_BOLD;
1554 else
1555 return FAILURE;
1556 } else if (eql(action, "set_font_size")) {
1557 struct set_font_size_args *args;
1559 if ((args = malloc(sizeof(*args))) == NULL)
1560 OOM_ABORT;
1561 op->op = SET_FONT_SIZE;
1562 op->op_args = args;
1563 if (sscanf(raw_args, "%lf %c", &args->size, &dummy) != 1)
1564 return FAILURE;
1565 } else if (eql(action, "set_line_cap")) {
1566 char str[6 + 1]; /* "square" */
1567 struct set_line_cap_args *args;
1569 if ((args = malloc(sizeof(*args))) == NULL)
1570 OOM_ABORT;
1571 op->op = SET_LINE_CAP;
1572 op->op_args = args;
1573 if (sscanf(raw_args, "%6s %c", str, &dummy) != 1)
1574 return FAILURE;
1575 if (eql(str, "butt"))
1576 args->line_cap = CAIRO_LINE_CAP_BUTT;
1577 else if (eql(str, "round"))
1578 args->line_cap = CAIRO_LINE_CAP_ROUND;
1579 else if (eql(str, "square"))
1580 args->line_cap = CAIRO_LINE_CAP_SQUARE;
1581 else
1582 return FAILURE;
1583 } else if (eql(action, "set_line_join")) {
1584 char str[5 + 1]; /* "miter" */
1585 struct set_line_join_args *args;
1587 if ((args = malloc(sizeof(*args))) == NULL)
1588 OOM_ABORT;
1589 op->op = SET_LINE_JOIN;
1590 op->op_args = args;
1591 if (sscanf(raw_args, "%5s %c", str, &dummy) != 1)
1592 return FAILURE;
1593 if (eql(str, "miter"))
1594 args->line_join = CAIRO_LINE_JOIN_MITER;
1595 else if (eql(str, "round"))
1596 args->line_join = CAIRO_LINE_JOIN_ROUND;
1597 else if (eql(str, "bevel"))
1598 args->line_join = CAIRO_LINE_JOIN_BEVEL;
1599 else
1600 return FAILURE;
1601 } else if (eql(action, "set_line_width")) {
1602 struct set_line_width_args *args;
1604 if ((args = malloc(sizeof(*args))) == NULL)
1605 OOM_ABORT;
1606 op->op = SET_LINE_WIDTH;
1607 op->op_args = args;
1608 if (sscanf(raw_args, "%lf %c", &args->width, &dummy) != 1)
1609 return FAILURE;
1610 } else if (eql(action, "set_source_rgba")) {
1611 struct set_source_rgba_args *args;
1613 if ((args = malloc(sizeof(*args))) == NULL)
1614 OOM_ABORT;
1615 op->op = SET_SOURCE_RGBA;
1616 op->op_args = args;
1617 gdk_rgba_parse(&args->color, raw_args);
1618 } else if (eql(action, "transform")) {
1619 char dummy;
1620 double xx, yx, xy, yy, x0, y0;
1622 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1623 &xx, &yx, &xy, &yy, &x0, &y0, &dummy) == 6) {
1624 struct transform_args *args;
1626 if ((args = malloc(sizeof(*args))) == NULL)
1627 OOM_ABORT;
1628 op->op_args = args;
1629 op->op = TRANSFORM;
1630 cairo_matrix_init(&args->matrix, xx, yx, xy, yy, x0, y0);
1631 } else if (sscanf(raw_args, " %c", &dummy) < 1) {
1632 op->op = RESET_CTM;
1633 op->op_args = NULL;
1634 } else
1635 return FAILURE;
1636 } else if (eql(action, "translate")) {
1637 double tx, ty;
1638 struct transform_args *args;
1640 if ((args = malloc(sizeof(*args))) == NULL)
1641 OOM_ABORT;
1642 op->op = TRANSFORM;
1643 op->op_args = args;
1644 if (sscanf(raw_args, "%lf %lf %c", &tx, &ty, &dummy) != 2)
1645 return FAILURE;
1646 cairo_matrix_init_translate(&args->matrix, tx, ty);
1647 } else if (eql(action, "scale")) {
1648 double sx, sy;
1649 struct transform_args *args;
1651 if ((args = malloc(sizeof(*args))) == NULL)
1652 OOM_ABORT;
1653 op->op = TRANSFORM;
1654 op->op_args = args;
1655 if (sscanf(raw_args, "%lf %lf %c", &sx, &sy, &dummy) != 2)
1656 return FAILURE;
1657 cairo_matrix_init_scale(&args->matrix, sx, sy);
1658 } else if (eql(action, "rotate")) {
1659 double angle;
1660 struct transform_args *args;
1662 if ((args = malloc(sizeof(*args))) == NULL)
1663 OOM_ABORT;
1664 op->op = TRANSFORM;
1665 op->op_args = args;
1666 if (sscanf(raw_args, "%lf %c", &angle, &dummy) != 1)
1667 return FAILURE;
1668 cairo_matrix_init_rotate(&args->matrix, angle * (M_PI / 180.L));
1669 } else
1670 return FAILURE;
1671 return result;
1675 * Add another element to widget's "draw_ops" list
1677 static enum draw_op_stat
1678 ins_draw_op(GObject *widget, const char *action, const char *data)
1680 enum draw_op_stat result;
1681 struct draw_op *new_op = NULL, *draw_ops = NULL, *prev_op = NULL;
1683 if ((new_op = malloc(sizeof(*new_op))) == NULL)
1684 OOM_ABORT;
1685 new_op->op_args = NULL;
1686 new_op->next = NULL;
1687 if ((result = set_draw_op(new_op, action, data)) == FAILURE) {
1688 free(new_op->op_args);
1689 free(new_op);
1690 return FAILURE;
1692 switch (new_op->policy) {
1693 case APPEND:
1694 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL)
1695 g_object_set_data(widget, "draw_ops", new_op);
1696 else {
1697 for (prev_op = draw_ops;
1698 prev_op->next != NULL;
1699 prev_op = prev_op->next);
1700 prev_op->next = new_op;
1702 break;
1703 case BEFORE:
1704 for (prev_op = NULL, draw_ops = g_object_get_data(widget, "draw_ops");
1705 draw_ops != NULL && draw_ops->id != new_op->before;
1706 prev_op = draw_ops, draw_ops = draw_ops->next);
1707 if (prev_op == NULL) { /* prepend a new first element */
1708 g_object_set_data(widget, "draw_ops", new_op);
1709 new_op->next = draw_ops;
1710 } else if (draw_ops == NULL) /* append */
1711 prev_op->next = new_op;
1712 else { /* insert */
1713 new_op->next = draw_ops;
1714 prev_op->next = new_op;
1716 break;
1717 case REPLACE:
1718 for (prev_op = NULL, draw_ops = g_object_get_data(widget, "draw_ops");
1719 draw_ops != NULL && draw_ops->id != new_op->id;
1720 prev_op = draw_ops, draw_ops = draw_ops->next);
1721 if (draw_ops == NULL && prev_op == NULL) /* start a new list */
1722 g_object_set_data(widget, "draw_ops", new_op);
1723 else if (prev_op == NULL) { /* replace the first element */
1724 g_object_set_data(widget, "draw_ops", new_op);
1725 new_op->next = draw_ops->next;
1726 free(draw_ops->op_args);
1727 free(draw_ops);
1728 } else if (draw_ops == NULL) /* append */
1729 prev_op->next = new_op;
1730 else { /* replace some other element */
1731 new_op->next = draw_ops->next;
1732 prev_op->next = new_op;
1733 free(draw_ops->op_args);
1734 free(draw_ops);
1736 break;
1737 default:
1738 ABORT;
1739 break;
1741 return result;
1745 * Remove all elements with the given id from widget's "draw_ops" list
1747 static enum draw_op_stat
1748 rem_draw_op(GObject *widget, const char *data)
1750 char dummy;
1751 struct draw_op *op, *next_op, *prev_op = NULL;
1752 unsigned long long int id;
1754 if (sscanf(data, "%llu %c", &id, &dummy) != 1)
1755 return FAILURE;
1756 op = g_object_get_data(widget, "draw_ops");
1757 while (op != NULL) {
1758 next_op = op->next;
1759 if (op->id == id) {
1760 if (prev_op == NULL) /* list head */
1761 g_object_set_data(widget, "draw_ops", op->next);
1762 else
1763 prev_op->next = op->next;
1764 free(op->op_args);
1765 free(op);
1766 } else
1767 prev_op = op;
1768 op = next_op;
1770 return NEED_REDRAW;
1773 static gboolean
1774 refresh_widget(GtkWidget *widget)
1776 gint height = gtk_widget_get_allocated_height(widget);
1777 gint width = gtk_widget_get_allocated_width(widget);
1779 gtk_widget_queue_draw_area(widget, 0, 0, width, height);
1780 return G_SOURCE_REMOVE;
1784 * Write the drawing from the GtkDrawingArea widget in an appropriate
1785 * format to file
1787 static enum draw_op_stat
1788 save_drawing(GtkWidget *widget, const char *fn)
1790 cairo_surface_t *sur;
1791 cairo_t *cr;
1792 int height = gtk_widget_get_allocated_height(widget);
1793 int width = gtk_widget_get_allocated_width(widget);
1795 if (has_suffix(fn, ".epsf") || has_suffix(fn, ".eps")) {
1796 sur = cairo_ps_surface_create(fn, width, height);
1797 cairo_ps_surface_set_eps(sur, TRUE);
1798 } else if (has_suffix(fn, ".pdf"))
1799 sur = cairo_pdf_surface_create(fn, width, height);
1800 else if (has_suffix(fn, ".ps"))
1801 sur = cairo_ps_surface_create(fn, width, height);
1802 else if (has_suffix(fn, ".svg"))
1803 sur = cairo_svg_surface_create(fn, width, height);
1804 else
1805 return FAILURE;
1806 cr = cairo_create(sur);
1807 cb_draw(widget, cr, NULL);
1808 cairo_destroy(cr);
1809 cairo_surface_destroy(sur);
1810 return SUCCESS;
1813 static void
1814 update_drawing_area(GObject *obj, const char *action,
1815 const char *data, const char *whole_msg, GType type)
1817 enum draw_op_stat dost;
1819 if (eql(action, "remove"))
1820 dost = rem_draw_op(obj, data);
1821 else if (eql(action, "save"))
1822 dost = save_drawing(GTK_WIDGET(obj), data);
1823 else
1824 dost = ins_draw_op(obj, action, data);
1825 switch (dost) {
1826 case NEED_REDRAW:
1827 gdk_threads_add_idle_full(G_PRIORITY_LOW,
1828 (GSourceFunc) refresh_widget,
1829 GTK_WIDGET(obj), NULL);
1830 break;
1831 case FAILURE:
1832 ign_cmd(type, whole_msg);
1833 break;
1834 case SUCCESS:
1835 break;
1836 default:
1837 ABORT;
1838 break;
1842 static void
1843 update_entry(GObject *obj, const char *action,
1844 const char *data, const char *whole_msg, GType type)
1846 GtkEntry *entry = GTK_ENTRY(obj);
1848 if (eql(action, "set_text"))
1849 gtk_entry_set_text(entry, data);
1850 else if (eql(action, "set_placeholder_text"))
1851 gtk_entry_set_placeholder_text(entry, data);
1852 else
1853 ign_cmd(type, whole_msg);
1856 static void
1857 update_expander(GObject *obj, const char *action,
1858 const char *data, const char *whole_msg, GType type)
1860 GtkExpander *expander = GTK_EXPANDER(obj);
1861 char dummy;
1862 unsigned int val;
1864 if (eql(action, "set_expanded") &&
1865 sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
1866 gtk_expander_set_expanded(expander, val);
1867 else if (eql(action, "set_label"))
1868 gtk_expander_set_label(expander, data);
1869 else
1870 ign_cmd(type, whole_msg);
1873 static void
1874 update_file_chooser_button(GObject *obj, const char *action,
1875 const char *data, const char *whole_msg, GType type)
1877 if (eql(action, "set_filename"))
1878 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
1879 else
1880 ign_cmd(type, whole_msg);
1883 static void
1884 update_file_chooser_dialog(GObject *obj, const char *action,
1885 const char *data, const char *whole_msg, GType type)
1887 GtkFileChooser *chooser = GTK_FILE_CHOOSER(obj);
1889 if (eql(action, "set_filename"))
1890 gtk_file_chooser_set_filename(chooser, data);
1891 else if (eql(action, "set_current_name"))
1892 gtk_file_chooser_set_current_name(chooser, data);
1893 else if (update_class_window(obj, action, data, whole_msg, type));
1894 else
1895 ign_cmd(type, whole_msg);
1898 static void
1899 update_focus(GObject *obj, const char *action,
1900 const char *data, const char *whole_msg, GType type)
1902 char dummy;
1904 (void) action;
1905 (void) data;
1906 if (sscanf(data, " %c", &dummy) < 1 &&
1907 gtk_widget_get_can_focus(GTK_WIDGET(obj)))
1908 gtk_widget_grab_focus(GTK_WIDGET(obj));
1909 else
1910 ign_cmd(type, whole_msg);
1913 static void
1914 update_font_button(GObject *obj, const char *action,
1915 const char *data, const char *whole_msg, GType type)
1917 GtkFontButton *font_button = GTK_FONT_BUTTON(obj);
1919 if (eql(action, "set_font_name"))
1920 gtk_font_button_set_font_name(font_button, data);
1921 else
1922 ign_cmd(type, whole_msg);
1925 static void
1926 update_frame(GObject *obj, const char *action,
1927 const char *data, const char *whole_msg, GType type)
1929 if (eql(action, "set_label"))
1930 gtk_frame_set_label(GTK_FRAME(obj), data);
1931 else
1932 ign_cmd(type, whole_msg);
1935 static void
1936 update_image(GObject *obj, const char *action,
1937 const char *data, const char *whole_msg, GType type)
1939 GtkIconSize size;
1940 GtkImage *image = GTK_IMAGE(obj);
1942 gtk_image_get_icon_name(image, NULL, &size);
1943 if (eql(action, "set_from_file"))
1944 gtk_image_set_from_file(image, data);
1945 else if (eql(action, "set_from_icon_name"))
1946 gtk_image_set_from_icon_name(image, data, size);
1947 else
1948 ign_cmd(type, whole_msg);
1951 static void
1952 update_label(GObject *obj, const char *action,
1953 const char *data, const char *whole_msg, GType type)
1955 if (eql(action, "set_text"))
1956 gtk_label_set_text(GTK_LABEL(obj), data);
1957 else
1958 ign_cmd(type, whole_msg);
1961 static void
1962 update_notebook(GObject *obj, const char *action,
1963 const char *data, const char *whole_msg, GType type)
1965 char dummy;
1966 int val, n_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(obj));
1968 if (eql(action, "set_current_page") &&
1969 sscanf(data, "%d %c", &val, &dummy) == 1 &&
1970 val >= 0 && val < n_pages)
1971 gtk_notebook_set_current_page(GTK_NOTEBOOK(obj), val);
1972 else
1973 ign_cmd(type, whole_msg);
1976 static void
1977 update_nothing(GObject *obj, const char *action,
1978 const char *data, const char *whole_msg, GType type)
1980 (void) obj;
1981 (void) action;
1982 (void) data;
1983 (void) whole_msg;
1984 (void) type;
1987 static void
1988 update_print_dialog(GObject *obj, const char *action,
1989 const char *data, const char *whole_msg, GType type)
1991 GtkPageSetup *page_setup;
1992 GtkPrintJob *job;
1993 GtkPrintSettings *settings;
1994 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(obj);
1995 GtkPrinter *printer;
1996 gint response_id;
1998 if (eql(action, "print")) {
1999 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
2000 switch (response_id) {
2001 case GTK_RESPONSE_OK:
2002 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
2003 settings = gtk_print_unix_dialog_get_settings(dialog);
2004 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
2005 job = gtk_print_job_new(data, printer, settings, page_setup);
2006 if (gtk_print_job_set_source_file(job, data, NULL))
2007 gtk_print_job_send(job, NULL, NULL, NULL);
2008 else
2009 ign_cmd(type, whole_msg);
2010 g_clear_object(&settings);
2011 g_clear_object(&job);
2012 break;
2013 case GTK_RESPONSE_CANCEL:
2014 case GTK_RESPONSE_DELETE_EVENT:
2015 break;
2016 default:
2017 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
2018 widget_name(GTK_BUILDABLE(dialog)), response_id);
2019 break;
2021 gtk_widget_hide(GTK_WIDGET(dialog));
2022 } else
2023 ign_cmd(type, whole_msg);
2026 static void
2027 update_progress_bar(GObject *obj, const char *action,
2028 const char *data, const char *whole_msg, GType type)
2030 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(obj);
2031 char dummy;
2032 double frac;
2034 if (eql(action, "set_text"))
2035 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
2036 else if (eql(action, "set_fraction") &&
2037 sscanf(data, "%lf %c", &frac, &dummy) == 1)
2038 gtk_progress_bar_set_fraction(progressbar, frac);
2039 else
2040 ign_cmd(type, whole_msg);
2043 static void
2044 update_scale(GObject *obj, const char *action,
2045 const char *data, const char *whole_msg, GType type)
2047 GtkRange *range = GTK_RANGE(obj);
2048 char dummy;
2049 double val1, val2;
2051 if (eql(action, "set_value") && sscanf(data, "%lf %c", &val1, &dummy) == 1)
2052 gtk_range_set_value(range, val1);
2053 else if (eql(action, "set_fill_level") &&
2054 sscanf(data, "%lf %c", &val1, &dummy) == 1) {
2055 gtk_range_set_fill_level(range, val1);
2056 gtk_range_set_show_fill_level(range, TRUE);
2057 } else if (eql(action, "set_fill_level") &&
2058 sscanf(data, " %c", &dummy) < 1)
2059 gtk_range_set_show_fill_level(range, FALSE);
2060 else if (eql(action, "set_range") &&
2061 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2062 gtk_range_set_range(range, val1, val2);
2063 else if (eql(action, "set_increments") &&
2064 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2065 gtk_range_set_increments(range, val1, val2);
2066 else
2067 ign_cmd(type, whole_msg);
2070 static void
2071 update_scrolled_window(GObject *obj, const char *action,
2072 const char *data, const char *whole_msg, GType type)
2074 GtkScrolledWindow *window = GTK_SCROLLED_WINDOW(obj);
2075 GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(window);
2076 GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(window);
2077 char dummy;
2078 double d0, d1;
2080 if (eql(action, "hscroll") && sscanf(data, "%lf %c", &d0, &dummy) == 1)
2081 gtk_adjustment_set_value(hadj, d0);
2082 else if (eql(action, "vscroll") && sscanf(data, "%lf %c", &d0, &dummy) == 1)
2083 gtk_adjustment_set_value(vadj, d0);
2084 else if (eql(action, "hscroll_to_range") &&
2085 sscanf(data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
2086 gtk_adjustment_clamp_page(hadj, d0, d1);
2087 else if (eql(action, "vscroll_to_range") &&
2088 sscanf(data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
2089 gtk_adjustment_clamp_page(vadj, d0, d1);
2090 else
2091 ign_cmd(type, whole_msg);
2094 static void
2095 update_sensitivity(GObject *obj, const char *action,
2096 const char *data, const char *whole_msg, GType type)
2098 char dummy;
2099 unsigned int val;
2101 (void) action;
2102 (void) whole_msg;
2103 (void) type;
2104 if (sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2105 gtk_widget_set_sensitive(GTK_WIDGET(obj), val);
2106 else
2107 ign_cmd(type, whole_msg);
2110 static void
2111 update_size_request(GObject *obj, const char *action,
2112 const char *data, const char *whole_msg, GType type)
2114 char dummy;
2115 int x, y;
2117 (void) action;
2118 (void) whole_msg;
2119 (void) type;
2120 if (sscanf(data, "%d %d %c", &x, &y, &dummy) == 2)
2121 gtk_widget_set_size_request(GTK_WIDGET(obj), x, y);
2122 else if (sscanf(data, " %c", &dummy) < 1)
2123 gtk_widget_set_size_request(GTK_WIDGET(obj), -1, -1);
2124 else
2125 ign_cmd(type, whole_msg);
2128 static void
2129 update_socket(GObject *obj, const char *action,
2130 const char *data, const char *whole_msg, GType type)
2132 GtkSocket *socket = GTK_SOCKET(obj);
2133 Window id;
2134 char str[BUFLEN], dummy;
2136 (void) data;
2137 if (eql(action, "id") && sscanf(data, " %c", &dummy) < 1) {
2138 id = gtk_socket_get_id(socket);
2139 snprintf(str, BUFLEN, "%lu", id);
2140 send_msg(GTK_BUILDABLE(socket), "id", str, NULL);
2141 } else
2142 ign_cmd(type, whole_msg);
2145 static void
2146 update_spin_button(GObject *obj, const char *action,
2147 const char *data, const char *whole_msg, GType type)
2149 GtkSpinButton *spinbutton = GTK_SPIN_BUTTON(obj);
2150 char dummy;
2151 double val1, val2;
2153 if (eql(action, "set_text") && /* TODO: rename to "set_value" */
2154 sscanf(data, "%lf %c", &val1, &dummy) == 1)
2155 gtk_spin_button_set_value(spinbutton, val1);
2156 else if (eql(action, "set_range") &&
2157 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2158 gtk_spin_button_set_range(spinbutton, val1, val2);
2159 else if (eql(action, "set_increments") &&
2160 sscanf(data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2161 gtk_spin_button_set_increments(spinbutton, val1, val2);
2162 else
2163 ign_cmd(type, whole_msg);
2166 static void
2167 update_spinner(GObject *obj, const char *action,
2168 const char *data, const char *whole_msg, GType type)
2170 GtkSpinner *spinner = GTK_SPINNER(obj);
2171 char dummy;
2173 (void) data;
2174 if (eql(action, "start") && sscanf(data, " %c", &dummy) < 1)
2175 gtk_spinner_start(spinner);
2176 else if (eql(action, "stop") && sscanf(data, " %c", &dummy) < 1)
2177 gtk_spinner_stop(spinner);
2178 else
2179 ign_cmd(type, whole_msg);
2182 static void
2183 update_statusbar(GObject *obj, const char *action,
2184 const char *data, const char *whole_msg, GType type)
2186 GtkStatusbar *statusbar = GTK_STATUSBAR(obj);
2187 char *ctx_msg, dummy;
2188 const char *msg;
2189 int ctx_len, t;
2191 /* TODO: remove "push", "pop", "remove_all"; rename "push_id" to "push", etc. */
2192 if ((ctx_msg = malloc(strlen(data) + 1)) == NULL)
2193 OOM_ABORT;
2194 t = sscanf(data, "%s %n%c", ctx_msg, &ctx_len, &dummy);
2195 msg = data + ctx_len;
2196 if (eql(action, "push"))
2197 gtk_statusbar_push(statusbar,
2198 gtk_statusbar_get_context_id(statusbar, "0"),
2199 data);
2200 else if (eql(action, "push_id") && t >= 1)
2201 gtk_statusbar_push(statusbar,
2202 gtk_statusbar_get_context_id(statusbar, ctx_msg),
2203 msg);
2204 else if (eql(action, "pop") && t < 1)
2205 gtk_statusbar_pop(statusbar,
2206 gtk_statusbar_get_context_id(statusbar, "0"));
2207 else if (eql(action, "pop_id") && t == 1)
2208 gtk_statusbar_pop(statusbar,
2209 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2210 else if (eql(action, "remove_all") && t < 1)
2211 gtk_statusbar_remove_all(statusbar,
2212 gtk_statusbar_get_context_id(statusbar, "0"));
2213 else if (eql(action, "remove_all_id") && t == 1)
2214 gtk_statusbar_remove_all(statusbar,
2215 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2216 else
2217 ign_cmd(type, whole_msg);
2218 free(ctx_msg);
2221 static void
2222 update_switch(GObject *obj, const char *action,
2223 const char *data, const char *whole_msg, GType type)
2225 char dummy;
2226 unsigned int val;
2228 if (eql(action, "set_active") &&
2229 sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2230 gtk_switch_set_active(GTK_SWITCH(obj), val);
2231 else
2232 ign_cmd(type, whole_msg);
2235 static void
2236 update_text_view(GObject *obj, const char *action,
2237 const char *data, const char *whole_msg, GType type)
2239 GtkTextView *view = GTK_TEXT_VIEW(obj);
2240 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
2241 GtkTextIter a, b;
2242 char dummy;
2243 int val;
2245 if (eql(action, "set_text"))
2246 gtk_text_buffer_set_text(textbuf, data, -1);
2247 else if (eql(action, "delete") && sscanf(data, " %c", &dummy) < 1) {
2248 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2249 gtk_text_buffer_delete(textbuf, &a, &b);
2250 } else if (eql(action, "insert_at_cursor"))
2251 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
2252 else if (eql(action, "place_cursor") && eql(data, "end")) {
2253 gtk_text_buffer_get_end_iter(textbuf, &a);
2254 gtk_text_buffer_place_cursor(textbuf, &a);
2255 } else if (eql(action, "place_cursor") &&
2256 sscanf(data, "%d %c", &val, &dummy) == 1) {
2257 gtk_text_buffer_get_iter_at_offset(textbuf, &a, val);
2258 gtk_text_buffer_place_cursor(textbuf, &a);
2259 } else if (eql(action, "place_cursor_at_line") &&
2260 sscanf(data, "%d %c", &val, &dummy) == 1) {
2261 gtk_text_buffer_get_iter_at_line(textbuf, &a, val);
2262 gtk_text_buffer_place_cursor(textbuf, &a);
2263 } else if (eql(action, "scroll_to_cursor") &&
2264 sscanf(data, " %c", &dummy) < 1)
2265 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
2266 0., 0, 0., 0.);
2267 else if (eql(action, "save") && data != NULL &&
2268 (save = fopen(data, "w")) != NULL) {
2269 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2270 save_msg(GTK_BUILDABLE(view), "insert_at_cursor",
2271 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
2272 fclose(save);
2273 } else
2274 ign_cmd(type, whole_msg);
2277 static void
2278 update_toggle_button(GObject *obj, const char *action,
2279 const char *data, const char *whole_msg, GType type)
2281 char dummy;
2282 unsigned int val;
2284 if (eql(action, "set_label"))
2285 gtk_button_set_label(GTK_BUTTON(obj), data);
2286 else if (eql(action, "set_active") &&
2287 sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2288 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), val);
2289 else
2290 ign_cmd(type, whole_msg);
2293 static void
2294 update_tooltip_text(GObject *obj, const char *action,
2295 const char *data, const char *whole_msg, GType type)
2297 (void) action;
2298 (void) whole_msg;
2299 (void) type;
2300 gtk_widget_set_tooltip_text(GTK_WIDGET(obj), data);
2304 * update_tree_view() needs a few helper functions
2308 * Check if s is a valid string representation of a GtkTreePath
2310 static bool
2311 is_path_string(char *s)
2313 return s != NULL &&
2314 strlen(s) == strspn(s, ":0123456789") &&
2315 strstr(s, "::") == NULL &&
2316 strcspn(s, ":") > 0;
2319 static void
2320 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
2321 GtkTreeIter *parent, GtkTreeIter *sibling)
2323 if (GTK_IS_TREE_STORE(model))
2324 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
2325 iter, parent, sibling);
2326 else if (GTK_IS_LIST_STORE(model))
2327 gtk_list_store_insert_before(GTK_LIST_STORE(model),
2328 iter, sibling);
2329 else
2330 ABORT;
2333 static void
2334 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
2335 GtkTreeIter *parent, GtkTreeIter *sibling)
2337 if (GTK_IS_TREE_STORE(model))
2338 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
2339 iter, parent, sibling);
2340 else if (GTK_IS_LIST_STORE(model))
2341 gtk_list_store_insert_after(GTK_LIST_STORE(model),
2342 iter, sibling);
2343 else
2344 ABORT;
2347 static void
2348 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
2349 GtkTreeIter *position)
2351 if (GTK_IS_TREE_STORE(model))
2352 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
2353 else if (GTK_IS_LIST_STORE(model))
2354 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
2355 else
2356 ABORT;
2359 static void
2360 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
2362 if (GTK_IS_TREE_STORE(model))
2363 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
2364 else if (GTK_IS_LIST_STORE(model))
2365 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
2366 else
2367 ABORT;
2370 static void
2371 tree_model_clear(GtkTreeModel *model)
2373 if (GTK_IS_TREE_STORE(model))
2374 gtk_tree_store_clear(GTK_TREE_STORE(model));
2375 else if (GTK_IS_LIST_STORE(model))
2376 gtk_list_store_clear(GTK_LIST_STORE(model));
2377 else
2378 ABORT;
2381 static void
2382 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
2384 va_list ap;
2386 va_start(ap, iter);
2387 if (GTK_IS_TREE_STORE(model))
2388 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
2389 else if (GTK_IS_LIST_STORE(model))
2390 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
2391 else
2392 ABORT;
2393 va_end(ap);
2397 * Create an empty row at path if it doesn't yet exist. Create older
2398 * siblings and parents as necessary.
2400 static void
2401 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
2403 GtkTreeIter iter_1; /* iter's predecessor */
2404 GtkTreePath *path_1; /* path's predecessor */
2406 if (gtk_tree_model_get_iter(model, iter, path))
2407 return;
2408 path_1 = gtk_tree_path_copy(path);
2409 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
2410 create_subtree(model, path_1, iter);
2411 iter_1 = *iter;
2412 tree_model_insert_after(model, iter, NULL, &iter_1);
2413 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
2414 create_subtree(model, path_1, iter);
2415 if (gtk_tree_path_get_depth(path_1) == 0)
2416 /* first toplevel row */
2417 tree_model_insert_after(model, iter, NULL, NULL);
2418 else { /* first row in a lower level */
2419 iter_1 = *iter;
2420 tree_model_insert_after(model, iter, &iter_1, NULL);
2422 } /* neither prev nor up mean we're at the root of an empty tree */
2423 gtk_tree_path_free(path_1);
2426 static bool
2427 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
2428 const char *path_s, int col, const char *new_text)
2430 GType col_type = gtk_tree_model_get_column_type(model, col);
2431 GtkTreePath *path;
2432 bool ok = false;
2433 char *endptr;
2434 double d;
2435 long long int n;
2437 path = gtk_tree_path_new_from_string(path_s);
2438 create_subtree(model, path, iter);
2439 gtk_tree_path_free(path);
2440 switch (col_type) {
2441 case G_TYPE_BOOLEAN:
2442 case G_TYPE_INT:
2443 case G_TYPE_LONG:
2444 case G_TYPE_INT64:
2445 case G_TYPE_UINT:
2446 case G_TYPE_ULONG:
2447 case G_TYPE_UINT64:
2448 errno = 0;
2449 endptr = NULL;
2450 n = strtoll(new_text, &endptr, 10);
2451 if (!errno && endptr != new_text) {
2452 tree_model_set(model, iter, col, n, -1);
2453 ok = true;
2455 break;
2456 case G_TYPE_FLOAT:
2457 case G_TYPE_DOUBLE:
2458 errno = 0;
2459 endptr = NULL;
2460 d = strtod(new_text, &endptr);
2461 if (!errno && endptr != new_text) {
2462 tree_model_set(model, iter, col, d, -1);
2463 ok = true;
2465 break;
2466 case G_TYPE_STRING:
2467 tree_model_set(model, iter, col, new_text, -1);
2468 ok = true;
2469 break;
2470 default:
2471 fprintf(stderr, "column %d: %s not implemented\n",
2472 col, g_type_name(col_type));
2473 ok = true;
2474 break;
2476 return ok;
2479 static void
2480 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
2482 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
2483 /* is just fine and this function need not exist. */
2484 if (path == NULL)
2485 path = gtk_tree_path_new();
2486 gtk_tree_view_set_cursor(view, path, col, false);
2489 static void
2490 update_tree_view(GObject *obj, const char *action,
2491 const char *data, const char *whole_msg, GType type)
2493 GtkTreeView *view = GTK_TREE_VIEW(obj);
2494 GtkTreeIter iter0, iter1;
2495 GtkTreeModel *model = gtk_tree_view_get_model(view);
2496 GtkTreePath *path = NULL;
2497 bool iter0_valid, iter1_valid;
2498 char *tokens, *arg0, *arg1, *arg2;
2499 int col = -1; /* invalid column number */
2501 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
2503 fprintf(stderr, "missing model/");
2504 ign_cmd(type, whole_msg);
2505 return;
2507 if ((tokens = malloc(strlen(data) + 1)) == NULL)
2508 OOM_ABORT;
2509 strcpy(tokens, data);
2510 arg0 = strtok(tokens, WHITESPACE);
2511 arg1 = strtok(NULL, WHITESPACE);
2512 arg2 = strtok(NULL, "");
2513 iter0_valid = is_path_string(arg0) &&
2514 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
2515 iter1_valid = is_path_string(arg1) &&
2516 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
2517 if (is_path_string(arg1))
2518 col = strtol(arg1, NULL, 10);
2519 if (eql(action, "set") &&
2520 col > -1 &&
2521 col < gtk_tree_model_get_n_columns(model) &&
2522 is_path_string(arg0)) {
2523 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
2524 ign_cmd(type, whole_msg);
2525 } else if (eql(action, "scroll") && iter0_valid && iter1_valid &&
2526 arg2 == NULL) {
2527 path = gtk_tree_path_new_from_string(arg0);
2528 gtk_tree_view_scroll_to_cell (view,
2529 path,
2530 gtk_tree_view_get_column(view, col),
2531 0, 0., 0.);
2532 } else if (eql(action, "expand") && iter0_valid && arg1 == NULL) {
2533 path = gtk_tree_path_new_from_string(arg0);
2534 gtk_tree_view_expand_row(view, path, false);
2535 } else if (eql(action, "expand_all") && iter0_valid && arg1 == NULL) {
2536 path = gtk_tree_path_new_from_string(arg0);
2537 gtk_tree_view_expand_row(view, path, true);
2538 } else if (eql(action, "expand_all") && arg0 == NULL)
2539 gtk_tree_view_expand_all(view);
2540 else if (eql(action, "collapse") && iter0_valid && arg1 == NULL) {
2541 path = gtk_tree_path_new_from_string(arg0);
2542 gtk_tree_view_collapse_row(view, path);
2543 } else if (eql(action, "collapse") && arg0 == NULL)
2544 gtk_tree_view_collapse_all(view);
2545 else if (eql(action, "set_cursor") && iter0_valid && arg1 == NULL) {
2546 path = gtk_tree_path_new_from_string(arg0);
2547 tree_view_set_cursor(view, path, NULL);
2548 } else if (eql(action, "set_cursor") && arg0 == NULL) {
2549 tree_view_set_cursor(view, NULL, NULL);
2550 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2551 } else if (eql(action, "insert_row") &&
2552 eql(arg0, "end") && arg1 == NULL)
2553 tree_model_insert_before(model, &iter1, NULL, NULL);
2554 else if (eql(action, "insert_row") && iter0_valid &&
2555 eql(arg1, "as_child") && arg2 == NULL)
2556 tree_model_insert_after(model, &iter1, &iter0, NULL);
2557 else if (eql(action, "insert_row") && iter0_valid && arg1 == NULL)
2558 tree_model_insert_before(model, &iter1, NULL, &iter0);
2559 else if (eql(action, "move_row") && iter0_valid &&
2560 eql(arg1, "end") && arg2 == NULL)
2561 tree_model_move_before(model, &iter0, NULL);
2562 else if (eql(action, "move_row") && iter0_valid && iter1_valid && arg2 == NULL)
2563 tree_model_move_before(model, &iter0, &iter1);
2564 else if (eql(action, "remove_row") && iter0_valid && arg1 == NULL)
2565 tree_model_remove(model, &iter0);
2566 else if (eql(action, "clear") && arg0 == NULL) {
2567 tree_view_set_cursor(view, NULL, NULL);
2568 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2569 tree_model_clear(model);
2570 } else if (eql(action, "save") && arg0 != NULL &&
2571 (save = fopen(arg0, "w")) != NULL) {
2572 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc) save_tree_row_msg, view);
2573 fclose(save);
2574 } else
2575 ign_cmd(type, whole_msg);
2576 free(tokens);
2577 gtk_tree_path_free(path);
2580 static void
2581 update_visibility(GObject *obj, const char *action,
2582 const char *data, const char *whole_msg, GType type)
2584 char dummy;
2585 unsigned int val;
2587 (void) action;
2588 (void) whole_msg;
2589 (void) type;
2590 if (sscanf(data, "%u %c", &val, &dummy) == 1 && val < 2)
2591 gtk_widget_set_visible(GTK_WIDGET(obj), val);
2592 else
2593 ign_cmd(type, whole_msg);
2597 * Change the style of the widget passed
2599 static void
2600 update_widget_style(GObject *obj, const char *name,
2601 const char *data, const char *whole_msg, GType type)
2603 GtkStyleContext *context;
2604 GtkStyleProvider *style_provider;
2605 char *style_decl;
2606 const char *prefix = "* {", *suffix = "}";
2607 size_t sz;
2609 (void) name;
2610 (void) whole_msg;
2611 (void) type;
2612 style_provider = g_object_get_data(obj, "style_provider");
2613 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
2614 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2615 gtk_style_context_remove_provider(context, style_provider);
2616 if ((style_decl = malloc(sz)) == NULL)
2617 OOM_ABORT;
2618 strcpy(style_decl, prefix);
2619 strcat(style_decl, data);
2620 strcat(style_decl, suffix);
2621 gtk_style_context_add_provider(context, style_provider,
2622 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2623 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
2624 style_decl, -1, NULL);
2625 free(style_decl);
2628 static void
2629 update_window(GObject *obj, const char *action,
2630 const char *data, const char *whole_msg, GType type)
2632 if (!update_class_window(obj, action, data, whole_msg, type))
2633 ign_cmd(type, whole_msg);
2637 * Simulate user activity on various widgets
2639 static void
2640 fake_ui_activity(GObject *obj, const char *action,
2641 const char *data, const char *whole_msg, GType type)
2643 char dummy;
2645 (void) action;
2646 (void) data;
2647 if (!GTK_IS_WIDGET(obj) || sscanf(data, " %c", &dummy) > 0)
2648 ign_cmd(type, whole_msg);
2649 else if (GTK_IS_SPIN_BUTTON(obj))
2650 cb_spin_button(GTK_BUILDABLE(obj), "text"); /* TODO: rename to "value" */
2651 else if (GTK_IS_SCALE(obj))
2652 cb_range(GTK_BUILDABLE(obj), "value");
2653 else if (GTK_IS_ENTRY(obj))
2654 cb_editable(GTK_BUILDABLE(obj), "text");
2655 else if (GTK_IS_CALENDAR(obj))
2656 cb_calendar(GTK_BUILDABLE(obj), "clicked");
2657 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
2658 cb_file_chooser_button(GTK_BUILDABLE(obj), "file");
2659 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
2660 ign_cmd(type, whole_msg);
2664 * The final UI update
2666 static void
2667 main_quit(GObject *obj, const char *action,
2668 const char *data, const char *whole_msg, GType type)
2670 char dummy;
2672 (void) obj;
2673 (void) action;
2674 (void) data;
2675 (void) whole_msg;
2676 (void) type;
2677 if (sscanf(data, " %c", &dummy) < 1)
2678 gtk_main_quit();
2679 else
2680 ign_cmd(type, whole_msg);
2684 * Don't update anything; just complain
2686 static void
2687 complain(GObject *obj, const char *action,
2688 const char *data, const char *whole_msg, GType type)
2690 (void) obj;
2691 (void) action;
2692 (void) data;
2693 ign_cmd(type, whole_msg);
2697 * Data to be passed to and from the GTK main loop
2699 struct ui_data {
2700 void (*fn)(GObject *, const char *action,
2701 const char *data, const char *msg, GType type);
2702 GObject *obj;
2703 char *action;
2704 char *data;
2705 char *msg;
2706 char *msg_tokens;
2707 GType type;
2711 * Parse command pointed to by ud, and act on ui accordingly. Runs
2712 * once per command inside gtk_main_loop().
2714 static gboolean
2715 update_ui(struct ui_data *ud)
2717 char *lc = lc_numeric();
2719 (ud->fn)(ud->obj, ud->action, ud->data, ud->msg, ud->type);
2720 free(ud->msg_tokens);
2721 free(ud->msg);
2722 free(ud);
2723 lc_numeric_free(lc);
2724 return G_SOURCE_REMOVE;
2728 * Keep track of loading files to avoid recursive loading of the same
2729 * file. If filename = NULL, forget the most recently remembered file.
2731 static bool
2732 remember_loading_file(char *filename)
2734 static char *filenames[BUFLEN];
2735 static size_t latest = 0;
2736 size_t i;
2738 if (filename == NULL) { /* pop */
2739 if (latest < 1)
2740 ABORT;
2741 latest--;
2742 return false;
2743 } else { /* push */
2744 for (i = 1; i <= latest; i++)
2745 if (eql(filename, filenames[i]))
2746 return false;
2747 if (latest > BUFLEN -2)
2748 return false;
2749 filenames[++latest] = filename;
2750 return true;
2755 * Read lines from stream cmd and perform appropriate actions on the
2756 * GUI
2758 static void *
2759 digest_msg(FILE *cmd)
2761 FILE *load; /* restoring user data */
2762 char *name;
2763 static int recursion = -1; /* > 0 means this is a recursive call */
2765 recursion++;
2766 for (;;) {
2767 struct ui_data *ud;
2768 char first_char = '\0';
2769 size_t msg_size = 32;
2770 int name_start = 0, name_end = 0;
2771 int action_start = 0, action_end = 0;
2772 int data_start;
2774 if (feof(cmd))
2775 break;
2776 if ((ud = malloc(sizeof(*ud))) == NULL)
2777 OOM_ABORT;
2778 if ((ud->msg = malloc(msg_size)) == NULL)
2779 OOM_ABORT;
2780 ud->type = G_TYPE_INVALID;
2781 pthread_testcancel();
2782 if (recursion == 0)
2783 log_msg(NULL);
2784 read_buf(cmd, &ud->msg, &msg_size);
2785 if (recursion == 0)
2786 log_msg(ud->msg);
2787 data_start = strlen(ud->msg);
2788 if ((ud->msg_tokens = malloc(strlen(ud->msg) + 1)) == NULL)
2789 OOM_ABORT;
2790 strcpy(ud->msg_tokens, ud->msg);
2791 sscanf(ud->msg, " %c", &first_char);
2792 if (strlen(ud->msg) == 0 || first_char == '#') { /* comment */
2793 ud->fn = update_nothing;
2794 goto exec;
2796 sscanf(ud->msg_tokens,
2797 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2798 &name_start, &name_end, &action_start, &action_end, &data_start);
2799 ud->msg_tokens[name_end] = ud->msg_tokens[action_end] = '\0';
2800 name = ud->msg_tokens + name_start;
2801 ud->action = ud->msg_tokens + action_start;
2802 ud->data = ud->msg_tokens + data_start;
2803 if (eql(ud->action, "main_quit")) {
2804 ud->fn = main_quit;
2805 goto exec;
2807 if (eql(ud->action, "load") && strlen(ud->data) > 0 &&
2808 (load = fopen(ud->data, "r")) != NULL &&
2809 remember_loading_file(ud->data)) {
2810 digest_msg(load);
2811 fclose(load);
2812 remember_loading_file(NULL);
2813 ud->fn = update_nothing;
2814 goto exec;
2816 if ((ud->obj = (gtk_builder_get_object(builder, name))) == NULL) {
2817 ud->fn = complain;
2818 goto exec;
2820 ud->type = G_TYPE_FROM_INSTANCE(ud->obj);
2821 if (eql(ud->action, "force"))
2822 ud->fn = fake_ui_activity;
2823 else if (eql(ud->action, "set_sensitive"))
2824 ud->fn = update_sensitivity;
2825 else if (eql(ud->action, "set_visible"))
2826 ud->fn = update_visibility;
2827 else if (eql(ud->action, "set_size_request"))
2828 ud->fn = update_size_request;
2829 else if (eql(ud->action, "set_tooltip_text"))
2830 ud->fn = update_tooltip_text;
2831 else if (eql(ud->action, "grab_focus"))
2832 ud->fn = update_focus;
2833 else if (eql(ud->action, "style")) {
2834 ud->action = name;
2835 ud->fn = update_widget_style;
2836 } else if (ud->type == GTK_TYPE_DRAWING_AREA)
2837 ud->fn = update_drawing_area;
2838 else if (ud->type == GTK_TYPE_TREE_VIEW)
2839 ud->fn = update_tree_view;
2840 else if (ud->type == GTK_TYPE_COMBO_BOX_TEXT)
2841 ud->fn = update_combo_box_text;
2842 else if (ud->type == GTK_TYPE_LABEL)
2843 ud->fn = update_label;
2844 else if (ud->type == GTK_TYPE_IMAGE)
2845 ud->fn = update_image;
2846 else if (ud->type == GTK_TYPE_TEXT_VIEW)
2847 ud->fn = update_text_view;
2848 else if (ud->type == GTK_TYPE_NOTEBOOK)
2849 ud->fn = update_notebook;
2850 else if (ud->type == GTK_TYPE_EXPANDER)
2851 ud->fn = update_expander;
2852 else if (ud->type == GTK_TYPE_FRAME)
2853 ud->fn = update_frame;
2854 else if (ud->type == GTK_TYPE_SCROLLED_WINDOW)
2855 ud->fn = update_scrolled_window;
2856 else if (ud->type == GTK_TYPE_BUTTON)
2857 ud->fn = update_button;
2858 else if (ud->type == GTK_TYPE_FILE_CHOOSER_DIALOG)
2859 ud->fn = update_file_chooser_dialog;
2860 else if (ud->type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2861 ud->fn = update_file_chooser_button;
2862 else if (ud->type == GTK_TYPE_COLOR_BUTTON)
2863 ud->fn = update_color_button;
2864 else if (ud->type == GTK_TYPE_FONT_BUTTON)
2865 ud->fn = update_font_button;
2866 else if (ud->type == GTK_TYPE_PRINT_UNIX_DIALOG)
2867 ud->fn = update_print_dialog;
2868 else if (ud->type == GTK_TYPE_SWITCH)
2869 ud->fn = update_switch;
2870 else if (ud->type == GTK_TYPE_TOGGLE_BUTTON ||
2871 ud->type == GTK_TYPE_RADIO_BUTTON ||
2872 ud->type == GTK_TYPE_CHECK_BUTTON)
2873 ud->fn = update_toggle_button;
2874 else if (ud->type == GTK_TYPE_ENTRY)
2875 ud->fn = update_entry;
2876 else if (ud->type == GTK_TYPE_SPIN_BUTTON)
2877 ud->fn = update_spin_button;
2878 else if (ud->type == GTK_TYPE_SCALE)
2879 ud->fn = update_scale;
2880 else if (ud->type == GTK_TYPE_PROGRESS_BAR)
2881 ud->fn = update_progress_bar;
2882 else if (ud->type == GTK_TYPE_SPINNER)
2883 ud->fn = update_spinner;
2884 else if (ud->type == GTK_TYPE_STATUSBAR)
2885 ud->fn = update_statusbar;
2886 else if (ud->type == GTK_TYPE_CALENDAR)
2887 ud->fn = update_calendar;
2888 else if (ud->type == GTK_TYPE_SOCKET)
2889 ud->fn = update_socket;
2890 else if (ud->type == GTK_TYPE_WINDOW ||
2891 ud->type == GTK_TYPE_DIALOG)
2892 ud->fn = update_window;
2893 else
2894 ud->fn = complain;
2895 exec:
2896 pthread_testcancel();
2897 gdk_threads_add_timeout(0, (GSourceFunc) update_ui, ud);
2899 recursion--;
2900 return NULL;
2905 * ============================================================
2906 * Initialization
2907 * ============================================================
2911 * Attach to renderer key "col_number". Associate "col_number" with
2912 * the corresponding column number in the underlying model.
2913 * Due to what looks like a gap in the GTK API, renderer id and column
2914 * number are taken directly from the XML .ui file.
2916 static bool
2917 tree_view_column_get_renderer_column(const char *ui_file, GtkTreeViewColumn *t_col,
2918 int n, GtkCellRenderer **renderer)
2920 bool r = false;
2921 char *xpath_base1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
2922 char *xpath_base2 = "\"]/child[";
2923 char *xpath_base3 = "]/object[@class=\"GtkCellRendererText\""
2924 " or @class=\"GtkCellRendererToggle\"]/";
2925 char *xpath_renderer_id = "/@id";
2926 char *xpath_text_col = "../attributes/attribute[@name=\"text\""
2927 " or @name=\"active\"]";
2928 const char *xpath_id = widget_name(GTK_BUILDABLE(t_col));
2929 int i;
2930 size_t xpath_len;
2931 size_t xpath_n_len = 3; /* Big Enough (TM) */
2932 xmlChar *xpath, *renderer_name = NULL, *m_col_s = NULL;
2933 xmlDocPtr doc;
2934 xmlNodePtr cur;
2935 xmlNodeSetPtr nodes;
2936 xmlXPathContextPtr xpath_ctx;
2937 xmlXPathObjectPtr xpath_obj;
2939 if ((doc = xmlParseFile(ui_file)) == NULL)
2940 return false;
2941 if ((xpath_ctx = xmlXPathNewContext(doc)) == NULL) {
2942 xmlFreeDoc(doc);
2943 return false;
2945 xpath_len = 2 * (strlen(xpath_base1) + strlen(xpath_id) +
2946 strlen(xpath_base2) + xpath_n_len +
2947 strlen(xpath_base3)) +
2948 sizeof('|') +
2949 strlen(xpath_text_col) + strlen(xpath_renderer_id) +
2950 sizeof('\0');
2951 if ((xpath = malloc(xpath_len)) == NULL)
2952 OOM_ABORT;
2953 snprintf((char *) xpath, xpath_len, "%s%s%s%d%s%s|%s%s%s%d%s%s",
2954 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_text_col,
2955 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_renderer_id);
2956 if ((xpath_obj = xmlXPathEvalExpression(xpath, xpath_ctx)) == NULL) {
2957 xmlXPathFreeContext(xpath_ctx);
2958 free(xpath);
2959 xmlFreeDoc(doc);
2960 return false;
2962 if ((nodes = xpath_obj->nodesetval) != NULL) {
2963 for (i = 0; i < nodes->nodeNr; ++i) {
2964 if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
2965 cur = nodes->nodeTab[i];
2966 m_col_s = xmlNodeGetContent(cur);
2967 } else {
2968 cur = nodes->nodeTab[i];
2969 renderer_name = xmlNodeGetContent(cur);
2973 if (renderer_name) {
2974 *renderer = GTK_CELL_RENDERER(
2975 gtk_builder_get_object(builder, (char *) renderer_name));
2976 if (m_col_s) {
2977 g_object_set_data(G_OBJECT(*renderer), "col_number",
2978 GINT_TO_POINTER(strtol((char *) m_col_s,
2979 NULL, 10)));
2980 xmlFree(m_col_s);
2981 r = true;
2983 xmlFree(renderer_name);
2985 xmlXPathFreeObject(xpath_obj);
2986 xmlXPathFreeContext(xpath_ctx);
2987 free(xpath);
2988 xmlFreeDoc(doc);
2989 return r;
2993 * Callbacks that forward a modification of a tree view cell to the
2994 * underlying model
2996 static void
2997 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
2998 const gchar *new_text, gpointer view)
3000 GtkTreeIter iter;
3001 GtkTreeModel *model = gtk_tree_view_get_model(view);
3002 int col = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer),
3003 "col_number"));
3005 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
3006 set_tree_view_cell(model, &iter, path_s, col,
3007 new_text);
3008 send_tree_cell_msg_by(send_msg, model, path_s, &iter, col,
3009 GTK_BUILDABLE(view));
3012 static void
3013 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, gpointer view)
3015 GtkTreeIter iter;
3016 GtkTreeModel *model = gtk_tree_view_get_model(view);
3017 bool toggle_state;
3018 int col = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer),
3019 "col_number"));
3021 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
3022 gtk_tree_model_get(model, &iter, col, &toggle_state, -1);
3023 set_tree_view_cell(model, &iter, path_s, col,
3024 toggle_state? "0" : "1");
3027 static void
3028 connect_widget_signals(gpointer *obj, char *ui_file)
3030 GObject *obj2;
3031 GType type = G_TYPE_INVALID;
3032 char *suffix = NULL;
3033 const char *name = NULL;
3035 type = G_TYPE_FROM_INSTANCE(obj);
3036 if (GTK_IS_BUILDABLE(obj))
3037 name = widget_name(GTK_BUILDABLE(obj));
3038 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
3039 GList *cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(obj));
3040 GtkTreeViewColumn *tv_col = GTK_TREE_VIEW_COLUMN(obj);
3041 unsigned int i, n_cells = g_list_length(cells);
3043 g_list_free(cells);
3044 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
3045 for (i = 1; i <= n_cells; i++) {
3046 GtkCellRenderer *renderer;
3047 GtkTreeView *view = GTK_TREE_VIEW(
3048 gtk_tree_view_column_get_tree_view(tv_col));
3049 gboolean editable = FALSE;
3051 if (!tree_view_column_get_renderer_column(ui_file, tv_col,
3052 i, &renderer))
3053 continue;
3054 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
3055 g_object_get(renderer, "editable", &editable, NULL);
3056 if (editable)
3057 g_signal_connect(renderer, "edited",
3058 G_CALLBACK(cb_tree_model_edit), view);
3059 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
3060 g_object_get(renderer, "activatable", &editable, NULL);
3061 if (editable)
3062 g_signal_connect(renderer, "toggled",
3063 G_CALLBACK(cb_tree_model_toggle), view);
3066 } else if (type == GTK_TYPE_BUTTON) {
3067 /* Button associated with a GtkTextView. */
3068 if ((suffix = strstr(name, "_send_text")) != NULL &&
3069 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
3070 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
3071 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
3072 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
3073 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
3074 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
3075 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
3076 else {
3077 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
3078 /* Buttons associated with (and part of) a GtkDialog.
3079 * (We shun response ids which could be returned from
3080 * gtk_dialog_run() because that would require the
3081 * user to define those response ids in Glade,
3082 * numerically */
3083 if ((suffix = strstr(name, "_cancel")) != NULL &&
3084 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
3085 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
3086 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
3087 else
3088 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
3089 else if ((suffix = strstr(name, "_ok")) != NULL &&
3090 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name))) {
3091 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
3092 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), GTK_FILE_CHOOSER(obj2));
3093 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
3094 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
3095 else
3096 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
3097 } else if ((suffix = strstr(name, "_apply")) != NULL &&
3098 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
3099 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
3101 } else if (GTK_IS_MENU_ITEM(obj))
3102 if ((suffix = strstr(name, "_invoke")) != NULL &&
3103 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
3104 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
3105 else
3106 g_signal_connect(obj, "activate", G_CALLBACK(cb_menu_item), "active");
3107 else if (GTK_IS_WINDOW(obj)) {
3108 g_signal_connect(obj, "delete-event", G_CALLBACK(cb_event_simple), "closed");
3109 if (eql(name, MAIN_WIN))
3110 g_signal_connect_swapped(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
3111 else
3112 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
3113 } else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
3114 g_signal_connect(obj, "file-set", G_CALLBACK(cb_file_chooser_button), "file");
3115 else if (type == GTK_TYPE_COLOR_BUTTON)
3116 g_signal_connect(obj, "color-set", G_CALLBACK(cb_color_button), "color");
3117 else if (type == GTK_TYPE_FONT_BUTTON)
3118 g_signal_connect(obj, "font-set", G_CALLBACK(cb_font_button), "font");
3119 else if (type == GTK_TYPE_SWITCH)
3120 g_signal_connect(obj, "notify::active", G_CALLBACK(cb_switch), NULL);
3121 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
3122 g_signal_connect(obj, "toggled", G_CALLBACK(cb_toggle_button), NULL);
3123 else if (type == GTK_TYPE_ENTRY)
3124 g_signal_connect(obj, "changed", G_CALLBACK(cb_editable), "text");
3125 else if (type == GTK_TYPE_SPIN_BUTTON)
3126 g_signal_connect(obj, "value_changed", G_CALLBACK(cb_spin_button), "text"); /* TODO: rename to "value" */
3127 else if (type == GTK_TYPE_SCALE)
3128 g_signal_connect(obj, "value-changed", G_CALLBACK(cb_range), "value");
3129 else if (type == GTK_TYPE_CALENDAR) {
3130 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb_calendar), "doubleclicked");
3131 g_signal_connect(obj, "day-selected", G_CALLBACK(cb_calendar), "clicked");
3132 } else if (type == GTK_TYPE_TREE_SELECTION)
3133 g_signal_connect(obj, "changed", G_CALLBACK(cb_tree_selection), "clicked");
3134 else if (type == GTK_TYPE_SOCKET) {
3135 g_signal_connect(obj, "plug-added", G_CALLBACK(cb_simple), "plug-added");
3136 g_signal_connect(obj, "plug-removed", G_CALLBACK(cb_simple), "plug-removed");
3137 } else if (type == GTK_TYPE_DRAWING_AREA)
3138 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
3139 else if (type == GTK_TYPE_EVENT_BOX) {
3140 gtk_widget_set_can_focus(GTK_WIDGET(obj), true);
3141 g_signal_connect(obj, "button-press-event", G_CALLBACK(cb_event_box_button), "button_press");
3142 g_signal_connect(obj, "button-release-event", G_CALLBACK(cb_event_box_button), "button_release");
3143 g_signal_connect(obj, "motion-notify-event", G_CALLBACK(cb_event_box_motion), "motion");
3144 g_signal_connect(obj, "key-press-event", G_CALLBACK(cb_event_box_key), "key_press");
3149 * We keep a style provider with each widget
3151 static void
3152 add_widget_style_provider(gpointer *obj, void *data)
3154 GtkCssProvider *style_provider;
3155 GtkStyleContext *context;
3157 (void) data;
3158 if (!GTK_IS_WIDGET(obj))
3159 return;
3160 style_provider = gtk_css_provider_new();
3161 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
3162 gtk_style_context_add_provider(context,
3163 GTK_STYLE_PROVIDER(style_provider),
3164 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
3165 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
3168 static void
3169 prepare_widgets(char *ui_file)
3171 GSList *objects = NULL;
3173 objects = gtk_builder_get_objects(builder);
3174 g_slist_foreach(objects, (GFunc) connect_widget_signals, ui_file);
3175 g_slist_foreach(objects, (GFunc) add_widget_style_provider, NULL);
3176 g_slist_free(objects);
3180 main(int argc, char *argv[])
3182 FILE *in = NULL; /* command input */
3183 GObject *main_window = NULL;
3184 bool bg = false;
3185 char *in_fifo = NULL, *out_fifo = NULL;
3186 char *ui_file = "pipeglade.ui", *log_file = NULL, *err_file = NULL;
3187 char *xid = NULL;
3188 char opt;
3189 pthread_t receiver;
3191 /* Disable runtime GLIB deprecation warnings: */
3192 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
3193 out = NULL;
3194 save = NULL;
3195 log_out = NULL;
3196 gtk_init(&argc, &argv);
3197 while ((opt = getopt(argc, argv, "bGhe:i:l:o:O:u:V")) != -1) {
3198 switch (opt) {
3199 case 'b': bg = true; break;
3200 case 'e': xid = optarg; break;
3201 case 'G': show_lib_versions(); break;
3202 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
3203 case 'i': in_fifo = optarg; break;
3204 case 'l': log_file = optarg; break;
3205 case 'o': out_fifo = optarg; break;
3206 case 'O': err_file = optarg; break;
3207 case 'u': ui_file = optarg; break;
3208 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
3209 case '?':
3210 default: bye(EXIT_FAILURE, stderr, USAGE); break;
3213 if (argv[optind] != NULL)
3214 bye(EXIT_FAILURE, stderr,
3215 "illegal parameter '%s'\n" USAGE, argv[optind]);
3216 redirect_stderr(err_file);
3217 in = open_in_fifo(in_fifo);
3218 out = open_out_fifo(out_fifo);
3219 go_bg_if(bg, in, out, err_file);
3220 builder = builder_from_file(ui_file);
3221 log_out = open_log(log_file);
3222 pthread_create(&receiver, NULL, (void *(*)(void *)) digest_msg, in);
3223 main_window = find_main_window();
3224 xmlInitParser();
3225 LIBXML_TEST_VERSION;
3226 prepare_widgets(ui_file);
3227 xembed_if(xid, main_window);
3228 gtk_main();
3229 rm_unless(stdin, in, in_fifo);
3230 rm_unless(stdout, out, out_fifo);
3231 pthread_cancel(receiver);
3232 pthread_join(receiver, NULL);
3233 xmlCleanupParser();
3234 exit(EXIT_SUCCESS);