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