309 lines
8.2 KiB
C
309 lines
8.2 KiB
C
/*
|
|
* L3afpad - GTK+ based simple text editor
|
|
* Copyright (C) 2004-2005 Tarot Osuji
|
|
* Copyright (C) 2012 Yoo, Taik-Yon <jaagar AT gmail DOT com>
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include <gtk/gtk.h>
|
|
#include "linenum.h"
|
|
|
|
#define DV(x)
|
|
|
|
static gint min_number_window_width;
|
|
static gboolean line_number_visible = FALSE;
|
|
#define margin 5
|
|
#define submargin 2
|
|
|
|
static gint calculate_min_number_window_width(GtkWidget *widget)
|
|
{
|
|
PangoLayout *layout;
|
|
gchar *str;
|
|
gint width, col = 4;
|
|
|
|
str = g_strnfill(col, 0x20);
|
|
layout = gtk_widget_create_pango_layout(widget, str);
|
|
g_free (str);
|
|
|
|
pango_layout_get_pixel_size(layout, &width, NULL);
|
|
g_object_unref(G_OBJECT(layout));
|
|
|
|
return width;
|
|
}
|
|
|
|
/* taken from gedit and gtksourceview */
|
|
/* originated from gtk+/tests/testtext.c */
|
|
|
|
static void
|
|
get_lines (GtkTextView *text_view,
|
|
gint y1,
|
|
gint y2,
|
|
GArray *buffer_coords,
|
|
GArray *numbers,
|
|
gint *countp)
|
|
{
|
|
GtkTextIter iter;
|
|
gint count;
|
|
gint last_line_num;
|
|
|
|
g_array_set_size (buffer_coords, 0);
|
|
g_array_set_size (numbers, 0);
|
|
|
|
/* Get iter at first y */
|
|
gtk_text_view_get_line_at_y (text_view, &iter, y1, NULL);
|
|
|
|
/* For each iter, get its location and add it to the arrays.
|
|
* Stop when we pass y2
|
|
*/
|
|
count = 0;
|
|
|
|
while (!gtk_text_iter_is_end (&iter))
|
|
{
|
|
gint y, height;
|
|
|
|
gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
|
|
|
|
g_array_append_val (buffer_coords, y);
|
|
last_line_num = gtk_text_iter_get_line (&iter);
|
|
g_array_append_val (numbers, last_line_num);
|
|
|
|
++count;
|
|
|
|
if ((y + height) >= y2)
|
|
break;
|
|
|
|
gtk_text_iter_forward_line (&iter);
|
|
}
|
|
|
|
if (gtk_text_iter_is_end (&iter))
|
|
{
|
|
gint y, height;
|
|
gint line_num;
|
|
|
|
gtk_text_view_get_line_yrange (text_view, &iter, &y, &height);
|
|
|
|
line_num = gtk_text_iter_get_line (&iter);
|
|
|
|
if (line_num != last_line_num) {
|
|
g_array_append_val (buffer_coords, y);
|
|
g_array_append_val (numbers, line_num);
|
|
++count;
|
|
}
|
|
}
|
|
|
|
*countp = count;
|
|
}
|
|
|
|
static inline PangoAttribute *
|
|
line_numbers_foreground_attr_new(GtkWidget *widget)
|
|
{
|
|
GtkStyleContext *context;
|
|
GdkRGBA rgb;
|
|
|
|
context = gtk_widget_get_style_context(widget);
|
|
gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, &rgb);
|
|
|
|
return pango_attr_foreground_new((guint16)(rgb.red * 65535),
|
|
(guint16)(rgb.green * 65535),
|
|
(guint16)(rgb.blue * 65535));
|
|
}
|
|
|
|
static gint
|
|
line_numbers_expose (GtkWidget *widget, cairo_t *event)
|
|
{
|
|
GtkTextView *text_view;
|
|
PangoLayout *layout;
|
|
PangoAttrList *alist;
|
|
PangoAttribute *attr;
|
|
GArray *numbers;
|
|
GArray *pixels;
|
|
gint y1, y2;
|
|
gint count;
|
|
gint layout_width;
|
|
gint justify_width = 0;
|
|
gint i;
|
|
gchar str [8]; /* we don't expect more than ten million lines */
|
|
|
|
cairo_rectangle_list_t *clips;
|
|
|
|
if (line_number_visible) {
|
|
|
|
text_view = GTK_TEXT_VIEW (widget);
|
|
|
|
#if 0
|
|
GdkWindow *win;
|
|
win = gtk_text_view_get_window (text_view,
|
|
GTK_TEXT_WINDOW_LEFT);
|
|
if (event->window != win)
|
|
return FALSE;
|
|
|
|
y1 = event->area.y;
|
|
y2 = y1 + event->area.height;
|
|
#endif
|
|
|
|
/* get origin of the clipping area. */
|
|
clips = cairo_copy_clip_rectangle_list(event);
|
|
|
|
i = (gint)clips->rectangles[0].x;
|
|
y1 = (gint)clips->rectangles[0].y;
|
|
|
|
cairo_rectangle_list_destroy(clips);
|
|
|
|
/* skip drawing if not in the line number area. */
|
|
if (i >= gtk_text_view_get_border_window_size(text_view, GTK_TEXT_WINDOW_LEFT))
|
|
return FALSE;
|
|
|
|
gtk_text_view_window_to_buffer_coords (text_view,
|
|
GTK_TEXT_WINDOW_LEFT,
|
|
0,
|
|
y1,
|
|
NULL,
|
|
&y1);
|
|
gtk_text_view_window_to_buffer_coords (text_view,
|
|
GTK_TEXT_WINDOW_LEFT,
|
|
0,
|
|
gtk_widget_get_allocated_height(widget),
|
|
NULL,
|
|
&y2);
|
|
|
|
numbers = g_array_new (FALSE, FALSE, sizeof (gint));
|
|
pixels = g_array_new (FALSE, FALSE, sizeof (gint));
|
|
|
|
get_lines (text_view,
|
|
y1,
|
|
y2,
|
|
pixels,
|
|
numbers,
|
|
&count);
|
|
|
|
/* a zero-lined document should display a "1"; we don't need to worry about
|
|
scrolling effects of the text widget in this special case */
|
|
|
|
if (count == 0) {
|
|
gint y = 0;
|
|
gint n = 0;
|
|
count = 1;
|
|
g_array_append_val (pixels, y);
|
|
g_array_append_val (numbers, n);
|
|
}
|
|
|
|
DV({g_print("Painting line numbers %d - %d\n",
|
|
g_array_index(numbers, gint, 0),
|
|
g_array_index(numbers, gint, count - 1)); });
|
|
|
|
layout = gtk_widget_create_pango_layout (widget, "");
|
|
|
|
g_snprintf (str, sizeof (str),
|
|
"%d", MAX (99, gtk_text_buffer_get_line_count(gtk_text_view_get_buffer(text_view))));
|
|
pango_layout_set_text (layout, str, -1);
|
|
|
|
pango_layout_get_pixel_size (layout, &layout_width, NULL);
|
|
|
|
min_number_window_width = calculate_min_number_window_width(widget);
|
|
if (layout_width > min_number_window_width)
|
|
gtk_text_view_set_border_window_size (text_view,
|
|
GTK_TEXT_WINDOW_LEFT, layout_width + margin + submargin);
|
|
else {
|
|
gtk_text_view_set_border_window_size (text_view,
|
|
GTK_TEXT_WINDOW_LEFT, min_number_window_width + margin + submargin);
|
|
justify_width = min_number_window_width - layout_width;
|
|
}
|
|
|
|
pango_layout_set_width (layout, layout_width);
|
|
pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
|
|
|
|
alist = pango_attr_list_new();
|
|
/* TODO: should change line number color by conffile */
|
|
attr = line_numbers_foreground_attr_new(widget);
|
|
attr->start_index = 0;
|
|
attr->end_index = G_MAXUINT;
|
|
pango_attr_list_insert(alist, attr);
|
|
pango_layout_set_attributes(layout, alist);
|
|
pango_attr_list_unref(alist);
|
|
|
|
/* Draw fully internationalized numbers! */
|
|
|
|
for (i = 0; i < count; i++) {
|
|
gint pos;
|
|
|
|
gtk_text_view_buffer_to_window_coords (text_view,
|
|
GTK_TEXT_WINDOW_LEFT,
|
|
0,
|
|
g_array_index (pixels, gint, i),
|
|
NULL,
|
|
&pos);
|
|
g_snprintf (str, sizeof (str),
|
|
"%d", g_array_index (numbers, gint, i) + 1);
|
|
|
|
pango_layout_set_text (layout, str, -1);
|
|
|
|
gtk_render_layout (gtk_widget_get_style_context(widget),
|
|
event,
|
|
layout_width + justify_width + margin / 2 + 1,
|
|
pos,
|
|
layout);
|
|
}
|
|
|
|
g_array_free (pixels, TRUE);
|
|
g_array_free (numbers, TRUE);
|
|
g_object_unref (G_OBJECT (layout));
|
|
}
|
|
|
|
#if 0
|
|
cairo_t *gc;
|
|
gint height;
|
|
gc = gdk_gc_new(event->window);
|
|
gdk_gc_set_foreground(gc, widget->style->base);
|
|
gdk_window_get_geometry(event->window, NULL, NULL, NULL, &height, NULL);
|
|
gdk_draw_rectangle(event->window, gc, TRUE,
|
|
line_number_visible ?
|
|
layout_width + justify_width + margin : 0,
|
|
0, submargin,
|
|
height);
|
|
|
|
g_object_unref(gc);
|
|
#endif
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void show_line_numbers(GtkWidget *text_view, gboolean visible)
|
|
{
|
|
line_number_visible = visible;
|
|
if (visible) {
|
|
gtk_text_view_set_border_window_size(
|
|
GTK_TEXT_VIEW(text_view),
|
|
GTK_TEXT_WINDOW_LEFT,
|
|
min_number_window_width + margin + submargin);
|
|
} else {
|
|
gtk_text_view_set_border_window_size(
|
|
GTK_TEXT_VIEW(text_view),
|
|
GTK_TEXT_WINDOW_LEFT,
|
|
submargin);
|
|
}
|
|
}
|
|
|
|
void linenum_init(GtkWidget *text_view)
|
|
{
|
|
min_number_window_width = calculate_min_number_window_width(text_view);
|
|
g_signal_connect(
|
|
G_OBJECT(text_view),
|
|
"draw",
|
|
G_CALLBACK(line_numbers_expose),
|
|
NULL);
|
|
show_line_numbers(text_view, FALSE);
|
|
}
|