summary refs log blame commit diff stats
path: root/src/fe-gtk/xtext.h
blob: 90fa1bca4c3e2d441cd332b5b905017298a7b81a (plain) (tree)















































































































































































































































































                                                                                                                               
                                                


                                
#ifndef __XTEXT_H__
#define __XTEXT_H__

#include <gtk/gtkadjustment.h>
#ifdef USE_XFT
#include <X11/Xft/Xft.h>
#endif

#ifdef USE_SHM
#include <X11/Xlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>
#endif

#define GTK_TYPE_XTEXT              (gtk_xtext_get_type ())
#define GTK_XTEXT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_XTEXT, GtkXText))
#define GTK_XTEXT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_XTEXT, GtkXTextClass))
#define GTK_IS_XTEXT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), GTK_TYPE_XTEXT))
#define GTK_IS_XTEXT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_XTEXT))
#define GTK_XTEXT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_XTEXT, GtkXTextClass))

#define ATTR_BOLD			'\002'
#define ATTR_COLOR		'\003'
#define ATTR_BLINK		'\006'
#define ATTR_BEEP			'\007'
#define ATTR_HIDDEN		'\010'
#define ATTR_ITALICS2	'\011'
#define ATTR_RESET		'\017'
#define ATTR_REVERSE		'\026'
#define ATTR_ITALICS		'\035'
#define ATTR_UNDERLINE	'\037'

/* these match palette.h */
#define XTEXT_MIRC_COLS 32
#define XTEXT_COLS 37		/* 32 plus 5 for extra stuff below */
#define XTEXT_MARK_FG 32	/* for marking text */
#define XTEXT_MARK_BG 33
#define XTEXT_FG 34
#define XTEXT_BG 35
#define XTEXT_MARKER 36		/* for marker line */

typedef struct _GtkXText GtkXText;
typedef struct _GtkXTextClass GtkXTextClass;
typedef struct textentry textentry;

typedef struct {
	GtkXText *xtext;					/* attached to this widget */

	gfloat old_value;					/* last known adj->value */
	textentry *text_first;
	textentry *text_last;
	guint16 grid_offset[256];

	textentry *last_ent_start;	  /* this basically describes the last rendered */
	textentry *last_ent_end;	  /* selection. */
	int last_offset_start;
	int last_offset_end;

	int last_pixel_pos;

	int pagetop_line;
	int pagetop_subline;
	textentry *pagetop_ent;			/* what's at xtext->adj->value */

	int num_lines;
	int indent;						  /* position of separator (pixels) from left */

	textentry *marker_pos;

	int window_width;				/* window size when last rendered. */
	int window_height;

	unsigned int time_stamp:1;
	unsigned int scrollbar_down:1;
	unsigned int needs_recalc:1;
	unsigned int grid_dirty:1;
	unsigned int marker_seen:1;
	unsigned int reset_marker_pos:1;
} xtext_buffer;

struct _GtkXText
{
	GtkWidget widget;

	xtext_buffer *buffer;
	xtext_buffer *orig_buffer;
	xtext_buffer *selection_buffer;

#ifdef USE_SHM
	XShmSegmentInfo shminfo;
#endif

	GtkAdjustment *adj;
	GdkPixmap *pixmap;				/* 0 = use palette[19] */
	GdkDrawable *draw_buf;			/* points to ->window */
	GdkCursor *hand_cursor;
	GdkCursor *resize_cursor;

	int pixel_offset;					/* amount of pixels the top line is chopped by */

	int last_win_x;
	int last_win_y;
	int last_win_h;
	int last_win_w;

	int tint_red;
	int tint_green;
	int tint_blue;

	GdkGC *bgc;						  /* backing pixmap */
	GdkGC *fgc;						  /* text foreground color */
	GdkGC *light_gc;				  /* sep bar */
	GdkGC *dark_gc;
	GdkGC *thin_gc;
	GdkGC *marker_gc;
	gulong palette[XTEXT_COLS];

	gint io_tag;					  /* for delayed refresh events */
	gint add_io_tag;				  /* "" when adding new text */
	gint scroll_tag;				  /* marking-scroll timeout */
	gulong vc_signal_tag;        /* signal handler for "value_changed" adj */

	int select_start_adj;		  /* the adj->value when the selection started */
	int select_start_x;
	int select_start_y;
	int select_end_x;
	int select_end_y;

	int max_lines;

	int col_fore;
	int col_back;

	int depth;						  /* gdk window depth */

	char num[8];					  /* for parsing mirc color */
	int nc;							  /* offset into xtext->num */

	textentry *hilight_ent;
	int hilight_start;
	int hilight_end;

	guint16 fontwidth[128];	  /* each char's width, only the ASCII ones */

#ifdef USE_XFT
	XftColor color[XTEXT_COLS];
	XftColor *xft_fg;
	XftColor *xft_bg;				/* both point into color[20] */
	XftDraw *xftdraw;
	XftFont *font;
	XftFont *ifont;				/* italics */
#else
	struct pangofont
	{
		PangoFontDescription *font;
		PangoFontDescription *ifont;	/* italics */
		int ascent;
		int descent;
	} *font, pango_font;
	PangoLayout *layout;
#endif

	int fontsize;
	int space_width;				  /* width (pixels) of the space " " character */
	int stamp_width;				  /* width of "[88:88:88]" */
	int max_auto_indent;

	unsigned char scratch_buffer[4096];

	void (*error_function) (int type);
	int (*urlcheck_function) (GtkWidget * xtext, char *word, int len);

	int jump_out_offset;	/* point at which to stop rendering */
	int jump_in_offset;	/* "" start rendering */

	int ts_x;			/* ts origin for ->bgc GC */
	int ts_y;

	int clip_x;			/* clipping (x directions) */
	int clip_x2;		/* from x to x2 */

	int clip_y;			/* clipping (y directions) */
	int clip_y2;		/* from y to y2 */

	/* current text states */
	unsigned int bold:1;
	unsigned int underline:1;
	unsigned int italics:1;
	unsigned int hidden:1;

	/* text parsing states */
	unsigned int parsing_backcolor:1;
	unsigned int parsing_color:1;
	unsigned int backcolor:1;

	/* various state information */
	unsigned int moving_separator:1;
	unsigned int word_or_line_select:1;
	unsigned int button_down:1;
	unsigned int hilighting:1;
	unsigned int dont_render:1;
	unsigned int dont_render2:1;
	unsigned int cursor_hand:1;
	unsigned int cursor_resize:1;
	unsigned int skip_border_fills:1;
	unsigned int skip_stamp:1;
	unsigned int mark_stamp:1;	/* Cut&Paste with stamps? */
	unsigned int force_stamp:1;	/* force redrawing it */
	unsigned int render_hilights_only:1;
	unsigned int in_hilight:1;
	unsigned int un_hilight:1;
	unsigned int recycle:1;
	unsigned int avoid_trans:1;
	unsigned int force_render:1;
	unsigned int shm:1;
	unsigned int color_paste:1; /* CTRL was pressed when selection finished */

	/* settings/prefs */
	unsigned int auto_indent:1;
	unsigned int thinline:1;
	unsigned int transparent:1;
	unsigned int shaded:1;
	unsigned int marker:1;
	unsigned int separator:1;
	unsigned int wordwrap:1;
	unsigned int overdraw:1;
	unsigned int ignore_hidden:1;	/* rawlog uses this */
};

struct _GtkXTextClass
{
	GtkWidgetClass parent_class;
	void (*word_click) (GtkXText * xtext, char *word, GdkEventButton * event);
};

GtkWidget *gtk_xtext_new (GdkColor palette[], int separator);
void gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len);
void gtk_xtext_append_indent (xtext_buffer *buf,
										unsigned char *left_text, int left_len,
										unsigned char *right_text, int right_len,
										time_t stamp);
int gtk_xtext_set_font (GtkXText *xtext, char *name);
void gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap, gboolean trans);
void gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]);
void gtk_xtext_clear (xtext_buffer *buf, int lines);
void gtk_xtext_save (GtkXText * xtext, int fh);
void gtk_xtext_refresh (GtkXText * xtext, int do_trans);
int gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area, int (*cmp_func) (char *, void *userdata), void *userdata);
textentry *gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward);
void gtk_xtext_reset_marker_pos (GtkXText *xtext);
void gtk_xtext_check_marker_visibility(GtkXText *xtext);

gboolean gtk_xtext_is_empty (xtext_buffer *buf);
typedef void (*GtkXTextForeach) (GtkXText *xtext, unsigned char *text, void *data);
void gtk_xtext_foreach (xtext_buffer *buf, GtkXTextForeach func, void *data);

void gtk_xtext_set_error_function (GtkXText *xtext, void (*error_function) (int));
void gtk_xtext_set_indent (GtkXText *xtext, gboolean indent);
void gtk_xtext_set_max_indent (GtkXText *xtext, int max_auto_indent);
void gtk_xtext_set_max_lines (GtkXText *xtext, int max_lines);
void gtk_xtext_set_show_marker (GtkXText *xtext, gboolean show_marker);
void gtk_xtext_set_show_separator (GtkXText *xtext, gboolean show_separator);
void gtk_xtext_set_thin_separator (GtkXText *xtext, gboolean thin_separator);
void gtk_xtext_set_time_stamp (xtext_buffer *buf, gboolean timestamp);
void gtk_xtext_set_tint (GtkXText *xtext, int tint_red, int tint_green, int tint_blue);
void gtk_xtext_set_urlcheck_function (GtkXText *xtext, int (*urlcheck_function) (GtkWidget *, char *, int));
void gtk_xtext_set_wordwrap (GtkXText *xtext, gboolean word_wrap);

xtext_buffer *gtk_xtext_buffer_new (GtkXText *xtext);
void gtk_xtext_buffer_free (xtext_buffer *buf);
void gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render);
void gtk_xtext_copy_selection (GtkXText *xtext);
GType gtk_xtext_get_type (void);

#endif
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
package HexChat::Embed;
use strict;
use warnings;
use Data::Dumper;
# list of loaded scripts keyed by their package names
# The package names are generated from the filename of the script using
# the file2pkg() function.
# The values of this hash are hash references with the following keys:
#   filename
#     The full path to the script.
#   gui_entry
#     This is hexchat_plugin pointer that is used to remove the script from
#     Plugins and Scripts window when a script is unloaded. This has also
#     been converted with the PTR2IV() macro.
#   hooks
#     This is an array of hooks that are associated with this script.
#     These are pointers that have been converted with the PTR2IV() macro.
#   inner_packages
#     Other packages that are defined in a script. This is not recommended
#     partly because these will also get removed when a script is unloaded.
#   loaded_at
#     A timestamp of when the script was loaded. The value is whatever
#     Time::HiRes::time() returns. This is used to retain load order when
#     using the RELOADALL command.
#   shutdown
#     This is either a code ref or undef. It will be executed just before a
#     script is unloaded.
our %scripts;

# This is a mapping of "inner package" => "containing script package"
our %owner_package;

# used to keep track of which package a hook belongs to, if the normal way of
# checking which script is calling a hook function fails this will be used
# instead. When a hook is created this will be copied to the HookData structure
# and when a callback is invoked this it will be used to set this value.
our $current_package;

sub load {
	my $file = expand_homedir( shift @_ );
	my $package = file2pkg( $file );
	
	if( exists $scripts{$package} ) {
		my $pkg_info = pkg_info( $package );
		my $filename = File::Basename::basename( $pkg_info->{filename} );
		HexChat::printf(
			qq{'%s' already loaded from '%s'.\n},
			$filename, $pkg_info->{filename}
		);
		HexChat::print(
			'If this is a different script then it rename and try '.
			'loading it again.'
		);
		return 2;
	}
	
	if( open my $source_handle, $file ) {
		my $source = do {local $/; <$source_handle>};
		close $source_handle;
		# we shouldn't care about things after __END__
		$source =~ s/^__END__.*//ms;
		
		# this must come before the eval or the filename will not be found in
		# HexChat::register
		$scripts{$package}{filename} = $file;
		$scripts{$package}{loaded_at} = Time::HiRes::time();

		# this must be done before the error check so the unload will remove
		# any inner packages defined by the script. if a script fails to load
		# then any inner packages need to be removed as well.
		my @inner_packages = $source =~
			m/^\s*package \s+
				((?:[^\W:]+(?:::)?)+)\s*? # package name
				# strict version number
				(?:\d+(?:[.]\d+) # positive integer or decimal-fraction
					|v\d+(?:[.]\d+){2,})? # dotted-decimal v-string
				[{;]
			/mgx;

		# check if any inner package defined in the to be loaded script has
		# already been defined by another script
		my @conflicts;
		for my $inner ( @inner_packages ) {
			if( exists $owner_package{ $inner } ) {
				push @conflicts, $inner;
			}
		}

		# report conflicts and bail out
		if( @conflicts ) {
			my $error_message =
				"'$file' won't be loaded due to conflicting inner packages:\n";
			for my $conflict_package ( @conflicts ) {
				$error_message .= "   $conflict_package already defined in " .
					pkg_info($owner_package{ $conflict_package })->{filename}."\n";
			}
			HexChat::print( $error_message );

			return 2;
		}

		my $full_path = File::Spec->rel2abs( $file );
		$source =~ s/^/#line 1 "$full_path"\n\x7Bpackage $package;/;

		# make sure we add the closing } even if the last line is a comment
		if( $source =~ /^#.*\Z/m ) {
			$source =~ s/^(?=#.*\Z)/\x7D/m;
		} else {
			$source =~ s/\Z/\x7D/;
		}

		$scripts{$package}{inner_packages} = [ @inner_packages ];
		@owner_package{ @inner_packages } = ($package) x @inner_packages;
		_do_eval( $source );

		unless( exists $scripts{$package}{gui_entry} ) {
			$scripts{$package}{gui_entry} =
				HexChat::Internal::register(
					"", "unknown", "", $file
				);
		}

		if( $@ ) {
			# something went wrong
			$@ =~ s/\(eval \d+\)/$file/g;
			HexChat::print( "Error loading '$file':\n$@\n" );
			# make sure the script list doesn't contain false information
			unload( $scripts{$package}{filename} );
			return 1;
		}
	} else {
		HexChat::print( "Error opening '$file': $!\n" );
		return 2;
	}

	return 0;
}

sub _do_eval {
	no strict;
	no warnings;
	eval $_[0];
}

sub unload {
	my $file = shift @_;
	my $package = file2pkg( $file );
	my $pkg_info = pkg_info( $package );

	if( $pkg_info ) {	
		# take care of the shutdown callback
		if( exists $pkg_info->{shutdown} ) {
			# allow incorrectly written scripts to be unloaded
			eval {
				if( ref $pkg_info->{shutdown} eq 'CODE' ) {
					$pkg_info->{shutdown}->();
				} elsif ( $pkg_info->{shutdown} ) {
					no strict 'refs';
					&{$pkg_info->{shutdown}};
				}
			};
		}

		if( exists $pkg_info->{hooks} ) {
			for my $hook ( @{$pkg_info->{hooks}} ) {
				HexChat::unhook( $hook, $package );
			}
		}

		if( exists $pkg_info->{gui_entry} ) {
			plugingui_remove( $pkg_info->{gui_entry} );
		}
		
		delete @owner_package{ @{$pkg_info->{inner_packages}} };
		for my $inner_package ( @{$pkg_info->{inner_packages}} ) {
			Symbol::delete_package( $inner_package );
		}
		Symbol::delete_package( $package );
		delete $scripts{$package};
		return HexChat::EAT_ALL;
	} else {
		HexChat::print( qq{"$file" is not loaded.\n} );
		return HexChat::EAT_NONE;
	}
}

sub unload_all {
	for my $package ( keys %scripts ) {
		unload( $scripts{$package}->{filename} );
	}
	
	return HexChat::EAT_ALL;
}

sub reload {
	my $file = shift @_;
	my $package = file2pkg( $file );
	my $pkg_info = pkg_info( $package );
	my $fullpath = $file;
	
	if( $pkg_info ) {
		$fullpath = $pkg_info->{filename};
		unload( $file );
	}
	
	load( $fullpath );
	return HexChat::EAT_ALL;
}

sub reload_all {
	my @dirs = HexChat::get_info( "configdir" );
	push @dirs, File::Spec->catdir( $dirs[0], "plugins" );
	for my $dir ( @dirs ) {
		my $auto_load_glob = File::Spec->catfile( $dir, "*.pl" );
		my @scripts = map { $_->{filename} }
			sort { $a->{loaded_at} <=> $b->{loaded_at} } values %scripts;
		push @scripts, File::Glob::bsd_glob( $auto_load_glob );

		my %seen;
		@scripts = grep { !$seen{ $_ }++ } @scripts;

		unload_all();
		for my $script ( @scripts ) {
			if( !pkg_info( file2pkg( $script ) ) ) {
				load( $script );
			}
		}
	}
}

sub evaluate {
	my ($code) = @_;

	my @results = eval $code;
	HexChat::print $@ if $@; #print warnings

	local $Data::Dumper::Sortkeys = 1;
	local $Data::Dumper::Terse    = 1;

	if (@results > 1) {
		HexChat::print Dumper \@results;
	}
	elsif (ref $results[0] || !$results[0]) {
		HexChat::print Dumper $results[0];
	}
	else {
		HexChat::print $results[0];
	}

	return HexChat::EAT_HEXCHAT;
};

sub expand_homedir {
	my $file = shift @_;

	if ( $^O eq "MSWin32" ) {
		$file =~ s/^~/$ENV{USERPROFILE}/;
	} else {
		$file =~ s{^~}{
			(getpwuid($>))[7] ||  $ENV{HOME} || $ENV{LOGDIR}
		}ex;
	}
	return $file;
}

sub file2pkg {
	my $string = File::Basename::basename( shift @_ );
	$string =~ s/\.pl$//i;
	$string =~ s|([^A-Za-z0-9/])|'_'.unpack("H*",$1)|eg;
	return "HexChat::Script::" . $string;
}

sub pkg_info {
	my $package = shift @_;
	return $scripts{$package};
}

sub find_external_pkg {
	my $level = 1;

	while( my @frame = caller( $level ) ) {
		return @frame if $frame[0] !~ /(?:^IRC$|^HexChat)/;
		$level++;
	}
	return;
}

sub find_pkg {
	my $level = 1;

	while( my ($package, $file, $line) = caller( $level ) ) {
		return $package if $package =~ /^HexChat::Script::/;
		$level++;
	}

	my $current_package = get_current_package();
	if( defined $current_package ) {
		return $current_package;
	}

	my @frame = find_external_pkg();
	my $location;

	if( $frame[0] or $frame[1] ) {
		my $calling_package = $frame[0];
		if( defined( my $owner = $owner_package{ $calling_package } ) ) {
			return ($owner, $calling_package);
		}

		$location = $frame[1] ? $frame[1] : "package $frame[0]";
		$location .= " line $frame[2]";
	} else {
		$location = "unknown location";
	}

	die "Unable to determine which script this hook belongs to. at $location\n";

}

# convert function names into code references
sub fix_callback {
	my ($package, $calling_package, $callback) = @_;
	
	unless( ref $callback ) {
		unless( $callback =~ /::/ ) {
			my $prefix = defined $calling_package ? $calling_package : $package;
			$callback =~ s/^/${prefix}::/;
		}

		no strict 'subs';
		$callback = \&{$callback};
	}
	
	return $callback;
}

sub get_current_package {
	return $current_package;
}

sub set_current_package {
	my $old_package = $current_package;
	$current_package = shift;

	return $old_package;
}

1