Put www stuff into a separate Makefile
[pipeglade.git] / pipeglade.c
blob6613cabb61a909ec32b46130e6be253adcc70bc9
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 do { \
66 fprintf(stderr, \
67 "In %s (%s:%d): ", \
68 __func__, __FILE__, __LINE__); \
69 abort(); \
70 } while (0)
72 #define OOM_ABORT \
73 do { \
74 fprintf(stderr, \
75 "Out of memory in %s (%s:%d): ", \
76 __func__, __FILE__, __LINE__); \
77 abort(); \
78 } while (0)
82 * ============================================================
83 * Helper functions
84 * ============================================================
88 * Check if s1 and s2 are equal strings
90 static bool
91 eql(const char *s1, const char *s2)
93 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
97 * Print a formatted message to stream s and give up with status
99 static void
100 bye(int status, FILE *s, const char *fmt, ...)
102 va_list ap;
104 va_start(ap, fmt);
105 vfprintf(s, fmt, ap);
106 va_end(ap);
107 exit(status);
110 static void
111 show_lib_versions(void)
113 bye(EXIT_SUCCESS, stdout,
114 "GTK+ v%d.%d.%d (running v%d.%d.%d)\n"
115 "cairo v%s (running v%s)\n",
116 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
117 gtk_get_major_version(), gtk_get_minor_version(),
118 gtk_get_micro_version(),
119 CAIRO_VERSION_STRING, cairo_version_string());
123 * XEmbed us if xid_s is given, or show a standalone window; give up
124 * on errors
126 static void
127 xembed_if(char *xid_s, GObject *main_window)
129 GtkWidget *plug, *body;
130 Window xid;
131 char xid_s2[BUFLEN];
133 if (xid_s == NULL) { /* standalone */
134 gtk_widget_show(GTK_WIDGET(main_window));
135 return;
137 /* We're being XEmbedded */
138 xid = strtoul(xid_s, NULL, 10);
139 snprintf(xid_s2, BUFLEN, "%lu", xid);
140 if (!eql(xid_s, xid_s2))
141 bye(EXIT_FAILURE, stderr,
142 "%s is not a valid XEmbed socket id\n", xid_s);
143 body = gtk_bin_get_child(GTK_BIN(main_window));
144 gtk_container_remove(GTK_CONTAINER(main_window), body);
145 plug = gtk_plug_new(xid);
146 if (!gtk_plug_get_embedded(GTK_PLUG(plug)))
147 bye(EXIT_FAILURE, stderr,
148 "unable to embed into XEmbed socket %s\n", xid_s);
149 gtk_container_add(GTK_CONTAINER(plug), body);
150 gtk_widget_show(plug);
154 * If requested, redirect stderr to file name
156 static void
157 redirect_stderr(const char *name)
159 if (name == NULL)
160 return;
161 if (freopen(name, "a", stderr) == NULL)
162 /* complaining on stdout since stderr is closed now */
163 bye(EXIT_FAILURE, stdout, "redirecting stderr to %s: %s\n",
164 name, strerror(errno));
165 if (fchmod(fileno(stderr), 0600) < 0)
166 bye(EXIT_FAILURE, stdout, "setting permissions of %s: %s\n",
167 name, strerror(errno));
168 setvbuf(stderr, NULL, _IOLBF, 0);
169 return;
173 * fork() if requested in bg; give up on errors
175 static void
176 go_bg_if(bool bg, FILE *in, FILE *out, char *err_file)
178 pid_t pid = 0;
180 if (!bg)
181 return;
182 if (in == stdin || out == stdout)
183 bye(EXIT_FAILURE, stderr,
184 "parameter -b requires both -i and -o\n");
185 pid = fork();
186 if (pid < 0)
187 bye(EXIT_FAILURE, stderr,
188 "going to background: %s\n", strerror(errno));
189 if (pid > 0)
190 bye(EXIT_SUCCESS, stdout, "%d\n", pid);
191 /* We're the child */
192 close(fileno(stdin)); /* making certain not-so-smart */
193 close(fileno(stdout)); /* system/run-shell commands happy */
194 if (err_file == NULL)
195 freopen("/dev/null", "w", stderr);
199 * Return the current locale and set it to "C". Should be free()d if
200 * done.
202 static char *
203 lc_numeric()
205 char *lc_orig;
206 char *lc = setlocale(LC_NUMERIC, NULL);
208 if ((lc_orig = malloc(strlen(lc) + 1)) == NULL)
209 OOM_ABORT;
210 strcpy(lc_orig, lc);
211 setlocale(LC_NUMERIC, "C");
212 return lc_orig;
216 * Set locale (back) to lc; free lc
218 static void
219 lc_numeric_free(char *lc)
221 setlocale(LC_NUMERIC, lc);
222 free(lc);
226 * Print a warning about a malformed command to stderr. Runs inside
227 * gtk_main().
229 static void
230 ign_cmd(GType type, const char *msg)
232 const char *name, *pad = " ";
234 if (type == G_TYPE_INVALID) {
235 name = "";
236 pad = "";
237 } else
238 name = g_type_name(type);
239 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
243 * Check if n is, or can be made, the name of a fifo, and put its
244 * struct stat into sb. Give up if n exists but is not a fifo.
246 static void
247 find_fifo(const char *n, struct stat *sb)
249 int fd;
251 if ((fd = open(n, O_RDONLY | O_NONBLOCK)) > -1) {
252 if (fstat(fd, sb) == 0 &&
253 S_ISFIFO(sb->st_mode) &&
254 fchmod(fd, 0600) == 0) {
255 fstat(fd, sb);
256 close(fd);
257 return;
259 bye(EXIT_FAILURE, stderr, "using pre-existing fifo %s: %s\n",
260 n, strerror(errno));
262 if (mkfifo(n, 0600) != 0)
263 bye(EXIT_FAILURE, stderr, "making fifo %s: %s\n",
264 n, strerror(errno));
265 find_fifo(n, sb);
268 static FILE *
269 open_fifo(const char *name, const char *fmode, FILE *fallback, int bmode)
271 FILE *s = NULL;
272 int fd;
273 struct stat sb1, sb2;
275 if (name == NULL)
276 s = fallback;
277 else {
278 find_fifo(name, &sb1);
279 /* TODO: O_RDWR on fifo is undefined in POSIX */
280 if (!((fd = open(name, O_RDWR)) > -1 &&
281 fstat(fd, &sb2) == 0 &&
282 sb1.st_mode == sb2.st_mode &&
283 sb1.st_ino == sb2.st_ino &&
284 sb1.st_dev == sb2.st_dev &&
285 (s = fdopen(fd, fmode)) != NULL))
286 bye(EXIT_FAILURE, stderr, "opening fifo %s (%s): %s\n",
287 name, fmode, strerror(errno));
289 setvbuf(s, NULL, bmode, 0);
290 return s;
294 * Create a log file if necessary, and open it. A name of "-"
295 * requests use of stderr.
297 static FILE *
298 open_log(const char *name)
300 FILE *s = NULL;
302 if (name == NULL)
303 return NULL;
304 if (eql(name, "-"))
305 return stderr;
306 if ((s = fopen(name, "a")) == NULL)
307 bye(EXIT_FAILURE, stderr, "opening log file %s: %s\n",
308 name, strerror(errno));
309 if (fchmod(fileno(s), 0600) < 0)
310 bye(EXIT_FAILURE, stderr, "setting permissions of %s: %s\n",
311 name, strerror(errno));
312 return s;
316 * Delete fifo fn if streams s and forbidden are distinct
318 static void
319 rm_unless(FILE *forbidden, FILE *s, char *fn)
321 if (s == forbidden)
322 return;
323 fclose(s);
324 remove(fn);
328 * Microseconds elapsed since start
330 static long int
331 usec_since(struct timespec *start)
333 struct timespec now;
335 clock_gettime(CLOCK_MONOTONIC, &now);
336 return (now.tv_sec - start->tv_sec) * 1e6 +
337 (now.tv_nsec - start->tv_nsec) / 1e3;
341 * Write string s to stream o, escaping newlines and backslashes
343 static void
344 fputs_escaped(const char *s, FILE *o)
346 size_t i = 0;
347 char c;
349 while ((c = s[i++]) != '\0')
350 switch (c) {
351 case '\\': fputs("\\\\", o); break;
352 case '\n': fputs("\\n", o); break;
353 default: putc(c, o); break;
358 * Write log file
360 static void
361 log_msg(FILE *l, char *msg)
363 static char *old_msg;
364 static struct timespec start;
366 if (l == NULL) /* no logging */
367 return;
368 if (msg == NULL && old_msg == NULL)
369 fprintf(l, "##########\t##### (New Pipeglade session) #####\n");
370 else if (msg == NULL && old_msg != NULL) {
371 /* command done; start idle */
372 fprintf(l, "%10ld\t", usec_since(&start));
373 fputs_escaped(old_msg, l);
374 putc('\n', l);
375 free(old_msg);
376 old_msg = NULL;
377 } else if (msg != NULL && old_msg == NULL) {
378 /* idle done; start command */
379 fprintf(l, "%10ld\t### (Idle) ###\n", usec_since(&start));
380 if ((old_msg = malloc(strlen(msg) + 1)) == NULL)
381 OOM_ABORT;
382 strcpy(old_msg, msg);
383 } else
384 ABORT;
385 clock_gettime(CLOCK_MONOTONIC, &start);
388 static bool
389 has_suffix(const char *s, const char *suffix)
391 int s_suf = strlen(s) - strlen(suffix);
393 if (s_suf < 0)
394 return false;
395 return eql(suffix, s + s_suf);
399 * Remove suffix from name; find the object named like this
401 static GObject *
402 obj_sans_suffix(GtkBuilder *builder, const char *suffix, const char *name)
404 char str[BUFLEN + 1] = {'\0'};
405 int str_l;
407 str_l = suffix - name;
408 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
409 return gtk_builder_get_object(builder, str);
413 * Read UI definition from ui_file; give up on errors
415 static GtkBuilder *
416 builder_from_file(char *ui_file)
418 GError *error = NULL;
419 GtkBuilder *b;
421 b = gtk_builder_new();
422 if (gtk_builder_add_from_file(b, ui_file, &error) == 0)
423 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
424 return b;
428 * Return the id attribute of widget
430 static const char *
431 widget_id(GtkBuildable *widget)
433 return gtk_buildable_get_name(widget);
437 * Get the main window; give up on errors
439 static GObject *
440 find_main_window(GtkBuilder *builder)
442 GObject *mw;
444 if (GTK_IS_WINDOW(mw = gtk_builder_get_object(builder, MAIN_WIN)))
445 return mw;
446 bye(EXIT_FAILURE, stderr, "no toplevel window with id \'" MAIN_WIN "\'\n");
447 return NULL; /* NOT REACHED */
451 * Store a line from stream s into buf, which should have been malloc'd
452 * to bufsize. Enlarge buf and bufsize if necessary.
454 static size_t
455 read_buf(FILE *s, char **buf, size_t *bufsize)
457 bool esc = false;
458 fd_set rfds;
459 int c;
460 int ifd = fileno(s);
461 size_t i = 0;
463 FD_ZERO(&rfds);
464 FD_SET(ifd, &rfds);
465 for (;;) {
466 select(ifd + 1, &rfds, NULL, NULL, NULL);
467 c = getc(s);
468 if (c == '\n' || feof(s))
469 break;
470 if (i >= *bufsize - 1)
471 if ((*buf = realloc(*buf, *bufsize *= 2)) == NULL)
472 OOM_ABORT;
473 if (esc) {
474 esc = false;
475 switch (c) {
476 case 'n': (*buf)[i++] = '\n'; break;
477 case 'r': (*buf)[i++] = '\r'; break;
478 default: (*buf)[i++] = c; break;
480 } else if (c == '\\')
481 esc = true;
482 else
483 (*buf)[i++] = c;
485 (*buf)[i] = '\0';
486 return i;
491 * ============================================================
492 * Receiving feedback from the GUI
493 * ============================================================
497 * Preclude triggering of GTK's default GtkLinkButton action which
498 * could otherwise interfere with pipeglade's own signal blocking
500 gboolean
501 gtk_show_uri(GdkScreen *s, const gchar *uri, guint32 ts, GError **e)
503 (void) s; (void) uri; (void) ts; (void) e;
504 return TRUE;
507 static void
508 send_msg_to(FILE* o, GtkBuildable *obj, const char *tag, va_list ap)
510 char *data;
511 const char *w_id = widget_id(obj);
512 fd_set wfds;
513 int ofd = fileno(o);
514 struct timeval timeout = {1, 0};
516 FD_ZERO(&wfds);
517 FD_SET(ofd, &wfds);
518 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
519 fprintf(o, "%s:%s ", w_id, tag);
520 while ((data = va_arg(ap, char *)) != NULL)
521 fputs_escaped(data, o);
522 putc('\n', o);
523 } else
524 fprintf(stderr,
525 "send error; discarding feedback message %s:%s\n",
526 w_id, tag);
530 * Send GUI feedback to stream o. The message format is
531 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
532 * last argument must be NULL.
534 static void
535 send_msg(FILE *o, GtkBuildable *obj, const char *tag, ...)
537 va_list ap;
539 va_start(ap, tag);
540 send_msg_to(o, obj, tag, ap);
541 va_end(ap);
545 * Send message from GUI to stream o. The message format is
546 * "<origin>:set <data ...>", which happens to be a legal command.
547 * The variadic arguments are strings; last argument must be NULL.
549 static void
550 send_msg_as_cmd(FILE *o, GtkBuildable *obj, const char *tag, ...)
552 va_list ap;
554 va_start(ap, tag);
555 send_msg_to(o, obj, "set", ap);
556 va_end(ap);
560 * Stuff to pass around
562 struct info {
563 FILE *fout; /* UI feedback messages */
564 FILE *fin; /* command input */
565 FILE *flog; /* logging output */
566 GtkBuilder *builder; /* to be read from .ui file */
567 GObject *obj;
568 GtkTreeModel *model;
569 char *txt;
573 * Data to be passed to and from the GTK main loop
575 struct ui_data {
576 void (*fn)(struct ui_data *);
577 GObject *obj;
578 char *action;
579 char *data;
580 char *cmd;
581 char *cmd_tokens;
582 GType type;
583 struct info *args;
587 * Return pointer to a newly allocated struct info
589 struct info *
590 info_new_full(FILE *stream, GObject *obj, GtkTreeModel *model, char *txt)
592 struct info *ar;
594 if ((ar = malloc(sizeof(struct info))) == NULL)
595 OOM_ABORT;
596 ar->fout = stream;
597 ar->fin = NULL;
598 ar->flog = NULL;
599 ar->builder = NULL;
600 ar->obj = obj;
601 ar->model = model;
602 ar->txt = txt;
603 return ar;
606 struct info *
607 info_txt_new(FILE *stream, char *txt)
609 return info_new_full(stream, NULL, NULL, txt);
612 struct info *
613 info_obj_new(FILE *stream, GObject *obj, GtkTreeModel *model)
615 return info_new_full(stream, obj, model, NULL);
619 * Use msg_sender() to send a message describing a particular cell
621 static void
622 send_tree_cell_msg_by(void msg_sender(FILE *, GtkBuildable *, const char *, ...),
623 const char *path_s,
624 GtkTreeIter *iter, int col, struct info *ar)
626 GtkBuildable *obj = GTK_BUILDABLE(ar->obj);
627 GtkTreeModel *model = ar->model;
628 GType col_type;
629 GValue value = G_VALUE_INIT;
630 char str[BUFLEN], *lc = lc_numeric();
632 gtk_tree_model_get_value(model, iter, col, &value);
633 col_type = gtk_tree_model_get_column_type(model, col);
634 switch (col_type) {
635 case G_TYPE_INT:
636 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
637 msg_sender(ar->fout, obj, "gint", path_s, str, NULL);
638 break;
639 case G_TYPE_LONG:
640 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
641 msg_sender(ar->fout, obj, "glong", path_s, str, NULL);
642 break;
643 case G_TYPE_INT64:
644 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
645 msg_sender(ar->fout, obj, "gint64", path_s, str, NULL);
646 break;
647 case G_TYPE_UINT:
648 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
649 msg_sender(ar->fout, obj, "guint", path_s, str, NULL);
650 break;
651 case G_TYPE_ULONG:
652 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
653 msg_sender(ar->fout, obj, "gulong", path_s, str, NULL);
654 break;
655 case G_TYPE_UINT64:
656 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
657 msg_sender(ar->fout, obj, "guint64", path_s, str, NULL);
658 break;
659 case G_TYPE_BOOLEAN:
660 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
661 msg_sender(ar->fout, obj, "gboolean", path_s, str, NULL);
662 break;
663 case G_TYPE_FLOAT:
664 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
665 msg_sender(ar->fout, obj, "gfloat", path_s, str, NULL);
666 break;
667 case G_TYPE_DOUBLE:
668 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
669 msg_sender(ar->fout, obj, "gdouble", path_s, str, NULL);
670 break;
671 case G_TYPE_STRING:
672 snprintf(str, BUFLEN, " %d ", col);
673 msg_sender(ar->fout, obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
674 break;
675 default:
676 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
677 break;
679 g_value_unset(&value);
680 lc_numeric_free(lc);
684 * Use msg_sender() to send one message per column for a single row
686 static void
687 send_tree_row_msg_by(void msg_sender(FILE *, GtkBuildable *, const char *, ...),
688 char *path_s, GtkTreeIter *iter, struct info *ar)
690 int col;
692 for (col = 0; col < gtk_tree_model_get_n_columns(ar->model); col++)
693 send_tree_cell_msg_by(msg_sender, path_s, iter, col, ar);
697 * send_tree_row_msg serves as an argument for
698 * gtk_tree_selection_selected_foreach()
700 static gboolean
701 send_tree_row_msg(GtkTreeModel *model,
702 GtkTreePath *path, GtkTreeIter *iter, struct info *ar)
704 char *path_s = gtk_tree_path_to_string(path);
706 ar->model = model;
707 send_tree_row_msg_by(send_msg, path_s, iter, ar);
708 g_free(path_s);
709 return FALSE;
713 * save_tree_row_msg serves as an argument for
714 * gtk_tree_model_foreach().
715 * Send message from GUI to global stream "save".
717 static gboolean
718 save_tree_row_msg(GtkTreeModel *model,
719 GtkTreePath *path, GtkTreeIter *iter, struct info *ar)
721 char *path_s = gtk_tree_path_to_string(path);
723 ar->model = model;
724 send_tree_row_msg_by(send_msg_as_cmd, path_s, iter, ar);
725 g_free(path_s);
726 return FALSE;
729 static void
730 cb_calendar(GtkBuildable *obj, struct info *ar)
732 char str[BUFLEN];
733 unsigned int year = 0, month = 0, day = 0;
735 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
736 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
737 send_msg(ar->fout, obj, ar->txt, str, NULL);
740 static void
741 cb_color_button(GtkBuildable *obj, struct info *ar)
743 GdkRGBA color;
745 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
746 send_msg(ar->fout, obj, ar->txt, gdk_rgba_to_string(&color), NULL);
749 static void
750 cb_editable(GtkBuildable *obj, struct info *ar)
752 send_msg(ar->fout, obj, ar->txt, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
756 * Callback that sends a message about a pointer device button press
757 * in a GtkEventBox
759 static bool
760 cb_event_box_button(GtkBuildable *obj, GdkEvent *e, struct info *ar)
762 char data[BUFLEN], *lc = lc_numeric();
764 snprintf(data, BUFLEN, "%d %.1lf %.1lf",
765 e->button.button, e->button.x, e->button.y);
766 send_msg(ar->fout, obj, ar->txt, data, NULL);
767 lc_numeric_free(lc);
768 return true;
772 * Callback that sends in a message the name of the key pressed when
773 * a GtkEventBox is focused
775 static bool
776 cb_event_box_key(GtkBuildable *obj, GdkEvent *e, struct info *ar)
778 send_msg(ar->fout, obj, ar->txt, gdk_keyval_name(e->key.keyval), NULL);
779 return true;
783 * Callback that sends a message about pointer device motion in a
784 * GtkEventBox
786 static bool
787 cb_event_box_motion(GtkBuildable *obj, GdkEvent *e, struct info *ar)
789 char data[BUFLEN], *lc = lc_numeric();
791 snprintf(data, BUFLEN, "%.1lf %.1lf", e->button.x, e->button.y);
792 send_msg(ar->fout, obj, ar->txt, data, NULL);
793 lc_numeric_free(lc);
794 return true;
798 * Callback that only sends "name:tag" and returns false
800 static bool
801 cb_event_simple(GtkBuildable *obj, GdkEvent *e, struct info *ar)
803 (void) e;
804 send_msg(ar->fout, obj, ar->txt, NULL);
805 return false;
808 static void
809 cb_file_chooser_button(GtkBuildable *obj, struct info *ar)
811 send_msg(ar->fout, obj, ar->txt,
812 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
815 static void
816 cb_font_button(GtkBuildable *obj, struct info *ar)
818 send_msg(ar->fout, obj, ar->txt,
819 gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
822 static void
823 cb_menu_item(GtkBuildable *obj, struct info *ar)
825 send_msg(ar->fout, obj, ar->txt,
826 gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
829 static void
830 cb_range(GtkBuildable *obj, struct info *ar)
832 char str[BUFLEN], *lc = lc_numeric();
834 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
835 send_msg(ar->fout, obj, ar->txt, str, NULL);
836 lc_numeric_free(lc);
840 * Callback that sends user's selection from a file dialog
842 static void
843 cb_send_file_chooser_dialog_selection(struct info *ar)
845 send_msg(ar->fout, GTK_BUILDABLE(ar->obj), "file",
846 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(ar->obj)),
847 NULL);
848 send_msg(ar->fout, GTK_BUILDABLE(ar->obj), "folder",
849 gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(ar->obj)),
850 NULL);
854 * Callback that sends in a message the content of the text buffer
855 * passed in user_data
857 static void
858 cb_send_text(GtkBuildable *obj, struct info *ar)
860 GtkTextIter a, b;
862 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(ar->obj), &a, &b);
863 send_msg(ar->fout, obj, "text",
864 gtk_text_buffer_get_text(GTK_TEXT_BUFFER(ar->obj), &a, &b, TRUE),
865 NULL);
869 * Callback that sends in a message the highlighted text from the text
870 * buffer which was passed in user_data
872 static void
873 cb_send_text_selection(GtkBuildable *obj, struct info *ar)
875 GtkTextIter a, b;
877 gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(ar->obj), &a, &b);
878 send_msg(ar->fout, obj, "text",
879 gtk_text_buffer_get_text(GTK_TEXT_BUFFER(ar->obj), &a, &b, TRUE),
880 NULL);
884 * Callback that only sends "name:tag" and returns true
886 static bool
887 cb_simple(GtkBuildable *obj, struct info *ar)
889 send_msg(ar->fout, obj, ar->txt, NULL);
890 return true;
893 static void
894 cb_spin_button(GtkBuildable *obj, struct info *ar)
896 char str[BUFLEN], *lc = lc_numeric();
898 snprintf(str, BUFLEN, "%f", gtk_spin_button_get_value(GTK_SPIN_BUTTON(obj)));
899 send_msg(ar->fout, obj, ar->txt, str, NULL);
900 lc_numeric_free(lc);
903 static void
904 cb_switch(GtkBuildable *obj, void *pspec, struct info *ar)
906 (void) pspec;
907 send_msg(ar->fout, obj,
908 gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0",
909 NULL);
912 static void
913 cb_toggle_button(GtkBuildable *obj, struct info *ar)
915 send_msg(ar->fout, obj,
916 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0",
917 NULL);
920 static void
921 cb_tree_selection(GtkBuildable *obj, struct info *ar)
923 GtkTreeSelection *sel = GTK_TREE_SELECTION(obj);
924 GtkTreeView *view = gtk_tree_selection_get_tree_view(sel);
926 ar->obj = G_OBJECT(view);
927 send_msg(ar->fout, GTK_BUILDABLE(view), ar->txt, NULL);
928 gtk_tree_selection_selected_foreach(
929 sel, (GtkTreeSelectionForeachFunc) send_tree_row_msg, ar);
934 * ============================================================
935 * cb_draw() maintains a drawing on a GtkDrawingArea; it needs a few
936 * helper functions
937 * ============================================================
941 * The set of supported drawing operations
943 enum cairo_fn {
944 ARC,
945 ARC_NEGATIVE,
946 CLOSE_PATH,
947 CURVE_TO,
948 FILL,
949 FILL_PRESERVE,
950 LINE_TO,
951 MOVE_TO,
952 RECTANGLE,
953 REL_CURVE_TO,
954 REL_LINE_TO,
955 REL_MOVE_TO,
956 REL_MOVE_FOR,
957 RESET_CTM,
958 SET_DASH,
959 SET_FONT_FACE,
960 SET_FONT_SIZE,
961 SET_LINE_CAP,
962 SET_LINE_JOIN,
963 SET_LINE_WIDTH,
964 SET_SOURCE_RGBA,
965 SHOW_TEXT,
966 STROKE,
967 STROKE_PRESERVE,
968 TRANSFORM,
972 * Text placement mode for rel_move_for()
974 enum ref_point {
986 enum draw_op_policy {
987 APPEND,
988 BEFORE,
989 REPLACE,
993 * One single element of a drawing
995 struct draw_op {
996 struct draw_op *next;
997 struct draw_op *prev;
998 unsigned long long int id;
999 unsigned long long int before;
1000 enum draw_op_policy policy;
1001 enum cairo_fn op;
1002 void *op_args;
1006 * Argument sets for the various drawing operations
1008 struct arc_args {
1009 double x;
1010 double y;
1011 double radius;
1012 double angle1;
1013 double angle2;
1016 struct curve_to_args {
1017 double x1;
1018 double y1;
1019 double x2;
1020 double y2;
1021 double x3;
1022 double y3;
1025 struct move_to_args {
1026 double x;
1027 double y;
1030 struct rectangle_args {
1031 double x;
1032 double y;
1033 double width;
1034 double height;
1037 struct rel_move_for_args {
1038 enum ref_point ref;
1039 int len;
1040 char text[];
1043 struct set_dash_args {
1044 int num_dashes;
1045 double dashes[];
1048 struct set_font_face_args {
1049 cairo_font_slant_t slant;
1050 cairo_font_weight_t weight;
1051 char family[];
1054 struct set_font_size_args {
1055 double size;
1058 struct set_line_cap_args {
1059 cairo_line_cap_t line_cap;
1062 struct set_line_join_args {
1063 cairo_line_join_t line_join;
1066 struct set_line_width_args {
1067 double width;
1070 struct set_source_rgba_args {
1071 GdkRGBA color;
1074 struct show_text_args {
1075 int len;
1076 char text[];
1079 struct transform_args {
1080 cairo_matrix_t matrix;
1083 static void
1084 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
1086 switch (op) {
1087 case LINE_TO: {
1088 struct move_to_args *args = op_args;
1090 cairo_line_to(cr, args->x, args->y);
1091 break;
1093 case REL_LINE_TO: {
1094 struct move_to_args *args = op_args;
1096 cairo_rel_line_to(cr, args->x, args->y);
1097 break;
1099 case MOVE_TO: {
1100 struct move_to_args *args = op_args;
1102 cairo_move_to(cr, args->x, args->y);
1103 break;
1105 case REL_MOVE_TO: {
1106 struct move_to_args *args = op_args;
1108 cairo_rel_move_to(cr, args->x, args->y);
1109 break;
1111 case ARC: {
1112 struct arc_args *args = op_args;
1114 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1115 break;
1117 case ARC_NEGATIVE: {
1118 struct arc_args *args = op_args;
1120 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
1121 break;
1123 case CURVE_TO: {
1124 struct curve_to_args *args = op_args;
1126 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1127 break;
1129 case REL_CURVE_TO: {
1130 struct curve_to_args *args = op_args;
1132 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
1133 break;
1135 case RECTANGLE: {
1136 struct rectangle_args *args = op_args;
1138 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
1139 break;
1141 case CLOSE_PATH:
1142 cairo_close_path(cr);
1143 break;
1144 case SHOW_TEXT: {
1145 struct show_text_args *args = op_args;
1147 cairo_show_text(cr, args->text);
1148 break;
1150 case REL_MOVE_FOR: {
1151 cairo_text_extents_t e;
1152 double dx = 0.0, dy = 0.0;
1153 struct rel_move_for_args *args = op_args;
1155 cairo_text_extents(cr, args->text, &e);
1156 switch (args->ref) {
1157 case C: dx = -e.width / 2; dy = e.height / 2; break;
1158 case E: dx = -e.width; dy = e.height / 2; break;
1159 case N: dx = -e.width / 2; dy = e.height; break;
1160 case NE: dx = -e.width; dy = e.height; break;
1161 case NW: dy = e.height; break;
1162 case S: dx = -e.width / 2; break;
1163 case SE: dx = -e.width; break;
1164 case SW: break;
1165 case W: dy = e.height / 2; break;
1166 default: ABORT; break;
1168 cairo_rel_move_to(cr, dx, dy);
1169 break;
1171 case RESET_CTM:
1172 cairo_identity_matrix(cr);
1173 break;
1174 case STROKE:
1175 cairo_stroke(cr);
1176 break;
1177 case STROKE_PRESERVE:
1178 cairo_stroke_preserve(cr);
1179 break;
1180 case FILL:
1181 cairo_fill(cr);
1182 break;
1183 case FILL_PRESERVE:
1184 cairo_fill_preserve(cr);
1185 break;
1186 case SET_DASH: {
1187 struct set_dash_args *args = op_args;
1189 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
1190 break;
1192 case SET_FONT_FACE: {
1193 struct set_font_face_args *args = op_args;
1195 cairo_select_font_face(cr, args->family, args->slant, args->weight);
1196 break;
1198 case SET_FONT_SIZE: {
1199 struct set_font_size_args *args = op_args;
1201 cairo_set_font_size(cr, args->size);
1202 break;
1204 case SET_LINE_CAP: {
1205 struct set_line_cap_args *args = op_args;
1207 cairo_set_line_cap(cr, args->line_cap);
1208 break;
1210 case SET_LINE_JOIN: {
1211 struct set_line_join_args *args = op_args;
1213 cairo_set_line_join(cr, args->line_join);
1214 break;
1216 case SET_LINE_WIDTH: {
1217 struct set_line_width_args *args = op_args;
1219 cairo_set_line_width(cr, args->width);
1220 break;
1222 case SET_SOURCE_RGBA: {
1223 struct set_source_rgba_args *args = op_args;
1225 gdk_cairo_set_source_rgba(cr, &args->color);
1226 break;
1228 case TRANSFORM: {
1229 struct transform_args *args = op_args;
1231 cairo_transform(cr, &args->matrix);
1232 break;
1234 default:
1235 ABORT;
1236 break;
1241 * Callback that draws on a GtkDrawingArea
1243 static gboolean
1244 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
1246 struct draw_op *op;
1248 (void) data;
1249 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
1250 op != NULL;
1251 op = op->next)
1252 draw(cr, op->op, op->op_args);
1253 return FALSE;
1258 * ============================================================
1259 * Manipulating the GUI
1260 * ============================================================
1264 * Generic actions that are applicable to most widgets
1268 * Simulate user activity on various widgets. Runs inside gtk_main().
1270 static void
1271 fake_ui_activity(struct ui_data *ud)
1273 char dummy;
1275 if (!GTK_IS_WIDGET(ud->obj) || sscanf(ud->data, " %c", &dummy) > 0)
1276 ign_cmd(ud->type, ud->cmd);
1277 else if (GTK_IS_SPIN_BUTTON(ud->obj)) {
1278 ud->args->txt = "text";
1279 cb_spin_button(GTK_BUILDABLE(ud->obj), ud->args); /* TODO: rename to "value" */
1280 } else if (GTK_IS_SCALE(ud->obj)) {
1281 ud->args->txt = "value";
1282 cb_range(GTK_BUILDABLE(ud->obj), ud->args);
1283 } else if (GTK_IS_ENTRY(ud->obj)) {
1284 ud->args->txt = "text";
1285 cb_editable(GTK_BUILDABLE(ud->obj), ud->args);
1286 } else if (GTK_IS_CALENDAR(ud->obj)) {
1287 ud->args->txt = "clicked";
1288 cb_calendar(GTK_BUILDABLE(ud->obj), ud->args);
1289 } else if (GTK_IS_FILE_CHOOSER_BUTTON(ud->obj)) {
1290 ud->args->txt = "file";
1291 cb_file_chooser_button(GTK_BUILDABLE(ud->obj), ud->args);
1292 } else if (!gtk_widget_activate(GTK_WIDGET(ud->obj)))
1293 ign_cmd(ud->type, ud->cmd);
1296 static void
1297 update_focus(struct ui_data *ud){
1298 char dummy;
1300 if (GTK_IS_WIDGET(ud->obj) &&
1301 sscanf(ud->data, " %c", &dummy) < 1 &&
1302 gtk_widget_get_can_focus(GTK_WIDGET(ud->obj)))
1303 gtk_widget_grab_focus(GTK_WIDGET(ud->obj));
1304 else
1305 ign_cmd(ud->type, ud->cmd);
1309 * Have the widget say "ping". Runs inside gtk_main().
1311 static void
1312 ping(struct ui_data *ud)
1314 char dummy;
1316 if (!GTK_IS_WIDGET(ud->obj) || sscanf(ud->data, " %c", &dummy) > 0)
1317 ign_cmd(ud->type, ud->cmd);
1318 ud->args->txt = "ping";
1319 cb_simple(GTK_BUILDABLE(ud->obj), ud->args);
1323 * Write snapshot of widget in an appropriate format to file
1325 static void
1326 take_snapshot(struct ui_data *ud)
1328 cairo_surface_t *sur = NULL;
1329 cairo_t *cr = NULL;
1330 int height;
1331 int width;
1333 if (!GTK_IS_WIDGET(ud->obj) ||
1334 !gtk_widget_is_drawable(GTK_WIDGET(ud->obj))) {
1335 ign_cmd(ud->type, ud->cmd);
1336 return;
1338 height = gtk_widget_get_allocated_height(GTK_WIDGET(ud->obj));
1339 width = gtk_widget_get_allocated_width(GTK_WIDGET(ud->obj));
1340 if (has_suffix(ud->data, ".epsf") || has_suffix(ud->data, ".eps")) {
1341 sur = cairo_ps_surface_create(ud->data, width, height);
1342 cairo_ps_surface_set_eps(sur, TRUE);
1343 } else if (has_suffix(ud->data, ".pdf"))
1344 sur = cairo_pdf_surface_create(ud->data, width, height);
1345 else if (has_suffix(ud->data, ".ps"))
1346 sur = cairo_ps_surface_create(ud->data, width, height);
1347 else if (has_suffix(ud->data, ".svg"))
1348 sur = cairo_svg_surface_create(ud->data, width, height);
1349 else {
1350 ign_cmd(ud->type, ud->cmd);
1351 return;
1353 cr = cairo_create(sur);
1354 gtk_widget_draw(GTK_WIDGET(ud->obj), cr);
1355 cairo_destroy(cr);
1356 cairo_surface_destroy(sur);
1359 struct handler_id {
1360 unsigned int id; /* returned by g_signal_connect() and friends */
1361 bool blocked; /* we avoid multiple blocking/unblocking */
1362 struct handler_id *next;
1365 static void
1366 update_blocked(struct ui_data *ud)
1368 char dummy;
1369 struct handler_id *hid;
1370 unsigned long int val;
1372 if (sscanf(ud->data, "%lu %c", &val, &dummy) == 1 && val < 2) {
1373 for (hid = g_object_get_data(ud->obj, "signal-id");
1374 hid != NULL; hid = hid->next) {
1375 if (val == 0 && hid->blocked == true) {
1376 g_signal_handler_unblock(ud->obj, hid->id);
1377 hid->blocked = false;
1378 } else if (val == 1 && hid->blocked == false) {
1379 g_signal_handler_block(ud->obj, hid->id);
1380 hid->blocked = true;
1383 } else
1384 ign_cmd(ud->type, ud->cmd);
1387 static void
1388 update_sensitivity(struct ui_data *ud)
1390 char dummy;
1391 unsigned int val;
1393 if (GTK_IS_WIDGET(ud->obj) &&
1394 sscanf(ud->data, "%u %c", &val, &dummy) == 1 && val < 2)
1395 gtk_widget_set_sensitive(GTK_WIDGET(ud->obj), val);
1396 else
1397 ign_cmd(ud->type, ud->cmd);
1400 static void
1401 update_size_request(struct ui_data *ud)
1403 char dummy;
1404 int x, y;
1406 if (GTK_IS_WIDGET(ud->obj) &&
1407 sscanf(ud->data, "%d %d %c", &x, &y, &dummy) == 2)
1408 gtk_widget_set_size_request(GTK_WIDGET(ud->obj), x, y);
1409 else if (GTK_IS_WIDGET(ud->obj) &&
1410 sscanf(ud->data, " %c", &dummy) < 1)
1411 gtk_widget_set_size_request(GTK_WIDGET(ud->obj), -1, -1);
1412 else
1413 ign_cmd(ud->type, ud->cmd);
1416 static void
1417 update_tooltip_text(struct ui_data *ud)
1419 if (GTK_IS_WIDGET(ud->obj))
1420 gtk_widget_set_tooltip_text(GTK_WIDGET(ud->obj), ud->data);
1421 else
1422 ign_cmd(ud->type, ud->cmd);
1425 static void
1426 update_visibility(struct ui_data *ud)
1428 char dummy;
1429 unsigned int val;
1431 if (GTK_IS_WIDGET(ud->obj) &&
1432 sscanf(ud->data, "%u %c", &val, &dummy) == 1 && val < 2)
1433 gtk_widget_set_visible(GTK_WIDGET(ud->obj), val);
1434 else
1435 ign_cmd(ud->type, ud->cmd);
1439 * Change the style of the widget passed. Runs inside gtk_main().
1441 static void
1442 update_widget_style(struct ui_data *ud)
1444 GtkStyleContext *context;
1445 GtkStyleProvider *style_provider;
1446 char *style_decl;
1447 const char *prefix = "* {", *suffix = "}";
1448 size_t sz;
1450 if (!GTK_IS_WIDGET(ud->obj)) {
1451 ign_cmd(ud->type, ud->cmd);
1452 return;
1454 style_provider = g_object_get_data(ud->obj, "style_provider");
1455 sz = strlen(prefix) + strlen(suffix) + strlen(ud->data) + 1;
1456 context = gtk_widget_get_style_context(GTK_WIDGET(ud->obj));
1457 gtk_style_context_remove_provider(context, style_provider);
1458 if ((style_decl = malloc(sz)) == NULL)
1459 OOM_ABORT;
1460 strcpy(style_decl, prefix);
1461 strcat(style_decl, ud->data);
1462 strcat(style_decl, suffix);
1463 gtk_style_context_add_provider(context, style_provider,
1464 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1465 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
1466 style_decl, -1, NULL);
1467 free(style_decl);
1471 * Check if one of the generic actions is requested; complain if none
1472 * of them is
1474 static void
1475 try_generic_cmds(struct ui_data *ud)
1477 if (eql(ud->action, "block"))
1478 update_blocked(ud);
1479 else if (eql(ud->action, "set_sensitive"))
1480 update_sensitivity(ud);
1481 else if (eql(ud->action, "set_visible"))
1482 update_visibility(ud);
1483 else if (eql(ud->action, "set_tooltip_text"))
1484 update_tooltip_text(ud);
1485 else if (eql(ud->action, "grab_focus"))
1486 update_focus(ud);
1487 else if (eql(ud->action, "set_size_request"))
1488 update_size_request(ud);
1489 else if (eql(ud->action, "style"))
1490 update_widget_style(ud);
1491 else if (eql(ud->action, "force"))
1492 fake_ui_activity(ud);
1493 /* next line intentionally mangled to exclude it from */
1494 /* auto-generated list of commands */
1495 else if (eql(ud->action, /* undocumented! */ "ping"))
1496 ping(ud);
1497 else if (eql(ud->action, "snapshot"))
1498 take_snapshot(ud);
1499 else
1500 ign_cmd(ud->type, ud->cmd);
1504 * Manipulation of specific widgets
1507 static void
1508 update_button(struct ui_data *ud)
1510 if (eql(ud->action, "set_label"))
1511 gtk_button_set_label(GTK_BUTTON(ud->obj), ud->data);
1512 else
1513 try_generic_cmds(ud);
1516 static void
1517 update_calendar(struct ui_data *ud)
1519 GtkCalendar *calendar = GTK_CALENDAR(ud->obj);
1520 char dummy;
1521 int year = 0, month = 0, day = 0;
1523 if (eql(ud->action, "select_date") &&
1524 sscanf(ud->data, "%d-%d-%d %c", &year, &month, &day, &dummy) == 3) {
1525 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
1526 gtk_calendar_select_month(calendar, --month, year);
1527 gtk_calendar_select_day(calendar, day);
1528 } else
1529 ign_cmd(ud->type, ud->cmd);
1530 } else if (eql(ud->action, "mark_day") &&
1531 sscanf(ud->data, "%d %c", &day, &dummy) == 1) {
1532 if (day > 0 && day <= 31)
1533 gtk_calendar_mark_day(calendar, day);
1534 else
1535 ign_cmd(ud->type, ud->cmd);
1536 } else if (eql(ud->action, "clear_marks") && sscanf(ud->data, " %c", &dummy) < 1)
1537 gtk_calendar_clear_marks(calendar);
1538 else
1539 try_generic_cmds(ud);
1543 * Common actions for various kinds of window. Return false if
1544 * command is ignored. Runs inside gtk_main().
1546 static bool
1547 update_class_window(struct ui_data *ud)
1549 GtkWindow *window = GTK_WINDOW(ud->obj);
1550 char dummy;
1551 int x, y;
1553 if (eql(ud->action, "set_title"))
1554 gtk_window_set_title(window, ud->data);
1555 else if (eql(ud->action, "fullscreen") && sscanf(ud->data, " %c", &dummy) < 1)
1556 gtk_window_fullscreen(window);
1557 else if (eql(ud->action, "unfullscreen") && sscanf(ud->data, " %c", &dummy) < 1)
1558 gtk_window_unfullscreen(window);
1559 else if (eql(ud->action, "resize") &&
1560 sscanf(ud->data, "%d %d %c", &x, &y, &dummy) == 2)
1561 gtk_window_resize(window, x, y);
1562 else if (eql(ud->action, "resize") && sscanf(ud->data, " %c", &dummy) < 1) {
1563 gtk_window_get_default_size(window, &x, &y);
1564 gtk_window_resize(window, x, y);
1565 } else if (eql(ud->action, "move") &&
1566 sscanf(ud->data, "%d %d %c", &x, &y, &dummy) == 2)
1567 gtk_window_move(window, x, y);
1568 else
1569 return false;
1570 return true;
1573 static void
1574 update_color_button(struct ui_data *ud)
1576 GdkRGBA color;
1578 if (eql(ud->action, "set_color")) {
1579 gdk_rgba_parse(&color, ud->data);
1580 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(ud->obj), &color);
1581 } else
1582 try_generic_cmds(ud);
1585 static void
1586 update_combo_box_text(struct ui_data *ud)
1588 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(ud->obj);
1589 char dummy;
1590 int txt0, pos;
1592 if (eql(ud->action, "prepend_text"))
1593 gtk_combo_box_text_prepend_text(combobox, ud->data);
1594 else if (eql(ud->action, "append_text"))
1595 gtk_combo_box_text_append_text(combobox, ud->data);
1596 else if (eql(ud->action, "remove") &&
1597 sscanf(ud->data, "%d %c", &pos, &dummy) == 1)
1598 gtk_combo_box_text_remove(combobox, pos);
1599 else if (eql(ud->action, "insert_text") &&
1600 sscanf(ud->data, "%d %n", &pos, &txt0) == 1)
1601 gtk_combo_box_text_insert_text(combobox, pos, ud->data + txt0);
1602 else
1603 try_generic_cmds(ud);
1607 * update_drawing_area(), which runs inside gtk_main(), maintains a
1608 * list of drawing operations. It needs a few helper functions. It
1609 * is the responsibility of cb_draw() to actually execute the list.
1612 enum draw_op_stat {
1613 FAILURE,
1614 SUCCESS,
1615 NEED_REDRAW,
1619 * Fill structure *op with the drawing operation according to action
1620 * and with the appropriate set of arguments
1622 static enum draw_op_stat
1623 set_draw_op(struct draw_op *op, const char *action, const char *data)
1625 char dummy;
1626 const char *raw_args = data;
1627 enum draw_op_stat result = SUCCESS;
1628 int args_start = 0;
1630 if (sscanf(data, "=%llu %n", &op->id, &args_start) == 1) {
1631 op->policy = REPLACE;
1632 result = NEED_REDRAW;
1633 } else if (sscanf(data, "%llu<%llu %n", &op->id, &op->before, &args_start) == 2) {
1634 op->policy = BEFORE;
1635 result = NEED_REDRAW;
1636 } else if (sscanf(data, "%llu %n", &op->id, &args_start) == 1)
1637 op->policy = APPEND;
1638 else
1639 return FAILURE;
1640 raw_args += args_start;
1641 if (eql(action, "line_to")) {
1642 struct move_to_args *args;
1644 if ((args = malloc(sizeof(*args))) == NULL)
1645 OOM_ABORT;
1646 op->op = LINE_TO;
1647 op->op_args = args;
1648 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1649 return FAILURE;
1650 } else if (eql(action, "rel_line_to")) {
1651 struct move_to_args *args;
1653 if ((args = malloc(sizeof(*args))) == NULL)
1654 OOM_ABORT;
1655 op->op = REL_LINE_TO;
1656 op->op_args = args;
1657 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1658 return FAILURE;
1659 } else if (eql(action, "move_to")) {
1660 struct move_to_args *args;
1662 if ((args = malloc(sizeof(*args))) == NULL)
1663 OOM_ABORT;
1664 op->op = MOVE_TO;
1665 op->op_args = args;
1666 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1667 return FAILURE;
1668 } else if (eql(action, "rel_move_to")) {
1669 struct move_to_args *args;
1671 if ((args = malloc(sizeof(*args))) == NULL)
1672 OOM_ABORT;
1673 op->op = REL_MOVE_TO;
1674 op->op_args = args;
1675 if (sscanf(raw_args, "%lf %lf %c", &args->x, &args->y, &dummy) != 2)
1676 return FAILURE;
1677 } else if (eql(action, "arc")) {
1678 struct arc_args *args;
1679 double deg1, deg2;
1681 if ((args = malloc(sizeof(*args))) == NULL)
1682 OOM_ABORT;
1683 op->op = ARC;
1684 op->op_args = args;
1685 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %c",
1686 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 5)
1687 return FAILURE;
1688 args->angle1 = deg1 * (M_PI / 180.L);
1689 args->angle2 = deg2 * (M_PI / 180.L);
1690 } else if (eql(action, "arc_negative")) {
1691 double deg1, deg2;
1692 struct arc_args *args;
1694 if ((args = malloc(sizeof(*args))) == NULL)
1695 OOM_ABORT;
1696 op->op = ARC_NEGATIVE;
1697 op->op_args = args;
1698 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %c",
1699 &args->x, &args->y, &args->radius, &deg1, &deg2, &dummy) != 5)
1700 return FAILURE;
1701 args->angle1 = deg1 * (M_PI / 180.L);
1702 args->angle2 = deg2 * (M_PI / 180.L);
1703 } else if (eql(action, "curve_to")) {
1704 struct curve_to_args *args;
1706 if ((args = malloc(sizeof(*args))) == NULL)
1707 OOM_ABORT;
1708 op->op = CURVE_TO;
1709 op->op_args = args;
1710 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1711 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 6)
1712 return FAILURE;
1713 } else if (eql(action, "rel_curve_to")) {
1714 struct curve_to_args *args;
1716 if ((args = malloc(sizeof(*args))) == NULL)
1717 OOM_ABORT;
1718 op->op = REL_CURVE_TO;
1719 op->op_args = args;
1720 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1721 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3, &dummy) != 6)
1722 return FAILURE;
1723 } else if (eql(action, "rectangle")) {
1724 struct rectangle_args *args;
1726 if ((args = malloc(sizeof(*args))) == NULL)
1727 OOM_ABORT;
1728 op->op = RECTANGLE;
1729 op->op_args = args;
1730 if (sscanf(raw_args, "%lf %lf %lf %lf %c",
1731 &args->x, &args->y, &args->width, &args->height, &dummy) != 4)
1732 return FAILURE;
1733 } else if (eql(action, "close_path")) {
1734 op->op = CLOSE_PATH;
1735 if (sscanf(raw_args, " %c", &dummy) > 0)
1736 return FAILURE;
1737 op->op_args = NULL;
1738 } else if (eql(action, "show_text")) {
1739 struct show_text_args *args;
1740 int len;
1742 len = strlen(raw_args) + 1;
1743 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1744 OOM_ABORT;
1745 op->op = SHOW_TEXT;
1746 op->op_args = args;
1747 args->len = len; /* not used */
1748 strncpy(args->text, raw_args, len);
1749 result = NEED_REDRAW;
1750 } else if (eql(action, "rel_move_for")) {
1751 char ref_point[2 + 1];
1752 int start, len;
1753 struct rel_move_for_args *args;
1755 if (sscanf(raw_args, "%2s %n", ref_point, &start) < 1)
1756 return FAILURE;
1757 len = strlen(raw_args + start) + 1;
1758 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1759 OOM_ABORT;
1760 if (eql(ref_point, "c"))
1761 args->ref = C;
1762 else if (eql(ref_point, "e"))
1763 args->ref = E;
1764 else if (eql(ref_point, "n"))
1765 args->ref = N;
1766 else if (eql(ref_point, "ne"))
1767 args->ref = NE;
1768 else if (eql(ref_point, "nw"))
1769 args->ref = NW;
1770 else if (eql(ref_point, "s"))
1771 args->ref = S;
1772 else if (eql(ref_point, "se"))
1773 args->ref = SE;
1774 else if (eql(ref_point, "sw"))
1775 args->ref = SW;
1776 else if (eql(ref_point, "w"))
1777 args->ref = W;
1778 else
1779 return FAILURE;
1780 op->op = REL_MOVE_FOR;
1781 op->op_args = args;
1782 args->len = len; /* not used */
1783 strncpy(args->text, (raw_args + start), len);
1784 } else if (eql(action, "stroke")) {
1785 op->op = STROKE;
1786 if (sscanf(raw_args, " %c", &dummy) > 0)
1787 return FAILURE;
1788 op->op_args = NULL;
1789 result = NEED_REDRAW;
1790 } else if (eql(action, "stroke_preserve")) {
1791 op->op = STROKE_PRESERVE;
1792 if (sscanf(raw_args, " %c", &dummy) > 0)
1793 return FAILURE;
1794 op->op_args = NULL;
1795 result = NEED_REDRAW;
1796 } else if (eql(action, "fill")) {
1797 op->op = FILL;
1798 if (sscanf(raw_args, " %c", &dummy) > 0)
1799 return FAILURE;
1800 op->op_args = NULL;
1801 result = NEED_REDRAW;
1802 } else if (eql(action, "fill_preserve")) {
1803 op->op = FILL_PRESERVE;
1804 if (sscanf(raw_args, " %c", &dummy) > 0)
1805 return FAILURE;
1806 op->op_args = NULL;
1807 result = NEED_REDRAW;
1808 } else if (eql(action, "set_dash")) {
1809 char *next, *end;
1810 char data1[strlen(raw_args) + 1];
1811 int n, i;
1812 struct set_dash_args *args;
1814 strcpy(data1, raw_args);
1815 next = end = data1;
1816 n = -1;
1817 do {
1818 n++;
1819 next = end;
1820 strtod(next, &end);
1821 } while (next != end);
1822 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
1823 OOM_ABORT;
1824 op->op = SET_DASH;
1825 op->op_args = args;
1826 args->num_dashes = n;
1827 for (i = 0, next = data1; i < n; i++, next = end) {
1828 args->dashes[i] = strtod(next, &end);
1830 } else if (eql(action, "set_font_face")) {
1831 char slant[7 + 1]; /* "oblique" */
1832 char weight[6 + 1]; /* "normal" */
1833 int family_start, family_len;
1834 struct set_font_face_args *args;
1836 if (sscanf(raw_args, "%7s %6s %n%*s", slant, weight, &family_start) != 2)
1837 return FAILURE;
1838 family_len = strlen(raw_args + family_start) + 1;
1839 if ((args = malloc(sizeof(*args) + family_len * sizeof(args->family[0]))) == NULL)
1840 OOM_ABORT;
1841 op->op = SET_FONT_FACE;
1842 op->op_args = args;
1843 strncpy(args->family, raw_args + family_start, family_len);
1844 if (eql(slant, "normal"))
1845 args->slant = CAIRO_FONT_SLANT_NORMAL;
1846 else if (eql(slant, "italic"))
1847 args->slant = CAIRO_FONT_SLANT_ITALIC;
1848 else if (eql(slant, "oblique"))
1849 args->slant = CAIRO_FONT_SLANT_OBLIQUE;
1850 else
1851 return FAILURE;
1852 if (eql(weight, "normal"))
1853 args->weight = CAIRO_FONT_WEIGHT_NORMAL;
1854 else if (eql(weight, "bold"))
1855 args->weight = CAIRO_FONT_WEIGHT_BOLD;
1856 else
1857 return FAILURE;
1858 } else if (eql(action, "set_font_size")) {
1859 struct set_font_size_args *args;
1861 if ((args = malloc(sizeof(*args))) == NULL)
1862 OOM_ABORT;
1863 op->op = SET_FONT_SIZE;
1864 op->op_args = args;
1865 if (sscanf(raw_args, "%lf %c", &args->size, &dummy) != 1)
1866 return FAILURE;
1867 } else if (eql(action, "set_line_cap")) {
1868 char str[6 + 1]; /* "square" */
1869 struct set_line_cap_args *args;
1871 if ((args = malloc(sizeof(*args))) == NULL)
1872 OOM_ABORT;
1873 op->op = SET_LINE_CAP;
1874 op->op_args = args;
1875 if (sscanf(raw_args, "%6s %c", str, &dummy) != 1)
1876 return FAILURE;
1877 if (eql(str, "butt"))
1878 args->line_cap = CAIRO_LINE_CAP_BUTT;
1879 else if (eql(str, "round"))
1880 args->line_cap = CAIRO_LINE_CAP_ROUND;
1881 else if (eql(str, "square"))
1882 args->line_cap = CAIRO_LINE_CAP_SQUARE;
1883 else
1884 return FAILURE;
1885 } else if (eql(action, "set_line_join")) {
1886 char str[5 + 1]; /* "miter" */
1887 struct set_line_join_args *args;
1889 if ((args = malloc(sizeof(*args))) == NULL)
1890 OOM_ABORT;
1891 op->op = SET_LINE_JOIN;
1892 op->op_args = args;
1893 if (sscanf(raw_args, "%5s %c", str, &dummy) != 1)
1894 return FAILURE;
1895 if (eql(str, "miter"))
1896 args->line_join = CAIRO_LINE_JOIN_MITER;
1897 else if (eql(str, "round"))
1898 args->line_join = CAIRO_LINE_JOIN_ROUND;
1899 else if (eql(str, "bevel"))
1900 args->line_join = CAIRO_LINE_JOIN_BEVEL;
1901 else
1902 return FAILURE;
1903 } else if (eql(action, "set_line_width")) {
1904 struct set_line_width_args *args;
1906 if ((args = malloc(sizeof(*args))) == NULL)
1907 OOM_ABORT;
1908 op->op = SET_LINE_WIDTH;
1909 op->op_args = args;
1910 if (sscanf(raw_args, "%lf %c", &args->width, &dummy) != 1)
1911 return FAILURE;
1912 } else if (eql(action, "set_source_rgba")) {
1913 struct set_source_rgba_args *args;
1915 if ((args = malloc(sizeof(*args))) == NULL)
1916 OOM_ABORT;
1917 op->op = SET_SOURCE_RGBA;
1918 op->op_args = args;
1919 gdk_rgba_parse(&args->color, raw_args);
1920 } else if (eql(action, "transform")) {
1921 char dummy;
1922 double xx, yx, xy, yy, x0, y0;
1924 if (sscanf(raw_args, "%lf %lf %lf %lf %lf %lf %c",
1925 &xx, &yx, &xy, &yy, &x0, &y0, &dummy) == 6) {
1926 struct transform_args *args;
1928 if ((args = malloc(sizeof(*args))) == NULL)
1929 OOM_ABORT;
1930 op->op_args = args;
1931 op->op = TRANSFORM;
1932 cairo_matrix_init(&args->matrix, xx, yx, xy, yy, x0, y0);
1933 } else if (sscanf(raw_args, " %c", &dummy) < 1) {
1934 op->op = RESET_CTM;
1935 op->op_args = NULL;
1936 } else
1937 return FAILURE;
1938 } else if (eql(action, "translate")) {
1939 double tx, ty;
1940 struct transform_args *args;
1942 if ((args = malloc(sizeof(*args))) == NULL)
1943 OOM_ABORT;
1944 op->op = TRANSFORM;
1945 op->op_args = args;
1946 if (sscanf(raw_args, "%lf %lf %c", &tx, &ty, &dummy) != 2)
1947 return FAILURE;
1948 cairo_matrix_init_translate(&args->matrix, tx, ty);
1949 } else if (eql(action, "scale")) {
1950 double sx, sy;
1951 struct transform_args *args;
1953 if ((args = malloc(sizeof(*args))) == NULL)
1954 OOM_ABORT;
1955 op->op = TRANSFORM;
1956 op->op_args = args;
1957 if (sscanf(raw_args, "%lf %lf %c", &sx, &sy, &dummy) != 2)
1958 return FAILURE;
1959 cairo_matrix_init_scale(&args->matrix, sx, sy);
1960 } else if (eql(action, "rotate")) {
1961 double angle;
1962 struct transform_args *args;
1964 if ((args = malloc(sizeof(*args))) == NULL)
1965 OOM_ABORT;
1966 op->op = TRANSFORM;
1967 op->op_args = args;
1968 if (sscanf(raw_args, "%lf %c", &angle, &dummy) != 1)
1969 return FAILURE;
1970 cairo_matrix_init_rotate(&args->matrix, angle * (M_PI / 180.L));
1971 } else
1972 return FAILURE;
1973 return result;
1977 * Add another element to widget's "draw_ops" list
1979 static enum draw_op_stat
1980 ins_draw_op(GObject *widget, const char *action, const char *data)
1982 enum draw_op_stat result;
1983 struct draw_op *new_op = NULL, *draw_ops = NULL, *prev_op = NULL;
1985 if ((new_op = malloc(sizeof(*new_op))) == NULL)
1986 OOM_ABORT;
1987 new_op->op_args = NULL;
1988 new_op->next = NULL;
1989 if ((result = set_draw_op(new_op, action, data)) == FAILURE) {
1990 free(new_op->op_args);
1991 free(new_op);
1992 return FAILURE;
1994 switch (new_op->policy) {
1995 case APPEND:
1996 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL)
1997 g_object_set_data(widget, "draw_ops", new_op);
1998 else {
1999 for (prev_op = draw_ops;
2000 prev_op->next != NULL;
2001 prev_op = prev_op->next);
2002 prev_op->next = new_op;
2004 break;
2005 case BEFORE:
2006 for (prev_op = NULL, draw_ops = g_object_get_data(widget, "draw_ops");
2007 draw_ops != NULL && draw_ops->id != new_op->before;
2008 prev_op = draw_ops, draw_ops = draw_ops->next);
2009 if (prev_op == NULL) { /* prepend a new first element */
2010 g_object_set_data(widget, "draw_ops", new_op);
2011 new_op->next = draw_ops;
2012 } else if (draw_ops == NULL) /* append */
2013 prev_op->next = new_op;
2014 else { /* insert */
2015 new_op->next = draw_ops;
2016 prev_op->next = new_op;
2018 break;
2019 case REPLACE:
2020 for (prev_op = NULL, draw_ops = g_object_get_data(widget, "draw_ops");
2021 draw_ops != NULL && draw_ops->id != new_op->id;
2022 prev_op = draw_ops, draw_ops = draw_ops->next);
2023 if (draw_ops == NULL && prev_op == NULL) /* start a new list */
2024 g_object_set_data(widget, "draw_ops", new_op);
2025 else if (prev_op == NULL) { /* replace the first element */
2026 g_object_set_data(widget, "draw_ops", new_op);
2027 new_op->next = draw_ops->next;
2028 free(draw_ops->op_args);
2029 free(draw_ops);
2030 } else if (draw_ops == NULL) /* append */
2031 prev_op->next = new_op;
2032 else { /* replace some other element */
2033 new_op->next = draw_ops->next;
2034 prev_op->next = new_op;
2035 free(draw_ops->op_args);
2036 free(draw_ops);
2038 break;
2039 default:
2040 ABORT;
2041 break;
2043 return result;
2047 * Remove all elements with the given id from widget's "draw_ops" list
2049 static enum draw_op_stat
2050 rem_draw_op(GObject *widget, const char *data)
2052 char dummy;
2053 struct draw_op *op, *next_op, *prev_op = NULL;
2054 unsigned long long int id;
2056 if (sscanf(data, "%llu %c", &id, &dummy) != 1)
2057 return FAILURE;
2058 op = g_object_get_data(widget, "draw_ops");
2059 while (op != NULL) {
2060 next_op = op->next;
2061 if (op->id == id) {
2062 if (prev_op == NULL) /* list head */
2063 g_object_set_data(widget, "draw_ops", op->next);
2064 else
2065 prev_op->next = op->next;
2066 free(op->op_args);
2067 free(op);
2068 } else
2069 prev_op = op;
2070 op = next_op;
2072 return NEED_REDRAW;
2075 static gboolean
2076 refresh_widget(GtkWidget *widget)
2078 gint height = gtk_widget_get_allocated_height(widget);
2079 gint width = gtk_widget_get_allocated_width(widget);
2081 gtk_widget_queue_draw_area(widget, 0, 0, width, height);
2082 return G_SOURCE_REMOVE;
2085 static void
2086 update_drawing_area(struct ui_data *ud)
2088 enum draw_op_stat dost;
2090 if (eql(ud->action, "remove"))
2091 dost = rem_draw_op(ud->obj, ud->data);
2092 else
2093 dost = ins_draw_op(ud->obj, ud->action, ud->data);
2094 switch (dost) {
2095 case NEED_REDRAW:
2096 gdk_threads_add_idle_full(G_PRIORITY_LOW,
2097 (GSourceFunc) refresh_widget,
2098 GTK_WIDGET(ud->obj), NULL);
2099 break;
2100 case FAILURE:
2101 try_generic_cmds(ud);
2102 break;
2103 case SUCCESS:
2104 break;
2105 default:
2106 ABORT;
2107 break;
2111 static void
2112 update_entry(struct ui_data *ud)
2114 GtkEntry *entry = GTK_ENTRY(ud->obj);
2116 if (eql(ud->action, "set_text"))
2117 gtk_entry_set_text(entry, ud->data);
2118 else if (eql(ud->action, "set_placeholder_text"))
2119 gtk_entry_set_placeholder_text(entry, ud->data);
2120 else
2121 try_generic_cmds(ud);
2124 static void
2125 update_expander(struct ui_data *ud)
2127 GtkExpander *expander = GTK_EXPANDER(ud->obj);
2128 char dummy;
2129 unsigned int val;
2131 if (eql(ud->action, "set_expanded") &&
2132 sscanf(ud->data, "%u %c", &val, &dummy) == 1 && val < 2)
2133 gtk_expander_set_expanded(expander, val);
2134 else if (eql(ud->action, "set_label"))
2135 gtk_expander_set_label(expander, ud->data);
2136 else
2137 try_generic_cmds(ud);
2140 static void
2141 update_file_chooser_button(struct ui_data *ud)
2143 if (eql(ud->action, "set_filename"))
2144 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(ud->obj), ud->data);
2145 else
2146 try_generic_cmds(ud);
2149 static void
2150 update_file_chooser_dialog(struct ui_data *ud)
2152 GtkFileChooser *chooser = GTK_FILE_CHOOSER(ud->obj);
2154 if (eql(ud->action, "set_filename"))
2155 gtk_file_chooser_set_filename(chooser, ud->data);
2156 else if (eql(ud->action, "set_current_name"))
2157 gtk_file_chooser_set_current_name(chooser, ud->data);
2158 else if (update_class_window(ud));
2159 else
2160 try_generic_cmds(ud);
2163 static void
2164 update_font_button(struct ui_data *ud){
2165 GtkFontButton *font_button = GTK_FONT_BUTTON(ud->obj);
2167 if (eql(ud->action, "set_font_name"))
2168 gtk_font_button_set_font_name(font_button, ud->data);
2169 else
2170 try_generic_cmds(ud);
2173 static void
2174 update_frame(struct ui_data *ud)
2176 if (eql(ud->action, "set_label"))
2177 gtk_frame_set_label(GTK_FRAME(ud->obj), ud->data);
2178 else
2179 try_generic_cmds(ud);
2182 static void
2183 update_image(struct ui_data *ud)
2185 GtkIconSize size;
2186 GtkImage *image = GTK_IMAGE(ud->obj);
2188 gtk_image_get_icon_name(image, NULL, &size);
2189 if (eql(ud->action, "set_from_file"))
2190 gtk_image_set_from_file(image, ud->data);
2191 else if (eql(ud->action, "set_from_icon_name"))
2192 gtk_image_set_from_icon_name(image, ud->data, size);
2193 else
2194 try_generic_cmds(ud);
2197 static void
2198 update_label(struct ui_data *ud)
2200 if (eql(ud->action, "set_text"))
2201 gtk_label_set_text(GTK_LABEL(ud->obj), ud->data);
2202 else
2203 try_generic_cmds(ud);
2206 static void
2207 update_link_button(struct ui_data *ud)
2209 char dummy;
2210 unsigned int val;
2212 if (eql(ud->action, "set_visited") &&
2213 sscanf(ud->data, "%u %c", &val, &dummy) == 1 && val < 2)
2214 gtk_link_button_set_visited(GTK_LINK_BUTTON(ud->obj), val);
2215 else
2216 update_button(ud);
2219 static void
2220 update_menu(struct ui_data *ud)
2222 char dummy;
2223 GtkMenu* menu = GTK_MENU(ud->obj);
2225 if (eql(ud->action, "popup") && sscanf(ud->data, " %c", &dummy) < 1)
2226 gtk_menu_popup(menu, NULL, NULL, NULL, NULL, 0,
2227 gtk_get_current_event_time());
2228 else if (eql(ud->action, "popdown") && sscanf(ud->data, " %c", &dummy) < 1)
2229 gtk_menu_popdown(menu);
2230 else
2231 try_generic_cmds(ud);
2234 static void
2235 update_menu_item(struct ui_data *ud)
2237 try_generic_cmds(ud);
2240 static void
2241 update_notebook(struct ui_data *ud)
2243 char dummy;
2244 int val, n_pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(ud->obj));
2246 if (eql(ud->action, "set_current_page") &&
2247 sscanf(ud->data, "%d %c", &val, &dummy) == 1 &&
2248 val >= 0 && val < n_pages)
2249 gtk_notebook_set_current_page(GTK_NOTEBOOK(ud->obj), val);
2250 else
2251 try_generic_cmds(ud);
2254 static void
2255 update_nothing(struct ui_data *ud)
2257 (void) ud;
2260 static void
2261 update_print_dialog(struct ui_data *ud)
2263 GtkPageSetup *page_setup;
2264 GtkPrintJob *job;
2265 GtkPrintSettings *settings;
2266 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(ud->obj);
2267 GtkPrinter *printer;
2268 gint response_id;
2270 if (eql(ud->action, "print")) {
2271 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
2272 switch (response_id) {
2273 case GTK_RESPONSE_OK:
2274 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
2275 settings = gtk_print_unix_dialog_get_settings(dialog);
2276 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
2277 job = gtk_print_job_new(ud->data, printer, settings, page_setup);
2278 if (gtk_print_job_set_source_file(job, ud->data, NULL))
2279 gtk_print_job_send(job, NULL, NULL, NULL);
2280 else
2281 ign_cmd(ud->type, ud->cmd);
2282 g_clear_object(&settings);
2283 g_clear_object(&job);
2284 break;
2285 case GTK_RESPONSE_CANCEL:
2286 case GTK_RESPONSE_DELETE_EVENT:
2287 break;
2288 default:
2289 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
2290 widget_id(GTK_BUILDABLE(dialog)), response_id);
2291 break;
2293 gtk_widget_hide(GTK_WIDGET(dialog));
2294 } else
2295 try_generic_cmds(ud);
2298 static void
2299 update_progress_bar(struct ui_data *ud)
2301 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(ud->obj);
2302 char dummy;
2303 double frac;
2305 if (eql(ud->action, "set_text"))
2306 gtk_progress_bar_set_text(progressbar, *(ud->data) == '\0' ? NULL : ud->data);
2307 else if (eql(ud->action, "set_fraction") &&
2308 sscanf(ud->data, "%lf %c", &frac, &dummy) == 1)
2309 gtk_progress_bar_set_fraction(progressbar, frac);
2310 else
2311 try_generic_cmds(ud);
2314 static void
2315 update_scale(struct ui_data *ud)
2317 GtkRange *range = GTK_RANGE(ud->obj);
2318 char dummy;
2319 double val1, val2;
2321 if (eql(ud->action, "set_value") && sscanf(ud->data, "%lf %c", &val1, &dummy) == 1)
2322 gtk_range_set_value(range, val1);
2323 else if (eql(ud->action, "set_fill_level") &&
2324 sscanf(ud->data, "%lf %c", &val1, &dummy) == 1) {
2325 gtk_range_set_fill_level(range, val1);
2326 gtk_range_set_show_fill_level(range, TRUE);
2327 } else if (eql(ud->action, "set_fill_level") &&
2328 sscanf(ud->data, " %c", &dummy) < 1)
2329 gtk_range_set_show_fill_level(range, FALSE);
2330 else if (eql(ud->action, "set_range") &&
2331 sscanf(ud->data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2332 gtk_range_set_range(range, val1, val2);
2333 else if (eql(ud->action, "set_increments") &&
2334 sscanf(ud->data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2335 gtk_range_set_increments(range, val1, val2);
2336 else
2337 try_generic_cmds(ud);
2340 static void
2341 update_scrolled_window(struct ui_data *ud)
2343 GtkScrolledWindow *window = GTK_SCROLLED_WINDOW(ud->obj);
2344 GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(window);
2345 GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(window);
2346 char dummy;
2347 double d0, d1;
2349 if (eql(ud->action, "hscroll") && sscanf(ud->data, "%lf %c", &d0, &dummy) == 1)
2350 gtk_adjustment_set_value(hadj, d0);
2351 else if (eql(ud->action, "vscroll") && sscanf(ud->data, "%lf %c", &d0, &dummy) == 1)
2352 gtk_adjustment_set_value(vadj, d0);
2353 else if (eql(ud->action, "hscroll_to_range") &&
2354 sscanf(ud->data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
2355 gtk_adjustment_clamp_page(hadj, d0, d1);
2356 else if (eql(ud->action, "vscroll_to_range") &&
2357 sscanf(ud->data, "%lf %lf %c", &d0, &d1, &dummy) == 2)
2358 gtk_adjustment_clamp_page(vadj, d0, d1);
2359 else
2360 try_generic_cmds(ud);
2363 static void
2364 update_socket(struct ui_data *ud)
2366 GtkSocket *socket = GTK_SOCKET(ud->obj);
2367 Window id;
2368 char str[BUFLEN], dummy;
2370 if (eql(ud->action, "id") && sscanf(ud->data, " %c", &dummy) < 1) {
2371 id = gtk_socket_get_id(socket);
2372 snprintf(str, BUFLEN, "%lu", id);
2373 send_msg(ud->args->fout, GTK_BUILDABLE(socket), "id", str, NULL);
2374 } else
2375 try_generic_cmds(ud);
2378 static void
2379 update_spin_button(struct ui_data *ud)
2381 GtkSpinButton *spinbutton = GTK_SPIN_BUTTON(ud->obj);
2382 char dummy;
2383 double val1, val2;
2385 if (eql(ud->action, "set_text") && /* TODO: rename to "set_value" */
2386 sscanf(ud->data, "%lf %c", &val1, &dummy) == 1)
2387 gtk_spin_button_set_value(spinbutton, val1);
2388 else if (eql(ud->action, "set_range") &&
2389 sscanf(ud->data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2390 gtk_spin_button_set_range(spinbutton, val1, val2);
2391 else if (eql(ud->action, "set_increments") &&
2392 sscanf(ud->data, "%lf %lf %c", &val1, &val2, &dummy) == 2)
2393 gtk_spin_button_set_increments(spinbutton, val1, val2);
2394 else
2395 try_generic_cmds(ud);
2398 static void
2399 update_spinner(struct ui_data *ud)
2401 GtkSpinner *spinner = GTK_SPINNER(ud->obj);
2402 char dummy;
2404 if (eql(ud->action, "start") && sscanf(ud->data, " %c", &dummy) < 1)
2405 gtk_spinner_start(spinner);
2406 else if (eql(ud->action, "stop") && sscanf(ud->data, " %c", &dummy) < 1)
2407 gtk_spinner_stop(spinner);
2408 else
2409 try_generic_cmds(ud);
2412 static void
2413 update_statusbar(struct ui_data *ud)
2415 GtkStatusbar *statusbar = GTK_STATUSBAR(ud->obj);
2416 char *ctx_msg, dummy;
2417 const char *status_msg;
2418 int ctx_len, t;
2420 /* TODO: remove "push", "pop", "remove_all"; rename "push_id" to "push", etc. */
2421 if ((ctx_msg = malloc(strlen(ud->data) + 1)) == NULL)
2422 OOM_ABORT;
2423 t = sscanf(ud->data, "%s %n%c", ctx_msg, &ctx_len, &dummy);
2424 status_msg = ud->data + ctx_len;
2425 if (eql(ud->action, "push"))
2426 gtk_statusbar_push(statusbar,
2427 gtk_statusbar_get_context_id(statusbar, "0"),
2428 ud->data);
2429 else if (eql(ud->action, "push_id") && t >= 1)
2430 gtk_statusbar_push(statusbar,
2431 gtk_statusbar_get_context_id(statusbar, ctx_msg),
2432 status_msg);
2433 else if (eql(ud->action, "pop") && t < 1)
2434 gtk_statusbar_pop(statusbar,
2435 gtk_statusbar_get_context_id(statusbar, "0"));
2436 else if (eql(ud->action, "pop_id") && t == 1)
2437 gtk_statusbar_pop(statusbar,
2438 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2439 else if (eql(ud->action, "remove_all") && t < 1)
2440 gtk_statusbar_remove_all(statusbar,
2441 gtk_statusbar_get_context_id(statusbar, "0"));
2442 else if (eql(ud->action, "remove_all_id") && t == 1)
2443 gtk_statusbar_remove_all(statusbar,
2444 gtk_statusbar_get_context_id(statusbar, ctx_msg));
2445 else
2446 try_generic_cmds(ud);
2447 free(ctx_msg);
2450 static void
2451 update_switch(struct ui_data *ud)
2453 char dummy;
2454 unsigned int val;
2456 if (eql(ud->action, "set_active") &&
2457 sscanf(ud->data, "%u %c", &val, &dummy) == 1 && val < 2)
2458 gtk_switch_set_active(GTK_SWITCH(ud->obj), val);
2459 else
2460 try_generic_cmds(ud);
2463 static void
2464 update_text_view(struct ui_data *ud)
2466 FILE *sv;
2467 GtkTextView *view = GTK_TEXT_VIEW(ud->obj);
2468 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
2469 GtkTextIter a, b;
2470 char dummy;
2471 int val;
2473 if (eql(ud->action, "set_text"))
2474 gtk_text_buffer_set_text(textbuf, ud->data, -1);
2475 else if (eql(ud->action, "delete") && sscanf(ud->data, " %c", &dummy) < 1) {
2476 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2477 gtk_text_buffer_delete(textbuf, &a, &b);
2478 } else if (eql(ud->action, "insert_at_cursor"))
2479 gtk_text_buffer_insert_at_cursor(textbuf, ud->data, -1);
2480 else if (eql(ud->action, "place_cursor") && eql(ud->data, "end")) {
2481 gtk_text_buffer_get_end_iter(textbuf, &a);
2482 gtk_text_buffer_place_cursor(textbuf, &a);
2483 } else if (eql(ud->action, "place_cursor") &&
2484 sscanf(ud->data, "%d %c", &val, &dummy) == 1) {
2485 gtk_text_buffer_get_iter_at_offset(textbuf, &a, val);
2486 gtk_text_buffer_place_cursor(textbuf, &a);
2487 } else if (eql(ud->action, "place_cursor_at_line") &&
2488 sscanf(ud->data, "%d %c", &val, &dummy) == 1) {
2489 gtk_text_buffer_get_iter_at_line(textbuf, &a, val);
2490 gtk_text_buffer_place_cursor(textbuf, &a);
2491 } else if (eql(ud->action, "scroll_to_cursor") &&
2492 sscanf(ud->data, " %c", &dummy) < 1)
2493 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
2494 0., 0, 0., 0.);
2495 else if (eql(ud->action, "save") && ud->data != NULL &&
2496 (sv = fopen(ud->data, "w")) != NULL) {
2497 gtk_text_buffer_get_bounds(textbuf, &a, &b);
2498 send_msg(sv, GTK_BUILDABLE(view), "insert_at_cursor",
2499 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
2500 fclose(sv);
2501 } else
2502 try_generic_cmds(ud);
2505 static void
2506 update_toggle_button(struct ui_data *ud)
2508 char dummy;
2509 unsigned int val;
2511 if (eql(ud->action, "set_label"))
2512 gtk_button_set_label(GTK_BUTTON(ud->obj), ud->data);
2513 else if (eql(ud->action, "set_active") &&
2514 sscanf(ud->data, "%u %c", &val, &dummy) == 1 && val < 2)
2515 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ud->obj), val);
2516 else
2517 try_generic_cmds(ud);
2521 * update_tree_view(), which runs inside gtk_main(), needs a few
2522 * helper functions
2526 * Check if s is a valid string representation of a GtkTreePath
2528 static bool
2529 is_path_string(char *s)
2531 return s != NULL &&
2532 strlen(s) == strspn(s, ":0123456789") &&
2533 strstr(s, "::") == NULL &&
2534 strcspn(s, ":") > 0;
2537 static void
2538 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
2539 GtkTreeIter *parent, GtkTreeIter *sibling)
2541 if (GTK_IS_TREE_STORE(model))
2542 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
2543 iter, parent, sibling);
2544 else if (GTK_IS_LIST_STORE(model))
2545 gtk_list_store_insert_before(GTK_LIST_STORE(model),
2546 iter, sibling);
2547 else
2548 ABORT;
2551 static void
2552 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
2553 GtkTreeIter *parent, GtkTreeIter *sibling)
2555 if (GTK_IS_TREE_STORE(model))
2556 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
2557 iter, parent, sibling);
2558 else if (GTK_IS_LIST_STORE(model))
2559 gtk_list_store_insert_after(GTK_LIST_STORE(model),
2560 iter, sibling);
2561 else
2562 ABORT;
2565 static void
2566 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
2567 GtkTreeIter *position)
2569 if (GTK_IS_TREE_STORE(model))
2570 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
2571 else if (GTK_IS_LIST_STORE(model))
2572 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
2573 else
2574 ABORT;
2577 static void
2578 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
2580 if (GTK_IS_TREE_STORE(model))
2581 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
2582 else if (GTK_IS_LIST_STORE(model))
2583 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
2584 else
2585 ABORT;
2588 static void
2589 tree_model_clear(GtkTreeModel *model)
2591 if (GTK_IS_TREE_STORE(model))
2592 gtk_tree_store_clear(GTK_TREE_STORE(model));
2593 else if (GTK_IS_LIST_STORE(model))
2594 gtk_list_store_clear(GTK_LIST_STORE(model));
2595 else
2596 ABORT;
2599 static void
2600 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
2602 va_list ap;
2604 va_start(ap, iter);
2605 if (GTK_IS_TREE_STORE(model))
2606 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
2607 else if (GTK_IS_LIST_STORE(model))
2608 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
2609 else
2610 ABORT;
2611 va_end(ap);
2615 * Create an empty row at path if it doesn't yet exist. Create older
2616 * siblings and parents as necessary.
2618 static void
2619 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
2621 GtkTreeIter iter_1; /* iter's predecessor */
2622 GtkTreePath *path_1; /* path's predecessor */
2624 if (gtk_tree_path_get_depth(path) > 0 &&
2625 gtk_tree_model_get_iter(model, iter, path))
2626 return;
2627 path_1 = gtk_tree_path_copy(path);
2628 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
2629 create_subtree(model, path_1, iter);
2630 iter_1 = *iter;
2631 tree_model_insert_after(model, iter, NULL, &iter_1);
2632 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
2633 create_subtree(model, path_1, iter);
2634 if (gtk_tree_path_get_depth(path_1) == 0)
2635 /* first toplevel row */
2636 tree_model_insert_after(model, iter, NULL, NULL);
2637 else { /* first row in a lower level */
2638 iter_1 = *iter;
2639 tree_model_insert_after(model, iter, &iter_1, NULL);
2641 } /* neither prev nor up mean we're at the root of an empty tree */
2642 gtk_tree_path_free(path_1);
2645 static bool
2646 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
2647 const char *path_s, int col, const char *new_text)
2649 GType col_type = gtk_tree_model_get_column_type(model, col);
2650 GtkTreePath *path;
2651 bool ok = false;
2652 char dummy;
2653 double d;
2654 long long int n;
2656 path = gtk_tree_path_new_from_string(path_s);
2657 switch (col_type) {
2658 case G_TYPE_BOOLEAN:
2659 case G_TYPE_INT:
2660 case G_TYPE_LONG:
2661 case G_TYPE_INT64:
2662 case G_TYPE_UINT:
2663 case G_TYPE_ULONG:
2664 case G_TYPE_UINT64:
2665 if (new_text != NULL &&
2666 sscanf(new_text, "%lld %c", &n, &dummy) == 1) {
2667 create_subtree(model, path, iter);
2668 tree_model_set(model, iter, col, n, -1);
2669 ok = true;
2671 break;
2672 case G_TYPE_FLOAT:
2673 case G_TYPE_DOUBLE:
2674 if (new_text != NULL &&
2675 sscanf(new_text, "%lf %c", &d, &dummy) == 1) {
2676 create_subtree(model, path, iter);
2677 tree_model_set(model, iter, col, d, -1);
2678 ok = true;
2680 break;
2681 case G_TYPE_STRING:
2682 create_subtree(model, path, iter);
2683 tree_model_set(model, iter, col, new_text, -1);
2684 ok = true;
2685 break;
2686 default:
2687 fprintf(stderr, "column %d: %s not implemented\n",
2688 col, g_type_name(col_type));
2689 ok = true;
2690 break;
2692 gtk_tree_path_free(path);
2693 return ok;
2696 static void
2697 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
2699 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
2700 /* is just fine and this function need not exist. */
2701 if (path == NULL)
2702 path = gtk_tree_path_new();
2703 gtk_tree_view_set_cursor(view, path, col, false);
2706 static void
2707 update_tree_view(struct ui_data *ud)
2709 GtkTreeView *view = GTK_TREE_VIEW(ud->obj);
2710 GtkTreeIter iter0, iter1;
2711 GtkTreeModel *model = gtk_tree_view_get_model(view);
2712 GtkTreePath *path = NULL;
2713 GtkTreeSelection *sel = gtk_tree_view_get_selection(view);
2714 bool iter0_valid, iter1_valid;
2715 char *tokens, *arg0, *arg1, *arg2;
2716 int col = -1; /* invalid column number */
2717 struct info ar;
2719 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
2721 fprintf(stderr, "missing model/");
2722 ign_cmd(ud->type, ud->cmd);
2723 return;
2725 if ((tokens = malloc(strlen(ud->data) + 1)) == NULL)
2726 OOM_ABORT;
2727 strcpy(tokens, ud->data);
2728 arg0 = strtok(tokens, WHITESPACE);
2729 arg1 = strtok(NULL, WHITESPACE);
2730 arg2 = strtok(NULL, "");
2731 iter0_valid = is_path_string(arg0) &&
2732 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
2733 iter1_valid = is_path_string(arg1) &&
2734 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
2735 if (is_path_string(arg1))
2736 col = strtol(arg1, NULL, 10);
2737 if (eql(ud->action, "set") &&
2738 col > -1 &&
2739 col < gtk_tree_model_get_n_columns(model) &&
2740 is_path_string(arg0)) {
2741 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
2742 ign_cmd(ud->type, ud->cmd);
2743 } else if (eql(ud->action, "scroll") && iter0_valid && iter1_valid &&
2744 arg2 == NULL) {
2745 path = gtk_tree_path_new_from_string(arg0);
2746 gtk_tree_view_scroll_to_cell (view,
2747 path,
2748 gtk_tree_view_get_column(view, col),
2749 0, 0., 0.);
2750 } else if (eql(ud->action, "expand") && iter0_valid && arg1 == NULL) {
2751 path = gtk_tree_path_new_from_string(arg0);
2752 gtk_tree_view_expand_row(view, path, false);
2753 } else if (eql(ud->action, "expand_all") && iter0_valid && arg1 == NULL) {
2754 path = gtk_tree_path_new_from_string(arg0);
2755 gtk_tree_view_expand_row(view, path, true);
2756 } else if (eql(ud->action, "expand_all") && arg0 == NULL)
2757 gtk_tree_view_expand_all(view);
2758 else if (eql(ud->action, "collapse") && iter0_valid && arg1 == NULL) {
2759 path = gtk_tree_path_new_from_string(arg0);
2760 gtk_tree_view_collapse_row(view, path);
2761 } else if (eql(ud->action, "collapse") && arg0 == NULL)
2762 gtk_tree_view_collapse_all(view);
2763 else if (eql(ud->action, "set_cursor") && iter0_valid && arg1 == NULL) {
2764 path = gtk_tree_path_new_from_string(arg0);
2765 tree_view_set_cursor(view, path, NULL);
2766 } else if (eql(ud->action, "set_cursor") && arg0 == NULL) {
2767 tree_view_set_cursor(view, NULL, NULL);
2768 gtk_tree_selection_unselect_all(sel);
2769 } else if (eql(ud->action, "insert_row") &&
2770 eql(arg0, "end") && arg1 == NULL)
2771 tree_model_insert_before(model, &iter1, NULL, NULL);
2772 else if (eql(ud->action, "insert_row") && iter0_valid &&
2773 eql(arg1, "as_child") && arg2 == NULL)
2774 tree_model_insert_after(model, &iter1, &iter0, NULL);
2775 else if (eql(ud->action, "insert_row") && iter0_valid && arg1 == NULL)
2776 tree_model_insert_before(model, &iter1, NULL, &iter0);
2777 else if (eql(ud->action, "move_row") && iter0_valid &&
2778 eql(arg1, "end") && arg2 == NULL)
2779 tree_model_move_before(model, &iter0, NULL);
2780 else if (eql(ud->action, "move_row") && iter0_valid && iter1_valid && arg2 == NULL)
2781 tree_model_move_before(model, &iter0, &iter1);
2782 else if (eql(ud->action, "remove_row") && iter0_valid && arg1 == NULL)
2783 tree_model_remove(model, &iter0);
2784 else if (eql(ud->action, "clear") && arg0 == NULL) {
2785 tree_view_set_cursor(view, NULL, NULL);
2786 gtk_tree_selection_unselect_all(sel);
2787 tree_model_clear(model);
2788 } else if (eql(ud->action, "block") && arg0 != NULL) {
2789 ud->obj=G_OBJECT(sel);
2790 update_blocked(ud);
2791 } else if (eql(ud->action, "save") && arg0 != NULL &&
2792 (ar.fout = fopen(arg0, "w")) != NULL) {
2793 ar.obj = ud->obj;
2794 gtk_tree_model_foreach(model,
2795 (GtkTreeModelForeachFunc) save_tree_row_msg,
2796 &ar);
2797 fclose(ar.fout);
2798 } else
2799 try_generic_cmds(ud);
2800 free(tokens);
2801 gtk_tree_path_free(path);
2804 static void
2805 update_window(struct ui_data *ud)
2807 if (!update_class_window(ud))
2808 try_generic_cmds(ud);
2812 * The final UI update. Runs inside gtk_main().
2814 static void
2815 main_quit(struct ui_data *ud)
2817 char dummy;
2819 if (sscanf(ud->data, " %c", &dummy) < 1)
2820 gtk_main_quit();
2821 else
2822 try_generic_cmds(ud);
2826 * Don't update anything; just complain from inside gtk_main()
2828 static void
2829 complain(struct ui_data *ud)
2831 ign_cmd(ud->type, ud->cmd);
2835 * Parse command pointed to by ud, and act on ui accordingly. Runs
2836 * once per command inside gtk_main().
2838 static gboolean
2839 update_ui(struct ui_data *ud)
2841 char *lc = lc_numeric();
2843 (ud->fn)(ud);
2844 free(ud->cmd_tokens);
2845 free(ud->cmd);
2846 free(ud);
2847 lc_numeric_free(lc);
2848 return G_SOURCE_REMOVE;
2852 * Keep track of loading files to avoid recursive loading of the same
2853 * file. If filename = NULL, forget the most recently remembered file.
2855 static bool
2856 remember_loading_file(char *filename)
2858 static char *filenames[BUFLEN];
2859 static size_t latest = 0;
2860 size_t i;
2862 if (filename == NULL) { /* pop */
2863 if (latest < 1)
2864 ABORT;
2865 latest--;
2866 return false;
2867 } else { /* push */
2868 for (i = 1; i <= latest; i++)
2869 if (eql(filename, filenames[i]))
2870 return false;
2871 if (latest > BUFLEN -2)
2872 return false;
2873 filenames[++latest] = filename;
2874 return true;
2879 * Read lines from stream cmd and perform appropriate actions on the
2880 * GUI. Runs inside receiver thread.
2882 static void *
2883 digest_cmd(struct info *ar)
2885 static int recursion = -1; /* > 0 means this is a recursive call */
2887 recursion++;
2888 for (;;) {
2889 FILE *cmd = ar->fin;
2890 struct ui_data *ud = NULL;
2891 char first_char = '\0';
2892 char *id; /* widget id */
2893 size_t msg_size = 32;
2894 int id_start = 0, id_end = 0;
2895 int action_start = 0, action_end = 0;
2896 int data_start = 0;
2898 if (feof(cmd))
2899 break;
2900 if ((ud = malloc(sizeof(*ud))) == NULL)
2901 OOM_ABORT;
2902 if ((ud->cmd = malloc(msg_size)) == NULL)
2903 OOM_ABORT;
2904 ud->args = ar;
2905 ud->type = G_TYPE_INVALID;
2906 pthread_testcancel();
2907 if (recursion == 0)
2908 log_msg(ar->flog, NULL);
2909 data_start = read_buf(cmd, &ud->cmd, &msg_size);
2910 if (recursion == 0)
2911 log_msg(ar->flog, ud->cmd);
2912 if ((ud->cmd_tokens = malloc(strlen(ud->cmd) + 1)) == NULL)
2913 OOM_ABORT;
2914 sscanf(ud->cmd, " %c", &first_char);
2915 if (data_start == 0 || /* empty line */
2916 first_char == '#') { /* comment */
2917 ud->fn = update_nothing;
2918 goto exec;
2920 strcpy(ud->cmd_tokens, ud->cmd);
2921 sscanf(ud->cmd_tokens,
2922 " %n%*[0-9a-zA-Z_-]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2923 &id_start, &id_end, &action_start, &action_end, &data_start);
2924 ud->cmd_tokens[id_end] = ud->cmd_tokens[action_end] = '\0';
2925 id = ud->cmd_tokens + id_start;
2926 ud->action = ud->cmd_tokens + action_start;
2927 ud->data = ud->cmd_tokens + data_start;
2928 if (eql(ud->action, "main_quit")) {
2929 ud->fn = main_quit;
2930 goto exec;
2932 if (eql(ud->action, "load") && strlen(ud->data) > 0 &&
2933 remember_loading_file(ud->data)) {
2934 struct info a = *ar;
2936 if ((a.fin = fopen(ud->data, "r")) != NULL) {
2937 digest_cmd(&a);
2938 fclose(a.fin);
2939 ud->fn = update_nothing;
2940 } else
2941 ud->fn = complain;
2942 remember_loading_file(NULL);
2943 goto exec;
2945 if ((ud->obj = (gtk_builder_get_object(ar->builder, id))) == NULL) {
2946 ud->fn = complain;
2947 goto exec;
2949 ud->type = G_TYPE_FROM_INSTANCE(ud->obj);
2950 if (ud->type == GTK_TYPE_DRAWING_AREA)
2951 ud->fn = update_drawing_area;
2952 else if (ud->type == GTK_TYPE_TREE_VIEW)
2953 ud->fn = update_tree_view;
2954 else if (ud->type == GTK_TYPE_COMBO_BOX_TEXT)
2955 ud->fn = update_combo_box_text;
2956 else if (ud->type == GTK_TYPE_LABEL)
2957 ud->fn = update_label;
2958 else if (ud->type == GTK_TYPE_IMAGE)
2959 ud->fn = update_image;
2960 else if (ud->type == GTK_TYPE_TEXT_VIEW)
2961 ud->fn = update_text_view;
2962 else if (ud->type == GTK_TYPE_NOTEBOOK)
2963 ud->fn = update_notebook;
2964 else if (ud->type == GTK_TYPE_EXPANDER)
2965 ud->fn = update_expander;
2966 else if (ud->type == GTK_TYPE_FRAME ||
2967 ud->type == GTK_TYPE_ASPECT_FRAME)
2968 ud->fn = update_frame;
2969 else if (ud->type == GTK_TYPE_SCROLLED_WINDOW)
2970 ud->fn = update_scrolled_window;
2971 else if (ud->type == GTK_TYPE_LINK_BUTTON)
2972 ud->fn = update_link_button;
2973 else if (ud->type == GTK_TYPE_BUTTON)
2974 ud->fn = update_button;
2975 else if (ud->type == GTK_TYPE_MENU)
2976 ud->fn = update_menu;
2977 else if (ud->type == GTK_TYPE_MENU_ITEM)
2978 ud->fn = update_menu_item;
2979 else if (ud->type == GTK_TYPE_FILE_CHOOSER_DIALOG)
2980 ud->fn = update_file_chooser_dialog;
2981 else if (ud->type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2982 ud->fn = update_file_chooser_button;
2983 else if (ud->type == GTK_TYPE_COLOR_BUTTON)
2984 ud->fn = update_color_button;
2985 else if (ud->type == GTK_TYPE_FONT_BUTTON)
2986 ud->fn = update_font_button;
2987 else if (ud->type == GTK_TYPE_PRINT_UNIX_DIALOG)
2988 ud->fn = update_print_dialog;
2989 else if (ud->type == GTK_TYPE_SWITCH)
2990 ud->fn = update_switch;
2991 else if (ud->type == GTK_TYPE_TOGGLE_BUTTON ||
2992 ud->type == GTK_TYPE_RADIO_BUTTON ||
2993 ud->type == GTK_TYPE_CHECK_BUTTON)
2994 ud->fn = update_toggle_button;
2995 else if (ud->type == GTK_TYPE_ENTRY)
2996 ud->fn = update_entry;
2997 else if (ud->type == GTK_TYPE_SPIN_BUTTON)
2998 ud->fn = update_spin_button;
2999 else if (ud->type == GTK_TYPE_SCALE)
3000 ud->fn = update_scale;
3001 else if (ud->type == GTK_TYPE_PROGRESS_BAR)
3002 ud->fn = update_progress_bar;
3003 else if (ud->type == GTK_TYPE_SPINNER)
3004 ud->fn = update_spinner;
3005 else if (ud->type == GTK_TYPE_STATUSBAR)
3006 ud->fn = update_statusbar;
3007 else if (ud->type == GTK_TYPE_CALENDAR)
3008 ud->fn = update_calendar;
3009 else if (ud->type == GTK_TYPE_SOCKET)
3010 ud->fn = update_socket;
3011 else if (ud->type == GTK_TYPE_WINDOW ||
3012 ud->type == GTK_TYPE_DIALOG)
3013 ud->fn = update_window;
3014 else
3015 ud->fn = try_generic_cmds;
3016 exec:
3017 pthread_testcancel();
3018 gdk_threads_add_timeout(0, (GSourceFunc) update_ui, ud);
3020 recursion--;
3021 return NULL;
3026 * ============================================================
3027 * Initialization
3028 * ============================================================
3032 * Return the first string xpath obtains from ui_file.
3033 * xmlFree(string) must be called when done
3035 static xmlChar *
3036 xpath1(xmlChar *xpath, const char *ui_file)
3038 xmlChar *r = NULL;
3039 xmlDocPtr doc = NULL;
3040 xmlNodeSetPtr nodes = NULL;
3041 xmlXPathContextPtr ctx = NULL;
3042 xmlXPathObjectPtr xpath_obj = NULL;
3044 if ((doc = xmlParseFile(ui_file)) == NULL)
3045 goto ret0;
3046 if ((ctx = xmlXPathNewContext(doc)) == NULL)
3047 goto ret1;
3048 if ((xpath_obj = xmlXPathEvalExpression(xpath, ctx)) == NULL)
3049 goto ret2;
3050 if ((nodes = xpath_obj->nodesetval) != NULL && nodes->nodeNr > 0)
3051 r = xmlNodeGetContent(nodes->nodeTab[0]);
3052 xmlXPathFreeObject(xpath_obj);
3053 ret2:
3054 xmlXPathFreeContext(ctx);
3055 ret1:
3056 xmlFreeDoc(doc);
3057 ret0:
3058 return r;
3062 * Attach key "col_number" to renderer. Associate "col_number" with
3063 * the corresponding column number in the underlying model.
3064 * Due to what looks like a gap in the GTK API, renderer id and column
3065 * number are taken directly from the XML .ui file.
3067 static bool
3068 tree_view_column_get_renderer_column(GtkBuilder *builder, const char *ui_file,
3069 GtkTreeViewColumn *t_col, int n,
3070 GtkCellRenderer **rnd)
3072 bool r = false;
3073 char *xp_bas1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
3074 char *xp_bas2 = "\"]/child[";
3075 char *xp_bas3 = "]/object[@class=\"GtkCellRendererText\""
3076 " or @class=\"GtkCellRendererToggle\"]/";
3077 char *xp_rnd_id = "@id";
3078 char *xp_text_col = "../attributes/attribute[@name=\"text\""
3079 " or @name=\"active\"]/text()";
3080 const char *tree_col_id = widget_id(GTK_BUILDABLE(t_col));
3081 size_t xp_rnd_nam_len, xp_mod_col_len;
3082 size_t xp_n_len = 3; /* Big Enough (TM) */
3083 xmlChar *xp_rnd_nam = NULL, *xp_mod_col = NULL;
3084 xmlChar *rnd_nam = NULL, *mod_col = NULL;
3086 /* find name of nth cell renderer under the GtkTreeViewColumn */
3087 /* tree_col_id */
3088 xp_rnd_nam_len = strlen(xp_bas1) + strlen(tree_col_id) +
3089 strlen(xp_bas2) + xp_n_len + strlen(xp_bas3) +
3090 strlen(xp_rnd_id) + sizeof('\0');
3091 if ((xp_rnd_nam = malloc(xp_rnd_nam_len)) == NULL)
3092 OOM_ABORT;
3093 snprintf((char *) xp_rnd_nam, xp_rnd_nam_len, "%s%s%s%d%s%s",
3094 xp_bas1, tree_col_id, xp_bas2, n,
3095 xp_bas3, xp_rnd_id);
3096 rnd_nam = xpath1(xp_rnd_nam, ui_file);
3097 /* find the model column that is attached to the nth cell */
3098 /* renderer under GtkTreeViewColumn tree_col_id */
3099 xp_mod_col_len = strlen(xp_bas1) + strlen(tree_col_id) +
3100 strlen(xp_bas2) + xp_n_len + strlen(xp_bas3) +
3101 strlen(xp_text_col) + sizeof('\0');
3102 if ((xp_mod_col = malloc(xp_mod_col_len)) == NULL)
3103 OOM_ABORT;
3104 snprintf((char *) xp_mod_col, xp_mod_col_len, "%s%s%s%d%s%s",
3105 xp_bas1, tree_col_id, xp_bas2, n, xp_bas3, xp_text_col);
3106 mod_col = xpath1(xp_mod_col, ui_file);
3107 if (rnd_nam) {
3108 *rnd = GTK_CELL_RENDERER(
3109 gtk_builder_get_object(builder, (char *) rnd_nam));
3110 if (mod_col) {
3111 g_object_set_data(G_OBJECT(*rnd), "col_number",
3112 GINT_TO_POINTER(strtol((char *) mod_col,
3113 NULL, 10)));
3114 r = true;
3117 free(xp_rnd_nam);
3118 free(xp_mod_col);
3119 xmlFree(rnd_nam);
3120 xmlFree(mod_col);
3121 return r;
3125 * Callbacks that forward a modification of a tree view cell to the
3126 * underlying model
3128 static void
3129 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
3130 const gchar *new_text, struct info *ar)
3132 GtkTreeIter iter;
3133 int col = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer),
3134 "col_number"));
3136 gtk_tree_model_get_iter_from_string(ar->model, &iter, path_s);
3137 set_tree_view_cell(ar->model, &iter, path_s, col,
3138 new_text);
3139 send_tree_cell_msg_by(send_msg, path_s, &iter, col, ar);
3142 static void
3143 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, struct info *ar)
3145 GtkTreeIter iter;
3146 bool toggle_state;
3147 int col = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer),
3148 "col_number"));
3150 gtk_tree_model_get_iter_from_string(ar->model, &iter, path_s);
3151 gtk_tree_model_get(ar->model, &iter, col, &toggle_state, -1);
3152 set_tree_view_cell(ar->model, &iter, path_s, col,
3153 toggle_state? "0" : "1");
3157 * Add new element containing id to the list of callback-handler ids
3158 * stored in obj's field named "signal_id"
3160 static void
3161 push_handler_id(gpointer *obj, unsigned int id)
3163 struct handler_id *prev_hid, *hid;
3165 prev_hid = g_object_get_data(G_OBJECT(obj), "signal-id");
3166 if ((hid = malloc(sizeof(struct handler_id))) == NULL)
3167 OOM_ABORT;
3168 hid->next = prev_hid;
3169 hid->id = id;
3170 hid->blocked = false;
3171 g_object_set_data(G_OBJECT(obj), "signal-id", hid);
3175 * Connect function cb to obj's widget signal sig, remembering the
3176 * handler id in a list in obj's field named "signal-id"
3178 static void
3179 sig_conn(gpointer *obj, char *sig, GCallback cb, struct info *ar)
3181 unsigned int handler_id = g_signal_connect(obj, sig, cb, ar);
3183 push_handler_id(obj, handler_id);
3186 static void
3187 sig_conn_swapped(gpointer *obj, char *sig, GCallback cb, void *data)
3189 unsigned int handler_id = g_signal_connect_swapped(obj, sig, cb, data);
3191 push_handler_id(obj, handler_id);
3194 static void
3195 connect_widget_signals(gpointer *obj, struct info *ar)
3197 GObject *obj2;
3198 GType type = G_TYPE_INVALID;
3199 char *suffix = NULL;
3200 const char *w_id = NULL;
3201 FILE *o = ar->fout;
3203 type = G_TYPE_FROM_INSTANCE(obj);
3204 if (GTK_IS_BUILDABLE(obj))
3205 w_id = widget_id(GTK_BUILDABLE(obj));
3206 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
3207 GList *cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(obj));
3208 GtkTreeViewColumn *tv_col = GTK_TREE_VIEW_COLUMN(obj);
3209 GObject *view = G_OBJECT(
3210 gtk_tree_view_column_get_tree_view(tv_col));
3211 unsigned int i, n_cells = g_list_length(cells);
3213 g_list_free(cells);
3214 sig_conn(obj, "clicked", G_CALLBACK(cb_simple), info_txt_new(o, "clicked"));
3215 for (i = 1; i <= n_cells; i++) {
3216 GtkCellRenderer *renderer;
3217 gboolean editable = FALSE;
3219 if (!tree_view_column_get_renderer_column(ar->builder, ar->txt, tv_col,
3220 i, &renderer))
3221 continue;
3222 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
3223 g_object_get(renderer, "editable", &editable, NULL);
3224 if (editable) {
3225 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
3227 g_signal_connect(renderer, "edited",
3228 G_CALLBACK(cb_tree_model_edit),
3229 info_obj_new(o, view, model));
3231 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
3232 g_object_get(renderer, "activatable", &editable, NULL);
3233 if (editable) {
3234 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
3236 g_signal_connect(renderer, "toggled",
3237 G_CALLBACK(cb_tree_model_toggle),
3238 info_obj_new(o, NULL, model));
3242 } else if (type == GTK_TYPE_LINK_BUTTON)
3243 sig_conn(obj, "activate-link", G_CALLBACK(cb_simple), info_txt_new(o, "clicked"));
3244 else if (type == GTK_TYPE_BUTTON)
3245 /* Button associated with a GtkTextView. */
3246 if ((suffix = strstr(w_id, "_send_text")) != NULL &&
3247 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(ar->builder, suffix, w_id)))
3248 sig_conn(obj, "clicked", G_CALLBACK(cb_send_text),
3249 info_obj_new(o, G_OBJECT(gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2))), NULL));
3250 else if ((suffix = strstr(w_id, "_send_selection")) != NULL &&
3251 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(ar->builder, suffix, w_id)))
3252 sig_conn(obj, "clicked", G_CALLBACK(cb_send_text_selection),
3253 info_obj_new(o, G_OBJECT(gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2))), NULL));
3254 else {
3255 sig_conn(obj, "clicked", G_CALLBACK(cb_simple), info_txt_new(o, "clicked"));
3256 /* Buttons associated with (and part of) a GtkDialog.
3257 * (We shun response ids which could be returned from
3258 * gtk_dialog_run() because that would require the
3259 * user to define those response ids in Glade,
3260 * numerically */
3261 if ((suffix = strstr(w_id, "_cancel")) != NULL &&
3262 GTK_IS_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, w_id)))
3263 if (eql(widget_id(GTK_BUILDABLE(obj2)), MAIN_WIN))
3264 sig_conn_swapped(obj, "clicked",
3265 G_CALLBACK(gtk_main_quit), NULL);
3266 else
3267 sig_conn_swapped(obj, "clicked",
3268 G_CALLBACK(gtk_widget_hide), obj2);
3269 else if ((suffix = strstr(w_id, "_ok")) != NULL &&
3270 GTK_IS_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, w_id))) {
3271 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
3272 sig_conn_swapped(obj, "clicked",
3273 G_CALLBACK(cb_send_file_chooser_dialog_selection),
3274 info_obj_new(o, obj2, NULL));
3275 if (eql(widget_id(GTK_BUILDABLE(obj2)), MAIN_WIN))
3276 sig_conn_swapped(obj, "clicked",
3277 G_CALLBACK(gtk_main_quit), NULL);
3278 else
3279 sig_conn_swapped(obj, "clicked",
3280 G_CALLBACK(gtk_widget_hide), obj2);
3281 } else if ((suffix = strstr(w_id, "_apply")) != NULL &&
3282 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, w_id)))
3283 sig_conn_swapped(obj, "clicked",
3284 G_CALLBACK(cb_send_file_chooser_dialog_selection),
3285 info_obj_new(o, obj2, NULL));
3287 else if (GTK_IS_MENU_ITEM(obj))
3288 if ((suffix = strstr(w_id, "_invoke")) != NULL &&
3289 GTK_IS_DIALOG(obj2 = obj_sans_suffix(ar->builder, suffix, w_id)))
3290 sig_conn_swapped(obj, "activate",
3291 G_CALLBACK(gtk_widget_show), obj2);
3292 else
3293 sig_conn(obj, "activate",
3294 G_CALLBACK(cb_menu_item), info_txt_new(o, "active"));
3295 else if (GTK_IS_WINDOW(obj)) {
3296 sig_conn(obj, "delete-event",
3297 G_CALLBACK(cb_event_simple), info_txt_new(o, "closed"));
3298 if (eql(w_id, MAIN_WIN))
3299 sig_conn_swapped(obj, "delete-event",
3300 G_CALLBACK(gtk_main_quit), NULL);
3301 else
3302 sig_conn(obj, "delete-event",
3303 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
3304 } else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
3305 sig_conn(obj, "file-set",
3306 G_CALLBACK(cb_file_chooser_button), info_txt_new(o, "file"));
3307 else if (type == GTK_TYPE_COLOR_BUTTON)
3308 sig_conn(obj, "color-set",
3309 G_CALLBACK(cb_color_button), info_txt_new(o, "color"));
3310 else if (type == GTK_TYPE_FONT_BUTTON)
3311 sig_conn(obj, "font-set",
3312 G_CALLBACK(cb_font_button), info_txt_new(o, "font"));
3313 else if (type == GTK_TYPE_SWITCH)
3314 sig_conn(obj, "notify::active",
3315 G_CALLBACK(cb_switch), info_txt_new(o, NULL));
3316 else if (type == GTK_TYPE_TOGGLE_BUTTON ||
3317 type == GTK_TYPE_RADIO_BUTTON ||
3318 type == GTK_TYPE_CHECK_BUTTON)
3319 sig_conn(obj, "toggled",
3320 G_CALLBACK(cb_toggle_button), info_txt_new(o, NULL));
3321 else if (type == GTK_TYPE_ENTRY)
3322 sig_conn(obj, "changed",
3323 G_CALLBACK(cb_editable), info_txt_new(o, "text"));
3324 else if (type == GTK_TYPE_SPIN_BUTTON)
3325 sig_conn(obj, "value_changed",
3326 G_CALLBACK(cb_spin_button), info_txt_new(o, "text")); /* TODO: rename to "value" */
3327 else if (type == GTK_TYPE_SCALE)
3328 sig_conn(obj, "value-changed",
3329 G_CALLBACK(cb_range), info_txt_new(o, "value"));
3330 else if (type == GTK_TYPE_CALENDAR) {
3331 sig_conn(obj, "day-selected-double-click",
3332 G_CALLBACK(cb_calendar), info_txt_new(o, "doubleclicked"));
3333 sig_conn(obj, "day-selected",
3334 G_CALLBACK(cb_calendar), info_txt_new(o, "clicked"));
3335 } else if (type == GTK_TYPE_TREE_SELECTION)
3336 sig_conn(obj, "changed",
3337 G_CALLBACK(cb_tree_selection), info_txt_new(o, "clicked"));
3338 else if (type == GTK_TYPE_SOCKET) {
3339 sig_conn(obj, "plug-added",
3340 G_CALLBACK(cb_simple), info_txt_new(o, "plug-added"));
3341 sig_conn(obj, "plug-removed",
3342 G_CALLBACK(cb_simple), info_txt_new(o, "plug-removed"));
3343 /* TODO: rename to plug_added, plug_removed */
3344 } else if (type == GTK_TYPE_DRAWING_AREA)
3345 sig_conn(obj, "draw", G_CALLBACK(cb_draw), NULL);
3346 else if (type == GTK_TYPE_EVENT_BOX) {
3347 gtk_widget_set_can_focus(GTK_WIDGET(obj), true);
3348 sig_conn(obj, "button-press-event",
3349 G_CALLBACK(cb_event_box_button),
3350 info_txt_new(o, "button_press"));
3351 sig_conn(obj, "button-release-event",
3352 G_CALLBACK(cb_event_box_button),
3353 info_txt_new(o, "button_release"));
3354 sig_conn(obj, "motion-notify-event",
3355 G_CALLBACK(cb_event_box_motion),
3356 info_txt_new(o, "motion"));
3357 sig_conn(obj, "key-press-event",
3358 G_CALLBACK(cb_event_box_key),
3359 info_txt_new(o, "key_press"));
3364 * We keep a style provider with each widget
3366 static void
3367 add_widget_style_provider(gpointer *obj, void *data)
3369 GtkCssProvider *style_provider;
3370 GtkStyleContext *context;
3372 (void) data;
3373 if (!GTK_IS_WIDGET(obj))
3374 return;
3375 style_provider = gtk_css_provider_new();
3376 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
3377 gtk_style_context_add_provider(context,
3378 GTK_STYLE_PROVIDER(style_provider),
3379 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
3380 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
3383 static void
3384 prepare_widgets(GtkBuilder *builder, char *ui_file, FILE *out)
3386 GSList *objects = NULL;
3387 struct info ar = {.builder = builder, .fout = out, .txt = ui_file};
3389 objects = gtk_builder_get_objects(builder);
3390 g_slist_foreach(objects, (GFunc) connect_widget_signals, &ar);
3391 g_slist_foreach(objects, (GFunc) add_widget_style_provider, NULL);
3392 g_slist_free(objects);
3396 main(int argc, char *argv[])
3398 GObject *main_window = NULL;
3399 bool bg = false;
3400 char *in_fifo = NULL, *out_fifo = NULL;
3401 char *ui_file = "pipeglade.ui", *log_file = NULL, *err_file = NULL;
3402 char *xid = NULL;
3403 char opt;
3404 pthread_t receiver;
3405 struct info ar;
3407 /* Disable runtime GLIB deprecation warnings: */
3408 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
3409 gtk_init(&argc, &argv);
3410 while ((opt = getopt(argc, argv, "bGhe:i:l:o:O:u:V")) != -1) {
3411 switch (opt) {
3412 case 'b': bg = true; break;
3413 case 'e': xid = optarg; break;
3414 case 'G': show_lib_versions(); break;
3415 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
3416 case 'i': in_fifo = optarg; break;
3417 case 'l': log_file = optarg; break;
3418 case 'o': out_fifo = optarg; break;
3419 case 'O': err_file = optarg; break;
3420 case 'u': ui_file = optarg; break;
3421 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
3422 case '?':
3423 default: bye(EXIT_FAILURE, stderr, USAGE); break;
3426 if (argv[optind] != NULL)
3427 bye(EXIT_FAILURE, stderr,
3428 "illegal parameter '%s'\n" USAGE, argv[optind]);
3429 redirect_stderr(err_file);
3430 ar.fin = open_fifo(in_fifo, "r", stdin, _IONBF);
3431 ar.fout = open_fifo(out_fifo, "w", stdout, _IOLBF);
3432 go_bg_if(bg, ar.fin, ar.fout, err_file);
3433 ar.builder = builder_from_file(ui_file);
3434 ar.flog = open_log(log_file);
3435 pthread_create(&receiver, NULL, (void *(*)(void *)) digest_cmd, &ar);
3436 main_window = find_main_window(ar.builder);
3437 xmlInitParser();
3438 LIBXML_TEST_VERSION;
3439 prepare_widgets(ar.builder, ui_file, ar.fout);
3440 xembed_if(xid, main_window);
3441 gtk_main();
3442 pthread_cancel(receiver);
3443 pthread_join(receiver, NULL);
3444 xmlCleanupParser();
3445 rm_unless(stdin, ar.fin, in_fifo);
3446 rm_unless(stdout, ar.fout, out_fifo);
3447 exit(EXIT_SUCCESS);