File: UToolTips.pas

package info (click to toggle)
tuxcmd 0.6.70%2Bdfsg-2
  • links: PTS
  • area: main
  • in suites: bullseye, buster, jessie, jessie-kfreebsd, stretch
  • size: 3,612 kB
  • ctags: 2,757
  • sloc: pascal: 49,754; ansic: 2,272; makefile: 106
file content (402 lines) | stat: -rw-r--r-- 14,285 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
(*
    Tux Commander - UToolTips - ToolTips utils and classes (used as the tree hints)
    Copyright (C) 2008 Tomas Bzatek <tbzatek@users.sourceforge.net>
    This unit is based on an idea from Gnome File Manager by Miroslav Bajtos <mirco@matfyz.cz>
    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 UToolTips;

interface

uses glib2, gdk2, gtk2, pango;

procedure FileListTipsInstall(ATreeView: PGtkTreeView);
procedure FileListTipsEnable;
procedure FileListTipsDisable;
procedure FileListTipsHide;

implementation

uses SysUtils, DateUtils, Classes, GTKForms, GTKView, GTKControls, UMain, UEngines, UCore, UConfig,
  UCoreUtils;

var tip_window: PGtkWindow;
    tip_label: PGtkLabel;
    timer_id: gint;
    tips_enabled: boolean;
    data_row: PGtkTreePath;
    data_column: PGtkTreeViewColumn;
    data_panel: PGtkTreeView;
    tips_timer: PGTimer;


function event_handler(widget: PGtkWidget; event: PGdkEvent; user_data: gpointer): gboolean; cdecl; forward;
function on_leave_notify(widget: PGtkWidget; event: PGdkEventCrossing; user_data: gpointer): gboolean; cdecl; forward;
function show_tip_widget(user_data: gpointer): gboolean; cdecl; forward;
procedure create_widgets; forward;
function tip_event_handler(widget: PGtkWidget; event: PGdkEvent; user_data: gpointer): gboolean; cdecl; forward;
function tips_button_press_event(widget: PGtkWidget; event: PGdkEventButton; user_data: gpointer):gboolean; cdecl; forward;



procedure FileListTipsInstall(ATreeView: PGtkTreeView);
begin
  g_signal_connect_after(ATreeView, 'event-after', G_CALLBACK(@event_handler), nil);
  g_signal_connect(ATreeView, 'leave-notify-event', G_CALLBACK(@on_leave_notify), nil);
end;

procedure file_list_tips_show_tip(AListView: PGtkTreeView; row: PGtkTreePath; column: PGtkTreeViewColumn);
var Timeout: integer;
    is_active: gboolean;
    ms: gulong;
    sec: gdouble;
begin
  if not tips_enabled then Exit;
  FileListTipsHide;
  g_object_get(G_OBJECT(FMain.FWidget), 'is-active', @is_active, nil);
  if not is_active then Exit;
  if Assigned(data_row) and (data_row <> row) then gtk_tree_path_free(data_row);
  data_panel := AListView;
  data_row := row;
  data_column := column;
  Timeout := ConstFileListTipsDelay;
  if tips_timer <> nil then begin
    sec := g_timer_elapsed(tips_timer, @ms);
    if (sec < 1) and (ms < ConstFileListTipsDelayNeighbour * 1000) then Timeout := 1;
//    DebugMsg(['Time elapsed = ', Double(sec), ':', integer(ms div 1000)]);
  end;
  timer_id := gtk_timeout_add(Timeout, show_tip_widget, nil);
end;


function event_handler(widget: PGtkWidget; event: PGdkEvent; user_data: gpointer): gboolean; cdecl;
var view: PGtkTreeView;
    column: PGtkTreeViewColumn;
    path: PGtkTreePath;
begin
  Result := False;
  view := GTK_TREE_VIEW(widget);
  if event^.any.window <> gtk_tree_view_get_bin_window(view) then Exit;
  case event^._type of
    GDK_KEY_PRESS, GDK_SCROLL:
      begin
        FileListTipsHide;
        if tips_timer <> nil then begin
          g_timer_destroy(tips_timer);
          tips_timer := nil;
        end;
        Result := True;
      end;
    GDK_FOCUS_CHANGE: begin
                        Result := True;
                        if event^.focus_change._in <> 1 then FileListTipsHide;
                      end;  
    GDK_MOTION_NOTIFY: begin
                         Result := True;
                         if not gtk_tree_view_get_path_at_pos(view, Trunc(event^.motion.x), Trunc(event^.motion.y), path, column, nil, nil)
                           then begin
                                  FileListTipsHide;
                                  if tips_timer <> nil then begin
                                    g_timer_destroy(tips_timer);
                                    tips_timer := nil;
                                  end;
                                  Exit;
                                end;
                         if Assigned(path) and Assigned(column) then file_list_tips_show_tip(view, path, column);
                       end;
  end;
end;

procedure draw_tip_widget(panel: PGtkTreeView; const text: Pgchar; x, y: integer);
var requisition: TGtkRequisition;
    widget: PGtkWidget;
    w, scr_w, wx, wy: Integer;
    FontDesc: PPangoFontDescription;
    hadjustment: PGtkAdjustment;
begin
  try

  if not Assigned(tip_window) then create_widgets;
  if not ConfUseSystemFont then begin
    FontDesc := pango_font_description_from_string(PChar(ConfPanelFont));
    gtk_widget_modify_font(PGtkWidget(tip_label), FontDesc);
  end else gtk_widget_modify_font(PGtkWidget(tip_label), nil);

  gtk_label_set_markup(GTK_LABEL(tip_label), Text);
  widget := GTK_WIDGET(panel);
  hadjustment := gtk_tree_view_get_hadjustment(panel);
  x := x - Trunc(gtk_adjustment_get_value(hadjustment));

  gdk_window_get_origin(widget^.window, @wx, @wy);
  x := x + wx;
  y := y + wy;
  scr_w := gdk_screen_width;

  gtk_label_set_line_wrap(tip_label, False);
  gtk_widget_size_request(GTK_WIDGET(tip_window), @requisition);
  if x + requisition.width > scr_w {PGtkWidget(panel)^.allocation.width} then begin
    gtk_label_set_line_wrap(tip_label, True);
    gtk_widget_size_request(GTK_WIDGET(tip_window), @requisition);
  end;

  w := requisition.width;
  if (x + w) > scr_w then x := scr_w - w 
                     else if x < 0 then x := 0;
  gtk_window_move(tip_window, x, y);
  except
    on E: Exception do DebugMsg(['*** Exception raised in function draw_tip_widget: (', E.ClassName, '): ', E.Message]);
  end;
end;

function gtk_tooltips_paint_window(widget: PGtkWidget; event: PGdkEventExpose; user_data: gpointer): gboolean; cdecl;
begin
  gtk_paint_flat_box(widget^.style, widget^.window, GTK_STATE_NORMAL, GTK_SHADOW_OUT,
                     nil, widget, 'tooltip', 0, 0, -1, -1);
  Result := False;
end;


procedure create_widgets;
begin
  if Assigned(tip_window) then Exit;
  tip_window := GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
  gtk_widget_set_app_paintable(GTK_WIDGET(tip_window), True);
  gtk_window_set_resizable(tip_window, False);
  gtk_widget_set_name(GTK_WIDGET(tip_window), 'gtk-tooltips');
  gtk_container_set_border_width(GTK_CONTAINER(tip_window), 4);
  gtk_widget_set_events(GTK_WIDGET(tip_window), GDK_POINTER_MOTION_MASK or GDK_BUTTON_PRESS_MASK or GDK_KEY_PRESS_MASK or GDK_FOCUS_CHANGE_MASK or GDK_SCROLL_MASK);
  g_signal_connect(tip_window, 'event', G_CALLBACK(@tip_event_handler), nil);
  g_signal_connect(tip_window, 'expose_event', G_CALLBACK(@gtk_tooltips_paint_window), tip_window);
  g_signal_connect(tip_window, 'button-press-event', G_CALLBACK(@tips_button_press_event), tip_window);

  tip_label := GTK_LABEL(gtk_label_new(nil));
  gtk_misc_set_alignment(GTK_MISC(tip_label), 0, 0.5);
  gtk_widget_show(GTK_WIDGET(tip_label));

  gtk_container_add(GTK_CONTAINER(tip_window), GTK_WIDGET(tip_label));
end;

procedure FileListTipsHide;
begin
  if not Assigned(tip_window) then Exit;
  if Assigned(data_row) then gtk_tree_path_free(data_row);
  data_row := nil;
  data_column := nil;

  if GTK_WIDGET_VISIBLE(tip_window) then begin
    gtk_widget_hide(GTK_WIDGET(tip_window));
    if tips_timer = nil then tips_timer := g_timer_new;
    g_timer_start(tips_timer);
//           gdk_beep();
//           DebugMsg(['FileListTipsHide, Setting last_hide_time to ', last_hide_time]);
  end;

  if timer_id <> 0 then begin
    gtk_timeout_remove(timer_id);
    timer_id := 0;
  end;
end;

function show_tip_widget(user_data: gpointer): gboolean; cdecl;
var Text: PChar;
    Data: PDataItem;
    AListView: TGTKListView;

  function ColumnFits(ColNo, ColumnWidth: integer): boolean;
  var Layout: PPangoLayout;
      FontDesc: PPangoFontDescription;
      w, h: Integer;
  begin
    Text := Data^.ColumnData[FMain.ColumnSortIDs[ColNo + 1] - 1];
    Layout := gtk_widget_create_pango_layout(AListView.FWidget, Text);
    if ConfDirsInBold and Data^.IsDir then begin
      Text := PChar(Format('<span weight="bold">%s</span>', [QuoteMarkupStr(Text)]));
      pango_layout_set_markup(Layout, Text, Length(Text));
    end;
    if not ConfUseSystemFont then begin
      FontDesc := pango_font_description_from_string(PChar(ConfPanelFont));
      pango_layout_set_font_description(Layout, FontDesc);
    end;
    pango_layout_get_pixel_size(Layout, @w, @h);
    g_object_unref(Layout);
    Result := ColumnWidth > w;
  end;

var Col, ColID: Integer;
    Rect: TGdkRectangle;
    iter: TGtkTreeIter;
    DataList: TList;
    TreePath: PGtkTreePath;
    IsFNameExtColumn, b: boolean;
begin
  Result := False;
  try

  if not gtk_tree_model_get_iter(gtk_tree_view_get_model(data_panel), @iter, data_row) then Exit;
  if Pointer(data_panel) = Pointer(FMain.LeftListView.FWidget) then begin
    DataList := LeftPanelData;
    AListView := FMain.LeftListView;
  end else begin
    DataList := RightPanelData;
    AListView := FMain.RightListView;
  end;

  if not Application.GTKVersion_2_0_5_Up then gtk_tree_model_get(gtk_tree_view_get_model(data_panel), @iter, 0, @Data, -1)
  else begin
    TreePath := gtk_tree_path_new_from_string(PChar(IntToStr(gtk_tree_path_get_indices(data_row)^)));
    if not Assigned(TreePath) then Exit;
    AListView.ConvertPathToChild(TreePath);
    if (TreePath = nil) or (gtk_tree_path_get_indices(TreePath)^ > DataList.Count - 1) then begin
      FileListTipsHide;
      Exit;
    end;
    Data := DataList[gtk_tree_path_get_indices(TreePath)^];
    gtk_tree_path_free(TreePath);
  end;
  if (Data = nil) or Data^.UpDir then begin
    FileListTipsHide;
    Exit;
  end;

  Col := gtk_tree_view_column_get_sort_column_id(data_column);
  ColID := FMain.ColumnSortIDs[Col + 1];
  IsFNameExtColumn := ((ColID = 1) and (ColID < AListView.Columns.Count - 1) and (FMain.ColumnSortIDs[Col + 2] = 3)) or
                      ((ColID = 3) and (ColID > 0) and (FMain.ColumnSortIDs[Col] = 1));
  gtk_tree_view_get_cell_area(data_panel, data_row, data_column, @rect);
  if (Col = 0) and ConfUseFileTypeIcons then begin
    Inc(Rect.x, ConfRowHeightReal + 2);
    Dec(Rect.width, ConfRowHeightReal + 2);
  end;

  //  Determine if the text fits into the cell area
  b := ColumnFits(Col, Rect.width - 10);
  if IsFNameExtColumn then begin
    gtk_tree_view_get_cell_area(data_panel, data_row, gtk_tree_view_get_column(data_panel, Col - Ord(ColID = 3) + Ord(ColID = 1)), @rect);
    if ((Col - Ord(ColID = 3) + Ord(ColID = 1)) = 0) and ConfUseFileTypeIcons then begin
      Inc(Rect.x, ConfRowHeightReal + 2);
      Dec(Rect.width, ConfRowHeightReal + 2);
    end;
    b := b and ColumnFits(Col - Ord(ColID = 3) + Ord(ColID = 1), Rect.width - 10);
  end;

  if b then begin
    FileListTipsHide;
    Exit;
  end;

  if IsFNameExtColumn then begin
    Text := PChar(Data^.FDisplayName);
    gtk_tree_view_get_cell_area(data_panel, data_row, gtk_tree_view_get_column(data_panel, Col - Ord(ColID = 3)), @rect);
    if ((Col - Ord(ColID = 3)) = 0) and ConfUseFileTypeIcons then begin
      Inc(Rect.x, ConfRowHeightReal + 2);
      Dec(Rect.width, ConfRowHeightReal + 2);
    end;
  end else Text := Data^.ColumnData[FMain.ColumnSortIDs[Col + 1] - 1];
  Text := PChar(QuoteMarkupStr(Text));

  if ConfDirsInBold and Data^.IsDir then Text := PChar(Format('<span weight="bold">%s</span>', [Text]));
  draw_tip_widget(data_panel, Text, Rect.x, Rect.y + Rect.height + 2);
//  if Assigned(text) then g_free(text);
  if not GTK_WIDGET_VISIBLE(GTK_WIDGET(tip_window)) then gtk_widget_show(GTK_WIDGET(tip_window));

  except
    on E: Exception do DebugMsg(['*** Exception raised in function show_tip_widget(user_data: gpointer): gboolean (', E.ClassName, '): ', E.Message]);
  end;
end;

procedure FileListTipsEnable;
begin
  tips_enabled := True and (not ConfDisableFileTips);
end;

procedure FileListTipsDisable;
begin
  tips_enabled := True;
  FileListTipsHide;
end;

procedure file_list_tips_hide_tip;
begin
  FileListTipsHide;
end;

function tip_event_handler(widget: PGtkWidget; event: PGdkEvent; user_data: gpointer): gboolean; cdecl;
begin
  Result := True;
  case event^._type of
    GDK_KEY_PRESS, {GDK_BUTTON_PRESS, }GDK_SCROLL, GDK_FOCUS_CHANGE{, GDK_MOTION_NOTIFY}:
      begin
        FileListTipsHide;
        if tips_timer <> nil then begin
          g_timer_destroy(tips_timer);
          tips_timer := nil;
        end;
        Exit;
      end;
  end;
  Result := False;
end;

function on_leave_notify(widget: PGtkWidget; event: PGdkEventCrossing; user_data: gpointer): gboolean; cdecl;
begin
  if Assigned(tip_window) and (not GTK_WIDGET_VISIBLE(tip_window))
    then begin
           FileListTipsHide;
           if tips_timer <> nil then begin
             g_timer_destroy(tips_timer);
             tips_timer := nil;
           end;
         end;
  Result := False;
end;

function tips_button_press_event(widget: PGtkWidget; event: PGdkEventButton; user_data: gpointer): gboolean; cdecl;
var FSelection: PGtkTreeSelection;
begin
  Result := True;
  if not Assigned(data_panel) then Exit;
  if not Assigned(data_row) then begin
    FileListTipsHide;
    if tips_timer <> nil then begin
      g_timer_destroy(tips_timer);
      tips_timer := nil;
    end;
    Exit;
  end;

  FMain.LastClick := Now;
  gtk_tree_view_set_cursor(data_panel, data_row, nil, False);
  FSelection := gtk_tree_view_get_selection(data_panel);
  gtk_tree_selection_select_path(FSelection, data_row);
  Result := False;

  FileListTipsHide;
  if tips_timer <> nil then begin
    g_timer_destroy(tips_timer);
    tips_timer := nil;
  end;

  gtk_widget_grab_focus(PGtkWidget(data_panel));
end;



initialization
  timer_id := 0;
  data_row := nil;
  tips_timer := nil;
end.