#! /usr/bin/env python
# -*- coding: utf-8 -*-

"""
Module that contains library item widget implementation
"""

from __future__ import print_function, division, absolute_import

import os
import math
import shutil
import tempfile
import traceback

from datetime import datetime
from functools import partial

from Qt.QtCore import *
from Qt.QtWidgets import *
from Qt.QtGui import *

import tpDcc as tp
from tpDcc.core import consts as dcc_consts

from tpDcc.libs.python import decorators, timedate, fileio, path as path_utils, folder as folder_utils

from tpDcc.libs import qt
from tpDcc.libs.qt.core import image, qtutils
from tpDcc.libs.qt.widgets import messagebox
from tpDcc.libs.qt.widgets.library import consts, savewidget, loadwidget, exceptions, utils

if tp.is_maya():
    from tpDcc.dccs.maya.core import decorators as maya_decorators
    show_wait_cursor_decorator = maya_decorators.show_wait_cursor
else:
    show_wait_cursor_decorator = decorators.empty_decorator


class GlobalSignals(QObject, object):
    blendChanged = Signal(float)


class LibraryItem(QTreeWidgetItem, object):
    """
    Stores information to work on Library views
    """

    SortRole = consts.ITEM_DEFAULT_SORT_ROLE
    DataRole = consts.ITEM_DEFAULT_DATA_ROLE

    ThreadPool = QThreadPool()
    DefaultThumbnailPath = tp.ResourcesMgr().get('icons', 'thumbnail.png')

    MAX_ICON_SIZE = consts.ITEM_DEFAULT_MAX_ICON_SIZE
    DEFAULT_FONT_SIZE = consts.ITEM_DEFAULT_FONT_SIZE
    DEFAULT_PLAYHEAD_COLOR = consts.ITEM_DEFAULT_PLAYHEAD_COLOR

    DEFAULT_THUMBNAIL_COLUMN = consts.ITEM_DEFAULT_THUMBNAIL_COLUMN
    ENABLE_THUMBNAIL_THREAD = consts.ITEM_DEFAULT_ENABLE_THUMBNAIL_THREAD

    _globalSignals = GlobalSignals()
    blendChanged = _globalSignals.blendChanged

    EnableDelete = consts.ITEM_DEFAULT_ENABLE_DELETE
    EnableNestedItems = consts.ITEM_DEFAULT_ENABLE_NESTED_ITEMS

    DataType = None
    Extension = consts.ITEM_DEFAULT_EXTENSION
    Extensions = list()

    MenuName = consts.ITEM_DEFAULT_MENU_NAME
    MenuOrder = consts.ITEM_DEFAULT_MENU_ORDER
    MenuIconPath = consts.ITEM_DEFAULT_MENU_ICON_PATH

    RegisterOrder = 10
    TypeIconPath = ''
    DisplayInSidebar = False
    CreateWidgetClass = savewidget.SaveWidget
    PreviewWidgetClass = loadwidget.LoadWidget

    _libraryItemSignals = consts.LibraryItemSignals()
    saved = _libraryItemSignals.saved
    saving = _libraryItemSignals.saving
    loaded = _libraryItemSignals.loaded
    copied = _libraryItemSignals.copied
    renamed = _libraryItemSignals.renamed
    deleted = _libraryItemSignals.deleted

    def __init__(self, path='', library=None, library_window=None, *args):

        self._url = None
        self._path = None
        self._size = None
        self._rect = None
        self._text_column_order = list()

        self._data = dict()
        self._item_data = dict()

        self._icon = dict()
        self._icon_path = None
        self._thumbnail_icon = None
        self._fonts = dict()
        self._thread = None
        self._pixmap = dict()
        self._pixmap_rect = None
        self._pixmap_scaled = None
        self._image_sequence = None
        self._image_sequence_path = None

        self._mime_text = None
        self._drag_enabled = True

        self._under_mouse = False
        self._search_text = None
        self._info_widget = None

        self._group_item = None
        self._group_column = 0

        self._viewer = None
        self._stretch_to_widget = None

        self._blend_value = 0.0
        self._blend_prev_value = 0.0
        self._blend_position = None
        self._blending_enabled = False

        self._metadata = None
        self._type_pixmap = None
        self._modal = None
        self._library = None
        self._library_window = None

        super(LibraryItem, self).__init__(*args)

        self._worker = image.ImageWorker()
        self._worker.setAutoDelete(False)
        self._worker.signals.triggered.connect(self._on_thumbnail_from_image)
        self._worker_started = False

        if library_window:
            self.set_library_window(library_window)

        if library:
            self.set_library(library)

        if path:
            self.set_path(path)

    def __eq__(self, other):
        return id(other) == id(self)

    def __ne__(self, other):
        return id(other) != id(self)

    def __del__(self):
        """
        When the object is deleted we make sure the sequence is stopped
        """

        self.stop()

    """
    ##########################################################################################
    CLASS METHODS
    ##########################################################################################
    """

    @classmethod
    def create_action(cls, menu, library_window):
        """
        Returns the action to be displayed when the user clicks the "plus" icon
        :param menu: QMenu
        :param library_window: LibraryWindow
        :return: QAction
        """

        if cls.MenuName:
            action_icon = QIcon(cls.MenuIconPath)
            callback = partial(cls.show_create_widget, library_window)
            action = QAction(action_icon, cls.MenuName, menu)
            action.triggered.connect(callback)

            return action

    @classmethod
    def show_create_widget(cls, library_window):
        """
        Shows the create widget for creating a new item
        :param library_window: LibraryWindow
        """

        settings = library_window.settings()

        widget = cls.CreateWidgetClass(item=cls(), settings=settings, parent=library_window)
        widget.set_library_window(library_window)
        library_window.set_create_widget(widget)

    @classmethod
    def match(cls, path):
        """
        Returns whether the given path locations is supported by the item
        :param path: str
        :return: bool
        """

        for ext in cls.Extensions:
            if path.endswith(ext):
                return True

        return False

    @decorators.abstractmethod
    def context_menu(self, menu):
        """
        Returns the context menu for the item
        This function MUST be implemented in subclass to return a custom context menu for the item
        :return: QMenu
        """

        raise NotImplementedError('LibraryItem context_menu() not implemented!')

    """
    ##########################################################################################
    OVERRIDES
    ##########################################################################################
    """

    def sizeHint(self, column=0):
        """
        Returns the current size of the item
        :param column: int
        :return: QSize
        """

        if self.stretch_to_widget():
            if self._size:
                size = self._size
            else:
                size = self.viewer().icon_size()
            w = self.stretch_to_widget().width()
            h = size.height()
            return QSize(w - 20, h)

        if self._size:
            return self._size
        else:
            icon_size = self.viewer().icon_size()
            if self.is_text_visible():
                w = icon_size.width()
                h = icon_size.width() + self.text_height()
                icon_size = QSize(w, h)

            return icon_size

    def setHidden(self, value):
        """
        Overrides base QTreeWidgetItem.setHidden function
        Set the item hidden
        :param value: bool
        """

        super(LibraryItem, self).setHidden(value)
        row = self.treeWidget().index_from_item(self).row()
        self.viewer().list_view().setRowHidden(row, value)

    def backgroundColor(self):
        """
        Returns the background color for the item
        :return: QColor
        """

        return self.viewer().background_color()

    def icon(self, column):
        """
        Overrides base QTreeWidgetItem icon function
        Overrides icon to add support for thumbnail icon
        :param column: int
        :return: QIcon
        """

        ic = QTreeWidgetItem.icon(self, column)
        if not ic and column == self.DEFAULT_THUMBNAIL_COLUMN:
            ic = self.thumbnail_icon()

        return ic

    def setIcon(self, column, icon, color=None):
        """
        Overrides base QTreeWidgetItem setIcon function
        :param column: int or str
        :param icon: QIcon
        :param color: QColor or None
        """

        is_app_running = bool(QApplication.instance())
        if not is_app_running:
            return

        if isinstance(icon, (str, unicode)):
            if not os.path.exists(icon):
                color = color or QColor(255, 255, 255, 20)
                icon = tp.ResourcesMgr().icon('image', color=color)
            else:
                icon = QIcon(icon)
        if isinstance(column, (str, unicode)):
            self._icon[column] = icon
        else:
            self._pixmap[column] = None
            super(LibraryItem, self).setIcon(column, icon)

        self.update_icon()

    def setFont(self, column, font):
        """
        Overrides base QTreeWidgetItem setFont function
        Sets the font for the given column
        :param column: int
        :param font: QFont
        """

        self._fonts[column] = font

    def textAlignment(self, column):
        """
        Returns the text alinment for the label in the given column
        :param column: int
        :return: QAlignmentFlag
        """

        if self.viewer().is_icon_view():
            return Qt.AlignCenter
        else:
            return QTreeWidgetItem.textAlignment(self, column)

    """
    ##########################################################################################
    BASE
    ##########################################################################################
    """

    def id(self):
        """
        Returns the unique id for the item
        :return: str
        """

        return self.path()

    def name(self):
        """
        Returns text for the name column
        :return: str
        """

        return self.item_data().get('name')

    def set_name(self, text):
        """
        Sets the name that is shown under the icon and in the name column
        :param text: str
        """

        item_data = self.item_data()
        item_data['icon'] = text
        item_data['name'] = text

    def display_text(self, label):
        """
        Returns the sort dat afor the given column
        :param label: str
        :return: str
        """

        return str(self.item_data().get(label, ''))

    def sort_text(self, label):
        """
        Returns the sort data for the given column
        :param label: str
        :return: str
        """

        return str(self.item_data().get(label, ''))

    def dirname(self):
        """
        Returns item directory name
        :return: str
        """

        return os.path.dirname(self.path())

    def extension(self):
        """
        Returns item file extension
        :return: str
        """

        _, extension = os.path.splitext(self.path())
        return extension

    def exists(self):
        """
        Returns whether item exists in disk or not
        :return: bool
        """

        return os.path.exists(self.path())

    def mtime(self):
        """
        Returns mtime of the item file
        :return: str
        """

        return os.path.getmtime(self.path())

    def ctime(self):
        """
        Returns when the item was created
        :return: int
        """

        if not os.path.exists(self.path()):
            return None

        return int(os.path.getctime(self.path()))

    def move(self, target):
        """
        Moves the current item to the given destination
        :param target: str
        """

        self.rename(target)

    def rename(self, target, extension=None, rename_file=False):
        """
        Renames the current path to the give destination path
        :param target: str
        :param extension: bool or None
        :param rename_file: bool or None
        """

        library = self.library()
        if not library:
            qt.logger.error('Impossible to rename item because library is not defined!')
            return

        source = self.path()
        self.library().rename_item(self, target, extension)

        self.renamed.emit(self, source, target)

    def copy(self, target):
        """
        Makes a copy/duplicate the current item to the given destination
        :param target: str
        """

        source = self.path()
        target = utils.copy_path(source, target)
        if self.library():
            self.library().copy_path(source, target)
        self.copied.emit(self, source, target)
        if self.library_window():
            self.library_window().refresh()

    def delete(self):
        """
        Deletes the item from disk and the library model
        """

        utils.remove_path(self.path())
        if self.library():
            self.library().remove_path(self.path())
        self.deleted.emit(self)

    def stretch_to_widget(self):
        """
        Returns the stretch to widget widget
        :return: QWidget
        """

        return self._stretch_to_widget

    def set_stretch_to_widget(self, widget):
        """
        Sets the width of the item to the width of the given widget
        :param widget: QWidget
        """

        self._stretch_to_widget = widget

    def set_size(self, size):
        """
        Sets the size for the item
        :param size: QSize
        """

        self._size = size

    def pixmap(self, column):
        """
        Returns the pixmap for the given column
        :param column: int
        :return: QPixmap
        """

        if not self._pixmap.get(column):
            icon = self.icon(column)
            if icon:
                size = QSize(self.MAX_ICON_SIZE, self.MAX_ICON_SIZE)
                icon_size = icon.actualSize(size)
                self._pixmap[column] = icon.pixmap(icon_size)

        return self._pixmap.get(column)

    def set_pixmap(self, column, pixmap):
        """
        Sets the pixmap to be displayed in the given column
        :param column: int
        :param pixmap: QPixmap
        """

        self._pixmap[column] = pixmap

    def take_from_tree(self):
        """
        Takes this item from the tree
        """

        tree = self.treeWidget()
        parent = self.parent()
        if parent:
            parent.takeChild(parent.indexOfChild(self))
        else:
            tree.takeTopLevelItem(tree.indexOfTopLevelItem(self))

    def group_item(self):
        """
        Returns current group item
        :return: LibraryGroupItem
        """

        return self._group_item

    def set_group_item(self, group_item):
        """
        Sets current group item
        :param group_item: LibraryGroupItem
        """

        self._group_item = group_item

    def info(self):
        """
        Returns the info to display to the user
        :return: list(dict)
        """

        return list()

    def show_in_folder(self):
        """
        Opens folder in OS folder explorer where file is located
        """

        path = self.path()
        if not os.path.isdir(path):
            return

        folder_utils.open_folder(path)

    """
    ##########################################################################################
    MOUSE/KEYBOARD
    ##########################################################################################
    """

    def under_mouse(self):
        """
        Returns whether the items is under the mouse cursor or not
        :return: bool
        """

        return self._under_mouse

    def context_menu(self, menu):
        """
        Returns the context menu for the item
        This function must be implemented in a subclass to return a custom context menu for the item
        :param menu: QMenu
        """

        pass

    def drop_event(self, event):
        """
        Reimplement in subclass to receive drop items for the item
        :param event: QDropEvent
        """

        pass

    def mouse_enter_event(self, event):
        """
        Reimplement in subclass to receive mouse enter events for the item
        :param event: QMouseEvent
        """

        self._under_mouse = True
        self.play()

    def mouse_leave_event(self, event):
        """
        Reimplement in subclass to receive mouse leave events for the item
        :param event: QMouseEvent
        """

        self._under_mouse = False
        self.stop()

    def mouse_move_event(self, event):
        """
        Reimplement in subclass to receive mouse move events for the item
        :param event: QMouseEvent
        """

        self.blending_event(event)
        self.image_sequence_event(event)

    def mouse_press_event(self, event):
        """
        Reimplement in subclass to receive mouse press events for the item
        :param event: QMouseEvent
        """

        if event.button() == Qt.MidButton:
            self._blend_position = event.pos()

    def mouse_release_event(self, event):
        """
        Reimplement in subclass to receive mouse release events for the item
        :param event: QMouseEvent
        """

        if self.is_blending():
            self._blend_position = None
            self._blend_prev_value = self.blend_value()

    def key_press_event(self, event):
        """
        Reimplement in subclass to receive key press events for the item
        :param event: QKeyEvent
        """

        pass

    def key_release_event(self, event):
        """
        Reimplement in subclass to receive key release events for the item
        :param event: QKeyEvent
        """

        pass

    def clicked(self):
        """
        Triggered when an item is clicked
        """

        pass

    def double_clicked(self):
        """
        Triggered when an item is double clicked
        """

        self.load_from_current_options()

    def selection_changed(self):
        """
        Triggered when an item has been either selected or deselected
        """

        self.reset_blending()

    """
    ##########################################################################################
    LIBRARY
    ##########################################################################################
    """

    def library_window(self):
        """
        Returns the library widget containing the item
        :return: LibraryWindow
        """

        return self._library_window

    def set_library_window(self, library_window):
        """
        Sets the library widget containing the item
        :param library_window: LibraryWindow
        """

        self._library_window = library_window

    def library(self):
        """
        Returns the library model for the item
        :return: Library
        """

        if not self._library and self.library_window():
            return self.library_window().library()

        return self._library

    def set_library(self, library):
        """
        Sets the library model for the item
        :param library: Library
        """

        self._library = library

    def path(self):
        """
        Returns the path for the item
        :return: str
        """

        return self._path

    def set_path(self, path):
        """
        Sets the path location on disk for the item
        :param path: str
        """

        if not path:
            raise exceptions.ItemError('Cannot set an empty item path')

        self.reset_image_sequence()

        path = path_utils.normalize_path(path)
        self._path = path

        self.update_item_data()

    """
    ##########################################################################################
    DRAG & DROP
    ##########################################################################################
    """

    def drag_enabled(self):
        """
        Return whether the item can be dragged or not
        :return: bool
        """

        return self._drag_enabled

    def set_drag_enabled(self, flag):
        """
        Set whether item can be dragged or not
        :param flag: bool
        """

        self._drag_enabled = flag

    def mime_text(self):
        """
        Returns the mime text for drag and drop
        :return: str
        """

        if self.path():
            return self.path()

        return self._mime_text or self.text(0)

    def set_mime_text(self, text):
        """
        Sets the mime text for drag and drop
        :param text: str
        """

        self._mime_text = text

    def url(self):
        """
        Used by the mime data when dragging/droping the item
        :return: Qurl
        """

        if self.path():
            return QUrl('file:///{}'.format(self.path()))

        if not self._url:
            self._url = QUrl(self.text(0))

        return self._url

    def set_url(self, url):
        """
        Sets the url object of the current item
        :param url: QUrl or None
        """

        self._url = url

    """
    ##########################################################################################
    ORDER STORAGE
    ##########################################################################################
    """

    def item_data(self):
        """
        Returns the current item data
        :return: dict
        """

        return self._item_data

    def set_item_data(self, data):
        """
        Sets the given dictionary as teh data for the item
        :param data: dict
        """

        self._item_data = data

    def create_item_data(self):
        """
        Creates item data
        """

        path = self.path()
        dirname, basename, extension = path_utils.split_path(path)
        name = os.path.basename(path)
        category = os.path.basename(dirname)

        item_data = {
            'name': name,
            'path': path,
            'type': extension,
            'folder': dirname,
            'category': category
        }

        return item_data

    def update_item_data(self):
        """
        Updates item data
        """

        item_data = self.create_item_data()
        self.set_item_data(item_data)

    def save_item_data(self):
        """
        Syncs the item data to the library
        """

        self.update_item_data()
        if self.library():
            self.library().update_item(self)

    """
    ##########################################################################################
    SEARCH
    ##########################################################################################
    """

    def search_text(self):
        """
        Returns the search string used for finding the item
        :return: str
        """

        if not self._search_text:
            self._search_text = str(self._data)

        return self._search_text

    """
    ##########################################################################################
    METADATA
    ##########################################################################################
    """

    def metadata(self):
        """
        Returns the metadata for the given item
        :return: dict
        """

        return self._metadata

    def set_metadata(self, metadata):
        """
        Sets the given metadata for the item
        :param metadata: dict
        """

        self._metadata = metadata

    """
    ##########################################################################################
    TREE WIDGET
    ##########################################################################################
    """

    def column_from_label(self, label):
        """
        Returns column fro mlabel
        :param label: str
        :return: str
        """

        if self.treeWidget():
            return self.treeWidget().column_from_label(label)

        return None

    def label_from_column(self, column):
        """
        Returns label of the given column
        :param column: int
        :return: str
        """

        if self.treeWidget():
            return self.treeWidget().label_from_column(column)

        return None

    """
    ##########################################################################################
    VIEWER WIDGET
    ##########################################################################################
    """

    def viewer(self):
        """
        Returns the viewer widget that contains the item
        :return: libraryViewer
        """

        viewer_widget = None
        if self.treeWidget():
            viewer_widget = self.treeWidget().parent()

        return viewer_widget

    def dpi(self):
        """
        Return current dpi
        :return: int
        """

        if self.viewer():
            return self.viewer().dpi()

        return 1

    def padding(self):
        """
        Returns the padding/border size for the item
        :return: int
        """

        return self.viewer().padding()

    def text_height(self):
        """
        Retuns the heigh of the text for the item
        :return: int
        """

        return self.viewer().item_text_height()

    def is_text_visible(self):
        """
        Retursn whether the text is visible or not
        :return: bool
        """

        return self.viewer().is_item_text_visible()

    """
    ##########################################################################################
    PREVIEW WIDGET
    ##########################################################################################
    """

    def preview_widget(self, library_window):
        """
        Returns the widget to be shown when the user clicks on the item
        :param library_window: LibraryWindow
        :return: QWidget or None
        """

        widget = None

        if self.PreviewWidgetClass:
            widget = self.PreviewWidgetClass(item=self)

        return widget

    def show_preview_widget(self, library_window):
        """
        Shows the preview widget for the item instance
        :param library_window: LibraryWindow
        """

        widget = self.preview_widget(library_window)
        library_window.set_preview_widget(widget)

    """
    ##########################################################################################
    THUMBNAIL
    ##########################################################################################
    """

    def type_icon_path(self):
        """
        Returns the type icon path on disk
        :return: str
        """

        return self.TypeIconPath

    def thumbnail_path(self):
        """
        Return the thumbnail path for the item on disk
        :return: str
        """

        if not self.path():
            return self.DefaultThumbnailPath

        thumbnail_path = self.path() + '/thumbnail.jpg'
        if os.path.exists(thumbnail_path):
            return thumbnail_path

        thumbnail_path = thumbnail_path.replace('.jpg', '.png')
        if os.path.exists(thumbnail_path):
            return thumbnail_path

        return self.DefaultThumbnailPath

    """
    ##########################################################################################
    LOAD/SAVE
    ##########################################################################################
    """

    def write(self, path, *args, **kwargs):
        """
        Writes the item IO data to the given path
        :param path: str
        :param args: list
        :param kwargs: kwargs
        """

        raise NotImplementedError('write method for {} has not been implemented!'.format(self.__class__.__name__))

    def load(self, *args, **kwargs):
        """
        This function MUST be reimplemented to load any item data
        :param args: list
        :param kwargs: dict
        """

        qt.logger.debug('Loading "{}"'.format(self.name()))
        qt.logger.debug('Loading kwargs {}'.format(kwargs))
        self.loaded.emit(self)

    @show_wait_cursor_decorator
    def save(self, path=None, *args, **kwargs):
        """
        Saves current item
        :param path:
        :param args:
        :param kwargs:
        :return:
        """
        path = path or self.path()
        if path and not path.endswith(self.Extension):
            path += self.Extension

        self.set_path(path)

        qt.logger.debug('Item Saving: {}'.format(path))
        self.saving.emit(self)

        if os.path.exists(path):
            self.show_already_existing_dialog()

        temp_path = os.path.join(tempfile.mkdtemp(), self.name())
        if not os.path.isdir(temp_path):
            os.mkdir(temp_path)
        valid_save = self.write(temp_path, *args, **kwargs)
        if not valid_save:
            qt.logger.warning('Item {} not saved!'.format(path))
            if self.library_window():
                self.library_window().show_warning_message('Item {} not saved!'.format(path))
            return

        new_path = os.path.join(os.path.dirname(path), self.name())
        shutil.move(temp_path, new_path)
        self.set_path(new_path)
        self.save_item_data()
        if self.library_window():
            self.library_window().select_items([self])

        comment = kwargs.get('comment', None)
        self.save_version(new_path, comment)

        self.saved.emit(self)
        qt.logger.debug('Item Saved: {}'.format(self.path()))

    def save_version(self, path, comment):
        """
        Function that creates a new version of the item data
        :param path: str
        :param comment: str
        """

        version = fileio.FileVersion(os.path.join(path, self.name()))
        if not comment:
            comment = qtutils.get_comment()
            if not comment:
                comment = '-'
        version.save(comment)

    """
    ##########################################################################################
    DIALOGS
    ##########################################################################################
    """

    def show_toast_message(self, text):
        """
        Function that shows the toast widget with the given text
        :param text: str
        """

        if self.library_window():
            self.library_window().show_toast_message(text)

    def show_error_dialog(self, title, text):
        """
        Function that shows an error dialog to the user
        :param title: str
        :param text: str
        :return: QMessageBox.StandardButton or None
        """

        if self.library_window():
            self.library_window().show_error_message(text)

        btn = None
        if not self._modal:
            self._modal = True
            try:
                btn = messagebox.MessageBox.critical(self.library_window(), title, text)
            finally:
                self._modal = False

        return btn

    def show_exception_dialog(self, title, error, exception):
        """
        Function that shows a question dialog to the user
        :param title: str
        :param error: str
        :param exception: str
        """

        qt.logger.exception(exception)
        return self.show_error_dialog(title, error)

    def show_question_dialog(self, title, text):
        """
        Function that shows a question dialog to the user
        :param title: str
        :param text: str
        :return: QMessageBox.StandardButton
        """

        return messagebox.MessageBox.question(self.library_window(), title, text)

    def show_rename_dialog(self, parent=None):
        """
        Shows the rename dialog
        :param parent: QWidget
        """

        select = False
        if self.library_window():
            parent = parent or self.library_window()
            select = self.library_window().selected_folder_path() == self.path()

        name, btn = messagebox.MessageBox.input(
            parent, 'Rename item', 'Rename the current item to:', input_text=self.name())
        if btn == QDialogButtonBox.Ok:
            try:
                self.rename(name)
                if select:
                    self.library_window().select_folder_path(self.path())
            except Exception as e:
                self.show_exception_dialog('Rename Error', e, traceback.format_exc())
                raise

        return btn

    def show_move_dialog(self, parent=None):
        """
        Shows the move to browser dialog
        :param parent: QWidget
        """

        path = os.path.dirname(self.dirname())
        target = QFileDialog.getExistingDirectory(parent, 'Move To ...', path)
        if target:
            try:
                self.move(target)
            except Exception as e:
                self.show_exception_dialog('Move Error', e, traceback.format_exc())
                raise

    def show_delete_dialog(self):
        """
        Shows the delete item dialog
        """

        btn = self.show_question_dialog('Delete Item', 'Are you sure you want to delete this item?')
        if btn == QDialogButtonBox.Yes:
            try:
                self.delete()
            except Exception as e:
                self.show_exception_dialog('Delete Error', e, traceback.format_exc())
                raise

    def show_already_existing_dialog(self):
        """
        Shows a warning dialog if the item already exists on save
        """

        if not self.library_window():
            raise exceptions.ItemSaveError('Item already exists!')

        path = self.path()
        buttons = QMessageBox.Yes | QMessageBox.Cancel

        try:
            QApplication.setOverrideCursor(Qt.ArrowCursor)
            btn = self.library_window().show_question_dialog(
                'Item already exists',
                'Would you like to move the existing item "{}" to trash?'.format(self.name()), buttons
            )
        finally:
            QApplication.restoreOverrideCursor()

        if btn == QMessageBox.Yes:
            library = self.library()
            item = LibraryItem(path=path, library=library)
            self.library_window().move_items_to_trash([item])
            self.set_path(path)
        else:
            raise exceptions.ItemSaveError('You cannot save over an existing item.')

        return btn

    """
    ##########################################################################################
    SCHEMA
    ##########################################################################################
    """

    def current_load_value(self, name):
        """
        Returns the current filed value for the given name
        :param name: str
        :return: variant
        """

        return self._current_load_values.get(name, None)

    def save_schema(self):
        """
        Returns the schema used for saving the item
        :return: dict
        """

        return list()

    def load_schema(self):
        """
        Gets the options used to load the item
        :return: list(dict)
        """

        return list()

    def save_validator(self, **fields):
        """
        Validates the given save fields
        :param fields: dict
        :return: list(dict)
        """

        return list()

    def load_validator(self, **options):
        """
        Validates the current load options
        :param options: dict
        :return: list(dict)
        """

        return list()

    """
    ##########################################################################################
    SEQUENCE
    ##########################################################################################
    """

    def image_sequence(self):
        """
        Return ImageSequence of the item
        :return: image.ImageSequence or QMovie
        """

        return self._image_sequence

    def set_image_sequence(self, image_sequence):
        """
        Set the image sequence of the item
        :param image_sequence: image.ImageSequence or QMovie
        """

        self._image_sequence = image_sequence

    def image_sequence_path(self):
        """
        Return the path where image sequence is located on disk
        :return: str
        """

        return self._image_sequence_path

    def set_image_sequence_path(self, path):
        """
        Set the path where image sequence is located on disk
        :param path: str
        """

        self._image_sequence_path = path

    def reset_image_sequence(self):
        """
        Reset image sequence
        """

        self._image_sequence = None

    def play(self):
        """
        Start play image sequence
        """

        self.reset_image_sequence()
        path = self.image_sequence_path() or self.thumbnail_path()
        movie = None

        if os.path.isfile(path) and path.lower().endswith('.gif'):
            movie = QMovie(path)
            movie.setCacheMode(QMovie.CacheAll)
            movie.frameChanged.connect(self._on_frame_changed)
        elif os.path.isdir(path):
            if not self.image_sequence():
                movie = image.ImageSequence(path)
                movie.frameChanged.connect(self._on_frame_changed)

        if movie:
            self.set_image_sequence(movie)
            self.image_sequence().start()

    def update_frame(self):
        """
        Function that updates the current frame
        """

        if self.image_sequence():
            pixmap = self.image_sequence().current_pixmap()
            self.setIcon(0, pixmap)

    def stop(self):
        """
        Stop play image sequence
        """

        if self.image_sequence():
            self.image_sequence().stop()

    def playhead_color(self):
        """
        Returns playehad color
        :return: QColor
        """

        return self.DEFAULT_PLAYHEAD_COLOR

    def paint_playhead(self, painter, option):
        """
        Pain the playhead if the item has an image sequence
        :param painter: QPainter
        :param option: QStyleOptionViewItem
        """

        image_sequence = self.image_sequence()
        if image_sequence and self.under_mouse():
            count = image_sequence.frame_count()
            current = image_sequence.current_frame_number()
            if count > 0:
                percent = float((count + current) + 1) / count - 1
            else:
                percent = 0

            r = self.icon_rect(option)
            c = self.playhead_color()

            painter.setPen(Qt.NoPen)
            painter.setBrush(QBrush(c))

            if percent <= 0:
                width = 0
            elif percent >= 1:
                width = r.width()
            else:
                width = (percent * r.width()) - 1

            height = 3 * self.dpi()
            y = r.y() + r.height() - (height - 1)

            painter.drawRect(r.x(), y, width, height)

    def update_icon(self):
        """
        Clears the pixmap cache for the item
        """

        self.clear_cache()

    def clear_cache(self):
        """
        Clears the thumbnail cache
        """

        self._pixmap = dict()
        self._pixmap_rect = None
        self._pixmap_scaled = None
        self._thumbnail_icon = None

    def update(self):
        """
        Refresh teh visual state of the icon
        """

        self.update_icon()
        self.update_frame()

    def image_sequence_event(self, event):
        """
        :param event: QEvent
        """

        if self.image_sequence():
            if qtutils.is_control_modifier():
                if self.rect():
                    x = event.pos().x() - self.rect().x()
                    width = self.rect().width()
                    percent = 1.0 - (float(width - x) / float(width))
                    frame = int(self.image_sequence().frameCount() * percent)
                    self.image_sequence().jumpToFrame(frame)
                    self.update_frame()

    # ============================================================================================================
    # THUMBNAIL
    # ============================================================================================================

    def default_thumbnail_path(self):
        """
        Returns the default thumbnail path
        :return: str
        """
        return self.DefaultThumbnailPath

    def default_thumbnail_icon(self):
        """
        Returns the default thumbnail icon
        :return: QIcon
        """

        return QIcon(self.default_thumbnail_path())

    def thumbnail_icon(self):
        """
        Returns the thumbnail icon
        :return: QIcon
        """

        thumbnail_path = self.thumbnail_path()
        if not self._thumbnail_icon:
            if self.ENABLE_THUMBNAIL_THREAD and not self._worker_started:
                self._worker_started = True
                self._worker.set_path(thumbnail_path)
                self.ThreadPool.start(self._worker)
                self._thumbnail_icon = self.default_thumbnail_icon()
            else:
                self._thumbnail_icon = QIcon(thumbnail_path)

        return self._thumbnail_icon

    def _thumbnail_from_image(self, image):
        """
        Called after the given image object has finished loading
        :param image: QImage
        """

        self.clear_cache()
        pixmap = QPixmap()
        pixmap.convertFromImage(image)
        icon = QIcon(pixmap)
        self._thumbnail_icon = icon
        if self.viewer():
            self.viewer().update()

    # ============================================================================================================
    # PAINT
    # ============================================================================================================

    def font_size(self):
        """
        Returns the font size for the item
        :return: int
        """

        return self.DEFAULT_FONT_SIZE

    def font(self, column):
        """
        Returns the font for the given column
        :param column: int
        :return: QFont
        """

        default = QTreeWidgetItem.font(self, column)
        font = self._fonts.get(column, default)
        font.setPixelSize(self.font_size() * self.dpi())
        return font

    def text_width(self, column):
        """
        Returns the text width of the given column
        :param column: int
        :return: int
        """

        text = self.text(column)
        font = self.font(column)
        metrics = QFontMetrics(font)
        text_width = metrics.width(text)
        return text_width

    def text_color(self):
        """
        Returns the text color for the item
        :return: QColor
        """

        # TODO: change to use foreground role
        # return self.itemsWidget().palette().color(self.itemsWidget().foregroundRole())
        return self.viewer().text_color()

    def text_selected_color(self):
        """
        Returns the selected txt color for the item
        :return: QColor
        """

        return self.viewer().text_selected_color()

    def background_hover_color(self):
        """
        Returns the background color when the mouse is over the item
        :return: QColor
        """

        return self.viewer().background_hover_color()

    def background_selected_color(self):
        """
        Returns the background color when the item is selected
        :return: QColor
        """

        return self.viewer().background_selected_color()

    def rect(self):
        """
        Returns the rect for the current paint frame
        :return: QRect
        """

        return self._rect

    def set_rect(self, rect):
        """
        Sets the rect for the current paint frame
        :param rect: QRect
        """

        self._rect = rect

    def visualRect(self, option):
        """
        Returns the visual rect for the item
        :param option: QStyleOptionViewItem
        :return: QRect
        """

        rect = QRect(option.rect)
        return rect

    def paint_row(self, painter, option, index):
        """
        Paint performs low-level painting for the item
        :param painter: QPainter
        :param option: QStyleOptionViewItem
        :param index: QModelIndex
        """

        QTreeWidget.drawRow(self.treeWidget(), painter, option, index)

    def paint(self, painter, option, index):
        """
        Paint performs low-level painting for the item
        :param painter: QPainter
        :param option: QStyleOptionViewItem
        :param index: QModelIndex
        """

        self.set_rect(QRect(option.rect))
        painter.save()
        try:
            self.paint_background(painter, option, index)
            if self.is_text_visible():
                self.paint_text(painter, option, index)
            self.paint_icon(painter, option, index)
            if index.column() == 0:
                if self.image_sequence():
                    self.paint_playhead(painter, option)
        finally:
            painter.restore()

    def paint_background(self, painter, option, index):
        """
        Draw the background for the item
        :param painter: QPainter
        :param option: QStyleOptionViewItem
        :param index:QModelIndex
        """

        is_selected = option.state & QStyle.State_Selected
        is_mouse_over = option.state & QStyle.State_MouseOver
        painter.setPen(QPen(Qt.NoPen))
        visual_rect = self.visualRect(option)
        if is_selected:
            color = self.background_selected_color()
        elif is_mouse_over:
            color = self.background_hover_color()
        else:
            color = self.backgroundColor()
        painter.setBrush(QBrush(color))

        painter.drawRect(visual_rect)

    def icon_rect(self, option):
        """
        Returns the icon rect for the item
        :param option: QStyleOptionViewItem
        :return: QRect
        """

        padding = self.padding()
        rect = self.visualRect(option)
        width = rect.width()
        height = rect.height()
        if self.is_text_visible() and self.viewer().is_icon_view():
            height -= self.text_height()
        width -= padding
        height -= padding
        rect.setWidth(width)
        rect.setHeight(height)

        x = 0
        x += float(padding) / 2
        x += float((width - rect.width())) / 2

        y = float((height - rect.height())) / 2
        y += float(padding) / 2
        rect.translate(x, y)

        return rect

    def scale_pixmap(self, pixmap, rect):
        """
        Scale the given pixmap to given rect size
        The scaled pixmap is cached and its reused if its called with the same size
        :param pixmap: QPixmap
        :param rect: QRect
        :return: QPixmap
        """

        rect_changed = True

        if self._pixmap_rect:
            width_changed = self._pixmap_rect.width() != rect.width()
            height_changed = self._pixmap_rect.height() != rect.height()
            rect_changed = width_changed or height_changed

        if not self._pixmap_scaled or rect_changed:
            self._pixmap_scaled = pixmap.scaled(
                rect.width(), rect.height(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
            self._pixmap_rect = rect

        return self._pixmap_scaled

    def paint_icon(self, painter, option, index, align=None):
        """
        Draws the icon for the item
        :param painter: QPainter
        :param option: QStyleOptionViewItem
        :param index: int
        :param align: Qt.Align
        """

        column = index.column()
        pixmap = self.pixmap(column)
        if not pixmap:
            return

        rect = self.icon_rect(option)
        pixmap = self.scale_pixmap(pixmap, rect)
        pixmap_rect = QRect(rect)
        pixmap_rect.setWidth(pixmap.width())
        pixmap_rect.setHeight(pixmap.height())

        x, y = 0, 0
        align = Qt.AlignHCenter | Qt.AlignVCenter
        align_bottom_a = Qt.AlignBottom | Qt.AlignLeft
        align_bottom_b = align == Qt.AlignBottom | Qt.AlignHCenter or align == Qt.AlignBottom | Qt.AlignRight
        align_h_center_a = Qt.AlignHCenter or align == Qt.AlignCenter
        align_h_center_b = align == Qt.AlignHCenter | Qt.AlignBottom or align == Qt.AlignHCenter | Qt.AlignTop
        align_v_center_a = align == Qt.AlignVCenter | Qt.AlignLeft or align == Qt.AlignVCenter | Qt.AlignRight
        align_v_center_b = Qt.AlignVCenter or align == Qt.AlignCenter

        is_align_bottom = align == align_bottom_a or align_bottom_b
        is_align_h_center = align == align_h_center_a or align_h_center_b
        is_align_v_center = align == align_v_center_a or align_v_center_b
        if is_align_h_center:
            x += float(rect.width() - pixmap.width()) / 2
        elif is_align_v_center:
            y += float(rect.height() - pixmap.height()) / 2
        elif is_align_bottom:
            y += float(rect.height() - pixmap.height()) / 2

        pixmap_rect.translate(x, y)
        painter.drawPixmap(pixmap_rect, pixmap)

    def draw_icon_border(self, painter, pixmap_rect):
        """
        Draws a border around the icon
        :param painter: QPainter
        :param pixmap_rect: QRect
        """

        pixmap_rect = QRect(pixmap_rect)
        pixmap_rect.setX(pixmap_rect.x() - 5)
        pixmap_rect.setY(pixmap_rect.y() - 5)
        pixmap_rect.setWidth(pixmap_rect.width() + 5)
        pixmap_rect.setHeight(pixmap_rect.height() + 5)
        color = QColor(255, 255, 255, 10)
        painter.setPen(QPen(Qt.NoPen))
        painter.setBrush(QBrush(color))
        painter.drawRect(pixmap_rect)

    def paint_text(self, painter, option, index):
        """
        Draws the text for the item
        :param painter: QPainter
        :param option: QStyleOptionViewItem
        :param index: int
        """

        column = index.column()
        if column == 0 and self.viewer().is_table_view():
            return

        self._paint_text(painter, option, column)

    def _paint_text(self, painter, option, column):
        """
        Internal function used to paint the text
        :param painter: QPainter
        :param option: QStyleOption
        :param column: int
        """

        if self.viewer().is_icon_view():
            text = self.name()
        else:
            label = self.label_from_column(column)
            text = self.display_text(label)

        color = self.text_color()
        is_selected = option.state & QStyle.State_Selected
        if is_selected:
            color = self.text_selected_color()

        visual_rect = self.visualRect(option)
        width = visual_rect.width()
        height = visual_rect.height()
        padding = self.padding()
        x = padding / 2
        y = padding / 2
        visual_rect.translate(x, y)
        visual_rect.setWidth(width - padding)
        visual_rect.setHeight(height - padding)

        font = self.font(column)
        align = self.textAlignment(column)
        metrics = QFontMetrics(font)
        text_width = 1
        if text:
            text_width = metrics.width(text)
        if text_width > visual_rect.width() - padding:
            visual_width = visual_rect.width()
            text = metrics.elidedText(text, Qt.ElideRight, visual_width)
            align = Qt.AlignLeft
        if self.viewer().is_icon_view():
            align = align | Qt.AlignBottom
        else:
            align = align | Qt.AlignVCenter

        pen = QPen(color)
        painter.setPen(pen)
        painter.setFont(font)
        painter.drawText(visual_rect, align, text)

    # ============================================================================================================
    # BLENDING
    # ============================================================================================================

    def is_blending_enabled(self):
        """
        Returns whether blending is enabled or not
        :return: bool
        """

        return self._blending_enabled

    def set_blending_enabled(self, flag):
        """
        Sets whether blending is enabled or not
        :param flag: bool
        """

        self._blending_enabled = flag

    def is_blending(self):
        """
        Returns whether blending is playing or not
        :return: bool
        """

        return self.blend_position() is not None

    def blend_value(self):
        """
        Returns the curreent blend value
        :return: float
        """

        return self._blend_value

    def set_blend_value(self, blend):
        """
        Sets the current blend value
        :param blend: float
        """

        if self.is_blending_enabled():
            self._blend_value = blend
            self.blendChanged.emit(blend)

    def blend_previous_value(self):
        """
        Returns the blend previous current value
        :return: float
        """

        return self._blend_prev_value

    def blend_position(self):
        """
        Returns current blend position
        :return: QPoint
        """

        return self._blend_position

    def blending_event(self, event):
        """
        Function that is called when the mouse moves while the middle mouse button is held down
        :param event: QMouseEvent
        """

        if self.is_blending():
            value = math.ceil((event.pos().x() - self.blend_position().x()) * 1.5) + self.blend_previous_value()
            try:
                self.set_blend_value(value)
            except Exception:
                self.stop_blending()

    def start_blending_event(self, event):
        """
        Called when the middle mouse button is pressed
        :param event: QMouseEvent
        """

        if self.is_blending_enabled():
            if event.button() == Qt.MidButton:
                self._blend_position = event.pos()

    def stop_blending(self):
        """
        Called when the middle mouse button is released
        :return: QMouseEvent
        """

        self._blend_position = None
        self._blend_prev_value = self.blend_value()

    def reset_blending(self):
        """
        Resets the blending value to zero
        """

        self._blend_value = 0.0
        self._blend_prev_value = 0.09

    # ============================================================================================================
    # CONTEXTUAL MENUS
    # ============================================================================================================

    def context_edit_menu(self, menu, items=None):
        """
        This function is called when the user opens context menu
        The given menu is shown as a submenu of the main context menu
        This function can be override to create custom context menus in LibraryItems
        :param menu: QMenu
        :param items: list(LibraryItem)
        """

        if self.EnableDelete:
            delete_action = QAction('Delete', menu)
            delete_action.triggered.connect(self._on_show_delete_dialog)
            menu.addAction(delete_action)
            menu.addSeparator()

        rename_action = QAction('Rename', menu)
        move_to_action = QAction('Move to', menu)
        show_in_folder_action = QAction('Show in Folder', menu)
        copy_path_action = QAction('Copy Path', menu)

        rename_action.triggered.connect(self._on_show_rename_dialog)
        move_to_action.triggered.connect(self._on_move_dialog)
        show_in_folder_action.triggered.connect(self._on_show_in_folder)
        copy_path_action.triggered.connect(self._on_copy_path)

        menu.addAction(rename_action)
        menu.addAction(move_to_action)
        menu.addAction(show_in_folder_action)
        menu.addAction(copy_path_action)

        if self.library_window():
            select_folder_action = QAction('Select Folder', menu)
            select_folder_action.triggered.connect(self._on_select_folder)
            menu.addAction(select_folder_action)

    # ============================================================================================================
    # CALLBACKS
    # ============================================================================================================

    def _on_thumbnail_from_image(self, image):
        """
        Internal callback function that is called when an image object has finished loading
        """

        self.clear_cache()
        pixmap = QPixmap()
        pixmap.convertFromImage(image)
        icon = QIcon(pixmap)
        self._thumbnail_icon = icon
        if self.viewer():
            self.viewer().update()

    def _on_frame_changed(self, frame):
        """
        Internal callback function that is triggered when the movei object updates to the given
        frame
        :return:
        """

        if not qtutils.is_control_modifier():
            self.update_frame()

    def _on_show_rename_dialog(self):
        self.show_rename_dialog()

    def _on_show_delete_dialog(self):
        pass

    def _on_move_dialog(self):
        pass

    def _on_show_in_folder(self):
        """
        Internal callback function that is called when Show in Folder action is clicked
        """

        self.show_in_folder()

    def _on_copy_path(self):
        pass

    def _on_select_folder(self):
        """
        Internal callback function that is triggered when Select Folder action is clicked
        """

        if self.library_window():
            path = '/'.join(path_utils.normalize_path(self.path()).split('/')[:-1])
            self.library_window().select_folder_path(path)


class LibraryGroupItem(LibraryItem, object):
    """
    Class that defines group of items
    """

    DEFAULT_FONT_SIZE = consts.GROUP_ITEM_DEFAULT_FONT_SIZE

    def __init__(self, *args):
        super(LibraryGroupItem, self).__init__(*args)

        self._children = list()

        self._font = self.font(0)
        self._font.setBold(True)

        self.setFont(0, self._font)
        self.setFont(1, self._font)
        self.set_drag_enabled(False)

    def textAlignment(self, column):
        """
        Overrides base LibraryItem textAlignment function
        :param column: int
        """

        return QTreeWidgetItem.textAlignment(self, column)

    def sizeHint(self, column=0):
        """
        Overrides base sizeHint textAlignment function
        Returns the size hint of the item
        :param column: int
        :return: QSize
        """

        width = self.viewer().width() - 20
        return QSize(width, 40 * self.dpi())

    def visualRect(self, option):
        """
        Overrides base sizeHint visualRect function
        Returns the visual rect for the item
        :param option: QStyleOptionViewItem
        :return: QRect
        """
        rect = QRect(option.rect)
        rect.setX(10 * self.dpi())
        rect.setWidth(self.sizeHint().width())
        return rect

    def backgroundColor(self):
        """
        Overrides base sizeHint backgroundColor function
        Return the background color for the item.
        :rtype: QtWidgets.QtColor
        """
        return QColor(0, 0, 0, 0)

    def icon(*args):
        """
        Overrides base sizeHint icon function
        Overrided so icon is not displayed
        :param args:
        :return:
        """
        return None

    def paint_row(self, painter, option, index):
        """
        Overrides base paint_row icon function
        Paint performs low-level painting for the item
        :param painter: QPainter
        :param option: QStyleOptionViewItem
        :param index: QModelIndex
        """

        self.set_rect(QRect(option.rect()))
        painter.save()
        try:
            pass
            # self.paint_background(painter, option, index)
            # if self.is_text_visible():
            #     self._paint_text(painter, option, 1)
            # self.paint_icon(painter, option, index)
        finally:
            painter.restore()

    def paint_background(self, painter, option, index):
        """
        Overrides base paint_background icon function
        Draw the background for the item
        :param painter: QPainter
        :param option: QStyleOptionViewItem
        :param index: QModelIndex
        """

        super(LibraryGroupItem, self).paint_background(painter, option, index)

        painter.setPen(QPen(Qt.NoPen))
        visual_rect = self.visualRect(option)
        text = self.name()
        metrics = QFontMetrics(self._font)
        text_width = metrics.width(text)
        padding = (25 * self.dpi())
        visual_rect.setX(text_width + padding)
        visual_rect.setY(visual_rect.y() + (visual_rect.height() / 2))
        visual_rect.setHeight(2 * self.dpi())
        visual_rect.setWidth(visual_rect.width() - padding)

        color = QColor(self.text_color().red(), self.text_color().green(), self.text_color().blue(), 10)
        painter.setBrush(QBrush(color))
        painter.drawRect(visual_rect)

    def children(self):
        """
        Returns the children for the group
        :return: list(LibraryItem)
        """

        return self._children

    def set_children(self, children):
        """
        Sets the children for the group
        :param children: list(LibraryItem)
        """

        self._children = children

    def children_hidden(self):
        """
        Returns whether childrens are hidden or not
        :return: bool
        """

        for child in self.children():
            if not child.isHidden():
                return False

        return True

    def update_children(self):
        """
        Updates the visibility if all children are hidden
        :return:
        """

        self.setHidden(self.children_hidden())

    def is_text_visible(self):
        """
        Returns whether the text is visible or not
        :return: bool
        """

        return True

    def text_selected_color(self):
        """
        Returns the selected text color for the item
        :return: QColor
        """

        return self.viewer().text_color()

    def background_hover_color(self):
        """
        Return the background color when the mouse is over the item.
        :rtype: QtWidgets.QtColor
        """
        return QColor(0, 0, 0, 0)

    def background_selected_color(self):
        """
        Return the background color when the item is selected.
        :rtype: QtWidgets.QtColor
        """
        return QColor(0, 0, 0, 0)


class LibraryFolderItem(LibraryItem, object):

    RegisterOrder = 100
    EnableNestedItems = True
    DisplayInSidebar = True

    MenuName = 'Folder'
    MenuOrder = 1
    MenuIconPath = tp.ResourcesMgr().get('icons', 'color', 'folder.png')
    DefaultThumbnailPath = tp.ResourcesMgr().get('icons', 'folder.png')
    TrashIconPath = tp.ResourcesMgr().get('icons', 'trash.png')

    @classmethod
    def match(cls, path):
        """
        Returns whether the given path is supported by the item or not
        :param path: str
        :return: bool
        """

        if os.path.isdir(path):
            return True

        return False

    @classmethod
    def show_create_widget(cls, library_window):
        """
        Shows the dialog for creating a new folder
        :param library_window: LibraryWindow
        """

        path = library_window.selected_folder_path() or library_window.path()
        name, btn = messagebox.MessageBox.input(library_window, 'Create folder', 'Create a new folder with the name:')
        name = name.strip()
        if name and btn == QDialogButtonBox.Ok:
            path = os.path.join(path, name)
            item = cls(path, library_window=library_window)
            item.save(path)
            if library_window:
                library_window.refresh()
                library_window.select_folder_path(path)

    def save_version(self, path, comment):
        """
        Overrides base save_version function
        When creating folders we do not create any kind of version
        :param path: str
        :param comment: str
        """

        return

    def info(self):
        """
        Returns the info to display to the user
        :return: list(dict)
        """

        created = os.stat(self.path()).st_ctime
        created = datetime.fromtimestamp(created).strftime("%Y-%m-%d %H:%M %p")
        modified = os.stat(self.path()).st_mtime
        modified = datetime.fromtimestamp(modified).strftime("%Y-%m-%d %H:%M %p")

        return [
            {
                "name": "name",
                "value": self.name()
            },
            {
                "name": "path",
                "value": self.path()
            },
            {
                "name": "created",
                "value": created,
            },
            {
                "name": "modified",
                "value": modified,
            }
        ]

    def double_clicked(self):
        """
        Overrides this method to show the items contained in the folder
        """

        self.library_window().select_folder_path(self.path())

    def write(self, path, *args, **kwargs):
        """
        Overrides function to avoid not implemented exception
        :param path: str
        :param args: list
        :param kwargs: dict
        """

        return True

    def create_item_data(self):
        """
        Overrides this function to force the item type to Folder
        """

        item_data = super(LibraryFolderItem, self).create_item_data()
        item_data['type'] = 'Folder'
        return item_data

    def item_data(self):
        """
        Overrides this function to set the trash icon
        :return: dict
        """

        data = super(LibraryFolderItem, self).item_data()
        if data.get('path').endswith('Trash'):
            data['iconPath'] = self.TrashIconPath

        return data


class NamespaceOption(object):
    FromFile = 'file'
    FromCustom = 'custom'
    FromSelection = 'selection'


class BaseItem(LibraryItem, object):
    def __init__(self, *args, **kwargs):
        super(BaseItem, self).__init__(*args, **kwargs)

        self._current_load_values = dict()
        self._current_load_schema = list()
        self._current_save_schema = list()

        self._namespaces = list()
        self._namespace_options = NamespaceOption.FromSelection

        self._transfer_class = None
        self._transfer_object = None
        self._transfer_basename = None

        # In standard files we only use the data JSON to store properties of the file
        # We can use transfer object to store other data in specific situations
        self.set_transfer_class(utils.TransferObject)
        self.set_transfer_basename(dcc_consts.DATA_FILE)

    # ============================================================================================================
    # OVERRIDES
    # ============================================================================================================

    def load(self, objects=None, namespaces=None, **kwargs):
        """
        Loads the data from the transfer object
        :param objects: list(str) or None
        :param namespaces: list(str) or None
        :param kwargs: dict
        """

        qt.logger.debug('Loading: {}'.format(self.transfer_path()))
        self.transfer_object().load(objects=objects, namespaces=namespaces, **kwargs)
        qt.logger.debug('Loaded: {}'.format(self.transfer_path()))

    def write(self, path, objects, icon_path='', **options):
        """
        Writes all the given object data to the given path on disk
        :param path: str
        :param objects: list(str)
        :param icon_path: str
        :param options: dict
        """

        if icon_path:
            base_name = os.path.basename(icon_path)
            shutil.copy(icon_path, path + '/' + base_name)

    def load_validator(self, **options):
        """
        Validates the current load options
        :param options: dict
        :return: list(dict)
        """

        self._current_load_values = options

    def save_validator(self, **fields):
        """
        Validates the given save fields
        :param fields: dict
        :return: list(dict)
        """

        self._current_save_schema = fields

        selection = tp.Dcc.selected_nodes(full_path=True) or list()
        count = len(selection)
        plural = 's' if count > 1 else ''
        msg = '{} object{} selected for saving'.format(count, plural)

        return [
            {
                'name': 'contains',
                'value': msg
            }
        ]

    def save_schema(self):
        """
        Returns the schema used for saving the item
        :return: dict
        """

        return [
            {
                'name': 'name',
                'type': 'string',
                'layout': 'vertical'
            },
            {
                'name': 'comment',
                'type': 'text',
                'layout': 'vertical'
            },
            {
                'name': 'contains',
                'type': 'label',
                'label': {'visible': False}
            }
        ]

    def info(self):
        """
        Returns the info to display to the user
        :return: list(dict)
        """

        ctime = self.ctime()
        if ctime:
            ctime = timedate.time_ago(ctime)

        count = self.object_count()
        plural = 's' if count > 1 else ''
        contains = str(count) + ' Object' + plural

        return [
            {
                'name': 'name',
                'value': self.name()
            },
            {
                "name": "owner",
                "value": self.owner(),
            },
            {
                "name": "created",
                "value": ctime,
            },
            {
                "name": "contains",
                "value": contains,
            },
            {
                "name": "comment",
                "value": self.description() or "No comment",
            },
        ]

    # ============================================================================================================
    # LOAD/SAVE
    # ============================================================================================================

    def load_from_current_options(self):
        """
        Loads the data of the item
        """

        kwargs = self.options_from_settings()
        namespaces = self.namespaces()
        objects = tp.Dcc.selected_nodes(full_path=False) or list()

        try:
            self.load(objects=objects, namespaces=namespaces, **kwargs)
        except Exception as e:
            self.show_error_dialog('Item Error', str(e))
            raise

    # ============================================================================================================
    # SCHEMA
    # ============================================================================================================

    def settings(self):
        """
        Returns tpRigBuilder library settings file
        :return: JSONSettings
        """

        raise NotImplementedError('settings function bor BaseItem not implemented!')

    def default_options(self):
        """
        Returns default options
        :return: dict
        """

        options = dict()
        for option in self.load_schema():
            options[option.get('name')] = option.get('default')

        return options

    def options_from_settings(self):
        """
        Returns the options from the user settings
        :return: dict
        """

        settings = self.settings()
        settings = settings.get(self.__class__.__name__, {})
        options = settings.get('loadOptions', {})
        default_options = self.default_options()

        if options:
            for option in self.load_schema():
                name = option.get('name')
                persistent = option.get('persistent')
                if not persistent and name in options:
                    options[name] = default_options[name]

        return options

    def current_load_value(self, name):
        """
        Returns the current filed value for the given name
        :param name: str
        :return: variant
        """

        return self._current_load_values.get(name, None)

    def current_load_schema(self):
        """
        Returns the current options ste by the user
        :return: dict
        """

        return self._current_load_schema or self.options_from_settings()

    # ============================================================================================================
    # ITEM PROPERTIES
    # ============================================================================================================

    def transfer_class(self):
        """
        Returns the transfer class used to read and write data
        :return: TransferObject
        """

        return self._transfer_class

    def set_transfer_class(self, class_name):
        """
        Sets the transfer class used to read and write the data
        :param class_name: TransferObject
        """

        self._transfer_class = class_name

    def transfer_basename(self):
        """
        Returns the filename of the transfer path
        :return: str
        """

        return self._transfer_basename

    def set_transfer_basename(self, transfer_basename):
        """
        Sets the filename of the transfer path
        :param transfer_basename: str
        """

        self._transfer_basename = transfer_basename

    def transfer_path(self):
        """
        Returns the disk location to transfer path
        :return: str
        """

        if self.transfer_basename():
            return os.path.join(self.path(), self.transfer_basename())
        else:
            return self.path()

    def transfer_object(self):
        """
        Returns the transfer object used to read and write the data
        :return: TransferObject
        """

        if not self._transfer_object:
            path = self.transfer_path()
            self._transfer_object = self.transfer_class().from_path(path)

        return self._transfer_object

    def owner(self):
        """
        Returns the user who created this item
        :return: str or None
        """

        return self.transfer_object().metadata().get('user', 'Unknown')

        # return self.transfer_object().metadata().get('user', '')

    def description(self):
        """
        Returns the user description for this item
        :return: str
        """

        return self.transfer_object().metadata().get('description', '')

    def object_count(self):
        """
        Returns the number of objects this item contains
        :return: int
        """

        return self.transfer_object().count()

    # ============================================================================================================
    # SCENE
    # ============================================================================================================

    def namespaces(self):
        """
        Returns the namespaces for this item depending on the namespace option
        :return: list(str)
        """

        return list()
