aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2025-12-20 09:37:17 +0100
committerMattias Andrée <m@maandree.se>2025-12-20 09:37:17 +0100
commitb23fac332ff4084e2925233791f057f30b7ed337 (patch)
treea2faa226da7a5b97b81bdfccb4edc195f93437d7
downloadgcmap-b23fac332ff4084e2925233791f057f30b7ed337.tar.gz
gcmap-b23fac332ff4084e2925233791f057f30b7ed337.tar.bz2
gcmap-b23fac332ff4084e2925233791f057f30b7ed337.tar.xz
First commit
Signed-off-by: Mattias Andrée <m@maandree.se>
-rw-r--r--.gitignore15
-rw-r--r--LICENSE15
-rw-r--r--Makefile37
-rw-r--r--README45
-rw-r--r--config.mk11
-rw-r--r--gcmap.167
-rw-r--r--gcmap.c700
7 files changed, 890 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..740df4a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*\#*
+*~
+*.o
+*.a
+*.lo
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
+/gcmap
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0e6be1c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2025 Mattias Andrée <m@maandree.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..599dfdf
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,37 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OBJ =\
+ gcmap.o
+
+HDR =
+
+all: gcmap
+$(OBJ): $(HDR)
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) $(CFLAGS_GTK2)
+
+gcmap: $(OBJ)
+ $(CC) -o $@ $(OBJ) $(LDFLAGS) $(LDFLAGS_GTK2)
+
+install: gcmap
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/bin"
+ mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/"
+ cp -- gcmap "$(DESTDIR)$(PREFIX)/bin/"
+ cp -- gcmap.1 "$(DESTDIR)$(MANPREFIX)/man1/"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/bin/gcmap"
+ -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/gcmap.1"
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda
+ -rm -f -- gcmap
+
+.SUFFIXES:
+.SUFFIXES: .o .c
+
+.PHONY: all install uninstall clean
diff --git a/README b/README
new file mode 100644
index 0000000..7634d8a
--- /dev/null
+++ b/README
@@ -0,0 +1,45 @@
+NAME
+ gcmap - graphical character selection utility
+
+SYNOPSIS
+ gcmap [-f font-family] [-s [font-size][/[[min]-[max]]]] [-B | -S] [-bi]
+
+DESCRIPTION
+ gcmap is a graphical utility for browsing and copying characters.
+
+OPTIONS
+ The gcmap utility conforms to the Base Definitions
+ volume of POSIX.1-2017, Section 12.2, Utility Syntax Guidelines.
+
+ The following options are supported:
+
+ -B
+ Use listing by Unicode block by default.
+
+ -b
+ Use bold font by default
+
+ -f font-family
+ Use font-family as the default font family.
+
+ -i
+ Use italic or oblique font by default.
+
+ -S
+ Use listing by script by default.
+
+ -s [font-size][/[[min]-[max]]]
+ Set the default font size, in the character
+ table, to font-size.
+
+ Set the minimum font size, in the character
+ table, to min.
+
+ Set the maximum font size, in the character
+ table, to max.
+
+OPERANDS
+ No operands are supported.
+
+SEE ALSO
+ None.
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..0729912
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,11 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = c99
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS =
+LDFLAGS = -lsimple -lcmap
+
+CFLAGS_GTK2 = $$(pkg-config --cflags gtk+-2.0)
+LDFLAGS_GTK2 = $$(pkg-config --libs gtk+-2.0)
diff --git a/gcmap.1 b/gcmap.1
new file mode 100644
index 0000000..864efb9
--- /dev/null
+++ b/gcmap.1
@@ -0,0 +1,67 @@
+.TH GCMAP 1 gcmap
+.SH NAME
+gcmap \- graphical character selection utility
+
+.SH SYNOPSIS
+.B gcmap
+[-f
+.IR font-family ]
+[-s
+.RI [ font-size ][\fB/\fP[[ min ]\fB\-\fP[ max ]]]]
+[-B | -S] [-bi]
+
+.SH DESCRIPTION
+.B gcmap
+is a graphical utility for browsing and copying characters.
+
+.SH OPTIONS
+The
+.B gcmap
+utility conforms to the Base Definitions
+volume of POSIX.1-2017,
+.IR "Section 12.2" ,
+.IR "Utility Syntax Guidelines" .
+.PP
+The following options are supported:
+.TP
+.B -B
+Use listing by Unicode block by default.
+.TP
+.B -b
+Use bold font by default
+.TP
+.BR -f \ \fIfont-family\fP
+Use
+.I font-family
+as the default font family.
+.TP
+.B -i
+Use italic or oblique font by default.
+.TP
+.B -S
+Use listing by script by default.
+.TP
+.RI \fB-s\fP\ [ font-size ][\fB/\fP[[ min ]\fB-\fP[ max ]]]
+Set the default font size, in the character
+table, to
+.IR font-size .
+
+Set the minimum font size, in the character
+table, to
+.IR min .
+
+Set the maximum font size, in the character
+table, to
+.IR max .
+
+.SH OPERANDS
+No operands are supported.
+
+.SH ENVIRONMENT VARIABLES
+The execution of
+.B gcmap
+is affected by environment variables that
+affects its tooltik.
+
+.SH SEE ALSO
+None.
diff --git a/gcmap.c b/gcmap.c
new file mode 100644
index 0000000..5c04c52
--- /dev/null
+++ b/gcmap.c
@@ -0,0 +1,700 @@
+/* See LICENSE file for copyright and license details. */
+#include <libsimple.h>
+#include <libsimple-arg.h>
+#include <libcmap.h>
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+# pragma GCC diagnostic ignored "-Wstrict-prototypes"
+#endif
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+USAGE("[-f font-family] [-s [font-size][/[[min]-[max]]]] [-B | -S] [-bi]");
+
+
+#define COMMA ,
+
+#define DEFAULT_LISTING_TYPE "script"
+#define LIST_LISTINGS(X, D)\
+ X(BY_SCRIPT, "By _script", DEFAULT_LISTING_TYPE, by_script_selected, populate_scripts, GDK_s) D\
+ X(BY_BLOCK, "By Unicode _block", "block", by_block_selected, populate_blocks, GDK_b)
+
+enum listing {
+#define X(ENUM, TITLE, TYPE, SELFUN, POPFUN, ACCEL) ENUM
+ LIST_LISTINGS(X, COMMA)
+#undef X
+};
+
+#define NLISTINGS_X(...) (size_t)1
+#define NLISTINGS (LIST_LISTINGS(NLISTINGS_X, +))
+
+static unsigned int min_font_size = 4;
+static unsigned int default_font_size = 22;
+static unsigned int max_font_size = 500;
+static unsigned int small_font_size_increment = 1;
+static unsigned int big_font_size_increment = 8;
+
+static GtkWidget *window;
+static GtkWidget *copytext;
+static GtkWidget *statusbar;
+static GtkWidget *listcombo;
+static GtkWidget *fontcombo;
+static GtkWidget *bold;
+static GtkWidget *italic;
+static GtkWidget *sizespinner;
+static GtkWidget *nextgroup;
+static GtkWidget *prevgroup;
+static GtkWidget *bylisting[NLISTINGS];
+static GtkListStore *fontcombo_store;
+static GtkAdjustment *fontsize_adjustment;
+static GtkAccelGroup *accelgroup;
+
+
+static void
+populate_font_families(GtkWidget *widget, GtkListStore *store)
+{
+ const char **names;
+ PangoFontFamily **families;
+ int nfamilies_int;
+ size_t i, nfamilies;
+ GtkTreeIter iter;
+
+ pango_context_list_families(gtk_widget_get_pango_context(GTK_WIDGET(widget)), &families, &nfamilies_int);
+ nfamilies = (size_t)nfamilies_int;
+ names = calloc(nfamilies, sizeof(*names));
+
+ for (i = 0; i < nfamilies; i++)
+ names[i] = pango_font_family_get_name(families[i]);
+ libsimple_qsort_str(names, nfamilies);
+ for (i = 0; i < nfamilies; i++) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, names[i], -1);
+ }
+
+ free(names);
+ g_free(families);
+}
+
+
+static void
+populate_scripts(GtkListStore *store)
+{
+ const struct libcmap_script *list = libcmap_script_list;
+ size_t i, n = libcmap_script_list_size;
+ GtkTreeIter iter;
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, "All", -1);
+ for (i = 0; i < n; i++) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, list[i].name, -1);
+ /* TODO tooltip should be the name, followed by the ranges in brackets */
+ }
+}
+
+
+static void
+populate_blocks(GtkListStore *store)
+{
+ const struct libcmap_block *list = libcmap_block_list;
+ size_t i, n = libcmap_block_list_size;
+ GtkTreeIter iter;
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, "All", -1);
+ for (i = 0; i < n; i++) {
+ gtk_list_store_append(store, &iter);
+ gtk_list_store_set(store, &iter, 0, list[i].name, -1);
+ /* TODO tooltip should be the name, followed by the range in brackets */
+ }
+}
+
+
+static void
+copytext_changed(GtkEditable *editable, GtkWidget *button)
+{
+ const char *text = gtk_entry_get_text(GTK_ENTRY(editable));
+ gtk_widget_set_sensitive(button, !!text[0]);
+}
+
+
+static void
+copybutton_clicked(GtkWidget *button, GtkEditable *editable)
+{
+ const char *text = gtk_entry_get_text(GTK_ENTRY(editable));
+ (void) button;
+ gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), text, -1);
+ gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), text, -1);
+ gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+}
+
+
+static void
+fontcombo_changed(GtkComboBox *combo, void *user_data) /* TODO */
+{
+ GtkTreeIter iter;
+ char *family;
+ (void) user_data;
+ if (!gtk_combo_box_get_active_iter(combo, &iter))
+ return;
+ gtk_tree_model_get(GTK_TREE_MODEL(fontcombo_store), &iter,0, &family, -1);
+ if (!family)
+ return;
+}
+
+
+static void
+bold_toggled(GtkToggleButton *button, void *user_data) /* TODO */
+{
+ int selected = gtk_toggle_button_get_active(button);
+ (void) selected;
+ (void) user_data;
+}
+
+
+static void
+italic_toggled(GtkToggleButton *button, void *user_data) /* TODO */
+{
+ int selected = gtk_toggle_button_get_active(button);
+ (void) selected;
+ (void) user_data;
+}
+
+
+static void
+fontsize_changed(GtkAdjustment *adjustment, void *user_data)
+{
+ double size = gtk_adjustment_get_value(adjustment);
+ (void) user_data;
+ (void) size; /* TODO apply font size */
+}
+
+
+static void
+listcombo_changed(GtkComboBox *combo, GtkListStore *store)
+{
+ static int recursion_guard = 0;
+ GtkWidget *label;
+
+ if (recursion_guard)
+ return;
+ recursion_guard++;
+
+ switch (gtk_combo_box_get_active(combo)) {
+#define X(ENUM, TITLE, TYPE, SELFUN, POPFUN, ACCEL)\
+ case ENUM:\
+ if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(bylisting[ENUM])))\
+ gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(bylisting[ENUM]), TRUE);\
+ label = gtk_bin_get_child(GTK_BIN(nextgroup));\
+ gtk_label_set_text_with_mnemonic(GTK_LABEL(label), "N_ext "TYPE);\
+ label = gtk_bin_get_child(GTK_BIN(prevgroup));\
+ gtk_label_set_text_with_mnemonic(GTK_LABEL(label), "P_revious "TYPE);\
+ gtk_list_store_clear(store);\
+ POPFUN(store);\
+ break
+ LIST_LISTINGS(X, ;);
+#undef X
+ }
+
+ /* TODO select appropriate list item to keep select character */
+
+ recursion_guard--;
+}
+
+
+#define X(ENUM, TITLE, TYPE, SELFUN, POPFUN, ACCEL)\
+ static void\
+ SELFUN(GtkWidget *menu_item, void *user_data)\
+ {\
+ (void) menu_item;\
+ (void) user_data;\
+ if (gtk_combo_box_get_active(GTK_COMBO_BOX(listcombo)) != ENUM)\
+ gtk_combo_box_set_active(GTK_COMBO_BOX(listcombo), ENUM);\
+ }
+LIST_LISTINGS(X,)
+#undef X
+
+
+static GtkWidget *
+add_menu(GtkWidget *parent, const char *label)
+{
+ GtkWidget *menu = gtk_menu_new();
+ GtkWidget *item = gtk_menu_item_new_with_mnemonic(label);
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), menu);
+ gtk_menu_shell_append(GTK_MENU_SHELL(parent), item);
+ return menu;
+}
+
+
+static GtkWidget *
+add_menu_item(GtkWidget *parent, const char *label, const char *icon, unsigned int key,
+ GdkModifierType mods, void (*action)(GtkWidget *menu_item, void *user_data))
+{
+ GtkWidget *item, *image;
+ if (icon) {
+ item = gtk_image_menu_item_new_with_mnemonic(label);
+ image = gtk_image_new_from_icon_name(icon, GTK_ICON_SIZE_MENU);
+ gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), image);
+ gtk_image_menu_item_set_always_show_image(GTK_IMAGE_MENU_ITEM(item), TRUE);
+ } else {
+ item = gtk_menu_item_new_with_mnemonic(label);
+ }
+ gtk_menu_shell_append(GTK_MENU_SHELL(parent), item);
+ gtk_widget_add_accelerator(item, "activate", accelgroup, key, mods, GTK_ACCEL_VISIBLE);
+ g_signal_connect(item, "activate", G_CALLBACK(action), NULL);
+ return item;
+}
+
+
+static GtkWidget *
+add_radio_menu_item(GtkWidget *parent, const char *label, GSList **groupp, unsigned int key,
+ GdkModifierType mods, void (*action)(GtkWidget *menu_item, void *user_data))
+{
+ GtkWidget *item;
+ item = gtk_radio_menu_item_new_with_mnemonic(*groupp, label);
+ if (!*groupp)
+ *groupp = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item));
+ gtk_menu_shell_append(GTK_MENU_SHELL(parent), item);
+ gtk_widget_add_accelerator(item, "activate", accelgroup, key, mods, GTK_ACCEL_VISIBLE);
+ g_signal_connect(item, "activate", G_CALLBACK(action), NULL);
+ return item;
+}
+
+
+static void
+close_window(GtkWidget *menu_item, void *user_data)
+{
+ (void) menu_item;
+ (void) user_data;
+ gtk_main_quit();
+}
+
+
+static void
+zoom_in(GtkWidget *menu_item, void *user_data)
+{
+ double size = gtk_adjustment_get_value(fontsize_adjustment);
+ (void) menu_item;
+ (void) user_data;
+ size += 1;
+ if (size > max_font_size)
+ size = max_font_size;
+ gtk_adjustment_set_value(fontsize_adjustment, size);
+}
+
+
+static void
+zoom_out(GtkWidget *menu_item, void *user_data)
+{
+ double size = gtk_adjustment_get_value(fontsize_adjustment);
+ (void) menu_item;
+ (void) user_data;
+ size -= 1;
+ if (size < min_font_size)
+ size = min_font_size;
+ gtk_adjustment_set_value(fontsize_adjustment, size);
+}
+
+
+static void
+zoom_original(GtkWidget *menu_item, void *user_data)
+{
+ (void) menu_item;
+ (void) user_data;
+ gtk_adjustment_set_value(fontsize_adjustment, default_font_size);
+}
+
+
+static int
+zoom_original_accel(GtkAccelGroup *accel_group, GObject *acceleratable, unsigned int keyval,
+ GdkModifierType modifier, void *user_data)
+{
+ (void) accel_group;
+ (void) acceleratable;
+ (void) keyval;
+ (void) modifier;
+ zoom_original(NULL, user_data);
+ return TRUE;
+}
+
+static void
+go_next(GtkWidget *menu_item, void *user_data) /* TODO */
+{
+ (void) menu_item;
+ (void) user_data;
+}
+
+
+static void
+go_prev(GtkWidget *menu_item, void *user_data) /* TODO */
+{
+ (void) menu_item;
+ (void) user_data;
+}
+
+
+static void
+go_down(GtkWidget *menu_item, void *user_data) /* TODO */
+{
+ (void) menu_item;
+ (void) user_data;
+}
+
+
+static void
+go_up(GtkWidget *menu_item, void *user_data) /* TODO */
+{
+ (void) menu_item;
+ (void) user_data;
+}
+
+
+static int
+go_down_accel(GtkAccelGroup *accel_group, GObject *acceleratable, unsigned int keyval,
+ GdkModifierType modifier, void *user_data)
+{
+ (void) accel_group;
+ (void) acceleratable;
+ (void) keyval;
+ (void) modifier;
+ go_down(nextgroup, user_data);
+ return TRUE;
+}
+
+
+static int
+go_up_accel(GtkAccelGroup *accel_group, GObject *acceleratable, unsigned int keyval,
+ GdkModifierType modifier, void *user_data)
+{
+ (void) accel_group;
+ (void) acceleratable;
+ (void) keyval;
+ (void) modifier;
+ go_down(prevgroup, user_data);
+ return TRUE;
+}
+
+
+static void
+create_window(void)
+{
+ GtkWidget *vbox, *hbox, *notebook, *left, *scrolled;
+ GtkWidget *label, *copybutton, *align, *menubar, *menu;
+ GtkListStore *store;
+ GtkWidget *treeview;
+ GtkCellRenderer *renderer;
+ GtkTreeViewColumn *column;
+ GtkObject *obj;
+ AtkObject *accessible;
+ GSList *group;
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ g_signal_connect(window, "destroy", G_CALLBACK(&gtk_main_quit), NULL);
+ gtk_window_set_title(GTK_WINDOW(window), "gcmap");
+
+ accelgroup = gtk_accel_group_new();
+ gtk_window_add_accel_group(GTK_WINDOW(window), accelgroup);
+
+ vbox = gtk_vbox_new(FALSE, 0);
+ gtk_container_add(GTK_CONTAINER(window), vbox);
+
+ menubar = gtk_menu_bar_new();
+ gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
+
+ menu = add_menu(menubar, "_File");
+ add_menu_item(menu, "_Close", "window-close", GDK_w, GDK_CONTROL_MASK, &close_window);
+
+ menu = add_menu(menubar, "_View");
+ group = NULL;
+#define X(ENUM, TITLE, TYPE, SELFUN, POPFUN, ACCEL)\
+ bylisting[ENUM] = add_radio_menu_item(menu, TITLE, &group, ACCEL, GDK_CONTROL_MASK, &SELFUN)
+ LIST_LISTINGS(X, ;);
+#undef X
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
+ add_menu_item(menu, "_Larger glyphs", "zoom-in", GDK_plus, GDK_CONTROL_MASK, &zoom_in);
+ add_menu_item(menu, "S_maller glyphs", "zoom-out", GDK_minus, GDK_CONTROL_MASK, &zoom_out);
+ add_menu_item(menu, "_Normal size", "zoom-original", GDK_equal, GDK_CONTROL_MASK, &zoom_original);
+ gtk_accel_group_connect(accelgroup, GDK_0, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
+ g_cclosure_new(G_CALLBACK(&zoom_original_accel), NULL, NULL));
+ gtk_accel_group_connect(accelgroup, GDK_1, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
+ g_cclosure_new(G_CALLBACK(&zoom_original_accel), NULL, NULL));
+
+ menu = add_menu(menubar, "_Go");
+ add_menu_item(menu, "_Next character", "go-next", GDK_n, GDK_CONTROL_MASK, &go_next);
+ add_menu_item(menu, "_Previous character", "go-previous", GDK_p, GDK_CONTROL_MASK, &go_prev);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
+ nextgroup = add_menu_item(menu, "N_ext "DEFAULT_LISTING_TYPE, "go-down", GDK_n, GDK_CONTROL_MASK | GDK_SHIFT_MASK, &go_down);
+ prevgroup = add_menu_item(menu, "P_revious "DEFAULT_LISTING_TYPE, "go-up", GDK_p, GDK_CONTROL_MASK | GDK_SHIFT_MASK, &go_up);
+ gtk_accel_group_connect(accelgroup, GDK_Down, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
+ g_cclosure_new(G_CALLBACK(&go_down_accel), NULL, NULL));
+ gtk_accel_group_connect(accelgroup, GDK_Up, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE,
+ g_cclosure_new(G_CALLBACK(&go_up_accel), NULL, NULL));
+
+ hbox = gtk_hbox_new(FALSE, 4);
+ gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+ accessible = gtk_widget_get_accessible(GTK_WIDGET(hbox));
+ atk_object_set_name(accessible, "Font");
+
+ fontcombo = gtk_combo_box_new();
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(fontcombo), renderer, TRUE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(fontcombo), renderer, "text", 0, NULL);
+ fontcombo_store = gtk_list_store_new(1, G_TYPE_STRING);
+ gtk_combo_box_set_model(GTK_COMBO_BOX(fontcombo), GTK_TREE_MODEL(fontcombo_store));
+ populate_font_families(fontcombo, fontcombo_store);
+ gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(fontcombo), FALSE);
+ gtk_box_pack_start(GTK_BOX(hbox), fontcombo, FALSE, FALSE, 0);
+ gtk_combo_box_set_active(GTK_COMBO_BOX(fontcombo), -1);
+ g_signal_connect(fontcombo, "changed", G_CALLBACK(fontcombo_changed), NULL);
+ accessible = gtk_widget_get_accessible(fontcombo);
+ atk_object_set_name(accessible, "Font family");
+
+ bold = gtk_toggle_button_new_with_mnemonic(GTK_STOCK_BOLD);
+ gtk_button_set_use_stock(GTK_BUTTON(bold), TRUE);
+ gtk_button_set_focus_on_click(GTK_BUTTON(bold), FALSE);
+ g_signal_connect(bold, "toggled", G_CALLBACK(&bold_toggled), NULL);
+ gtk_box_pack_start(GTK_BOX(hbox), bold, FALSE, FALSE, 0);
+
+ italic = gtk_toggle_button_new_with_mnemonic(GTK_STOCK_ITALIC);
+ gtk_button_set_use_stock(GTK_BUTTON(italic), TRUE);
+ gtk_button_set_focus_on_click(GTK_BUTTON(italic), FALSE);
+ g_signal_connect(italic, "toggled", G_CALLBACK(&italic_toggled), NULL);
+ gtk_box_pack_start(GTK_BOX(hbox), italic, FALSE, FALSE, 0);
+
+ obj = gtk_adjustment_new(default_font_size, min_font_size, max_font_size,
+ small_font_size_increment, big_font_size_increment, 0);
+ fontsize_adjustment = GTK_ADJUSTMENT(obj);
+ sizespinner = gtk_spin_button_new(fontsize_adjustment, 0, 0);
+ g_signal_connect(fontsize_adjustment, "value-changed", G_CALLBACK(fontsize_changed), NULL);
+ gtk_box_pack_start(GTK_BOX(hbox), sizespinner, FALSE, FALSE, 0);
+ accessible = gtk_widget_get_accessible(sizespinner);
+ atk_object_set_name(accessible, "Font size");
+
+ left = gtk_vbox_new(FALSE, 0);
+
+ listcombo = gtk_combo_box_new_text();
+ gtk_combo_box_append_text(GTK_COMBO_BOX(listcombo), "Scripts");
+ gtk_combo_box_append_text(GTK_COMBO_BOX(listcombo), "Unicode blocks");
+ gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(listcombo), FALSE);
+ gtk_box_pack_start(GTK_BOX(left), listcombo, FALSE, FALSE, 0);
+
+ scrolled = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_ETCHED_IN);
+ store = gtk_list_store_new(1, G_TYPE_STRING);
+ populate_scripts(store);
+ treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
+ renderer = gtk_cell_renderer_text_new();
+ column = gtk_tree_view_column_new_with_attributes(NULL, renderer, "text", 0, NULL);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
+ gtk_container_add(GTK_CONTAINER(scrolled), treeview);
+ gtk_box_pack_start(GTK_BOX(left), scrolled, TRUE, TRUE, 0);
+
+ g_signal_connect(G_OBJECT(listcombo), "changed", G_CALLBACK(&listcombo_changed), store);
+
+ notebook = gtk_notebook_new();
+
+ /* TODO character map tab pane (should have focus: gtk_widget_grab_focus(GTK_WIDGET(...)) */
+ scrolled = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_NONE);
+ gtk_container_add(GTK_CONTAINER(scrolled), gtk_label_new("TODO"));
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled, gtk_label_new_with_mnemonic("Character _table"));
+
+ scrolled = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled), GTK_SHADOW_NONE);
+ gtk_container_add(GTK_CONTAINER(scrolled), gtk_label_new("TODO"));
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled, gtk_label_new_with_mnemonic("Character _details"));
+
+ hbox = gtk_hpaned_new();
+ gtk_paned_pack1(GTK_PANED(hbox), left, FALSE, TRUE);
+ gtk_paned_pack2(GTK_PANED(hbox), notebook, TRUE, TRUE);
+ align = gtk_alignment_new(0.0f, 0.0f, 1.0f, 1.0f);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 8, 8);
+ gtk_container_add(GTK_CONTAINER(align), hbox);
+ gtk_box_pack_start(GTK_BOX(vbox), align, TRUE, TRUE, 0);
+
+ hbox = gtk_hbox_new(FALSE, 8);
+ gtk_container_set_border_width(GTK_CONTAINER(hbox), 8);
+ gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+
+ label = gtk_label_new_with_mnemonic("Te_xt to copy:");
+ gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
+
+ copytext = gtk_entry_new();
+ gtk_box_pack_start(GTK_BOX(hbox), copytext, TRUE, TRUE, 0);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(label), copytext);
+
+ copybutton = gtk_button_new_from_stock(GTK_STOCK_COPY);
+ gtk_widget_set_tooltip_text(copybutton, "Copy to the clipboard");
+ gtk_widget_set_sensitive(copybutton, FALSE);
+ gtk_box_pack_start(GTK_BOX(hbox), copybutton, FALSE, FALSE, 0);
+
+ g_signal_connect(G_OBJECT(copybutton), "clicked", G_CALLBACK(&copybutton_clicked), copytext);
+ g_signal_connect(G_OBJECT(copytext), "changed", G_CALLBACK(&copytext_changed), copybutton);
+
+ statusbar = gtk_statusbar_new();
+ gtk_statusbar_set_has_resize_grip(GTK_STATUSBAR(statusbar), TRUE);
+ gtk_box_pack_start(GTK_BOX(vbox), statusbar, FALSE, FALSE, 0);
+
+ gtk_widget_show_all(vbox);
+}
+
+
+static const char *
+maybe_sat_parse_int_as_uint(const char *s, unsigned int *out, int *setp)
+{
+ unsigned int digit;
+ if (!isdigit(*s))
+ return s;
+ *out = 0;
+ *setp = 1;
+ for (; isdigit(*s); s++) {
+ digit = (unsigned int)(*s & 15);
+ if (*out > ((unsigned int)INT_MAX - digit) / 10U)
+ *out = (unsigned int)INT_MAX;
+ else
+ *out = *out * 10U + digit;
+ }
+ return s;
+}
+
+
+static void
+parse_fontsize(const char *s, int *default_font_size_setp, int *min_font_size_setp, int *max_font_size_setp)
+{
+ s = maybe_sat_parse_int_as_uint(s, &default_font_size, default_font_size_setp);
+
+ if (!*s)
+ return;
+ if (*s++ != '/')
+ usage();
+ if (!*s)
+ return;
+
+ s = maybe_sat_parse_int_as_uint(s, &min_font_size, min_font_size_setp);
+
+ if (*s++ != '-')
+ usage();
+ if (!*s)
+ return;
+
+ s = maybe_sat_parse_int_as_uint(s, &max_font_size, max_font_size_setp);
+
+ if (*s)
+ usage();
+
+ if (!default_font_size)
+ eprintf("default font size cannot be zero");
+ if (!min_font_size)
+ eprintf("minimum font size cannot be zero");
+ if (!max_font_size)
+ eprintf("maximum font size cannot be zero");
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ GtkIconTheme *theme;
+ const char *icon;
+ int xargc = 1;
+ char *xargva[] = {argv[0], NULL};
+ char **xargv = xargva;
+ enum listing listing = (enum listing)0;
+ int use_bold = 0;
+ int use_italic = 0;
+ int default_font_size_set = 0;
+ int min_font_size_set = 0;
+ int max_font_size_set = 0;
+ const char *font_family = "sans";
+
+ ARGBEGIN {
+ case 'B':
+ listing = 1;
+ break;
+ case 'S':
+ listing = 0;
+ break;
+ case 'b':
+ use_bold = 1;
+ break;
+ case 'i':
+ use_italic = 1;
+ break;
+ case 'f':
+ font_family = ARG();
+ break;
+ case 's':
+ parse_fontsize(ARG(), &default_font_size_set, &min_font_size_set, &max_font_size_set);
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if (argc)
+ usage();
+
+ if (min_font_size_set && max_font_size_set) {
+ if (min_font_size > max_font_size)
+ eprintf("minimum font size cannot be greater than maximum font size");
+ } else if (min_font_size_set) {
+ if (max_font_size < min_font_size)
+ max_font_size = min_font_size;
+ } else if (max_font_size_set) {
+ if (min_font_size > max_font_size)
+ min_font_size = max_font_size;
+ }
+ if (default_font_size_set) {
+ if (default_font_size < min_font_size) {
+ if (min_font_size_set)
+ eprintf("default font size cannot be less than the minimum font size");
+ min_font_size = default_font_size;
+ }
+ if (default_font_size > max_font_size) {
+ if (max_font_size_set)
+ eprintf("default font size cannot be greater than the minimum font size");
+ max_font_size = default_font_size;
+ }
+ } else {
+ if (default_font_size < min_font_size)
+ default_font_size = min_font_size;
+ else if (default_font_size > max_font_size)
+ default_font_size = max_font_size;
+ }
+
+ g_set_prgname(argv0);
+ g_set_application_name("gcmap");
+
+ gtk_init(&xargc, &xargv);
+ theme = gtk_icon_theme_get_default();
+ if (!gtk_icon_theme_has_icon(theme, icon = "apps/gcmap") ||
+ !gtk_icon_theme_has_icon(theme, icon = "gcmap") ||
+ !gtk_icon_theme_has_icon(theme, icon = "apps/accessories-character-map") ||
+ !gtk_icon_theme_has_icon(theme, icon = "accessories-character-map"))
+ icon = "gcmap";
+ gtk_window_set_default_icon_name(icon);
+
+ create_window();
+ gtk_window_set_icon_name(GTK_WINDOW(window), icon);
+
+ if (gtk_combo_box_get_active(GTK_COMBO_BOX(listcombo)) != (int)listing)
+ gtk_combo_box_set_active(GTK_COMBO_BOX(listcombo), (int)listing);
+ if (gtk_combo_box_get_active(GTK_COMBO_BOX(listcombo)) != (int)listing)
+ gtk_combo_box_set_active(GTK_COMBO_BOX(listcombo), (int)listing);
+ if (use_bold)
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(bold), TRUE);
+ if (use_italic)
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(italic), TRUE);
+
+ /* TODO select font family */
+ /* TODO select character (U+0000) and group (locate the on containing it, go to All if not found) */
+
+ gtk_window_present(GTK_WINDOW(window));
+ gtk_main();
+}