1/* 2 * This file is part of GNOME LaTeX. 3 * 4 * Copyright © 2011, 2012 Sébastien Wilmet 5 * 6 * GNOME LaTeX is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * GNOME LaTeX is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with GNOME LaTeX. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20using Gtk; 21 22public class CleanBuildFiles : GLib.Object 23{ 24 private enum CleanFileColumn 25 { 26 DELETE, 27 NAME, 28 FILE, 29 N_COLUMNS 30 } 31 32 private unowned MainWindow _main_window; 33 private Document _doc; 34 private GLib.Settings _settings; 35 36 public CleanBuildFiles (MainWindow main_window, Document doc) 37 { 38 _main_window = main_window; 39 _doc = doc; 40 _settings = new GLib.Settings ("org.gnome.gnome-latex.preferences.latex"); 41 } 42 43 public void clean () 44 { 45 if (!_doc.is_main_file_a_tex_file ()) 46 return; 47 48 Gee.ArrayList<File> files_to_delete; 49 File directory; 50 51 Project? project = _doc.get_project (); 52 53 // the document is part of a project 54 if (project != null) 55 { 56 directory = project.directory; 57 files_to_delete = get_build_files_in_directory (directory); 58 } 59 else 60 { 61 directory = _doc.location.get_parent (); 62 files_to_delete = get_build_files_simple (); 63 } 64 65 bool no_confirm = _settings.get_boolean ("no-confirm-clean"); 66 67 if (files_to_delete.size == 0) 68 { 69 if (!no_confirm) 70 show_info_no_file (); 71 return; 72 } 73 74 if (no_confirm) 75 { 76 foreach (File file_to_delete in files_to_delete) 77 Utils.delete_file (file_to_delete); 78 } 79 else 80 confirm_cleanup (files_to_delete, directory); 81 } 82 83 // Get the list of build files for a simple document (not part of a project). 84 private Gee.ArrayList<File> get_build_files_simple () 85 { 86 File location = _doc.location; 87 File directory = location.get_parent (); 88 89 string exts = _settings.get_string ("clean-extensions"); 90 string[] extensions = exts.split (" "); 91 92 string shortname = Tepl.utils_get_file_shortname (location.get_basename ()); 93 94 Gee.ArrayList<File> files_to_delete = new Gee.ArrayList<File> (); 95 96 foreach (string extension in extensions) 97 { 98 string basename = shortname + extension; 99 File file = directory.get_child (basename); 100 if (file.query_exists ()) 101 files_to_delete.add (file); 102 } 103 104 return files_to_delete; 105 } 106 107 // Get the list of build files of a project. 108 private Gee.ArrayList<File> get_build_files_in_directory (File directory) 109 { 110 string exts = _settings.get_string ("clean-extensions"); 111 string[] extensions = exts.split (" "); 112 113 Gee.ArrayList<File> files_to_delete = new Gee.ArrayList<File> (); 114 FileEnumerator enumerator; 115 116 try 117 { 118 enumerator = directory.enumerate_children ("standard::type,standard::name", 119 FileQueryInfoFlags.NONE); 120 } 121 catch (Error e) 122 { 123 warning ("Clean build files: %s", e.message); 124 return files_to_delete; 125 } 126 127 while (true) 128 { 129 FileInfo? info = null; 130 131 try 132 { 133 info = enumerator.next_file (); 134 } 135 catch (Error e) 136 { 137 warning ("Clean build files: %s", e.message); 138 break; 139 } 140 141 if (info == null) 142 break; 143 144 string name = info.get_name (); 145 146 // don't take into account hidden files and directories 147 // example: Git have a *.idx file in the .git/ directory 148 if (name[0] == '.') 149 continue; 150 151 File file = directory.get_child (name); 152 153 FileType type = info.get_file_type (); 154 if (type == FileType.DIRECTORY) 155 { 156 var files_to_delete_in_dir = get_build_files_in_directory (file); 157 files_to_delete.add_all (files_to_delete_in_dir); 158 continue; 159 } 160 161 foreach (string ext in extensions) 162 { 163 if (name.has_suffix (ext)) 164 { 165 files_to_delete.add (file); 166 break; 167 } 168 } 169 } 170 171 return files_to_delete; 172 } 173 174 private void confirm_cleanup (Gee.ArrayList<File> files_to_delete, File directory) 175 { 176 return_if_fail (0 < files_to_delete.size); 177 178 TreeView list_files = get_list_files (files_to_delete, directory); 179 Dialog dialog = get_dialog (list_files); 180 181 run_dialog (dialog, list_files.get_model ()); 182 } 183 184 private TreeView get_list_files (Gee.ArrayList<File> files_to_delete, File directory) 185 { 186 TreeView treeview = new TreeView (); 187 treeview.set_size_request (260, 120); 188 treeview.headers_visible = false; 189 treeview.enable_search = false; 190 191 Gtk.ListStore store = new Gtk.ListStore (CleanFileColumn.N_COLUMNS, 192 typeof (bool), typeof (string), typeof (File)); 193 194 store.set_sort_func (0, on_sort_list_files); 195 store.set_sort_column_id (0, SortType.ASCENDING); 196 197 // fill the list 198 foreach (File file_to_delete in files_to_delete) 199 { 200 string relative_path = Utils.get_relative_path (directory, file_to_delete, 201 directory); 202 TreeIter iter; 203 store.append (out iter); 204 store.set (iter, 205 CleanFileColumn.DELETE, true, 206 CleanFileColumn.NAME, relative_path, 207 CleanFileColumn.FILE, file_to_delete 208 ); 209 } 210 211 treeview.set_model (store); 212 CellRendererToggle toggle_renderer = new CellRendererToggle (); 213 214 toggle_renderer.toggled.connect ((path_str) => 215 { 216 TreePath path = new TreePath.from_string (path_str); 217 TreeIter iter; 218 bool active; 219 store.get_iter (out iter, path); 220 store.get (iter, CleanFileColumn.DELETE, out active); 221 // inverse the value 222 store.set (iter, CleanFileColumn.DELETE, !active); 223 }); 224 225 TreeViewColumn column = new TreeViewColumn.with_attributes ("Delete?", 226 toggle_renderer, "active", CleanFileColumn.DELETE, null); 227 treeview.append_column (column); 228 229 CellRendererText text_renderer = new CellRendererText (); 230 column = new TreeViewColumn.with_attributes ("Name", text_renderer, 231 "text", CleanFileColumn.NAME); 232 treeview.append_column (column); 233 234 return treeview; 235 } 236 237 private Dialog get_dialog (TreeView list_files) 238 { 239 Dialog dialog = new Dialog.with_buttons (null, 240 _main_window, 241 DialogFlags.DESTROY_WITH_PARENT, 242 _("_Cancel"), ResponseType.CANCEL, 243 _("_Delete"), ResponseType.ACCEPT 244 ); 245 246 Grid grid = new Grid (); 247 grid.set_column_spacing (12); 248 grid.set_row_spacing (8); 249 grid.border_width = 5; 250 251 Box content_area = dialog.get_content_area () as Box; 252 content_area.pack_start (grid); 253 254 /* image */ 255 Image image = new Image.from_icon_name ("dialog-warning", IconSize.DIALOG); 256 image.set_valign (Align.START); 257 grid.attach (image, 0, 0, 1, 3); 258 259 /* primary label */ 260 Label primary_label = new Label (null); 261 primary_label.set_line_wrap (true); 262 primary_label.set_use_markup (true); 263 primary_label.set_halign (Align.START); 264 primary_label.set_selectable (true); 265 primary_label.margin_bottom = 4; 266 primary_label.set_markup ("<span weight=\"bold\" size=\"larger\">" 267 + _("Do you really want to delete these files?") + "</span>"); 268 269 grid.attach (primary_label, 1, 0, 1, 1); 270 271 /* secondary label */ 272 Label select_label = new Label (_("Select the files you want to delete:")); 273 select_label.set_line_wrap (true); 274 select_label.set_halign (Align.START); 275 grid.attach (select_label, 1, 1, 1, 1); 276 277 /* list of files with a scrollbar */ 278 ScrolledWindow sw = Utils.add_scrollbar (list_files); 279 sw.set_shadow_type (ShadowType.IN); 280 sw.expand = true; 281 grid.attach (sw, 1, 2, 1, 1); 282 283 grid.show_all (); 284 285 return dialog; 286 } 287 288 private void run_dialog (Dialog dialog, TreeModel list_store) 289 { 290 if (dialog.run () == ResponseType.ACCEPT) 291 { 292 // get files to delete 293 File[] selected_files = {}; 294 TreeIter iter; 295 bool valid = list_store.get_iter_first (out iter); 296 while (valid) 297 { 298 bool selected; 299 File file_to_delete; 300 301 list_store.get (iter, 302 CleanFileColumn.DELETE, out selected, 303 CleanFileColumn.FILE, out file_to_delete 304 ); 305 306 if (selected) 307 selected_files += file_to_delete; 308 309 valid = list_store.iter_next (ref iter); 310 } 311 312 foreach (File file_to_delete in selected_files) 313 Utils.delete_file (file_to_delete); 314 } 315 316 dialog.destroy (); 317 } 318 319 private int on_sort_list_files (TreeModel model, TreeIter a, TreeIter b) 320 { 321 string name_a; 322 string name_b; 323 324 model.get (a, CleanFileColumn.NAME, out name_a); 325 model.get (b, CleanFileColumn.NAME, out name_b); 326 327 return name_a.collate (name_b); 328 } 329 330 private void show_info_no_file () 331 { 332 Dialog dialog = new MessageDialog (_main_window, 333 DialogFlags.DESTROY_WITH_PARENT, 334 MessageType.INFO, 335 ButtonsType.OK, 336 "%s", _("No build file to clean up.")); 337 338 dialog.run (); 339 dialog.destroy (); 340 } 341} 342