File: treeview_documents.ml

package info (click to toggle)
marionnet 0.90.6+bzr508-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 9,532 kB
  • sloc: ml: 18,130; sh: 5,384; xml: 1,152; makefile: 1,003; ansic: 275
file content (347 lines) | stat: -rw-r--r-- 13,477 bytes parent folder | download
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
(* This file is part of Marionnet, a virtual network laboratory
   Copyright (C) 2007, 2008, 2009  Luca Saiu
   Copyright (C) 2009, 2010  Jean-Vincent Loddo
   Copyright (C) 2007, 2008, 2009, 2010  Université Paris 13

   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, see <http://www.gnu.org/licenses/>. *)

(* Authors:
 * - Luca Saiu: initial version
 * - Jean-Vincent Loddo: Unix.system calls replaced by UnixExtra's functions
     calls, and some other minor changes
 *)

open Gettext;;
module Row_item = Treeview.Row_item ;;

(* --- *)
(* Ex: Some "Jean-Vincent Loddo" *)
let get_full_user_name () : string option =
  let user = Sys.getenv "USER" in
  let cmd = Printf.sprintf "getent passwd %s | cut -d: -f 5 | cut -d, -f 1" user in
  match UnixExtra.run cmd with
  | (full_name, Unix.WEXITED 0) -> Some (StringExtra.chop full_name)
  | _ -> None
(* --- *)

class t =
fun ~packing
    ~method_directory 
    ~method_filename
    ~after_user_edit_callback
    () ->
object(self)
  inherit
    Treeview.t
      ~packing 
      ~method_directory
      ~method_filename
      ~hide_reserved_fields:true
      ()
  as super

  val icon_header = "Icon"
  method get_row_icon = self#get_Icon_field (icon_header)
  method set_row_icon = self#set_Icon_field (icon_header)

  val title_header = "Title"
  method get_row_title = self#get_String_field (title_header)
  method set_row_title = self#set_String_field (title_header)

  val author_header = "Author"
  method get_row_author = self#get_String_field (author_header)
  method set_row_author = self#set_String_field (author_header)

  val type_header = "Type"
  method get_row_type = self#get_String_field (type_header)
  method set_row_type = self#set_String_field (type_header)

  val comment_header = "Comment"
  method get_row_comment = self#get_String_field (comment_header)
  method set_row_comment = self#set_String_field (comment_header)

  val filename_header = "FileName"
  method get_row_filename = self#get_String_field (filename_header)
  method set_row_filename = self#set_String_field (filename_header)

  val format_header = "Format"
  method get_row_format = self#get_String_field (format_header)
  method set_row_format = self#set_String_field (format_header)

  (** Display the document at the given row, in an asynchronous process: *)
  method private display row_id =
    let frmt = self#get_row_format (row_id) in
    let reader = self#format_to_reader frmt in
    let pathname = Filename.concat (self#directory) (self#get_row_filename row_id) in
    let command_line =
      Printf.sprintf "%s '%s'&" reader pathname in
    (* Here ~force:true would be useless, because of '&' (the shell well exit in any case). *)
    Log.system_or_ignore command_line

  val error_message =
    (s_ "You should select an existing document in PDF, Postscript, DVI, HTML or text format.")

  (** Ask the user to choose a file, and return its pathname. Fail if the user doesn't
      choose a file or cancels: *)
  method (* private *) ask_file : string option =
    let dialog = GWindow.file_chooser_dialog
        ~icon:Icon.icon_pixbuf
        ~action:`OPEN
        ~title:((*utf8*)(s_ "Choose the document to import"))
        ~modal:true () 
    in
    dialog#add_button_stock `CANCEL `CANCEL;
    dialog#add_button_stock `OK `OK;
    dialog#unselect_all;
    dialog#add_filter
      (GFile.filter
         ~name:(s_ "Texts (PDF, PostScript, DVI, HTML, text)")
         ~patterns:["*.pdf"; "*.ps"; "*.dvi"; "*.text"; "*.txt"; "*.html"; "*.htm"; "README";
                    (s_ "README") (* it's nice to also support something like LISEZMOI... *)]
         ());
    dialog#set_default_response `OK;
    (* --- *)
    (match dialog#run () with
      `OK ->
        (match dialog#filename with
          Some result ->
            dialog#destroy ();
            Log.printf1 "* Ok: \"%s\"\n" result;
            Some result
        | None -> begin
            dialog#destroy ();
            Log.printf "* No document was selected\n";
            None
          end)
    | _ ->
        dialog#destroy ();
        Log.printf "* You cancelled\n";
        None)

  method private file_to_format pathname =
    if Filename.check_suffix pathname ".html" || 
      Filename.check_suffix pathname ".htm" || 
      Filename.check_suffix pathname ".HTML" || 
      Filename.check_suffix pathname ".HTM" then
      "html"
    else if Filename.check_suffix pathname ".text" || 
      Filename.check_suffix pathname ".txt" || 
      Filename.check_suffix pathname "readme" || 
      Filename.check_suffix pathname "lisezmoi" || 
      Filename.check_suffix pathname ".TEXT" || 
      Filename.check_suffix pathname ".TXT" || 
      Filename.check_suffix pathname "README" || 
      Filename.check_suffix pathname "LISEZMOI" then
      "text"
    else if Filename.check_suffix pathname ".ps" || 
      Filename.check_suffix pathname ".eps" || 
      Filename.check_suffix pathname ".PS" || 
      Filename.check_suffix pathname ".EPS" then
      "ps"
    else if Filename.check_suffix pathname ".dvi" || 
      Filename.check_suffix pathname ".DVI" then
      "dvi"
    else if Filename.check_suffix pathname ".pdf" || 
      Filename.check_suffix pathname ".PDF" then
      "pdf"
    else
      failwith ("I cannot recognize the file type of " ^ pathname);

  method private format_to_reader format =
    match format with
    | "pdf"  -> Configuration.extract_string_variable_or ~default:"evince" "MARIONNET_PDF_READER"
    | "ps"   -> Configuration.extract_string_variable_or ~default:"evince" "MARIONNET_POSTSCRIPT_READER"
    | "dvi"  -> Configuration.extract_string_variable_or ~default:"evince" "MARIONNET_DVI_READER"
      (* 'file' may recognize (X)HTML as XML... *)
    | "html" -> Configuration.extract_string_variable_or ~default:"galeon" "MARIONNET_HTML_READER"
    | "text" -> Configuration.extract_string_variable_or ~default:"emacs"  "MARIONNET_TEXT_EDITOR"
      (* the file type in unknown: web browsers can open most everything... *)
    | "auto" -> Configuration.extract_string_variable_or ~default:"galeon" "MARIONNET_HTML_READER"
    | _ ->
      failwith ("The format \"" ^ format ^ "\" is not supported");

  (** Import the given file, copying it into the appropriate directory with a fresh name;
      return the fresh name (just the file name, not a complete pathname) and the name
      of an application suitable to read it, as a pair. In case of failure show an error
      message and raise an exception. If ~move is true then the file is moved instead of
      copied. *)
  method private import_file ?(move=false) pathname =
    try
      let file_format    = self#file_to_format pathname in
      let parent         = self#directory in
      let fresh_pathname = UnixExtra.temp_file ~parent ~prefix:"document-" () in
      let fresh_name     = Filename.basename fresh_pathname in
      let result         = (fresh_name, file_format) in
     (try
      (match move with
      | false -> UnixExtra.file_copy pathname fresh_pathname
      | true  -> UnixExtra.file_move pathname fresh_pathname
      );
      UnixExtra.set_perm ~a:() ~w:false fresh_pathname;
      Log.Command.ll fresh_pathname;
      result
      with Unix.Unix_error (_,_, _) ->
       begin
         UnixExtra.apply_ignoring_Unix_error Unix.unlink fresh_pathname;
         let title =
           Printf.sprintf "Failed copying the file \n\"%s\"\n" pathname in
         failwith title;
       end)
     with (Failure title) as e -> begin
      Simple_dialogs.error title error_message ();
      raise e (* Re-raise *)
    end

  method import_report ~machine_or_router_name ~pathname () =
    let title = (s_ "Report on ") ^ machine_or_router_name in
    let row_id = self#import_document ~move:true pathname in
    self#set_row_title   row_id title;
    self#set_row_author  row_id "-";
    self#set_row_type    row_id (s_ "Report");
    self#set_row_comment row_id ((s_ "created on ") ^ (UnixExtra.date ~dot:" " ()));

  method import_history ~machine_or_router_name ~pathname () =
    let title = (s_ "History of ") ^ machine_or_router_name in
    let row_id = self#import_document ~move:true pathname in
    self#set_row_title   row_id title;
    self#set_row_author  row_id "-";
    self#set_row_type    row_id (s_ "History");
    self#set_row_comment row_id ((s_ "created on ") ^ (UnixExtra.date ~dot:" " ()));

  method import_document ?(move=false) user_path_name =
    let internal_file_name, format = self#import_file user_path_name in
    let row_id =
      self#add_row
        [ filename_header, Row_item.String internal_file_name;
          format_header,   Row_item.String format ] 
    in
    let title = Filename.chop_extension (Filename.basename user_path_name) in
    let otype = FilenameExtra.get_extension user_path_name in
    let oauth = get_full_user_name () in
    let () = self#set_row_title (row_id) title in
    let () = Option.iter (self#set_row_type   row_id) otype in
    let () = Option.iter (self#set_row_author row_id) oauth in
    row_id

  initializer
    let _ =
      self#add_icon_column
        ~header:icon_header
        ~shown_header:(s_ "Icon")
        ~strings_and_pixbufs:[ "text", Initialization.Path.images^"treeview-icons/text.xpm"; ]
        ~default:(fun () -> Row_item.Icon "text")
        () in
    let _ =
      self#add_editable_string_column
        ~header:title_header
        ~shown_header:(s_ "Title")
        ~italic:true
        ~default:(fun () -> Row_item.String "Please edit this")
        () in
    let _ =
      self#add_editable_string_column
        ~header:author_header
        ~shown_header:(s_ "Author")
        ~italic:false
        ~default:(fun () -> Row_item.String "Please edit this")
        () in
    let _ =
      self#add_editable_string_column
        ~header:type_header
        ~shown_header:(s_ "Type")
        ~italic:false
        ~default:(fun () -> Row_item.String "Please edit this")
        () in
    let _ =
      self#add_editable_string_column
        ~shown_header:(s_ "Comment")
        ~header:"Comment"
        ~italic:true
        ~default:(fun () -> Row_item.String "Please edit this")
        () in
    let _ =
      self#add_string_column
        ~header:"FileName"
        ~hidden:true
        () in
    let _ =
      self#add_string_column
        ~header:"Format"
        ~default:(fun () -> Row_item.String "auto") (* unknown format; this is usefule for
                                              backward-compatibility, as this column
                                              didn't exist in older Marionnet versions *)
        ~hidden:true
        () in
    (* Make internal data structures: no more columns can be added now: *)
    self#create_store_and_view;

    (* Setup the contextual menu: *)
    self#set_contextual_menu_title "Texts operations";
    self#add_menu_item
      (s_ "Import a document")
      (fun _ -> true)
      (fun _ ->
        ignore (Option.map self#import_document self#ask_file));

    self#add_menu_item
      (s_ "Display this document")
      Option.to_bool
      (fun selected_rowid_if_any ->
        let row_id = Option.extract selected_rowid_if_any in
        self#display row_id);
    self#set_double_click_on_row_callback (fun row_id -> self#display row_id);

    self#add_menu_item
      (s_ "Remove this document")
      Option.to_bool
      (fun selected_rowid_if_any ->
        let row_id = Option.extract selected_rowid_if_any in
        let file_name = (self#get_row_filename row_id) in
        let pathname = Filename.concat (self#directory) (file_name) in
        UnixExtra.apply_ignoring_Unix_error Unix.unlink pathname;
        self#remove_row row_id;
        );

     (* J.V. *)
     self#set_after_update_callback after_user_edit_callback;

end;;

class treeview = t
module The_unique_treeview = Stateful_modules.Variable (struct
  type t = treeview
  let name = Some "treeview_documents"
  end)
let extract = The_unique_treeview.extract


(* Add the button "Import" at right side of the treeview. *)
let add_import_button ~(window:GWindow.window) ~(hbox:GPack.box) ~(toolbar:GButton.toolbar) (treeview:t) : unit =
  let packing = toolbar#add in
  (* --- *)  
  let b = Gui_bricks.button_image ~window ~packing ~stock:`ADD ~stock_size:`SMALL_TOOLBAR ~tooltip:(s_ "Import a document") () in
  (* --- *)
  (* Behaviour on click: *)
  let callback () = ignore (Option.map treeview#import_document treeview#ask_file) in
  let () = ignore (b#connect#clicked ~callback) in
  ()

let make ~(window:GWindow.window) ~(hbox:GPack.box) ~after_user_edit_callback ~method_directory ~method_filename () =
  let result = new t ~packing:(hbox#add) ~after_user_edit_callback ~method_directory ~method_filename () in
  let toolbar = Treeview.add_expand_and_collapse_button ~window ~hbox (result:>Treeview.t) in
  let _import = add_import_button ~window ~hbox ~toolbar (result) in
  The_unique_treeview.set result;
  result
;;