medit/moo/mooterm/mooterm-draw.c

1217 lines
34 KiB
C

/*
* mootermdraw.c
*
* Copyright (C) 2004-2007 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* See COPYING file that comes with this distribution.
*/
#define MOOTERM_COMPILATION
#include "mooterm/mooterm-private.h"
#include "mooterm/mooterm-selection.h"
#include "mooterm/mootermbuffer-private.h"
#include "mooterm/mootermline-private.h"
#include "mooutils/mooutils-misc.h"
#include <string.h>
#define CHAR_WIDTH(term__) ((term__)->priv->font->width)
#define CHAR_HEIGHT(term__) ((term__)->priv->font->height)
#define CHAR_ASCENT(term__) ((term__)->priv->font->ascent)
#define PIXEL_WIDTH(term__) (CHAR_WIDTH (term__) * (term__)->priv->width)
#define PIXEL_HEIGHT(term__) (CHAR_HEIGHT (term__) * (term__)->priv->height)
#define CHARS \
"`1234567890-=~!@#$%^&*()_+qwertyuiop[]\\QWERTYUIOP"\
"{}|asdfghjkl;'ASDFGHJKL:\"zxcvbnm,./ZXCVBNM<>?"
#define HOW_MANY(x__,y__) (((x__) + (y__) - 1) / (y__))
static void add_update_timeout (MooTerm *term);
static void
font_calculate (MooTermFont *font)
{
PangoRectangle logical;
PangoLayout *layout;
PangoLayoutIter *iter;
g_assert (font->ctx != NULL);
layout = pango_layout_new (font->ctx);
pango_layout_set_text (layout, CHARS, strlen(CHARS));
pango_layout_get_extents (layout, NULL, &logical);
font->width = HOW_MANY (logical.width, strlen (CHARS));
font->width = PANGO_PIXELS ((int)font->width);
iter = pango_layout_get_iter (layout);
font->height = PANGO_PIXELS (logical.height);
font->ascent = PANGO_PIXELS (pango_layout_iter_get_baseline (iter));
pango_layout_iter_free (iter);
g_object_unref (layout);
}
static MooTermFont*
moo_term_font_new (PangoContext *ctx)
{
MooTermFont *font = g_new0 (MooTermFont, 1);
font->ctx = ctx;
g_object_ref (ctx);
font_calculate (font);
return font;
}
static void
moo_term_update_font (MooTerm *term)
{
PangoFontDescription *font;
GtkWidget *widget = GTK_WIDGET (term);
_moo_term_init_font_stuff (term);
font = widget->style->font_desc;
if (!pango_font_description_get_size (font))
{
g_return_if_reached ();
}
pango_context_set_font_description (term->priv->font->ctx, font);
font_calculate (term->priv->font);
if (GTK_WIDGET_REALIZED (term))
_moo_term_update_size (term, FALSE);
}
void
moo_term_set_font_from_string (MooTerm *term,
const char *fontname)
{
PangoFontDescription *font;
g_return_if_fail (MOO_IS_TERM (term));
g_return_if_fail (fontname != NULL);
font = pango_font_description_from_string (fontname);
g_return_if_fail (font != NULL);
gtk_widget_modify_font (GTK_WIDGET (term), font);
pango_font_description_free (font);
}
void
_moo_term_init_font_stuff (MooTerm *term)
{
PangoContext *ctx;
if (term->priv->font)
_moo_term_font_free (term->priv->font);
if (term->priv->layout)
g_object_unref (term->priv->layout);
gtk_widget_ensure_style (GTK_WIDGET (term));
ctx = gtk_widget_create_pango_context (GTK_WIDGET (term));
g_return_if_fail (ctx != NULL);
term->priv->font = moo_term_font_new (ctx);
term->priv->layout = pango_layout_new (ctx);
g_object_unref (ctx);
gtk_widget_set_size_request (GTK_WIDGET (term),
term->priv->font->width * MIN_TERMINAL_WIDTH,
term->priv->font->height * MIN_TERMINAL_HEIGHT);
}
void
_moo_term_font_free (MooTermFont *font)
{
if (font)
{
g_object_unref (font->ctx);
g_free (font);
}
}
void
_moo_term_invalidate (MooTerm *term)
{
GdkRectangle rec = {0, 0, 0, 0};
rec.width = term->priv->width;
rec.height = term->priv->height;
_moo_term_invalidate_screen_rect (term, &rec);
}
void
_moo_term_invalidate_screen_rect (MooTerm *term,
GdkRectangle *rect)
{
if (GTK_WIDGET_REALIZED (term))
{
GdkRectangle r;
r.x = rect->x * CHAR_WIDTH(term);
r.y = rect->y * CHAR_HEIGHT(term);
r.width = rect->width * CHAR_WIDTH(term);
r.height = rect->height * CHAR_HEIGHT(term);
gdk_window_invalidate_rect (GTK_WIDGET(term)->window,
&r, FALSE);
}
}
void
moo_term_set_colors (MooTerm *term,
GdkColor *colors,
guint n_colors)
{
guint i;
g_return_if_fail (MOO_IS_TERM (term));
g_return_if_fail (colors != NULL);
g_return_if_fail (n_colors == MOO_TERM_NUM_COLORS ||
n_colors == 2 * MOO_TERM_NUM_COLORS);
for (i = 0; i < MOO_TERM_NUM_COLORS; ++i)
{
term->priv->palette[i] = colors[i];
if (n_colors > MOO_TERM_NUM_COLORS)
term->priv->palette[i + MOO_TERM_NUM_COLORS] = colors[i + MOO_TERM_NUM_COLORS];
else
term->priv->palette[ + MOO_TERM_NUM_COLORS] = colors[i];
}
if (GTK_WIDGET_REALIZED (term))
_moo_term_update_palette (term);
}
static void
moo_term_update_text_colors (MooTerm *term)
{
GtkWidget *widget = GTK_WIDGET (term);
GdkColormap *colormap;
GdkGCValues vals;
if (!GTK_WIDGET_REALIZED (widget))
return;
colormap = gdk_colormap_get_system ();
g_return_if_fail (colormap != NULL);
gdk_colormap_alloc_color (colormap, &term->priv->fg_color[0],
TRUE, TRUE);
gdk_colormap_alloc_color (colormap, &term->priv->fg_color[1],
TRUE, TRUE);
gdk_colormap_alloc_color (colormap, &term->priv->bg_color,
TRUE, TRUE);
if (term->priv->fg[0])
{
g_object_unref (term->priv->fg[0]);
g_object_unref (term->priv->fg[1]);
g_object_unref (term->priv->bg);
}
term->priv->fg[0] =
gdk_gc_new_with_values (widget->window, &vals, 0);
term->priv->fg[1] =
gdk_gc_new_with_values (widget->window, &vals, 0);
term->priv->bg =
gdk_gc_new_with_values (widget->window, &vals, 0);
gdk_gc_set_foreground (term->priv->fg[0], &term->priv->fg_color[0]);
gdk_gc_set_foreground (term->priv->fg[1], &term->priv->fg_color[1]);
gdk_gc_set_foreground (term->priv->bg, &term->priv->bg_color);
gdk_window_set_background (widget->window, &term->priv->bg_color);
_moo_term_invalidate (term);
}
static void
moo_term_set_text_colors (MooTerm *term,
GdkColor *fg,
GdkColor *fg_bold,
GdkColor *bg)
{
g_return_if_fail (MOO_IS_TERM (term));
if (fg)
term->priv->fg_color[0] = *fg;
if (fg_bold)
term->priv->fg_color[1] = *fg_bold;
else if (fg)
term->priv->fg_color[1] = *fg;
if (bg)
term->priv->bg_color = *bg;
if (GTK_WIDGET_REALIZED (term))
moo_term_update_text_colors (term);
}
void
_moo_term_init_palette (MooTerm *term)
{
int i;
GdkColor colors[2 * MOO_TERM_NUM_COLORS];
GtkWidget *widget = GTK_WIDGET (term);
gtk_widget_ensure_style (widget);
moo_term_set_text_colors (term,
&widget->style->text[GTK_STATE_NORMAL],
&widget->style->text[GTK_STATE_NORMAL],
&widget->style->base[GTK_STATE_NORMAL]);
for (i = 0; i < 2; ++i)
{
gdk_color_parse ("black", &colors[8 * i + MOO_TERM_BLACK]);
gdk_color_parse ("red", &colors[8 * i + MOO_TERM_RED]);
gdk_color_parse ("green", &colors[8 * i + MOO_TERM_GREEN]);
gdk_color_parse ("yellow", &colors[8 * i + MOO_TERM_YELLOW]);
gdk_color_parse ("blue", &colors[8 * i + MOO_TERM_BLUE]);
gdk_color_parse ("magenta", &colors[8 * i + MOO_TERM_MAGENTA]);
gdk_color_parse ("cyan", &colors[8 * i + MOO_TERM_CYAN]);
gdk_color_parse ("white", &colors[8 * i + MOO_TERM_WHITE]);
}
moo_term_set_colors (term, colors, 2 * MOO_TERM_NUM_COLORS);
}
void
_moo_term_update_palette (MooTerm *term)
{
int i, j;
GtkWidget *widget = GTK_WIDGET (term);
GdkColormap *colormap;
GdkGCValues vals;
g_return_if_fail (GTK_WIDGET_REALIZED (widget));
colormap = gdk_colormap_get_system ();
g_return_if_fail (colormap != NULL);
gdk_colormap_alloc_color (colormap, &term->priv->fg_color[0],
TRUE, TRUE);
gdk_colormap_alloc_color (colormap, &term->priv->fg_color[1],
TRUE, TRUE);
gdk_colormap_alloc_color (colormap, &term->priv->bg_color,
TRUE, TRUE);
if (term->priv->color[0])
for (i = 0; i < 2 * MOO_TERM_NUM_COLORS; ++i)
g_object_unref (term->priv->color[i]);
for (i = 0; i < MOO_TERM_NUM_COLORS; ++i)
for (j = 0; j < 2; ++j)
gdk_colormap_alloc_color (colormap,
&term->priv->palette[8*j + i],
TRUE, TRUE);
for (i = 0; i < MOO_TERM_NUM_COLORS; ++i)
for (j = 0; j < 2; ++j)
{
vals.foreground = term->priv->palette[8*j + i];
term->priv->color[8*j + i] =
gdk_gc_new_with_values (widget->window,
&vals,
GDK_GC_FOREGROUND);
}
moo_term_update_text_colors (term);
}
void
_moo_term_style_set (GtkWidget *widget,
G_GNUC_UNUSED GtkStyle *previous_style)
{
MooTerm *term = MOO_TERM (widget);
g_return_if_fail (widget->style != NULL);
moo_term_set_text_colors (term,
&widget->style->text[GTK_STATE_NORMAL],
&widget->style->text[GTK_STATE_NORMAL],
&widget->style->base[GTK_STATE_NORMAL]);
moo_term_update_text_colors (term);
moo_term_update_font (term);
}
static void
invalidate_screen_cell (MooTerm *term,
guint row,
guint column)
{
int scrollback, top_line;
GdkRectangle small_rect = {0, 0, 1, 1};
scrollback = buf_scrollback (term->priv->buffer);
top_line = term_top_line (term);
small_rect.x = column;
small_rect.y = row + scrollback - top_line;
_moo_term_invalidate_screen_rect (term, &small_rect);
}
void
_moo_term_cursor_moved (MooTerm *term,
MooTermBuffer *buf)
{
if (buf == term->priv->buffer)
{
term->priv->cursor_row = buf_cursor_row (buf);
term->priv->cursor_col = buf_cursor_col_display (buf);
add_update_timeout (term);
}
}
/* absolute row */
static void term_draw_range (MooTerm *term,
GdkDrawable *drawable,
guint row,
guint start,
guint len);
inline static void
rect_window_to_screen (MooTerm *term,
GdkRectangle *window,
GdkRectangle *screen)
{
int cw = CHAR_WIDTH (term);
int ch = CHAR_HEIGHT (term);
screen->width = (window->x + window->width -1)/cw - window->x/cw + 1;
screen->x = window->x/cw;
screen->height = (window->y + window->height -1)/ch - window->y/ch + 1;
screen->y = window->y/ch;
}
static void
moo_term_draw (MooTerm *term,
GdkDrawable *drawable,
GdkRegion *region)
{
GdkRectangle *rects = NULL;
int n_rects;
int i, j;
int top_line = term_top_line (term);
int width = term->priv->width;
int height = term->priv->height;
GdkRectangle clip = {0, 0, 0, 0};
g_return_if_fail (region != NULL);
clip.width = width;
clip.height = height;
gdk_region_get_rectangles (region, &rects, &n_rects);
for (i = 0; i < n_rects; ++i)
{
GdkRectangle *r = &rects[i];
rect_window_to_screen (term, r, r);
if (gdk_rectangle_intersect (r, &clip, r))
{
for (j = 0; j < r->height; ++j)
term_draw_range (term, drawable,
top_line + r->y + j,
r->x, r->width);
}
}
g_free (rects);
}
inline static void
rect_screen_to_window (MooTerm *term,
GdkRectangle *screen,
GdkRectangle *window)
{
int cw = CHAR_WIDTH (term);
int ch = CHAR_HEIGHT (term);
window->x = screen->x * cw;
window->width = screen->width * cw;
window->y = screen->y * ch;
window->height = screen->height * ch;
}
static GdkRegion *
region_rectangle (int x,
int y,
int width,
int height)
{
GdkRectangle rect;
rect.x = x;
rect.y = y;
rect.width = width;
rect.height = height;
return gdk_region_rectangle (&rect);
}
static void
region_union_with_rect (GdkRegion **region,
int x,
int y,
int width,
int height)
{
GdkRectangle rect;
rect.x = x;
rect.y = y;
rect.width = width;
rect.height = height;
if (*region)
gdk_region_union_with_rect (*region, &rect);
else
*region = gdk_region_rectangle (&rect);
}
gboolean
_moo_term_expose_event (GtkWidget *widget,
GdkEventExpose *event)
{
GdkRegion *text_region;
MooTerm *term = MOO_TERM (widget);
if (widget->window != event->window)
return FALSE;
g_assert (term_top_line (term) <= buf_scrollback (term->priv->buffer));
text_region = region_rectangle (0, 0, PIXEL_WIDTH (term), PIXEL_HEIGHT (term));
gdk_region_intersect (text_region, event->region);
if (!gdk_region_empty (text_region))
moo_term_draw (term, event->window, text_region);
gdk_region_destroy (text_region);
return FALSE;
}
/****************************************************************************/
/* Drawing
*/
static void term_draw_range_simple (MooTerm *term,
GdkDrawable *drawable,
guint abs_row,
guint start,
guint len,
int selected);
static void term_draw_cells (MooTerm *term,
GdkDrawable *drawable,
guint abs_row,
guint start,
guint len,
MooTermTextAttr attr,
int selected);
static void term_draw_cursor (MooTerm *term,
GdkDrawable *drawable);
static void
term_draw_range (MooTerm *term,
GdkDrawable *drawable,
guint abs_row,
guint start,
guint len)
{
int selected;
guint first = start;
guint last = start + len;
g_return_if_fail (len != 0);
g_assert (start + len <= term->priv->width);
g_assert (abs_row < buf_total_height (term->priv->buffer));
if (term->priv->cursor_visible && term->priv->blink_cursor_visible &&
term->priv->cursor_row + buf_scrollback (term->priv->buffer) == abs_row)
{
guint cursor = buf_cursor_col_display (term->priv->buffer);
if (cursor >= start && cursor < start + len)
{
if (cursor > start)
term_draw_range (term, drawable, abs_row,
start, cursor - start);
term_draw_cursor (term, drawable);
if (cursor < start + len - 1)
term_draw_range (term, drawable, abs_row,
cursor + 1, start + len - 1 - cursor);
return;
}
}
selected = _moo_term_row_selected (term, abs_row);
switch (selected)
{
case FULL_SELECTED:
case NOT_SELECTED:
term_draw_range_simple (term, drawable, abs_row, first, len, selected);
break;
case PART_SELECTED:
{
guint l_row, l_col, r_row, r_col;
_moo_term_get_selected_cells (term, &l_row, &l_col,
&r_row, &r_col);
if (l_row == r_row)
{
g_assert (abs_row == l_row);
if (r_col <= first || last <= l_col)
{
term_draw_range_simple (term, drawable, abs_row,
first, len, FALSE);
}
else if (l_col <= first && last <= r_col)
{
term_draw_range_simple (term, drawable, abs_row,
first, len, TRUE);
}
else if (first < l_col)
{
term_draw_range_simple (term, drawable, abs_row,
first, l_col - first, FALSE);
term_draw_range_simple (term, drawable, abs_row, l_col,
MIN (last, r_col) - l_col, TRUE);
if (r_col < last)
term_draw_range_simple (term, drawable, abs_row,
r_col, last - r_col, FALSE);
}
else
{
term_draw_range_simple (term, drawable, abs_row, first,
MIN (last, r_col) - first, TRUE);
if (r_col < last)
term_draw_range_simple (term, drawable, abs_row,
r_col, last - r_col, FALSE);
}
}
else if (l_row == abs_row)
{
if (last <= l_col)
{
term_draw_range_simple (term, drawable, abs_row,
first, len, FALSE);
}
else if (l_col <= first)
{
term_draw_range_simple (term, drawable, abs_row,
first, len, TRUE);
}
else
{
term_draw_range_simple (term, drawable, abs_row,
first, l_col - first, FALSE);
term_draw_range_simple (term, drawable, abs_row,
l_col, last - l_col, TRUE);
}
}
else
{
g_assert (abs_row == r_row);
if (last <= r_col)
{
term_draw_range_simple (term, drawable, abs_row,
first, len, TRUE);
}
else if (r_col <= first)
{
term_draw_range_simple (term, drawable, abs_row,
first, len, FALSE);
}
else {
term_draw_range_simple (term, drawable, abs_row,
first, r_col - first, TRUE);
term_draw_range_simple (term, drawable, abs_row,
r_col, last - r_col, FALSE);
}
}
break;
}
default:
g_assert_not_reached ();
}
}
static void
term_draw_range_simple (MooTerm *term,
GdkDrawable *drawable,
guint abs_row,
guint start,
guint len,
gboolean selected)
{
MooTermLine *line = buf_line (term->priv->buffer, abs_row);
int y = (abs_row - term_top_line (term)) * CHAR_HEIGHT(term);
GdkGC *bg = NULL;
guint invert;
g_assert (selected == 0 || selected == 1);
invert = (selected ? 1 : 0) + (term->priv->colors_inverted ? 1 : 0);
invert %= 2;
if (invert)
bg = term->priv->fg[COLOR_NORMAL];
if (start >= _moo_term_line_width (line))
{
if (bg)
gdk_draw_rectangle (drawable, bg, TRUE,
start * CHAR_WIDTH(term),
y,
len * CHAR_WIDTH(term),
CHAR_HEIGHT(term));
return;
}
else if (start + len > _moo_term_line_width (line))
{
if (bg)
gdk_draw_rectangle (drawable, bg, TRUE,
_moo_term_line_width (line) * CHAR_WIDTH(term),
y,
(start + len - _moo_term_line_width (line)) * CHAR_WIDTH(term),
CHAR_HEIGHT(term));
len = _moo_term_line_width (line) - start;
}
g_assert (start + len <= _moo_term_line_width (line));
while (len)
{
guint n = 1;
MooTermTextAttr attr = _moo_term_line_get_attr (line, start);
while (n < len)
if (MOO_TERM_TEXT_ATTR_EQUAL (attr, _moo_term_line_get_attr (line, start + n)))
n++;
else
break;
term_draw_cells (term, drawable, abs_row, start, n, attr, selected);
len -= n;
start += n;
}
}
static void
term_draw_cells (MooTerm *term,
GdkDrawable *drawable,
guint abs_row,
guint start,
guint len,
MooTermTextAttr attr,
gboolean selected)
{
static char buf[8 * MAX_TERMINAL_WIDTH];
guint buf_len;
GdkGC *fg = NULL;
GdkGC *bg = NULL;
guint bold;
guint invert;
PangoLayout *layout = term->priv->layout;
MooTermLine *line = buf_line (term->priv->buffer, abs_row);
g_assert (len != 0);
g_assert (start + len <= _moo_term_line_width (line));
buf_len = _moo_term_line_get_chars (line, buf, start, len);
g_return_if_fail (buf_len != 0);
pango_layout_set_text (term->priv->layout, buf, buf_len);
bold = (attr.mask & MOO_TERM_TEXT_BOLD) ? COLOR_BOLD : COLOR_NORMAL;
if (attr.mask & MOO_TERM_TEXT_FOREGROUND)
{
g_return_if_fail (attr.foreground < MOO_TERM_NUM_COLORS);
fg = term->priv->color[8 * bold + attr.foreground];
}
else
{
fg = term->priv->fg[bold];
}
if (attr.mask & MOO_TERM_TEXT_BACKGROUND)
{
g_return_if_fail (attr.foreground < MOO_TERM_NUM_COLORS);
bg = term->priv->color[8 * bold + attr.background];
}
else
{
bg = term->priv->bg;
}
invert = (selected ? 1 : 0) + (term->priv->colors_inverted ? 1 : 0) +
(attr.mask & MOO_TERM_TEXT_REVERSE ? 1 : 0);
invert %= 2;
if (invert)
{
GdkGC *tmp = fg;
fg = bg;
bg = tmp;
}
if (bg == term->priv->bg)
bg = NULL;
if (bg)
gdk_draw_rectangle (drawable, bg, TRUE,
start * CHAR_WIDTH(term),
(abs_row - term_top_line (term)) * CHAR_HEIGHT(term),
len * CHAR_WIDTH(term),
CHAR_HEIGHT(term));
if ((attr.mask & MOO_TERM_TEXT_BOLD) && term->priv->settings.bold_pango)
{
PangoAttrList *list = pango_attr_list_new ();
PangoAttribute *a = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
a->start_index = 0;
a->end_index = buf_len;
pango_attr_list_insert (list, a);
pango_layout_set_attributes (layout, list);
pango_attr_list_unref (list);
}
gdk_draw_layout (drawable,
fg,
start * CHAR_WIDTH(term),
(abs_row - term_top_line (term)) * CHAR_HEIGHT(term),
term->priv->layout);
if ((attr.mask & MOO_TERM_TEXT_BOLD) && term->priv->settings.bold_pango)
pango_layout_set_attributes (layout, NULL);
if ((attr.mask & MOO_TERM_TEXT_BOLD) && term->priv->settings.bold_offset)
gdk_draw_layout (drawable,
fg,
start * CHAR_WIDTH(term) + 1,
(abs_row - term_top_line (term)) * CHAR_HEIGHT(term),
term->priv->layout);
if (attr.mask & MOO_TERM_TEXT_UNDERLINE)
gdk_draw_line (drawable,
fg,
start * CHAR_WIDTH(term),
(abs_row - term_top_line (term)) * CHAR_HEIGHT(term) + CHAR_ASCENT(term) + 1,
(start + len) * CHAR_WIDTH(term) + 1,
(abs_row - term_top_line (term)) * CHAR_HEIGHT(term) + CHAR_ASCENT(term) + 1);
}
static void
term_draw_cursor (MooTerm *term,
GdkDrawable *drawable)
{
guint scrollback = buf_scrollback (term->priv->buffer);
guint abs_row = term->priv->cursor_row + scrollback;
guint column = term->priv->cursor_col;
MooTermLine *line = buf_line (term->priv->buffer, abs_row);
if (_moo_term_line_width (line) > column)
{
term_draw_cells (term, drawable, abs_row, column, 1,
_moo_term_line_get_attr (line, column),
!_moo_term_cell_selected (term, abs_row, column));
return;
}
else
{
GdkGC *color;
guint invert;
invert = 1 + (_moo_term_cell_selected (term, abs_row, column) ? 1 : 0) +
(term->priv->colors_inverted ? 1 : 0);
invert %= 2;
if (invert)
color = term->priv->fg[COLOR_NORMAL];
else
color = term->priv->bg;
gdk_draw_rectangle (drawable,
color,
TRUE,
column * CHAR_WIDTH(term),
(abs_row - term_top_line (term)) * CHAR_HEIGHT(term),
CHAR_WIDTH(term),
CHAR_HEIGHT(term));
}
}
static void
add_buffer_changed (MooTerm *term)
{
GdkRegion *changed;
guint top_line, scrollback;
int height;
MooTermBuffer *buf;
if (!GTK_WIDGET_DRAWABLE (term))
return;
buf = term->priv->buffer;
changed = buf->priv->changed;
if (!changed || gdk_region_empty (changed))
return;
buf->priv->changed = NULL;
top_line = term_top_line (term);
scrollback = buf_scrollback (buf);
height = term->priv->height;
g_assert (top_line <= scrollback);
if (top_line != scrollback)
{
GdkRegion *tmp;
gdk_region_offset (changed, 0, scrollback - top_line);
tmp = region_rectangle (0, 0, term->priv->width, height);
gdk_region_intersect (changed, tmp);
gdk_region_destroy (tmp);
}
if (gdk_region_empty (changed))
{
gdk_region_destroy (changed);
return;
}
if (term->priv->changed)
{
gdk_region_union (term->priv->changed, changed);
gdk_region_destroy (changed);
}
else
{
term->priv->changed = changed;
}
}
static gboolean
invalidate_window (MooTerm *term)
{
GdkRegion *region, *changed, *clip_region;
GdkRectangle *rectangles;
int n_rectangles, i;
int top_line = term_top_line (term);
int scrollback = buf_scrollback (term->priv->buffer);
GdkWindow *window = GTK_WIDGET(term)->window;
gboolean need_redraw = FALSE;
if (!GTK_WIDGET_DRAWABLE (term))
return FALSE;
add_buffer_changed (term);
if (!term->priv->changed &&
term->priv->cursor_col == term->priv->cursor_col_old &&
term->priv->cursor_row == term->priv->cursor_row_old)
goto out;
if (term->priv->changed)
{
changed = term->priv->changed;
term->priv->changed = NULL;
}
else
{
changed = gdk_region_new ();
}
if (term->priv->cursor_col != term->priv->cursor_col_old ||
term->priv->cursor_row != term->priv->cursor_row_old)
{
int row;
row = scrollback + term->priv->cursor_row - top_line;
if (term->priv->cursor_col < term->priv->width &&
row >= 0 && row < (int) term->priv->height)
{
region_union_with_rect (&changed,
term->priv->cursor_col,
row, 1, 1);
}
row = scrollback + term->priv->cursor_row_old - top_line;
if (term->priv->cursor_col_old < term->priv->width &&
row >= 0 && row < (int) term->priv->height)
{
region_union_with_rect (&changed,
term->priv->cursor_col_old,
row, 1, 1);
}
}
clip_region = region_rectangle (0, 0, term->priv->width, term->priv->height);
gdk_region_intersect (changed, clip_region);
gdk_region_destroy (clip_region);
if (gdk_region_empty (changed))
{
gdk_region_destroy (changed);
goto out;
}
gdk_region_get_rectangles (changed, &rectangles, &n_rectangles);
g_return_val_if_fail (n_rectangles > 0, TRUE);
for (i = 0; i < n_rectangles; ++i)
{
rect_screen_to_window (term, &rectangles[i], &rectangles[i]);
}
region = gdk_region_rectangle (&rectangles[0]);
for (i = 1; i < n_rectangles; ++i)
gdk_region_union_with_rect (region, &rectangles[i]);
gdk_window_invalidate_region (window, region, FALSE);
need_redraw = TRUE;
g_free (rectangles);
gdk_region_destroy (region);
gdk_region_destroy (changed);
out:
term->priv->cursor_col_old = term->priv->cursor_col;
term->priv->cursor_row_old = term->priv->cursor_row;
return need_redraw;
}
static gboolean
update_delay_timeout (MooTerm *term)
{
/* We only stop the timer if no update request was received in this
* past cycle.
*/
if (invalidate_window (term))
return TRUE;
term->priv->update_timer = 0;
return FALSE;
}
static gboolean
update_timeout (MooTerm *term)
{
invalidate_window (term);
/* Set a timer such that we do not invalidate for a while. */
/* This limits the number of times we draw to ~40fps. */
term->priv->update_timer =
_moo_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
UPDATE_REPEAT_TIMEOUT,
(GSourceFunc) update_delay_timeout,
term, NULL);
return FALSE;
}
static void
add_update_timeout (MooTerm *term)
{
if (!term->priv->update_timer)
term->priv->update_timer =
_moo_timeout_add_full (G_PRIORITY_DEFAULT_IDLE,
UPDATE_TIMEOUT,
(GSourceFunc) update_timeout,
term, NULL);
}
void
_moo_term_buf_content_changed (MooTerm *term,
MooTermBuffer *buf)
{
if (GTK_WIDGET_DRAWABLE (term) &&
buf == term->priv->buffer &&
buf->priv->changed &&
!gdk_region_empty (buf->priv->changed))
{
add_update_timeout (term);
}
}
void
_moo_term_buffer_scrolled (MooTermBuffer *buf,
guint lines,
MooTerm *term)
{
if (lines && term->priv->buffer == buf && GTK_WIDGET_DRAWABLE (term))
{
region_union_with_rect (&term->priv->changed, 0, 0,
term->priv->width,
term->priv->height);
add_update_timeout (term);
}
}
void
_moo_term_invert_colors (MooTerm *term,
gboolean invert)
{
if (invert != term->priv->colors_inverted)
{
_moo_term_invalidate (term);
term->priv->colors_inverted = invert;
}
}
void
_moo_term_set_cursor_visible (MooTerm *term,
gboolean visible)
{
term->priv->cursor_visible = visible;
invalidate_screen_cell (term,
term->priv->cursor_row,
term->priv->cursor_col);
}
static gboolean
blink (MooTerm *term)
{
term->priv->blink_cursor_visible =
!term->priv->blink_cursor_visible;
invalidate_screen_cell (term,
term->priv->cursor_row,
term->priv->cursor_col);
return TRUE;
}
static void
start_cursor_blinking (MooTerm *term)
{
if (!term->priv->cursor_blink_timeout_id && term->priv->cursor_blinks)
term->priv->cursor_blink_timeout_id =
_moo_timeout_add (term->priv->cursor_blink_time,
(GSourceFunc) blink,
term);
}
static void
stop_cursor_blinking (MooTerm *term)
{
if (term->priv->cursor_blink_timeout_id)
{
g_source_remove (term->priv->cursor_blink_timeout_id);
term->priv->cursor_blink_timeout_id = 0;
term->priv->blink_cursor_visible = TRUE;
invalidate_screen_cell (term,
term->priv->cursor_row,
term->priv->cursor_col);
}
}
void
_moo_term_pause_cursor_blinking (MooTerm *term)
{
if (term->priv->cursor_blinks)
{
stop_cursor_blinking (term);
start_cursor_blinking (term);
}
}
void
_moo_term_set_cursor_blinks (MooTerm *term,
gboolean blinks)
{
term->priv->cursor_blinks = blinks;
if (blinks)
start_cursor_blinking (term);
else
stop_cursor_blinking (term);
g_object_notify (G_OBJECT (term), "cursor-blinks");
}
void
moo_term_set_cursor_blink_time (MooTerm *term,
guint ms)
{
if (ms)
{
term->priv->cursor_blink_time = ms;
_moo_term_set_cursor_blinks (term, TRUE);
_moo_term_pause_cursor_blinking (term);
}
else
{
_moo_term_set_cursor_blinks (term, FALSE);
}
}