(*
    Tux Commander - UGnome - libgnome and libgnomeui related functions and classes
    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
*)
unit UGnome;

interface

uses glib2, gdk2, gdk2pixbuf, gtk2, Classes, ULibc,
     GTKForms, GTKControls, GTKStdCtrls, GTKExtCtrls, GTKClasses, GTKDialogs, GTKUtils, GTKConsts,
     uVFSprototypes;

type TGnomeColorButton = class(TGTKButton)
     private
       FUseGnomeUI: boolean;
       FColor: TGDKColor;
       FColorChanged: TNotifyEvent;
       function GetColor: TGDKColor;
       procedure SetColor(Value: TGDKColor);
     public
       constructor Create(AOwner: TComponent); override;
       destructor Destroy; override;
       procedure SetDefaultColor;
       procedure UnsetDefaultColor;
       property Color: GTKClasses.TGDKColor read GetColor write SetColor;
     published
       property OnColorChanged: TNotifyEvent read FColorChanged write FColorChanged;
     end;

     TGnomeIconEntry = class(TGTKVBox)
     private
       FIconChanged: TNotifyEvent;
       function GetFilename: string;
       procedure SetFilename(Value: string);
       procedure SetPixmapSubdir(Value: string);
     public
       constructor Create(AOwner: TComponent); override;
       destructor Destroy; override;
     published
       property Filename: string read GetFilename write SetFilename;
       property PixpamSubdir: string write SetPixmapSubdir;
       property OnIconChanged: TNotifyEvent read FIconChanged write FIconChanged;
     end;

     TGnomeDateEdit = class(TGTKHBox)
     private
       function GetTime: time_t;
       procedure SetTime(Value: time_t);
     public
       constructor Create(AOwner: TComponent); override;
       destructor Destroy; override;
     published
       property Time: time_t read GetTime write SetTime;
     end;

     TEphyNotebook = class;

     TEphyNotebookReorderedEvent = procedure (Sender: TObject; const Source, Dest: integer) of object;
     TEphyNotebookTabCloseEvent = procedure (Sender: TObject; const TabNum: integer; var CanClose: boolean) of object;
     TEphyNotebookTabDoubleClickEvent = procedure (Sender: TObject; const TabNum: integer) of object;
     TEphyNotebookFindNotebookAtPointerEvent = function (Sender: TObject; const AbsX, AbsY: integer): TEphyNotebook of object;
     TEphyNotebookMoveTabToAnotherNotebookEvent = function (Sender: TObject; Destination: TEphyNotebook; const SourceTabNo, DestTabNo: integer): boolean of object;
     TEphyNotebookTabSwitchedEvent = procedure (Sender: TObject; const NewTabNum: integer; const ShouldFocus: boolean) of object;
     TEphyNotebookTabFocusOnlyEvent = procedure (Sender: TObject; const NewTabNum: integer) of object;
     //  Extended capatibilities notebook (icons, close buttons, reordering by drag&drop)
     TEphyNotebook = class(TGTKNotebook)
     private
       FShowCloseButtons: boolean;
       FShowTooltips: boolean;
       FAllowDragDrop: boolean;
       FAllowDragOutside: boolean;
       FTooltips: PGtkTooltips;
       FOnNotebookReordered: TEphyNotebookReorderedEvent;
       FOnTabClose: TEphyNotebookTabCloseEvent;
       FOnTabDoubleClick: TEphyNotebookTabDoubleClickEvent;
       FOnFindNotebookAtPointer: TEphyNotebookFindNotebookAtPointerEvent;
       FOnMoveTabToAnotherNotebook: TEphyNotebookMoveTabToAnotherNotebookEvent;
       FOnTabSwitchedEvent: TEphyNotebookTabSwitchedEvent;
       FOnTabFocusOnlyEvent: TEphyNotebookTabFocusOnlyEvent;

       //  Notebook private
       motion_notify_handler_id, grab_notify_handler_id, toplevel_grab_broken_handler_id, toplevel_motion_notify_handler_id,
       toplevel_button_release_handler_id: gulong;
       x_start, y_start, drag_start_idx: gint;
       drag_in_progress: gboolean;
       cursor: PGdkCursor;

       FBusy: boolean;

       procedure SetShowCloseButtons(Value: boolean);
       procedure SetShowTooltips(Value: boolean);
       procedure SetAllowDragDrop(Value: boolean);
       procedure SetAllowDragOutside(Value: boolean);

       function find_tab_num_at_pos(abs_x, abs_y: integer): integer;
       function find_notebook_at_pointer(abs_x, abs_y: integer): TEphyNotebook;
       function is_in_notebook_window(notebook: TEphyNotebook; abs_x, abs_y: integer): boolean;
       function drag_start(time: guint32): gboolean;
       procedure drag_stop(time: guint32);
       procedure move_tab(dest_position: integer);
       procedure move_tab_to_another_notebook(dest: TEphyNotebook; event: PGdkEventMotion);

       function GetPageIndex: integer;
       procedure SetPageIndex(Value: integer);
     public
       constructor Create(AOwner: TComponent); override;
       destructor Destroy; override;
       function AppendPage(Child: TGTKControl; Caption: string): integer;
       function InsertPage(Position: integer; Child: TGTKControl; Caption: string): integer;
       procedure RemovePage(PageNo: integer);
       function GetTabCaption(PageNo: integer): string;
       procedure SetTabCaption(PageNo: integer; Caption: string);
       procedure SetTabTooltip(PageNo: integer; Tooltip: string);
     published
       property PageIndex: integer read GetPageIndex write SetPageIndex;
       property ShowCloseButtons: boolean read FShowCloseButtons write SetShowCloseButtons default False;
       property ShowTooltips: boolean read FShowTooltips write SetShowTooltips default False;
       property AllowDragDrop: boolean read FAllowDragDrop write SetAllowDragDrop default False;
       property AllowDragOutside: boolean read FAllowDragOutside write SetAllowDragOutside default False;
       property OnNotebookReordered: TEphyNotebookReorderedEvent read FOnNotebookReordered write FOnNotebookReordered;
       property OnTabClose: TEphyNotebookTabCloseEvent read FOnTabClose write FOnTabClose;
       property OnTabDoubleClick: TEphyNotebookTabDoubleClickEvent read FOnTabDoubleClick write FOnTabDoubleClick;
       property OnFindNotebookAtPointer: TEphyNotebookFindNotebookAtPointerEvent read FOnFindNotebookAtPointer write FOnFindNotebookAtPointer;
       property OnMoveTabToAnotherNotebook: TEphyNotebookMoveTabToAnotherNotebookEvent read FOnMoveTabToAnotherNotebook write FOnMoveTabToAnotherNotebook;
       property OnTabSwitched: TEphyNotebookTabSwitchedEvent read FOnTabSwitchedEvent write FOnTabSwitchedEvent;
       property OnTabFocusOnlyEvent: TEphyNotebookTabFocusOnlyEvent read FOnTabFocusOnlyEvent write FOnTabFocusOnlyEvent;
     end;




function MessageBoxShowOnce(ParentWindow: PGtkWindow; const Text: string; const DontShowAgainText: string; var DontShowAgainChecked: boolean;
                            Buttons: TMessageButtons = [mbOK]; Style: TMessageStyle = mbInfo;
                            Default: TMessageButton = mbNone; Escape: TMessageButton = mbNone): TMessageButton;

procedure HandleVFSAskQuestionCallback(DialogParent: PGtkWidget;
                                       const AMessage: PChar;
                                       const Choices: PPChar;
                                       choice: PInteger);

function HandleVFSAskPasswordCallback(DialogParent: PGtkWidget;
                                      const AMessage: PChar;
                                      const default_user: PChar;
                                      const default_domain: PChar;
                                      const default_password: PChar;
                                      flags: TVFSAskPasswordFlags;
                                      username: PPChar;
                                      password: PPChar;
                                      anonymous: PInteger;
                                      domain: PPChar;
                                      password_save: PVFSPasswordSave): LongBool;



type PGnomeColorPicker = PGtkWidget;
     PGnomeIconEntry = PGtkWidget;
     PGnomeDateEdit = PGtkWidget;

const AFTER_ALL_TABS = -1;
      NOT_IN_APP_WINDOWS = -2;

var libGlib2Handle, libGtk2Handle, libGnome2Handle, libGnomeUI2Handle: Pointer;
    gnome_about_new: function (const name, version, copyright, comments: Pchar; const authors, documenters: PPchar;
                               const translator_credits: Pchar; logo_pixbuf: PGdkPixbuf): PGtkWidget; cdecl;
{$IFDEF KYLIX}
    gnome_program_init: function (const app_id, app_version: PChar; const module_info: Pointer; argc: longint; argv: PPChar;
                                  const first_property_name: PChar; const first_property_value: Int64; const second_property_name: PChar): Pointer; cdecl;
{$ELSE}
    gnome_program_init: function (const app_id, app_version: PChar; const module_info: Pointer; argc: integer; argv: PPChar): Pointer; varargs; cdecl;
{$ENDIF}
    libgnome_module_info_get: function: Pointer; cdecl;
    libgnomeui_module_info_get: function: Pointer; cdecl;
    gnome_color_picker_new: function: PGnomeColorPicker; cdecl;
    gnome_color_picker_get_i16: procedure (cp: PGnomeColorPicker; R, G, B, A: Pword); cdecl;
    gnome_color_picker_set_i16: procedure (cp: PGnomeColorPicker; R, G, B, A: word); cdecl;
    gnome_icon_entry_new: function (const history_id, browse_dialog_title: Pchar): PGtkWidget; cdecl;
    gnome_icon_entry_set_pixmap_subdir: procedure (ientry: PGnomeIconEntry; const subdir: Pgchar); cdecl;
    gnome_icon_entry_get_filename: function (ientry: PGnomeIconEntry): Pgchar; cdecl;
    gnome_icon_entry_set_filename: function (ientry: PGnomeIconEntry; const filename: Pgchar): gboolean; cdecl;
    gnome_date_edit_new: function (the_time: time_t; show_time, use_24_format: gboolean): PGtkWidget; cdecl;
    gnome_date_edit_set_time: procedure (gde: PGnomeDateEdit; the_time: time_t); cdecl;
    gnome_date_edit_get_time: function (gde: PGnomeDateEdit): time_t; cdecl;

    gtk_event_box_set_visible_window: procedure (event_box: PGtkEventBox; visible_window: gboolean); cdecl;
    gtk_icon_size_lookup_for_settings: function (settings: PGtkSettings; size: TGtkIconSize; width, height: Pgint): gboolean; cdecl;
    gtk_window_set_icon_name: procedure (window: PGtkWindow; const name: Pgchar); cdecl;
    g_filename_display_name: function (const filename: PChar): PChar; cdecl;
{$IFDEF KYLIX}
    gtk_message_dialog_new_with_markup: function (parent:PGtkWindow; flags:TGtkDialogFlags;
                                                  _type:TGtkMessageType; buttons:TGtkButtonsType;
                                                  message_format:Pgchar; const format1: PChar; const format2: PChar):PGtkWidget; cdecl;
{$ELSE}
    gtk_message_dialog_new_with_markup: function (parent:PGtkWindow; flags:TGtkDialogFlags;
                                                  _type:TGtkMessageType; buttons:TGtkButtonsType;
                                                  message_format:Pgchar):PGtkWidget; varargs; cdecl;
{$ENDIF}
    g_mkdir_with_parents: function (const pathname: PChar; mode: integer): integer; cdecl;




procedure LoadGnomeLibs;

implementation

uses SysUtils, DateUtils, UConfig, UCoreUtils, ULocale, UFileAssoc;


(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TGnomeColorButton_OnClick(button: PGtkButton; user_data: Pgpointer); cdecl;
var Dialog: TGTKColorSelectionDialog;
    i: integer;
begin
  if Assigned(user_data) then
  with TGnomeColorButton(user_data) do begin
    Dialog := TGTKColorSelectionDialog.CreateWithTitle(Parent, LANGSelectFileTypeColor);
    try
      Dialog.ShowPalette := True;
      Dialog.Color := Color;
      if Byte(Dialog.Run) = 251 then begin
        for i := 0 to 4 do SetBackgroundColor(i, GDKColorToPGdkColor(Dialog.Color));
        FColor := Dialog.Color;
        if Assigned(FColorChanged) then FColorChanged(TGnomeColorButton(user_data));
      end;
    finally
      Dialog.Free;
    end;
  end;
end;

procedure TGnomeColorButton_color_set(colorpicker: PGnomeColorPicker; arg1, arg2, arg3, arg4: Word; user_data: Pointer); cdecl;
begin
  if Assigned(user_data) and Assigned(TGnomeColorButton(user_data).FColorChanged) then
  with TGnomeColorButton(user_data) do begin
    FColorChanged(TGnomeColorButton(user_data));
    FColor := Color;
  end;
end;

constructor TGnomeColorButton.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FUseGnomeUI := Assigned(gnome_color_picker_new) and Assigned(gnome_color_picker_get_i16) and Assigned(gnome_color_picker_set_i16);
  if FUseGnomeUI then begin
    FWidget := gnome_color_picker_new;
    g_signal_connect(PGtkObject(FWidget), 'color-set', G_CALLBACK(@TGnomeColorButton_color_set), Self);
  end else begin
    FWidget := gtk_button_new_with_label('');   //  This should be here due to height-related troubles
    g_signal_connect(PGtkObject(FWidget), 'clicked', G_CALLBACK(@TGnomeColorButton_OnClick), Self);
    BorderStyle := bsHalf;
  end;
  Show;
  FColorChanged := nil;
end;

destructor TGnomeColorButton.Destroy;
begin
  inherited Destroy;
end;

function TGnomeColorButton.GetColor: TGDKColor;
var R, G, B, A: word;
begin
  if FUseGnomeUI then begin
    if Enabled then begin
      gnome_color_picker_get_i16(FWidget, @R, @G, @B, @A);
      Result := PGdkColorToGDKColor(AllocateColor(nil, R, G, B));
    end else Result := FColor;
  end else Result := FColor;
end;

procedure TGnomeColorButton.SetColor(Value: TGDKColor);
var i: integer;
begin
  FColor := Value;
  if FUseGnomeUI then gnome_color_picker_set_i16(FWidget, Value.red, Value.green, Value.blue, 0)
  else for i := 0 to 4 do SetBackgroundColor(i, GDKColorToPGdkColor(Value));
end;

procedure TGnomeColorButton.SetDefaultColor;
var i: integer;
begin
  if not Enabled then Exit;
  gtk_widget_set_sensitive(PGtkWidget(FWidget), False);
  if FUseGnomeUI then begin
    FColor := GetColor;
    gnome_color_picker_set_i16(FWidget, 0, 0, 0, 0)
//    SetColor(PGdkColorToGDKColor(AllocateColor(FWidget, 0, 0, 0){GetDefaultBackgroundColor(1)}));
  end else for i := 0 to 4 do SetBackgroundColor(i, GetDefaultBackgroundColor(i));
end;

procedure TGnomeColorButton.UnsetDefaultColor;
var i: integer;
begin
  if Enabled then Exit;
  gtk_widget_set_sensitive(PGtkWidget(FWidget), True);
  if FUseGnomeUI then SetColor(FColor) else
  for i := 0 to 4 do SetBackgroundColor(i, GDKColorToPGdkColor(FColor));
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
procedure TGnomeIconEntry_changed(iconentry: PGnomeIconEntry; user_data: gpointer); cdecl;
begin
  if Assigned(user_data) and Assigned(TGnomeIconEntry(user_data).FIconChanged) then
    TGnomeIconEntry(user_data).FIconChanged(TGnomeIconEntry(user_data));
end;

constructor TGnomeIconEntry.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FIconChanged := nil;
  FWidget := gnome_icon_entry_new('History', 'Browse');
  g_signal_connect(PGtkObject(FWidget), 'changed', G_CALLBACK(@TGnomeIconEntry_changed), Self);
  Show;
end;

destructor TGnomeIconEntry.Destroy;
begin
  inherited Destroy;
end;

function TGnomeIconEntry.GetFilename: string;
begin
  Result := gnome_icon_entry_get_filename(FWidget);
end;

procedure TGnomeIconEntry.SetFilename(Value: string);
begin
  gnome_icon_entry_set_filename(FWidget, PChar(Value));
end;

procedure TGnomeIconEntry.SetPixmapSubdir(Value: string);
begin
  gnome_icon_entry_set_pixmap_subdir(FWidget, PChar(Value));
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)
constructor TGnomeDateEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FWidget := gnome_date_edit_new(0, False, True);
  Show;
end;

destructor TGnomeDateEdit.Destroy;
begin
  inherited Destroy;
end;

function TGnomeDateEdit.GetTime: time_t;
begin
  Result := gnome_date_edit_get_time(FWidget);
end;

procedure TGnomeDateEdit.SetTime(Value: time_t);
begin
  gnome_date_edit_set_time(FWidget, Value);
end;

(********************************************************************************************************************************)
(********************************************************************************************************************************)

function button_press_cb(notebook: PGtkWidget; event: PGdkEventButton; data: gpointer): gboolean; cdecl; forward;
function button_release_cb(notebook: PGtkWidget; event: PGdkEventButton; data: gpointer): gboolean; cdecl; forward;
function scroll_event_callback(widget: PGtkWidget; event: PGdkEventScroll; user_data: gpointer): gboolean; cdecl; forward;
function label_ebox_button_pressed(widget: PGtkWidget; event: PGdkEventButton; data: pointer): gboolean; cdecl; forward;

constructor TEphyNotebook.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FTooltips := gtk_tooltips_new;
  gtk_tooltips_disable(FTooltips);
  cursor := nil;
  FShowCloseButtons := False;
  FShowTooltips := False;
  FAllowDragDrop := False;
  FAllowDragOutside := False;
  FOnNotebookReordered := nil;
  FOnTabClose := nil;
  FOnTabDoubleClick := nil;
  FOnFindNotebookAtPointer := nil;
  FOnMoveTabToAnotherNotebook := nil;
  FOnTabFocusOnlyEvent := nil;
  FBusy := False;
  
  // Set up drag-and-drop
  g_signal_connect(FWidget, 'button-press-event', G_CALLBACK(@button_press_cb), Self);
  g_signal_connect(FWidget, 'button-release-event', G_CALLBACK(@button_release_cb), Self);
  g_signal_connect(FWidget, 'scroll-event', G_CALLBACK(@scroll_event_callback), Self);
  gtk_widget_add_events(FWidget, GDK_BUTTON1_MOTION_MASK);

  Show;
end;

destructor TEphyNotebook.Destroy;
begin
  inherited Destroy;
end;

procedure TEphyNotebook.SetShowCloseButtons(Value: boolean);
begin
  FShowCloseButtons := Value;

  //  Apply settings here
end;


procedure TEphyNotebook.SetShowTooltips(Value: boolean);
begin
  FShowTooltips := Value;
  if Value then gtk_tooltips_enable(FTooltips)
           else gtk_tooltips_disable(FTooltips);
end;

procedure TEphyNotebook.SetAllowDragDrop(Value: boolean);
begin
  FAllowDragDrop := Value;
  if (not Value) and drag_in_progress then drag_stop(GDK_CURRENT_TIME (* FIXME? *));
end;

procedure TEphyNotebook.SetAllowDragOutside(Value: boolean);
begin
  FAllowDragOutside := Value;
  if (not Value) and drag_in_progress then drag_stop(GDK_CURRENT_TIME (* FIXME? *));
end;

//  Following code is inspired by ephy-notebook.c from Epiphany 2.14.1
procedure tab_label_style_set_cb(hbox: PGtkWidget; previous_style: PGtkStyle; user_data: gpointer); cdecl;
var button: PGtkWidget;
{    metrics: PPangoFontMetrics;
    context: PPangoContext;
    char_width, }h, w: integer;
begin
{  context := gtk_widget_get_pango_context(hbox);
  metrics := pango_context_get_metrics(context, hbox^.style^.font_desc, pango_context_get_language(context));

  char_width := pango_font_metrics_get_approximate_digit_width(metrics);
  pango_font_metrics_unref(metrics); }

  if @gtk_icon_size_lookup_for_settings <> nil
    then gtk_icon_size_lookup_for_settings(gtk_widget_get_settings(hbox), GTK_ICON_SIZE_MENU, @w, @h)
    else begin w := 16; h := 16; end;

//  gtk_widget_set_size_request(hbox, TAB_WIDTH_N_CHARS * PANGO_PIXELS(char_width) + 2 * w, -1);

  button := g_object_get_data(G_OBJECT(hbox), 'close-button');
  gtk_widget_set_size_request(button, w + 2, h + 2);
end;

procedure close_button_clicked_cb(widget: PGtkWidget; data: pointer); cdecl;
var tab: PGtkWidget;
    ntb: TEphyNotebook;
    position: integer;
    CanClose: boolean;
begin
  tab := g_object_get_data(G_OBJECT(widget), 'child');
  ntb := TEphyNotebook(data);

  position := gtk_notebook_page_num(GTK_NOTEBOOK(ntb.FWidget), GTK_WIDGET(tab));
//  DebugMsg(['@******* close_button_clicked_cb: Position = ', position, ', data = 0x', IntToHex(Integer(data), 8), ', widget = 0x', IntToHex(Integer(widget), 8)]);

  CanClose := True;

  if @ntb.FOnTabClose <> nil then ntb.FOnTabClose(ntb, position, CanClose);
  
  if CanClose then gtk_notebook_remove_page(GTK_NOTEBOOK(ntb.FWidget), position);
end;

function TEphyNotebook.AppendPage(Child: TGTKControl; Caption: string): integer;
begin
  Result := InsertPage(ChildrenCount, Child, Caption);
end;

function TEphyNotebook.InsertPage(Position: integer; Child: TGTKControl; Caption: string): integer;
var hbox, label_hbox, label_ebox, _label, close_button, image, icon: PGtkWidget;
    rcstyle: PGtkRcStyle;
begin
  // set hbox spacing and label padding (see below) so that there's an equal amount of space around the label
  hbox := gtk_hbox_new(FALSE, 0);

  label_ebox := gtk_event_box_new();
  if @gtk_event_box_set_visible_window <> nil then gtk_event_box_set_visible_window(GTK_EVENT_BOX (label_ebox), FALSE);
  gtk_box_pack_start(GTK_BOX (hbox), label_ebox, TRUE, TRUE, 0);
  g_signal_connect(G_OBJECT(label_ebox), 'button-press-event', G_CALLBACK(@label_ebox_button_pressed), Self);
  g_object_set_data(G_OBJECT(label_ebox), 'child', Child.FWidget);

  label_hbox := gtk_hbox_new (FALSE, 0);
  gtk_container_add(GTK_CONTAINER (label_ebox), label_hbox);

  // setup close button
  close_button := gtk_button_new ();
  gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);
  // don't allow focus on the close button
  g_object_set(G_OBJECT(close_button), 'can-focus', gboolean(FALSE), nil);
  gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE);

  rcstyle := gtk_rc_style_new ();
  rcstyle^.xthickness := Ord(rcstyle^.ythickness = 0);
  gtk_widget_modify_style (close_button, rcstyle);
  gtk_rc_style_unref (rcstyle);

  image := gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU);
  gtk_container_add (GTK_CONTAINER (close_button), image);
  gtk_box_pack_start (GTK_BOX (hbox), close_button, FALSE, FALSE, 0);

  gtk_tooltips_set_tip(FTooltips, close_button, 'Close tab', nil);
  g_object_set_data(G_OBJECT(close_button), 'tab', hbox);
  g_object_set_data(G_OBJECT(close_button), 'child', Child.FWidget);
  g_signal_connect(G_OBJECT(close_button), 'clicked', G_CALLBACK(@close_button_clicked_cb), Self);

  // setup site icon, empty by default
  icon := gtk_image_new ();
  gtk_box_pack_start (GTK_BOX (label_hbox), icon, FALSE, FALSE, 0);

  // setup label
  _label := gtk_label_new(PChar(Caption));
//  gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
//  gtk_label_set_single_line_mode (GTK_LABEL (label), TRUE);
  gtk_misc_set_alignment (GTK_MISC(_label), 0.0, 0.5);
  gtk_misc_set_padding (GTK_MISC(_label), 2, 0);
  gtk_box_pack_start (GTK_BOX(label_hbox), _label, TRUE, TRUE, 0);

  // Set minimal size
  g_signal_connect(hbox, 'style-set', G_CALLBACK(@tab_label_style_set_cb), nil);


  gtk_widget_show (hbox);
  gtk_widget_show (label_ebox);
  gtk_widget_show (label_hbox);
  gtk_widget_show (_label);
  gtk_widget_show (image);
  if FShowCloseButtons then gtk_widget_show (close_button);

  g_object_set_data (G_OBJECT (hbox), 'label', _label);
  g_object_set_data (G_OBJECT (hbox), 'label-ebox', label_ebox);
  g_object_set_data (G_OBJECT (hbox), 'icon', icon);
  g_object_set_data (G_OBJECT (hbox), 'close-button', close_button);
  g_object_set_data (G_OBJECT (hbox), 'tooltips', FTooltips);

  Result := gtk_notebook_insert_page(PGtkNotebook(FWidget), Child.FWidget, hbox, Position);
end;

procedure TEphyNotebook.RemovePage(PageNo: integer);
begin
  gtk_notebook_remove_page(PGtkNotebook(FWidget), PageNo);
end;

function TEphyNotebook.GetTabCaption(PageNo: integer): string;
var wid, lab: PGtkWidget;
begin
  wid := gtk_notebook_get_tab_label(PGtkNotebook(FWidget), gtk_notebook_get_nth_page(PGtkNotebook(FWidget), PageNo));
  lab := g_object_get_data (G_OBJECT(wid), 'label');
  Result := PgcharToString(gtk_label_get_text(GTK_LABEL(lab)));
end;

procedure TEphyNotebook.SetTabCaption(PageNo: integer; Caption: string);
var wid, lab: PGtkWidget;
begin
  wid := gtk_notebook_get_tab_label(PGtkNotebook(FWidget), gtk_notebook_get_nth_page(PGtkNotebook(FWidget), PageNo));
  lab := g_object_get_data (G_OBJECT(wid), 'label');
  gtk_label_set_text(GTK_LABEL(lab), StringToPgchar(Caption));
end;

procedure TEphyNotebook.SetTabTooltip(PageNo: integer; Tooltip: string);
var wid, lab: PGtkWidget;
begin
  wid := gtk_notebook_get_tab_label(PGtkNotebook(FWidget), gtk_notebook_get_nth_page(PGtkNotebook(FWidget), PageNo));
  lab := g_object_get_data (G_OBJECT(wid), 'label-ebox');
  gtk_tooltips_set_tip(FTooltips, lab, StringToPgchar(Tooltip), nil);
end;

function TEphyNotebook.GetPageIndex: integer;
begin
  Result := gtk_notebook_get_current_page(PGtkNotebook(FWidget));
end;

procedure TEphyNotebook.SetPageIndex(Value: integer);
begin
  DebugMsg(['SetPageIndex(Value = ', Value, ')']);
  if (GetPageIndex <> Value) and (Value >= 0) then begin
    gtk_notebook_set_page(PGtkNotebook(FWidget), Value);
    if Assigned(FOnTabSwitchedEvent) then FOnTabSwitchedEvent(Self, Value, True);
  end;
end;


function TEphyNotebook.find_tab_num_at_pos(abs_x, abs_y: integer): integer;
var tab_pos: TGtkPositionType;
    page_num: integer;
    page, tab: PGtkWidget;
    max_x, max_y, x_root, y_root: integer;
begin
  page_num := 0;
  tab_pos := gtk_notebook_get_tab_pos(PGtkNotebook(FWidget));

  if PGtkNotebook(FWidget)^.first_tab = nil then begin
    DebugMsg(['@***************** first_tab = nil']);
    Result := AFTER_ALL_TABS;
//    DebugMsg(['@******* find_tab_num_at_pos(abs_x = ', abs_x, ', abs_y = ', abs_y, ', Result = ', Result, ')']);
    Exit;
  end;

  // For some reason unfullscreen + quick click can cause a wrong click event to be reported to the tab
  if not is_in_notebook_window(Self, abs_x, abs_y) then begin
    Result := NOT_IN_APP_WINDOWS;
//    DebugMsg(['@******* find_tab_num_at_pos(abs_x = ', abs_x, ', abs_y = ', abs_y, ', Result = ', Result, ')']);
    Exit;
  end;

  while gtk_notebook_get_nth_page(PGtkNotebook(FWidget), page_num) <> nil do
  begin
    page := gtk_notebook_get_nth_page(PGtkNotebook(FWidget), page_num);
    tab := gtk_notebook_get_tab_label(PGtkNotebook(FWidget), page);
    if tab = nil then begin
      Result := AFTER_ALL_TABS;
//      DebugMsg(['@******* find_tab_num_at_pos(abs_x = ', abs_x, ', abs_y = ', abs_y, ', Result = ', Result, ')']);
      Exit;
    end;

    if (not GTK_WIDGET_MAPPED(GTK_WIDGET(tab))) then begin
      Inc(page_num);
      Continue;
    end;

    gdk_window_get_origin(GDK_WINDOW(tab^.window), @x_root, @y_root);

    max_x := x_root + tab^.allocation.x + tab^.allocation.width;
    max_y := y_root + tab^.allocation.y + tab^.allocation.height;

    if (((tab_pos = GTK_POS_TOP) or (tab_pos = GTK_POS_BOTTOM)) and (abs_x <= max_x)) or
       ((tab_pos = GTK_POS_LEFT) or (tab_pos = GTK_POS_RIGHT)) and (abs_y <= max_y) then
    begin
      Result := page_num;
//      DebugMsg(['@******* find_tab_num_at_pos(abs_x = ', abs_x, ', abs_y = ', abs_y, ', Result = ', Result, ')']);
      Exit;
    end;

    Inc(page_num);
  end;
  Result := AFTER_ALL_TABS;
//  DebugMsg(['@******* find_tab_num_at_pos(abs_x = ', abs_x, ', abs_y = ', abs_y, ', Result = ', Result, ')']);
end;

function TEphyNotebook.find_notebook_at_pointer(abs_x, abs_y: integer): TEphyNotebook;
(* var win_at_pointer{, toplevel_win}: PGdkWindow;
    x, y: integer;
//    toplevel: pointer;  *)
begin
  Result := nil;
  if Assigned(FOnFindNotebookAtPointer) then Result := FOnFindNotebookAtPointer(Self, abs_x, abs_y);
  

//        toplevel := NULL;
{
        // FIXME multi-head
        win_at_pointer := gdk_window_at_pointer(@x, @y);
        if win_at_pointer = nil then begin
          // We are outside all windows containing a notebook
          Result := nil;
          Exit;
        end;
}

(*        toplevel_win := gdk_window_get_toplevel(win_at_pointer);

        // get the GtkWidget which owns the toplevel GdkWindow
        gdk_window_get_user_data(toplevel_win, @toplevel);

        // toplevel should be an EphyWindow
{@@@}        if (toplevel <> nil) and GTK_IS_WINDOW toplevel) then
        begin
          Result := EPHY_NOTEBOOK(ephy_window_get_notebook(EPHY_WINDOW (toplevel)));
          Exit;
        end;
  *)
{        Result := nil;}
end;

function TEphyNotebook.is_in_notebook_window(notebook: TEphyNotebook; abs_x, abs_y: integer): boolean;
begin
  Result := find_notebook_at_pointer(abs_x, abs_y) <> nil;
//  Result := notebook = find_notebook_at_pointer(abs_x, abs_y);
//  Result := True;
end;




(*
void
terminal_notebook_move_tab (TerminalNotebook *src,
                            TerminalNotebook *dest,
                            TerminalScreen *screen,
                            int dest_position)
{
  if (dest == NULL || src == dest)
    {
      gtk_notebook_reorder_child
        (GTK_NOTEBOOK (src), GTK_WIDGET (screen), dest_position);

      if (src->priv->drag_in_progress == FALSE)
        {
          g_signal_emit (G_OBJECT (src), signals[TABS_REORDERED], 0);
        }
    }
  else
    {
      GtkWidget *toplevel;

      /* make sure the screen isn't destroyed while we move it */
      g_object_ref (screen);

      terminal_notebook_remove_tab (src, screen);

      /* Set new window for screen so TerminalScreen widget realize function
       * works.
       */
      toplevel = gtk_widget_get_toplevel (GTK_WIDGET (dest));

      g_assert (GTK_WIDGET_TOPLEVEL (toplevel));
      g_assert (TERMINAL_IS_WINDOW (toplevel));

      terminal_screen_set_window (screen, TERMINAL_WINDOW(toplevel));

      terminal_notebook_add_tab (dest, screen, dest_position, TRUE);
      g_object_unref (screen);
    }
}      *)

procedure TEphyNotebook.drag_stop(time: guint32);
var toplevel{, child}: PGtkWidget;
    drag_stop_idx: integer;
begin
  DebugMsg(['@######## drag_stop']);

  toplevel := gtk_widget_get_toplevel(FWidget);
  if not GTK_WIDGET_TOPLEVEL(toplevel) then begin
    DebugMsg(['@######## drag_stop: GTK_WIDGET_TOPLEVEL(toplevel) = False']);
    Exit;
  end;
{    child := gtk_bin_get_child(GTK_BIN(toplevel));
    if child = nil then begin
      DebugMsg(['@######## drag_stop: child = nil']);
      Exit;
    end;    }

  // disconnect the signals before ungrabbing!
  if toplevel_grab_broken_handler_id <> 0 then begin
    g_signal_handler_disconnect(toplevel, toplevel_grab_broken_handler_id);
    toplevel_grab_broken_handler_id := 0;
  end;
  if grab_notify_handler_id <> 0 then begin
    g_signal_handler_disconnect(FWidget, grab_notify_handler_id);
    grab_notify_handler_id := 0;
  end;
  if toplevel_motion_notify_handler_id <> 0 then begin
    g_signal_handler_disconnect(toplevel, toplevel_motion_notify_handler_id);
    toplevel_motion_notify_handler_id := 0;
  end;
  if toplevel_button_release_handler_id <> 0 then begin
    g_signal_handler_disconnect(toplevel, toplevel_button_release_handler_id);
    toplevel_button_release_handler_id := 0;
  end;
  if motion_notify_handler_id <> 0 then begin
    g_signal_handler_disconnect(FWidget, motion_notify_handler_id);
    motion_notify_handler_id := 0;
  end;

  // ungrab the pointer if it's grabbed
  // FIXME multihead
  if gdk_pointer_is_grabbed() then gdk_pointer_ungrab(time);
  gtk_grab_remove(toplevel);

  if drag_in_progress then begin
    if FBusy then Exit;
    drag_stop_idx := gtk_notebook_get_current_page(GTK_NOTEBOOK(FWidget));
    DebugMsg(['@######## drag_stop: final move: ', drag_start_idx, ' --> ', drag_stop_idx]);
    
    //  The two indexes are equal when moving tab to another notebook
    if (@FOnNotebookReordered <> nil) and (drag_start_idx <> drag_stop_idx) then begin
      FBusy := True;
      FOnNotebookReordered(Self, drag_start_idx, drag_stop_idx);
      Application.ProcessMessages;
      FBusy := False;
    end;
  end;

  drag_in_progress := False;
  x_start := -1;
  y_start := -1;
end;

function grab_broken_event_cb(widget: PGtkWidget; event: PGdkEvent; data: gpointer): gboolean; cdecl;
begin
  if data <> nil then TEphyNotebook(data).drag_stop(GDK_CURRENT_TIME (* FIXME? *));
  Result := False;
end;

procedure grab_notify_cb(widget: PGtkWidget; was_grabbed: gboolean; data: gpointer); cdecl;
begin
  if data <> nil then TEphyNotebook(data).drag_stop(GDK_CURRENT_TIME (* FIXME? *));
end;

function toplevel_motion_notify_cb(widget: PGtkWidget; event: PGdkEventMotion; data: gpointer): gboolean; cdecl;
var ntb, dest: TEphyNotebook;
    page_num: integer;
begin
//  DebugMsg(['@######## toplevel_motion_notify_cb(..., data = 0x', IntToHex(Integer(data), 8), ')']);
  Result := False;
  ntb := TEphyNotebook(data);
  if ntb.FBusy then Exit;
  
  page_num := ntb.find_tab_num_at_pos(Trunc(event^.x_root), Trunc(event^.y_root));
//  if page_num >= -1 then ntb.move_tab(page_num);
  dest := ntb.find_notebook_at_pointer(Trunc(event^.x_root), Trunc(event^.y_root));

//  DebugMsg(['@######## toplevel_motion_notify_cb: page_num = ', page_num, ', dest = 0x', IntToHex(Integer(dest), 8), ')']);

  if (page_num <> NOT_IN_APP_WINDOWS) and (dest <> nil) then begin
    if (dest <> ntb) and ntb.FAllowDragOutside then ntb.move_tab_to_another_notebook(dest, event) else
      if page_num >= 0 then ntb.move_tab(page_num)  //  Same notebook
                       else {DebugMsg(['@######## toplevel_motion_notify_cb: Assertion failed: page_num >= 0'])};
  end;


(*  TerminalNotebook *dest = NULL;
  int page_num, result;

  result = find_notebook_and_tab_at_pos ((gint)event->x_root,
                                         (gint)event->y_root,
                                         &dest, &page_num);

  if (result != NOT_IN_APP_WINDOWS)
    {
      if (dest != notebook)
        {
          move_tab_to_another_notebook (notebook, dest,
                                        event, page_num);
        }
      else
        {
          g_assert (page_num >= -1);
          move_tab (notebook, page_num);
        }
    }
                 *)
end;

function toplevel_button_release_cb(widget: PGtkWidget; event: PGdkEventButton; data: gpointer): gboolean; cdecl;
var ntb: TEphyNotebook;
//    cur_page_num: integer;
//    cur_page: PGtkWidget;
begin
  DebugMsg(['@######## toplevel_button_release_cb(..., data = 0x', IntToHex(Integer(data), 8), ')']);
  ntb := TEphyNotebook(data);

  if ntb.drag_in_progress then begin
//    cur_page_num := gtk_notebook_get_current_page(GTK_NOTEBOOK(ntb.FWidget));
//    cur_page := gtk_notebook_get_nth_page(GTK_NOTEBOOK(ntb.FWidget), cur_page_num);

(*    if (!is_in_notebook_window (notebook, event->x_root, event->y_root)
          && gtk_notebook_get_n_pages (GTK_NOTEBOOK (notebook)) > 1)
        {
          /* Tab was detached */
          g_signal_emit (G_OBJECT (notebook),
                         signals[TAB_DETACHED], 0, cur_page);
        } *)
  end;

  // This must be called even if a drag isn't happening
  ntb.drag_stop(event^.time);
  Result := False;
end;

function TEphyNotebook.drag_start(time: guint32): gboolean;
var toplevel, child: PGtkWidget;
begin
  DebugMsg(['@######## drag_start']);
  Result := False;
  // FIXME multihead
  if drag_in_progress or gdk_pointer_is_grabbed() then Exit;
  if not FAllowDragDrop then Exit;
  if FBusy then Exit;

  drag_in_progress := True;

  // get a new cursor, if necessary
  // FIXME multi-head
  if cursor = nil then cursor := gdk_cursor_new(GDK_FLEUR);

  toplevel := gtk_widget_get_toplevel(FWidget);
  if not GTK_WIDGET_TOPLEVEL(toplevel) then begin
    DebugMsg(['@######## drag_start: GTK_WIDGET_TOPLEVEL(toplevel) = False']);
    Result := False;
    Exit;
  end;

  child := gtk_bin_get_child(GTK_BIN(toplevel));
  if child = nil then begin
    DebugMsg(['@######## drag_start: child = nil']);
    Result := False;
    Exit;
  end;

  // grab the pointer
  gtk_grab_add(toplevel);

  // FIXME multi-head
  if gdk_pointer_grab(toplevel^.window, False, GDK_BUTTON1_MOTION_MASK or GDK_BUTTON_RELEASE_MASK, nil,
                      cursor, time) <> GDK_GRAB_SUCCESS then
  begin
    DebugMsg(['@######## drag_start: gdk_pointer_grab <> GDK_GRAB_SUCCESS']);
    drag_stop(time);
    Result := False;
    Exit;
  end;

  if (toplevel_grab_broken_handler_id = 0) and (toplevel_motion_notify_handler_id = 0) and
     (toplevel_button_release_handler_id = 0) and (grab_notify_handler_id = 0) then
  begin
    toplevel_grab_broken_handler_id := g_signal_connect(toplevel, 'grab-broken-event', G_CALLBACK(@grab_broken_event_cb), Self);
    toplevel_motion_notify_handler_id := g_signal_connect(toplevel, 'motion-notify-event', G_CALLBACK(@toplevel_motion_notify_cb), Self);
    toplevel_button_release_handler_id := g_signal_connect(toplevel, 'button-release-event', G_CALLBACK(@toplevel_button_release_cb), Self);
    grab_notify_handler_id := g_signal_connect(FWidget, 'grab-notify', G_CALLBACK(@grab_notify_cb), Self);
    
    drag_start_idx := gtk_notebook_get_current_page(GTK_NOTEBOOK(FWidget));
    Result := True;
  end else begin
    DebugMsg(['@######## drag_start: some handlers are non-null']);
    Result := False;
  end;
end;

// this function is only called during dnd, we don't need to emit TABS_REORDERED here, instead we do it on drag_stop
procedure TEphyNotebook.move_tab(dest_position: integer);
var cur_page_num: integer;
    cur_tab: PGtkWidget;
begin
  cur_page_num := gtk_notebook_get_current_page(GTK_NOTEBOOK(FWidget));
//  DebugMsg(['@######## move_tab(dest_position = ', dest_position, ', cur_page_num = ', cur_page_num]);
  if (dest_position <> cur_page_num) and (dest_position >= 0) then begin
    cur_tab := gtk_notebook_get_nth_page(GTK_NOTEBOOK(FWidget), cur_page_num);
    gtk_notebook_reorder_child(GTK_NOTEBOOK(FWidget), cur_tab, dest_position);

{    terminal_notebook_move_tab (TERMINAL_NOTEBOOK (notebook), NULL,
                                  TERMINAL_SCREEN (cur_tab),
                                  dest_position); }
  end;
end;

function motion_notify_cb(notebook: PGtkWidget; event: PGdkEventMotion; data: gpointer): gboolean; cdecl;
var ntb: TEphyNotebook;
begin
//  DebugMsg(['@######## motion_notify_cb(..., data = 0x', IntToHex(Integer(data), 8), ')']);
  ntb := TEphyNotebook(data);
  Result := False;

  if (not ntb.drag_in_progress) and (ntb.x_start >= 0) and (ntb.y_start >= 0) and
      gtk_drag_check_threshold(GTK_WIDGET(notebook), ntb.x_start, ntb.y_start,
                               Trunc(event^.x_root), Trunc(event^.y_root))
    then Result := ntb.drag_start(event^.time);
end;

procedure TEphyNotebook.move_tab_to_another_notebook(dest: TEphyNotebook; event: PGdkEventMotion);
var cur_page, dest_page_num: integer;
begin
  // This is getting tricky, the screen was dragged in a notebook in another window of the same app, we move the screen
  // to that new notebook, and let this notebook handle the drag

  DebugMsg(['@######## move_tab_to_another_notebook(dest = 0x', IntToHex(Integer(pointer(dest)), 8), ')']);
  if not (dest is TEphyNotebook) then begin
    DebugMsg(['@######## move_tab_to_another_notebook: dest(', dest.ClassName, ') is not TEphyNotebook']);
    Exit;
  end;

  if Self = dest then begin
    DebugMsg(['@######## move_tab_to_another_notebook: dest = Self']);
    Exit;
  end;
  
  if not Assigned(@FOnMoveTabToAnotherNotebook) then begin
    DebugMsg(['@######## move_tab_to_another_notebook: FOnMoveTabToAnotherNotebook not handled, giving up.']);
    Exit;
  end;

  cur_page := gtk_notebook_get_current_page(GTK_NOTEBOOK(FWidget));

  // stop drag in origin window
  // ungrab the pointer if it's grabbed
  drag_stop(event^.time);

{  if gdk_pointer_is_grabbed() then gdk_pointer_ungrab(event^.time);
  gtk_grab_remove(FWidget); }


(*  g_assert (TERMINAL_IS_NOTEBOOK (dest));
  g_assert (dest != src);

  cur_page = gtk_notebook_get_current_page (GTK_NOTEBOOK (src));
  screen = gtk_notebook_get_nth_page (GTK_NOTEBOOK (src), cur_page);

  /* stop drag in origin window */
  /* ungrab the pointer if it's grabbed */
  drag_stop (src, event->time);
  if (gdk_pointer_is_grabbed ())
    {
      gdk_pointer_ungrab (event->time);
    }
  gtk_grab_remove (GTK_WIDGET (src));

  terminal_notebook_move_tab (src, dest, TERMINAL_SCREEN (screen),
                              dest_position);

  /* start drag handling in dest notebook */

  dest->priv->motion_notify_handler_id =
    g_signal_connect (G_OBJECT (dest),
                      "motion-notify-event",
                      G_CALLBACK (motion_notify_cb),
                      NULL);
  *)

  dest_page_num := dest.find_tab_num_at_pos(Trunc(event^.x_root), Trunc(event^.y_root));
  if FOnMoveTabToAnotherNotebook(Self, dest, cur_page, dest_page_num) then begin
    // move newly created tab in dest notebook to position of mouse cursor
//    toplevel_motion_notify_cb(dest.FWidget, event, dest);

    Application.ProcessMessages;

    // start drag handling in dest notebook
    dest.x_start := Trunc(event^.x_root);
    dest.y_start := Trunc(event^.y_root);
    dest.motion_notify_handler_id := g_signal_connect(G_OBJECT(dest.FWidget), 'motion-notify-event', G_CALLBACK(@motion_notify_cb), dest);
    dest.drag_start(event^.time);

    
{    dest.motion_notify_handler_id := g_signal_connect(G_OBJECT(dest.FWidget), 'motion-notify-event', G_CALLBACK(@motion_notify_cb), nil);
    dest.drag_start(event^.time);  }
  end;
end;

function button_release_cb(notebook: PGtkWidget; event: PGdkEventButton; data: gpointer): gboolean; cdecl;
var ntb: TEphyNotebook;
begin
  ntb := TEphyNotebook(data);
  DebugMsg(['@######## button_release_cb(..., data = 0x', IntToHex(Integer(data), 8), ')']);

  // This must be called even if a drag isn't happening
  ntb.drag_stop(event^.time);
  Result := False;
end;

function label_ebox_button_pressed(widget: PGtkWidget; event: PGdkEventButton; data: pointer): gboolean; cdecl;
var tab: PGtkWidget;
    ntb: TEphyNotebook;
    position: integer;
begin
  Result := False;
  ntb := TEphyNotebook(data);
  tab := g_object_get_data(G_OBJECT(widget), 'child');
  position := gtk_notebook_page_num(GTK_NOTEBOOK(ntb.FWidget), GTK_WIDGET(tab));
//  DebugMsg(['@******* label_ebox_button_pressed, button = ', event^.button, ', Position = ', position, ', data = 0x', IntToHex(Integer(data), 8), ', widget = 0x', IntToHex(Integer(widget), 8)]);

  if (((event^.button = 2) and (event^._type = GDK_BUTTON_PRESS)) or    //  Middle-click
     ((event^.button = 1) and (event^._type = GDK_2BUTTON_PRESS))) and  //  Double-click
     Assigned(ntb.FOnTabDoubleClick) then
  begin
    ntb.FOnTabDoubleClick(ntb, position);
    Result := True;
    ntb.x_start := -1;
    ntb.y_start := -1;
  end;
end;

function button_press_cb(notebook: PGtkWidget; event: PGdkEventButton; data: gpointer): gboolean; cdecl;
var tab_clicked: integer;
    ntb: TEphyNotebook;
begin
  Result := False;
  ntb := TEphyNotebook(data);
  if ntb.FBusy then Exit;

  DebugMsg(['@######## button_press_cb(..., data = 0x', IntToHex(Integer(data), 8), ')']);
  tab_clicked := ntb.find_tab_num_at_pos(Trunc(event^.x_root), Trunc(event^.y_root));
  DebugMsg(['@################# tab_clicked = ', tab_clicked]);

  if ntb.drag_in_progress then begin
    DebugMsg(['drag_in_progress, exiting.']);
    Result := True;
    Exit;
  end;

  if ((event^.button = 1) or (event^.button = 3)) and (event^._type = GDK_BUTTON_PRESS) and (tab_clicked >= 0) then
  begin
    if event^.button = 1 then begin
      ntb.x_start := Trunc(event^.x_root);
      ntb.y_start := Trunc(event^.y_root);
      DebugMsg(['@################# x_start = ', ntb.x_start, ', y_start = ', ntb.y_start]);
      ntb.motion_notify_handler_id := g_signal_connect(G_OBJECT(notebook), 'motion-notify-event', G_CALLBACK(@motion_notify_cb), data);
    end;
    
    if tab_clicked >= 0 then begin
      if tab_clicked <> gtk_notebook_get_current_page(PGtkNotebook(ntb.FWidget)) then begin
        ntb.FBusy := True;
        gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), tab_clicked);
        if Assigned(ntb.FOnTabSwitchedEvent) then ntb.FOnTabSwitchedEvent(ntb, tab_clicked, True);
        Application.ProcessMessages;
        ntb.FBusy := False;
      end else begin
        if Assigned(ntb.FOnTabFocusOnlyEvent) then ntb.FOnTabFocusOnlyEvent(ntb, tab_clicked);
      end;
    end;
    //  don't consume the event if right mouse button pressed - allow display the popup menu
    Result := event^.button <> 3;
  end else Result := True;  //  consume the event if not clicked any tab
{   else
  if (GDK_BUTTON_PRESS = event^._type) and (event^.button = 3) then begin
    if tab_clicked = -1 then begin
      // consume event, so that we don't pop up the context menu when the mouse if not over a screen label
      Result := True;
    end else
      // switch to the page the mouse is over, but don't consume the event
      if (tab_clicked >= 0) then gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), tab_clicked);
  end;  }
end;

function scroll_event_callback(widget: PGtkWidget; event: PGdkEventScroll; user_data: gpointer): gboolean; cdecl;
var ntb: TEphyNotebook;
    page: integer;
    child: PGtkWidget;
    px, py: gint;
begin
  Result := True;
  ntb := TEphyNotebook(user_data);
  if ntb.FBusy then Exit;
  
  child := gtk_notebook_get_nth_page(GTK_NOTEBOOK(ntb.FWidget), gtk_notebook_get_current_page(PGtkNotebook(ntb.FWidget)));
  if (child <> nil) then begin
//    DebugMsg(['scroll_event_callback: x = ', event^.x, ', y = ', event^.y]);
    px := 0;
    py := 0;
    gtk_widget_get_pointer(child, @px, @py);
//    DebugMsg(['scroll_event_callback: px = ', px, ', py = ', py]);
    if (px >= 0) and (py >= 0) then Exit;
  end;
  
  page := gtk_notebook_get_current_page(PGtkNotebook(ntb.FWidget))
          - Ord(event^.direction = GDK_SCROLL_UP) + Ord(event^.direction = GDK_SCROLL_DOWN);
  if (page < 0) or (page > ntb.ChildrenCount - 1) then Exit;

  ntb.FBusy := True;
  gtk_notebook_set_current_page(PGtkNotebook(ntb.FWidget), page);
  //* FIXME: should this be ShouldFocus = False?
  if Assigned(ntb.FOnTabSwitchedEvent) then ntb.FOnTabSwitchedEvent(ntb, page, True);
  Application.ProcessMessages;
  ntb.FBusy := False;
end;





(********************************************************************************************************************************)
(********************************************************************************************************************************)
function Gnome_MessageBox_key_press_event(widget: PGtkWidget; event: PGdkEventKey; user_data : gpointer): gboolean; cdecl;
begin
  Result := False;
  if event^.keyval = GDK_ESCAPE then begin
    gtk_dialog_response(PGtkDialog(widget), integer(user_data));
//    Beep;
    Result := True;
  end;
end;

function MessageBoxShowOnce(ParentWindow: PGtkWindow; const Text: string; const DontShowAgainText: string; var DontShowAgainChecked: boolean;
                            Buttons: TMessageButtons = [mbOK]; Style: TMessageStyle = mbInfo;
                            Default: TMessageButton = mbNone; Escape: TMessageButton = mbNone): TMessageButton;
const TMessageStyleID : array[0..3] of TGtkMessageType = (GTK_MESSAGE_ERROR, GTK_MESSAGE_INFO, GTK_MESSAGE_QUESTION, GTK_MESSAGE_WARNING);
var Dialog: PGtkWidget;
    CheckBox: PGtkCheckButton;
    i: integer;
    w: PGtkWidget;
begin
  if Application.Terminated then
  begin
    Result := Escape;
    Exit;
  end;
  Dialog := gtk_message_dialog_new(ParentWindow, GTK_DIALOG_MODAL or GTK_DIALOG_DESTROY_WITH_PARENT, TMessageStyleID[Integer(Style)],
                                   GTK_BUTTONS_NONE, StringToPgchar(Text));
  CheckBox := PGtkCheckButton(gtk_check_button_new_with_mnemonic(StringToPgchar(DontShowAgainText)));
  gtk_widget_show(PGtkWidget(CheckBox));
  gtk_box_pack_end(GTK_BOX(GTK_DIALOG(Dialog).vbox), PGtkWidget(CheckBox), False, False, 12);
  for i := 1 to NumMessageButtons do
    if TMessageButton(i - 1) in Buttons then begin
      w := gtk_dialog_add_button(PGtkDialog(Dialog), MessageButtonID[i], i);
      if TMessageButton(i - 1) = Default then gtk_widget_grab_focus(w);
    end;
  if Escape <> mbNone then g_signal_connect(PGtkObject(Dialog), 'key-press-event', G_CALLBACK(@Gnome_MessageBox_key_press_event),
                                            Pointer(Ord(Escape) + 1{MessageButtonID[Ord(Escape)]}));
  if Default <> mbNone then gtk_dialog_set_default_response(PGtkDialog(Dialog), Ord(Default));
  Result := TMessageButton(gtk_dialog_run(PGtkDialog(Dialog)) - 1);
  DontShowAgainChecked := gtk_toggle_button_get_active(PGtkToggleButton(CheckBox));
  gtk_widget_destroy(Dialog);
end;


(********************************************************************************************************************************)
(********************************************************************************************************************************)
//  The following code has been stolen from gtkmountoperation.c
//    Copyright (C) Christian Kellner <gicmo@gnome.org>
procedure HandleVFSAskQuestionCallback(DialogParent: PGtkWidget;
                                       const AMessage: PChar;
                                       const Choices: PPChar;
                                       choice: PInteger);
var dialog: PGtkWidget;
    secondary: PChar;
    primary: PChar;
    count, len, res: integer;
begin
  len := 0;
  secondary := nil;
  if (AMessage = nil) or (choices = nil) or Application.Terminated then Exit;

  primary := strstr (AMessage, #10);
  if primary <> nil then begin
    secondary := primary + 1;
    primary := g_strndup (AMessage, primary - AMessage);
  end;

  if (primary <> nil) and (secondary <> nil) and (@gtk_message_dialog_new_with_markup <> nil)
    then dialog := gtk_message_dialog_new_with_markup (PGtkWindow(DialogParent), 0,
                                                       GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
                                                       '<span size="large" weight="ultrabold">%s</span>'#10'%s',
                                                       primary, secondary)
    else dialog := gtk_message_dialog_new (PGtkWindow(DialogParent), 0,
                                           GTK_MESSAGE_QUESTION,
                                           GTK_BUTTONS_NONE, '%s', AMessage);
  if primary <> nil then g_free (primary);

//  gtk_window_set_title (PGtkWindow(dialog), 'VFS Question');
  gtk_window_set_title (PGtkWindow(dialog), '');

  //  First count the items in the list then add the buttons in reverse order
  while (TOpenPCharArray(choices)[len] <> nil)
    do Inc(len);

  for count := len - 1 downto 0 do
    gtk_dialog_add_button (PGtkDialog(dialog), TOpenPCharArray(choices)[count], count);

  res := gtk_dialog_run (PGtkDialog(Dialog));
  if choice <> nil then choice^ := res;

  gtk_widget_destroy (PGtkWidget(Dialog));
end;

(********************************************************************************************************************************)
type PVFSAskPasswordCallbackPriv = ^TVFSAskPasswordCallbackPriv;
     TVFSAskPasswordCallbackPriv = record
       dialog: PGtkDialog;
       entry_container: PGtkWidget;
       choice_anon, choice_user: PGtkWidget;
       username_entry, domain_entry, password_entry: PGtkEntry;
     end;

function entry_has_input(entry_widget: PGtkEntry): boolean;
var text: PChar;
begin
  if entry_widget = nil then Result := True
  else begin
    text := gtk_entry_get_text (entry_widget);
    Result := (text <> nil) and (strlen(text) > 0);
  end;
end;

function pw_dialog_input_is_valid(priv: PVFSAskPasswordCallbackPriv): boolean;
begin
  Result := entry_has_input(priv^.username_entry) and
            entry_has_input(priv^.domain_entry) and
            entry_has_input(priv^.password_entry);
end;

procedure pw_dialog_verify_input(editable: PGtkEditable; priv: PVFSAskPasswordCallbackPriv); cdecl;
begin
  gtk_dialog_set_response_sensitive (GTK_DIALOG (priv^.dialog),
                                     GTK_RESPONSE_OK,
                                     pw_dialog_input_is_valid(priv));
end;

procedure pw_dialog_anonymous_toggled(widget: PGtkWidget; priv: PVFSAskPasswordCallbackPriv); cdecl;
var is_valid, anonymous: boolean;
begin
  anonymous := widget = priv^.choice_anon;
  if anonymous then is_valid := True
               else is_valid := pw_dialog_input_is_valid(priv);
  gtk_widget_set_sensitive (priv^.entry_container, not anonymous);
  gtk_dialog_set_response_sensitive(GTK_DIALOG (priv^.dialog), GTK_RESPONSE_OK, is_valid);
end;

procedure pw_dialog_cycle_focus(widget: PGtkWidget; priv: PVFSAskPasswordCallbackPriv); cdecl;
var next_widget: PGtkWidget;
begin
  next_widget := nil;
  if widget = PGtkWidget(priv^.username_entry) then begin
    if priv^.domain_entry <> nil then next_widget := PGtkWidget(priv^.domain_entry)
    else if priv^.password_entry <> nil then next_widget := PGtkWidget(priv^.password_entry);
  end else if (widget = PGtkWidget(priv^.domain_entry)) and (priv^.password_entry <> nil)
             then next_widget := PGtkWidget(priv^.password_entry);
  if next_widget <> nil then gtk_widget_grab_focus(next_widget)
  else if pw_dialog_input_is_valid(priv)
         then gtk_window_activate_default(GTK_WINDOW(priv^.dialog));
end;

procedure unmask_checkbox_toggled(togglebutton: PGtkToggleButton; priv: PVFSAskPasswordCallbackPriv); cdecl;
var active: gboolean;
begin
  active := gtk_toggle_button_get_active(togglebutton);
  if priv^.password_entry <> nil then gtk_entry_set_visibility(priv^.password_entry, active);
end;

//  The following code has been stolen from gtkmountoperation.c
//    Copyright (C) Christian Kellner <gicmo@gnome.org>
function HandleVFSAskPasswordCallback(DialogParent: PGtkWidget;
                                      const AMessage: PChar;
                                      const default_user: PChar;
                                      const default_domain: PChar;
                                      const default_password: PChar;
                                      flags: TVFSAskPasswordFlags;
                                      username: PPChar;
                                      password: PPChar;
                                      anonymous: PInteger;
                                      domain: PPChar;
                                      password_save: PVFSPasswordSave): LongBool;

function table_add_entry(table: PGtkWidget; row: integer; const label_text: PChar; const value: PChar; user_data: PVFSAskPasswordCallbackPriv): PGtkEntry;
var entry: PGtkEntry;
    alabel: PGtkWidget;
begin
  alabel := gtk_label_new_with_mnemonic (label_text);
  gtk_misc_set_alignment (GTK_MISC (alabel), 0.0, 0.5);
  entry := PGtkEntry(gtk_entry_new ());
  if value <> nil then gtk_entry_set_text (PGtkEntry(entry), value);
  gtk_table_attach (GTK_TABLE (table), alabel, 0, 1, row, row + 1, GTK_FILL, GTK_EXPAND or GTK_FILL, 0, 0);
  gtk_table_attach_defaults (GTK_TABLE (table), PGtkWidget(entry), 1, 2, row, row + 1);
  gtk_label_set_mnemonic_widget (PGtkLabel(alabel), PGtkWidget(entry));
  g_signal_connect (entry, 'changed', G_CALLBACK (@pw_dialog_verify_input), user_data);
  g_signal_connect (entry, 'activate', G_CALLBACK (@pw_dialog_cycle_focus), user_data);
  Result := entry;
end;

var widget: PGtkWidget;
    window: PGtkWindow;
    hbox, main_vbox, vbox, icon: PGtkWidget;
    table: PGtkWidget;
    message_label: PGtkWidget;
    can_anonymous: boolean;
    Rows: integer;
    anon_box: PGtkWidget;
    radio_forget, radio_session, radio_remember: PGtkWidget;
    group: PGSList;
    remember_box: PGtkWidget;
    priv: TVFSAskPasswordCallbackPriv;
    s: PChar;
    unmask_checkbox: PGtkWidget;
begin
  FillChar(priv, sizeof(priv), 0);
  radio_forget := nil;
  radio_session := nil;
  radio_remember := nil;

  widget := gtk_dialog_new ();
  priv.dialog := PGtkDialog(widget);
  window := PGtkWindow(widget);
//  gtk_window_set_title (window, 'VFS Question');
  gtk_window_set_title (window, '');

  //  Set the dialog up with HIG properties
  gtk_dialog_set_has_separator (priv.dialog, False);
  gtk_container_set_border_width (GTK_CONTAINER (priv.dialog), 5);
  gtk_box_set_spacing (GTK_BOX (priv.dialog^.vbox), 2);   //  2 * 5 + 2 = 12
  gtk_container_set_border_width (GTK_CONTAINER (priv.dialog^.action_area), 5);
  gtk_box_set_spacing (GTK_BOX (priv.dialog^.action_area), 6);

  gtk_window_set_resizable (window, False);
//  gtk_window_set_icon_name (window, 'gtk-dialog-authentication');

  if (flags and VFS_ASK_PASSWORD_ARCHIVE_MODE) = VFS_ASK_PASSWORD_ARCHIVE_MODE
    then s := GTK_STOCK_OK
    else s := PChar(LANGConnMgr_ConnectButton);

  gtk_dialog_add_buttons (priv.dialog,
                          GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                          s, GTK_RESPONSE_OK,
                          nil);
  gtk_dialog_set_default_response (priv.dialog, GTK_RESPONSE_OK);

  //  Build contents
  hbox := gtk_hbox_new (FALSE, 12);
  gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
  gtk_box_pack_start (GTK_BOX (priv.dialog^.vbox), hbox, TRUE, TRUE, 0);

  if gtk_style_lookup_icon_set(gtk_widget_get_style(PGtkWidget(priv.dialog)), 'gtk-dialog-authentication') <> nil
    then icon := gtk_image_new_from_stock('gtk-dialog-authentication', GTK_ICON_SIZE_DIALOG)
    else icon := gtk_image_new_from_pixbuf(StockLock48.FPixbuf);

  gtk_misc_set_alignment (GTK_MISC (icon), 0.5, 0.0);
  gtk_box_pack_start (GTK_BOX (hbox), icon, FALSE, FALSE, 0);

  main_vbox := gtk_vbox_new (FALSE, 18);
  gtk_box_pack_start (GTK_BOX (hbox), main_vbox, TRUE, TRUE, 0);

  message_label := gtk_label_new (AMessage);
  gtk_misc_set_alignment (GTK_MISC (message_label), 0.0, 0.5);
  gtk_label_set_line_wrap (GTK_LABEL (message_label), TRUE);
  gtk_box_pack_start (GTK_BOX (main_vbox), GTK_WIDGET (message_label), FALSE, FALSE, 0);

  vbox := gtk_vbox_new (FALSE, 6);
  gtk_box_pack_start (GTK_BOX (main_vbox), vbox, FALSE, FALSE, 0);

  can_anonymous := (flags and VFS_ASK_PASSWORD_ANONYMOUS_SUPPORTED) = VFS_ASK_PASSWORD_ANONYMOUS_SUPPORTED;

  if can_anonymous then begin
    anon_box := gtk_vbox_new (FALSE, 6);
    gtk_box_pack_start (GTK_BOX (vbox), anon_box, FALSE, FALSE, 0);

    priv.choice_anon := gtk_radio_button_new_with_mnemonic (nil, PChar(LANGGtkMountOperation_ConnectAnonymously));
    gtk_box_pack_start (GTK_BOX (anon_box), priv.choice_anon, FALSE, FALSE, 0);
    g_signal_connect (priv.choice_anon, 'toggled', G_CALLBACK (@pw_dialog_anonymous_toggled), @priv);

    group := gtk_radio_button_get_group (GTK_RADIO_BUTTON (priv.choice_anon));
    priv.choice_user := gtk_radio_button_new_with_mnemonic (group, PChar(LANGGtkMountOperation_ConnectAsUser));
    gtk_box_pack_start (GTK_BOX (anon_box), priv.choice_user, FALSE, FALSE, 0);
    g_signal_connect (priv.choice_user, 'toggled', G_CALLBACK (@pw_dialog_anonymous_toggled), @priv);
  end;

  rows := 0;
  if (flags and VFS_ASK_PASSWORD_NEED_PASSWORD) = VFS_ASK_PASSWORD_NEED_PASSWORD then Inc(rows);
  if (flags and VFS_ASK_PASSWORD_NEED_USERNAME) = VFS_ASK_PASSWORD_NEED_USERNAME then Inc(rows);
  if (flags and VFS_ASK_PASSWORD_NEED_DOMAIN) = VFS_ASK_PASSWORD_NEED_DOMAIN then Inc(rows);

  //  The table that holds the entries
  priv.entry_container := gtk_alignment_new (0.0, 0.0, 1.0, 1.0);

  if can_anonymous and Application.GTKVersion_2_4_0_Up then g_object_set (priv.entry_container, 'left-padding', 12, nil);
  gtk_box_pack_start (GTK_BOX (vbox), priv.entry_container, FALSE, FALSE, 0);

  table := gtk_table_new (rows, 2, FALSE);
  gtk_table_set_col_spacings (GTK_TABLE (table), 12);
  gtk_table_set_row_spacings (GTK_TABLE (table), 6);
  gtk_container_add (GTK_CONTAINER (priv.entry_container), table);

  Rows := 0;
  if (flags and VFS_ASK_PASSWORD_NEED_USERNAME) = VFS_ASK_PASSWORD_NEED_USERNAME then begin
    priv.username_entry := table_add_entry (table, rows, PChar(LANGGtkMountOperation_Username), default_user, @priv);
    Inc(Rows);
  end;
  if (flags and VFS_ASK_PASSWORD_NEED_DOMAIN) = VFS_ASK_PASSWORD_NEED_DOMAIN then begin
    priv.domain_entry := table_add_entry (table, rows, PChar(LANGGtkMountOperation_Domain), default_domain, @priv);
    Inc(Rows);
  end;
  if (flags and VFS_ASK_PASSWORD_NEED_PASSWORD) = VFS_ASK_PASSWORD_NEED_PASSWORD then begin
    priv.password_entry := table_add_entry (table, rows, PChar(LANGGtkMountOperation_Password), nil, @priv);
    if (default_password <> nil) and (strlen(default_password) > 0) then
      gtk_entry_set_text(priv.password_entry, default_password);
//    Inc(Rows);
    gtk_entry_set_visibility (GTK_ENTRY (priv.password_entry), FALSE);
  end;

  if (flags and VFS_ASK_PASSWORD_ARCHIVE_MODE) = VFS_ASK_PASSWORD_ARCHIVE_MODE then begin
    unmask_checkbox := gtk_check_button_new_with_mnemonic (PChar(LANGFSetPassword_ShowPasswordCheckButton));
    g_object_set(G_OBJECT(unmask_checkbox), 'can-focus', 0, nil);
    gtk_box_pack_start (GTK_BOX (vbox), unmask_checkbox, FALSE, FALSE, 0);
    g_signal_connect (unmask_checkbox, 'toggled', G_CALLBACK (@unmask_checkbox_toggled), @priv);
  end;

  if ((flags and VFS_ASK_PASSWORD_SAVING_SUPPORTED) = VFS_ASK_PASSWORD_SAVING_SUPPORTED) or
     ((flags and VFS_ASK_PASSWORD_SAVE_INTERNAL) = VFS_ASK_PASSWORD_SAVE_INTERNAL) then
  begin
    remember_box := gtk_vbox_new (FALSE, 6);
    gtk_box_pack_start (GTK_BOX (vbox), remember_box, FALSE, FALSE, 0);

    if (flags and VFS_ASK_PASSWORD_SAVING_SUPPORTED) = 0 then s := PChar(LANGGtkMountOperation_DoNotSavePassword)
                                                         else s := PChar(LANGGtkMountOperation_ForgetPasswordImmediately);
    radio_forget := gtk_radio_button_new_with_mnemonic (nil, s);
    gtk_box_pack_start (GTK_BOX (remember_box), radio_forget, FALSE, FALSE, 0);

    group := gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_forget));
    //  gnome-keyring only
    if (flags and VFS_ASK_PASSWORD_SAVING_SUPPORTED) = VFS_ASK_PASSWORD_SAVING_SUPPORTED then begin
      radio_session := gtk_radio_button_new_with_mnemonic (group, PChar(LANGGtkMountOperation_RememberPasswordUntilYouLogout));
      gtk_box_pack_start (GTK_BOX (remember_box), radio_session, FALSE, FALSE, 0);
      group := gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_session));
    end;

    if (flags and VFS_ASK_PASSWORD_SAVING_SUPPORTED) = 0 then s := PChar(LANGGtkMountOperation_SavePasswordInConnectionManager)
                                                         else s := PChar(LANGGtkMountOperation_RememberForever);
    radio_remember := gtk_radio_button_new_with_mnemonic (group, s);
    gtk_box_pack_start (GTK_BOX (remember_box), radio_remember, FALSE, FALSE, 0);

    //  Select to save password when internal saving is supported
    if (flags and VFS_ASK_PASSWORD_SAVE_INTERNAL) = VFS_ASK_PASSWORD_SAVE_INTERNAL
      then gtk_toggle_button_set_active(PGtkToggleButton(radio_remember), True);
  end;

  // The anonymous option will be active by default, ensure the toggled signal is emitted for it.
  if can_anonymous then gtk_toggle_button_toggled (GTK_TOGGLE_BUTTON (priv.choice_anon)) else
  if not pw_dialog_input_is_valid(@priv) then gtk_dialog_set_response_sensitive(priv.dialog, GTK_RESPONSE_OK, False);

  gtk_widget_show_all (GTK_WIDGET (priv.dialog));
  gtk_window_set_transient_for (window, PGtkWindow(DialogParent));

  Result := gtk_dialog_run(PGtkDialog(priv.dialog)) = GTK_RESPONSE_OK;
  if Result then begin
    if (priv.username_entry <> nil) and (username <> nil) then username^ := g_strdup(gtk_entry_get_text(priv.username_entry));
    if (priv.domain_entry <> nil) and (domain <> nil) then domain^ := g_strdup(gtk_entry_get_text(priv.domain_entry));
    if (priv.password_entry <> nil) and (password <> nil) then password^ := g_strdup(gtk_entry_get_text(priv.password_entry));
    if (priv.choice_anon <> nil) and (anonymous <> nil) then anonymous^ := Ord(gtk_toggle_button_get_active(PGtkToggleButton(priv.choice_anon)));
    if (password_save <> nil) and (radio_forget <> nil) and (radio_remember <> nil) then begin
      if gtk_toggle_button_get_active(PGtkToggleButton(radio_forget)) then password_save^ := VFS_PASSWORD_SAVE_NEVER else
      if (radio_session <> nil) and gtk_toggle_button_get_active(PGtkToggleButton(radio_session)) then password_save^ := VFS_PASSWORD_SAVE_FOR_SESSION else
      if gtk_toggle_button_get_active(PGtkToggleButton(radio_remember)) then password_save^ := VFS_PASSWORD_SAVE_PERMANENTLY;
    end;
  end;

  gtk_widget_destroy (PGtkWidget(priv.dialog));
end;


(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)
(********************************************************************************************************************************)

procedure SetupGnomeLibs;
var g: Pointer;
begin
  try
    if Assigned(gnome_program_init) and Assigned(libgnomeui_module_info_get) and Assigned(libgnome_module_info_get) then begin
      DebugMsg(['Initializing Gnome...']);
      g := gnome_program_init('TuxCommander', PChar(ConstAboutVersion), libgnomeui_module_info_get, GTKForms.argc, GTKForms.argv,
                              'show-crash-dialog', 0, nil);
      DebugMsg([' *GnomeProgram = 0x', IntToHex(QWORD(g), 8)]);

    end;
  except end;
end;

procedure LoadGnomeLibs;
begin
  //  Set default values
  @gnome_about_new := nil;
  @gnome_color_picker_new := nil;
  @gnome_color_picker_get_i16 := nil;
  @gnome_color_picker_set_i16 := nil;
  @libgnomeui_module_info_get := nil;
  @gnome_program_init := nil;
  @libgnome_module_info_get := nil;
  @gnome_icon_entry_new := nil;
  @gnome_icon_entry_set_pixmap_subdir := nil;
  @gnome_icon_entry_get_filename := nil;
  @gnome_icon_entry_set_filename := nil;
  @gtk_event_box_set_visible_window := nil;

  //  Dynamic loading
  libGlib2Handle := dlopen('libglib-2.0.so.0', RTLD_LAZY);
  if libGlib2Handle = nil then libGlib2Handle := dlopen('libglib-2.0.so', RTLD_LAZY);
  if libGlib2Handle <> nil then begin
    @g_filename_display_name := dlsym(libGlib2Handle, 'g_filename_display_name');
    @g_mkdir_with_parents := dlsym(libGlib2Handle, 'g_mkdir_with_parents');
    DebugMsg(['libglib-2.0.so loaded, @g_filename_display_name = 0x', IntToHex(QWORD(@g_filename_display_name), 8)]);
  end;
  libGtk2Handle := dlopen('libgtk-x11-2.0.so.0', RTLD_LAZY);
  if libGtk2Handle = nil then libGtk2Handle := dlopen('libgtk-x11-2.0.so', RTLD_LAZY);
  if libGtk2Handle <> nil then begin
    @gtk_event_box_set_visible_window := dlsym(libGtk2Handle, 'gtk_event_box_set_visible_window');
    @gtk_icon_size_lookup_for_settings := dlsym(libGtk2Handle, 'gtk_icon_size_lookup_for_settings');
    @gtk_window_set_icon_name := dlsym(libGtk2Handle, 'gtk_window_set_icon_name');
    @gtk_message_dialog_new_with_markup := dlsym(libGtk2Handle, 'gtk_message_dialog_new_with_markup');
    DebugMsg(['libgtk-x11-2.0.so loaded, @gtk_event_box_set_visible_window = 0x', IntToHex(QWORD(@gtk_event_box_set_visible_window), 8),
              ', @gtk_icon_size_lookup_for_settings = 0x', IntToHex(QWORD(@gtk_icon_size_lookup_for_settings), 8)]);
  end;
  libGnome2Handle := dlopen('libgnome-2.so.0', RTLD_LAZY);
  if libGnome2Handle = nil then libGnome2Handle := dlopen('libgnome-2.so', RTLD_LAZY);
  if libGnome2Handle <> nil then begin
    @gnome_program_init := dlsym(libGnome2Handle, 'gnome_program_init');
    @libgnome_module_info_get := dlsym(libGnome2Handle, 'libgnome_module_info_get');
    DebugMsg(['libgnome-2.so loaded, @gnome_program_init = 0x', IntToHex(QWORD(@gnome_program_init), 8), ', @libgnome_module_info_get = 0x', IntToHex(QWORD(@libgnome_module_info_get), 8)]);
  end;
  libGnomeUI2Handle := dlopen('libgnomeui-2.so.0', RTLD_LAZY);
  if libGnomeUI2Handle = nil then libGnomeUI2Handle := dlopen('libgnomeui-2.so', RTLD_LAZY);
  if libGnomeUI2Handle <> nil then begin
    @gnome_about_new := dlsym(libGnomeUI2Handle, 'gnome_about_new');
    @gnome_color_picker_new := dlsym(libGnomeUI2Handle, 'gnome_color_picker_new');
    @gnome_color_picker_get_i16 := dlsym(libGnomeUI2Handle, 'gnome_color_picker_get_i16');
    @gnome_color_picker_set_i16 := dlsym(libGnomeUI2Handle, 'gnome_color_picker_set_i16');
    @libgnomeui_module_info_get := dlsym(libGnomeUI2Handle, 'libgnomeui_module_info_get');
    @gnome_icon_entry_new := dlsym(libGnomeUI2Handle, 'gnome_icon_entry_new');
    @gnome_icon_entry_set_pixmap_subdir := dlsym(libGnomeUI2Handle, 'gnome_icon_entry_set_pixmap_subdir');
    @gnome_icon_entry_get_filename := dlsym(libGnomeUI2Handle, 'gnome_icon_entry_get_filename');
    @gnome_icon_entry_set_filename := dlsym(libGnomeUI2Handle, 'gnome_icon_entry_set_filename');
    @gnome_date_edit_new := dlsym(libGnomeUI2Handle, 'gnome_date_edit_new');
    @gnome_date_edit_set_time := dlsym(libGnomeUI2Handle, 'gnome_date_edit_set_time');
    @gnome_date_edit_get_time := dlsym(libGnomeUI2Handle, 'gnome_date_edit_get_time');
    DebugMsg(['libgnomeui-2.so loaded, @gnome_about_new = 0x', IntToHex(QWORD(@gnome_about_new), 8), ', @gnome_color_picker_new = 0x', IntToHex(QWORD(@gnome_color_picker_new), 8),
              ', @gnome_icon_entry_new = 0x', IntToHex(QWORD(@gnome_icon_entry_new), 8), ', @gnome_date_edit_new = 0x', IntToHex(QWORD(@gnome_date_edit_new), 8),
              ', @libgnomeui_module_info_get = 0x', IntToHex(QWORD(@libgnomeui_module_info_get), 8)]);
  end;
  SetupGnomeLibs;
end;


initialization
finalization
//  if libGnomeUI2Handle <> 0 then FreeLibrary(libGnomeUI2Handle);
//  if libGnome2Handle <> 0 then FreeLibrary(libGnome2Handle);
end.

