(*
    Tux Commander - UViewer - Internal viewer
    Copyright (C) 2008 Tomas Bzatek <tbzatek@users.sourceforge.net>
    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, UGnome;



// 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;
  if @gtk_window_set_icon_name <> nil then gtk_window_set_icon_name(PGtkWindow(FWidget), 'text-x-generic');  // accessories-text-editor

  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: gulong;
    timer: PGTimer;
    microseconds, dur, st: gulong;
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;
  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;
    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 := True;

  case Key of
    GDK_HOME:  begin
                 smooth_scroll(vadj, 0, True);
                 smooth_scroll(hadj, 0, True);
                 Accept := False;
               end;
    GDK_END:   begin
                 smooth_scroll(vadj, vadj^.upper - vadj^.page_size, True);
                 Accept := False;
               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);
                 Accept := False;
               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);
                 Accept := False;
               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);
                 Accept := 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);
                 Accept := 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);
                 Accept := 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);
                 Accept := False;
               end;
    GDK_W, GDK_Capital_W: if (Shift = []) then begin
                            if TextView.WrapMode = wmWrapNone then TextView.WrapMode := wmWrapWord
                                                              else TextView.WrapMode := wmWrapNone;
                            Accept := False;
                          end;
//    GDK_C, GDK_Capital_C, GDK_Insert: if (ssCtrl in Shift) then Accept := True;
    GDK_S, GDK_Capital_S: if (Shift = []) then begin
                             gtk_widget_modify_font(TextView.FWidget, pango_font_description_from_string('Monospace'));
                             Accept := False;
                          end;
    GDK_A, GDK_Capital_A: if (Shift = []) then begin
                            gtk_widget_modify_font(TextView.FWidget, nil);
                            Accept := False;
                          end;
  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.
