Do command parsing and UI updating in parallel
[pipeglade.git] / pipeglade.c
blobe9a0b2589e6486863f56d53ccd04ed1a876a61e2
1 /*
2 * Copyright (c) 2014-2016 Bert Burgemeister <trebbu@googlemail.com>
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <gtk/gtk.h>
27 #include <gtk/gtkunixprint.h>
28 #include <gtk/gtkx.h>
29 #include <inttypes.h>
30 #include <libxml/xpath.h>
31 #include <locale.h>
32 #include <math.h>
33 #include <pthread.h>
34 #include <stdio.h>
35 #include <stdarg.h>
36 #include <stdbool.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <sys/select.h>
40 #include <sys/stat.h>
41 #include <time.h>
42 #include <unistd.h>
44 #define VERSION "4.7.0"
45 #define BUFLEN 256
46 #define WHITESPACE " \t\n"
47 #define MAIN_WIN "main"
48 #define USAGE \
49 "usage: pipeglade [[-i in-fifo] " \
50 "[-o out-fifo] " \
51 "[-u glade-file.ui] " \
52 "[-e xid]\n" \
53 " [-l log-file] " \
54 "[--display X-server]] | " \
55 "[-h | " \
56 "-G | " \
57 "-V]\n"
59 #define ABORT \
60 { \
61 fprintf(stderr, \
62 "In %s (%s:%d): ", \
63 __func__, __FILE__, __LINE__); \
64 abort(); \
67 #define OOM_ABORT \
68 { \
69 fprintf(stderr, \
70 "Out of memory in %s (%s:%d): ", \
71 __func__, __FILE__, __LINE__); \
72 abort(); \
75 static FILE *out; /* UI feedback messages */
76 static FILE *save; /* saving user data */
77 static FILE *log_out; /* logging output */
78 static GtkBuilder *builder; /* to be read from .ui file */
81 * ============================================================
82 * Helper functions
83 * ============================================================
87 * Check if s1 and s2 are equal strings
89 static bool
90 eql(const char *s1, const char *s2)
92 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
96 * Print a formatted message to stream s and give up with status
98 static void
99 bye(int status, FILE *s, const char *fmt, ...)
101 va_list ap;
103 va_start(ap, fmt);
104 vfprintf(s, fmt, ap);
105 va_end(ap);
106 exit(status);
110 * Print a warning about a malformed command to stderr
112 static void
113 ign_cmd(GType type, const char *msg)
115 const char *name, *pad = " ";
117 if (type == G_TYPE_INVALID) {
118 name = "";
119 pad = "";
121 else
122 name = g_type_name(type);
123 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
127 * Create a fifo if necessary, and open it. Give up if the file
128 * exists but is not a fifo
130 static FILE *
131 fifo(const char *name, const char *mode)
133 struct stat sb;
134 int fd;
135 FILE *s = NULL;
136 int bufmode;
138 if (name != NULL) {
139 stat(name, &sb);
140 if (S_ISFIFO(sb.st_mode)) {
141 if (chmod(name, 0600) != 0)
142 bye(EXIT_FAILURE, stderr,
143 "using pre-existing fifo %s: %s\n",
144 name, strerror(errno));
145 } else if (mkfifo(name, 0600) != 0)
146 bye(EXIT_FAILURE, stderr,
147 "making fifo %s: %s\n", name, strerror(errno));
149 switch (mode[0]) {
150 case 'r':
151 bufmode = _IONBF;
152 if (name == NULL)
153 s = stdin;
154 else {
155 if ((fd = open(name, O_RDWR | O_NONBLOCK)) < 0)
156 bye(EXIT_FAILURE, stderr,
157 "opening fifo %s (%s): %s\n",
158 name, mode, strerror(errno));
159 s = fdopen(fd, "r");
161 break;
162 case 'w':
163 bufmode = _IOLBF;
164 if (name == NULL)
165 s = stdout;
166 else
167 s = fopen(name, "w+");
168 break;
169 default:
170 ABORT;
171 break;
173 if (s == NULL)
174 bye(EXIT_FAILURE, stderr, "opening fifo %s (%s): %s\n",
175 name, mode, strerror(errno));
176 else
177 setvbuf(s, NULL, bufmode, 0);
178 return s;
182 * Create a log file if necessary, and open it. A name of "-"
183 * requests use of stderr.
185 static FILE *
186 log_file(const char *name)
188 FILE *s = NULL;
190 if (eql(name, "-"))
191 s = stderr;
192 else if (name && (s = fopen(name, "a")) == NULL)
193 bye(EXIT_FAILURE, stderr,
194 "opening log file %s: %s\n", name, strerror(errno));
195 return s;
199 * Microseconds elapsed since start
201 static long int
202 usec_since(struct timespec *start)
204 struct timespec now;
206 clock_gettime(CLOCK_MONOTONIC, &now);
207 return (now.tv_sec - start->tv_sec) * 1e6 +
208 (now.tv_nsec - start->tv_nsec) / 1e3;
212 * Write log file
214 static void
215 log_msg(char *msg)
217 static struct timespec start;
218 static char *old_msg;
220 if (log_out == NULL) /* no logging */
221 return;
222 if (msg == NULL && old_msg == NULL)
223 fprintf(log_out,
224 "##########\t##### (New Pipeglade session) #####\n");
225 else if (msg == NULL && old_msg != NULL) { /* command done; start idle */
226 fprintf(log_out,
227 "%10ld\t%s\n", usec_since(&start), old_msg);
228 free(old_msg);
229 old_msg = NULL;
230 } else if (msg != NULL && old_msg == NULL) { /* idle done; start command */
231 fprintf(log_out,
232 "%10ld\t### (Idle) ###\n", usec_since(&start));
233 if ((old_msg = malloc(strlen(msg) + 1)) == NULL)
234 OOM_ABORT;
235 strcpy(old_msg, msg);
236 } else
237 ABORT;
238 clock_gettime(CLOCK_MONOTONIC, &start);
242 * Remove suffix from name; find the object named like this
244 static GObject *
245 obj_sans_suffix(const char *suffix, const char *name)
247 int str_l;
248 char str[BUFLEN + 1] = {'\0'};
250 str_l = suffix - name;
251 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
252 return gtk_builder_get_object(builder, str);
255 static const char *
256 widget_name(GtkBuildable *obj)
258 return gtk_buildable_get_name(obj);
262 * Store a line from stream s into buf, which should have been malloc'd
263 * to bufsize. Enlarge buf and bufsize if necessary.
265 static size_t
266 read_buf(FILE *s, char **buf, size_t *bufsize)
268 size_t i = 0;
269 int c;
270 fd_set rfds;
271 int ifd = fileno(s);
272 bool esc = false;
274 FD_ZERO(&rfds);
275 FD_SET(ifd, &rfds);
276 for (;;) {
277 select(ifd + 1, &rfds, NULL, NULL, NULL);
278 c = getc(s);
279 if (c == '\n' || feof(s))
280 break;
281 if (i >= *bufsize - 1)
282 if ((*buf = realloc(*buf, *bufsize = *bufsize * 2)) == NULL)
283 OOM_ABORT;
284 if (esc) {
285 esc = false;
286 switch (c) {
287 case 'n': (*buf)[i++] = '\n'; break;
288 case 'r': (*buf)[i++] = '\r'; break;
289 default: (*buf)[i++] = c; break;
291 } else if (c == '\\')
292 esc = true;
293 else
294 (*buf)[i++] = c;
296 (*buf)[i] = '\0';
297 return i;
301 * ============================================================
302 * Receiving feedback from the GUI
303 * ============================================================
306 static void
307 send_msg_to(FILE* o, GtkBuildable *obj, const char *tag, va_list ap)
309 char *data;
310 const char *w_name = widget_name(obj);
311 fd_set wfds;
312 int ofd = fileno(o);
313 struct timeval timeout = {1, 0};
315 FD_ZERO(&wfds);
316 FD_SET(ofd, &wfds);
317 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
318 fprintf(o, "%s:%s ", w_name, tag);
319 while ((data = va_arg(ap, char *)) != NULL) {
320 size_t i = 0;
321 char c;
323 while ((c = data[i++]) != '\0')
324 if (c == '\\')
325 fprintf(o, "\\\\");
326 else if (c == '\n')
327 fprintf(o, "\\n");
328 else
329 putc(c, o);
331 putc('\n', o);
332 } else
333 fprintf(stderr,
334 "send error; discarding feedback message %s:%s\n",
335 w_name, tag);
339 * Send GUI feedback to global stream "out". The message format is
340 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
341 * last argument must be NULL.
343 static void
344 send_msg(GtkBuildable *obj, const char *tag, ...)
346 va_list ap;
348 va_start(ap, tag);
349 send_msg_to(out, obj, tag, ap);
350 va_end(ap);
354 * Send message from GUI to global stream "save". The message format
355 * is "<origin>:<tag> <data ...>". The variadic arguments are strings;
356 * last argument must be NULL.
358 static void
359 save_msg(GtkBuildable *obj, const char *tag, ...)
361 va_list ap;
363 va_start(ap, tag);
364 send_msg_to(save, obj, tag, ap);
365 va_end(ap);
369 * Send message from GUI to global stream "save". The message format
370 * is "<origin>:set <data ...>". The variadic arguments are strings;
371 * last argument must be NULL.
373 static void
374 save_action_set_msg(GtkBuildable *obj, const char *tag, ...)
376 va_list ap;
378 va_start(ap, tag);
379 send_msg_to(save, obj, "set", ap);
380 va_end(ap);
384 * Use msg_sender() to send a message describing a particular cell
386 static void
387 send_tree_cell_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
388 GtkTreeModel *model, const char *path_s,
389 GtkTreeIter *iter, int col, GtkBuildable *obj)
391 GValue value = G_VALUE_INIT;
392 GType col_type;
393 char str[BUFLEN];
395 gtk_tree_model_get_value(model, iter, col, &value);
396 col_type = gtk_tree_model_get_column_type(model, col);
397 switch (col_type) {
398 case G_TYPE_INT:
399 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
400 msg_sender(obj, "gint", path_s, str, NULL);
401 break;
402 case G_TYPE_LONG:
403 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
404 msg_sender(obj, "glong", path_s, str, NULL);
405 break;
406 case G_TYPE_INT64:
407 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
408 msg_sender(obj, "gint64", path_s, str, NULL);
409 break;
410 case G_TYPE_UINT:
411 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
412 msg_sender(obj, "guint", path_s, str, NULL);
413 break;
414 case G_TYPE_ULONG:
415 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
416 msg_sender(obj, "gulong", path_s, str, NULL);
417 break;
418 case G_TYPE_UINT64:
419 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
420 msg_sender(obj, "guint64", path_s, str, NULL);
421 break;
422 case G_TYPE_BOOLEAN:
423 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
424 msg_sender(obj, "gboolean", path_s, str, NULL);
425 break;
426 case G_TYPE_FLOAT:
427 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
428 msg_sender(obj, "gfloat", path_s, str, NULL);
429 break;
430 case G_TYPE_DOUBLE:
431 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
432 msg_sender(obj, "gdouble", path_s, str, NULL);
433 break;
434 case G_TYPE_STRING:
435 snprintf(str, BUFLEN, " %d ", col);
436 msg_sender(obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
437 break;
438 default:
439 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
440 break;
442 g_value_unset(&value);
446 * Use msg_sender() to send one message per column for a single row
448 static void
449 send_tree_row_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
450 GtkTreeModel *model, char *path_s,
451 GtkTreeIter *iter, GtkBuildable *obj)
453 int col;
455 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++)
456 send_tree_cell_msg_by(msg_sender, model, path_s, iter, col, obj);
460 * send_tree_row_msg serves as an argument for
461 * gtk_tree_selection_selected_foreach()
463 static gboolean
464 send_tree_row_msg(GtkTreeModel *model,
465 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
467 char *path_s = gtk_tree_path_to_string(path);
469 send_tree_row_msg_by(send_msg, model, path_s, iter, obj);
470 g_free(path_s);
471 return FALSE;
475 * save_tree_row_msg serves as an argument for
476 * gtk_tree_model_foreach().
477 * Send message from GUI to global stream "save".
479 static gboolean
480 save_tree_row_msg(GtkTreeModel *model,
481 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
483 char *path_s = gtk_tree_path_to_string(path);
485 (void) path;
486 send_tree_row_msg_by(save_action_set_msg, model, path_s, iter, obj);
487 g_free(path_s);
488 return FALSE;
491 static void
492 cb_calendar(GtkBuildable *obj, const char *tag)
494 char str[BUFLEN];
495 unsigned int year = 0, month = 0, day = 0;
497 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
498 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
499 send_msg(obj, tag, str, NULL);
502 static void
503 cb_color_button(GtkBuildable *obj, const char *tag)
505 GdkRGBA color;
507 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
508 send_msg(obj, tag, gdk_rgba_to_string(&color), NULL);
511 static void
512 cb_editable(GtkBuildable *obj, const char *tag)
514 send_msg(obj, tag, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
518 * Callback that sends a message about a pointer device button press
519 * in a GtkEventBox
521 static bool
522 cb_event_box_button(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
524 char data[BUFLEN];
526 snprintf(data, BUFLEN, "%d %.1lf %.1lf",
527 e->button.button, e->button.x, e->button.y);
528 send_msg(obj, user_data, data, NULL);
529 return true;
533 * Callback that sends in a message the name of the key pressed when
534 * a GtkEventBox is focused
536 static bool
537 cb_event_box_key(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
539 send_msg(obj, user_data, gdk_keyval_name(e->key.keyval), NULL);
540 return true;
544 * Callback that sends a message about pointer device motion in a
545 * GtkEventBox
547 static bool
548 cb_event_box_motion(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
550 char data[BUFLEN];
552 snprintf(data, BUFLEN, "%.1lf %.1lf", e->button.x, e->button.y);
553 send_msg(obj, user_data, data, NULL);
554 return true;
558 * Callback that only sends "name:tag" and returns false
560 static bool
561 cb_event_simple(GtkBuildable *obj, GdkEvent *e, const char *tag)
563 (void) e;
564 send_msg(obj, tag, NULL);
565 return false;
568 static void
569 cb_file_chooser_button(GtkBuildable *obj, const char *tag)
571 send_msg(obj, tag, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
574 static void
575 cb_font_button(GtkBuildable *obj, const char *tag)
577 send_msg(obj, tag, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
580 static void
581 cb_menu_item(GtkBuildable *obj, const char *tag)
583 send_msg(obj, tag, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
586 static void
587 cb_range(GtkBuildable *obj, const char *tag)
589 char str[BUFLEN];
591 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
592 send_msg(obj, tag, str, NULL);
596 * Callback that sends user's selection from a file dialog
598 static void
599 cb_send_file_chooser_dialog_selection(gpointer user_data)
601 send_msg(user_data, "file",
602 gtk_file_chooser_get_filename(user_data), NULL);
603 send_msg(user_data, "folder",
604 gtk_file_chooser_get_current_folder(user_data), NULL);
608 * Callback that sends in a message the content of the text buffer
609 * passed in user_data
611 static void
612 cb_send_text(GtkBuildable *obj, gpointer user_data)
614 GtkTextIter a, b;
616 gtk_text_buffer_get_bounds(user_data, &a, &b);
617 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
621 * Callback that sends in a message the highlighted text from the text
622 * buffer which was passed in user_data
624 static void
625 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
627 GtkTextIter a, b;
629 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
630 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
634 * Callback that only sends "name:tag" and returns true
636 static bool
637 cb_simple(GtkBuildable *obj, const char *tag)
639 send_msg(obj, tag, NULL);
640 return true;
643 static void
644 cb_switch(GtkBuildable *obj, void *pspec, void *user_data)
646 (void) pspec;
647 (void) user_data;
648 send_msg(obj, gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0", NULL);
651 static void
652 cb_toggle_button(GtkBuildable *obj, const char *tag)
654 (void) tag;
655 send_msg(obj, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0", NULL);
658 static void
659 cb_tree_selection(GtkBuildable *obj, const char *tag)
661 GtkTreeView *view;
663 view = gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj));
664 send_msg(GTK_BUILDABLE(view), tag, NULL);
665 gtk_tree_selection_selected_foreach(GTK_TREE_SELECTION(obj),
666 (GtkTreeSelectionForeachFunc) send_tree_row_msg,
667 view);
671 * ============================================================
672 * cb_draw() maintains a drawing on a GtkDrawingArea; it needs a few
673 * helper functions
674 * ============================================================
678 * The set of supported drawing operations
680 enum cairo_fn {
681 ARC,
682 ARC_NEGATIVE,
683 CLOSE_PATH,
684 CURVE_TO,
685 FILL,
686 FILL_PRESERVE,
687 LINE_TO,
688 MOVE_TO,
689 RECTANGLE,
690 REL_CURVE_TO,
691 REL_LINE_TO,
692 REL_MOVE_TO,
693 SET_DASH,
694 SET_FONT_SIZE,
695 SET_LINE_CAP,
696 SET_LINE_JOIN,
697 SET_LINE_WIDTH,
698 SET_SOURCE_RGBA,
699 SHOW_TEXT,
700 STROKE,
701 STROKE_PRESERVE,
705 * One single element of a drawing
707 struct draw_op {
708 struct draw_op *next;
709 struct draw_op *prev;
710 unsigned int id;
711 enum cairo_fn op;
712 void *op_args;
716 * Argument sets for the various drawing operations
718 struct arc_args {
719 double x;
720 double y;
721 double radius;
722 double angle1;
723 double angle2;
726 struct curve_to_args {
727 double x1;
728 double y1;
729 double x2;
730 double y2;
731 double x3;
732 double y3;
735 struct move_to_args {
736 double x;
737 double y;
740 struct rectangle_args {
741 double x;
742 double y;
743 double width;
744 double height;
747 struct set_dash_args {
748 int num_dashes;
749 double dashes[];
752 struct set_font_size_args {
753 double size;
756 struct set_line_cap_args {
757 cairo_line_cap_t line_cap;
760 struct set_line_join_args {
761 cairo_line_join_t line_join;
764 struct set_line_width_args {
765 double width;
768 struct set_source_rgba_args {
769 GdkRGBA color;
772 struct show_text_args {
773 int len;
774 char text[];
777 static void
778 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
780 switch (op) {
781 case LINE_TO: {
782 struct move_to_args *args = op_args;
784 cairo_line_to(cr, args->x, args->y);
785 break;
787 case REL_LINE_TO: {
788 struct move_to_args *args = op_args;
790 cairo_rel_line_to(cr, args->x, args->y);
791 break;
793 case MOVE_TO: {
794 struct move_to_args *args = op_args;
796 cairo_move_to(cr, args->x, args->y);
797 break;
799 case REL_MOVE_TO: {
800 struct move_to_args *args = op_args;
802 cairo_rel_move_to(cr, args->x, args->y);
803 break;
805 case ARC: {
806 struct arc_args *args = op_args;
808 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
809 break;
811 case ARC_NEGATIVE: {
812 struct arc_args *args = op_args;
814 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
815 break;
817 case CURVE_TO: {
818 struct curve_to_args *args = op_args;
820 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
821 break;
823 case REL_CURVE_TO: {
824 struct curve_to_args *args = op_args;
826 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
827 break;
829 case RECTANGLE: {
830 struct rectangle_args *args = op_args;
832 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
833 break;
835 case CLOSE_PATH:
836 cairo_close_path(cr);
837 break;
838 case SHOW_TEXT: {
839 struct show_text_args *args = op_args;
841 cairo_show_text(cr, args->text);
842 break;
844 case STROKE:
845 cairo_stroke(cr);
846 break;
847 case STROKE_PRESERVE:
848 cairo_stroke_preserve(cr);
849 break;
850 case FILL:
851 cairo_fill(cr);
852 break;
853 case FILL_PRESERVE:
854 cairo_fill_preserve(cr);
855 break;
856 case SET_DASH: {
857 struct set_dash_args *args = op_args;
859 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
860 break;
862 case SET_FONT_SIZE: {
863 struct set_font_size_args *args = op_args;
865 cairo_set_font_size(cr, args->size);
866 break;
868 case SET_LINE_CAP: {
869 struct set_line_cap_args *args = op_args;
871 cairo_set_line_cap(cr, args->line_cap);
872 break;
874 case SET_LINE_JOIN: {
875 struct set_line_join_args *args = op_args;
877 cairo_set_line_join(cr, args->line_join);
878 break;
880 case SET_LINE_WIDTH: {
881 struct set_line_width_args *args = op_args;
883 cairo_set_line_width(cr, args->width);
884 break;
886 case SET_SOURCE_RGBA: {
887 struct set_source_rgba_args *args = op_args;
889 gdk_cairo_set_source_rgba(cr, &args->color);
890 break;
892 default:
893 ABORT;
894 break;
899 * Callback that draws on a GtkDrawingArea
901 static gboolean
902 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
904 struct draw_op *op;
906 (void) data;
907 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
908 op != NULL;
909 op = op->next)
910 draw(cr, op->op, op->op_args);
911 return FALSE;
915 * ============================================================
916 * Manipulating the GUI
917 * ============================================================
920 static void
921 update_button(GObject *obj, const char *action,
922 const char *data, const char *whole_msg, GType type)
924 if (eql(action, "set_label"))
925 gtk_button_set_label(GTK_BUTTON(obj), data);
926 else
927 ign_cmd(type, whole_msg);
930 static void
931 update_calendar(GObject *obj, const char *action,
932 const char *data, const char *whole_msg, GType type)
934 GtkCalendar *calendar = GTK_CALENDAR(obj);
935 int year = 0, month = 0, day = 0;
937 if (eql(action, "select_date")) {
938 sscanf(data, "%d-%d-%d", &year, &month, &day);
939 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
940 gtk_calendar_select_month(calendar, --month, year);
941 gtk_calendar_select_day(calendar, day);
942 } else
943 ign_cmd(type, whole_msg);
944 } else if (eql(action, "mark_day")) {
945 day = strtol(data, NULL, 10);
946 if (day > 0 && day <= 31)
947 gtk_calendar_mark_day(calendar, day);
948 else
949 ign_cmd(type, whole_msg);
950 } else if (eql(action, "clear_marks"))
951 gtk_calendar_clear_marks(calendar);
952 else
953 ign_cmd(type, whole_msg);
957 * Common actions for various kinds of window. Return false if
958 * command is ignored
960 static bool
961 update_class_window(GObject *obj, const char *action,
962 const char *data, const char *whole_msg, GType type)
964 GtkWindow *window = GTK_WINDOW(obj);
965 int x, y;
966 bool result = true;
968 if (eql(action, "set_title"))
969 gtk_window_set_title(window, data);
970 else if (eql(action, "fullscreen"))
971 gtk_window_fullscreen(window);
972 else if (eql(action, "unfullscreen"))
973 gtk_window_unfullscreen(window);
974 else if (eql(action, "resize")) {
975 if (sscanf(data, "%d %d", &x, &y) != 2)
976 gtk_window_get_default_size(window, &x, &y);
977 gtk_window_resize(window, x, y);
978 } else if (eql(action, "move")) {
979 if (sscanf(data, "%d %d", &x, &y) == 2)
980 gtk_window_move(window, x, y);
981 else
982 ign_cmd(type, whole_msg);
983 } else
984 result = false;
985 return result;
988 static void
989 update_color_button(GObject *obj, const char *action,
990 const char *data, const char *whole_msg, GType type)
992 GdkRGBA color;
994 if (eql(action, "set_color")) {
995 gdk_rgba_parse(&color, data);
996 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
997 } else
998 ign_cmd(type, whole_msg);
1001 static void
1002 update_combo_box_text(GObject *obj, const char *action,
1003 const char *data, const char *whole_msg, GType type)
1005 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(obj);
1006 char data1[strlen(data) + 1];
1008 strcpy(data1, data);
1009 if (eql(action, "prepend_text"))
1010 gtk_combo_box_text_prepend_text(combobox, data1);
1011 else if (eql(action, "append_text"))
1012 gtk_combo_box_text_append_text(combobox, data1);
1013 else if (eql(action, "remove"))
1014 gtk_combo_box_text_remove(combobox, strtol(data1, NULL, 10));
1015 else if (eql(action, "insert_text")) {
1016 char *position = strtok(data1, WHITESPACE);
1017 char *text = strtok(NULL, WHITESPACE);
1018 gtk_combo_box_text_insert_text(combobox,
1019 strtol(position, NULL, 10), text);
1020 } else
1021 ign_cmd(type, whole_msg);
1025 * Maintaining a list of drawing operations. It is the responsibility
1026 * of cb_draw() to actually draw them. update_drawing_area() needs a
1027 * few helper functions.
1031 * Fill structure *op with the drawing operation according to action
1032 * and with the appropriate set of arguments
1034 static bool
1035 set_draw_op(struct draw_op *op, const char *action, const char *data)
1037 if (eql(action, "line_to")) {
1038 struct move_to_args *args;
1040 if ((args = malloc(sizeof(*args))) == NULL)
1041 OOM_ABORT;
1042 op->op = LINE_TO;
1043 op->op_args = args;
1044 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1045 return false;
1046 } else if (eql(action, "rel_line_to")) {
1047 struct move_to_args *args;
1049 if ((args = malloc(sizeof(*args))) == NULL)
1050 OOM_ABORT;
1051 op->op = REL_LINE_TO;
1052 op->op_args = args;
1053 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1054 return false;
1055 } else if (eql(action, "move_to")) {
1056 struct move_to_args *args;
1058 if ((args = malloc(sizeof(*args))) == NULL)
1059 OOM_ABORT;
1060 op->op = MOVE_TO;
1061 op->op_args = args;
1062 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1063 return false;
1064 } else if (eql(action, "rel_move_to")) {
1065 struct move_to_args *args;
1067 if ((args = malloc(sizeof(*args))) == NULL)
1068 OOM_ABORT;
1069 op->op = REL_MOVE_TO;
1070 op->op_args = args;
1071 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1072 return false;
1073 } else if (eql(action, "arc")) {
1074 struct arc_args *args;
1075 double deg1, deg2;
1077 if ((args = malloc(sizeof(*args))) == NULL)
1078 OOM_ABORT;
1079 op->op = ARC;
1080 op->op_args = args;
1081 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id,
1082 &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
1083 return false;
1084 args->angle1 = deg1 * (M_PI / 180.);
1085 args->angle2 = deg2 * (M_PI / 180.);
1086 } else if (eql(action, "arc_negative")) {
1087 struct arc_args *args;
1088 double deg1, deg2;
1090 if ((args = malloc(sizeof(*args))) == NULL)
1091 OOM_ABORT;
1092 op->op = ARC_NEGATIVE;
1093 op->op_args = args;
1094 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id,
1095 &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
1096 return false;
1097 args->angle1 = deg1 * (M_PI / 180.);
1098 args->angle2 = deg2 * (M_PI / 180.);
1099 } else if (eql(action, "curve_to")) {
1100 struct curve_to_args *args;
1102 if ((args = malloc(sizeof(*args))) == NULL)
1103 OOM_ABORT;
1104 op->op = CURVE_TO;
1105 op->op_args = args;
1106 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf", &op->id,
1107 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3) != 7)
1108 return false;
1109 } else if (eql(action, "rel_curve_to")) {
1110 struct curve_to_args *args;
1112 if ((args = malloc(sizeof(*args))) == NULL)
1113 OOM_ABORT;
1114 op->op = REL_CURVE_TO;
1115 op->op_args = args;
1116 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf", &op->id,
1117 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3) != 7)
1118 return false;
1119 } else if (eql(action, "rectangle")) {
1120 struct rectangle_args *args;
1122 if ((args = malloc(sizeof(*args))) == NULL)
1123 OOM_ABORT;
1124 op->op = RECTANGLE;
1125 op->op_args = args;
1126 if (sscanf(data, "%u %lf %lf %lf %lf", &op->id,
1127 &args->x, &args->y, &args->width, &args->height) != 5)
1128 return false;
1129 } else if (eql(action, "close_path")) {
1130 op->op = CLOSE_PATH;
1131 if (sscanf(data, "%u", &op->id) != 1)
1132 return false;
1133 op->op_args = NULL;
1134 } else if (eql(action, "show_text")) {
1135 struct show_text_args *args;
1136 int start, len;
1138 if (sscanf(data, "%u %n", &op->id, &start) < 1)
1139 return false;
1140 len = strlen(data + start) + 1;
1141 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1142 OOM_ABORT;
1143 op->op = SHOW_TEXT;
1144 op->op_args = args;
1145 args->len = len; /* not used */
1146 strncpy(args->text, (data + start), len);
1147 } else if (eql(action, "stroke")) {
1148 op->op = STROKE;
1149 if (sscanf(data, "%u", &op->id) != 1)
1150 return false;
1151 op->op_args = NULL;
1152 } else if (eql(action, "stroke_preserve")) {
1153 op->op = STROKE_PRESERVE;
1154 if (sscanf(data, "%u", &op->id) != 1)
1155 return false;
1156 op->op_args = NULL;
1157 } else if (eql(action, "fill")) {
1158 op->op = FILL;
1159 if (sscanf(data, "%u", &op->id) != 1)
1160 return false;
1161 op->op_args = NULL;
1162 } else if (eql(action, "fill_preserve")) {
1163 op->op = FILL_PRESERVE;
1164 if (sscanf(data, "%u", &op->id) != 1)
1165 return false;
1166 op->op_args = NULL;
1167 } else if (eql(action, "set_dash")) {
1168 struct set_dash_args *args;
1169 int d_start, n, i;
1170 char data1[strlen(data) + 1];
1171 char *next, *end;
1173 strcpy(data1, data);
1174 if (sscanf(data1, "%u %n", &op->id, &d_start) < 1)
1175 return false;
1176 next = end = data1 + d_start;
1177 n = -1;
1178 do {
1179 n++;
1180 next = end;
1181 strtod(next, &end);
1182 } while (next != end);
1183 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
1184 OOM_ABORT;
1185 op->op = SET_DASH;
1186 op->op_args = args;
1187 args->num_dashes = n;
1188 for (i = 0, next = data1 + d_start; i < n; i++, next = end) {
1189 args->dashes[i] = strtod(next, &end);
1191 } else if (eql(action, "set_font_size")) {
1192 struct set_font_size_args *args;
1194 if ((args = malloc(sizeof(*args))) == NULL)
1195 OOM_ABORT;
1196 op->op = SET_FONT_SIZE;
1197 op->op_args = args;
1198 if (sscanf(data, "%u %lf", &op->id, &args->size) != 2)
1199 return false;
1200 } else if (eql(action, "set_line_cap")) {
1201 struct set_line_cap_args *args;
1202 char str[6 + 1];
1204 if ((args = malloc(sizeof(*args))) == NULL)
1205 OOM_ABORT;
1206 op->op = SET_LINE_CAP;
1207 op->op_args = args;
1208 if (sscanf(data, "%u %6s", &op->id, str) != 2)
1209 return false;
1210 if (eql(str, "butt"))
1211 args->line_cap = CAIRO_LINE_CAP_BUTT;
1212 else if (eql(str, "round"))
1213 args->line_cap = CAIRO_LINE_CAP_ROUND;
1214 else if (eql(str, "square"))
1215 args->line_cap = CAIRO_LINE_CAP_SQUARE;
1216 else
1217 return false;
1218 } else if (eql(action, "set_line_join")) {
1219 struct set_line_join_args *args;
1220 char str[5 + 1];
1222 if ((args = malloc(sizeof(*args))) == NULL)
1223 OOM_ABORT;
1224 op->op = SET_LINE_JOIN;
1225 op->op_args = args;
1226 if (sscanf(data, "%u %5s", &op->id, str) != 2)
1227 return false;
1228 if (eql(str, "miter"))
1229 args->line_join = CAIRO_LINE_JOIN_MITER;
1230 else if (eql(str, "round"))
1231 args->line_join = CAIRO_LINE_JOIN_ROUND;
1232 else if (eql(str, "bevel"))
1233 args->line_join = CAIRO_LINE_JOIN_BEVEL;
1234 else
1235 return false;
1236 } else if (eql(action, "set_line_width")) {
1237 struct set_line_width_args *args;
1239 if ((args = malloc(sizeof(*args))) == NULL)
1240 OOM_ABORT;
1241 op->op = SET_LINE_WIDTH;
1242 op->op_args = args;
1243 if (sscanf(data, "%u %lf", &op->id, &args->width) != 2)
1244 return false;
1245 } else if (eql(action, "set_source_rgba")) {
1246 struct set_source_rgba_args *args;
1247 int c_start;
1249 if ((args = malloc(sizeof(*args))) == NULL)
1250 OOM_ABORT;
1251 op->op = SET_SOURCE_RGBA;
1252 op->op_args = args;
1253 if ((sscanf(data, "%u %n", &op->id, &c_start) < 1))
1254 return false;;
1255 gdk_rgba_parse(&args->color, data + c_start);
1256 } else
1257 return false;
1258 return true;
1262 * Append another element to widget's "draw_ops" list
1264 static bool
1265 ins_draw_op(GObject *widget, const char *action, const char *data)
1267 struct draw_op *op, *draw_ops, *last_op;
1269 if ((op = malloc(sizeof(*op))) == NULL)
1270 OOM_ABORT;
1271 op->op_args = NULL;
1272 op->next = NULL;
1273 if (!set_draw_op(op, action, data)) {
1274 free(op->op_args);
1275 free(op);
1276 return false;
1278 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL)
1279 g_object_set_data(widget, "draw_ops", op);
1280 else {
1281 for (last_op = draw_ops;
1282 last_op->next != NULL;
1283 last_op = last_op->next);
1284 last_op->next = op;
1286 return true;
1290 * Remove all elements with the given id from widget's "draw_ops" list
1292 static bool
1293 rem_draw_op(GObject *widget, const char *data)
1295 struct draw_op *op, *next_op, *prev_op = NULL;
1296 unsigned int id;
1298 if (sscanf(data, "%u", &id) != 1)
1299 return false;
1300 op = g_object_get_data(widget, "draw_ops");
1301 while (op != NULL) {
1302 next_op = op->next;
1303 if (op->id == id) {
1304 if (prev_op == NULL) /* list head */
1305 g_object_set_data(widget, "draw_ops", op->next);
1306 else
1307 prev_op->next = op->next;
1308 free(op->op_args);
1309 free(op);
1310 } else
1311 prev_op = op;
1312 op = next_op;
1314 return true;
1317 static void
1318 update_drawing_area(GObject *obj, const char *action,
1319 const char *data, const char *whole_msg, GType type)
1321 if (eql(action, "remove")) {
1322 if (!rem_draw_op(obj, data))
1323 ign_cmd(type, whole_msg);
1324 } else if (eql(action, "refresh")) {
1325 gint width = gtk_widget_get_allocated_width(GTK_WIDGET(obj));
1326 gint height = gtk_widget_get_allocated_height(GTK_WIDGET(obj));
1328 gtk_widget_queue_draw_area(GTK_WIDGET(obj), 0, 0, width, height);
1329 } else if (ins_draw_op(obj, action, data));
1330 else
1331 ign_cmd(type, whole_msg);
1334 static void
1335 update_entry(GObject *obj, const char *action,
1336 const char *data, const char *whole_msg, GType type)
1338 GtkEntry *entry = GTK_ENTRY(obj);
1340 if (eql(action, "set_text"))
1341 gtk_entry_set_text(entry, data);
1342 else if (eql(action, "set_placeholder_text"))
1343 gtk_entry_set_placeholder_text(entry, data);
1344 else
1345 ign_cmd(type, whole_msg);
1348 static void
1349 update_expander(GObject *obj, const char *action,
1350 const char *data, const char *whole_msg, GType type)
1352 GtkExpander *expander = GTK_EXPANDER(obj);
1354 if (eql(action, "set_expanded"))
1355 gtk_expander_set_expanded(expander, strtol(data, NULL, 10));
1356 else if (eql(action, "set_label"))
1357 gtk_expander_set_label(expander, data);
1358 else
1359 ign_cmd(type, whole_msg);
1362 static void
1363 update_file_chooser_button(GObject *obj, const char *action,
1364 const char *data, const char *whole_msg, GType type)
1366 if (eql(action, "set_filename"))
1367 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
1368 else
1369 ign_cmd(type, whole_msg);
1372 static void
1373 update_file_chooser_dialog(GObject *obj, const char *action,
1374 const char *data, const char *whole_msg, GType type)
1376 GtkFileChooser *chooser = GTK_FILE_CHOOSER(obj);
1378 if (eql(action, "set_filename"))
1379 gtk_file_chooser_set_filename(chooser, data);
1380 else if (eql(action, "set_current_name"))
1381 gtk_file_chooser_set_current_name(chooser, data);
1382 else if (update_class_window(obj, action, data, whole_msg, type));
1383 else
1384 ign_cmd(type, whole_msg);
1387 static void
1388 update_focus(GObject *obj, const char *action,
1389 const char *data, const char *whole_msg, GType type)
1391 (void) action;
1392 (void) data;
1393 if (gtk_widget_get_can_focus(GTK_WIDGET(obj)))
1394 gtk_widget_grab_focus(GTK_WIDGET(obj));
1395 else
1396 ign_cmd(type, whole_msg);
1399 static void
1400 update_font_button(GObject *obj, const char *action,
1401 const char *data, const char *whole_msg, GType type)
1403 GtkFontButton *font_button = GTK_FONT_BUTTON(obj);
1405 if (eql(action, "set_font_name"))
1406 gtk_font_button_set_font_name(font_button, data);
1407 else
1408 ign_cmd(type, whole_msg);
1411 static void
1412 update_frame(GObject *obj, const char *action,
1413 const char *data, const char *whole_msg, GType type)
1415 if (eql(action, "set_label"))
1416 gtk_frame_set_label(GTK_FRAME(obj), data);
1417 else
1418 ign_cmd(type, whole_msg);
1421 static void
1422 update_image(GObject *obj, const char *action,
1423 const char *data, const char *whole_msg, GType type)
1425 GtkImage *image = GTK_IMAGE(obj);
1426 GtkIconSize size;
1428 gtk_image_get_icon_name(image, NULL, &size);
1429 if (eql(action, "set_from_file"))
1430 gtk_image_set_from_file(image, data);
1431 else if (eql(action, "set_from_icon_name"))
1432 gtk_image_set_from_icon_name(image, data, size);
1433 else
1434 ign_cmd(type, whole_msg);
1437 static void
1438 update_label(GObject *obj, const char *action,
1439 const char *data, const char *whole_msg, GType type)
1441 if (eql(action, "set_text"))
1442 gtk_label_set_text(GTK_LABEL(obj), data);
1443 else
1444 ign_cmd(type, whole_msg);
1447 static void
1448 update_notebook(GObject *obj, const char *action,
1449 const char *data, const char *whole_msg, GType type)
1451 if (eql(action, "set_current_page"))
1452 gtk_notebook_set_current_page(GTK_NOTEBOOK(obj), strtol(data, NULL, 10));
1453 else
1454 ign_cmd(type, whole_msg);
1457 static void
1458 update_nothing(GObject *obj, const char *action,
1459 const char *data, const char *whole_msg, GType type)
1461 (void) obj;
1462 (void) action;
1463 (void) data;
1464 (void) whole_msg;
1465 (void) type;
1468 static void
1469 update_print_dialog(GObject *obj, const char *action,
1470 const char *data, const char *whole_msg, GType type)
1472 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(obj);
1473 gint response_id;
1474 GtkPrinter *printer;
1475 GtkPrintSettings *settings;
1476 GtkPageSetup *page_setup;
1477 GtkPrintJob *job;
1479 if (eql(action, "print")) {
1480 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
1481 switch (response_id) {
1482 case GTK_RESPONSE_OK:
1483 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
1484 settings = gtk_print_unix_dialog_get_settings(dialog);
1485 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
1486 job = gtk_print_job_new(data, printer, settings, page_setup);
1487 if (gtk_print_job_set_source_file(job, data, NULL))
1488 gtk_print_job_send(job, NULL, NULL, NULL);
1489 else
1490 ign_cmd(type, whole_msg);
1491 g_clear_object(&settings);
1492 g_clear_object(&job);
1493 break;
1494 case GTK_RESPONSE_CANCEL:
1495 case GTK_RESPONSE_DELETE_EVENT:
1496 break;
1497 default:
1498 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
1499 widget_name(GTK_BUILDABLE(dialog)), response_id);
1500 break;
1502 gtk_widget_hide(GTK_WIDGET(dialog));
1503 } else
1504 ign_cmd(type, whole_msg);
1507 static void
1508 update_progress_bar(GObject *obj, const char *action,
1509 const char *data, const char *whole_msg, GType type)
1511 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(obj);
1513 if (eql(action, "set_text"))
1514 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
1515 else if (eql(action, "set_fraction"))
1516 gtk_progress_bar_set_fraction(progressbar, strtod(data, NULL));
1517 else
1518 ign_cmd(type, whole_msg);
1521 static void
1522 update_scale(GObject *obj, const char *action,
1523 const char *data, const char *whole_msg, GType type)
1525 if (eql(action, "set_value"))
1526 gtk_range_set_value(GTK_RANGE(obj), strtod(data, NULL));
1527 else
1528 ign_cmd(type, whole_msg);
1531 static void
1532 update_scrolled_window(GObject *obj, const char *action,
1533 const char *data, const char *whole_msg, GType type)
1535 GtkScrolledWindow *window = GTK_SCROLLED_WINDOW(obj);
1536 GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(window);
1537 GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(window);
1538 double d0, d1;
1540 if (eql(action, "hscroll") && sscanf(data, "%lf", &d0) == 1)
1541 gtk_adjustment_set_value(hadj, d0);
1542 else if (eql(action, "vscroll") && sscanf(data, "%lf", &d0) == 1)
1543 gtk_adjustment_set_value(vadj, d0);
1544 else if (eql(action, "hscroll_to_range") &&
1545 sscanf(data, "%lf %lf", &d0, &d1) == 2)
1546 gtk_adjustment_clamp_page(hadj, d0, d1);
1547 else if (eql(action, "vscroll_to_range") &&
1548 sscanf(data, "%lf %lf", &d0, &d1) == 2)
1549 gtk_adjustment_clamp_page(vadj, d0, d1);
1550 else
1551 ign_cmd(type, whole_msg);
1554 static void
1555 update_sensitivity(GObject *obj, const char *action,
1556 const char *data, const char *whole_msg, GType type)
1558 (void) action;
1559 (void) whole_msg;
1560 (void) type;
1561 gtk_widget_set_sensitive(GTK_WIDGET(obj), strtol(data, NULL, 10));
1564 static void
1565 update_size_request(GObject *obj, const char *action,
1566 const char *data, const char *whole_msg, GType type)
1568 int x, y;
1570 (void) action;
1571 (void) whole_msg;
1572 (void) type;
1573 if (sscanf(data, "%d %d", &x, &y) == 2)
1574 gtk_widget_set_size_request(GTK_WIDGET(obj), x, y);
1575 else
1576 gtk_widget_set_size_request(GTK_WIDGET(obj), -1, -1);
1579 static void
1580 update_socket(GObject *obj, const char *action,
1581 const char *data, const char *whole_msg, GType type)
1583 GtkSocket *socket = GTK_SOCKET(obj);
1584 Window id;
1585 char str[BUFLEN];
1587 (void) data;
1588 if (eql(action, "id")) {
1589 id = gtk_socket_get_id(socket);
1590 snprintf(str, BUFLEN, "%lu", id);
1591 send_msg(GTK_BUILDABLE(socket), "id", str, NULL);
1592 } else
1593 ign_cmd(type, whole_msg);
1596 static void
1597 update_spinner(GObject *obj, const char *action,
1598 const char *data, const char *whole_msg, GType type)
1600 GtkSpinner *spinner = GTK_SPINNER(obj);
1602 (void) data;
1603 if (eql(action, "start"))
1604 gtk_spinner_start(spinner);
1605 else if (eql(action, "stop"))
1606 gtk_spinner_stop(spinner);
1607 else
1608 ign_cmd(type, whole_msg);
1611 static void
1612 update_statusbar(GObject *obj, const char *action,
1613 const char *data, const char *whole_msg, GType type)
1615 GtkStatusbar *statusbar = GTK_STATUSBAR(obj);
1616 char *ctx_msg, *msg;
1617 size_t ctx_len;
1619 if ((ctx_msg = malloc(strlen(data) + 1)) == NULL)
1620 OOM_ABORT;
1621 strcpy(ctx_msg, data);
1622 ctx_len = strcspn(ctx_msg, WHITESPACE);
1623 if (ctx_len > 0) {
1624 ctx_msg[ctx_len] = '\0';
1625 msg = ctx_msg + ctx_len + 1;
1626 } else
1627 msg = ctx_msg + strlen(ctx_msg);
1628 if (eql(action, "push"))
1629 gtk_statusbar_push(statusbar,
1630 gtk_statusbar_get_context_id(statusbar, "0"),
1631 data);
1632 else if (eql(action, "push_id"))
1633 gtk_statusbar_push(statusbar,
1634 gtk_statusbar_get_context_id(statusbar, ctx_msg),
1635 msg);
1636 else if (eql(action, "pop"))
1637 gtk_statusbar_pop(statusbar,
1638 gtk_statusbar_get_context_id(statusbar, "0"));
1639 else if (eql(action, "pop_id"))
1640 gtk_statusbar_pop(statusbar,
1641 gtk_statusbar_get_context_id(statusbar, ctx_msg));
1642 else if (eql(action, "remove_all"))
1643 gtk_statusbar_remove_all(statusbar,
1644 gtk_statusbar_get_context_id(statusbar, "0"));
1645 else if (eql(action, "remove_all_id"))
1646 gtk_statusbar_remove_all(statusbar,
1647 gtk_statusbar_get_context_id(statusbar, ctx_msg));
1648 else
1649 ign_cmd(type, whole_msg);
1650 free(ctx_msg);
1653 static void
1654 update_switch(GObject *obj, const char *action,
1655 const char *data, const char *whole_msg, GType type)
1657 if (eql(action, "set_active"))
1658 gtk_switch_set_active(GTK_SWITCH(obj), strtol(data, NULL, 10));
1659 else
1660 ign_cmd(type, whole_msg);
1663 static void
1664 update_text_view(GObject *obj, const char *action,
1665 const char *data, const char *whole_msg, GType type)
1667 GtkTextView *view = GTK_TEXT_VIEW(obj);
1668 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
1669 GtkTextIter a, b;
1671 if (eql(action, "set_text"))
1672 gtk_text_buffer_set_text(textbuf, data, -1);
1673 else if (eql(action, "delete")) {
1674 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1675 gtk_text_buffer_delete(textbuf, &a, &b);
1676 } else if (eql(action, "insert_at_cursor"))
1677 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
1678 else if (eql(action, "place_cursor")) {
1679 if (eql(data, "end"))
1680 gtk_text_buffer_get_end_iter(textbuf, &a);
1681 else /* numeric offset */
1682 gtk_text_buffer_get_iter_at_offset(textbuf, &a,
1683 strtol(data, NULL, 10));
1684 gtk_text_buffer_place_cursor(textbuf, &a);
1685 } else if (eql(action, "place_cursor_at_line")) {
1686 gtk_text_buffer_get_iter_at_line(textbuf, &a, strtol(data, NULL, 10));
1687 gtk_text_buffer_place_cursor(textbuf, &a);
1688 } else if (eql(action, "scroll_to_cursor"))
1689 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
1690 0., 0, 0., 0.);
1691 else if (eql(action, "save") && data != NULL &&
1692 (save = fopen(data, "w")) != NULL) {
1693 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1694 save_msg(GTK_BUILDABLE(view), "insert_at_cursor",
1695 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
1696 fclose(save);
1697 } else
1698 ign_cmd(type, whole_msg);
1701 static void
1702 update_toggle_button(GObject *obj, const char *action,
1703 const char *data, const char *whole_msg, GType type)
1705 if (eql(action, "set_label"))
1706 gtk_button_set_label(GTK_BUTTON(obj), data);
1707 else if (eql(action, "set_active"))
1708 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), strtol(data, NULL, 10));
1709 else
1710 ign_cmd(type, whole_msg);
1713 static void
1714 update_tooltip_text(GObject *obj, const char *action,
1715 const char *data, const char *whole_msg, GType type)
1717 (void) action;
1718 (void) whole_msg;
1719 (void) type;
1720 gtk_widget_set_tooltip_text(GTK_WIDGET(obj), data);
1724 * update_tree_view() needs a few helper functions
1728 * Check if s is a valid string representation of a GtkTreePath
1730 static bool
1731 is_path_string(char *s)
1733 return s != NULL &&
1734 strlen(s) == strspn(s, ":0123456789") &&
1735 strstr(s, "::") == NULL &&
1736 strcspn(s, ":") > 0;
1739 static void
1740 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
1741 GtkTreeIter *parent, GtkTreeIter *sibling)
1743 if (GTK_IS_TREE_STORE(model))
1744 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
1745 iter, parent, sibling);
1746 else if (GTK_IS_LIST_STORE(model))
1747 gtk_list_store_insert_before(GTK_LIST_STORE(model),
1748 iter, sibling);
1749 else
1750 ABORT;
1753 static void
1754 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
1755 GtkTreeIter *parent, GtkTreeIter *sibling)
1757 if (GTK_IS_TREE_STORE(model))
1758 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
1759 iter, parent, sibling);
1760 else if (GTK_IS_LIST_STORE(model))
1761 gtk_list_store_insert_after(GTK_LIST_STORE(model),
1762 iter, sibling);
1763 else
1764 ABORT;
1767 static void
1768 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
1769 GtkTreeIter *position)
1771 if (GTK_IS_TREE_STORE(model))
1772 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
1773 else if (GTK_IS_LIST_STORE(model))
1774 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
1775 else
1776 ABORT;
1779 static void
1780 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
1782 if (GTK_IS_TREE_STORE(model))
1783 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
1784 else if (GTK_IS_LIST_STORE(model))
1785 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
1786 else
1787 ABORT;
1790 static void
1791 tree_model_clear(GtkTreeModel *model)
1793 if (GTK_IS_TREE_STORE(model))
1794 gtk_tree_store_clear(GTK_TREE_STORE(model));
1795 else if (GTK_IS_LIST_STORE(model))
1796 gtk_list_store_clear(GTK_LIST_STORE(model));
1797 else
1798 ABORT;
1801 static void
1802 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
1804 va_list ap;
1806 va_start(ap, iter);
1807 if (GTK_IS_TREE_STORE(model))
1808 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
1809 else if (GTK_IS_LIST_STORE(model))
1810 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
1811 else
1812 ABORT;
1813 va_end(ap);
1817 * Create an empty row at path if it doesn't yet exist. Create older
1818 * siblings and parents as necessary.
1820 static void
1821 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
1823 GtkTreePath *path_1; /* path's predecessor */
1824 GtkTreeIter iter_1; /* iter's predecessor */
1826 if (gtk_tree_model_get_iter(model, iter, path))
1827 return;
1828 path_1 = gtk_tree_path_copy(path);
1829 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
1830 create_subtree(model, path_1, iter);
1831 iter_1 = *iter;
1832 tree_model_insert_after(model, iter, NULL, &iter_1);
1833 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
1834 create_subtree(model, path_1, iter);
1835 if (gtk_tree_path_get_depth(path_1) == 0)
1836 /* first toplevel row */
1837 tree_model_insert_after(model, iter, NULL, NULL);
1838 else { /* first row in a lower level */
1839 iter_1 = *iter;
1840 tree_model_insert_after(model, iter, &iter_1, NULL);
1842 } /* neither prev nor up mean we're at the root of an empty tree */
1843 gtk_tree_path_free(path_1);
1846 static bool
1847 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
1848 const char *path_s, int col, const char *new_text)
1850 GType col_type = gtk_tree_model_get_column_type(model, col);
1851 long long int n;
1852 double d;
1853 GtkTreePath *path;
1854 char *endptr;
1855 bool ok = false;
1857 path = gtk_tree_path_new_from_string(path_s);
1858 create_subtree(model, path, iter);
1859 gtk_tree_path_free(path);
1860 switch (col_type) {
1861 case G_TYPE_BOOLEAN:
1862 case G_TYPE_INT:
1863 case G_TYPE_LONG:
1864 case G_TYPE_INT64:
1865 case G_TYPE_UINT:
1866 case G_TYPE_ULONG:
1867 case G_TYPE_UINT64:
1868 errno = 0;
1869 endptr = NULL;
1870 n = strtoll(new_text, &endptr, 10);
1871 if (!errno && endptr != new_text) {
1872 tree_model_set(model, iter, col, n, -1);
1873 ok = true;
1875 break;
1876 case G_TYPE_FLOAT:
1877 case G_TYPE_DOUBLE:
1878 errno = 0;
1879 endptr = NULL;
1880 d = strtod(new_text, &endptr);
1881 if (!errno && endptr != new_text) {
1882 tree_model_set(model, iter, col, d, -1);
1883 ok = true;
1885 break;
1886 case G_TYPE_STRING:
1887 tree_model_set(model, iter, col, new_text, -1);
1888 ok = true;
1889 break;
1890 default:
1891 fprintf(stderr, "column %d: %s not implemented\n",
1892 col, g_type_name(col_type));
1893 ok = true;
1894 break;
1896 return ok;
1899 static void
1900 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
1902 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
1903 /* is just fine and this function need not exist. */
1904 if (path == NULL)
1905 path = gtk_tree_path_new();
1906 gtk_tree_view_set_cursor(view, path, col, false);
1909 static void
1910 update_tree_view(GObject *obj, const char *action,
1911 const char *data, const char *whole_msg, GType type)
1913 GtkTreeView *view = GTK_TREE_VIEW(obj);
1914 GtkTreeModel *model = gtk_tree_view_get_model(view);
1915 GtkTreeIter iter0, iter1;
1916 GtkTreePath *path = NULL;
1917 bool iter0_valid, iter1_valid;
1918 char *tokens, *arg0, *arg1, *arg2;
1919 int col = -1; /* invalid column number */
1921 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
1923 fprintf(stderr, "missing model/");
1924 ign_cmd(type, whole_msg);
1925 return;
1927 if ((tokens = malloc(strlen(data) + 1)) == NULL)
1928 OOM_ABORT;
1929 strcpy(tokens, data);
1930 arg0 = strtok(tokens, WHITESPACE);
1931 arg1 = strtok(NULL, WHITESPACE);
1932 arg2 = strtok(NULL, "");
1933 iter0_valid = is_path_string(arg0) &&
1934 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
1935 iter1_valid = is_path_string(arg1) &&
1936 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
1937 if (is_path_string(arg1))
1938 col = strtol(arg1, NULL, 10);
1939 if (eql(action, "set") &&
1940 col > -1 && col < gtk_tree_model_get_n_columns(model) &&
1941 is_path_string(arg0)) {
1942 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
1943 ign_cmd(type, whole_msg);
1944 } else if (eql(action, "scroll") && iter0_valid && iter1_valid) {
1945 path = gtk_tree_path_new_from_string(arg0);
1946 gtk_tree_view_scroll_to_cell (view,
1947 path,
1948 gtk_tree_view_get_column(view, col),
1949 0, 0., 0.);
1950 } else if (eql(action, "expand") && iter0_valid) {
1951 path = gtk_tree_path_new_from_string(arg0);
1952 gtk_tree_view_expand_row(view, path, false);
1953 } else if (eql(action, "expand_all") && iter0_valid) {
1954 path = gtk_tree_path_new_from_string(arg0);
1955 gtk_tree_view_expand_row(view, path, true);
1956 } else if (eql(action, "expand_all") && arg0 == NULL)
1957 gtk_tree_view_expand_all(view);
1958 else if (eql(action, "collapse") && iter0_valid) {
1959 path = gtk_tree_path_new_from_string(arg0);
1960 gtk_tree_view_collapse_row(view, path);
1961 } else if (eql(action, "collapse") && arg0 == NULL)
1962 gtk_tree_view_collapse_all(view);
1963 else if (eql(action, "set_cursor") && iter0_valid) {
1964 path = gtk_tree_path_new_from_string(arg0);
1965 tree_view_set_cursor(view, path, NULL);
1966 } else if (eql(action, "set_cursor") && arg0 == NULL) {
1967 tree_view_set_cursor(view, NULL, NULL);
1968 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
1969 } else if (eql(action, "insert_row") && eql(arg0, "end"))
1970 tree_model_insert_before(model, &iter1, NULL, NULL);
1971 else if (eql(action, "insert_row") && iter0_valid && eql(arg1, "as_child"))
1972 tree_model_insert_after(model, &iter1, &iter0, NULL);
1973 else if (eql(action, "insert_row") && iter0_valid)
1974 tree_model_insert_before(model, &iter1, NULL, &iter0);
1975 else if (eql(action, "move_row") && iter0_valid && eql(arg1, "end"))
1976 tree_model_move_before(model, &iter0, NULL);
1977 else if (eql(action, "move_row") && iter0_valid && iter1_valid)
1978 tree_model_move_before(model, &iter0, &iter1);
1979 else if (eql(action, "remove_row") && iter0_valid)
1980 tree_model_remove(model, &iter0);
1981 else if (eql(action, "clear") && arg0 == NULL) {
1982 tree_view_set_cursor(view, NULL, NULL);
1983 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
1984 tree_model_clear(model);
1985 } else if (eql(action, "save") && arg0 != NULL &&
1986 (save = fopen(arg0, "w")) != NULL) {
1987 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc) save_tree_row_msg, view);
1988 fclose(save);
1989 } else
1990 ign_cmd(type, whole_msg);
1991 free(tokens);
1992 gtk_tree_path_free(path);
1995 static void
1996 update_visibility(GObject *obj, const char *action,
1997 const char *data, const char *whole_msg, GType type)
1999 (void) action;
2000 (void) whole_msg;
2001 (void) type;
2002 gtk_widget_set_visible(GTK_WIDGET(obj), strtol(data, NULL, 10));
2006 * Change the style of the widget passed
2008 static void
2009 update_widget_style(GObject *obj, const char *name,
2010 const char *data, const char *whole_msg, GType type)
2012 GtkStyleContext *context;
2013 GtkStyleProvider *style_provider;
2014 char *style_decl;
2015 const char *prefix = "* {", *suffix = "}";
2016 size_t sz;
2018 (void) name;
2019 (void) whole_msg;
2020 (void) type;
2021 style_provider = g_object_get_data(obj, "style_provider");
2022 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
2023 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2024 gtk_style_context_remove_provider(context, style_provider);
2025 if ((style_decl = malloc(sz)) == NULL)
2026 OOM_ABORT;
2027 strcpy(style_decl, prefix);
2028 strcat(style_decl, data);
2029 strcat(style_decl, suffix);
2030 gtk_style_context_add_provider(context, style_provider,
2031 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2032 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
2033 style_decl, -1, NULL);
2034 free(style_decl);
2037 static void
2038 update_window(GObject *obj, const char *action,
2039 const char *data, const char *whole_msg, GType type)
2041 if (!update_class_window(obj, action, data, whole_msg, type))
2042 ign_cmd(type, whole_msg);
2046 * Simulate user activity on various widgets
2048 static void
2049 fake_ui_activity(GObject *obj, const char *action,
2050 const char *data, const char *whole_msg, GType type)
2052 (void) action;
2053 (void) data;
2054 if (!GTK_IS_WIDGET(obj))
2055 ign_cmd(type, whole_msg);
2056 else if (GTK_IS_ENTRY(obj) || GTK_IS_SPIN_BUTTON(obj))
2057 cb_editable(GTK_BUILDABLE(obj), "text");
2058 else if (GTK_IS_SCALE(obj))
2059 cb_range(GTK_BUILDABLE(obj), "value");
2060 else if (GTK_IS_CALENDAR(obj))
2061 cb_calendar(GTK_BUILDABLE(obj), "clicked");
2062 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
2063 cb_file_chooser_button(GTK_BUILDABLE(obj), "file");
2064 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
2065 ign_cmd(type, whole_msg);
2069 * The final UI update
2071 static void
2072 main_quit(GObject *obj, const char *action,
2073 const char *data, const char *whole_msg, GType type)
2075 (void) obj;
2076 (void) action;
2077 (void) data;
2078 (void) whole_msg;
2079 (void) type;
2080 gtk_main_quit();
2084 * Don't update anything; just complain
2086 static void
2087 complain(GObject *obj, const char *action,
2088 const char *data, const char *whole_msg, GType type)
2090 (void) obj;
2091 (void) action;
2092 (void) data;
2093 ign_cmd(type, whole_msg);
2097 * Data to be passed to and from the GTK main loop
2099 struct ui_data {
2100 void (*fn)(GObject *, const char *action,
2101 const char *data, const char *msg, GType type);
2102 GObject *obj;
2103 char *action;
2104 char *data;
2105 char *msg;
2106 char *msg_tokens;
2107 GType type;
2111 * Parse command pointed to by ud, and act on ui accordingly; post
2112 * semaphore ud.msg_digested if done. Runs once per command inside
2113 * gtk_main_loop().
2115 static gboolean
2116 update_ui(struct ui_data *ud)
2118 (ud->fn)(ud->obj, ud->action, ud->data, ud->msg, ud->type);
2119 free(ud->msg_tokens);
2120 free(ud->msg);
2121 free(ud);
2122 return G_SOURCE_REMOVE;
2126 * Keep track of loading files to avoid recursive loading of the same
2127 * file. If filename = NULL, forget the most recently remembered file.
2129 static bool
2130 remember_loading_file(char *filename)
2132 static char *filenames[BUFLEN];
2133 static size_t latest = 0;
2134 size_t i;
2136 if (filename == NULL) { /* pop */
2137 if (latest < 1)
2138 ABORT;
2139 latest--;
2140 return false;
2141 } else { /* push */
2142 for (i = 1; i <= latest; i++)
2143 if (eql(filename, filenames[i]))
2144 return false;
2145 if (latest > BUFLEN -2)
2146 return false;
2147 filenames[++latest] = filename;
2148 return true;
2153 * Read lines from stream cmd and perform appropriate actions on the
2154 * GUI
2156 static void *
2157 digest_msg(FILE *cmd)
2159 FILE *load; /* restoring user data */
2160 char *name;
2161 static int recursion = -1; /* > 0 means this is a recursive call */
2163 recursion++;
2164 for (;;) {
2165 struct ui_data *ud;
2166 char first_char = '\0';
2167 size_t msg_size = 32;
2168 int name_start = 0, name_end = 0;
2169 int action_start = 0, action_end = 0;
2170 int data_start;
2172 if (feof(cmd))
2173 break;
2174 if ((ud = malloc(sizeof(*ud))) == NULL)
2175 OOM_ABORT;
2176 if ((ud->msg = malloc(msg_size)) == NULL)
2177 OOM_ABORT;
2178 ud->type = G_TYPE_INVALID;
2179 pthread_testcancel();
2180 if (recursion == 0)
2181 log_msg(NULL);
2182 read_buf(cmd, &ud->msg, &msg_size);
2183 if (recursion == 0)
2184 log_msg(ud->msg);
2185 data_start = strlen(ud->msg);
2186 if ((ud->msg_tokens = malloc(strlen(ud->msg) + 1)) == NULL)
2187 OOM_ABORT;
2188 strcpy(ud->msg_tokens, ud->msg);
2189 sscanf(ud->msg, " %c", &first_char);
2190 if (strlen(ud->msg) == 0 || first_char == '#') { /* comment */
2191 ud->fn = update_nothing;
2192 goto exec;
2194 sscanf(ud->msg_tokens,
2195 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2196 &name_start, &name_end, &action_start, &action_end, &data_start);
2197 ud->msg_tokens[name_end] = ud->msg_tokens[action_end] = '\0';
2198 name = ud->msg_tokens + name_start;
2199 ud->action = ud->msg_tokens + action_start;
2200 if (eql(ud->action, "main_quit")) {
2201 ud->fn = main_quit;
2202 goto exec;
2204 ud->data = ud->msg_tokens + data_start;
2205 if (eql(ud->action, "load") && strlen(ud->data) > 0 &&
2206 (load = fopen(ud->data, "r")) != NULL &&
2207 remember_loading_file(ud->data)) {
2208 digest_msg(load);
2209 fclose(load);
2210 remember_loading_file(NULL);
2211 ud->fn = update_nothing;
2212 goto exec;
2214 if ((ud->obj = (gtk_builder_get_object(builder, name))) == NULL) {
2215 ud->fn = complain;
2216 goto exec;
2218 ud->type = G_TYPE_FROM_INSTANCE(ud->obj);
2219 if (eql(ud->action, "force"))
2220 ud->fn = fake_ui_activity;
2221 else if (eql(ud->action, "set_sensitive"))
2222 ud->fn = update_sensitivity;
2223 else if (eql(ud->action, "set_visible"))
2224 ud->fn = update_visibility;
2225 else if (eql(ud->action, "set_size_request"))
2226 ud->fn = update_size_request;
2227 else if (eql(ud->action, "set_tooltip_text"))
2228 ud->fn = update_tooltip_text;
2229 else if (eql(ud->action, "grab_focus"))
2230 ud->fn = update_focus;
2231 else if (eql(ud->action, "style")) {
2232 ud->action = name;
2233 ud->fn = update_widget_style;
2234 } else if (ud->type == GTK_TYPE_DRAWING_AREA)
2235 ud->fn = update_drawing_area;
2236 else if (ud->type == GTK_TYPE_TREE_VIEW)
2237 ud->fn = update_tree_view;
2238 else if (ud->type == GTK_TYPE_COMBO_BOX_TEXT)
2239 ud->fn = update_combo_box_text;
2240 else if (ud->type == GTK_TYPE_LABEL)
2241 ud->fn = update_label;
2242 else if (ud->type == GTK_TYPE_IMAGE)
2243 ud->fn = update_image;
2244 else if (ud->type == GTK_TYPE_TEXT_VIEW)
2245 ud->fn = update_text_view;
2246 else if (ud->type == GTK_TYPE_NOTEBOOK)
2247 ud->fn = update_notebook;
2248 else if (ud->type == GTK_TYPE_EXPANDER)
2249 ud->fn = update_expander;
2250 else if (ud->type == GTK_TYPE_FRAME)
2251 ud->fn = update_frame;
2252 else if (ud->type == GTK_TYPE_SCROLLED_WINDOW)
2253 ud->fn = update_scrolled_window;
2254 else if (ud->type == GTK_TYPE_BUTTON)
2255 ud->fn = update_button;
2256 else if (ud->type == GTK_TYPE_FILE_CHOOSER_DIALOG)
2257 ud->fn = update_file_chooser_dialog;
2258 else if (ud->type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2259 ud->fn = update_file_chooser_button;
2260 else if (ud->type == GTK_TYPE_COLOR_BUTTON)
2261 ud->fn = update_color_button;
2262 else if (ud->type == GTK_TYPE_FONT_BUTTON)
2263 ud->fn = update_font_button;
2264 else if (ud->type == GTK_TYPE_PRINT_UNIX_DIALOG)
2265 ud->fn = update_print_dialog;
2266 else if (ud->type == GTK_TYPE_SWITCH)
2267 ud->fn = update_switch;
2268 else if (ud->type == GTK_TYPE_TOGGLE_BUTTON ||
2269 ud->type == GTK_TYPE_RADIO_BUTTON ||
2270 ud->type == GTK_TYPE_CHECK_BUTTON)
2271 ud->fn = update_toggle_button;
2272 else if (ud->type == GTK_TYPE_SPIN_BUTTON ||
2273 ud->type == GTK_TYPE_ENTRY)
2274 ud->fn = update_entry;
2275 else if (ud->type == GTK_TYPE_SCALE)
2276 ud->fn = update_scale;
2277 else if (ud->type == GTK_TYPE_PROGRESS_BAR)
2278 ud->fn = update_progress_bar;
2279 else if (ud->type == GTK_TYPE_SPINNER)
2280 ud->fn = update_spinner;
2281 else if (ud->type == GTK_TYPE_STATUSBAR)
2282 ud->fn = update_statusbar;
2283 else if (ud->type == GTK_TYPE_CALENDAR)
2284 ud->fn = update_calendar;
2285 else if (ud->type == GTK_TYPE_SOCKET)
2286 ud->fn = update_socket;
2287 else if (ud->type == GTK_TYPE_WINDOW ||
2288 ud->type == GTK_TYPE_DIALOG)
2289 ud->fn = update_window;
2290 else
2291 ud->fn = complain;
2292 exec:
2293 pthread_testcancel();
2294 gdk_threads_add_timeout(0, (GSourceFunc) update_ui, ud);
2296 recursion--;
2297 return NULL;
2301 * ============================================================
2302 * Initialization
2303 * ============================================================
2307 * Callbacks that forward a modification of a tree view cell to the
2308 * underlying model
2310 static void
2311 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
2312 const gchar *new_text, gpointer model)
2314 GtkTreeIter iter;
2315 GtkTreeView *view;
2316 void *col;
2318 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2319 view = g_object_get_data(G_OBJECT(renderer), "tree_view");
2320 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2321 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2322 new_text);
2323 send_tree_cell_msg_by(send_msg, model, path_s, &iter, GPOINTER_TO_INT(col),
2324 GTK_BUILDABLE(view));
2327 static void
2328 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, gpointer model)
2330 GtkTreeIter iter;
2331 void *col;
2332 bool toggle_state;
2334 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2335 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2336 gtk_tree_model_get(model, &iter, col, &toggle_state, -1);
2337 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2338 toggle_state? "0" : "1");
2342 * Attach to renderer key "col_number". Associate "col_number" with
2343 * the corresponding column number in the underlying model.
2344 * Due to what looks like a gap in the GTK API, renderer id and column
2345 * number are taken directly from the XML .ui file.
2347 static bool
2348 tree_view_column_get_renderer_column(const char *ui_file, GtkTreeViewColumn *t_col,
2349 int n, GtkCellRenderer **renderer)
2351 xmlDocPtr doc;
2352 xmlXPathContextPtr xpath_ctx;
2353 xmlXPathObjectPtr xpath_obj;
2354 xmlNodeSetPtr nodes;
2355 xmlNodePtr cur;
2356 int i;
2357 xmlChar *xpath, *renderer_name = NULL, *m_col_s = NULL;
2358 char *xpath_base1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
2359 const char *xpath_id = widget_name(GTK_BUILDABLE(t_col));
2360 char *xpath_base2 = "\"]/child[";
2361 size_t xpath_n_len = 3; /* Big Enough (TM) */
2362 char *xpath_base3 = "]/object[@class=\"GtkCellRendererText\""
2363 " or @class=\"GtkCellRendererToggle\"]/";
2364 char *xpath_text_col = "../attributes/attribute[@name=\"text\""
2365 " or @name=\"active\"]";
2366 char *xpath_renderer_id = "/@id";
2367 size_t xpath_len;
2368 bool r = false;
2370 if ((doc = xmlParseFile(ui_file)) == NULL)
2371 return false;
2372 if ((xpath_ctx = xmlXPathNewContext(doc)) == NULL) {
2373 xmlFreeDoc(doc);
2374 return false;
2376 xpath_len = 2 * (strlen(xpath_base1) + strlen(xpath_id) +
2377 strlen(xpath_base2) + xpath_n_len +
2378 strlen(xpath_base3))
2379 + 1 /* "|" */
2380 + strlen(xpath_text_col) + strlen(xpath_renderer_id)
2381 + 1; /* '\0' */
2382 if ((xpath = malloc(xpath_len)) == NULL) {
2383 xmlFreeDoc(doc);
2384 return false;
2386 snprintf((char *) xpath, xpath_len, "%s%s%s%d%s%s|%s%s%s%d%s%s",
2387 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_text_col,
2388 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_renderer_id);
2389 if ((xpath_obj = xmlXPathEvalExpression(xpath, xpath_ctx)) == NULL) {
2390 xmlXPathFreeContext(xpath_ctx);
2391 free(xpath);
2392 xmlFreeDoc(doc);
2393 return false;
2395 if ((nodes = xpath_obj->nodesetval) != NULL) {
2396 for (i = 0; i < nodes->nodeNr; ++i) {
2397 if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
2398 cur = nodes->nodeTab[i];
2399 m_col_s = xmlNodeGetContent(cur);
2400 } else {
2401 cur = nodes->nodeTab[i];
2402 renderer_name = xmlNodeGetContent(cur);
2406 if (renderer_name) {
2407 *renderer = GTK_CELL_RENDERER(
2408 gtk_builder_get_object(builder, (char *) renderer_name));
2409 if (m_col_s) {
2410 g_object_set_data(G_OBJECT(*renderer), "col_number",
2411 GINT_TO_POINTER(strtol((char *) m_col_s,
2412 NULL, 10)));
2413 xmlFree(m_col_s);
2414 r = true;
2416 xmlFree(renderer_name);
2418 xmlXPathFreeObject(xpath_obj);
2419 xmlXPathFreeContext(xpath_ctx);
2420 free(xpath);
2421 xmlFreeDoc(doc);
2422 return r;
2425 static void
2426 connect_widget_signals(gpointer *obj, char *ui_file)
2428 const char *name = NULL;
2429 char *suffix = NULL;
2430 GObject *obj2;
2431 GType type = G_TYPE_INVALID;
2433 type = G_TYPE_FROM_INSTANCE(obj);
2434 if (GTK_IS_BUILDABLE(obj))
2435 name = widget_name(GTK_BUILDABLE(obj));
2436 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
2437 gboolean editable = FALSE;
2438 GtkTreeView *view;
2439 GtkTreeModel *model;
2440 GtkCellRenderer *renderer;
2441 int i;
2443 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
2444 view = GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(GTK_TREE_VIEW_COLUMN(obj)));
2445 model = gtk_tree_view_get_model(view);
2446 for (i = 1;; i++) {
2447 if (!tree_view_column_get_renderer_column(ui_file, GTK_TREE_VIEW_COLUMN(obj), i, &renderer))
2448 break;
2449 g_object_set_data(G_OBJECT(renderer), "tree_view", view);
2450 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
2451 g_object_get(renderer, "editable", &editable, NULL);
2452 if (editable)
2453 g_signal_connect(renderer, "edited", G_CALLBACK(cb_tree_model_edit), model);
2454 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
2455 g_object_get(renderer, "activatable", &editable, NULL);
2456 if (editable)
2457 g_signal_connect(renderer, "toggled", G_CALLBACK(cb_tree_model_toggle), model);
2461 else if (type == GTK_TYPE_BUTTON) {
2462 /* Button associated with a GtkTextView. */
2463 if ((suffix = strstr(name, "_send_text")) != NULL &&
2464 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2465 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
2466 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2467 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
2468 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2469 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
2470 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2471 else {
2472 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
2473 /* Buttons associated with (and part of) a GtkDialog.
2474 * (We shun response ids which could be returned from
2475 * gtk_dialog_run() because that would require the
2476 * user to define those response ids in Glade,
2477 * numerically */
2478 if ((suffix = strstr(name, "_cancel")) != NULL &&
2479 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2480 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
2481 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2482 else
2483 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2484 else if ((suffix = strstr(name, "_ok")) != NULL &&
2485 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name))) {
2486 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
2487 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), GTK_FILE_CHOOSER(obj2));
2488 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
2489 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2490 else
2491 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2492 } else if ((suffix = strstr(name, "_apply")) != NULL &&
2493 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2494 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
2496 } else if (GTK_IS_MENU_ITEM(obj))
2497 if ((suffix = strstr(name, "_invoke")) != NULL &&
2498 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2499 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
2500 else
2501 g_signal_connect(obj, "activate", G_CALLBACK(cb_menu_item), "active");
2502 else if (GTK_IS_WINDOW(obj)) {
2503 g_signal_connect(obj, "delete-event", G_CALLBACK(cb_event_simple), "closed");
2504 if (eql(name, MAIN_WIN))
2505 g_signal_connect_swapped(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
2506 else
2507 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
2508 } else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2509 g_signal_connect(obj, "file-set", G_CALLBACK(cb_file_chooser_button), "file");
2510 else if (type == GTK_TYPE_COLOR_BUTTON)
2511 g_signal_connect(obj, "color-set", G_CALLBACK(cb_color_button), "color");
2512 else if (type == GTK_TYPE_FONT_BUTTON)
2513 g_signal_connect(obj, "font-set", G_CALLBACK(cb_font_button), "font");
2514 else if (type == GTK_TYPE_SWITCH)
2515 g_signal_connect(obj, "notify::active", G_CALLBACK(cb_switch), NULL);
2516 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
2517 g_signal_connect(obj, "toggled", G_CALLBACK(cb_toggle_button), NULL);
2518 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
2519 g_signal_connect(obj, "changed", G_CALLBACK(cb_editable), "text");
2520 else if (type == GTK_TYPE_SCALE)
2521 g_signal_connect(obj, "value-changed", G_CALLBACK(cb_range), "value");
2522 else if (type == GTK_TYPE_CALENDAR) {
2523 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb_calendar), "doubleclicked");
2524 g_signal_connect(obj, "day-selected", G_CALLBACK(cb_calendar), "clicked");
2525 } else if (type == GTK_TYPE_TREE_SELECTION)
2526 g_signal_connect(obj, "changed", G_CALLBACK(cb_tree_selection), "clicked");
2527 else if (type == GTK_TYPE_SOCKET) {
2528 g_signal_connect(obj, "plug-added", G_CALLBACK(cb_simple), "plug-added");
2529 g_signal_connect(obj, "plug-removed", G_CALLBACK(cb_simple), "plug-removed");
2530 } else if (type == GTK_TYPE_DRAWING_AREA)
2531 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
2532 else if (type == GTK_TYPE_EVENT_BOX) {
2533 gtk_widget_set_can_focus(GTK_WIDGET(obj), true);
2534 g_signal_connect(obj, "button-press-event", G_CALLBACK(cb_event_box_button), "button_press");
2535 g_signal_connect(obj, "button-release-event", G_CALLBACK(cb_event_box_button), "button_release");
2536 g_signal_connect(obj, "motion-notify-event", G_CALLBACK(cb_event_box_motion), "motion");
2537 g_signal_connect(obj, "key-press-event", G_CALLBACK(cb_event_box_key), "key_press");
2542 * We keep a style provider with each widget
2544 static void
2545 add_widget_style_provider(gpointer *obj, void *data)
2547 GtkStyleContext *context;
2548 GtkCssProvider *style_provider;
2550 (void) data;
2551 if (!GTK_IS_WIDGET(obj))
2552 return;
2553 style_provider = gtk_css_provider_new();
2554 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2555 gtk_style_context_add_provider(context,
2556 GTK_STYLE_PROVIDER(style_provider),
2557 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2558 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
2561 static void
2562 prepare_widgets(char *ui_file)
2564 GSList *objects = NULL;
2566 objects = gtk_builder_get_objects(builder);
2567 g_slist_foreach(objects, (GFunc) connect_widget_signals, ui_file);
2568 g_slist_foreach(objects, (GFunc) add_widget_style_provider, NULL);
2569 g_slist_free(objects);
2573 main(int argc, char *argv[])
2575 char opt;
2576 char *in_fifo = NULL, *out_fifo = NULL, *ui_file = NULL;
2577 char *log_name = NULL;
2578 char *xid_s = NULL, xid_s2[BUFLEN];
2579 Window xid;
2580 GtkWidget *plug, *body;
2581 pthread_t receiver;
2582 GError *error = NULL;
2583 GObject *main_window = NULL;
2584 FILE *in = NULL; /* command input */
2586 /* Disable runtime GLIB deprecation warnings: */
2587 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
2588 out = NULL;
2589 save = NULL;
2590 log_out = NULL;
2591 gtk_init(&argc, &argv);
2592 while ((opt = getopt(argc, argv, "he:i:l:o:u:GV")) != -1) {
2593 switch (opt) {
2594 case 'e': xid_s = optarg; break;
2595 case 'i': in_fifo = optarg; break;
2596 case 'l': log_name = optarg; break;
2597 case 'o': out_fifo = optarg; break;
2598 case 'u': ui_file = optarg; break;
2599 case 'G': bye(EXIT_SUCCESS, stdout,
2600 "GTK+ v%d.%d.%d (running v%d.%d.%d)\n"
2601 "cairo v%s (running v%s)\n",
2602 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
2603 gtk_get_major_version(), gtk_get_minor_version(),
2604 gtk_get_micro_version(),
2605 CAIRO_VERSION_STRING, cairo_version_string());
2606 break;
2607 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
2608 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
2609 case '?':
2610 default: bye(EXIT_FAILURE, stderr, USAGE); break;
2613 if (argv[optind] != NULL)
2614 bye(EXIT_FAILURE, stderr,
2615 "illegal parameter '%s'\n" USAGE, argv[optind]);
2616 in = fifo(in_fifo, "r");
2617 out = fifo(out_fifo, "w");
2618 if (ui_file == NULL)
2619 ui_file = "pipeglade.ui";
2620 builder = gtk_builder_new();
2621 if (gtk_builder_add_from_file(builder, ui_file, &error) == 0)
2622 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
2623 log_out = log_file(log_name);
2624 pthread_create(&receiver, NULL, (void *(*)(void *)) digest_msg, in);
2625 main_window = gtk_builder_get_object(builder, MAIN_WIN);
2626 if (!GTK_IS_WINDOW(main_window))
2627 bye(EXIT_FAILURE, stderr,
2628 "no toplevel window named \'" MAIN_WIN "\'\n");
2629 xmlInitParser();
2630 LIBXML_TEST_VERSION;
2631 prepare_widgets(ui_file);
2632 if (xid_s == NULL) /* standalone */
2633 gtk_widget_show(GTK_WIDGET(main_window));
2634 else { /* We're being XEmbedded */
2635 xid = strtoul(xid_s, NULL, 10);
2636 snprintf(xid_s2, BUFLEN, "%lu", xid);
2637 if (!eql(xid_s, xid_s2))
2638 bye(EXIT_FAILURE, stderr,
2639 "%s is not a valid XEmbed socket id\n", xid_s);
2640 body = gtk_bin_get_child(GTK_BIN(main_window));
2641 gtk_container_remove(GTK_CONTAINER(main_window), body);
2642 plug = gtk_plug_new(xid);
2643 if (!gtk_plug_get_embedded(GTK_PLUG(plug)))
2644 bye(EXIT_FAILURE, stderr,
2645 "unable to embed into XEmbed socket %s\n", xid_s);
2646 gtk_container_add(GTK_CONTAINER(plug), body);
2647 gtk_widget_show(plug);
2649 gtk_main();
2650 if (in != stdin) {
2651 fclose(in);
2652 unlink(in_fifo);
2654 if (out != stdout) {
2655 fclose(out);
2656 unlink(out_fifo);
2658 pthread_cancel(receiver);
2659 pthread_join(receiver, NULL);
2660 xmlCleanupParser();
2661 exit(EXIT_SUCCESS);