view_text-1/COPYING0000644000000000000000000000136210703744546014443 0ustar00usergroup00000000000000Copyright (c) 2007, Henry Precheur Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. view_text-1/Makefile0000644000000000000000000000043210703744546015045 0ustar00usergroup00000000000000CC=cc CFLAGS= -Wall -W -std=c99 -g -O2 `pkg-config gtk+-2.0 --cflags` LIBS=`pkg-config gtk+-2.0 --libs` OBJS=view_text.o all: view_text clean: -rm -f ${OBJS} view_text *.core view_text: ${OBJS} ${CC} ${LIBS} ${OBJS} -o $@ view_text.o: view_text.c ${CC} ${CFLAGS} -c $< -o $@ view_text-1/view_text.c0000644000000000000000000002576710703744546015611 0ustar00usergroup00000000000000/* * Copyright (c) 2007 Henry Precheur * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #define BORDER_SIZE 8 PangoFontDescription* g_font_desc = NULL; gchar* g_font_name; gchar* g_file_content; size_t g_file_size; gboolean g_use_stripped_content = FALSE; gchar* g_file_content_stripped = NULL; size_t g_file_size_stripped = 0; int g_mouse_prev_x = -1; int g_mouse_prev_y = -1; int g_mouse_x = 0; int g_mouse_y = 0; static void strip_spaces() { if (g_file_content_stripped != NULL) return; g_file_content_stripped = g_malloc(g_file_size + 1); /* extra byte to have a null terminated string */ char* n = g_file_content_stripped; g_file_size_stripped = 0; gboolean was_blank = FALSE; for (const char* p = g_file_content; p < g_file_content + g_file_size; p++) { if (isblank(*p)) { /* insert space is the last character was not a blank character */ if (!was_blank) { *n++ = ' '; g_file_size_stripped++; was_blank = TRUE; } } else { *n++ = *p; g_file_size_stripped++; was_blank = FALSE; } } *n = 0; /* null terminated string */ } /* helper function to get the size of the window without the borders */ static void get_window_size(GdkWindow* window, int* x, int* y) { gdk_drawable_get_size(GDK_DRAWABLE(window), x, y); *x -= 2 * BORDER_SIZE; *y -= 2 * BORDER_SIZE; } static void draw_window(GdkWindow* window) { cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(window)); /* paint surface in white */ cairo_set_source_rgb(cr, 1, 1, 1); cairo_paint(cr); cairo_set_source_rgb(cr, 0, 0, 0); PangoLayout* layout = pango_cairo_create_layout(cr); if (g_font_desc != NULL) { pango_layout_set_font_description(layout, g_font_desc); } if (g_use_stripped_content) pango_layout_set_text(layout, g_file_content_stripped, g_file_size_stripped); else pango_layout_set_text(layout, g_file_content, g_file_size); /* make sure the text is not moved outside of the drawing area */ { PangoRectangle logical_rect; pango_layout_get_pixel_extents(layout, NULL, &logical_rect); int draw_width, draw_height; get_window_size(window, &draw_width, &draw_height); /* these makes sure the text is moved within a "good" range. */ g_mouse_x = MIN(0, MAX(MIN(draw_width - logical_rect.width, 0), g_mouse_x)); g_mouse_y = MIN(0, MAX(MIN(draw_height - logical_rect.height, 0), g_mouse_y)); cairo_move_to(cr, g_mouse_x + BORDER_SIZE, g_mouse_y + BORDER_SIZE); } pango_cairo_show_layout(cr, layout); g_object_unref(layout); cairo_destroy(cr); } static void double_buffer_draw_window(GdkWindow* window) { /* repaint the window */ gdk_window_begin_paint_region(window, gdk_drawable_get_visible_region(GDK_DRAWABLE(window))); draw_window(window); gdk_window_end_paint(window); } /* * GTK callbacks definition. */ static void destroy(GtkWidget* widget, gpointer data) { /* avoid warning "unused parameters" */ (void) widget; (void) data; gtk_main_quit (); } static gboolean expose( GtkWidget* widget, GdkEventExpose* event, gpointer user_data) { (void) widget; (void) user_data; draw_window(event->window); return TRUE; } static void change_font(char* font_name) { if (font_name == NULL) return; PangoFontDescription* desc = pango_font_description_from_string(font_name); if (desc == NULL || pango_font_description_get_family(desc) == NULL) { g_warning("Invalide font family in '%s'\n", font_name); g_free(font_name); g_font_desc = NULL; g_font_name = NULL; } else { /* if font size == 0, set it to 10. Doing so avoid a segv in Pango. */ if (pango_font_description_get_size(desc) == 0) { pango_font_description_set_size(desc, 10 * PANGO_SCALE); if (font_name) g_free(font_name); font_name = pango_font_description_to_string(desc); } if (g_font_name) g_free(g_font_name); g_font_name = font_name; if (g_font_desc != NULL) pango_font_description_free(g_font_desc); g_font_desc = desc; } } static gboolean button_press(GtkWidget* widget, GdkEventButton* event, gpointer user_data) { (void) widget; (void) user_data; /* save the mouse position so we can deduce the relative movement of the * mouse. */ g_mouse_prev_x = event->x; g_mouse_prev_y = event->y; return FALSE; } static gboolean mouse_motion(GtkWidget* widget, GdkEventMotion* event, gpointer user_data) { (void) widget; (void) user_data; g_assert((event->state & GDK_BUTTON1_MASK) == GDK_BUTTON1_MASK); int x, y; GdkModifierType mask; GdkWindow* window = event->window; gdk_window_get_pointer(window, &x, &y, &mask); g_mouse_x += x - g_mouse_prev_x; g_mouse_y += y - g_mouse_prev_y; g_mouse_prev_x = x; g_mouse_prev_y = y; double_buffer_draw_window(window); return TRUE; } static gboolean scroll(GtkWidget* widget, GdkEventScroll* event, gpointer user_data) { (void) widget; (void) user_data; /* the scrolling is done relatively to the size of the window. 1st get the * window's size. */ int draw_width, draw_height; get_window_size(event->window, &draw_width, &draw_height); /* scrolling by an "entire" window is a bit brutal. One third seems to be * better. */ const int scroll_divide = 3; switch (event->direction) { case GDK_SCROLL_UP: g_mouse_y += draw_height / scroll_divide; break; case GDK_SCROLL_DOWN: g_mouse_y -= draw_height / scroll_divide; break; case GDK_SCROLL_LEFT: g_mouse_x -= draw_width / scroll_divide; break; case GDK_SCROLL_RIGHT: g_mouse_x += draw_width / scroll_divide; break; } double_buffer_draw_window(event->window); return TRUE; } static gboolean key_press(GtkWidget* widget, GdkEventKey* event, gpointer user_data) { (void) widget; (void) user_data; int draw_width, draw_height; get_window_size(event->window, &draw_width, &draw_height); /* scrolling by an "entire" window is a bit brutal. One tens seems to be * better. */ const int scroll_divide = 10; switch (event->keyval) { case GDK_Up: g_mouse_y += draw_height / scroll_divide; break; case GDK_Down: g_mouse_y -= draw_height / scroll_divide; break; case GDK_Left: g_mouse_x += draw_width / scroll_divide; break; case GDK_Right: g_mouse_x -= draw_width / scroll_divide; break; case GDK_Page_Up: g_mouse_y += draw_height; break; case GDK_Page_Down: g_mouse_y -= draw_height; break; case GDK_Q: case GDK_q: case GDK_Escape: gtk_widget_destroy(widget); break; case GDK_s: case GDK_S: strip_spaces(); g_use_stripped_content = !g_use_stripped_content; break; case GDK_c: case GDK_C: { GtkWidget* font_sel = gtk_font_selection_dialog_new("Choose font"); gtk_window_set_modal(GTK_WINDOW(font_sel), TRUE); gtk_font_selection_dialog_set_font_name( GTK_FONT_SELECTION_DIALOG(font_sel), g_font_name); gint result = gtk_dialog_run (GTK_DIALOG(font_sel)); if (result == GTK_RESPONSE_OK) { change_font(gtk_font_selection_dialog_get_font_name( GTK_FONT_SELECTION_DIALOG(font_sel))); } gtk_widget_destroy (font_sel); } break; } double_buffer_draw_window(event->window); return TRUE; } int main(int argc, char *argv[]) { if (argc != 2 && argc != 3) { g_printerr( "Usage: %s filename [font name]\n\n" " Use 'c' to change font\n" " 's' to strip spaces from text\n" " 'q' or 'escape' to quit\n", argv[0]); return 1; } gtk_init(&argc, &argv); const char* filename = argv[1]; change_font(argc == 3 ? g_strdup(argv[2]) : g_strdup("Sans 12")); GError* error = NULL; if (!g_file_get_contents(filename, &g_file_content, &g_file_size, &error)) { g_printerr("%s\n", error->message); g_error_free(error); return 1; } GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_default_size(GTK_WINDOW(window), 700, 700); g_signal_connect(G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL); g_signal_connect(G_OBJECT (window), "expose-event", G_CALLBACK (expose), NULL); g_signal_connect(G_OBJECT (window), "motion-notify-event", G_CALLBACK (mouse_motion), NULL); g_signal_connect(G_OBJECT (window), "button-press-event", G_CALLBACK (button_press), NULL); g_signal_connect(G_OBJECT (window), "scroll-event", G_CALLBACK (scroll), NULL); g_signal_connect(G_OBJECT (window), "key-press-event", G_CALLBACK (key_press), NULL); gtk_widget_add_events(window, GDK_BUTTON1_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_SCROLL_MASK); gtk_widget_show(window); gtk_main (); if (g_font_desc && g_font_name) { pango_font_description_free(g_font_desc); g_free(g_font_name); } if (g_file_content_stripped) g_free(g_file_content_stripped); g_free(g_file_content); return 0; } /* vim:set et tw=80 sw=4 ts=4: */