def show_exc_info(parent, ex, tracebk=True, details=None, title=None):
    '''
        Shows a MyMessageBox dialog with information about given exception.
        parent --- Qt parent of the dialog to be shown.
        ex --- an exception to be described in the dialog.
        tracebk --- when tracebk is True the dialog would contain a DetailedText
    button. When user presses that button --- an info with stack trace is shown in
    the dialog.
        details --- when details is not None, then it is displayed in the DetailedText
    area of the dialog (instead of stack trace). In this case value of tracebk is
    ignored.
        title --- set different title to the dialog. When title is None, the dialog
    has a default title.
    '''
    mb = MyMessageBox(parent)
    defaultTitle = QCoreApplication.translate("helpers", "Information",
                                              None, QCoreApplication.UnicodeUTF8)
    mb.setWindowTitle(defaultTitle if title is None else title)
    mb.setText(str(ex))
    if not is_none_or_empty(details):
        mb.setDetailedText(details)
    elif tracebk:
        mb.setDetailedText(traceback.format_exc())

    mb.exec_() STATUSBAR_TIMEOUT = 5000 class MsgException(Exception):
    '''This exception is used in situations when no error occurred,
    but we have to tell the user some text information.'''
    def __init__(self, msg=None):
        super(MsgException, self).__init__(msg) class LoginError(Exception):
    def __init__(self, msg=None):
        super(LoginError, self).__init__(msg) class Ui_AboutDialog(object):
    def setupUi(self, AboutDialog):
        AboutDialog.setObjectName(_fromUtf8("AboutDialog"))
        AboutDialog.resize(553, 372)
        self.verticalLayout = QtGui.QVBoxLayout(AboutDialog)
        self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
        self.textEdit = QtGui.QTextEdit(AboutDialog)
        self.textEdit.setReadOnly(True)
        self.textEdit.setObjectName(_fromUtf8("textEdit"))
        self.verticalLayout.addWidget(self.textEdit)
        self.buttonBox = QtGui.QDialogButtonBox(AboutDialog)
        self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
        self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Ok)
        self.buttonBox.setCenterButtons(True)
        self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
        self.verticalLayout.addWidget(self.buttonBox)

        self.retranslateUi(AboutDialog)
        QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), AboutDialog.accept)
        QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), AboutDialog.reject)
        QtCore.QMetaObject.connectSlotsByName(AboutDialog)

    def retranslateUi(self, AboutDialog):
        AboutDialog.setWindowTitle(_translate("AboutDialog", "About Reggata", None)) class User(Base):
    __tablename__ = "users"

    USER = 'USER'
    ADMIN = 'ADMIN'

    login = sqa.Column(sqa.String, primary_key=True)
    password = sqa.Column(sqa.String)
    group = sqa.Column(sqa.Enum(USER, ADMIN), nullable=False, default=USER)
    date_created = sqa.Column(sqa.DateTime)

    def __init__(self, login=None, password=None, group=USER):
        self.login = login
        self.password = password
        self.group = group
        self.date_created = datetime.datetime.today()

    def checkValid(self):
        if helpers.is_none_or_empty(self.login):
            raise ValueError("Attribute User.login shouldn't be empty.")
        return True class SaveNewUserCommand(AbstractCommand):
    def __init__(self, user):
        self.__user = user

    def _execute(self, uow):
        self._session = uow.session
        self.__saveNewUser(self.__user)

    def __saveNewUser(self, user):
        #TODO: have to do some basic check of user object validity
        self._session.add(user)
        self._session.commit()
        self._session.refresh(user)
        self._session.expunge(user) class ChangeUserPasswordCommand(AbstractCommand):
    def __init__(self, userLogin, newPasswordHash):
        self.__userLogin = userLogin
        self.__newPasswordHash = newPasswordHash

    def _execute(self, uow):
        user = uow.session.query(db.User).get(self.__userLogin)
        if user is None:
            raise err.LoginError("User {} doesn't exist.".format(self.__userLogin))

        user.password = self.__newPasswordHash
        uow.session.commit() class RepoMgr(object):
    '''
        Represents one single repository. Manages it as a whole.
    '''

    def __init__(self, path_to_repo):
        '''
            Opens an existing repository at given path.
        '''
        try:
            self._base_path = path_to_repo
            if not os.path.exists(self.base_path + os.sep + consts.METADATA_DIR):
                raise Exception("Directory {} is not a repository base path."
                                .format(self.base_path))

            engine_echo = stringToBool(UserConfig().get("sqlalchemy.engine_echo"))

            self.__engine = sqa.create_engine(\
                "sqlite:///" + self.base_path + os.sep + consts.METADATA_DIR + os.sep + \
                consts.DB_FILE, echo=engine_echo)

            self.Session = sessionmaker(bind=self.__engine)
        except Exception as ex:
            raise CannotOpenRepoError(ex)

    def __del__(self):
        pass

    @property
    def base_path(self):
        '''
            Repository base path is the root directory of the repository.
        '''
        return self._base_path

    @base_path.setter
    def base_path(self, value):
        self._base_path = value

    @staticmethod
    def createNewRepo(base_path):
        '''
            Initializes a new repo at a given path. This consists of these steps:
            1) Checks that base_path exists
            2) Checks that <base_path>/.reggata directory does not exist yet
            3) Creates <base_path>/.reggata directory in repository root and
        empty sqlite database inside it.
            4) At last, this function opens just created repository and
        returns RepoMgr object, associated with it.
        '''
        if (not os.path.exists(base_path)):
            raise Exception("Directory {} doesn't exists.".format(base_path))

        if (os.path.exists(base_path + os.sep + consts.METADATA_DIR)):
            raise Exception("It looks like {} is already a repository base path."
                            .format(base_path))

        os.mkdir(base_path + os.sep + consts.METADATA_DIR)

        engine = sqa.create_engine("sqlite:///" + base_path + os.sep + \
                                   consts.METADATA_DIR + os.sep + consts.DB_FILE)
        Base.metadata.create_all(engine)

        return RepoMgr(base_path)


    def createUnitOfWork(self):
        return UnitOfWork(self.Session(), self.base_path)
 class ExtAppMgr(QtCore.QObject):
    '''
        This class invokes preferred external applications to open repository files.
    Preferred applications are configured in text file reggata.conf.
    '''

    def __init__(self, parent=None):
        super(ExtAppMgr, self).__init__(parent)

        self.updateState()


    def updateState(self):
        self.__state = ExtAppMgr.readCurrentState()

        # Key - file extension (in lowercase), Value - external app executable
        extentionsDict = dict()
        for appDescription in self.__state.appDescriptions:
            for ext in appDescription.fileExtentions:
                ext = ext.lower()

                if ext in extentionsDict.keys():
                    msg = QCoreApplication.translate("ext_app_mgr",
                        "File extension {} cannot be in more than one file_type group.",
                        None, QCoreApplication.UnicodeUTF8)
                    raise ValueError(msg.format(ext))

                extentionsDict[ext] = appDescription.appCommandPattern
        self.__extensions = extentionsDict


    @staticmethod
    def readCurrentState():
        filesCategories = eval(UserConfig().get('ext_app_mgr_file_types', "[]"))

        appDescriptions = []
        for filesCategory in filesCategories:
            extentions = eval(UserConfig().get('ext_app_mgr.{}.extensions'
                                                      .format(filesCategory)))
            appCmd = UserConfig().get("ext_app_mgr.{}.command"
                                       .format(filesCategory))
            appDescriptions.append(ExtAppDescription(filesCategory, appCmd, extentions))

        extFileManagerCommandPattern = UserConfig().get('ext_file_manager')

        state = ExtAppMgrState(appDescriptions,
                               extFileManagerCommandPattern)
        return state


    @staticmethod
    def setCurrentState(extAppMgrState):
        extAppMgrState.raiseErrorIfNotValid()

        stateDict = dict()
        categories = []
        for appDescription in extAppMgrState.appDescriptions:
            category = appDescription.filesCategory
            categories.append(category)
            stateDict["ext_app_mgr.{}.command".format(category)] = appDescription.appCommandPattern
            stateDict["ext_app_mgr.{}.extensions".format(category)] = appDescription.fileExtentions

        stateDict["ext_app_mgr_file_types"] = categories
        stateDict["ext_file_manager"] = extAppMgrState.extFileMgrCommandPattern

        UserConfig().storeAll(stateDict)



    def openFileWithExtApp(self, abs_path):

        _, ext = os.path.splitext(abs_path)
        appCommandPattern = self.__extensions.get(ext.lower(), None)

        if appCommandPattern is None:
            msg = QCoreApplication.translate("ext_app_mgr",
                        "External application for {0} file extension is not set. "
                        "Please, go to 'Main Menu -> Repository -> Manage External Applications' "
                        "and configure it",
                        None, QCoreApplication.UnicodeUTF8)
            raise Exception(msg.format(ext))

        appCommand = self.__replaceCommandPatternKeys(appCommandPattern,
                                                      filePath=abs_path,
                                                      dirPath=os.path.dirname(abs_path))
        self.__createSubprocess(appCommand)


    def openContainingDirWithExtAppManager(self, abs_path):
        if self.__state.extFileMgrCommandPattern is None:
            msg = QCoreApplication.translate("ext_app_mgr",
                        "No external file manager command is set. "
                        "Please, go to 'Main Menu -> Repository -> Manage External Applications' "
                        "and configure it",
                        None, QCoreApplication.UnicodeUTF8)
            raise MsgException(msg.format(consts.USER_CONFIG_FILE))

        appCommand = self.__replaceCommandPatternKeys(self.__state.extFileMgrCommandPattern,
                                                      filePath=abs_path,
                                                      dirPath=os.path.dirname(abs_path))
        self.__createSubprocess(appCommand)


    def __replaceCommandPatternKeys(self, commandPattern, filePath, dirPath):
        result = commandPattern.replace('%f', '"' + filePath + '"')
        result = result.replace('%d', '"' + dirPath + '"')
        return result


    def __createSubprocess(self, commandWithArgs):
        args = shlex.split(commandWithArgs)
        args = self.__modifyArgsIfOnWindowsAndPathIsNetwork(args)
        logger.debug("subprocess.Popen(args), args=%s", args)

        #subprocess.call(args) #This call would block the current thread
        pid = subprocess.Popen(args).pid #This call would not block the current thread
        logger.info("Created subprocess with PID = %d", pid)

        # TODO: raise an exception if application executable file not found


    def __modifyArgsIfOnWindowsAndPathIsNetwork(self, args):
        # This is a hack... On Windows shlex fails to handle correctly network paths such as
        # \\Tiger\SYSTEM (C)\home\testrepo.rgt\file.txt
        if not sys.platform.startswith("win"):
            return args
        if args[1].startswith("\\"):
            args[1] = "\\" + args[1]
        return args class HandlerSignals():
    '''Named constants in this class is not the signal names, but the signal types.
    They are arguments of the following signals: handlerSignal, handlerSignals.
    handlerSignal accepts single signal type. handlerSignals accepts a list of
    signal types. See also WidgetsUpdateManager, ActionHandlerStorage and
    AbstractActionHandler classes.
    '''

    ITEM_CREATED = "itemCreated"
    ITEM_CHANGED = "itemChanged"
    ITEM_DELETED = "itemDeleted"
    LIST_OF_FAVORITE_REPOS_CHANGED = "listOfFavoriteReposChanged"
    STATUS_BAR_MESSAGE = "statusBarMessage"
    RESET_SINGLE_ROW = "resetSingleRow"     # TODO: This signal type should be hidden in ItemsTableTool, because it's not global
    RESET_ROW_RANGE = "resetRowRange"       # TODO: This signal type should be hidden in ItemsTableTool too
    REGGATA_CONF_CHANGED = "reggata.conf file has changed"


    @staticmethod
    def allPossibleSignals():
        return [HandlerSignals.ITEM_CREATED,
                HandlerSignals.ITEM_CHANGED,
                HandlerSignals.ITEM_DELETED,
                HandlerSignals.LIST_OF_FAVORITE_REPOS_CHANGED,
                HandlerSignals.STATUS_BAR_MESSAGE,
                HandlerSignals.RESET_SINGLE_ROW,
                HandlerSignals.RESET_ROW_RANGE,
                HandlerSignals.REGGATA_CONF_CHANGED,
                ] class ImportItemsThread(AbstractWorkerThread):
    def __init__(self, parent, repo, importFromFilename, userLogin):
        super(ImportItemsThread, self).__init__(parent)
        self.repo = repo
        self.srcFile = importFromFilename
        self.userLogin = userLogin

    def doWork(self):
        srcArchive = TarFile.open(self.srcFile, "r", format=tarfile.PAX_FORMAT)
        try:
            filenames = self.__getListOfMetadataFiles(srcArchive)
            for i in range(len(filenames)):
                filename = filenames[i]

                #Restore item from json state
                itemStateBytes = srcArchive.extractfile(filename).read()
                itemState = str(itemStateBytes, encoding="utf-8")
                item = memento.Decoder().decode(itemState)

                #Imported item will be owned by that user, who is performing the import
                item.user_login = self.userLogin

                #Move physical file to repository
                dstAbsPath = None
                if item.data_ref is not None:
                    dstAbsPath = os.path.join(self.repo.base_path, item.data_ref.url)
                    if os.path.exists(dstAbsPath):
                        #Skip this item. Count and remember this error
                        continue
                    srcArchive.extract(item.data_ref.url_raw, path=self.repo.base_path)

                uow = self.repo.createUnitOfWork()
                try:
                    itemSrcAbsPath = dstAbsPath
                    itemDstRelPath = os.path.relpath(dstAbsPath, self.repo.base_path) \
                        if dstAbsPath is not None else None
                    cmd = cmds.SaveNewItemCommand(item, itemSrcAbsPath, itemDstRelPath)
                    uow.executeCommand(cmd)
                except Exception:
                    if dstAbsPath is not None:
                        os.remove(dstAbsPath)
                    #count and remember error details to show it later in GUI
                finally:
                    uow.close()

                self.emit(QtCore.SIGNAL("progress"), int(100.0*float(i) / len(filenames)))
        finally:
            srcArchive.close()


    def __getListOfMetadataFiles(self, srcArchive):
        result = []
        tarInfos = srcArchive.getmembers()
        for tarInfo in tarInfos:
            #TODO use regexp here, for more precise check
            filename = tarInfo.name
            if not filename.startswith(".reggata"):
                continue
            result.append(filename)
        return result class AbstractActionHandler(QtCore.QObject):
    def __init__(self, tool=None):
        super(AbstractActionHandler, self).__init__()
        self._tool = tool
        self._model = tool

    def handle(self):
        raise NotImplementedError("This function should be overriden in subclass")

    def connectSignals(self, widgetsUpdateManager):
        self.connect(self, QtCore.SIGNAL("handlerSignal"), \
                     widgetsUpdateManager.onHandlerSignal)
        self.connect(self, QtCore.SIGNAL("handlerSignals"), \
                     widgetsUpdateManager.onHandlerSignals)

    def disconnectSignals(self, widgetsUpdateManager):
        self.disconnect(self, QtCore.SIGNAL("handlerSignal"), \
                     widgetsUpdateManager.onHandlerSignal)
        self.disconnect(self, QtCore.SIGNAL("handlerSignals"), \
                     widgetsUpdateManager.onHandlerSignals)

    def _emitHandlerSignal(self, handlerSignalType, *params):
        self.emit(QtCore.SIGNAL("handlerSignal"), handlerSignalType, *params)

    def _emitHandlerSignals(self, handlerSignalTypes):
        self.emit(QtCore.SIGNAL("handlerSignals"), handlerSignalTypes) class ExternalAppsDialog(QtGui.QDialog):

    def __init__(self, parent, extAppMgrState, dialogs):
        super(ExternalAppsDialog, self).__init__(parent)
        self.ui = Ui_ExternalAppsDialog()
        self.ui.setupUi(self)

        self.__extAppMgrState = extAppMgrState
        self.__dialogs = dialogs
        self.__read()

        self.connect(self.ui.buttonBox, QtCore.SIGNAL("accepted()"), self.__buttonOkClicked)
        self.connect(self.ui.buttonBox, QtCore.SIGNAL("rejected()"), self.__buttonCancelClicked)

        self.connect(self.ui.comboBoxCategory, QtCore.SIGNAL("currentIndexChanged(int)"),
                     self.__updateCategoryDependentWidgets)

        validator = FileExtentionsListValidator(self.ui.lineEditFileExtensions)
        self.ui.lineEditFileExtensions.setValidator(validator)
        self.connect(self.ui.lineEditFileExtensions, QtCore.SIGNAL("editingFinished()"),
                     self.__parseAndWriteFileExtentions)

        self.connect(self.ui.lineEditAppCmd, QtCore.SIGNAL("textChanged(const QString&)"),
                     self.__writeApplicationCommand)
        self.connect(self.ui.buttonSelectApp, QtCore.SIGNAL("clicked()"),
                     self.__onButtonSelectAppClicked)

        self.connect(self.ui.buttonNewCategory, QtCore.SIGNAL("clicked()"),
                     self.__onButtonNewCategoryClicked)

        self.connect(self.ui.buttonDeleteCategory, QtCore.SIGNAL("clicked()"),
                     self.__onButtonDeleteCategoryClicked)

        self.connect(self.ui.lineEditExtFileBrowserCmd, QtCore.SIGNAL("textChanged(const QString&)"),
                     self.__writeFileBrowserCommand)
        self.connect(self.ui.buttonSelectFileBrowser, QtCore.SIGNAL("clicked()"),
                     self.__onButtonSelectFileBrowserClicked)


    def extAppMgrState(self):
        return self.__extAppMgrState


    def __read(self):
        for appDescription in self.__extAppMgrState.appDescriptions:
            self.ui.comboBoxCategory.addItem(appDescription.filesCategory)

        self.__updateCategoryDependentWidgets(self.__currentCategoryIndex())

        fileBrowserCmd = self.__extAppMgrState.extFileMgrCommandPattern
        self.ui.lineEditExtFileBrowserCmd.setText(fileBrowserCmd)


    def __currentCategoryIndex(self):
        return self.ui.comboBoxCategory.currentIndex()


    def __updateCategoryDependentWidgets(self, groupIndex):
        if groupIndex >= 0:
            appDescription = self.__extAppMgrState.appDescriptions[groupIndex]

            self.ui.lineEditAppCmd.setText(appDescription.appCommandPattern)
            self.ui.lineEditAppCmd.setEnabled(True)

            self.ui.lineEditFileExtensions.setText(
                helpers.to_commalist(appDescription.fileExtentions, apply_each=str, sep=" "))
            self.ui.lineEditFileExtensions.setEnabled(True)

        else:
            self.ui.lineEditAppCmd.setText("")
            self.ui.lineEditAppCmd.setEnabled(False)

            self.ui.lineEditFileExtensions.setText("")
            self.ui.lineEditFileExtensions.setEnabled(False)



    def __parseAndWriteFileExtentions(self):
        index = self.__currentCategoryIndex()
        if index < 0:
            return

        userText = self.ui.lineEditFileExtensions.text().strip()
        assert not helpers.is_none_or_empty(userText), "This is a sign of bug in FileExtentionsListValidator.."

        self.__extAppMgrState.appDescriptions[index].fileExtentions = userText.split()



    def __writeApplicationCommand(self):
        index = self.__currentCategoryIndex()
        if index < 0:
            return
        currentAppCmd = self.__extAppMgrState.appDescriptions[index].appCommandPattern

        try:
            userText = self.ui.lineEditAppCmd.text().strip()
            if helpers.is_none_or_empty(userText):
                raise MsgException(self.tr("Application command should not be empty."))

            self.__extAppMgrState.appDescriptions[index].appCommandPattern = userText

        except Exception as ex:
            helpers.show_exc_info(self, ex)
            self.ui.lineEditAppCmd.setText(currentAppCmd)


    def __writeFileBrowserCommand(self):
        currentCmd = self.__extAppMgrState.extFileMgrCommandPattern
        try:
            userText = self.ui.lineEditExtFileBrowserCmd.text().strip()
            if helpers.is_none_or_empty(userText):
                raise MsgException(self.tr("File Browser command should not be empty."))

            self.__extAppMgrState.extFileMgrCommandPattern = userText

        except Exception as ex:
            helpers.show_exc_info(self, ex)
            self.ui.lineEditExtFileBrowserCmd.setText(currentCmd)



    def __onButtonSelectAppClicked(self):
        executableFile = self.__dialogs.getOpenFileName(self, self.tr("Select application executable file"))
        if not executableFile:
            return

        executableFile = executableFile.strip()
        if " " in executableFile:
            executableFile = '"' + executableFile + '"'
        self.ui.lineEditAppCmd.setText(executableFile + " %f")


    def __onButtonSelectFileBrowserClicked(self):
        executableFile = self.__dialogs.getOpenFileName(self, self.tr("Select file browser executable file"))
        if not executableFile:
            return

        executableFile = executableFile.strip()
        if " " in executableFile:
            executableFile = '"' + executableFile + '"'
        self.ui.lineEditExtFileBrowserCmd.setText(executableFile + " %d")


    def __onButtonNewCategoryClicked(self):
        try:
            categoriesCountBefore = self.ui.comboBoxCategory.count()

            text, isOk = self.__dialogs.execGetTextDialog(self,
                self.tr("Input Dialog"), self.tr("Enter the name for new category of files."),
                defaultText="Category_{}".format(categoriesCountBefore))
            if not isOk:
                return

            if helpers.is_none_or_empty(text):
                raise MsgException(self.tr("Category name should not be empty"))

            if self.__isCategoryExists(text):
                raise MsgException(self.tr("Category with given name already exists. Choose different name."))

            appDescription = ExtAppDescription(text, "<path to executable> %f", [".mp3", ".ogg"])
            self.__extAppMgrState.appDescriptions.append(appDescription)

            self.ui.comboBoxCategory.addItem(text)
            categoriesCountAfter = self.ui.comboBoxCategory.count()
            self.ui.comboBoxCategory.setCurrentIndex(categoriesCountAfter - 1)

        except Exception as ex:
            helpers.show_exc_info(self, ex)


    def __isCategoryExists(self, categoryName):
        for appDescription in self.__extAppMgrState.appDescriptions:
            if appDescription.filesCategory.strip() == categoryName.strip():
                return True
        return False



    def __onButtonDeleteCategoryClicked(self):
        index = self.__currentCategoryIndex()
        if index < 0:
            return
        categoryName = self.__extAppMgrState.appDescriptions[index].filesCategory

        mbResult = self.__dialogs.execMessageBox(self,
            text=self.tr("Do you really want to delete category '{}'?".format(categoryName)),
            buttons=[QtGui.QMessageBox.Yes, QtGui.QMessageBox.No])
        if mbResult != QtGui.QMessageBox.Yes:
            return

        del self.__extAppMgrState.appDescriptions[index]
        self.ui.comboBoxCategory.removeItem(index)


    def __checkCategoriesValid(self):
        self.__extAppMgrState.raiseErrorIfNotValid()


    def __buttonOkClicked(self):
        try:
            self.__checkCategoriesValid()
            return self.accept()

        except Exception as ex:
            helpers.show_exc_info(self, ex)


    def __buttonCancelClicked(self):
        return self.reject()
 class UserDialogsFacade(AbstractDialogsFacade):
    '''
        It's a facade-like class, that have functions to invoke different dialogs to
    interact with user.
    '''

    def execUserDialog(self, user, gui, dialogMode):
        u = UserDialog(user, gui, dialogMode)
        return u.exec_()

    def execChangeUserPasswordDialog(self, user, gui):
        dialog = ChangeUserPasswordDialog(gui, user)
        if dialog.exec_():
            return (True, dialog.newPasswordHash)
        else:
            return (False, None)

    def execItemDialog(self, item, gui, repo, dialogMode):
        completer = Completer(repo, gui)
        dialog = ItemDialog(gui, item, repo.base_path, dialogMode, completer=completer)
        return dialog.exec_()


    def execItemsDialog(self, items, gui, repo, dialogMode, sameDstPath):
        completer = Completer(repo, gui)
        d = ItemsDialog(gui, repo.base_path, items, dialogMode,
                        same_dst_path=sameDstPath, completer=completer)
        return d.exec_()


    def startThreadWithWaitDialog(self, thread, gui, indeterminate):
        wd = WaitDialog(gui, indeterminate)
        wd.connect(thread, QtCore.SIGNAL("finished"), wd.reject)
        wd.connect(thread, QtCore.SIGNAL("exception"), wd.exception)
        wd.connect(thread, QtCore.SIGNAL("progress"), wd.set_progress)
        wd.startWithWorkerThread(thread)


    def getOpenFileName(self, gui, textMessageForUser, fileFilter=""):
        file = QFileDialog.getOpenFileName(gui, textMessageForUser, filter=fileFilter)
        return file

    def getOpenFileNames(self, gui, textMessageForUser):
        files = QFileDialog.getOpenFileNames(gui, textMessageForUser)
        return files

    def getOpenFilesAndDirs(self, gui, textMessageForUser):
        fd = FileDialog(gui, textMessageForUser)
        if not fd.exec_():
            return []
        files = fd.getSelectedFiles()
        return files

    def getExistingDirectory(self, gui, textMessageForUser):
        dirPath = QFileDialog.getExistingDirectory(gui, textMessageForUser)
        return dirPath

    def getSaveFileName(self, gui, textMessageForUser, fileFilter=""):
        filename = QFileDialog.getSaveFileName(parent=gui, caption=textMessageForUser,
                                               filter=fileFilter,
                                               options=QFileDialog.DontConfirmOverwrite)
        return filename

    def execMessageBox(self, parent, text, title="Reggata", buttons=[QMessageBox.Ok], detailedText=None):
        '''
            Shows modal QtGui.QMessageBox and returns code of the clicked button.
        '''
        mb = QMessageBox(parent)
        mb.setText(text)
        mb.setWindowTitle(title)

        buttonsCode = QMessageBox.NoButton
        for button in buttons:
            buttonsCode = buttonsCode | button
        mb.setStandardButtons(buttonsCode)

        if not helpers.is_none_or_empty(detailedText):
            mb.setDetailedText(detailedText)

        return mb.exec_()


    def execGetTextDialog(self, gui, dialogTitle, textMessageForUser, defaultText=""):
        text, isOk = QInputDialog.getText(
            gui, dialogTitle, textMessageForUser, text=defaultText)
        return (text, isOk)


    def execGetYesNoAnswerDialog(self, gui, title, question):
        return QtGui.QMessageBox.question(gui, title, question, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.Yes) class UserDialog(QtGui.QDialog):

    CREATE_MODE = "CREATE_MODE"
    LOGIN_MODE = "LOGIN_MODE"

    def __init__(self, user, parent, mode):
        super(UserDialog, self).__init__(parent)
        if type(user) != User:
            raise TypeError(self.tr("Argument user must be a User class instance."))
        self.user = user
        self.ui = Ui_UserDialog()
        self.ui.setupUi(self)
        self.mode = None

        self.ui.label_group.setVisible(False)
        self.ui.comboBox_group.setVisible(False)

        self.connect(self.ui.buttonBox, QtCore.SIGNAL("accepted()"), self.button_ok)
        self.connect(self.ui.buttonBox, QtCore.SIGNAL("rejected()"), self.button_cancel)

        self.setMode(mode)


    def setMode(self, mode):
        if mode == UserDialog.CREATE_MODE:
            self.setWindowTitle(self.tr("Create new repository user"))

            self.ui.label_notice.setVisible(False)

        elif mode == UserDialog.LOGIN_MODE:
            self.setWindowTitle(self.tr("Repository login"))

            self.ui.label_password_repeat.setVisible(False)
            self.ui.lineEdit_password_repeat.setVisible(False)

            self.ui.label_group.setVisible(False)
            self.ui.comboBox_group.setVisible(False)
        else:
            raise UnsupportedDialogModeError(self.tr("Mode={} is not supported by this dialog.")
                                             .format(mode))

        self.mode = mode


    def write(self):
        self.user.login = self.ui.lineEdit_login.text()
        if self.mode == UserDialog.CREATE_MODE \
        and self.ui.lineEdit_password.text() != self.ui.lineEdit_password_repeat.text():
            raise MsgException(self.tr("Entered passwords do not match."))

        self.user.password = computePasswordHash(self.ui.lineEdit_password.text())

        self.user.group = self.ui.comboBox_group.currentText()

    def button_ok(self):
        try:
            self.write()
            self.user.checkValid()
            self.accept()
        except Exception as ex:
            show_exc_info(self, ex)

    def button_cancel(self):
        self.reject()
import os
import PyQt4.QtGui as QtGui
import reggata
import reggata.helpers as helpers
import reggata.consts as consts
import reggata.statistics as stats
from reggata.helpers import show_exc_info
from reggata.consts import STATUSBAR_TIMEOUT
from reggata.errors import MsgException, LoginError
from reggata.ui.ui_aboutdialog import Ui_AboutDialog # TODO: gui imports should be removed from logic package!
from reggata.data.db_schema import User
from reggata.data.commands import SaveNewUserCommand, ChangeUserPasswordCommand
from reggata.data.repo_mgr import RepoMgr
from reggata.logic.ext_app_mgr import ExtAppMgr
from reggata.logic.handler_signals import HandlerSignals
from reggata.logic.worker_threads import ImportItemsThread
from reggata.logic.action_handlers import AbstractActionHandler
from reggata.gui.external_apps_dialog import ExternalAppsDialog
from reggata.gui.user_dialogs_facade import UserDialogsFacade
from reggata.gui.user_dialog import UserDialog

            self._emitHandlerSignal(HandlerSignals.ITEM_CREATED)

            #TODO: display information about how many items were imported
            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE,
                self.tr("Operation completed."), STATUSBAR_TIMEOUT)

            stats.sendEvent("main_window.import_items")

        except Exception as ex:
            show_exc_info(self._model.gui, ex)



class ExitReggataActionHandler(AbstractActionHandler):
    def __init__(self, tool):
        super(ExitReggataActionHandler, self).__init__(tool)

    def handle(self):
        try:
            self._tool.gui.close()
            stats.sendEvent("main_window.exit_reggata")
        except Exception as ex:
            show_exc_info(self._tool.gui, ex)


class ManageExternalAppsActionHandler(AbstractActionHandler):
    def __init__(self, model, dialogs):
        super(ManageExternalAppsActionHandler, self).__init__(model)
        self._dialogs = dialogs

    def handle(self):
        try:
            extAppMgrState = ExtAppMgr.readCurrentState()
            dialog = ExternalAppsDialog(self._model.gui, extAppMgrState, self._dialogs)
            if dialog.exec_() != QtGui.QDialog.Accepted:
                return

            ExtAppMgr.setCurrentState(dialog.extAppMgrState())
            self._emitHandlerSignal(HandlerSignals.REGGATA_CONF_CHANGED)
            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE,
                self.tr("Operation completed."), STATUSBAR_TIMEOUT)

            stats.sendEvent("main_window.manage_external_apps")

        except Exception as ex:
            show_exc_info(self._model.gui, ex)




class ShowAboutDialogActionHandler(AbstractActionHandler):
    def __init__(self, model):
        super(ShowAboutDialogActionHandler, self).__init__(model)

    def handle(self):
        try:
            ad = AboutDialog(self._model.gui)
            ad.exec_()
            stats.sendEvent("main_window.show_about_dialog")
        except Exception as ex:
            show_exc_info(self._model.gui, ex)
        else:
            self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE,
                self.tr("Operation completed."), STATUSBAR_TIMEOUT)



about_message = \
'''
<h1>Reggata</h1>
<p>Version: {0}</p>

<p>Reggata is a tagging system for local files.</p>

<p>Copyright 2012 Vitaly Volkov, <font color="blue">vitvlkv@gmail.com</font></p>

<p>Home page: <font color="blue">http://github.com/vlkv/reggata</font></p>

<p>Reggata is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
</p>

<p>Reggata is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
</p>

<p>You should have received a copy of the GNU General Public License
along with Reggata.  If not, see <font color="blue">http://www.gnu.org/licenses</font>.
</p>
'''.format(reggata.__version__)


class AboutDialog(QtGui.QDialog):
    def __init__(self, parent=None):
        super(AboutDialog, self).__init__(parent)
        self.ui = Ui_AboutDialog()
        self.ui.setupUi(self)
        self.ui.textEdit.setHtml(about_message)


class OpenFavoriteRepoActionHandler(AbstractActionHandler):
    def __init__(self, model):
        super(OpenFavoriteRepoActionHandler, self).__init__(model)

    def handle(self):
        try:
            action = self.sender()
            repoBasePath = action.repoBasePath

            currentUser = self._model.user
            assert currentUser is not None

            self._model.repo = RepoMgr(repoBasePath)

            try:
                self._model.loginUser(currentUser.login, currentUser.password)

                self._emitHandlerSignal(HandlerSignals.STATUS_BAR_MESSAGE,
