diff --git a/Makefile.am b/Makefile.am index 9781ca84..df891ac0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -250,6 +250,8 @@ openbox_openbox_SOURCES = \ openbox/ping.h \ openbox/place.c \ openbox/place.h \ + openbox/prompt.c \ + openbox/prompt.h \ openbox/popup.c \ openbox/popup.h \ openbox/prop.c \ diff --git a/openbox/event.c b/openbox/event.c index 1b3c1c23..f41140eb 100644 --- a/openbox/event.c +++ b/openbox/event.c @@ -31,6 +31,7 @@ #include "frame.h" #include "grab.h" #include "menu.h" +#include "prompt.h" #include "menuframe.h" #include "keyboard.h" #include "modkeys.h" @@ -459,6 +460,7 @@ static void event_process(const XEvent *ec, gpointer data) ObWindow *obwin = NULL; XEvent ee, *e; ObEventData *ed = data; + ObPrompt *prompt = NULL; /* make a copy we can mangle */ ee = *ec; @@ -483,6 +485,9 @@ static void event_process(const XEvent *ec, gpointer data) case Window_Internal: /* we don't do anything with events directly on these windows */ break; + case Window_Prompt: + prompt = WINDOW_AS_PROMPT(obwin); + break; } } diff --git a/openbox/openbox.c b/openbox/openbox.c index 12106f6b..277294d0 100644 --- a/openbox/openbox.c +++ b/openbox/openbox.c @@ -46,6 +46,7 @@ #include "config.h" #include "ping.h" #include "mainloop.h" +#include "prompt.h" #include "gettext.h" #include "parser/parse.h" #include "render/render.h" @@ -314,6 +315,7 @@ gint main(gint argc, gchar **argv) grab_startup(reconfigure); group_startup(reconfigure); ping_startup(reconfigure); + prompt_startup(reconfigure); client_startup(reconfigure); dock_startup(reconfigure); moveresize_startup(reconfigure); @@ -373,6 +375,7 @@ gint main(gint argc, gchar **argv) moveresize_shutdown(reconfigure); dock_shutdown(reconfigure); client_shutdown(reconfigure); + prompt_shutdown(reconfigure); ping_shutdown(reconfigure); group_shutdown(reconfigure); grab_shutdown(reconfigure); diff --git a/openbox/prompt.c b/openbox/prompt.c new file mode 100644 index 00000000..38152411 --- /dev/null +++ b/openbox/prompt.c @@ -0,0 +1,286 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + prompt.c for the Openbox window manager + Copyright (c) 2008 Dana Jansens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#include "prompt.h" +#include "openbox.h" +#include "screen.h" +#include "openbox.h" +#include "gettext.h" + +static GList *prompt_list = NULL; + +/* we construct these */ +static RrAppearance *prompt_a_button; +static RrAppearance *prompt_a_hover; +static RrAppearance *prompt_a_press; + +#define msg_appearance(self) (ob_rr_theme->osd_hilite_label) + +void prompt_startup(gboolean reconfig) +{ + RrColor *c_button, *c_hover, *c_press; + + prompt_a_button = RrAppearanceCopy(ob_rr_theme->a_focused_unpressed_close); + prompt_a_hover = RrAppearanceCopy(ob_rr_theme->a_hover_focused_close); + prompt_a_press = RrAppearanceCopy(ob_rr_theme->a_focused_pressed_close); + + c_button = prompt_a_button->texture[0].data.mask.color; + c_hover = prompt_a_button->texture[0].data.mask.color; + c_press = prompt_a_button->texture[0].data.mask.color; + + RrAppearanceRemoveTextures(prompt_a_button); + RrAppearanceRemoveTextures(prompt_a_hover); + RrAppearanceRemoveTextures(prompt_a_press); + + RrAppearanceAddTextures(prompt_a_button, 1); + RrAppearanceAddTextures(prompt_a_hover, 1); + RrAppearanceAddTextures(prompt_a_press, 1); + + /* totally cheating here.. */ + prompt_a_button->texture[0] = ob_rr_theme->osd_hilite_label->texture[0]; + prompt_a_hover->texture[0] = ob_rr_theme->osd_hilite_label->texture[0]; + prompt_a_press->texture[0] = ob_rr_theme->osd_hilite_label->texture[0]; + + prompt_a_button->texture[0].data.text.color = c_button; + prompt_a_hover->texture[0].data.text.color = c_hover; + prompt_a_press->texture[0].data.text.color = c_press; +} + +void prompt_shutdown(gboolean reconfig) +{ + RrAppearanceFree(prompt_a_button); + RrAppearanceFree(prompt_a_hover); + RrAppearanceFree(prompt_a_press); +} + +ObPrompt* prompt_new(const gchar *msg, const gchar *const *answers) +{ + ObPrompt *self; + XSetWindowAttributes attrib; + guint i; + const gchar *const *c; + + attrib.override_redirect = TRUE; + + self = g_new0(ObPrompt, 1); + self->ref = 1; + self->super.type = Window_Prompt; + self->super.window = XCreateWindow(ob_display, + RootWindow(ob_display, ob_screen), + 0, 0, 1, 1, 0, + CopyFromParent, InputOutput, + CopyFromParent, + CWOverrideRedirect, &attrib); + g_hash_table_insert(window_map, &self->super.window, + PROMPT_AS_WINDOW(self)); + + self->a_bg = RrAppearanceCopy(ob_rr_theme->osd_hilite_bg); + + self->msg.text = g_strdup(msg); + self->msg.window = XCreateWindow(ob_display, self->super.window, + 0, 0, 1, 1, 0, + CopyFromParent, InputOutput, + CopyFromParent, 0, NULL); + XMapWindow(ob_display, self->msg.window); + + self->n_buttons = 0; + for (c = answers; *c != NULL; ++c) + ++self->n_buttons; + + if (!self->n_buttons) + self->n_buttons = 1; + + self->button = g_new(ObPromptElement, self->n_buttons); + + if (!answers) { + g_assert(self->n_buttons == 1); /* should be set to this above.. */ + self->button[0].text = g_strdup(_("OK")); + } + else { + g_assert(self->n_buttons > 0); + for (i = 0; i < self->n_buttons; ++i) + self->button[i].text = g_strdup(answers[i]); + } + + for (i = 0; i < self->n_buttons; ++i) { + self->button[i].window = XCreateWindow(ob_display, self->super.window, + 0, 0, 1, 1, 0, + CopyFromParent, InputOutput, + CopyFromParent, 0, NULL); + XMapWindow(ob_display, self->button[i].window); + g_hash_table_insert(window_map, &self->button[i].window, + PROMPT_AS_WINDOW(self)); + } + + return self; +} + +void prompt_ref(ObPrompt *self) +{ + ++self->ref; +} + +void prompt_unref(ObPrompt *self) +{ + if (self && --self->ref == 0) { + guint i; + + for (i = 0; i < self->n_buttons; ++i) { + g_hash_table_remove(window_map, &self->button[i].window); + XDestroyWindow(ob_display, self->button[i].window); + } + + XDestroyWindow(ob_display, self->msg.window); + + RrAppearanceFree(self->a_bg); + + g_hash_table_remove(window_map, &self->super.window); + XDestroyWindow(ob_display, self->super.window); + g_free(self); + } +} + +static void prompt_layout(ObPrompt *self, const Rect *area) +{ + RrAppearance *a_msg = msg_appearance(self); + gint l, r, t, b; + guint i; + gint allbuttonsw, allbuttonsh, buttonx; + gint w, h; + + const gint OUTSIDE_MARGIN = 4; + const gint MSG_BUTTON_SEPARATION = 4; + const gint BUTTON_SEPARATION = 4; + + RrMargins(self->a_bg, &l, &t, &r, &b); + l += OUTSIDE_MARGIN; + t += OUTSIDE_MARGIN; + r += OUTSIDE_MARGIN; + b += OUTSIDE_MARGIN; + + /* find the button sizes and how much space we need for them */ + allbuttonsw = allbuttonsh = 0; + for (i = 0; i < self->n_buttons; ++i) { + gint bw, bh; + + prompt_a_button->texture[0].data.text.string = self->button[i].text; + prompt_a_hover->texture[0].data.text.string = self->button[i].text; + prompt_a_press->texture[0].data.text.string = self->button[i].text; + RrMinSize(prompt_a_button, &bw, &bh); + self->button[i].width = bw; + self->button[i].height = bh; + RrMinSize(prompt_a_hover, &bw, &bh); + self->button[i].width = MAX(self->button[i].width, bw); + self->button[i].height = MAX(self->button[i].height, bh); + RrMinSize(prompt_a_press, &bw, &bh); + self->button[i].width = MAX(self->button[i].width, bw); + self->button[i].height = MAX(self->button[i].height, bh); + + allbuttonsw += self->button[i].width + (i > 0 ? BUTTON_SEPARATION : 0); + allbuttonsh = MAX(allbuttonsh, self->button[i].height); + } + + self->msg_wbound = MAX(allbuttonsw, area->width*3/5); + + /* measure the text message area */ + a_msg->texture[0].data.text.string = self->msg.text; + a_msg->texture[0].data.text.maxwidth = self->msg_wbound; + RrMinSize(a_msg, &self->msg.width, &self->msg.height); + a_msg->texture[0].data.text.maxwidth = 0; + + /* width and height inside the outer margins */ + w = MAX(self->msg.width, allbuttonsw); + h = self->msg.height + MSG_BUTTON_SEPARATION + allbuttonsh; + + /* position the text message */ + self->msg.x = l + (w - self->msg.width) / 2; + self->msg.y = t; + + /* position the button buttons */ + buttonx = l + (w - allbuttonsw) / 2; + for (i = 0; i < self->n_buttons; ++i) { + self->button[i].x = buttonx; + buttonx += self->button[i].width + BUTTON_SEPARATION; + self->button[i].y = h - allbuttonsh; + self->button[i].y += (allbuttonsh - self->button[i].height) / 2; + } + + /* size and position the toplevel window */ + self->width = w + l + r; + self->height = h + t + b; + self->x = (area->width - self->width) / 2; + self->y = (area->height - self->height) / 2; + + /* move and resize the actual windows */ + XMoveResizeWindow(ob_display, self->super.window, + self->x, self->y, self->width, self->height); + XMoveResizeWindow(ob_display, self->msg.window, + self->msg.x, self->msg.y, + self->msg.width, self->msg.height); + for (i = 0; i < self->n_buttons; ++i) + XMoveResizeWindow(ob_display, self->button[i].window, + self->button[i].x, self->button[i].y, + self->button[i].width, self->button[i].height); +} + +static void render_button(ObPrompt *self, ObPromptElement *e) +{ + prompt_a_button->surface.parent = self->a_bg; + prompt_a_button->surface.parentx = e->x; + prompt_a_button->surface.parentx = e->y; + + prompt_a_button->texture[0].data.text.string = e->text; + RrPaint(prompt_a_button, e->window, e->width, e->height); +} + +static void render_all(ObPrompt *self) +{ + guint i; + + RrPaint(self->a_bg, self->super.window, self->width, self->height); + + msg_appearance()->surface.parent = self->a_bg; + msg_appearance()->surface.parentx = self->msg.x; + msg_appearance()->surface.parentx = self->msg.y; + + msg_appearance()->texture[0].data.text.string = self->msg.text; + msg_appearance()->texture[0].data.text.maxwidth = self->msg_wbound; + RrPaint(msg_appearance(), self->msg.window, + self->msg.width, self->msg.height); + msg_appearance()->texture[0].data.text.maxwidth = 0; + + for (i = 0; i < self->n_buttons; ++i) + render_button(self, &self->button[i]); +} + +void prompt_show(ObPrompt *self, const Rect *area) +{ + if (self->mapped) return; + + prompt_layout(self, area); + render_all(self); + XMapWindow(ob_display, self->super.window); + + self->mapped = TRUE; +} + +void prompt_hide(ObPrompt *self) +{ + XUnmapWindow(ob_display, self->super.window); + self->mapped = FALSE; +} diff --git a/openbox/prompt.h b/openbox/prompt.h new file mode 100644 index 00000000..6b27b8d8 --- /dev/null +++ b/openbox/prompt.h @@ -0,0 +1,68 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + prompt.h for the Openbox window manager + Copyright (c) 2008 Dana Jansens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#ifndef ob__prompt_h +#define ob__prompt_h + +typedef struct _ObPrompt ObPrompt; +typedef struct _ObPromptElement ObPromptElement; + +#include "window.h" +#include "geom.h" +#include "render/render.h" +#include + +struct _ObPromptElement { + gchar *text; + Window window; + + gint x, y, width, height; +}; + +struct _ObPrompt +{ + InternalWindow super; + gint ref; + + /* keep a copy of this because we re-render things that may need it + (i.e. the buttons) */ + RrAppearance *a_bg; + + gboolean mapped; + gint x, y, width, height; + gint msg_wbound; + + ObPromptElement msg; + + /* one for each answer */ + ObPromptElement *button; + guint n_buttons; +}; + +void prompt_startup(gboolean reconfig); +void prompt_shutdown(gboolean reconfig); + +ObPrompt* prompt_new(const gchar *msg, const gchar *const *answers); +void prompt_ref(ObPrompt *self); +void prompt_unref(ObPrompt *self); + +/*! Show the prompt. It will be centered within the given area rectangle */ +void prompt_show(ObPrompt *self, const Rect *area); +void prompt_hide(ObPrompt *self); + +#endif diff --git a/openbox/window.c b/openbox/window.c index 3dd8f5ad..84afc4d8 100644 --- a/openbox/window.c +++ b/openbox/window.c @@ -22,6 +22,7 @@ #include "dock.h" #include "client.h" #include "frame.h" +#include "prompt.h" GHashTable *window_map; @@ -58,6 +59,8 @@ Window window_top(ObWindow *self) return ((ObClient*)self)->frame->window; case Window_Internal: return ((InternalWindow*)self)->window; + case Window_Prompt: + return ((ObPrompt*)self)->super.window; } g_assert_not_reached(); return None; @@ -77,6 +80,7 @@ ObStackingLayer window_layer(ObWindow *self) case Window_Client: return ((ObClient*)self)->layer; case Window_Internal: + case Window_Prompt: return OB_STACKING_LAYER_INTERNAL; } g_assert_not_reached(); diff --git a/openbox/window.h b/openbox/window.h index 7a905dea..76615c03 100644 --- a/openbox/window.h +++ b/openbox/window.h @@ -32,8 +32,9 @@ typedef enum { Window_Dock, Window_DockApp, /* used for events but not stacking */ Window_Client, - Window_Internal /* used for stacking but not events (except to filter + Window_Internal,/* used for stacking but not events (except to filter events on the root window) */ + Window_Prompt, } Window_InternalType; struct _ObWindow @@ -53,23 +54,27 @@ typedef struct InternalWindow { #define WINDOW_IS_DOCKAPP(win) (((ObWindow*)win)->type == Window_DockApp) #define WINDOW_IS_CLIENT(win) (((ObWindow*)win)->type == Window_Client) #define WINDOW_IS_INTERNAL(win) (((ObWindow*)win)->type == Window_Internal) +#define WINDOW_IS_PROMPT(win) (((ObWindow*)win)->type == Window_Prompt) struct _ObMenu; struct _ObDock; struct _ObDockApp; struct _ObClient; +struct _ObPrompt; #define WINDOW_AS_MENU(win) ((struct _ObMenuFrame*)win) #define WINDOW_AS_DOCK(win) ((struct _ObDock*)win) #define WINDOW_AS_DOCKAPP(win) ((struct _ObDockApp*)win) #define WINDOW_AS_CLIENT(win) ((struct _ObClient*)win) #define WINDOW_AS_INTERNAL(win) ((struct InternalWindow*)win) +#define WINDOW_AS_PROMPT(win) ((struct _ObPrompt*)win) #define MENU_AS_WINDOW(menu) ((ObWindow*)menu) #define DOCK_AS_WINDOW(dock) ((ObWindow*)dock) #define DOCKAPP_AS_WINDOW(dockapp) ((ObWindow*)dockapp) #define CLIENT_AS_WINDOW(client) ((ObWindow*)client) #define INTERNAL_AS_WINDOW(intern) ((ObWindow*)intern) +#define PROMPT_AS_WINDOW(prompt) ((ObWindow*)prompt) extern GHashTable *window_map; diff --git a/po/POTFILES.in b/po/POTFILES.in index f7918cec..294127c8 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -15,3 +15,4 @@ openbox/session.c openbox/startupnotify.c openbox/translate.c openbox/xerror.c +openbox/prompt.c diff --git a/render/font.c b/render/font.c index 369f262e..29d48215 100644 --- a/render/font.c +++ b/render/font.c @@ -109,6 +109,7 @@ RrFont *RrFontOpen(const RrInstance *inst, const gchar *name, gint size, /* setup the layout */ pango_layout_set_font_description(out->layout, out->font_desc); pango_layout_set_single_paragraph_mode(out->layout, TRUE); + pango_layout_set_wrap(out->layout, PANGO_WRAP_WORD_CHAR); /* get the ascent and descent */ measure_font(inst, out); @@ -139,12 +140,14 @@ void RrFontClose(RrFont *f) } static void font_measure_full(const RrFont *f, const gchar *str, - gint *x, gint *y, gint shadow_x, gint shadow_y) + gint *x, gint *y, gint shadow_x, gint shadow_y, + gint maxwidth) { PangoRectangle rect; pango_layout_set_text(f->layout, str, -1); - pango_layout_set_width(f->layout, -1); + pango_layout_set_width(f->layout, + (maxwidth <= 0 ? -1 : maxwidth * PANGO_SCALE)); /* pango_layout_get_pixel_extents lies! this is the right way to get the size of the text's area */ @@ -163,11 +166,12 @@ static void font_measure_full(const RrFont *f, const gchar *str, } RrSize *RrFontMeasureString(const RrFont *f, const gchar *str, - gint shadow_x, gint shadow_y) + gint shadow_x, gint shadow_y, gint maxwidth) { RrSize *size; size = g_new(RrSize, 1); - font_measure_full(f, str, &size->width, &size->height, shadow_x, shadow_y); + font_measure_full(f, str, &size->width, &size->height, shadow_x, shadow_y, + maxwidth); return size; } diff --git a/render/render.c b/render/render.c index 4119dc7f..2147df01 100644 --- a/render/render.c +++ b/render/render.c @@ -178,6 +178,14 @@ RrAppearance *RrAppearanceNew(const RrInstance *inst, gint numtex) return out; } +void RrAppearanceRemoveTextures(RrAppearance *a) +{ + gint i; + + g_free(a->texture); + a->textures = 0; +} + void RrAppearanceAddTextures(RrAppearance *a, gint numtex) { g_assert(a->textures == 0); @@ -378,7 +386,8 @@ gint RrMinWidth(RrAppearance *a) m = RrFontMeasureString(a->texture[i].data.text.font, a->texture[i].data.text.string, a->texture[i].data.text.shadow_offset_x, - a->texture[i].data.text.shadow_offset_y); + a->texture[i].data.text.shadow_offset_y, + a->texture[i].data.text.maxwidth); w = MAX(w, m->width); g_free(m); break; diff --git a/render/render.h b/render/render.h index 1f87c6e0..c812c316 100644 --- a/render/render.h +++ b/render/render.h @@ -141,6 +141,7 @@ struct _RrTextureText { gboolean shortcut; /*!< Underline a character */ guint shortcut_pos; /*!< Position in bytes of the character to underline */ RrEllipsizeMode ellipsize; + gint maxwidth; }; struct _RrPixmapMask { @@ -244,6 +245,7 @@ GC RrColorGC (RrColor *c); RrAppearance *RrAppearanceNew (const RrInstance *inst, gint numtex); RrAppearance *RrAppearanceCopy (RrAppearance *a); void RrAppearanceFree (RrAppearance *a); +void RrAppearanceRemoveTextures(RrAppearance *a); void RrAppearanceAddTextures(RrAppearance *a, gint numtex); RrFont *RrFontOpen (const RrInstance *inst, const gchar *name, @@ -251,7 +253,8 @@ RrFont *RrFontOpen (const RrInstance *inst, const gchar *name, RrFont *RrFontOpenDefault (const RrInstance *inst); void RrFontClose (RrFont *f); RrSize *RrFontMeasureString (const RrFont *f, const gchar *str, - gint shadow_offset_x, gint shadow_offset_y); + gint shadow_offset_x, gint shadow_offset_y, + gint maxwidth); gint RrFontHeight (const RrFont *f, gint shadow_offset_y); gint RrFontMaxCharWidth (const RrFont *f);