From 501a421b337b6e08b58904b1c008bd05dbbf954b Mon Sep 17 00:00:00 2001 From: Andreas Fink Date: Mon, 14 Sep 2009 21:28:17 +0000 Subject: [PATCH] *add* tooltips --- src/Makefile.am | 6 +- src/config.c | 39 +++++++ src/panel.c | 15 ++- src/taskbar/task.c | 3 +- src/taskbar/task.h | 4 +- src/tint.c | 33 ++++-- src/tooltip/tooltip.c | 234 ++++++++++++++++++++++++++++++++++++++++++ src/tooltip/tooltip.h | 40 ++++++++ tintrc01 | 17 +++ tintrc02 | 18 ++++ 10 files changed, 396 insertions(+), 13 deletions(-) create mode 100644 src/tooltip/tooltip.c create mode 100644 src/tooltip/tooltip.h diff --git a/src/Makefile.am b/src/Makefile.am index 7441734..bed1864 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,7 +4,7 @@ endif AM_CFLAGS += @PANGOCAIRO_CFLAGS@ @PANGO_CFLAGS@ @CAIRO_CFLAGS@ @GLIB2_CFLAGS@ @GOBJECT2_CFLAGS@ @X11_CFLAGS@ @XINERAMA_CFLAGS@ @IMLIB2_CFLAGS@ LIBS = @PANGOCAIRO_LIBS@ @PANGO_LIBS@ @CAIRO_LIBS@ @GLIB2_LIBS@ @GOBJECT2_LIBS@ @X11_LIBS@ @XINERAMA_LIBS@ @IMLIB2_LIBS@ -INCLUDES = -Iutil -Iclock -Itaskbar -Isystray +INCLUDES = -Iutil -Iclock -Itaskbar -Isystray -Itooltip bin_PROGRAMS = tint2 tint2_SOURCES = config.c \ @@ -27,7 +27,9 @@ tint2_SOURCES = config.c \ taskbar/taskbar.c \ taskbar/task.c \ taskbar/taskbar.h \ - taskbar/task.h + taskbar/task.h \ + tooltip/tooltip.c \ + tooltip/tooltip.h if ENABLE_BATTERY DEFS += -DENABLE_BATTERY diff --git a/src/config.c b/src/config.c index 9396dcb..9fda951 100644 --- a/src/config.c +++ b/src/config.c @@ -43,6 +43,7 @@ #include "panel.h" #include "config.h" #include "window.h" +#include "tooltip.h" #ifdef ENABLE_BATTERY #include "battery.h" @@ -575,6 +576,43 @@ void add_entry (char *key, char *value) systray.sort = 1; } + /* Tooltip */ + else if (strcmp (key, "tooltip") == 0) + g_tooltip.enabled = atoi(value); + else if (strcmp (key, "tooltip_show_timeout") == 0) { + double timeout = atof(value); + int sec = (int)timeout; + int usec = (timeout-sec)*1e6; + g_tooltip.show_timeout.it_value = (struct timeval){.tv_sec=sec, .tv_usec=usec}; + } + else if (strcmp (key, "tooltip_hide_timeout") == 0) { + double timeout = atof(value); + int sec = (int)timeout; + int usec = (timeout-sec)*1e6; + g_tooltip.hide_timeout.it_value = (struct timeval){.tv_sec=sec, .tv_usec=usec}; + } + else if (strcmp (key, "tooltip_padding") == 0) { + extract_values(value, &value1, &value2, &value3); + if (value1) g_tooltip.paddingx = atoi(value1); + if (value2) g_tooltip.paddingy = atoi(value2); + } + else if (strcmp (key, "tooltip_background_id") == 0) { + int id = atoi (value); + Area *a = g_slist_nth_data(list_back, id); + memcpy(&g_tooltip.background_color, &a->pix.back, sizeof(Color)); + memcpy(&g_tooltip.border, &a->pix.border, sizeof(Border)); + } + else if (strcmp (key, "tooltip_font_color") == 0) { + extract_values(value, &value1, &value2, &value3); + get_color(value1, g_tooltip.font_color.color); + if (value2) g_tooltip.font_color.alpha = (atoi (value2) / 100.0); + else g_tooltip.font_color.alpha = 0.1; + } + else if (strcmp (key, "tooltip_font") == 0) { + if (g_tooltip.font_desc) pango_font_description_free(g_tooltip.font_desc); + g_tooltip.font_desc = pango_font_description_from_string(value); + } + /* Mouse actions */ else if (strcmp (key, "mouse_middle") == 0) get_action (value, &mouse_middle); @@ -737,6 +775,7 @@ void config_finish () #endif init_systray(); init_taskbar(); + init_tooltip(); visible_object(); cleanup_config(); diff --git a/src/panel.c b/src/panel.c index bc0d1b0..7ef7e20 100644 --- a/src/panel.c +++ b/src/panel.c @@ -148,7 +148,10 @@ void init_panel() // printf("panel : posx %d, posy %d, width %d, height %d\n", p->posx, p->posy, p->area.width, p->area.height); // Catch some events - XSetWindowAttributes att = { ParentRelative, 0L, 0, 0L, 0, 0, Always, 0L, 0L, False, ExposureMask|ButtonPressMask|ButtonReleaseMask, NoEventMask, False, 0, 0 }; + long event_mask = ExposureMask|ButtonPressMask|ButtonReleaseMask; + if (g_tooltip.enabled) + event_mask |= PointerMotionMask|LeaveWindowMask; + XSetWindowAttributes att = { ParentRelative, 0L, 0, 0L, 0, 0, Always, 0L, 0L, False, event_mask, NoEventMask, False, 0, 0 }; if (p->main_win) XDestroyWindow(server.dsp, p->main_win); p->main_win = XCreateWindow(server.dsp, server.root_win, p->posx, p->posy, p->area.width, p->area.height, 0, server.depth, InputOutput, CopyFromParent, CWEventMask, &att); @@ -198,6 +201,15 @@ void cleanup_panel() if (panel1) free(panel1); panel1 = 0; + + if (g_tooltip.window) { + XDestroyWindow(server.dsp, g_tooltip.window); + g_tooltip.window = 0; + } + if (g_tooltip.font_desc) { + pango_font_description_free(g_tooltip.font_desc); + g_tooltip.font_desc = 0; + } } @@ -371,6 +383,7 @@ void set_panel_properties(Panel *p) // Unfocusable XWMHints wmhints; if (panel_dock) { + // TODO: Xdnd feature cannot be used in withdrawn state at the moment (at least GTK apps fail, qt seems to work) wmhints.icon_window = wmhints.window_group = p->main_win; wmhints.flags = StateHint | IconWindowHint; wmhints.initial_state = WithdrawnState; diff --git a/src/taskbar/task.c b/src/taskbar/task.c index 55292ab..b8ca678 100644 --- a/src/taskbar/task.c +++ b/src/taskbar/task.c @@ -30,6 +30,7 @@ #include "task.h" #include "server.h" #include "panel.h" +#include "tooltip.h" @@ -139,7 +140,7 @@ void get_title(Task *tsk) Panel *panel = tsk->area.panel; char *title, *name; - if (!panel->g_task.text) return; + if (!panel->g_task.text && !g_tooltip.enabled) return; name = server_get_property (tsk->win, server.atom._NET_WM_VISIBLE_NAME, server.atom.UTF8_STRING, 0); if (!name || !strlen(name)) { diff --git a/src/taskbar/task.h b/src/taskbar/task.h index f0cefe2..172b0d5 100644 --- a/src/taskbar/task.h +++ b/src/taskbar/task.h @@ -26,8 +26,8 @@ typedef struct { int icon_size1; int maximum_width; int maximum_height; - int hue, saturation, brightness; - int hue_active, saturation_active, brightness_active; + int hue, saturation, brightness; + int hue_active, saturation_active, brightness_active; // starting position for text ~ task_padding + task_border + icon_size double text_posx, text_posy; diff --git a/src/tint.c b/src/tint.c index 33f5e67..c23f115 100644 --- a/src/tint.c +++ b/src/tint.c @@ -37,6 +37,7 @@ #include "taskbar.h" #include "systraybar.h" #include "panel.h" +#include "tooltip.h" void signal_handler(int sig) @@ -72,6 +73,7 @@ void init (int argc, char *argv[]) signal(SIGTERM, signal_handler); signal(SIGHUP, signal_handler); signal(SIGCHLD, SIG_IGN); // don't have to wait() after fork() + signal(SIGALRM, tooltip_sighandler); // set global data memset(&server, 0, sizeof(Server_global)); @@ -587,12 +589,15 @@ void event_property_notify (XEvent *e) void event_expose (XEvent *e) { - Panel *panel; - - panel = get_panel(e->xany.window); - if (!panel) return; - // TODO : one panel_refresh per panel ? - panel_refresh = 1; + if (e->xany.window == g_tooltip.window) + tooltip_update(); + else { + Panel *panel; + panel = get_panel(e->xany.window); + if (!panel) return; + // TODO : one panel_refresh per panel ? + panel_refresh = 1; + } } @@ -709,7 +714,6 @@ int main (int argc, char *argv[]) GSList *it; init (argc, argv); - load_config: i = 0; init_config(); @@ -749,6 +753,7 @@ load_config: switch (e.type) { case ButtonPress: + tooltip_hide(); event_button_press (&e); break; @@ -756,6 +761,20 @@ load_config: event_button_release(&e); break; + case MotionNotify: { + Panel* panel = get_panel(e.xmotion.window); + Task* task = click_task(panel, e.xmotion.x, e.xmotion.y); + if (task) + tooltip_trigger_show(task, e.xmotion.x_root, e.xmotion.y_root); + else + tooltip_trigger_hide(); + break; + } + + case LeaveNotify: + tooltip_trigger_hide(); + break; + case Expose: event_expose(&e); break; diff --git a/src/tooltip/tooltip.c b/src/tooltip/tooltip.c new file mode 100644 index 0000000..29552b0 --- /dev/null +++ b/src/tooltip/tooltip.c @@ -0,0 +1,234 @@ +#include +#include +#include +#include + +#include "server.h" +#include "tooltip.h" +#include "panel.h" + +// TODO: Use timer_create instead of setitimer, because SIGALRM is not the right signal for this... +// Reason: If we want to implement autohide we have to use another signal... + +static int x, y, width, height; + +// give the tooltip some reasonable default values +Tooltip g_tooltip = { + .task = 0, + .window = 0, + .show_timeout = { .it_interval={0, 0}, .it_value={0, 0} }, + .hide_timeout = { .it_interval={0, 0}, .it_value={0, 0} }, + .enabled = False, + .current_state = TOOLTIP_ABOUT_TO_HIDE, + .mapped = False, + .paddingx = 0, + .paddingy = 0, + .font_color = { .color={1, 1, 1}, .alpha=1 }, + .background_color = { .color={0.5, 0.4, 0.5}, .alpha=1 }, + .border = { .color={0, 0, 0}, .alpha=1, .width=1, .rounded=0 }, + .font_desc = 0 +}; + +void init_tooltip() +{ + if (!g_tooltip.font_desc) + g_tooltip.font_desc = pango_font_description_from_string("sans 10"); + + XSetWindowAttributes attr; + attr.override_redirect = True; + attr.event_mask = ExposureMask; + if (g_tooltip.window) XDestroyWindow(server.dsp, g_tooltip.window); + g_tooltip.window = XCreateWindow(server.dsp, server.root_win, 0, 0, 100, 20, 0, server.depth, InputOutput, CopyFromParent, CWOverrideRedirect|CWEventMask, &attr); +} + + +void tooltip_sighandler(int sig) +{ + if (g_tooltip.current_state == TOOLTIP_ABOUT_TO_SHOW) + tooltip_show(); + else if (g_tooltip.current_state == TOOLTIP_ABOUT_TO_HIDE) + tooltip_hide(); +} + + +void tooltip_trigger_show(Task* task, int x_root, int y_root) +{ + x = x_root; + y = y_root; + + if (g_tooltip.mapped && g_tooltip.task != task) { + g_tooltip.task = task; + tooltip_update(); + alarm(0); + } + else if (!g_tooltip.mapped) { + g_tooltip.current_state = TOOLTIP_ABOUT_TO_SHOW; + g_tooltip.task = task; + struct timeval t = g_tooltip.show_timeout.it_value; + if (t.tv_sec == 0 && t.tv_usec == 0) + tooltip_show(); + else + setitimer(ITIMER_REAL, &g_tooltip.show_timeout, 0); + } +} + + +void tooltip_show() +{ + if (!g_tooltip.mapped) { + g_tooltip.mapped = True; + XMapWindow(server.dsp, g_tooltip.window); + tooltip_update(); + alarm(0); + } +} + + +void tooltip_update_geometry() +{ + cairo_surface_t *cs; + cairo_t *c; + PangoLayout* layout; + cs = cairo_xlib_surface_create(server.dsp, g_tooltip.window, server.visual, width, height); + c = cairo_create(cs); + layout = pango_cairo_create_layout(c); + pango_layout_set_font_description(layout, g_tooltip.font_desc); + pango_layout_set_text(layout, g_tooltip.task->title, -1); + PangoRectangle r1, r2; + pango_layout_get_pixel_extents(layout, &r1, &r2); + width = 2*g_tooltip.border.width + 2*g_tooltip.paddingx + r2.width; + height = 2*g_tooltip.border.width + 2*g_tooltip.paddingy + r2.height; + + Panel* panel = g_tooltip.task->area.panel; + if (panel_horizontal && panel_position & BOTTOM) + y = panel->posy-height; + else if (panel_horizontal && panel_position & TOP) + y = panel->posy + panel->area.height; + else if (panel_position & LEFT) + x = panel->posx + panel->area.width; + else + x = panel->posx - width; + g_object_unref(layout); + cairo_destroy(c); + cairo_surface_destroy(cs); +} + + +void tooltip_adjust_geometry() +{ + // adjust coordinates and size to not go offscreen + // it seems quite impossible that the height needs to be adjusted, but we do it anyway. + + int min_x, min_y, max_width, max_height; + Panel* panel = g_tooltip.task->area.panel; + int screen_width = server.monitor[panel->monitor].width; + int screen_height = server.monitor[panel->monitor].height; + if ( x+width <= screen_width && y+height <= screen_height && x>=0 && y>=0) + return; // no adjustment needed + + if (panel_horizontal) { + min_x=0; + max_width=screen_width; + max_height=screen_height-panel->area.height; + if (panel_position & BOTTOM) + min_y=0; + else + min_y=panel->area.height; + } + else { + max_width=screen_width-panel->area.width; + min_y=0; + max_height=screen_height; + if (panel_position & LEFT) + min_x=panel->area.width; + else + min_x=0; + } + + if (x+width > server.monitor[panel->monitor].width) + x = server.monitor[panel->monitor].width-width; + if ( y+height>server.monitor[panel->monitor].height) + y = server.monitor[panel->monitor].height-height; + + if (xmax_width) + width = max_width; + if (ymax_height) + height=max_height; +} + +void tooltip_update() +{ + if (!g_tooltip.task) { + tooltip_hide(); + return; + } + + tooltip_update_geometry(); + tooltip_adjust_geometry(); + XMoveResizeWindow(server.dsp, g_tooltip.window, x, y, width, height); + + // Stuff for drawing the tooltip + cairo_surface_t *cs; + cairo_t *c; + PangoLayout* layout; + cs = cairo_xlib_surface_create(server.dsp, g_tooltip.window, server.visual, width, height); + c = cairo_create(cs); + Color bc = g_tooltip.background_color; + cairo_rectangle(c, 0, 0, width, height); + cairo_set_source_rgb(c, bc.color[0], bc.color[1], bc.color[2]); + cairo_fill(c); + Border b = g_tooltip.border; + cairo_set_source_rgba(c, b.color[0], b.color[1], b.color[2], b.alpha); + cairo_set_line_width(c, b.width); + cairo_rectangle(c, b.width/2.0, b.width/2.0, width-b.width, height-b.width); + cairo_stroke(c); + + config_color fc = g_tooltip.font_color; + cairo_set_source_rgba(c, fc.color[0], fc.color[1], fc.color[2], fc.alpha); + layout = pango_cairo_create_layout(c); + pango_layout_set_font_description(layout, g_tooltip.font_desc); + pango_layout_set_text(layout, g_tooltip.task->title, -1); + PangoRectangle r1, r2; + pango_layout_get_pixel_extents(layout, &r1, &r2); + pango_layout_set_width(layout, width*PANGO_SCALE); + pango_layout_set_height(layout, height*PANGO_SCALE); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + // I do not know why this is the right way, but with the below cairo_move_to it seems to be centered (horiz. and vert.) + cairo_move_to(c, -r1.x/2+g_tooltip.border.width+g_tooltip.paddingx, -r1.y/2+g_tooltip.border.width+g_tooltip.paddingy); + pango_cairo_show_layout (c, layout); + + g_object_unref (layout); + cairo_destroy (c); + cairo_surface_destroy (cs); +} + + +void tooltip_trigger_hide(Tooltip* tooltip) +{ + if (g_tooltip.mapped) { + g_tooltip.current_state = TOOLTIP_ABOUT_TO_HIDE; + struct timeval t = g_tooltip.hide_timeout.it_value; + if (t.tv_sec == 0 && t.tv_usec == 0) + tooltip_hide(); + else + setitimer(ITIMER_REAL, &g_tooltip.hide_timeout, 0); + } + else { + // tooltip not visible yet, but maybe an alarm is still pending + alarm(0); + } +} + + +void tooltip_hide() +{ + if (g_tooltip.mapped) { + g_tooltip.mapped = False; + XUnmapWindow(server.dsp, g_tooltip.window); + } + g_tooltip.task = 0; +} diff --git a/src/tooltip/tooltip.h b/src/tooltip/tooltip.h new file mode 100644 index 0000000..3e2b459 --- /dev/null +++ b/src/tooltip/tooltip.h @@ -0,0 +1,40 @@ +#ifndef TOOLTIP_H +#define TOOLTIP_H + +#include + +#include "task.h" + +enum tooltip_state { + TOOLTIP_ABOUT_TO_SHOW, + TOOLTIP_ABOUT_TO_HIDE, +}; + +typedef struct { + Task* task; + Window window; + struct itimerval show_timeout; + struct itimerval hide_timeout; + Bool enabled; + enum tooltip_state current_state; + Bool mapped; + int paddingx; + int paddingy; + PangoFontDescription* font_desc; + config_color font_color; + Color background_color; + Border border; +} Tooltip; + +extern Tooltip g_tooltip; + + +void init_tooltip(); +void tooltip_sighandler(int sig); +void tooltip_trigger_show(Task* task, int x, int y); +void tooltip_show(); +void tooltip_update(); +void tooltip_trigger_hide(); +void tooltip_hide(); + +#endif // TOOLTIP_H diff --git a/tintrc01 b/tintrc01 index 3966e21..17c7d9e 100644 --- a/tintrc01 +++ b/tintrc01 @@ -20,6 +20,12 @@ border_width = 0 background_color = #ffffff 18 border_color = #ffffff 70 +# tooltip +rounded = 0 +border_width = 2 +background_color = #030905 +border_color = #ffffff 30 + #--------------------------------------------- # PANEL #--------------------------------------------- @@ -87,6 +93,17 @@ battery_font_color = #ffffff 76 battery_padding = 1 0 battery_background_id = 0 +#--------------------------------------------- +# TOOLTIP +#--------------------------------------------- +tooltip = 1 +tooltip_padding = 2 2 +tooltip_show_timeout = 0.7 +tooltip_hide_timeout = 0.3 +tooltip_background_id = 4 +tooltip_font_color = #ffffff 80 +tooltip_font = sans 10 + #--------------------------------------------- # MOUSE ACTION ON TASK diff --git a/tintrc02 b/tintrc02 index 1c19500..7bd59cc 100644 --- a/tintrc02 +++ b/tintrc02 @@ -20,6 +20,12 @@ border_width = 0 background_color = #cccccc 20 border_color = #cccccc 40 +# tooltip +rounded = 0 +border_width = 2 +background_color = #030905 +border_color = #ffffff 30 + #--------------------------------------------- # PANEL #--------------------------------------------- @@ -86,6 +92,18 @@ battery_font_color = #ffffff 100 battery_padding = 1 0 battery_background_id = 0 +#--------------------------------------------- +# TOOLTIP +#--------------------------------------------- +tooltip = 1 +tooltip_padding = 2 2 +tooltip_show_timeout = 1.5 +tooltip_hide_timeout = 0.5 +tooltip_background_id = 4 +tooltip_font_color = #ffffff 80 +tooltip_font = sans 10 + + #--------------------------------------------- # MOUSE ACTION ON TASK #--------------------------------------------- -- 2.45.2