diff -Nur notification-daemon-0.3.4/configure.ac notification-daemon-0.3.4.new/configure.ac --- notification-daemon-0.3.4/configure.ac 2006-02-05 06:18:51.000000000 +0200 +++ notification-daemon-0.3.4.new/configure.ac 2006-05-12 20:38:18.000000000 +0300 @@ -175,6 +175,7 @@ themes/Makefile themes/bubble/Makefile themes/standard/Makefile +themes/ubuntu/Makefile ]) AC_OUTPUT diff -Nur notification-daemon-0.3.4/themes/Makefile.am notification-daemon-0.3.4.new/themes/Makefile.am --- notification-daemon-0.3.4/themes/Makefile.am 2006-01-12 08:52:04.000000000 +0200 +++ notification-daemon-0.3.4.new/themes/Makefile.am 2006-05-12 20:38:18.000000000 +0300 @@ -1 +1 @@ -SUBDIRS = standard bubble +SUBDIRS = standard bubble ubuntu diff -Nur notification-daemon-0.3.4/themes/ubuntu/Makefile.am notification-daemon-0.3.4.new/themes/ubuntu/Makefile.am --- notification-daemon-0.3.4/themes/ubuntu/Makefile.am 1970-01-01 02:00:00.000000000 +0200 +++ notification-daemon-0.3.4.new/themes/ubuntu/Makefile.am 2006-05-12 20:38:18.000000000 +0300 @@ -0,0 +1,9 @@ +enginedir = $(libdir)/notification-daemon-1.0/engines + +engine_LTLIBRARIES = libubuntu.la + +libubuntu_la_SOURCES = theme.c +libubuntu_la_LDFLAGS = -module -avoid-version +libubuntu_la_LIBADD = $(NOTIFICATION_DAEMON_LIBS) + +INCLUDES = $(NOTIFICATION_DAEMON_CFLAGS) diff -Nur notification-daemon-0.3.4/themes/ubuntu/theme.c notification-daemon-0.3.4.new/themes/ubuntu/theme.c --- notification-daemon-0.3.4/themes/ubuntu/theme.c 1970-01-01 02:00:00.000000000 +0200 +++ notification-daemon-0.3.4.new/themes/ubuntu/theme.c 2006-05-12 20:38:34.000000000 +0300 @@ -0,0 +1,866 @@ +#include +#include + +typedef void (*ActionInvokedCb)(GtkWindow *nw, const char *key); +typedef void (*UrlClickedCb)(GtkWindow *nw, const char *url); + +#define M_PI 3.14159265358979323846 + +typedef struct +{ + GtkWidget *win; + GtkWidget *top_spacer; + GtkWidget *bottom_spacer; + GtkWidget *main_hbox; + GtkWidget *iconbox; + GtkWidget *icon; + GtkWidget *content_hbox; + GtkWidget *summary_label; + GtkWidget *body_label; + GtkWidget *actions_box; + GtkWidget *last_sep; + GtkWidget *stripe_spacer; + GtkWidget *pie_countdown; + GtkWidget *close_button; + + gboolean has_arrow; + + int point_x; + int point_y; + + int drawn_arrow_begin_x; + int drawn_arrow_begin_y; + int drawn_arrow_middle_x; + int drawn_arrow_middle_y; + int drawn_arrow_end_x; + int drawn_arrow_end_y; + GtkArrowType arrow_type; + + GdkGC *gc; + GdkPoint *border_points; + size_t num_border_points; + GdkRegion *window_region; + + guchar urgency; + glong timeout; + glong remaining; + + UrlClickedCb url_clicked; + +} WindowData; + +enum +{ + URGENCY_LOW, + URGENCY_NORMAL, + URGENCY_CRITICAL +}; + +#define WIDTH 300 +#define IMAGE_SIZE 32 +#define IMAGE_PADDING 10 +#define STRIPE_WIDTH 30 +#define PIE_WIDTH 24 +#define PIE_HEIGHT 24 +#define BODY_X_OFFSET (IMAGE_SIZE + 8) +#define DEFAULT_ARROW_OFFSET (STRIPE_WIDTH + 2) +#define DEFAULT_ARROW_HEIGHT 12 +#define DEFAULT_ARROW_WIDTH 8 +#define ARROW_DISTANCE 6 + +static void +draw_stripe(GtkWidget *win, cairo_t *cr, WindowData *windata) +{ + GdkGC *gc; + GdkGCValues *values; + GtkStyle *style = gtk_widget_get_style(windata->win); + GdkColor color; + GdkColormap *colormap; + + switch (windata->urgency) + { + case URGENCY_LOW: + cairo_set_source_rgb(cr, + 242.0/255.0, + 242.0/255.0, + 190.0/255.0); + break; + case URGENCY_NORMAL: + cairo_set_source_rgb(cr, + 220.0/255.0, + 220.0/255.0, + 160.0/255.0); + break; + case URGENCY_CRITICAL: + + cairo_set_source_rgb(cr, + style->bg[GTK_STATE_SELECTED].red/65535.0, + style->bg[GTK_STATE_SELECTED].green/65535.0, + style->bg[GTK_STATE_SELECTED].blue/65535.0); + + break; + } + cairo_rectangle(cr, + windata->main_hbox->allocation.x, + windata->main_hbox->allocation.y, + STRIPE_WIDTH, + windata->main_hbox->allocation.height); + cairo_fill(cr); + return; +} + +void draw_rounded_window(cairo_t *mask_cr, float x, float y, + float w, float h, WindowData *windata) +{ + if(windata->has_arrow) + { + // fix the border + if(windata->arrow_type == GTK_ARROW_UP) { + y += DEFAULT_ARROW_HEIGHT; + h -= DEFAULT_ARROW_HEIGHT; + } else { + //y -= DEFAULT_ARROW_HEIGHT; + h -= (DEFAULT_ARROW_HEIGHT+ARROW_DISTANCE); + } + } + + int radius = 6; + int off_x = 15; + int bx, by, mx, my, ex, ey; + + bx = windata->drawn_arrow_begin_x; + by = windata->drawn_arrow_begin_y; + mx = windata->drawn_arrow_middle_x; + my = windata->drawn_arrow_middle_y; + ex = windata->drawn_arrow_end_x; + ey = windata->drawn_arrow_end_y; + + cairo_move_to(mask_cr, x+radius, y); + + // the arrow is on top, so we need to draw it here + if(windata->has_arrow && windata->arrow_type == GTK_ARROW_UP) { + if (mx < w/2 ) { + cairo_line_to(mask_cr, bx-10, by); + cairo_curve_to(mask_cr, mx-3, by, mx-3, by, mx, my+ARROW_DISTANCE); + cairo_curve_to(mask_cr, mx+8, ey, mx+8, ey, ex+16, ey); + } else { + cairo_line_to(mask_cr, bx-16, by); + cairo_curve_to(mask_cr, mx-8, by, mx-8, by, mx, my+ARROW_DISTANCE); + cairo_curve_to(mask_cr, mx+3, ey, mx+5, ey, ex+10, ey); + } + } + + cairo_arc(mask_cr, x+w-radius, y+radius, radius, M_PI * 1.5, M_PI * 2); + cairo_arc(mask_cr, x+w-radius, y+h-radius, radius, 0, M_PI * 0.5); + + // the arrow is at the bottom, so it needs to be drawn here + if(windata->has_arrow && windata->arrow_type == GTK_ARROW_DOWN) { + if (mx > w/2 ) { + cairo_line_to(mask_cr, bx+10, by); + + cairo_curve_to(mask_cr, mx+3, by, mx+3, by, mx, my-ARROW_DISTANCE); + cairo_curve_to(mask_cr, mx-8, ey, mx-8, ey, ex-16, ey); + } else { + cairo_line_to(mask_cr, bx+16, by); + + cairo_curve_to(mask_cr, mx+8, by, mx+8, by, mx, my-ARROW_DISTANCE); + cairo_curve_to(mask_cr, mx-3, ey, mx-5, ey, ex-10, ey); + } + } + + cairo_arc(mask_cr, x+radius, y+h-radius, radius, M_PI * 0.5, M_PI); + cairo_arc(mask_cr, x+radius, y+radius, radius, M_PI, M_PI *1.5); +} + +static gboolean +mouse_over(GtkWidget *win, GdkEventExpose *event, WindowData *windata) +{ + gtk_widget_set_state((WindowData*)windata->close_button, GTK_STATE_PRELIGHT); +} + +static gboolean +mouse_over_end(GtkWidget *win, GdkEventExpose *event, WindowData *windata) +{ + gtk_widget_set_state((WindowData*)windata->close_button, GTK_STATE_NORMAL); +} + + +static gboolean +draw_border(GtkWidget *win, GdkEventExpose *event, WindowData *windata) +{ + int w, h; + + gdk_drawable_get_size(win->window, &w, &h); + + // calculate a shape for it + GdkPixmap *mask; + cairo_t *mask_cr; + + mask = gdk_pixmap_new (NULL, w, h, 1); + mask_cr = gdk_cairo_create ((GdkDrawable *) mask); + + // draw border + cairo_t *cr = gdk_cairo_create((GdkDrawable*) win->window); + + // set the background color + cairo_set_source_rgb(cr, + 255.0/255.0, + 255.0/255.0, + 230.0/255.0); + draw_rounded_window(cr, 0, 0, w, h, windata); + cairo_fill(cr); + + // draw a stripe + draw_stripe(win, cr, windata); + + // then a redish one + cairo_set_source_rgb(cr, + 218.0/255.0, + 178.0/255.0, + 85.0/255.0); + draw_rounded_window(cr, 0.5, 1.5, w-1, h-3, windata); + cairo_set_line_width (cr, 1); + cairo_stroke(cr); + + // clear the mask + cairo_set_operator (mask_cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (mask_cr); + + // draw a "black" rounded thing + cairo_set_source_rgba(mask_cr, 1,1,1,1); + cairo_set_operator (mask_cr, CAIRO_OPERATOR_OVER); + + draw_rounded_window(mask_cr, 0,1,w,h-2, windata); + cairo_fill(mask_cr); + gdk_window_shape_combine_mask (win->window, (GdkBitmap *) mask, 0, 0); + + return FALSE; +} + + + +static void +destroy_windata(WindowData *windata) +{ + if (windata->gc != NULL) + g_object_unref(G_OBJECT(windata->gc)); + + if (windata->border_points != NULL) + g_free(windata->border_points); + + if (windata->window_region != NULL) + gdk_region_destroy(windata->window_region); +} + +static void +update_content_hbox_visibility(WindowData *windata) +{ + /* + * This is all a hack, but until we have a libview-style ContentBox, + * it'll just have to do. + */ + if (GTK_WIDGET_VISIBLE(windata->icon) || + GTK_WIDGET_VISIBLE(windata->body_label) || + GTK_WIDGET_VISIBLE(windata->actions_box)) + { + gtk_widget_show(windata->content_hbox); + } + else + { + gtk_widget_hide(windata->content_hbox); + } +} + +GtkWindow * +create_notification(UrlClickedCb url_clicked) +{ + GtkWidget *spacer; + GtkWidget *win; + GtkWidget *main_vbox; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *close_button; + GtkWidget *image; + GtkWidget *alignment; + WindowData *windata; + + windata = g_new0(WindowData, 1); + windata->urgency = URGENCY_NORMAL; + windata->url_clicked = url_clicked; + + win = gtk_window_new(GTK_WINDOW_POPUP); + windata->win = win; + gtk_widget_add_events(win, GDK_BUTTON_RELEASE_MASK); + gtk_widget_realize(win); + g_object_set_data_full(G_OBJECT(win), "windata", windata, + (GDestroyNotify)destroy_windata); + gtk_widget_set_app_paintable(win, TRUE); + + g_signal_connect(G_OBJECT(win), "expose-event", + G_CALLBACK(draw_border), windata); + g_signal_connect(G_OBJECT(win), "motion-notify-event", + G_CALLBACK(mouse_over), windata); + g_signal_connect(G_OBJECT(win), "leave-notify-event", + G_CALLBACK(mouse_over_end), windata); + + main_vbox = gtk_vbox_new(FALSE, 0); + gtk_widget_show(main_vbox); + gtk_container_add(GTK_CONTAINER(win), main_vbox); + gtk_container_set_border_width(GTK_CONTAINER(main_vbox), 1); + + windata->top_spacer = gtk_image_new(); + gtk_box_pack_start(GTK_BOX(main_vbox), windata->top_spacer, + FALSE, FALSE, 0); + gtk_widget_set_size_request(windata->top_spacer, -1, DEFAULT_ARROW_HEIGHT); + + windata->main_hbox = gtk_hbox_new(FALSE, 0); + gtk_widget_show(windata->main_hbox); + gtk_box_pack_start(GTK_BOX(main_vbox), windata->main_hbox, + FALSE, FALSE, 0); + + windata->bottom_spacer = gtk_image_new(); + gtk_box_pack_start(GTK_BOX(main_vbox), windata->bottom_spacer, + FALSE, FALSE, 0); + gtk_widget_set_size_request(windata->bottom_spacer, -1, + DEFAULT_ARROW_HEIGHT); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_widget_show(vbox); + gtk_box_pack_start(GTK_BOX(windata->main_hbox), vbox, TRUE, TRUE, 0); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); + + hbox = gtk_hbox_new(FALSE, 6); + gtk_widget_show(hbox); + gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + spacer = gtk_image_new(); + gtk_widget_show(spacer); + gtk_box_pack_start(GTK_BOX(hbox), spacer, FALSE, FALSE, 0); + gtk_widget_set_size_request(spacer, STRIPE_WIDTH, -1); + + windata->summary_label = gtk_label_new(NULL); + /* mvo: hardcode black here, it sucks */ + GdkColor black; + gdk_color_black(gdk_colormap_get_system(), &black); + gtk_widget_modify_fg(windata->summary_label, + GTK_STATE_NORMAL, &black); + + gtk_widget_show(windata->summary_label); + + + gtk_box_pack_start(GTK_BOX(hbox), windata->summary_label, TRUE, TRUE, 0); + gtk_misc_set_alignment(GTK_MISC(windata->summary_label), 0, 0); + gtk_label_set_line_wrap(GTK_LABEL(windata->summary_label), TRUE); + + /* Add the close button */ + windata->close_button = close_button = gtk_button_new(); + gtk_widget_show(close_button); + gtk_box_pack_start(GTK_BOX(hbox), close_button, FALSE, FALSE, 0); + gtk_button_set_relief(GTK_BUTTON(close_button), GTK_RELIEF_NONE); + gtk_container_set_border_width(GTK_CONTAINER(close_button), 0); + gtk_widget_set_size_request(close_button, 20, 20); + g_signal_connect_swapped(G_OBJECT(close_button), "clicked", + G_CALLBACK(gtk_widget_destroy), win); + + image = gtk_image_new_from_stock(GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU); + gtk_widget_show(image); + gtk_container_add(GTK_CONTAINER(close_button), image); + + windata->content_hbox = gtk_hbox_new(FALSE, 6); + gtk_box_pack_start(GTK_BOX(vbox), windata->content_hbox, FALSE, FALSE, 0); + + windata->iconbox = gtk_hbox_new(FALSE, 0); + gtk_widget_show(windata->iconbox); + gtk_box_pack_start(GTK_BOX(windata->content_hbox), windata->iconbox, + FALSE, FALSE, 0); + gtk_widget_set_size_request(windata->iconbox, BODY_X_OFFSET, -1); + + windata->icon = gtk_image_new(); + gtk_box_pack_start(GTK_BOX(windata->iconbox), windata->icon, + TRUE, TRUE, 0); + gtk_misc_set_alignment(GTK_MISC(windata->icon), 0.5, 0.0); + + vbox = gtk_vbox_new(FALSE, 6); + gtk_widget_show(vbox); + gtk_box_pack_start(GTK_BOX(windata->content_hbox), vbox, TRUE, TRUE, 0); + + windata->body_label = sexy_url_label_new(); + /* mvo: hardcode black here, it sucks */ + gtk_widget_modify_fg(windata->body_label, GTK_STATE_NORMAL, &black); + + gtk_box_pack_start(GTK_BOX(vbox), windata->body_label, TRUE, TRUE, 0); + gtk_misc_set_alignment(GTK_MISC(windata->body_label), 0, 0); + gtk_label_set_line_wrap(GTK_LABEL(windata->body_label), TRUE); + g_signal_connect_swapped(G_OBJECT(windata->body_label), "url_activated", + G_CALLBACK(windata->url_clicked), win); + + alignment = gtk_alignment_new(1, 0.5, 0, 0); + gtk_widget_show(alignment); + gtk_box_pack_start(GTK_BOX(vbox), alignment, FALSE, TRUE, 0); + + windata->actions_box = gtk_hbox_new(FALSE, 6); + gtk_container_add(GTK_CONTAINER(alignment), windata->actions_box); + + return GTK_WINDOW(win); +} + +void +set_notification_hints(GtkWindow *nw, GHashTable *hints) +{ + WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata"); + GValue *value; + + g_assert(windata != NULL); + + value = (GValue *)g_hash_table_lookup(hints, "urgency"); + + if (value) + windata->urgency = g_value_get_uchar(value); +} + +void +set_notification_timeout(GtkWindow *nw, glong timeout) +{ + WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata"); + g_assert(windata != NULL); + + windata->timeout = timeout; +} + +void +notification_tick(GtkWindow *nw, glong remaining) +{ + WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata"); + windata->remaining = remaining; + + if (windata->pie_countdown != NULL) + { + gtk_widget_queue_draw_area(windata->pie_countdown, 0, 0, + PIE_WIDTH, PIE_HEIGHT); + } +} + +void +set_notification_text(GtkWindow *nw, const char *summary, const char *body) +{ + char *str; + WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata"); + g_assert(windata != NULL); + + str = g_strdup_printf("%s", summary); + gtk_label_set_markup(GTK_LABEL(windata->summary_label), str); + g_free(str); + + sexy_url_label_set_markup(SEXY_URL_LABEL(windata->body_label), body); + + if (body == NULL || *body == '\0') + gtk_widget_hide(windata->body_label); + else + gtk_widget_show(windata->body_label); + + + update_content_hbox_visibility(windata); +#if 0 + gtk_widget_set_size_request( + ((body != NULL && *body == '\0') + ? windata->body_label : windata->summary_label), + WIDTH - (IMAGE_SIZE + IMAGE_PADDING) - 10, + -1); +#endif + // get the correct height + GtkRequisition req; + gtk_widget_size_request(windata->body_label, &req); + gtk_widget_realize(windata->body_label); + gtk_widget_set_size_request(windata->body_label, -1, + MAX(req.height, IMAGE_SIZE+4)); +} + +void +set_notification_icon(GtkWindow *nw, GdkPixbuf *pixbuf) +{ + WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata"); + g_assert(windata != NULL); + + gtk_image_set_from_pixbuf(GTK_IMAGE(windata->icon), pixbuf); + + if (pixbuf != NULL) + { + int pixbuf_width = gdk_pixbuf_get_width(pixbuf); + + gtk_widget_show(windata->icon); + gtk_widget_set_size_request(windata->iconbox, + MAX(BODY_X_OFFSET, pixbuf_width), -1); + } + else + { + gtk_widget_hide(windata->icon); + gtk_widget_set_size_request(windata->iconbox, BODY_X_OFFSET, -1); + } + + update_content_hbox_visibility(windata); +} + +void +set_notification_arrow(GtkWindow *nw, gboolean visible, int x, int y) +{ + WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata"); + g_assert(windata != NULL); + + windata->has_arrow = visible; + windata->point_x = x; + windata->point_y = y; + + if (!visible) + { + gtk_widget_hide(windata->top_spacer); + gtk_widget_hide(windata->bottom_spacer); + } +} + +static gboolean +countdown_expose_cb(GtkWidget *pie, GdkEventExpose *event, + WindowData *windata) +{ + GtkStyle *style = gtk_widget_get_style(windata->win); + GdkGC *bg_gc = style->base_gc[GTK_STATE_NORMAL]; + + gdk_draw_rectangle(GDK_DRAWABLE(pie->window), bg_gc, TRUE, + 0, 0, pie->allocation.width, pie->allocation.height); + + if (windata->timeout > 0) + { + GdkGC *pie_gc = style->bg_gc[GTK_STATE_ACTIVE]; + gdouble pct = (gdouble)windata->remaining / (gdouble)windata->timeout; + + gdk_draw_arc(GDK_DRAWABLE(windata->pie_countdown->window), + pie_gc, TRUE, + 0, 0, PIE_WIDTH, PIE_HEIGHT, + 90 * 64, pct * 360.0 * 64.0); + } + + return TRUE; +} + +static void +action_clicked_cb(GtkWidget *w, GdkEventButton *event, + ActionInvokedCb action_cb) +{ + GtkWindow *nw = g_object_get_data(G_OBJECT(w), "_nw"); + const char *key = g_object_get_data(G_OBJECT(w), "_action_key"); + + action_cb(nw, key); +} + +void +add_notification_action(GtkWindow *nw, const char *text, const char *key, + ActionInvokedCb cb) +{ + WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata"); + GtkWidget *label; + GtkWidget *button; + GtkWidget *hbox; + GdkPixbuf *pixbuf; + char *buf; + + g_assert(windata != NULL); + + if (!GTK_WIDGET_VISIBLE(windata->actions_box)) + { + GtkWidget *alignment; + + gtk_widget_show(windata->actions_box); + update_content_hbox_visibility(windata); + + alignment = gtk_alignment_new(1, 0.5, 0, 0); + gtk_widget_show(alignment); + gtk_box_pack_end(GTK_BOX(windata->actions_box), alignment, + FALSE, TRUE, 0); + + windata->pie_countdown = gtk_drawing_area_new(); + gtk_widget_show(windata->pie_countdown); + gtk_container_add(GTK_CONTAINER(alignment), windata->pie_countdown); + gtk_widget_set_size_request(windata->pie_countdown, + PIE_WIDTH, PIE_HEIGHT); + g_signal_connect(G_OBJECT(windata->pie_countdown), "expose_event", + G_CALLBACK(countdown_expose_cb), windata); + } + + button = gtk_button_new(); + gtk_widget_show(button); + gtk_box_pack_start(GTK_BOX(windata->actions_box), button, FALSE, FALSE, 0); + + hbox = gtk_hbox_new(FALSE, 6); + gtk_widget_show(hbox); + gtk_container_add(GTK_CONTAINER(button), hbox); + + /* Try to be smart and find a suitable icon. */ + buf = g_strdup_printf("stock_%s", key); + pixbuf = gtk_icon_theme_load_icon( + gtk_icon_theme_get_for_screen( + gdk_drawable_get_screen(GTK_WIDGET(nw)->window)), + buf, 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); + g_free(buf); + + if (pixbuf != NULL) + { + GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf); + gtk_widget_show(image); + gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0); + gtk_misc_set_alignment(GTK_MISC(image), 0.5, 0.5); + } + + label = gtk_label_new(NULL); + /* mvo: hardcode black here, it sucks */ + GdkColor black; + gdk_color_black(gdk_colormap_get_system(), &black); + gtk_widget_modify_fg(label, GTK_STATE_NORMAL, &black); + + gtk_widget_show(label); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5); + buf = g_strdup_printf("%s", text); + gtk_label_set_markup(GTK_LABEL(label), buf); + + g_free(buf); + + g_object_set_data(G_OBJECT(button), "_nw", nw); + g_object_set_data_full(G_OBJECT(button), + "_action_key", g_strdup(key), g_free); + g_signal_connect(G_OBJECT(button), "button-release-event", + G_CALLBACK(action_clicked_cb), cb); +} + +#define ADD_POINT(_x, _y, shapeoffset_x, shapeoffset_y) \ + do { \ + windata->border_points[i].x = (_x); \ + windata->border_points[i].y = (_y); \ + shape_points[i].x = (_x) + (shapeoffset_x); \ + shape_points[i].y = (_y) + (shapeoffset_y); \ + i++;\ + } while (0) + +static void +create_border_with_arrow(GtkWidget *nw, WindowData *windata) +{ + GtkRequisition req; + GdkScreen *screen; + int monitor_num; + GdkRectangle monitor_geom; + int monitor_left, monitor_right, monitor_top, monitor_bottom; + int arrow_side1_width = DEFAULT_ARROW_WIDTH / 2; + int arrow_side2_width = DEFAULT_ARROW_WIDTH / 2; + int arrow_offset = DEFAULT_ARROW_OFFSET; + GdkPoint *shape_points; + int i = 0; + + gtk_widget_realize(nw); + gtk_widget_size_request(nw, &req); + + screen = gdk_drawable_get_screen(GDK_DRAWABLE(nw->window)); + monitor_num = gdk_screen_get_monitor_at_point(screen, + windata->point_x, windata->point_y); + gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor_geom); + + monitor_left = monitor_geom.x; + monitor_right = monitor_left + monitor_geom.width; + monitor_top = monitor_geom.y; + monitor_bottom = monitor_top + monitor_geom.height; + + if (windata->border_points != NULL) + g_free(windata->border_points); + + windata->num_border_points = 5; + + if (windata->point_y + req.height + DEFAULT_ARROW_HEIGHT >= monitor_bottom) + windata->arrow_type = GTK_ARROW_DOWN; + else + windata->arrow_type = GTK_ARROW_UP; + + /* Handle the offset and such */ + switch (windata->arrow_type) + { + case GTK_ARROW_UP: + case GTK_ARROW_DOWN: + if (windata->point_x < monitor_left + arrow_side1_width) + { + arrow_side1_width = 0; + arrow_offset = 0; + } + else if (windata->point_x >= monitor_right - arrow_side2_width) + { + arrow_side2_width = 0; + arrow_offset = req.width - arrow_side1_width; + } + else + { + if (windata->point_x - arrow_side2_width + req.width >= + monitor_right) + { + arrow_offset = + req.width - arrow_side1_width - arrow_side2_width - + (monitor_right - MAX(windata->point_x + + arrow_side1_width, + monitor_right - + DEFAULT_ARROW_OFFSET)); + } + else + { + arrow_offset = MIN(windata->point_x - arrow_side1_width - monitor_left, + DEFAULT_ARROW_OFFSET); + } + + if (arrow_offset == 0 || + arrow_offset == req.width - arrow_side1_width) + windata->num_border_points++; + else + windata->num_border_points += 2; + } + + /* + * Why risk this for official builds? If it's somehow off the + * screen, it won't horribly impact the user. Definitely less + * than an assertion would... + */ +#if 0 + g_assert(arrow_offset + arrow_side1_width >= 0); + g_assert(arrow_offset + arrow_side1_width + arrow_side2_width <= + req.width); +#endif + + windata->border_points = g_new0(GdkPoint, + windata->num_border_points); + shape_points = g_new0(GdkPoint, windata->num_border_points); + + windata->drawn_arrow_begin_x = arrow_offset; + windata->drawn_arrow_middle_x = arrow_offset + arrow_side1_width; + windata->drawn_arrow_end_x = arrow_offset + arrow_side1_width + + arrow_side2_width; + + if (windata->arrow_type == GTK_ARROW_UP) + { + gtk_widget_show(windata->top_spacer); + windata->drawn_arrow_begin_y = DEFAULT_ARROW_HEIGHT; + windata->drawn_arrow_middle_y = 0; + windata->drawn_arrow_end_y = DEFAULT_ARROW_HEIGHT; + + if (arrow_side1_width == 0) + { + ADD_POINT(0, 0, 0, 0); + } + else + { + ADD_POINT(0, DEFAULT_ARROW_HEIGHT, 0, 0); + + if (arrow_offset > 0) + ADD_POINT(arrow_offset - + (arrow_side2_width > 0 ? 0 : 1), + DEFAULT_ARROW_HEIGHT, 0, 0); + + ADD_POINT(arrow_offset + arrow_side1_width - + (arrow_side2_width > 0 ? 0 : 1), + 0, 0, 0); + } + + if (arrow_side2_width > 0) + { + ADD_POINT(windata->drawn_arrow_end_x, + windata->drawn_arrow_end_y, 1, 0); + ADD_POINT(req.width - 1, DEFAULT_ARROW_HEIGHT, 1, 0); + } + + ADD_POINT(req.width - 1, + req.height + DEFAULT_ARROW_HEIGHT - 1, 1, 1); + ADD_POINT(0, req.height + DEFAULT_ARROW_HEIGHT - 1, 0, 1); + } + else + { + gtk_widget_show(windata->bottom_spacer); + windata->drawn_arrow_begin_y = req.height; + windata->drawn_arrow_middle_y = req.height + + DEFAULT_ARROW_HEIGHT; + windata->drawn_arrow_end_y = req.height; + + ADD_POINT(0, 0, 0, 0); + ADD_POINT(req.width - 1, 0, 1, 0); + + if (arrow_side2_width == 0) + { + ADD_POINT(req.width - 1, + req.height + DEFAULT_ARROW_HEIGHT, + (arrow_side1_width > 0 ? 0 : 1), 0); + } + else + { + ADD_POINT(req.width - 1, req.height, 1, 1); + + if (arrow_offset < req.width - arrow_side1_width) + { + ADD_POINT(arrow_offset + arrow_side1_width + + arrow_side2_width, req.height, 0, 1); + } + + ADD_POINT(arrow_offset + arrow_side1_width, + req.height + DEFAULT_ARROW_HEIGHT, 0, 1); + } + + if (arrow_side1_width > 0) + { + ADD_POINT(windata->drawn_arrow_begin_x - + (arrow_side2_width > 0 ? 0 : 1), + windata->drawn_arrow_begin_y, 0, 0); + ADD_POINT(0, req.height, 0, 1); + } + } + +#if 0 + g_assert(i == windata->num_border_points); + g_assert(windata->point_x - arrow_offset - arrow_side1_width >= 0); +#endif + gtk_window_move(GTK_WINDOW(nw), + windata->point_x - arrow_offset - + arrow_side1_width, + (windata->arrow_type == GTK_ARROW_UP + ? windata->point_y + : windata->point_y - req.height - + DEFAULT_ARROW_HEIGHT)); + + break; + + case GTK_ARROW_LEFT: + case GTK_ARROW_RIGHT: + if (windata->point_y < monitor_top + arrow_side1_width) + { + arrow_side1_width = 0; + arrow_offset = windata->point_y; + } + else if (windata->point_y >= monitor_bottom - arrow_side2_width) + { + arrow_side2_width = 0; + arrow_offset = windata->point_y - arrow_side1_width; + } + break; + } + + windata->window_region = + gdk_region_polygon(shape_points, windata->num_border_points, + GDK_EVEN_ODD_RULE); + g_free(shape_points); + + draw_border(nw, NULL, windata); +} + +void +move_notification(GtkWindow *nw, int x, int y) +{ + WindowData *windata = g_object_get_data(G_OBJECT(nw), "windata"); + g_assert(windata != NULL); + + if (windata->has_arrow) + { + create_border_with_arrow(GTK_WIDGET(nw), windata); + } + else + { + gtk_window_move(GTK_WINDOW(nw), x, y); + } +}