1''' Library watch list dialog and backend classes. ''' 2 3import os 4from gi.repository import Gtk 5from gi.repository import GLib, GObject 6 7from mcomix import tools 8from mcomix.library import backend_types 9from mcomix.preferences import prefs 10 11 12COL_DIRECTORY = 0 13COL_COLLECTION = 0 14COL_COLLECTION_ID = 1 15COL_RECURSIVE = 2 16 17class WatchListDialog(Gtk.Dialog): 18 ''' Dialog for managing watched directories. ''' 19 20 RESPONSE_SCANNOW = 1000 21 22 def __init__(self, library): 23 ''' Dialog constructor. 24 @param library: Dialog parent window, should be library window. 25 ''' 26 super(WatchListDialog, self).__init__( 27 title=_('Library watch list'), modal=True, destroy_with_parent=True 28 ) 29 30 self.add_buttons( 31 _('_Scan now'), WatchListDialog.RESPONSE_SCANNOW, 32 Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE 33 ) 34 35 #: Stores a reference to the library 36 self.library = library 37 #: True if changes were made to the watchlist. Not 100% accurate. 38 self._changed = False 39 40 self.set_default_response(Gtk.ResponseType.CLOSE) 41 42 # Initialize treeview control showing existing watch directories 43 self._treeview = Gtk.TreeView(model=self._create_model()) 44 self._treeview.set_headers_visible(True) 45 self._treeview.get_selection().connect('changed', self._item_selected_cb) 46 47 dir_renderer = Gtk.CellRendererText() 48 dir_column = Gtk.TreeViewColumn(_('Directory'), dir_renderer) 49 dir_column.set_attributes(dir_renderer, text=COL_DIRECTORY) 50 dir_column.set_expand(True) 51 self._treeview.append_column(dir_column) 52 53 collection_model = self._create_collection_model() 54 collection_renderer = Gtk.CellRendererCombo() 55 collection_renderer.set_property('model', collection_model) 56 collection_renderer.set_property('text-column', COL_COLLECTION) 57 collection_renderer.set_property('editable', True) 58 collection_renderer.set_property('has-entry', False) 59 collection_renderer.connect('changed', self._collection_changed_cb, collection_model) 60 collection_column = Gtk.TreeViewColumn(_('Collection'), collection_renderer) 61 collection_column.set_cell_data_func(collection_renderer, 62 self._treeview_collection_id_to_name) 63 self._treeview.append_column(collection_column) 64 65 recursive_renderer = Gtk.CellRendererToggle() 66 recursive_renderer.set_activatable(True) 67 recursive_renderer.connect('toggled', self._recursive_changed_cb) 68 recursive_column = Gtk.TreeViewColumn(_('With subdirectories'), 69 recursive_renderer) 70 recursive_column.add_attribute(recursive_renderer, 'active', COL_RECURSIVE) 71 self._treeview.append_column(recursive_column) 72 73 add_button = Gtk.Button.new_from_stock(Gtk.STOCK_ADD) 74 add_button.connect('clicked', self._add_cb) 75 remove_button = Gtk.Button.new_from_stock(Gtk.STOCK_REMOVE) 76 remove_button.set_sensitive(False) 77 remove_button.connect('clicked', self._remove_cb) 78 self._remove_button = remove_button 79 80 button_box = Gtk.VBox() 81 button_box.pack_start(add_button, False, True, 0) 82 button_box.pack_start(remove_button, False, True, 2) 83 84 main_box = Gtk.HBox() 85 scroll_window = Gtk.ScrolledWindow() 86 scroll_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) 87 scroll_window.add(self._treeview) 88 main_box.pack_start(scroll_window, True, True, 2) 89 main_box.pack_end(button_box, False, True, 0) 90 self.vbox.pack_start(main_box, True, True, 0) 91 92 auto_checkbox = Gtk.CheckButton( 93 label=_('Automatically scan for new books when library is _opened'), use_underline=True) 94 auto_checkbox.set_active(prefs['scan for new books on library startup']) 95 auto_checkbox.connect('toggled', self._auto_scan_toggled_cb) 96 self.vbox.pack_end(auto_checkbox, False, False, 5) 97 98 self.resize(475, 350) 99 self.connect('response', self._close_cb) 100 self.show_all() 101 102 def get_selected_watchlist_entry(self): 103 ''' Returns the selected watchlist entry, or C{None} if no 104 item is selected. ''' 105 selection = self._treeview.get_selection() 106 107 model, iter = selection.get_selected() 108 if iter is not None: 109 path = str(model.get_value(iter, COL_DIRECTORY)) 110 return self.library.backend.watchlist.get_watchlist_entry(path) 111 else: 112 return None 113 114 def get_watchlist_entry_for_treepath(self, treepath): 115 ''' Converts a tree path to WatchlistEntry object. ''' 116 model = self._treeview.get_model() 117 iter = model.get_iter(treepath) 118 dirpath = str(model.get_value(iter, COL_DIRECTORY)) 119 return self.library.backend.watchlist.get_watchlist_entry(dirpath) 120 121 def _create_model(self): 122 ''' Creates a model containing all watched directories. ''' 123 # Watched directory, associated library collection ID 124 model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_INT, GObject.TYPE_BOOLEAN) 125 self._fill_model(model) 126 return model 127 128 def _fill_model(self, model): 129 ''' Empties the model's data and updates it from the database. ''' 130 model.clear() 131 for entry in self.library.backend.watchlist.get_watchlist(): 132 if entry.collection.id is None: 133 id = -1 134 else: 135 id = entry.collection.id 136 137 model.append((entry.directory, id, entry.recursive)) 138 139 def _create_collection_model(self): 140 ''' Creates a model containing all available collections. ''' 141 # Collection ID, collection name 142 model = Gtk.ListStore(GObject.TYPE_STRING, GObject.TYPE_INT) 143 144 ids = self.library.backend.get_all_collections() 145 model.append((backend_types.DefaultCollection.name, -1)) 146 for id in ids: 147 model.append((self.library.backend.get_collection_name(id), id)) 148 149 return model 150 151 def _collection_changed_cb(self, column, path, 152 collection_iter, collection_model, *args): 153 ''' A new collection was set for a watched directory. ''' 154 # Get new collection ID from collection model 155 new_id = collection_model.get_value(collection_iter, COL_COLLECTION_ID) 156 collection = self.library.backend.get_collection_by_id(new_id) 157 158 # Update database 159 self.get_watchlist_entry_for_treepath(path).set_collection(collection) 160 161 # Update collection ID in watchlist model 162 model = self._treeview.get_model() 163 iter = model.get_iter(path) 164 # Editing the model in the CellRendererCombo callback stops the editing 165 # operation, causing GTK warnings. Delay until callback is finished. 166 GLib.idle_add(model.set_value, iter, COL_COLLECTION_ID, new_id) 167 168 self._changed = True 169 170 def _recursive_changed_cb(self, toggle_renderer, path, *args): 171 ''' Recursive reading was enabled or disabled. ''' 172 status = not toggle_renderer.get_active() 173 self.get_watchlist_entry_for_treepath(path).set_recursive(status) 174 175 # Update recursive status in watchlist model 176 model = self._treeview.get_model() 177 iter = model.get_iter(path) 178 model.set_value(iter, COL_RECURSIVE, status) 179 180 self._changed = True 181 182 def _add_cb(self, button, *args): 183 ''' Called when a new watch list entry should be added. ''' 184 filechooser = Gtk.FileChooserDialog( 185 action=Gtk.FileChooserAction.SELECT_FOLDER 186 ) 187 filechooser.set_transient_for(self) 188 filechooser.add_buttons( 189 Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, 190 Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT 191 ) 192 result = filechooser.run() 193 if filechooser.get_filename() is not None: 194 directory = filechooser.get_filename() 195 else: 196 directory = u'' 197 filechooser.destroy() 198 directory = tools.relpath2root(directory,abs_fallback=prefs['portable allow abspath']) 199 if not directory: 200 # directory is None, means running in portable mode 201 # and currect path is out of same mount point 202 # so do not add directory to watchlist 203 return 204 205 if result == Gtk.ResponseType.ACCEPT \ 206 and os.path.isdir(directory): 207 208 self.library.backend.watchlist.add_directory(directory) 209 self._fill_model(self._treeview.get_model()) 210 211 self._changed = True 212 213 def _remove_cb(self, button, *args): 214 ''' Called when a watch list entry should be removed. ''' 215 entry = self.get_selected_watchlist_entry() 216 if entry: 217 entry.remove() 218 219 # Remove selection from list 220 selection = self._treeview.get_selection() 221 model, iter = selection.get_selected() 222 model.remove(iter) 223 224 def _item_selected_cb(self, selection, *args): 225 ''' Called when an item is selected. Enables or disables the 'Remove' 226 button. ''' 227 self._remove_button.set_sensitive(selection.count_selected_rows() > 0) 228 229 def _auto_scan_toggled_cb(self, checkbox, *args): 230 ''' Toggles automatic library book scanning. ''' 231 prefs['scan for new books on library startup'] = checkbox.get_active() 232 233 def _treeview_collection_id_to_name(self, column, cell, model, iter, *args): 234 ''' Maps a collection ID to the corresponding collection name. ''' 235 id = model.get_value(iter, COL_COLLECTION_ID) 236 if id != -1: 237 text = self.library.backend.get_collection_name(id) 238 else: 239 text = backend_types.DefaultCollection.name 240 241 cell.set_property('text', text) 242 243 def _close_cb(self, dialog, response, *args): 244 ''' Trigger scan for new files after watch dialog closes. ''' 245 self.destroy() 246 if response == Gtk.ResponseType.CLOSE and self._changed: 247 self.library.scan_for_new_files() 248 elif response == WatchListDialog.RESPONSE_SCANNOW: 249 self.library.scan_for_new_files() 250 251 252# vim: expandtab:sw=4:ts=4 253