From d28a3b8c51ad11ec7aae94cd8812fcaab33c5728 Mon Sep 17 00:00:00 2001 From: Nick Gasson Date: Wed, 29 Sep 2010 11:17:07 +0100 Subject: [PATCH] Word wrapping in text bubbles Use Pango word wrapping features to wrap text if bubble would be too long to fit on the screen. A new --no-wrap option disables this. --- ChangeLog | 16 ++++++++++++++++ src/bubblegen.c | 15 +++++++++++++-- src/display_cow.c | 47 ++++++++++++++++++++++++++++------------------- src/xcowsay.c | 7 +++++++ xcowsay.6 | 10 ++++++++++ 5 files changed, 74 insertions(+), 21 deletions(-) diff --git a/ChangeLog b/ChangeLog index 91f008a..8a0a24b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2010-09-29 Nick Gasson + + * src/display_cow.c (normal_setup, display_cow): Calculate + maximum size of bubble to support word wrapping. + * src/bubblegen.c (make_text_bubble): Use Pango to word-wrap + text that is too wide to fit in the bubble. + * src/xcowsay.c (main): Add --no-wrap option to disable word + wrapping. + * xcowsay.6: Document new word wrapping feature. + +2010-09-28 Nick Gasson + + * configure.ac: Place install prefix in config.h rather than on + the command line. This fixes a bug where code would not be + rebuilt if the prefix changed. + 2010-08-28 Nick Gasson * configure.ac: Fix bug where --disable-dbus would still define diff --git a/src/bubblegen.c b/src/bubblegen.c index fff1e5d..17696de 100644 --- a/src/bubblegen.c +++ b/src/bubblegen.c @@ -273,8 +273,8 @@ GdkPixbuf *make_dream_bubble(const char *file, int *p_width, int *p_height) return bubble_tidy(&bubble); } -GdkPixbuf *make_text_bubble(char *text, int *p_width, - int *p_height, cowmode_t mode) +GdkPixbuf *make_text_bubble(char *text, int *p_width, int *p_height, + int max_width, cowmode_t mode) { bubble_t bubble; int text_width, text_height; @@ -286,6 +286,17 @@ GdkPixbuf *make_text_bubble(char *text, int *p_width, pango_font_description_from_string(get_string_option("font")); PangoAttrList *pango_attrs = NULL; + // Adjust max width to account for bubble edges + max_width -= LEFT_BUF; + max_width -= TIP_WIDTH; + max_width -= 2 * BUBBLE_BORDER; + max_width -= CORNER_DIAM; + + if (get_bool_option("wrap")) { + pango_layout_set_width(layout, max_width * PANGO_SCALE); + pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR); + } + char *stripped; if (!pango_parse_markup(text, -1, 0, &pango_attrs, &stripped, NULL, NULL)) { diff --git a/src/display_cow.c b/src/display_cow.c index af3e155..2a26dc2 100644 --- a/src/display_cow.c +++ b/src/display_cow.c @@ -40,7 +40,8 @@ #include "settings.h" #include "i18n.h" -GdkPixbuf *make_text_bubble(char *text, int *p_width, int *p_height, cowmode_t mode); +GdkPixbuf *make_text_bubble(char *text, int *p_width, int *p_height, + int max_width, cowmode_t mode); GdkPixbuf *make_dream_bubble(const char *file, int *p_width, int *p_height); #define TICK_TIMEOUT 100 @@ -58,6 +59,7 @@ typedef struct { cowstate_t state; int transition_timeout; int display_time; + int screen_width, screen_height; } xcowsay_t; static xcowsay_t xcowsay; @@ -212,9 +214,13 @@ static void normal_setup(const char *text, bool debug, cowmode_t mode) xcowsay.display_time = max_display; debug_msg("Display time too long: clamped to %d\n", max_display); } + + const int cow_width = shape_width(xcowsay.cow); + const int max_width = xcowsay.screen_width - cow_width; - xcowsay.bubble_pixbuf = make_text_bubble(text_copy, &xcowsay.bubble_width, - &xcowsay.bubble_height, mode); + xcowsay.bubble_pixbuf = make_text_bubble( + text_copy, &xcowsay.bubble_width, &xcowsay.bubble_height, + max_width, mode); free(text_copy); } @@ -232,6 +238,23 @@ static void dream_setup(const char *file, bool debug) void display_cow(bool debug, const char *text, bool run_main, cowmode_t mode) { + GdkScreen *screen = gdk_screen_get_default(); + + gint n_monitors = gdk_screen_get_n_monitors(screen); + + gint pick = get_int_option("monitor"); + if (pick < 0 || pick >= n_monitors) + pick = rand() % n_monitors; + + GdkRectangle geom; + gdk_screen_get_monitor_geometry(screen, pick, &geom); + + xcowsay.screen_width = geom.width; + xcowsay.screen_height = geom.height; + + g_assert(xcowsay.cow_pixbuf); + xcowsay.cow = make_shape_from_pixbuf(xcowsay.cow_pixbuf); + switch (mode) { case COWMODE_NORMAL: case COWMODE_THINK: @@ -245,9 +268,6 @@ void display_cow(bool debug, const char *text, bool run_main, cowmode_t mode) exit(1); } - g_assert(xcowsay.cow_pixbuf); - xcowsay.cow = make_shape_from_pixbuf(xcowsay.cow_pixbuf); - int total_width = shape_width(xcowsay.cow) + get_int_option("bubble_x") + xcowsay.bubble_width; @@ -255,19 +275,8 @@ void display_cow(bool debug, const char *text, bool run_main, cowmode_t mode) int bubble_off = max((xcowsay.bubble_height - shape_height(xcowsay.cow))/2, 0); - GdkScreen *screen = gdk_screen_get_default(); - - gint n_monitors = gdk_screen_get_n_monitors(screen); - - gint pick = get_int_option("monitor"); - if (pick < 0 || pick >= n_monitors) - pick = rand() % n_monitors; - - GdkRectangle geom; - gdk_screen_get_monitor_geometry(screen, pick, &geom); - - int area_w = geom.width - total_width; - int area_h = geom.height - total_height; + int area_w = xcowsay.screen_width - total_width; + int area_h = xcowsay.screen_height - total_height; // Fit the cow on the screen as best as we can // The area can't be be zero or we'd get an FPE diff --git a/src/xcowsay.c b/src/xcowsay.c index 21292a6..99b9822 100644 --- a/src/xcowsay.c +++ b/src/xcowsay.c @@ -72,6 +72,7 @@ static struct option long_options[] = { {"monitor", required_argument, 0, 'm'}, {"bubble-at", required_argument, 0, 'b'}, {"at", required_argument, 0, 'a'}, + {"no-wrap", no_argument, 0, 'w'}, {"config", required_argument, 0, 'o'}, {"debug", no_argument, &debug, 1}, {0, 0, 0, 0} @@ -110,6 +111,7 @@ static void usage() " --monitor=N\t%s\n" " --at=X,Y\t\t%s\n" " --bubble-at=X,Y\t%s\n" + " --no-wrap\t\t%s\n" " --config=FILE\t%s\n" " --debug\t\t%s\n\n" "%s\n\n" @@ -131,6 +133,7 @@ static void usage() i18n("Display cow on monitor N."), i18n("Force the cow to appear at screen location (X,Y)."), i18n("Change relative position of bubble."), + i18n("Disable wrapping if text cannot fit on screen."), i18n("Specify alternative config file."), i18n("Keep daemon attached to terminal."), i18n("Default values for these options can be specified in the " @@ -240,6 +243,7 @@ int main(int argc, char **argv) add_int_option("bubble_x", DEF_BUBBLE_X); add_int_option("bubble_y", 0); add_string_option("alt_config_file", ""); + add_bool_option("wrap", true); parse_config_file(); @@ -298,6 +302,9 @@ int main(int argc, char **argv) set_string_option("alt_config_file", optarg); parse_config_file(); break; + case 'w': + set_bool_option("wrap", false); + break; case '?': // getopt_long already printed an error message failure = 1; diff --git a/xcowsay.6 b/xcowsay.6 index 193362a..0abc909 100644 --- a/xcowsay.6 +++ b/xcowsay.6 @@ -144,6 +144,16 @@ option to create your own characters. The config file options are and .IR bubble_y . .TP +.B "--no-wrap" +Disable word wrapping when the bubble is too wide to fit on the screen. +The config file option +.I wrap +can be set to +.I true +or +.I false +to set this explicitly. +.TP .B "--debug" Print messages about what .B xcowsay -- 2.39.2