/* Functionality for a bar across the bottom of the screen listing the * windows currently managed. * * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts * * This file is part of ratpoison. * * ratpoison 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, or (at your option) * any later version. * * ratpoison 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. * * You should have received a copy of the GNU General Public License * along with this software; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA */ #include #include #include #ifdef USE_XFT_FONT #include #endif #include #include #include #include #include "ratpoison.h" #include "assert.h" /* Possible values for bar_is_raised status. */ #define BAR_IS_HIDDEN 0 #define BAR_IS_WINDOW_LIST 1 #define BAR_IS_GROUP_LIST 2 #define BAR_IS_MESSAGE 3 /* A copy of the last message displayed in the message bar. */ static char *last_msg = NULL; static int last_mark_start = 0; static int last_mark_end = 0; static void marked_message_internal (char *msg, int mark_start, int mark_end); /* Reset the alarm to auto-hide the bar in BAR_TIMEOUT seconds. */ static void reset_alarm (void) { alarm (defaults.bar_timeout); alarm_signalled = 0; } /* Hide the bar from sight. */ int hide_bar (rp_screen *s) { if (s->bar_is_raised) { s->bar_is_raised = 0; XUnmapWindow (dpy, s->bar_window); /* Possibly restore colormap. */ if (current_window()) { XUninstallColormap (dpy, s->def_cmap); XInstallColormap (dpy, current_window()->colormap); } return 1; } return 0; } /* Show window listing in bar. */ int show_bar (rp_screen *s, char *fmt) { if (!s->bar_is_raised) { s->bar_is_raised = BAR_IS_WINDOW_LIST; XMapRaised (dpy, s->bar_window); update_window_names (s, fmt); /* Switch to the default colormap */ if (current_window()) XUninstallColormap (dpy, current_window()->colormap); XInstallColormap (dpy, s->def_cmap); reset_alarm(); return 1; } /* If the bar is raised we still need to display the window names. */ update_window_names (s, fmt); return 0; } /* Show group listing in bar. */ int show_group_bar (rp_screen *s) { if (!s->bar_is_raised) { s->bar_is_raised = BAR_IS_GROUP_LIST; XMapRaised (dpy, s->bar_window); update_group_names (s); /* Switch to the default colormap */ if (current_window()) XUninstallColormap (dpy, current_window()->colormap); XInstallColormap (dpy, s->def_cmap); reset_alarm(); return 1; } /* If the bar is raised we still need to display the window names. */ update_group_names (s); return 0; } int bar_x (rp_screen *s, int width) { int x = 0; switch (defaults.bar_location) { case NorthWestGravity: case WestGravity: case SouthWestGravity: x = s->left + (defaults.bar_in_padding ? 0 : defaults.padding_left); break; case NorthGravity: case CenterGravity: case SouthGravity: x = s->left + (s->width - width - defaults.bar_border_width * 2) / 2 - (defaults.bar_in_padding ? 0 : defaults.padding_left); break; case NorthEastGravity: case EastGravity: case SouthEastGravity: x = s->left + s->width - width - defaults.bar_border_width * 2 - (defaults.bar_in_padding ? 0 : defaults.padding_right); break; } return x; } int bar_y (rp_screen *s, int height) { int y = 0; switch (defaults.bar_location) { case NorthEastGravity: case NorthGravity: case NorthWestGravity: y = s->top + (defaults.bar_in_padding ? 0 : defaults.padding_top); break; case EastGravity: case CenterGravity: case WestGravity: y = s->top + (s->height - height - defaults.bar_border_width * 2) / 2 - (defaults.bar_in_padding ? 0 : defaults.padding_top); break; case SouthEastGravity: case SouthGravity: case SouthWestGravity: y = s->top + (s->height - height - defaults.bar_border_width * 2) - (defaults.bar_in_padding ? 0 : defaults.padding_top); break; } return y; } void update_bar (rp_screen *s) { if (s->bar_is_raised == BAR_IS_WINDOW_LIST) { update_window_names (s, defaults.window_fmt); return; } if (s->bar_is_raised == BAR_IS_GROUP_LIST) { update_group_names (s); return; } if (s->bar_is_raised == BAR_IS_HIDDEN) return; redraw_last_message(); } /* Note that we use marked_message_internal to avoid resetting the alarm. */ void update_window_names (rp_screen *s, char *fmt) { struct sbuf *bar_buffer; int mark_start = 0; int mark_end = 0; if (s->bar_is_raised != BAR_IS_WINDOW_LIST) return; bar_buffer = sbuf_new (0); #ifdef WINLISTSTYLE if(defaults.window_list_style == STYLE_ROW) { get_window_list (fmt, NULL, bar_buffer, &mark_start, &mark_end); marked_message_internal (sbuf_get (bar_buffer), mark_start, mark_end); } else #endif { get_window_list (fmt, "\n", bar_buffer, &mark_start, &mark_end); marked_message_internal (sbuf_get (bar_buffer), mark_start, mark_end); } /* marked_message (sbuf_get (bar_buffer), mark_start, mark_end); */ sbuf_free (bar_buffer); } /* Note that we use marked_message_internal to avoid resetting the alarm. */ void update_group_names (rp_screen *s) { struct sbuf *bar_buffer; int mark_start = 0; int mark_end = 0; if (s->bar_is_raised != BAR_IS_GROUP_LIST) return; bar_buffer = sbuf_new (0); get_group_list ("\n", bar_buffer, &mark_start, &mark_end); marked_message_internal (sbuf_get (bar_buffer), mark_start, mark_end); sbuf_free (bar_buffer); } void message (char *s) { marked_message (s, 0, 0); } void marked_message_printf (int mark_start, int mark_end, char *fmt, ...) { char *buffer; va_list ap; va_start (ap, fmt); buffer = xvsprintf (fmt, ap); va_end (ap); marked_message (buffer, mark_start, mark_end); free (buffer); } static int count_lines (char* msg, int len) { int ret = 1; int i; if (len < 1) return 1; for(i=0; i ret) { ret = current_width; } /* Update the start of the new line. */ start = i + 1; } } return ret; } static int pos_in_line (char* msg, int pos) { int ret; int i; if(pos <= 0) return 0; /* Go backwards until we hit the beginning of the string or a new line. */ ret = 0; for(i=pos-1; i>=0; ret++, i--) { if(msg[i]=='\n') break; } return ret; } static int line_beginning (char* msg, int pos) { int ret = 0; int i; if(pos <= 0) return 0; /* Go backwards until we hit a new line or the beginning of the string. */ for(i=pos-1; i>=0; --i) { if (msg[i]=='\n') { ret = i + 1; break; } } return ret; } static void draw_partial_string (rp_screen *s, char *msg, int line_no, int start, int end, int style) { int line_height = FONT_HEIGHT (s); rp_draw_string (s, s->bar_window, style, defaults.bar_x_padding, defaults.bar_y_padding + FONT_ASCENT(s) + line_no * line_height, msg + start, end - start + 1); } static void draw_string (rp_screen *s, char *msg, int mark_start, int mark_end) { size_t i; int line_no; int start; int style = STYLE_NORMAL, update = 0; /* Walk through the string, print each line. */ start = 0; line_no = 0; /* if (mark_start == 0 && mark_end == 0) */ /* mark_start = mark_end = -1; */ for(i=0; i < strlen(msg); ++i) { if (i == (size_t)mark_start) { style = STYLE_INVERSE; update = 1; } if (i == (size_t)mark_end) { style = STYLE_NORMAL; update = 1; } if (msg[i] == '\n') update = 2; if (update) { draw_partial_string (s, msg, line_no, start, update == 2 ? i-1:i, style); start = i; if (update == 2) { line_no++; start++; } update = 0; } } /* Print the last line. */ draw_partial_string (s, msg, line_no, start, strlen (msg)-1, style); XSync (dpy, False); } /* Move the marks if they are outside the string or if the start is after the end. */ static void correct_mark (int msg_len, int *mark_start, int *mark_end) { /* Make sure the marks are inside the string. */ if (*mark_start < 0) *mark_start = 0; if (*mark_end < 0) *mark_end = 0; if (*mark_start > msg_len) *mark_start = msg_len; if (*mark_end > msg_len) *mark_end = msg_len; /* Make sure the marks aren't reversed. */ if (*mark_start > *mark_end) { int tmp; tmp = *mark_start; *mark_start = *mark_end; *mark_end = tmp; } } /* Raise the bar and put it in the right spot */ static void prepare_bar (rp_screen *s, int width, int height) { width = width < s->width ? width : s->width; height = height < s->height ? height : s->height; XMoveResizeWindow (dpy, s->bar_window, bar_x (s, width), bar_y (s, height), width, height); /* Map the bar if needed */ if (!s->bar_is_raised) { s->bar_is_raised = BAR_IS_MESSAGE; XMapRaised (dpy, s->bar_window); /* Switch to the default colormap */ if (current_window()) XUninstallColormap (dpy, current_window()->colormap); XInstallColormap (dpy, s->def_cmap); } XRaiseWindow (dpy, s->bar_window); XClearWindow (dpy, s->bar_window); XSync (dpy, False); } static void get_mark_box (char *msg, size_t mark_start, size_t mark_end, int *x, int *y, int *width, int *height) { rp_screen *s = current_screen (); int start, end; int mark_end_is_new_line = 0; int start_line; int end_line; int start_pos_in_line; int end_pos_in_line; int start_line_beginning; int end_line_beginning; /* If the mark_end is on a new line or the end of the string, then back it up one character. */ if (msg[mark_end-1] == '\n' || mark_end == strlen (msg)) { mark_end--; mark_end_is_new_line = 1; } start_line = count_lines(msg, mark_start); end_line = count_lines(msg, mark_end); start_pos_in_line = pos_in_line(msg, mark_start); end_pos_in_line = pos_in_line(msg, mark_end); start_line_beginning = line_beginning(msg, mark_start); end_line_beginning = line_beginning(msg, mark_end); PRINT_DEBUG (("start_line = %d, end_line = %d\n", start_line, end_line)); PRINT_DEBUG (("start_line_beginning = %d, end_line_beginning = %d\n", start_line_beginning, end_line_beginning)); if (mark_start == 0 || start_pos_in_line == 0) start = 0; else start = rp_text_width (s, &msg[start_line_beginning], start_pos_in_line) + defaults.bar_x_padding; end = rp_text_width (s, &msg[end_line_beginning], end_pos_in_line) + defaults.bar_x_padding * 2; if (mark_end != strlen (msg)) end -= defaults.bar_x_padding; /* A little hack to highlight to the end of the line, if the mark_end is at the end of a line. */ if (mark_end_is_new_line) { *width = max_line_length(msg) + defaults.bar_x_padding * 2; } else { *width = end - start; } *x = start; *y = (start_line - 1) * FONT_HEIGHT (s) + defaults.bar_y_padding; *height = (end_line - start_line + 1) * FONT_HEIGHT (s); } static void draw_box (rp_screen *s, int x, int y, int width, int height) { XGCValues lgv; GC lgc; unsigned long mask; lgv.foreground = s->fg_color; mask = GCForeground; lgc = XCreateGC(dpy, s->root, mask, &lgv); XFillRectangle (dpy, s->bar_window, lgc, x, y, width, height); XFreeGC (dpy, lgc); } static void draw_mark (rp_screen *s, char *msg, int mark_start, int mark_end) { int x, y, width, height; /* when this happens, there is no mark. */ if (mark_end == 0 || mark_start == mark_end) return; get_mark_box (msg, mark_start, mark_end, &x, &y, &width, &height); draw_box (s, x, y, width, height); } static void update_last_message (char *msg, int mark_start, int mark_end) { free (last_msg); last_msg = xstrdup (msg); last_mark_start = mark_start; last_mark_end = mark_end; } void marked_message (char *msg, int mark_start, int mark_end) { /* Schedule the bar to be hidden after some amount of time. */ reset_alarm (); marked_message_internal (msg, mark_start, mark_end); } static void marked_message_internal (char *msg, int mark_start, int mark_end) { rp_screen *s = current_screen (); int num_lines; int width; int height; PRINT_DEBUG (("msg = %s\n", msg?msg:"NULL")); PRINT_DEBUG (("mark_start = %d, mark_end = %d\n", mark_start, mark_end)); /* Calculate the width and height of the window. */ num_lines = count_lines (msg, strlen(msg)); width = defaults.bar_x_padding * 2 + max_line_length(msg); height = FONT_HEIGHT (s) * num_lines + defaults.bar_y_padding * 2; prepare_bar (s, width, height); /* Draw the mark over the designated part of the string. */ correct_mark (strlen (msg), &mark_start, &mark_end); draw_mark (s, msg, mark_start, mark_end); draw_string (s, msg, mark_start, mark_end); /* Keep a record of the message. */ update_last_message (msg, mark_start, mark_end); } /* Use this just to update the bar. show_last_message will draw it and leave it up for a period of time. */ void redraw_last_message (void) { char *msg; if (last_msg == NULL) return; /* A little kludge to avoid last_msg in marked_message from being strdup'd right after freeing the pointer. Note: in this case marked_message's msg arg would have been the same as last_msg. */ msg = xstrdup (last_msg); marked_message_internal (msg, last_mark_start, last_mark_end); free (msg); } void show_last_message (void) { redraw_last_message(); reset_alarm(); } /* Free any memory associated with the bar. */ void free_bar (void) { free (last_msg); last_msg = NULL; }