(* Tux Commander - UViewer - Internal viewer Copyright (C) 2008 Tomas Bzatek Check for updates on tuxcmd.sourceforge.net 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA WARNING: This is highly experimental feature, use with caution! *) unit UViewer; interface uses glib2, gdk2, gtk2, pango, SysUtils, Types, Classes, Variants, GTKControls, GTKForms, GTKUtils, GTKDialogs, GTKPixbuf, GTKClasses, GTKExtCtrls, GTKConsts, GTKText; type TFViewer = class(TGTKForm) // FDrawingArea: TGTKControl; TextView: TGTKTextView; ScrolledWindow: TGTKScrolledWindow; procedure FormCreate(Sender: TObject); override; procedure FormDestroy(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean); // procedure DrawingAreaKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean); procedure TextViewKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean); private // LineHeight, NumLines: integer; LineBuffer: TStringList; protected AThread: TThread; public function LoadFile(FileName: string): boolean; end; TViewerThread = class(TThread) private // procedure DoIt; protected procedure Execute; override; public AViewer: TFViewer; constructor Create(Parent: TComponent); destructor Destroy; override; function LoadFile(FileName: string): boolean; end; var FViewer: TFViewer; implementation uses UCoreUtils, UCoreClasses, UConfig, DateUtils, ULibc; // function expose_event_callback(AWidget: PGtkWidget; event: PGdkEventExpose; data: gpointer): gboolean; cdecl; forward; function scroll_event_callback(widget: PGtkWidget; event: PGdkEventScroll; user_data: gpointer): gboolean; cdecl; forward; procedure TFViewer.FormCreate(Sender: TObject); begin SetDefaultSize(700, 500); Caption := 'TuxView'; BorderWidth := 5; AThread := nil; ScrolledWindow := TGTKScrolledWindow.Create(Self); ScrolledWindow.HorizScrollBarPolicy := sbAutomatic; ScrolledWindow.VertScrollBarPolicy := sbAutomatic; ScrolledWindow.ShadowType := stShadowIn; g_signal_connect(G_OBJECT(ScrolledWindow.FWidget), 'scroll-event', G_CALLBACK (@scroll_event_callback), Self); AddControl(ScrolledWindow); // ClientArea.AddControl(ScrolledWindow); (* FDrawingArea := TGTKControl.Create(Self); FDrawingArea.FWidget := gtk_drawing_area_new; gtk_widget_set_events(FDrawingArea.FWidget, GDK_EXPOSURE_MASK or GDK_BUTTON_PRESS_MASK or GDK_POINTER_MOTION_MASK or GDK_KEY_PRESS_MASK); gtk_widget_set_size_request(FDrawingArea.FWidget, 10000, 40000); g_signal_connect(G_OBJECT(FDrawingArea.FWidget), 'expose_event', G_CALLBACK (@expose_event_callback), Self); // g_signal_connect_after(G_OBJECT(FDrawingArea.FWidget), 'key_press_event', G_CALLBACK (@key_press_event_callback), NULL); gtk_widget_set(FDrawingArea.FWidget, 'can_focus', TRUE, 0); gtk_widget_set(FDrawingArea.FWidget, 'can_default', TRUE, 0); gtk_widget_show(FDrawingArea.FWidget); FDrawingArea.OnKeyDown := DrawingAreaKeyDown; ScrolledWindow.AddWithViewPort(FDrawingArea); FDrawingArea.SetFocus; *) TextView := TGTKTextView.Create(Self); TextView.CursorVisible := False; TextView.ReadOnly := True; TextView.OnKeyDown := TextViewKeyDown; ScrolledWindow.AddControl(TextView); TextView.SetFocus; // ****************** OnKeyDown := FormKeyDown; OnDestroy := FormDestroy; OnClose := FormClose; end; procedure TFViewer.FormKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean); begin if Key = GDK_ESCAPE then Close; end; procedure TFViewer.FormClose(Sender: TObject; var Action: TCloseAction); begin Action := caFree; SetParent(nil); if Assigned(FWidget) and GTK_IS_WIDGET(FWidget) then gtk_widget_destroy(PGtkWidget(FWidget)); // AThread.Terminate; // ModalResult := mbOK; end; procedure TFViewer.FormDestroy(Sender: TObject); begin try LineBuffer.Free; except end; end; procedure smooth_scroll(adj: PGtkAdjustment; dest_value: Double; PageScroll: boolean); var increment, val: Double; i, x: integer; timer: PGTimer; microseconds: gulong; dur, st: integer; begin if gtk_adjustment_get_value(adj) = dest_value then Exit; if not ConfUseSmoothScrolling then begin gtk_adjustment_set_value(adj, dest_value); Exit; end; if not PageScroll then begin dur := SMOOTH_SCROLL_DURATION; st := SMOOTH_SCROLL_STEPS; end else begin dur := SMOOTH_SCROLL_DURATION_PAGE; st := SMOOTH_SCROLL_STEPS_PAGE; end; timer := g_timer_new; increment := (dest_value - gtk_adjustment_get_value(adj)) / st; val := gtk_adjustment_get_value(adj); // DebugMsg(['Start value ', val]); i := 1; x := 0; g_timer_start(timer); repeat g_timer_elapsed(timer, @microseconds); if (microseconds > i*1000*dur / st) and (i < st) then begin val := val + increment; gtk_adjustment_set_value(adj, val); // DebugMsg(['Setting value to ', val]); Inc(i); end; gdk_window_process_all_updates; Inc(x); usleep(100); until (microseconds div 1000) > dur; gtk_adjustment_set_value(adj, dest_value); gdk_window_process_all_updates; // DebugMsg(['End value ', dest_value]); g_timer_stop(timer); { g_timer_elapsed(timer, @microseconds); DebugMsg(['Smooth scroll: required = ', dur, 'ms, elapsed = ', Integer(microseconds div 1000), 'ms, iterations = ', x]); } g_timer_destroy(timer); end; procedure TFViewer.TextViewKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean); var vadj, hadj: PGtkAdjustment; x: Double; begin vadj := gtk_scrolled_window_get_vadjustment(PGtkScrolledWindow(ScrolledWindow.FWidget)); hadj := gtk_scrolled_window_get_hadjustment(PGtkScrolledWindow(ScrolledWindow.FWidget)); // debugmsg(['lower = ', adj^.lower, ', upper = ', adj^.upper, ', value = ', x, ', page_size = ', adj^.page_size]); Accept := False; case Key of GDK_HOME: begin smooth_scroll(vadj, 0, True); smooth_scroll(hadj, 0, True); end; GDK_END: begin smooth_scroll(vadj, vadj^.upper - vadj^.page_size, True); end; GDK_Page_Down, GDK_SPACE: begin x := gtk_adjustment_get_value(vadj) + vadj^.page_increment; if x + vadj^.page_size > vadj^.upper then x := vadj^.upper - vadj^.page_size; smooth_scroll(vadj, x, True); end; GDK_Page_Up: begin x := gtk_adjustment_get_value(vadj) - vadj^.page_increment; if x < 0 then x := 0; smooth_scroll(vadj, x, True); end; GDK_Down: begin x := gtk_adjustment_get_value(vadj) + vadj^.step_increment; if x + vadj^.page_size > vadj^.upper then x := vadj^.upper - vadj^.page_size; smooth_scroll(vadj, x, False); end; GDK_Up: begin x := gtk_adjustment_get_value(vadj) - vadj^.step_increment; if x < 0 then x := 0; smooth_scroll(vadj, x, False); end; GDK_Right: begin x := gtk_adjustment_get_value(hadj) + hadj^.step_increment; if x + hadj^.page_size > hadj^.upper then x := hadj^.upper - hadj^.page_size; smooth_scroll(hadj, x, False); end; GDK_Left: begin x := gtk_adjustment_get_value(hadj) - hadj^.step_increment; if x < 0 then x := 0; smooth_scroll(hadj, x, False); end; GDK_W, GDK_Capital_W: if TextView.WrapMode = wmWrapNone then TextView.WrapMode := wmWrapWord else TextView.WrapMode := wmWrapNone; GDK_C, GDK_Capital_C, GDK_Insert: if (ssCtrl in Shift) then Accept := True; GDK_S, GDK_Capital_S: gtk_widget_modify_font(TextView.FWidget, pango_font_description_from_string('Monospace')); GDK_A, GDK_Capital_A: gtk_widget_modify_font(TextView.FWidget, nil); end; end; function scroll_event_callback(widget: PGtkWidget; event: PGdkEventScroll; user_data: gpointer): gboolean; cdecl; var vadj: PGtkAdjustment; x: Double; begin vadj := gtk_scrolled_window_get_vadjustment(PGtkScrolledWindow(TFViewer(user_data).ScrolledWindow.FWidget)); Result := True; case event^.direction of GDK_SCROLL_UP: begin x := gtk_adjustment_get_value(vadj) - 1.5*vadj^.step_increment; if x < 0 then x := 0; smooth_scroll(vadj, x, False); end; GDK_SCROLL_DOWN: begin x := gtk_adjustment_get_value(vadj) + 1.5*vadj^.step_increment; if x + vadj^.page_size > vadj^.upper then x := vadj^.upper - vadj^.page_size; smooth_scroll(vadj, x, False); end; end; end; (* procedure TFViewer.DrawingAreaKeyDown(Sender: TObject; Key: Word; Shift: TShiftState; var Accept: boolean); var adj: PGtkAdjustment; x: integer; begin Accept := True; adj := gtk_scrolled_window_get_vadjustment(PGtkScrolledWindow(ScrolledWindow.FWidget)); case Key of GDK_ESCAPE: Close; GDK_Down: begin x := Trunc(gtk_adjustment_get_value(adj)) + LineHeight; if x + Trunc(adj^.page_size) > LineHeight * NumLines then x := LineHeight * NumLines - Trunc(adj^.page_size); gtk_adjustment_set_value(adj, x); end; GDK_Up: begin x := Trunc(gtk_adjustment_get_value(adj)) - LineHeight; if x < 0 then x := 0; gtk_adjustment_set_value(adj, x); end; GDK_space: begin x := Trunc(gtk_adjustment_get_value(adj) + adj^.page_increment); if x + Trunc(adj^.page_size) > LineHeight * NumLines then x := LineHeight * NumLines - Trunc(adj^.page_size); gtk_adjustment_set_value(adj, x); end; end; { if event^.keyval = 65364 then begin // Down adj := gtk_scrolled_window_get_vadjustment(PGtkScrolledWindow(scrolledWindow)); x := Trunc(gtk_adjustment_get_value(adj)) + LineHeight; if x + Trunc(adj^.page_size) > LineHeight * NumLines then x := LineHeight * NumLines - Trunc(adj^.page_size); gtk_adjustment_set_value(adj, x); end; if event^.keyval = 65362 then begin // Up adj := gtk_scrolled_window_get_vadjustment(PGtkScrolledWindow(scrolledWindow)); x := Trunc(gtk_adjustment_get_value(adj)) - LineHeight; if x < 0 then x := 0; gtk_adjustment_set_value(adj, x); end; if (event^.keyval = 65366) or (event^.keyval = 32) then begin // PageDown, Space adj := gtk_scrolled_window_get_vadjustment(PGtkScrolledWindow(scrolledWindow)); x := Trunc(gtk_adjustment_get_value(adj) + adj^.page_increment); if x + Trunc(adj^.page_size) > LineHeight * NumLines then x := LineHeight * NumLines - Trunc(adj^.page_size); gtk_adjustment_set_value(adj, x); end; if event^.keyval = 65365 then begin // PageUp adj := gtk_scrolled_window_get_vadjustment(PGtkScrolledWindow(scrolledWindow)); x := Trunc(gtk_adjustment_get_value(adj) - adj^.page_increment); if x < 0 then x := 0; gtk_adjustment_set_value(adj, x); end; if event^.keyval = 65360 then begin // Home adj := gtk_scrolled_window_get_vadjustment(PGtkScrolledWindow(scrolledWindow)); gtk_adjustment_set_value(adj, 0); end; if event^.keyval = 65367 then begin // End adj := gtk_scrolled_window_get_vadjustment(PGtkScrolledWindow(scrolledWindow)); gtk_adjustment_set_value(adj, LineHeight * NumLines - adj^.page_size); end; } end; *) (********************************************************************************************************************************) (* function expose_event_callback(AWidget: PGtkWidget; event: PGdkEventExpose; data: gpointer): gboolean; cdecl; var Layout: PPangoLayout; s: string; i, LineFrom, LineTo: integer; var Cursor: PGdkCursor; begin // Count the visible lines LineFrom := (event^.area.y div TFViewer(data).LineHeight) - 1; if LineFrom < 0 then LineFrom := 0; LineTo := ((event^.area.y + event^.area.height) div TFViewer(data).LineHeight) + 1; // if LineTo > MaxLineNum then LineTo := MaxLineNum; // DebugMsg(['Rendering lines ', LineFrom, ' - ', LineTo, '; expose_area: [', event^.area.x, ', ', event^.area.y, '] - ', event^.area.width, 'x', event^.area.height]); gdk_draw_rectangle(PGdkDrawable(Awidget^.window), Awidget^.style^.white_gc, Integer(TRUE), event^.area.x, event^.area.y, event^.area.width, event^.area.height); Layout := pango_layout_new(gtk_widget_get_pango_context(Awidget)); for i := LineFrom to LineTo do begin // s := Format('This is the text on the line %d. Hope the rendering will be fast', [i]); if i < TFViewer(data).LineBuffer.Count then s := {ANSIToUTF8(}TFViewer(data).LineBuffer[i] else s := ''; pango_layout_set_text(Layout, PChar(s), Length(PChar(s))); gdk_draw_layout(PGdkDrawable(Awidget^.window), Awidget^.style^.fg_gc[GTK_WIDGET_STATE (Awidget)], 5, i * TFViewer(data).LineHeight, Layout); end; g_object_unref(Layout); ( * gdk_draw_arc(PGdkDrawable(Awidget^.window), Awidget^.style^.fg_gc[GTK_WIDGET_STATE (Awidget)], Integer(TRUE), 0, 0, 500, 500, {Awidget^.allocation.width, Awidget^.allocation.height,} 0, 64 * 360); * ) // !!!!!!!!!!!! Toto oddelat (musi se volat asi po Application.ProcessMessages (se mne nechtelo s tim drbat tady)) cursor := gdk_cursor_new(152); gdk_window_set_cursor(AWidget^.window, cursor); gdk_cursor_unref(cursor); Result := True; end; *) function TFViewer.LoadFile(FileName: string): boolean; var Valid: boolean; i: integer; begin Result := False; try LineBuffer := TStringList.Create; LineBuffer.LoadFromFile(FileName); Valid := True; for i := 0 to Min(100, LineBuffer.Count - 1) do begin Valid := Valid and g_utf8_validate(PChar(LineBuffer[i]), Length(LineBuffer[i]), nil); if not Valid then begin DebugMsg(['** TFViewer.LoadFile(', FileName, ', Line ', i + 1, ') is not UTF-8 valid.']); Break; end; end; if Valid then begin for i := 0 to LineBuffer.Count - 1 do TextView.Lines.InsertText(String(EnsureUTF8String(PChar(LineBuffer[i]))) + #10); end else begin for i := 0 to LineBuffer.Count - 1 do TextView.Lines.InsertText(StrToUTF8(LineBuffer[i]) + #10); end; { LineHeight := 16; NumLines := LineBuffer.Count; } Caption := Format('TuxView [%s]', [StrToUTF8(FileName)]); // FDrawingArea.SetSizeRequest(-1, NumLines * LineHeight); Result := True; except Exit; end; end; (********************************************************************************************************************************) procedure TViewerThread.Execute; begin gdk_threads_enter; // DoIt; AViewer.Show; // AViewer.Run; gdk_threads_leave; end; constructor TViewerThread.Create(Parent: TComponent); begin AViewer := TFViewer.Create(Parent); AViewer.AThread := Self; FreeOnTerminate := True; inherited Create(True); end; destructor TViewerThread.Destroy; begin { AViewer.Free; inherited Destroy; } end; function TViewerThread.LoadFile(FileName: string): boolean; begin Result := AViewer.LoadFile(FileName); end; {procedure TViewerThread.DoIt; begin Application.MessageBox('ddd') end; } end.