Esempio n. 1
0
#!/usr/bin/env python3

import os

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import exc
from sqlalchemy.sql.expression import func

from src.Config import ConfigManager
from src.Logging import createLogger

logger = createLogger("CommonDAO")

db_path = os.path.join(ConfigManager.profile_folder, 'data.db')
engine = create_engine('sqlite:///' + db_path, echo=ConfigManager.debugSql)
if not os.path.exists(db_path):
    logger.info("Create database: " + db_path)
    from .entities.Persistent import Base
    Base.metadata.create_all(engine)

sessionMaker = sessionmaker(bind=engine,
                            expire_on_commit=False)

class SessionDAO:
    _session = None

    def getSession(self):
        '''
            Get the current session.
Esempio n. 2
0
class EditorCtrl(BaseController):

    log = createLogger(__name__)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.ui = EditorUI(self)

    @ensureLoading
    def start(self):
        self.ui.show()

    def stop(self):
        self.ui.close()

    def load(self):
        self.metatags = metatagsDao.getAll()
        self.tags = tagsDao.getAll()

    def setupUpdateEvents(self):
        super().setupUpdateEvents()
        self.on_update[UPDATE_METATAGS] = []
        self.on_update[UPDATE_TAGS] = []

    def _updateTags(self):
        self.tags = tagsDao.getAll()
        self.trigger(UPDATE_TAGS)

    def _updateMetatags(self):
        self.metatags = metatagsDao.getAll()
        self.trigger(UPDATE_METATAGS)

    # Public methods
    def addTag(self, name, metatag):
        self.log.info("Add tag %s with metatag %s" % (name, metatag.name))
        try:
            tagsDao.insert(name, metatag)
        except Exception:
            return False
        # Update
        self._updateTags()
        return True

    def addMetatag(self, name):
        self.log.info("Add metatag %s" % name)
        try:
            metatagsDao.insert(name)
        except Exception:
            return False
        # Update
        self._updateMetatags()
        return True

    def deleteTag(self, tag):
        self.log.info("Delete tag %s" % tag.name)
        try:
            tagsDao.delete(tag)
        except Exception:
            return False
        # Update
        self._updateTags()
        return True

    def deleteMetatag(self, metatag):
        self.log.info("Delete metatag %s" % metatag.name)
        try:
            metatagsDao.delete(metatag)
        except Exception:
            return False
        # Update
        self._updateMetatags()
        return True

    def editTag(self, tag, new_name, new_metatag):
        self.log.info("Edit tag %s" % tag.name)
        self.log.info("New name %s, new metatag: %s" %
                      (new_name, new_metatag.name))
        try:
            tagsDao.update(tag, new_name, new_metatag)
        except Exception:
            return False
        # Update
        self._updateTags()
        return True

    def editMetatag(self, metatag, new_name):
        self.log.info("Edit metatag %s" % metatag.name)
        self.log.info("New name %s" % new_name)
        try:
            metatagsDao.update(metatag, name=new_name)
        except Exception:
            return False
        # Update
        self._updateMetatags()
        self._updateTags()
        return True

    # Update listeners
    def onUpdateMetatags(self, func):
        self.onUpdate(UPDATE_METATAGS, func)

    def onUpdateTags(self, func):
        self.onUpdate(UPDATE_TAGS, func)
Esempio n. 3
0
class EditorUI(BaseInterface):

    log = createLogger(__name__)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.window = None
        self.log.debug("Done")

    def _build(self):
        '''
            Build the interface.
        '''
        self.builder = Gtk.Builder()
        ui_file = os.path.join(GLADE_FOLDER, 'Editor.glade')
        self.builder.add_from_file(ui_file)
        self._buildTagsAutocompleteBox()
        self.window = self.builder.get_object('Main')
        self.window.resize(400, 100)
        # Register window
        app = self.ctrl.services.getApplication()
        app.add_window(self.window)
        # Register the update events
        self.ctrl.onUpdateMetatags(self.onUpdateMetatags)
        self.ctrl.onUpdateTags(self.onUpdateTags)
        # Connect signals
        self.builder.connect_signals(self)

    def _buildTagsAutocompleteBox(self):
        self.tags_store = Gtk.ListStore(int, str, int)
        edit_entry = self.builder.get_object("EditTagSelect")
        self._buildAutocompletionBox(edit_entry, self.onTagChanged)
        delete_entry = self.builder.get_object("DeleteTagSelect")
        self._buildAutocompletionBox(delete_entry)

    def _buildAutocompletionBox(self, search_entry, on_match_selected=None):
        '''
            Set the autocompletion behaviour.
        '''
        completion = Gtk.EntryCompletion.new()
        # Set model
        completion.set_model(self.tags_store)
        completion.set_text_column(1)
        # Settings
        completion.set_inline_completion(False)
        completion.set_popup_completion(True)
        completion.set_popup_set_width(True)
        completion.set_popup_single_match(True)
        completion.set_inline_selection(True)
        if on_match_selected is not None:
            completion.connect("match-selected", on_match_selected)
        # Add to entry
        search_entry.set_completion(completion)

    def show(self):
        if self.window is None:
            self._build()
        self.window.show()

    def close(self):
        self.window.close()

    def setParent(self, window):
        self.window.set_transient_for(window)

    # LISTENERS
    def _updateMetatagSelector(self, selector):
        selector.remove_all()
        for metatag in self.ctrl.metatags:
            selector.append_text(metatag.name)

    def onUpdateMetatags(self):
        selector_ids = ["AddTagMetatagSelect", "EditTagMetatagSelect", "EditMetatagSelect", "DeleteMetatagSelect"]
        for sid in selector_ids:
            selector = self.builder.get_object(sid)
            self._updateMetatagSelector(selector)

    def onUpdateTags(self):
        self.tags_store.clear()
        tags_store = [[index, tag, tag.name.lower()] for index, tag in enumerate(self.ctrl.tags)]
        sorted_tags = sorted(tags_store, key=lambda element: element[2])
        for position, tag, _ in sorted_tags:
            self.tags_store.append([tag.id, tag.name, position])

    # SELECTORS GETTERS
    def _getSelectedTag(self, widget):
        '''
            Return the tag selected in the widget.
            Widget should extend Gtk.Entry.
        '''
        tag_name = widget.get_text()
        matched_tags = list(filter(lambda t: t.name == tag_name, self.ctrl.tags))
        if(len(matched_tags) == 0):
            return None
        else:
            return matched_tags[0]

    # SIGNALS
    def _getWidgetText(self, selector_id):
        label = self.builder.get_object(selector_id)
        name = label.get_text()
        return name.strip()

    def _getSelectorElement(self, selector_id, model):
        selector = self.builder.get_object(selector_id)
        active = selector.get_active()
        if active == -1:
            return None
        else:
            return model[active]

    def _resetLabel(self, selector_id):
        label = self.builder.get_object(selector_id)
        label.set_text("")

    def _resetSelector(self, selector_id):
        selector = self.builder.get_object(selector_id)
        selector.set_active(-1)

    def onClose(self, widget):
        self.ctrl.stop()

    def onAddMetatag(self, widget):
        name = self._getWidgetText("AddMetatagName")
        if len(name) == 0:
            return
        success = self.ctrl.addMetatag(name)
        if success:
            # Reset
            self._resetLabel("AddMetatagName")
        else:
            self.createMessageDialog("Error", "Could not add the metatag %s" % name)

    def onAddTag(self, widget):
        name = self._getWidgetText("AddTagName")
        metatag = self._getSelectorElement("AddTagMetatagSelect", self.ctrl.metatags)
        if len(name) == 0 or metatag is None:
            return
        success = self.ctrl.addTag(name, metatag)
        if success:
            # Reset
            self._resetLabel("AddTagName")
            self._resetSelector("AddTagMetatagSelect")
        else:
            self.createMessageDialog("Error", "Could not add the tag %s" % name)

    def onEditMetatag(self, widget):
        metatag = self._getSelectorElement("EditMetatagSelect", self.ctrl.metatags)
        new_name = self._getWidgetText("EditMetatagName")
        if metatag is None or len(new_name) == 0:
            return
        success = self.ctrl.editMetatag(metatag, new_name)
        if success:
            # Reset
            self._resetSelector("EditMetatagSelect")
            self._resetLabel("EditMetatagName")
        else:
            self.createMessageDialog("Error", "Could not rename the metatag %s" % metatag.name)

    def onEditTag(self, widget):
        tag_widget = self.builder.get_object("EditTagSelect")
        tag = self._getSelectedTag(tag_widget)
        new_name = self._getWidgetText("EditTagName")
        new_metatag = self._getSelectorElement("EditTagMetatagSelect", self.ctrl.metatags)
        if tag is None or len(new_name) == 0 or new_metatag is None:
            return
        success = self.ctrl.editTag(tag, new_name, new_metatag)
        if success:
            # Reset
            self._resetLabel("EditTagSelect")
            self._resetLabel("EditTagName")
            self._resetSelector("EditTagMetatagSelect")
        else:
            self.createMessageDialog("Error", "Could not edit the tag %s" % tag.name)

    def onDeleteMetatag(self, widget):
        metatag = self._getSelectorElement("DeleteMetatagSelect", self.ctrl.metatags)
        if metatag is None:
            return
        onConfirm = lambda : self.onDeleteMetatagReal(metatag)
        dialog = self.createConfirmationDialog("Confirm deletion",
                                               "Do you want to delete the metatag %s ?" % metatag.name,
                                               onConfirm)

    def onDeleteTag(self, widget):
        tag_widget = self.builder.get_object("DeleteTagSelect")
        tag = self._getSelectedTag(tag_widget)
        if tag is None:
            return
        onConfirm = lambda : self.onDeleteTagReal(tag)
        dialog = self.createConfirmationDialog("Confirm deletion",
                                               "Do you want to delete the tag %s ?" % tag.name,
                                               onConfirm)

    def onDeleteMetatagReal(self, metatag):
        success = self.ctrl.deleteMetatag(metatag)
        if success:
            self._resetSelector("DeleteMetatagSelect")
        else:
            self.createMessageDialog("Error", "Could not delete metatag %s" % metatag.name)

    def onDeleteTagReal(self, tag):
        success = self.ctrl.deleteTag(tag)
        if success:
            self._resetLabel("DeleteTagSelect")
        else:
            self.createMessageDialog("Error", "Could not delete tag %s" % tag.name)

    def onMetatagChanged(self, widget):
        metatag = self._getSelectorElement("EditMetatagSelect", self.ctrl.metatags)
        if metatag is None:
            return
        label = self.builder.get_object("EditMetatagName")
        label.set_text(metatag.name)

    def onTagChanged(self, widget, model, path):
        _, _, index = model[path]
        tag = self.ctrl.tags[index]
        if tag is None:
            return
        # Set name
        label = self.builder.get_object("EditTagName")
        label.set_text(tag.name)
        # Set metatag
        active_index = -1
        for index, metatag in enumerate(self.ctrl.metatags):
            if metatag.name == tag.metatag.name:
                active_index = index
                break
        selector = self.builder.get_object("EditTagMetatagSelect")
        selector.set_active(active_index)
Esempio n. 4
0
class MoverUI(BaseInterface):

    log = createLogger(__name__)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.window = None
        self.metatag_selectors = {}
        self.custom_selectors = {}
        self.suggested_tags = []
        self.log.debug("Done")

    def _build(self):
        '''
            Build the interface.
        '''
        self.builder = Gtk.Builder()
        ui_file = os.path.join(GLADE_FOLDER, 'AddFile.glade')
        self.builder.add_from_file(ui_file)
        self.window = self.builder.get_object('Main')
        self.window.resize(WINDOW_WIDTH, WINDOW_HEIGHT)
        self.window.set_title("Move file to profile: %s" %
                              ConfigManager.getProfileName())
        # Register window
        app = self.ctrl.services.getApplication()
        self.window.set_icon_from_file(app.icon_path)
        app.add_window(self.window)
        # Build custom components
        self._buildCustom()
        # Setup target name cell
        if self.ctrl.target_name_pattern is not None:
            entry = self.builder.get_object('DestinationName')
            entry.set_editable(False)
        # Register the update events
        self.ctrl.onUpdatePath(self.onPathChange)
        # Connect signals
        self.builder.connect_signals(self)

    def _buildCustom(self):
        '''
            Build custom components.
        '''
        custom_box = self.builder.get_object("CustomSelectors")
        # Build metatags selectors
        for metatag, selector_name in self.ctrl.metatags.items():
            selector = None
            if selector_name == SELECTOR_AUTOCOMPLETE:
                selector = self._buildAutocompletion(metatag)
            elif selector_name == SELECTOR_COMBOBOX:
                selector = self._buildComboBox(metatag)
            self.metatag_selectors[metatag] = selector
            # Add to UI
            self._addToCustomSelectors(custom_box, metatag, selector)
            '''
            box = self._buildSelectorBox(metatag, selector)
            custom_box.add(box)
            '''
        for entry_name, entry in self.ctrl.custom_entries.items():
            selector = None
            if entry["type"] == SELECTOR_FREETEXT:
                selector = self._buildFreeTextEntry(entry_name, entry)
            if selector is None:
                continue
            self.custom_selectors[entry_name] = selector
            self._addEntryToCustomSelectors(custom_box, entry_name, selector)
        custom_box.show_all()
        self.onSelectorValueChaged()
        # Build suggested tags
        pass

    def show(self):
        if self.window is None:
            self._build()
        self.window.show()

    def _addToCustomSelectors(self, custom_grid, metatag, selector):
        self._addEntryToCustomSelectors(custom_grid, metatag.name, selector)

    def _addEntryToCustomSelectors(self, custom_grid, entry_name, selector):
        label = Gtk.Label(entry_name + ": ")
        label.set_halign(Gtk.Align.END)
        label.set_valign(Gtk.Align.FILL)
        custom_grid.add(label)
        selector.set_halign(Gtk.Align.START)
        selector.set_valign(Gtk.Align.FILL)
        custom_grid.attach_next_to(selector, label, Gtk.PositionType.RIGHT, 1,
                                   1)

    def _buildAutocompletion(self, metatag):
        '''
            Build an autocompletion box for a metatag.

            :param dao.entities.IMetatag: metatag
            :return: Autocompletion box for the metatag
            :rtype: Gtk.SearchEntry
        '''
        completion = Gtk.EntryCompletion.new()
        # Set model
        model = Gtk.ListStore(str)
        for tag in metatag.tags:
            model.append([tag.name])
        completion.set_model(model)
        completion.set_text_column(0)
        # Settings
        completion.set_inline_completion(False)
        completion.set_popup_completion(True)
        completion.set_popup_set_width(True)
        completion.set_popup_single_match(True)
        completion.set_inline_selection(True)
        # Create entry with completion
        entry = Gtk.SearchEntry()
        entry.set_completion(completion)
        entry.set_width_chars(ENTRIES_WIDTH_CHARS)
        # Set default value
        value = self._getMetatagDefaultValue(metatag)
        if value is not None:
            entry.set_text(value)
        # Connect events
        entry.connect("changed", self.onSelectorChanged, metatag)
        return entry

    def _buildComboBox(self, metatag):
        '''
            Build a selector box for a metatag.

            :param dao.entities.IMetatag: metatag
            :return: Combo box for the metatag
            :rtype: Gtk.ComboBoxText
        '''
        # Build the selector
        selector = Gtk.ComboBoxText()
        for tag in metatag.tags:
            selector.append_text(tag.name)
        # Get default value
        value = self._getMetatagDefaultValue(metatag)
        if value is None:
            selector.set_active(0)
        else:
            for i, tag in enumerate(metatag.tags):
                if tag.name == value:
                    selector.set_active(i)
                    break
        # Connect signals
        selector.connect("changed", self.onSelectorChanged, metatag)
        return selector

    def _buildFreeTextEntry(self, name, entry):
        default_value = entry["default-value"]
        selector = Gtk.Entry()
        selector.set_text(default_value)
        selector.set_width_chars(ENTRIES_WIDTH_CHARS)
        selector.connect("changed", self.onCustomSelectorChanged, name)
        return selector

    def _getMetatagDefaultValue(self, metatag):
        '''
            Get the default value for a metatag.

            :param dao.entitis.IMetatag: metatag
            :returns: Default value
            :rtype: str
        '''
        value = None
        if self.ctrl.default_values is not None and \
           DEFAULT_METATAGS in self.ctrl.default_values and \
           metatag.name in self.ctrl.default_values[DEFAULT_METATAGS]:
            value = self.ctrl.default_values[DEFAULT_METATAGS][metatag.name]
        return value

    def _buildSuggestedTagList(self):
        '''
            Build the suggested tags list.
        '''
        pass

    def _getSelectorValue(self, metatag, widget):
        '''
            Get the value of a custom selector.

            :param Gtk.ComboBoxText or Gtk.Entry: Custom selector
            :param dao.entities.IMetatag: metatag associated to the seletor
            :return: Value of the selector
            :rtype: str
        '''
        if type(widget) == Gtk.ComboBoxText:
            active = widget.get_active()
            tag = metatag.tags[active]
            value = tag.name
        else:
            value = widget.get_text().strip()
        return value

    def _getPatternBasicReplace(self):
        '''
            Common keywords.

            {_FileExtension}: source filename extension, None if it is a folder
        '''
        replacements = {}
        _, ext = os.path.splitext(self.ctrl.path)
        replacements["{_FileExtension}"] = ext
        return replacements

    def _getPatternMetatagsReplace(self):
        '''
            Get a dictionary with the replacements.
            e.g. {'Content' : 'documents'}

            :return: Dictionary with replacements
            :rtype: dict str -> srt
        '''
        replacements = {}
        for metatag, selector in self.metatag_selectors.items():
            value = self._getSelectorValue(metatag, selector)
            replace_key = "{%s}" % metatag.name
            replacements[replace_key] = value
        return replacements

    def _getPatternCustomEntriesReplace(self):
        replacements = {}
        for entry_name, selector in self.custom_selectors.items():
            value = self._getSelectorValue(None, selector)
            replace_key = "{%s}" % entry_name
            replacements[replace_key] = value
        return replacements

    def _getPatternAdvancedReplace(self, replacements):
        '''
            Get a dictionary with advanced replacements.
            These keys can be configured in the config folder/modules/moverKeys.py
            class.

            :param dict replacements: Metatag replacements are returned by _getPatternMetatagsReplace
            :return: Custom replacements
            :rtype: dict str -> str
        '''
        if self.ctrl.custom_target_keys is None or \
           self.ctrl.custom_target_keys_evaluator is None:
            return {}
        advanced = {}
        target_name = self.getDestinationName()
        self.log.info("Target name: %s" % target_name)
        for key in self.ctrl.custom_target_keys:
            replace_key = '{' + key + '}'
            value = self.ctrl.custom_target_keys_evaluator(
                key, self.ctrl.path, target_name, replacements)
            advanced[replace_key] = value
        return advanced

    def _evaluateTargetFolderPattern(self):
        '''
            Evaluate the target folder pattern using
            the values in the custom selectors.

            :return: Evaluated target folder
            :rtype: str
        '''
        target_folder = self._evaluatePattern(self.ctrl.target_folder_pattern)
        if target_folder.startswith('/'):
            target_folder = target_folder[1:]
        if not target_folder.endswith('/'):
            target_folder = target_folder + '/'
        return target_folder

    def _evaluateTargetNamePattern(self):
        return self._evaluatePattern(self.ctrl.target_name_pattern)

    def _evaluatePattern(self, pattern):
        if pattern is None:
            return '/'
        replacements = self._getPatternBasicReplace()
        replacements.update(self._getPatternMetatagsReplace())
        replacements.update(self._getPatternCustomEntriesReplace())
        advanced_replacements = self._getPatternAdvancedReplace(replacements)
        self.log.debug("Advanced replacements %s" % str(advanced_replacements))
        for key, value in replacements.items():
            pattern = pattern.replace(key, value)
        for key, value in advanced_replacements.items():
            pattern = pattern.replace(key, value)
        # Replace replicated /
        pattern = pattern.replace('//', '/')
        # Remove special characters
        pattern = pattern.replace('?',
                                  '').replace('!',
                                              '').replace('*',
                                                          '').replace(':', '')
        return pattern

    def _setDefaultTargetName(self, name):
        if self.ctrl.default_values is not None:
            if DEFAULT_TARGET_NAME in self.ctrl.default_values:
                name = self.ctrl.default_values[DEFAULT_TARGET_NAME]
        self.setDestinationName(name)
        self.onTargetPathChanged()

    def _getFileTags(self):
        '''
            Get the tags to be applied to the file.

            :return: list of tuples (tag_name, metatag)
            :rtype: list of (str, dao.entities.Common.IMetatag)
        '''
        tags = []
        for metatag, selector in self.metatag_selectors.items():
            name = self._getSelectorValue(metatag, selector)
            if len(name) > 0:
                tags.append((name, metatag))
        # TODO: Get suggested tags values
        return tags

    def _setAddEnabled(self, enable):
        self.log.debug("Enable button: %r" % enable)
        btn = self.builder.get_object('AddFileButton')
        btn.set_sensitive(enable)

    # Error message
    def _showErrorMessage(self, msg):
        label = self.builder.get_object('ErrorMessage')
        label.set_text(msg)

    def _hideErrorMessage(self):
        label = self.builder.get_object('ErrorMessage')
        label.set_text('')

    # Custom signals
    def onSelectorChanged(self, widget, metatag):
        self.log.debug("Selector for %s changed" % metatag.name)
        value = self._getSelectorValue(metatag, widget)
        self.log.debug("New value: %s" % value)
        self.onSelectorValueChaged()

    def onCustomSelectorChanged(self, widget, name):
        self.log.debug("Selector for %s changed" % name)
        self.onSelectorValueChaged()

    def onSelectorValueChaged(self):
        target_folder = self._evaluateTargetFolderPattern()
        self.setDestinationFolder(target_folder)
        if self.ctrl.target_name_pattern is not None:
            target_name = self._evaluateTargetNamePattern()
            self.setDestinationName(target_name)

    # Public methods
    def onPathChange(self):
        # Set basename
        basename = os.path.basename(self.ctrl.path)
        label = self.builder.get_object('SourceName')
        label.set_text('Source: %s' % basename)
        # Set default target name
        self._setDefaultTargetName(basename)
        # Re evaluate pattern
        self.onSelectorValueChaged()

    def setDestinationFolder(self, folder):
        self.log.debug("Set destination folder: %s" % folder)
        label = self.builder.get_object('DestinationLabel')
        label.set_text(folder)

    def getDestinationFolder(self):
        label = self.builder.get_object('DestinationLabel')
        return label.get_text()

    def getDestinationName(self):
        entry = self.builder.get_object('DestinationName')
        return entry.get_text()

    def setDestinationName(self, name):
        entry = self.builder.get_object('DestinationName')
        entry.set_text(name)

    # SIGNALS
    def onAdd(self, widget):
        tags = self._getFileTags()
        folder = self.getDestinationFolder()
        name = self.getDestinationName()
        target = self.ctrl.moveFileTo(folder, name)
        self.ctrl.addFile(target, tags)
        self.ctrl.stop()

    def onCancel(self, widget):
        self.ctrl.stop()

    def onUpdateFilename(self, widget):
        self.onSelectorValueChaged()
        self.onTargetPathChanged()

    def onTargetPathChanged(self):
        folder = self.getDestinationFolder()
        name = self.getDestinationName()
        path = self.ctrl.getTargetFullPath(folder, name)
        self.log.debug("New path: %s" % path)
        if not os.path.exists(path):
            self._setAddEnabled(True)
            self._hideErrorMessage()
        else:
            self._setAddEnabled(False)
            self._showErrorMessage("Target path already exists")
Esempio n. 5
0
class TaggerUI(BaseInterface):

    log = createLogger(__name__)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.window = None

    def _build(self):
        '''
            Build the interface. Should be called only once.
        '''
        self.log.info("Building...")
        self.builder = Gtk.Builder()
        ui_file = os.path.join(GLADE_FOLDER, 'Tagger.glade')
        self.builder.add_from_file(ui_file)
        # Create main window
        self.window = self.builder.get_object("TagFile")
        self.window.resize(400, 600)
        # Set window title
        fname = os.path.basename(self.ctrl.file.name)
        self.window.set_title("Tag: " + fname)
        # Register main window
        app = self.ctrl.services.getApplication()
        self.window.set_icon_from_file(app.icon_path)
        app.add_window(self.window)
        # Setup ui variables
        self.tags_store = Gtk.ListStore(int, str, int)
        self.metatag_tagstore = {}
        self.metatag_view = {}
        self.metatag_selected = None
        self.tag_selected = None
        # Build interface
        self._buildAutocompletionBox()
        # Register the create events
        self.ctrl.onUpdateMetatags(self.updateMetatagList)
        self.ctrl.onUpdateTags(self.updateTagsList)
        self.ctrl.onUpdateTags(self.updateTagsGrids)
        self.ctrl.onUpdateFileTag(self.onFileTagChanged)
        # Add a signal handler
        self.builder.connect_signals(self)

    def show(self):
        '''
            Show the main window.
        '''
        if self.window is None:
            self._build()
        self.window.show()

    def _buildAutocompletionBox(self):
        '''
            Set the autocompletion behaviour.
        '''
        completion = Gtk.EntryCompletion.new()
        # Set model
        completion.set_model(self.tags_store)
        completion.set_text_column(1)
        # Settings
        completion.set_inline_completion(False)
        completion.set_popup_completion(True)
        completion.set_popup_set_width(True)
        completion.set_popup_single_match(True)
        completion.set_inline_selection(True)
        # Connect
        completion.connect("match-selected", self.onTagCompletionSelected)
        # Add to entry
        sentry = self.builder.get_object("TFSearchEntry")
        sentry.set_completion(completion)

    def _buildTagsGridFor(self, metatag):
        '''
            Build the tags grid for the given metatag

            :param dao.Common.IMetatag metatag
            :return: List of tags associated
            :rtype: Gtk.ListStore
        '''
        self.metatag_tagstore[metatag.id] = Gtk.ListStore(int, str, bool)
        # Treeview
        treeview = Gtk.TreeView(model=self.metatag_tagstore[metatag.id])
        treeview.set_hexpand(True)
        # Cell renderer
        renderer_toggle = Gtk.CellRendererToggle()
        column_toggle = Gtk.TreeViewColumn("", renderer_toggle, active=2)
        renderer_toggle.connect("toggled", self.onTagToggled,
                                self.metatag_tagstore[metatag.id])
        treeview.append_column(column_toggle)
        renderer_text = Gtk.CellRendererText()
        column_text = Gtk.TreeViewColumn("Tag", renderer_text, text=1)
        treeview.append_column(column_text)
        treeview.set_search_column(1)
        # Add scrolled window
        scrolled_view = Gtk.ScrolledWindow()
        scrolled_view.set_policy(Gtk.PolicyType.NEVER,
                                 Gtk.PolicyType.AUTOMATIC)
        scrolled_view.add(treeview)
        scrolled_view.set_vexpand(True)
        self.metatag_view[metatag.id] = scrolled_view
        # Add to the main window
        all_tags_grid = self.builder.get_object("TFTagsBox")
        all_tags_grid.add(scrolled_view)
        return self.metatag_tagstore[metatag.id]

    @inhibitSignals
    def updateMetatagList(self):
        '''
            Update the metatag selectors.
        '''
        # Grid selector
        selector = self.builder.get_object("TFCategorySelector")
        selector.remove_all()
        for metatag in self.ctrl.metatags:
            selector.append_text(metatag.name)
        # Add tag selector
        add_selector = self.builder.get_object("TFAddTagCategory")
        add_selector.remove_all()
        for metatag in self.ctrl.metatags:
            add_selector.append_text(metatag.name)
        # Tags grids
        for metatag in self.ctrl.metatags:
            if not metatag.id in self.metatag_tagstore:
                self._buildTagsGridFor(metatag)
        if len(self.ctrl.metatags) > 0:
            selector.set_active(0)
            add_selector.set_active(0)
            self._setMetatag(self.ctrl.metatags[0])

    def updateTagsList(self):
        '''
            Update the tags list.
        '''
        self.tags_store.clear()
        sorted_tags = sorted(self.ctrl.tags, key=lambda tag: tag.name.lower())
        for tag in sorted_tags:
            if self.ctrl.isTagInAutocomplete(tag):
                self.tags_store.append([tag.id, tag.name, tag.metatag.id])

    def updateTagsGrids(self):
        '''
            Update the tags grids.
        '''
        # Clear all
        for metatag in self.ctrl.metatags:
            if metatag.id in self.metatag_tagstore:
                self.metatag_tagstore[metatag.id].clear()
        # Populate
        for tag in self.ctrl.tags:
            metatag = tag.metatag
            if metatag.id in self.metatag_tagstore:
                self.metatag_tagstore[metatag.id].append(
                    [tag.id, tag.name,
                     self.ctrl.fileHasTag(tag.id)])

    def _setMetatag(self, metatag):
        '''
            Set the metatag in the selector.
        '''
        # Hide old
        if self.metatag_selected is not None:
            self.metatag_view[self.metatag_selected.id].hide()
        # Set and show new
        self.metatag_selected = metatag
        self.metatag_view[self.metatag_selected.id].show_all()

    def _setTagToFile(self, tag_id, add):
        '''
            Toggle a tag.

            :param int tag_id: tag id
            :param bool add: True if I should add the tag, False otherwise
        '''
        if add:
            self.ctrl.addTagToFile(tag_id)
        else:
            self.ctrl.removeTagFromFile(tag_id)

    def onFileTagChanged(self, tag=None, added=True):
        '''
            When a tag has been added or removed from a file
        '''
        if tag is None:
            return
        # Edit all the list_store
        if tag.metatag.id in self.metatag_tagstore:
            store = self.metatag_tagstore[tag.metatag.id]
            for i in range(len(store)):
                tag_id, _, _ = store[i]
                if tag_id == tag.id:
                    store[i][-1] = added
                    break
        # Edit the tag_selected button if present
        if self.tag_selected == tag.id:
            btn = self.builder.get_object('TFSearchTagValue')
            btn.set_active(added)

    def onCreateMetatag(self, widget):
        entry = self.builder.get_object("TFAddCategoryName")
        name = entry.get_text().strip()
        if len(name) > 0:
            self.ctrl.createMetatag(name)

    def onCreateTag(self, widget):
        entry = self.builder.get_object("TFAddTagName")
        name = entry.get_text().strip()
        if len(name) == 0:
            return True
        m_selector = self.builder.get_object("TFAddTagCategory")
        metatag = self.ctrl.metatags[m_selector.get_active()]
        tag = self.ctrl.createTag(name, metatag)
        self.ctrl.addTagToFile(tag.id)

    # SIGNALS
    def onTagCompletionSelected(self, widget, model, path):
        '''
            On tag selected from autocompletion.
        '''
        tag_id, tag_name, metatag_id = model[path]
        self.tag_selected = tag_id
        # Show name
        label = self.builder.get_object("TFSearchTagName")
        label.set_text(tag_name)
        label.show()
        # Clear entry
        entry = self.builder.get_object("TFSearchEntry")
        entry.set_text("")
        # Set/Unset
        btn = self.builder.get_object('TFSearchTagValue')
        btn.set_active(True)
        btn.show()
        self._setTagToFile(self.tag_selected, True)
        # Return True otherwise the match-selected default behaviour
        # overwrites the text inside the entry
        # see: https://developer.gnome.org/gtk3/stable/GtkEntryCompletion.html#GtkEntryCompletion-match-selected
        return True

    @withInhibit
    def onMetatagChange(self, widget):
        '''
            On metagag changed event.
        '''
        index = widget.get_active()
        self._setMetatag(self.ctrl.metatags[index])

    def onTagToggled(self, widget, path=None, model=None):
        '''
            On tag toggled (from the autocompletion box
            or tag grid).
        '''
        if model is None:
            tag = self.tag_selected
            add = widget.get_active()
        else:
            tag = model[path][0]
            add = not model[path][2]
        if tag is None:
            return
        self._setTagToFile(tag, add)
        return True

    def onCloseClicked(self, widget):
        self.log.info("Stop the controller")
        self.ctrl.stop()

    def onDestroy(self, widget):
        self.log.info("Stop the controller")
        self.ctrl.stop(False)
Esempio n. 6
0
class MoverCtrl(BaseController):

    log = createLogger(__name__)

    def __init__(self, services, path):
        super().__init__(services)
        self.path = path
        self.loadConfiguration()
        self.ui = MoverUI(self)

    def loadConfiguration(self):
        self.metatags = {}
        metatags = ConfigManager.UI.getMoverMetatags()
        if metatags is not None:
            for name, selector in metatags.items():
                metatag = metatagsDao.getByName(name)
                self.metatags[metatag] = selector
        self.custom_entries = ConfigManager.UI.getMoverCustomEntries()
        self.target_folder_pattern = ConfigManager.UI.getMoverTargetFolder()
        self.target_name_pattern = ConfigManager.UI.getMoverTargetName()
        self.custom_target_keys = ConfigManager.UI.getMoverCustomPatternKeys()
        if self.custom_target_keys is not None:
            self.custom_target_keys_evaluator = self._loadCustomKeysEvaluator()
        self.default_values = self._getDefaultValues()

    def _loadCustomKeysEvaluator(self):
        module_path = ConfigManager.UI.getMoverCustomPatternKeysEvaluator()
        self.log.debug("Try to load %s" % module_path)
        custom = loadModuleFromPath("custom.keys", module_path)
        if custom is None:
            return None
        else:
            return custom.evaluate

    def _getDefaultValues(self):
        '''
            Get the default target name for the given path
            and some automatic tag.
        '''
        module_path = ConfigManager.UI.getMoverDefaultValues()
        self.log.debug("Try to load %s" % module_path)
        if not os.path.exists(module_path):
            return None
        custom = loadModuleFromPath("custom.values", module_path)
        if custom is None:
            return None
        else:
            return custom.values(self.path)

    def setupUpdateEvents(self):
        '''
            Initialize the lists of the functions
            to call on update event.
        '''
        self.on_update = {}
        self.on_update[UPDATE_PATH] = []

    @ensureLoading
    def start(self):
        self.ui.show()

    def stop(self, close_ui=True):
        self.log.info("Stopping controller")
        if close_ui:
            self.log.info("Close ui")
            self.ui.close()
            return
        self.log.info("Ui closed, cleanup if necessary")

    def getTargetFullPath(self, folder, fname):
        '''
            Get the full path for a file saved with the given name in
            a folder in the root.

            :param str folder: target folder
            :param str fname: target name
            :return: target path
            :rtype: str
        '''
        target_base = os.path.join(folder, fname)
        while target_base.startswith('/'):
            target_base = target_base[1:]
        return os.path.join(ConfigManager.getRoot(), target_base)

    def moveFileTo(self, folder, fname):
        '''
            Move the instance file to the given folder with
            the given name.

            :param str folder: target folder
            :param str fname: target name
            :return: target path
            :rtype: str
        '''
        target = self.getTargetFullPath(folder, fname)
        self.log.info("Moving file from %s to %s" % (self.path, target))
        folder = os.path.dirname(target)
        if not os.path.isdir(folder):
            self.log.debug("Create folder %s" % folder)
            os.makedirs(folder)
        shutil.move(self.path, target)
        return target

    def addFile(self, path, tags):
        '''
            Open the tagger for a file.

            :param str path: Path of the file
            :param list of str: Tags names to add to the file
            :return: File added
            :rtype: dao.entities.Common.IFile
        '''
        self.log.info("Add file %s" % path)
        file = addFile(path)
        self.log.info("Added file #%d, name: %s, mime: %s" %
                      (file.id, file.name, file.mime))
        # Apply tags
        session = tagsDao.openSession()
        filesDao.setSession(session)
        for tag_info in tags:
            name, metatag = tag_info
            self.log.info("Add tag %s" % name)
            tag = tagsDao.getByName(name)
            if tag is None:
                self.log.info("Create tag %s [%s]" % (name, metatag.name))
                tag = tagsDao.insert(name, metatag)
            file = filesDao.addTag(file, tag)
        tagsDao.closeSession(commit=True)
        filesDao.closeSession(commit=True)
        # Open file
        if ConfigManager.UI.getMoverOpenOnTag():
            folder = os.path.join(file.relpath, file.name)
            openFile(folder)
        self.services.getApplication().openTagger(file)
        return file

    # Update listeners
    def onUpdatePath(self, func):
        self.onUpdate(UPDATE_PATH, func)
Esempio n. 7
0
class BrowserCtrl(BaseController):

    log = createLogger(__name__)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.log.debug("Initialize")
        self.ui = BrowserUI(self)
        self.log.debug("Done")

    def setupUpdateEvents(self):
        super().setupUpdateEvents()
        self.on_update[UPDATE_METATAGS] = []
        self.on_update[UPDATE_TAGS] = []
        self.on_update[UPDATE_FILES] = []
        self.on_update[UPDATE_USED_TAGS] = []
        self.on_update[UPDATE_AVAILABLE_METATAGS] = []
        self.on_update[UPDATE_AVAILABLE_TAGS] = []

    @ensureLoading
    def start(self):
        self.ui.show()

    def load(self):
        self.metatags = metatagsDao.getAll()
        self.tags = tagsDao.getAll()
        self.used_tags = []
        self.name_filter = None
        self.files = self._getFiles(self.used_tags)
        self.available_tags = self._getAvailableTags(self.files)
        self.available_metatags = self._getAvailableMetatags(
            self.available_tags)

    def _getRandomFiles(self):
        '''
            Get a list of random files.

            :return: A list of random files
            :rtype: list of dao.entities.Common.IFile
        '''
        return filesDao.getRandom(START_RANDOM_FILES)

    def _getAvailableTags(self, files):
        '''
            Get the tags useful to filter on the
            given files.

            :param list of IFile files: Files
            :return: list of tags
            :rtype: list of dao.entities.Common.ITag
        '''
        if len(self.used_tags) == 0 and not self.name_filter:
            # Get all the tags with at least one file tagged
            return tagsDao.getAllWithOneFileTagged()
        # Get the common tags
        used_ids = list(map(lambda t: t.id, self.used_tags))
        if self.name_filter is not None:
            name_like = "%" + self.name_filter + "%"
            common_tags = tagsDao.getRelatedTags(used_ids, name_like=name_like)
        else:
            common_tags = tagsDao.getRelatedTags(used_ids)
        # Remove the used tags
        available_tags = []
        for tag in common_tags:
            if tag.id not in used_ids:
                available_tags.append(tag)
        return available_tags

    def _getAvailableMetatags(self, tags):
        metatags = {}
        for tag in tags:
            if not tag.metatag.id in metatags:
                metatags[tag.metatag.id] = tag.metatag
        return sorted(list(metatags.values()), key=lambda m: m.name.lower())

    def _getFiles(self, tags, name=None):
        if len(tags) == 0 and name is None:
            if ConfigManager.UI.getRandomize():
                return self._getRandomFiles()
            else:
                return []
        return filesDao.getByNameAndTags(name=name, tags=tags)

    def addTag(self, tag):
        self.used_tags.append(tag)
        self._searchFiles()

    def removeTag(self, tag):
        self.used_tags.remove(tag)
        self._searchFiles()

    def addNameFilter(self, name):
        if name is None:
            self.name_filter = None
        else:
            self.name_filter = '%'.join(name.strip().split())
            if self.name_filter == '':
                self.name_filter = None
            else:
                self.name_filter = '%' + self.name_filter + '%'
        self._searchFiles()

    def _searchFiles(self):
        self.files = self._getFiles(self.used_tags, self.name_filter)
        self.available_tags = self._getAvailableTags(self.files)
        self.available_metatags = self._getAvailableMetatags(
            self.available_tags)
        # Trigger
        self.trigger(UPDATE_USED_TAGS)
        self.trigger(UPDATE_FILES)
        self.trigger(UPDATE_AVAILABLE_METATAGS)
        self.trigger(UPDATE_AVAILABLE_TAGS)

    def openTagger(self, file):
        tagger_ctrl = self.services.getApplication().openTagger(file)

    def removeFile(self, file):
        '''
            Remove a file from database and filesystem.

            :param dao.entities.IFileLazy file: File to remove
        '''
        # Remove the file from the system
        self.log.info("Remove file: %s" % file.name)
        System.removeFile(file)
        self.log.debug("Remove from controller files list")
        # Remove file from the list
        for cfile in self.files:
            if cfile.id == file.id:
                self.files.remove(cfile)
                break
        # Update tags and metatags
        self.log.debug("Update available tags and metatags")
        self.available_tags = self._getAvailableTags(self.files)
        self.available_metatags = self._getAvailableMetatags(
            self.available_tags)
        self.trigger(UPDATE_FILES)
        self.trigger(UPDATE_AVAILABLE_METATAGS)
        self.trigger(UPDATE_AVAILABLE_TAGS)

    # Update listeners
    def onUpdateMetags(self, func):
        self.onUpdate(UPDATE_METATAGS, func)

    def onUpdateTags(self, func):
        self.onUpdate(UPDATE_TAGS, func)

    def onUpdateFiles(self, func):
        self.onUpdate(UPDATE_FILES, func)

    def onUpdateUsedTags(self, func):
        self.onUpdate(UPDATE_USED_TAGS, func)

    def onUpdateAvailableMetatags(self, func):
        self.onUpdate(UPDATE_AVAILABLE_METATAGS, func)

    def onUpdateAvailableTags(self, func):
        self.onUpdate(UPDATE_AVAILABLE_TAGS, func)
Esempio n. 8
0
class TaggerCtrl(BaseController):

    log = createLogger(__name__)

    def __init__(self, services, file):
        super().__init__(services)
        self.loadConfiguration()
        self.ui = TaggerUI(self)
        self.file = file
        # Ensure tags are defined
        if not hasattr(self.file, 'tags'):
            self.file = filesDao.getById(self.file.id)
        self.log.debug("Initialized")

    def loadConfiguration(self):
        self.autocomplete_metatags = ConfigManager.UI.getTaggerAutocompleteMetatags(
        )

    def isTagInAutocomplete(self, tag):
        if self.autocomplete_metatags is None:
            return True
        if tag.metatag.name in self.autocomplete_metatags:
            return True
        else:
            return False

    def setupUpdateEvents(self):
        super().setupUpdateEvents()
        self.on_update[UPDATE_METATAGS] = []
        self.on_update[UPDATE_TAGS] = []
        self.on_update[UPDATE_FILE_TAG] = []

    @ensureLoading
    def start(self):
        self.ui.show()

    def stop(self, close_ui=True):
        self.log.info("Stopping controller")
        if close_ui:
            self.log.info("Close ui")
            self.ui.close()
            return
        self.log.info("Ui closed, cleanup if necessary")
        # TODO

    def load(self):
        self.metatags = metatagsDao.getAll()
        self.tags = tagsDao.getAll()

    def fileHasTag(self, tag_id):
        '''
            Check if the file has the given tag.

            :param int tag_id: Tag id
            :return: True if the file has this tag, False otherwise
            :rtype: bool
        '''
        in_tags = False
        for tag in self.file.tags:
            if tag.id == tag_id:
                in_tags = True
                break
        return in_tags

    def addTagToFile(self, tag_id):
        if self.fileHasTag(tag_id):
            return
        tag = tagsDao.getById(tag_id)
        self.file = filesDao.addTag(self.file, tag)
        self.trigger(UPDATE_FILE_TAG, (tag, True))

    def removeTagFromFile(self, tag_id):
        if not self.fileHasTag(tag_id):
            return
        tag = tagsDao.getById(tag_id)
        self.file = filesDao.removeTag(self.file, tag)
        self.trigger(UPDATE_FILE_TAG, (tag, False))

    def createMetatag(self, name):
        metatag = metatagsDao.insert(name)
        self.metatags = metatagsDao.getAll()
        self.trigger(UPDATE_METATAGS)
        return metatag

    def createTag(self, name, metatag):
        tag = tagsDao.insert(name, metatag)
        self.tags = tagsDao.getAll()
        self.trigger(UPDATE_TAGS)
        return tag

    # Update listeners
    def onUpdateMetatags(self, func):
        self.onUpdate(UPDATE_METATAGS, func)

    def onUpdateTags(self, func):
        self.onUpdate(UPDATE_TAGS, func)

    def onUpdateFileTag(self, func):
        self.onUpdate(UPDATE_FILE_TAG, func)
Esempio n. 9
0
class BrowserUI(BaseInterface):

    log = createLogger(__name__)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.window = None
        self.log.debug("Done")

    def _build(self):
        '''
            Build the interface. Should be called only once.
        '''
        self.log.debug("Building...")
        # Load the builder
        self.builder = self._loadBuilder()
        # Set the logo
        self._setLogo()
        # Register main window
        self._loadMainWindow()
        # Update the UI
        self._initializeUIVariables()
        # Setup files view
        self._setupFilesView()
        # Resgister events on the controller
        self._registerEvents()
        # Add a signal handler
        self.builder.connect_signals(self)
        self.log.debug("Done")

    def _loadBuilder(self):
        '''
            Load the gtk builder.

            :return: The UI builder
            :rtype: Gtk.Builder
        '''
        builder = Gtk.Builder()
        ui_file = os.path.join(GLADE_FOLDER, 'Browser.glade')
        builder.add_from_file(ui_file)
        return builder

    def _setLogo(self):
        '''
            Set the logo.
        '''
        logo = self.builder.get_object("Logo")
        logo_file = os.path.join(ConfigManager.getOverridesFolder(),
                                 "logo.png")
        if not os.path.exists(logo_file):
            logo_file = os.path.join(ICONS_FOLDER, "logo.png")
        logo.set_from_pixbuf(Pixbuf.new_from_file(logo_file))

    def _loadMainWindow(self):
        '''
            Load the main window, set the default size
            and register on the application.
        '''
        self.window = self.builder.get_object("Main")
        self.window.resize(1300, 800)
        # Register main window
        app = self.ctrl.services.getApplication()
        self.window.set_icon_from_file(app.icon_path)
        app.add_window(self.window)

    def _initializeUIVariables(self):
        '''
            Initialize the variables used in the UI.
        '''
        self.metatag_selected = self._getDefaultMetatag()
        self.selected_tag_name = ''
        self.files_limit = FILES_LIMIT

    def _setupFilesView(self):
        '''
            Create setup the files view and create its right-click menu.
        '''
        # Set menu
        self.files_view_menu = FilesViewMenu.new(self.openFolder,
                                                 self.openTagger,
                                                 self.removeFile)
        # Set files store
        self.files_store = Gtk.ListStore(int, str, Pixbuf, str)
        # Setup files view
        files_view = self.builder.get_object('FilesView')
        files_view.set_text_column(1)
        files_view.set_pixbuf_column(2)
        files_view.set_model(self.files_store)

    def _registerEvents(self):
        '''
            Register the event listeners on the controller.
        '''
        # Register the create event
        self.ctrl.onUpdateTags(self.recreateTagsList)
        self.ctrl.onUpdateAvailableMetatags(self.updateMetatagSelector)
        # Register the update events
        self.ctrl.onUpdateAvailableTags(self.updateTagsList)
        self.ctrl.onUpdateFiles(self.updateFilesList)
        self.ctrl.onUpdateUsedTags(self.updateUsedTags)

    def show(self):
        '''
            Show the main window.
        '''
        if self.window is None:
            self._build()
        self.window.show()

    def recreateTagsList(self):
        self.tags_buttons = {}
        tags_list = self.builder.get_object("TagsList")
        # Clean the container
        self.cleanContainer(tags_list)
        # Add the tag buttons
        for tag in self.ctrl.tags:
            btn = self._createTagButton(tag, self.onTagSelected)
            tags_list.add(btn)
            self.tags_buttons[tag.id] = btn

    @inhibitSignals
    def updateMetatagSelector(self):
        '''
            Update the metatag selector.
        '''
        selector = self.builder.get_object("MetatagSelector")
        if len(self.ctrl.available_metatags) == 0:
            # TODO: better
            selector.hide()
            return
        if self.metatag_selected is None or not self._isCurrentMetatagAvailable(
        ):
            self.metatag_selected = self.ctrl.available_metatags[0]
        # Update the selector
        selector.remove_all()
        active_index = -1
        for index, metatag in enumerate(self.ctrl.available_metatags):
            selector.append_text(metatag.name)
            if metatag.name == self.metatag_selected.name:
                active_index = index
        # Set the active metatag
        if active_index > -1:
            selector.set_active(active_index)
        selector.show_all()

    def updateTagsList(self):
        '''
            Update the tags list.
        '''
        no_tags_label = self.builder.get_object("NoTagsAvailable")
        # Show/hide the no tags available label
        if len(self.ctrl.available_tags) == 0:
            no_tags_label.show()
        else:
            no_tags_label.hide()
        # Hide all the tags
        tags_list = self.builder.get_object("TagsList")
        for child in tags_list.get_children():
            child.hide()
        # Show available tags
        for tag in self.ctrl.available_tags:
            self.tags_buttons[tag.id].show()
        # Filter
        self.filterTagsList()

    def updateFilesList(self, append=False):
        if not append:
            self.files_store.clear()
        max_files = len(self.ctrl.files)
        max_index = min(len(self.files_store) + self.files_limit, max_files)
        for i in range(len(self.files_store), max_index):
            file = self.ctrl.files[i]
            pixbuf = self._getFilePixbuf(file)
            relpath = os.path.join(file.relpath, file.name)
            self.files_store.append([file.id, file.name, pixbuf, relpath])
        files_view = self.builder.get_object('FilesView')
        files_view.show_all()
        # Show/hide the load more files button
        btn = self.builder.get_object("LoadMoreFiles")
        if len(self.files_store) == max_files:
            btn.hide()
        else:
            btn.show()

    def updateUsedTags(self):
        '''
            Update the list of the used tags.
        '''
        tags_list = self.builder.get_object("UsedTagsList")
        self.cleanContainer(tags_list)
        for tag in self.ctrl.used_tags:
            btn = self._createTagButton(tag, self.onTagRemoved)
            tags_list.add(btn)
        tags_list.show_all()

    def filterTagsList(self):
        '''
            Hide the tags with metatag not selected or name filtered.
        '''
        for tag in self.ctrl.available_tags:
            btn = self.tags_buttons[tag.id]
            if tag.metatag.id == self.metatag_selected.id and \
               self.selected_tag_name in tag.name.lower():
                btn.show()
            else:
                btn.hide()

    def _createTagButton(self, tag, activate_function=None):
        '''
            Create the select tag button.
        '''
        btn = Gtk.LinkButton()
        btn.set_label(tag.name)
        btn.set_size_request(160, 0)
        lbl = btn.get_children()[0]
        lbl.set_line_wrap(True)
        lbl.set_justify(Gtk.Justification.CENTER)
        if activate_function is not None:
            btn.connect("activate-link", activate_function, tag)
        return btn

    def _getFilePixbuf(self, file):
        '''
            Get the pixbuf for a file. Try to use the thumbnail if possible,
            fallback to the mime icon.

            :param dao.entities.Common.IFile file: File
            :return: Pixbuf for the file
            :rtype: GdkPixbuf.Pixbuf
        '''
        icon_name = "thumbnails/%d/%d.png" % (ICON_SIZE, file.id)
        icon_path = os.path.join(ConfigManager.profile_folder, icon_name)
        pixbuf = None
        # Try to load the thumbnail
        if os.path.exists(icon_path):
            try:
                pixbuf = Pixbuf.new_from_file(icon_path)
            except Exception:
                pixbuf = None
        # Use the mime icon
        if pixbuf is None:
            pixbuf = self._getMimePixbuf(file.mime)
        return pixbuf

    def _getMimePixbuf(self, mime):
        '''
            Get the icon pixbuf representing a mime.

            :param str mime: Mime
            :return: Pixbuf for the mime
            :rtype: GdkPixbuf.Pixbuf
        '''
        # Get the icon name
        theme = Gtk.IconTheme.get_default()
        name = None
        for icon_name in self._generateGtkIconNames(mime):
            if theme.has_icon(icon_name):
                name = icon_name
                break
        # Load the pixbuf
        try:
            pixbuf = theme.load_icon(name, ICON_SIZE, 0)
        except Exception:
            pixbuf = None
        return pixbuf

    def _generateGtkIconNames(self, mime):
        '''
            Generate a list of possible icon names
            for a given mimetype.

            :param str mime: Mime
            :return: Generator of possible icon names
            :rtype: Generator of str
        '''
        # Specific mime
        yield mime
        alt_mime = mime.replace('/', '-')
        yield alt_mime
        yield 'gnome-mime-' + alt_mime
        # Generic mime
        gmime = mime.split('/')[0]
        yield gmime
        yield 'gnome-mime-' + gmime
        yield Gtk.STOCK_FILE

    def _isCurrentMetatagAvailable(self):
        '''
            Check if the current metatag is available.
        '''
        if self.metatag_selected is None:
            return False
        available = False
        for metatag in self.ctrl.available_metatags:
            if metatag.id == self.metatag_selected.id:
                available = True
                break
        return available

    def _getDefaultMetatag(self):
        name = ConfigManager.UI.getMetatagName()
        if name is None:
            return None
        default = None
        for metatag in self.ctrl.available_metatags:
            if metatag.name == name:
                default = metatag
                break
        return default

    def _getFileInStore(self, file_id):
        file = None
        for cfile in self.ctrl.files:
            if cfile.id == file_id:
                file = cfile
                break
        return file

    # Menu options
    def _getFilesViewSelected(self):
        '''
            Get the file currently selected in the files view.
            Return only if there is only one file selected.

            :return: The selected file if any, None otherwise
            :rtype: dao.entities.IFileLazy
        '''
        filesview = self.builder.get_object('FilesView')
        paths = filesview.get_selected_items()
        if len(paths) != 1:
            return None
        path = paths[0]
        file_id = self.files_store[path][0]
        file = self._getFileInStore(file_id)
        return file

    def openFolder(self, widget, data):
        file = self._getFilesViewSelected()
        if file is None:
            return
        folder = os.path.dirname(os.path.join(file.relpath, file.name))
        self.log.info("Opening folder: %s" % folder)
        openFile(folder)

    def openTagger(self, widget, data):
        file = self._getFilesViewSelected()
        if file is None:
            return
        self.log.info("Opening tagger for: %s" % file.name)
        self.ctrl.openTagger(file)

    def removeFile(self, widget, data):
        file = self._getFilesViewSelected()
        if file is None:
            return
        self.log.info("Confirm file removal: %s" % file.name)
        onConfirm = lambda: self.onRemoveFile(file)
        dialog = self.createConfirmationDialog(
            "Confirm deletion", "Do you want to delete %s ?" % file.name,
            onConfirm)

    def onRemoveFile(self, file):
        self.log.info("Delete file: %s" % file.name)
        self.ctrl.removeFile(file)

    @withInhibit
    def onMetatagChange(self, widget):
        '''
            On metagag changed event.
        '''
        index = widget.get_active()
        self.metatag_selected = self.ctrl.available_metatags[index]
        self.filterTagsList()

    def onTagSelected(self, widget, tag):
        self.ctrl.addTag(tag)
        return True

    def onTagRemoved(self, widget, tag):
        self.ctrl.removeTag(tag)
        return True

    def onFilterByFileName(self, widget):
        if ConfigManager.UI.getFastFilter():
            self._onFilterByFileName(widget)

    def onFilterByFileNameEnter(self, widget):
        self._onFilterByFileName(widget)

    def _onFilterByFileName(self, widget):
        name = widget.get_text()
        name = name.strip()
        if len(name) == 0:
            name = None
        self.ctrl.addNameFilter(name)

    def onFilterByTagName(self, widget):
        self.selected_tag_name = widget.get_text().lower()
        self.filterTagsList()

    def onLoadMoreFiles(self, *args, **kwargs):
        self.files_limit += FILES_LIMIT
        self.updateFilesList(append=True)

    def onFileClick(self, icon, treepath):
        findex = int(treepath.to_string())
        relpath = self.files_store[findex][-1]
        self.log.info("Opening file: %s" % relpath)
        openFile(relpath)

    def onButtonPress(self, widget, event):
        # event.button == 3 iff right-click
        if event.type != Gdk.EventType.BUTTON_PRESS or event.button != 3:
            return False
        coords = event.get_coords()
        ipath = widget.get_path_at_pos(coords[0], coords[1])
        if ipath is None:
            return False
        # Unselect other paths
        widget.unselect_all()
        # select the element
        widget.select_path(ipath)
        # Show right-click menu
        self.files_view_menu.popup(None, None, None, None, event.button,
                                   event.time)
        return True