예제 #1
0
class SupportManager(QtCore.QObject, PMXSupportBaseManager):
    #Signals for bundle
    bundleAdded = QtCore.pyqtSignal(object)
    bundleRemoved = QtCore.pyqtSignal(object)
    bundleChanged = QtCore.pyqtSignal(object)
    bundlePopulated = QtCore.pyqtSignal(object)

    #Signals for bundle items
    bundleItemAdded = QtCore.pyqtSignal(object)
    bundleItemRemoved = QtCore.pyqtSignal(object)
    bundleItemChanged = QtCore.pyqtSignal(object)
    bundleItemTriggered = QtCore.pyqtSignal(object)

    #Signals for themes
    themeAdded = QtCore.pyqtSignal(object)
    themeRemoved = QtCore.pyqtSignal(object)
    themeChanged = QtCore.pyqtSignal(object)

    #Settings
    shellVariables = pmxConfigPorperty(default=[], tm_name=u'OakShelVariables')

    @pmxConfigPorperty(default=[], tm_name=u'OakBundleManagerDeletedBundles')
    def deleted(self, deleted):
        self.deletedObjects = map(lambda uuid: uuidmodule.UUID(uuid), deleted)

    @pmxConfigPorperty(default=[], tm_name=u'OakBundleManagerDeletedBundles')
    def disabled(self, disabled):
        self.disabledObjects = map(lambda uuid: uuidmodule.UUID(uuid),
                                   disabled)

    #http://manual.macromates.com/en/expert_preferences.html
    #When you create a new item in the bundle editor without having selected a bundle first, then the bundle with the UUID held by this defaults key is used as the target
    defaultBundleForNewBundleItems = pmxConfigPorperty(
        default=u'B7BC3FFD-6E4B-11D9-91AF-000D93589AF6',
        tm_name=u'OakDefaultBundleForNewBundleItems')

    SETTINGS_GROUP = 'SupportManager'

    def __init__(self, application):
        QtCore.QObject.__init__(self)
        PMXSupportBaseManager.__init__(self)
        self.application = application
        self.bundleTreeModel = BundleItemTreeModel(self)
        self.themeListModel = ThemeListModel(self)
        self.themeStylesTableModel = ThemeStylesTableModel(self)
        self.processTableModel = ExternalProcessTableModel(self)

        #STYLE PROXY
        self.themeStyleProxyModel = ThemeStyleProxyTableModel(self)
        self.themeStyleProxyModel.setSourceModel(self.themeStylesTableModel)

        #TREE PROXY
        self.bundleProxyTreeModel = BundleItemProxyTreeModel(self)
        self.bundleProxyTreeModel.setSourceModel(self.bundleTreeModel)

        #BUNDLES
        self.bundleProxyModel = BundleListModel(self)
        self.bundleProxyModel.setSourceModel(self.bundleTreeModel)

        #TEMPLATES
        self.templateProxyModel = TemplateListModel(self)
        self.templateProxyModel.setSourceModel(self.bundleTreeModel)

        #PROJECTS
        self.projectProxyModel = ProjectListModel(self)
        self.projectProxyModel.setSourceModel(self.bundleTreeModel)

        #SYNTAX
        self.syntaxProxyModel = SyntaxListModel(self)
        self.syntaxProxyModel.setSourceModel(self.bundleTreeModel)

        #INTERACTIVEITEMS
        self.actionItemsProxyModel = BundleItemTypeProxyModel(
            ["command", "snippet", "macro"], self)
        self.actionItemsProxyModel.setSourceModel(self.bundleTreeModel)

        #PREFERENCES
        self.preferenceProxyModel = BundleItemTypeProxyModel(
            "preference", self)
        self.preferenceProxyModel.setSourceModel(self.bundleTreeModel)

        #DRAGCOMMANDS
        self.dragcommandProxyModel = BundleItemTypeProxyModel(
            "dragcommand", self)
        self.dragcommandProxyModel.setSourceModel(self.bundleTreeModel)

        #BUNDLEMENUGROUP
        self.bundleMenuGroup = BundleItemMenuGroup(self)

    @classmethod
    def contributeToSettings(cls):
        from prymatex.gui.settings.environment import PMXEnvVariablesWidget
        return [PMXEnvVariablesWidget]

    def setEditorAvailable(self, available):
        self.editorAvailable = available

    def appendMenuToBundleMenuGroup(self, menu, offset=None):
        self.bundleMenuGroup.appendMenu(menu, offset)

    def menuForBundle(self, bundle):
        return self.bundleMenuGroup.menuForBundle(bundle)

    #---------------------------------------------------
    # Environment
    #---------------------------------------------------
    def environmentVariables(self):
        environment = PMXSupportBaseManager.buildEnvironment(self)
        #Extend wiht the user shell variables
        for var in self.shellVariables:
            if var['enabled']:
                environment[var['variable']] = var['value']
        return environment

    def buildEnvironment(self, systemEnvironment=True):
        env = PMXSupportBaseManager.buildEnvironment(self, systemEnvironment)
        for var in self.shellVariables:
            if var['enabled']:
                env[var['variable']] = var['value']
        return env

    # Override loadSupport for emit signals
    def loadSupport(self, *largs, **kwargs):
        PMXSupportBaseManager.loadSupport(self, *largs, **kwargs)
        self.bundleProxyTreeModel.sort(0, QtCore.Qt.AscendingOrder)
        self.bundleProxyTreeModel.setDynamicSortFilter(True)

    def runProcess(self, context, callback):
        if context.asynchronous:
            return self.runQProcess(context, callback)
        else:
            return PMXSupportBaseManager.runProcess(self, context, callback)

    #Interface
    def runQProcess(self, context, callback):
        process = QtCore.QProcess(self)
        if context.workingDirectory is not None:
            process.setWorkingDirectory(context.workingDirectory)

        self.processTableModel.appendProcess(process,
                                             description=context.description())

        environment = QtCore.QProcessEnvironment()
        for key, value in context.environment.iteritems():
            environment.insert(key, value)

        process.setProcessEnvironment(environment)

        def onQProcessFinished(process, context, callback):
            def runCallback(exitCode):
                self.processTableModel.removeProcess(process)
                context.errorValue = str(
                    process.readAllStandardError()).decode("utf-8")
                context.outputValue = str(
                    process.readAllStandardOutput()).decode("utf-8")
                context.outputType = exitCode
                callback(context)

            return runCallback

        process.finished[int].connect(
            onQProcessFinished(process, context, callback))

        if context.inputType is not None:
            process.start(context.shellCommand, QtCore.QIODevice.ReadWrite)
            if not process.waitForStarted():
                raise Exception("No puedo correr")
            process.write(unicode(context.inputValue).encode("utf-8"))
            process.closeWriteChannel()
        else:
            process.start(context.shellCommand, QtCore.QIODevice.ReadOnly)

    def buildAdHocCommand(self, *largs, **kwargs):
        return BundleItemTreeNode(
            PMXSupportBaseManager.buildAdHocCommand(self, *largs, **kwargs))

    def buildAdHocSnippet(self, *largs, **kwargs):
        return BundleItemTreeNode(
            PMXSupportBaseManager.buildAdHocSnippet(self, *largs, **kwargs))

    #---------------------------------------------------
    # MANAGED OBJECTS OVERRIDE INTERFACE
    #---------------------------------------------------
    def setDeleted(self, uuid):
        """
        Marcar un managed object como eliminado
        """
        self.deletedObjects.append(uuid)
        deleted = map(lambda uuid: unicode(uuid).upper(), self.deletedObjects)
        self.settings.setValue('deleted', deleted)

    def isDeleted(self, uuid):
        return uuid in self.deletedObjects

    def isEnabled(self, uuid):
        return uuid not in self.disabledObjects

    def setDisabled(self, uuid):
        self.disabledObjects.append(uuid)
        disabled = map(lambda uuid: unicode(uuid).upper(),
                       self.disabledObjects)
        self.settings.setValue('disabled', disabled)

    def setEnabled(self, uuid):
        self.disabledObjects.remove(uuid)
        disabled = map(lambda uuid: unicode(uuid).upper(),
                       self.disabledObjects)
        self.settings.setValue('disabled', disabled)

    #---------------------------------------------------
    # BUNDLE OVERRIDE INTERFACE
    #---------------------------------------------------
    def addBundle(self, bundle):
        bundleNode = BundleItemTreeNode(bundle)
        self.bundleTreeModel.appendBundle(bundleNode)
        self.bundleAdded.emit(bundleNode)
        return bundleNode

    def modifyBundle(self, bundle):
        self.bundleChanged.emit(bundle)

    def removeBundle(self, bundle):
        self.bundleTreeModel.removeBundle(bundle)
        self.bundleRemoved.emit(bundle)

    def getAllBundles(self):
        return self.bundleProxyModel.getAllItems()

    def getDefaultBundle(self):
        return self.getBundle(self.defaultBundleForNewBundleItems)

    def populatedBundle(self, bundle):
        self.bundlePopulated.emit(bundle)

    #---------------------------------------------------
    # BUNDLEITEM OVERRIDE INTERFACE
    #---------------------------------------------------
    def addBundleItem(self, bundleItem):
        bundleItemNode = BundleItemTreeNode(bundleItem)
        self.bundleTreeModel.appendBundleItem(bundleItemNode)
        self.bundleItemAdded.emit(bundleItemNode)
        return bundleItemNode

    def modifyBundleItem(self, bundleItem):
        self.bundleItemChanged.emit(bundleItem)

    def removeBundleItem(self, bundleItem):
        self.bundleTreeModel.removeBundleItem(bundleItem)
        self.bundleItemRemoved.emit(bundleItem)

    def getAllBundleItems(self):
        nodes = []
        for bundle in self.getAllBundles():
            for node in bundle.childNodes():
                nodes.append(node)
        return nodes

    #---------------------------------------------------
    # TEMPLATEFILE OVERRIDE INTERFACE
    #---------------------------------------------------
    def addTemplateFile(self, file):
        bundleTemplateFileNode = BundleItemTreeNode(file)
        self.bundleTreeModel.appendTemplateFile(bundleTemplateFileNode)
        return bundleTemplateFileNode

    #---------------------------------------------------
    # THEME OVERRIDE INTERFACE
    #---------------------------------------------------
    def addTheme(self, theme):
        themeRow = ThemeStyleTableRow(theme, self.scores)
        self.themeListModel.appendTheme(themeRow)
        self.themeAdded.emit(themeRow)
        return themeRow

    def modifyTheme(self, theme):
        self.themeChanged.emit(theme)

    def removeTheme(self, theme):
        self.themeListModel.removeTheme(theme)
        self.themeRemoved.emit(theme)

    def getAllThemes(self):
        return self.themeListModel.getAllItems()

    #---------------------------------------------------
    # THEME STYLE OVERRIDE INTERFACE
    #---------------------------------------------------
    def addThemeStyle(self, style):
        themeStyle = ThemeStyleTableRow(style)
        self.themeStylesTableModel.appendStyle(themeStyle)
        return themeStyle

    def removeThemeStyle(self, style):
        self.themeStylesTableModel.removeStyle(style)

    #---------------------------------------------------
    # PREFERENCES OVERRIDE INTERFACE
    #---------------------------------------------------
    @dynamic_memoized
    def getAllPreferences(self):
        return self.preferenceProxyModel.getAllItems()

    #---------------------------------------------------
    # TABTRIGGERS OVERRIDE INTERFACE
    #---------------------------------------------------
    @dynamic_memoized
    def getAllTabTriggerItems(self):
        tabTriggers = []
        for item in self.actionItemsProxyModel.getAllItems():
            if item.tabTrigger != None:
                tabTriggers.append(item)
        return tabTriggers

    @dynamic_memoized
    def getAllBundleItemsByTabTrigger(self, tabTrigger):
        items = []
        for item in self.actionItemsProxyModel.getAllItems():
            if item.tabTrigger == tabTrigger:
                items.append(item)
        return items

    #---------------------------------------------------
    # KEYEQUIVALENT OVERRIDE INTERFACE
    #---------------------------------------------------
    @dynamic_memoized
    def getAllKeyEquivalentItems(self):
        keyEquivalent = []
        for item in self.actionItemsProxyModel.getAllItems(
        ) + self.syntaxProxyModel.getAllItems():
            if item.keyEquivalent != None:
                keyEquivalent.append(item)
        return keyEquivalent

    @dynamic_memoized
    def getAllBundleItemsByKeyEquivalent(self, keyEquivalent):
        items = []
        for item in self.actionItemsProxyModel.getAllItems():
            if item.keyEquivalent == keyEquivalent:
                items.append(item)
        for syntax in self.syntaxProxyModel.getAllItems():
            if syntax.keyEquivalent == keyEquivalent:
                items.append(syntax)
        return items

    #---------------------------------------------------
    # FILE EXTENSION OVERRIDE INTERFACE
    #---------------------------------------------------
    def getAllBundleItemsByFileExtension(self, path):
        items = []
        for item in self.dragcommandProxyModel.getAllItems():
            if any(
                    map(
                        lambda extension: fnmatch.fnmatch(
                            path, "*.%s" % extension),
                        item.draggedFileExtensions)):
                items.append(item)
        return items

    #---------------------------------------------------
    # ACTION ITEMS OVERRIDE INTERFACE
    #---------------------------------------------------
    def getAllActionItems(self):
        return self.actionItemsProxyModel.getAllItems()

    #---------------------------------------------------
    # SYNTAXES OVERRIDE INTERFACE
    #---------------------------------------------------
    def getAllSyntaxes(self):
        return self.syntaxProxyModel.getAllItems()
예제 #2
0
class CommitDialog(QtGui.QDialog, Ui_CommitDialog, PMXBaseDialog):
    lastCommitSummary = pmxConfigPorperty(default=[])

    def __init__(self, parent=None):
        QtGui.QDialog.__init__(self, parent)
        PMXBaseDialog.__init__(self)
        self.setupUi(self)
        self.filesTableModel = FilesTableModel(self)
        self.tableViewFiles.setModel(self.filesTableModel)
        self.tableViewFiles.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tableViewFiles.customContextMenuRequested.connect(
            self.showTableViewFilesContextMenu)
        self.tableViewFiles.setSelectionBehavior(
            QtGui.QAbstractItemView.SelectRows)
        self.tableViewFiles.horizontalHeader().setResizeMode(
            1, QtGui.QHeaderView.Stretch)

        #Setup Context Menu
        selectMenu = {
            "title":
            "Select Menu",
            "items": [
                {
                    'text': 'Choose All',
                    'callback': lambda dialog: dialog.chooseAll(),
                },
                {
                    'text': 'Choose None',
                    'callback': lambda dialog: dialog.chooseNone(),
                },
                {
                    'text': 'Revert to Default Choices',
                    'callback': lambda dialog: dialog.revertChoices(),
                },
            ]
        }

        self.selectMenu, _ = create_menu(self, selectMenu, connectActions=True)
        self.toolButtonSelect.setMenu(self.selectMenu)

    def chooseAll(self):
        print "chooseAll"

    def chooseNone(self):
        print "chooseNone"

    def revertChoices(self):
        print "revertChoices"

    def initialize(self, mainWindow):
        self.lastCommitSummary = self.lastCommitSummary[:10]
        self.comboBoxSummary.addItem("Previous Summaries")
        for summary in self.lastCommitSummary:
            self.comboBoxSummary.addItem("%s ..." %
                                         " ".join(summary.split()[:8]))

    @QtCore.pyqtSlot(int)
    def on_comboBoxSummary_activated(self, index):
        index -= 1
        if index < len(self.lastCommitSummary):
            self.textEditSummary.setPlainText(self.lastCommitSummary[index])
            self.comboBoxSummary.setCurrentIndex(0)

    def showTableViewFilesContextMenu(self, point):
        index = self.tableViewFiles.indexAt(point)
        if index.isValid():
            # TODO Obtener el item y armar el menu
            menu = QtGui.QMenu(self)
            for action in self.actions:
                menu.addAction(action["name"])
            menu.popup(self.tableViewFiles.mapToGlobal(point))

    def setParameters(self, parameters):
        filePaths = []
        fileStatus = []
        self.actions = []
        if "title" in parameters:
            self.setWindowTitle(parameters["title"])
        if "files" in parameters:
            filePaths = parameters["files"].split()
        if "status" in parameters:
            fileStatus = parameters["status"].split(":")
        if "diff-cmd" in parameters:
            cmd, args = parameters["diff-cmd"].split(',')
            self.actions.append({
                'name': 'Diff',
                'command': cmd,
                'args': args,
                'status': []
            })
        if "action-cmd" in parameters:
            for command in parameters["action-cmd"]:
                status, action = command.split(':')
                name, cmd, args = action.split(',')
                self.actions.append({
                    'name': name,
                    'command': cmd,
                    'args': args,
                    'status': status.split(",")
                })
        if len(filePaths) == len(fileStatus):
            self.filesTableModel.setFiles(
                map(
                    lambda (f, s): {
                        'path': f,
                        'status': s,
                        'checked': s != "?"
                    }, zip(filePaths, fileStatus)))
            self.tableViewFiles.resizeColumnsToContents()
            self.tableViewFiles.resizeRowsToContents()

    def on_buttonOk_pressed(self):
        self.accept()

    def on_buttonCancel_pressed(self):
        self.close()

    def execModal(self):
        code = self.exec_()
        if code == QtGui.QDialog.Accepted:
            args = ["dylan"]
            message = self.textEditSummary.toPlainText()
            args.append("'%s'" % message)
            if message not in self.lastCommitSummary:
                self.lastCommitSummary.insert(0, message)
                self.settings.setValue("lastCommitSummary",
                                       self.lastCommitSummary)
            args.append(" ".join(self.filesTableModel.selectedFiles()))
            return " ".join(args)
        return 'cancel'
예제 #3
0
class PMXBrowserDock(QtGui.QDockWidget, Ui_BrowserDock, PMXBaseDock):
    SHORTCUT = "F9"
    ICON = resources.getIcon("internet-web-browser")
    PREFERED_AREA = QtCore.Qt.BottomDockWidgetArea

    SETTINGS_GROUP = "Browser"

    updateInterval = pmxConfigPorperty(default=3000)
    homePage = pmxConfigPorperty(default="http://www.prymatex.org")

    @pmxConfigPorperty(default=os.environ.get('http_proxy', ''))
    def proxy(self, value):
        """System wide proxy"""
        proxy_url = QtCore.QUrl(value)
        #TODO: Una regexp para filtar basura y quitar el try except
        if not value:
            network_proxy = QNetworkProxy(QNetworkProxy.NoProxy)
        else:
            try:
                protocol = QNetworkProxy.NoProxy
                if proxy_url.scheme().startswith('http'):
                    protocol = QNetworkProxy.HttpProxy
                else:
                    protocol = QNetworkProxy.Socks5Proxy
                network_proxy = QNetworkProxy(protocol, proxy_url.host(),
                                              proxy_url.port(),
                                              proxy_url.userName(),
                                              proxy_url.password())
            except:
                network_proxy = QNetworkProxy(QNetworkProxy.NoProxy)
        QNetworkProxy.setApplicationProxy(network_proxy)

    @classmethod
    def contributeToSettings(cls):
        from prymatex.gui.settings.browser import NetworkSettingsWidget
        from prymatex.gui.settings.addons import AddonsSettingsWidgetFactory
        return [NetworkSettingsWidget, AddonsSettingsWidgetFactory("browser")]

    def __init__(self, parent):
        QtGui.QDockWidget.__init__(self, parent)
        PMXBaseDock.__init__(self)
        self.setupUi(self)
        self.setupToolBar()

        #Developers, developers, developers!!! Extras
        QtWebKit.QWebSettings.globalSettings().setAttribute(
            QtWebKit.QWebSettings.DeveloperExtrasEnabled, True)
        QtWebKit.QWebSettings.globalSettings().setAttribute(
            QtWebKit.QWebSettings.PluginsEnabled, True)

        #New manager
        self.networkAccessManager = NetworkAccessManager(
            self,
            self.webView.page().networkAccessManager())
        self.webView.page().setNetworkAccessManager(self.networkAccessManager)
        self.networkAccessManager.commandUrlRequested.connect(
            self.on_manager_commandUrlRequested)

        # Set the default home page
        self.lineUrl.setText(self.homePage)
        self.webView.setUrl(QtCore.QUrl(self.homePage))

        self.scrollValues = (
            False, 0, 0
        )  #(<restore scroll values>, <horizontalValue>, <verticalValue>)

        # history buttons:
        self.buttonBack.setEnabled(False)
        self.buttonNext.setEnabled(False)

        #Capturar editor, bundleitem
        self.currentEditor = None
        self.runningContext = None

        #Sync Timer
        self.updateTimer = QtCore.QTimer()
        self.updateTimer.timeout.connect(self.updateHtmlCurrentEditorContent)

    def setupToolBar(self):
        #Setup Context Menu
        optionsMenu = {
            "text": "Browser Options",
            "items": [self.actionSyncEditor, self.actionConnectEditor]
        }

        self.browserOptionsMenu, _ = create_menu(self, optionsMenu)
        self.toolButtonOptions.setMenu(self.browserOptionsMenu)

    def initialize(self, mainWindow):
        PMXBaseDock.initialize(self, mainWindow)
        #TODO: ver el tema de proveer servicios esta instalacion en la main window es pedorra
        mainWindow.browser = self

    def showEvent(self, event):
        self.setFocus()

    def event(self, event):
        if event.type() == QtCore.QEvent.KeyPress:
            if event.key() == QtCore.Qt.Key_Escape:
                self.close()
                self.mainWindow.currentEditor().setFocus()
                return True
            elif event.key() == QtCore.Qt.Key_L and event.modifiers(
            ) == QtCore.Qt.ControlModifier:
                self.lineUrl.setFocus()
                self.lineUrl.selectAll()
                return True
        return QtGui.QDockWidget.event(self, event)

    #=======================================================================
    # Browser main methods
    #=======================================================================
    def setHtml(self, string, url=None):
        if not self.isVisible():
            self.show()
        self.raise_()
        if url is not None:
            self.lineUrl.setText(url.toString())
        self.webView.setHtml(string, url)
        self.runningContext = None

    def setRunningContext(self, context):
        url = QtCore.QUrl.fromUserInput("about:%s" % context.description())
        self.setHtml(context.outputValue, url)
        self.runningContext = context

    def runCommand(self, command):
        print command
        command, environment, tempFile = prepareShellScript(
            unicode(command), self.runningContext.environment)
        process = Popen(command,
                        stdout=PIPE,
                        stdin=PIPE,
                        stderr=PIPE,
                        env=environment)
        self.webView.page().mainFrame().addToJavaScriptWindowObject(
            "_systemWrapper", SystemWrapper(process, command))

    #=======================================================================
    # Browser Signals handlers
    #=======================================================================
    def on_manager_commandUrlRequested(self, url):
        self.application.handleUrlCommand(url)

    def on_lineUrl_returnPressed(self):
        """Url have been changed by user"""

        page = self.webView.page()
        history = page.history()
        self.buttonBack.setEnabled(history.canGoBack())
        self.buttonNext.setEnabled(history.canGoForward())

        url = QtCore.QUrl.fromUserInput(self.lineUrl.text())
        self.webView.setUrl(url)

    def on_buttonStop_clicked(self):
        """Stop loading the page"""
        self.webView.stop()

    def on_buttonReload_clicked(self):
        """Reload the web page"""
        url = QtCore.QUrl.fromUserInput(self.lineUrl.text())
        self.webView.setUrl(url)

    def on_buttonBack_clicked(self):
        """Back button clicked, go one page back"""
        page = self.webView.page()
        history = page.history()
        history.back()
        self.buttonBack.setEnabled(history.canGoBack())

    def on_buttonNext_clicked(self):
        """Next button clicked, go to next page"""
        page = self.webView.page()
        history = page.history()
        history.forward()
        self.buttonNext.setEnabled(history.canGoForward())

    #=============================
    # QWebView Signals handlers
    #=============================
    def on_webView_iconChanged(self):
        # print "iconChanged"
        pass

    def on_webView_linkClicked(self, link):
        #Terminar el timer y navegar hasta esa Url
        #TODO: validar que el link este bien
        self.stopTimer()
        map(lambda action: action.setChecked(False),
            [self.actionSyncEditor, self.actionConnectEditor])
        self.webView.load(link)

    def on_webView_loadFinished(self, ok):
        if not ok:
            return
        self.webView.page().mainFrame().addToJavaScriptWindowObject(
            "TextMate", TextMate(self))
        environment = ""
        if self.runningContext is not None:
            environment = "\n".join(
                map(
                    lambda
                    (key, value): 'window["{0}"]="{1}";'.format(key, value),
                    self.runningContext.environment.iteritems()))
        self.webView.page().mainFrame().evaluateJavaScript(BASE_JS %
                                                           environment)

        #Restore scroll
        if self.scrollValues[0]:
            self.webView.page().mainFrame().setScrollBarValue(
                QtCore.Qt.Horizontal, self.scrollValues[1])
            self.webView.page().mainFrame().setScrollBarValue(
                QtCore.Qt.Vertical, self.scrollValues[2])

    def on_webView_urlChanged(self, url):
        """Update the URL if a link on a web page is clicked"""
        #History
        page = self.webView.page()
        history = page.history()
        self.buttonBack.setEnabled(history.canGoBack())
        self.buttonNext.setEnabled(history.canGoForward())

        #Scroll Values
        self.scrollValues = (url.toString() == self.webView.url().toString(),
                             self.scrollValues[1], self.scrollValues[2])

        #Line Location
        self.lineUrl.setText(url.toString())

    def on_webView_loadStarted(self):
        self.scrollValues = (False,
                             self.webView.page().mainFrame().scrollBarValue(
                                 QtCore.Qt.Horizontal),
                             self.webView.page().mainFrame().scrollBarValue(
                                 QtCore.Qt.Vertical))

    def on_webView_loadProgress(self, load):
        """Page load progress"""
        self.buttonStop.setEnabled(load != 100)

    def on_webView_selectionChanged(self):
        # print "selectionChanged"
        pass

    def on_webView_statusBarMessage(self, message):
        # print "statusBarMessage", message
        pass

    def on_webView_titleChanged(self, title):
        """Web page title changed - change the tab name"""
        #print "titleChanged", title
        #self.setWindowTitle(title)
        pass

    #=======================================================================
    # Browser Auto update for current Editor
    #=======================================================================
    def updateHtmlCurrentEditorContent(self):
        # TODO Resolver url, asegurar que sea html para no hacer cochinadas
        editor = self.currentEditor if self.currentEditor is not None else self.mainWindow.currentEditor(
        )
        content = editor.toPlainText()
        url = QtCore.QUrl.fromUserInput(editor.filePath)

        self.webView.settings().clearMemoryCaches()

        self.webView.setHtml(content, url)

    def stopTimer(self):
        self.updateTimer.stop()
        self.webView.page().setLinkDelegationPolicy(
            QtWebKit.QWebPage.DontDelegateLinks)

    def startTimer(self):
        self.updateTimer.start(self.updateInterval)
        self.webView.page().setLinkDelegationPolicy(
            QtWebKit.QWebPage.DelegateAllLinks)

    def connectCurrentEditor(self):
        self.currentEditor = self.mainWindow.currentEditor()
        self.connect(self.currentEditor, QtCore.SIGNAL("close()"),
                     self.disconnectCurrentEditor)

    def disconnectCurrentEditor(self):
        if self.currentEditor is not None:
            self.disconnect(self.currentEditor, QtCore.SIGNAL("close()"),
                            self.disconnectCurrentEditor)
            self.currentEditor = None

    @QtCore.pyqtSlot(bool)
    def on_actionSyncEditor_toggled(self, checked):
        if checked:
            #Quitar otro check
            self.actionConnectEditor.setChecked(False)
            #Desconectar editor
            self.disconnectCurrentEditor()
            self.startTimer()
        else:
            self.stopTimer()

    @QtCore.pyqtSlot(bool)
    def on_actionConnectEditor_toggled(self, checked):
        # TODO Capturar el current editor y usarlo para el update
        if checked:
            #Quitar otro check
            self.actionSyncEditor.setChecked(False)
            #Capturar editor
            self.connectCurrentEditor()
            self.startTimer()
        else:
            self.stopTimer()
            self.disconnectCurrentEditor()
예제 #4
0
class ProjectManager(QtCore.QObject, PMXBaseComponent):
    #Signals
    projectAdded = QtCore.pyqtSignal(object)
    projectRemoved = QtCore.pyqtSignal(object)
    projectClose = QtCore.pyqtSignal(object)
    projectOpen = QtCore.pyqtSignal(object)

    #Settings
    SETTINGS_GROUP = 'ProjectManager'

    workspaceDirectory = pmxConfigPorperty(default=os.path.join(
        get_home_dir(), "workspace"))  #Eclipse muejejeje
    knownProjects = pmxConfigPorperty(default=[])
    workingSets = pmxConfigPorperty(default={})

    VALID_PATH_CARACTERS = "-_.() %s%s" % (string.ascii_letters, string.digits)

    def __init__(self, application):
        QtCore.QObject.__init__(self)
        self.fileManager = application.fileManager
        self.supportManager = application.supportManager

        self.projectTreeModel = ProjectTreeModel(self)

        self.projectTreeProxyModel = ProjectTreeProxyModel(self)
        self.projectTreeProxyModel.setSourceModel(self.projectTreeModel)

        self.projectMenuProxyModel = ProjectMenuProxyModel(self)
        self.projectMenuProxyModel.setSourceModel(
            self.application.supportManager.bundleProxyModel)

        self.supportManager.bundleAdded.connect(
            self.on_supportManager_bundleAdded)
        self.supportManager.bundleRemoved.connect(
            self.on_supportManager_bundleRemoved)

    @classmethod
    def contributeToSettings(cls):
        return []

    def convertToValidPath(self, name):
        #TODO: este y el del manager de bundles pasarlos a utils
        validPath = []
        for char in unicodedata.normalize('NFKD', unicode(name)).encode(
                'ASCII', 'ignore'):
            char = char if char in self.VALID_PATH_CARACTERS else '-'
            validPath.append(char)
        return ''.join(validPath)

    def on_supportManager_bundleAdded(self, bundle):
        for project in self.getAllProjects():
            if project.namespace is not None and bundle.hasNamespace(
                    project.namespace) and not project.hasBundleMenu(bundle):
                self.addProjectBundleMenu(project, bundle)

    def on_supportManager_bundleRemoved(self, bundle):
        for project in self.getAllProjects():
            if project.namespace is not None and bundle.hasNamespace(
                    project.namespace) and project.hasBundleMenu(bundle):
                self.removeProjectBundleMenu(project, bundle)

    def loadProjects(self):
        for path in self.knownProjects[:]:
            try:
                ProjectTreeNode.loadProject(path, self)
            except exceptions.FileNotExistsException as e:
                print e
                self.knownProjects.remove(path)
                self.settings.setValue('knownProjects', self.knownProjects)

    def isOpen(self, project):
        return True

    def appendToKnowProjects(self, project):
        self.knownProjects.append(project.path())
        self.settings.setValue('knownProjects', self.knownProjects)

    def removeFromKnowProjects(self, project):
        self.knownProjects.remove(project.path())
        self.settings.setValue('knownProjects', self.knownProjects)

    #---------------------------------------------------
    # Environment
    #---------------------------------------------------
    def environmentVariables(self):
        return {}

    def supportProjectEnvironment(self, project):
        return self.supportManager.projectEnvironment(project)

    #---------------------------------------------------
    # PROJECT CRUD
    #---------------------------------------------------
    def createProject(self,
                      name,
                      directory,
                      description=None,
                      reuseDirectory=True):
        """
        Crea un proyecto nuevo lo agrega en los existentes y lo retorna,
        """
        #TODO: dejar este trabajo al file manager
        if not os.path.exists(directory):
            os.makedirs(directory)
        elif not reuseDirectory:
            raise Exception()
        project = ProjectTreeNode(directory, {
            "name": name,
            "description": description
        })
        try:
            project.save()
        except ProjectExistsException:
            rslt = QtGui.QMessageBox.information(
                None,
                _("Project already created on %s") % name,
                _("Directory %s already contains .pmxproject directory structure. "
                  "Unless you know what you are doing, Cancel and import project,"
                  " if it still fails, choose overwirte. Overwrite?") %
                directory, QtGui.QMessageBox.Cancel | QtGui.QMessageBox.Ok,
                QtGui.QMessageBox.Cancel)
            if rslt == QtGui.QMessageBox.Cancel:
                return
            try:
                project.save(overwirte=True)
            except FileException as excp:
                QtGui.QMessageBox.critical(
                    None, _("Project creation failed"),
                    _("<p>Project %s could not be created<p><pre>%s</pre>") %
                    (name, excp))
        self.addProject(project)
        self.appendToKnowProjects(project)
        return project

    def updateProject(self, project, **attrs):
        """Actualiza un proyecto"""
        if len(attrs) == 1 and "name" in attrs and attrs["name"] == item.name:
            #Updates que no son updates
            return item

        project.update(attrs)
        project.save()
        return project

    def importProject(self, directory):
        try:
            project = ProjectTreeNode.loadProject(directory, self)
        except exceptions.FileNotExistsException:
            raise exceptions.LocationIsNotProject()
        self.appendToKnowProjects(project)

    def deleteProject(self, project, removeFiles=False):
        """
        Elimina un proyecto
        """
        project.delete(removeFiles)
        self.removeProject(project)

    #---------------------------------------------------
    # PROJECT INTERFACE
    #---------------------------------------------------
    def addProject(self, project):
        project.setManager(self)
        # Todo proyecto define un namespace en el manager de support
        self.application.supportManager.addProjectNamespace(project)
        self.projectTreeModel.appendProject(project)
        self.projectAdded.emit(project)

    def modifyProject(self, project):
        pass

    def removeProject(self, project):
        self.removeFromKnowProjects(project)
        self.projectTreeModel.removeProject(project)

    def getAllProjects(self):
        #TODO: devolver un copia o no hace falta?
        return self.projectTreeModel.rootNode.childNodes()

    def openProject(self, project):
        # Cuando abro un proyecto agrego su namespace al support para aportar bundles y themes
        print project.directory

    def closeProject(self, project):
        # Cuando cierro un proyecto quito su namespace al support
        print project.directory

    def setWorkingSet(self, project, workingSet):
        projects = self.workingSets.setdefault(workingSet)
        projects.append(project.filePath)
        project.setWorkingSet(workingSet)
        self.projectTreeModel.dataChanged.emit()

    def addProjectBundleMenu(self, project, bundle):
        project.addBundleMenu(bundle)
        project.save()

    def removeProjectBundleMenu(self, project, bundle):
        project.removeBundleMenu(bundle)
        project.save()

    def findProjectForPath(self, path):
        for project in self.getAllProjects():
            if self.application.fileManager.issubpath(path, project.path()):
                return project
예제 #5
0
class PMXApplication(QtGui.QApplication):
    """The application instance.
    There can't be two apps running simultaneously, since configuration issues may occur.
    The application loads the PMX Support."""

    #=======================================================================
    # Settings
    #=======================================================================
    SETTINGS_GROUP = "Global"

    @pmxConfigPorperty(default=resources.APPLICATION_STYLE)
    def styleSheet(self, style):
        self.setStyleSheet(style)

    askAboutExternalActions = pmxConfigPorperty(default=False)

    RESTART_CODE = 1000

    def __init__(self):
        """Inicialización de la aplicación."""
        #TODO: Pasar los argumentos a la QApplication
        QtGui.QApplication.__init__(self, [])

        # Some init's
        self.setApplicationName(prymatex.__name__.title())
        self.setApplicationVersion(prymatex.__version__)
        self.setOrganizationDomain(prymatex.__url__)
        self.setOrganizationName(prymatex.__author__)
        self.platform = sys.platform

        resources.loadPrymatexResources(PMXProfile.PMX_SHARE_PATH)

        #Connects
        self.aboutToQuit.connect(self.closePrymatex)

    def installTranslator(self):
        pass
        #slanguage = QtCore.QLocale.system().name()
        #print language
        #self.translator = QtCore.QTranslator()
        #print os.path.join(PMXProfile.PMX_SHARE_PATH, "Languages")

        #self.translator.load(settings.LANGUAGE)
        #self.installTranslator(translator)

    def buildSplashScreen(self):
        from prymatex.widgets.splash import SplashScreen
        splash_image = resources.getImage('newsplash')
        splash = SplashScreen(splash_image)
        splash.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint
                              | QtCore.Qt.SplashScreen)

        splashFont = QtGui.QFont("Monospace", 11)
        splashFont.setStyleStrategy(QtGui.QFont.PreferAntialias)

        splash.setFont(splashFont)
        splash.setMask(splash_image.mask())

        return splash

    def loadGraphicalUserInterface(self):
        splash = self.buildSplashScreen()
        splash.show()
        try:
            self.cacheManager = self.setupCacheManager()  #Cache system Manager
            self.pluginManager = self.setupPluginManager(
            )  #Prepare plugin manager

            #TODO: Cambiar los setup por build, que retornen los manager
            # Loads
            self.supportManager = self.setupSupportManager()  #Support Manager
            self.fileManager = self.setupFileManager()  #File Manager
            self.projectManager = self.setupProjectManager()  #Project Manager
            self.kernelManager = self.setupKernelManager(
            )  #Console kernel Manager
            self.setupCoroutines()
            self.setupZeroMQContext()
            self.setupMainWindow()
            self.setupServer()

            # Setup Dialogs
            self.setupDialogs()

            #Connect all loads
            self.projectManager.loadProjects()

            self.supportManager.loadSupport(splash.showMessage)
            self.settingsDialog.loadSettings()

            # Creates the Main Window
            self.createMainWindow()

            splash.finish(self.mainWindow)

        except KeyboardInterrupt:
            self.logger.critical(
                "\nQuit signal catched during application startup. Quiting...")
            self.quit()

    def unloadGraphicalUserInterface(self):
        #TODO: ver como dejar todo lindo y ordenado para terminar correctamente
        #if self.zmqContext is not None:
        #    self.zmqContext.destroy()
        self.mainWindow.close()
        del self.mainWindow

    def resetSettings(self):
        self.profile.clear()

    def switchProfile(self):
        from prymatex.gui.dialogs.profile import PMXProfileDialog
        profile = PMXProfileDialog.switchProfile(PMXProfile.PMX_PROFILES_FILE)
        if profile is not None and profile != self.profile.PMX_PROFILE_NAME:
            self.restart()

    def restart(self):
        self.exit(self.RESTART_CODE)

    def buildSettings(self, profile):
        # TODO Cambiar este metodo a buildProfile
        if profile is None or (profile == ""
                               and not PMXProfile.PMX_PROFILES_DONTASK):
            #Select profile
            from prymatex.gui.dialogs.profile import PMXProfileDialog
            profile = PMXProfileDialog.selectProfile(
                PMXProfile.PMX_PROFILES_FILE)
        elif profile == "":
            #Find default profile in config
            profile = PMXProfile.PMX_PROFILE_DEFAULT

        #Settings
        from prymatex.gui.dialogs.settings import PMXSettingsDialog
        if not profile:
            raise ValueError("Invalid Profile")
        self.profile = PMXProfile(profile)

        # Configure application
        self.profile.registerConfigurable(self.__class__)
        self.profile.configure(self)

        # TODO Este dialogo no va mas aca
        self.settingsDialog = PMXSettingsDialog(self)

    def checkSingleInstance(self):
        """
        Checks if there's another instance using current profile
        """
        self.fileLock = os.path.join(self.profile.PMX_PROFILE_PATH,
                                     'prymatex.pid')

        if os.path.exists(self.fileLock):
            #Mejorar esto
            pass
            #raise exceptions.AlreadyRunningError('%s seems to be runnig. Please close the instance or run other profile.' % (self.profile.PMX_PROFILE_NAME))
        else:
            f = open(self.fileLock, 'w')
            f.write('%s' % self.applicationPid())
            f.close()

    #========================================================
    # Logging system and loggers
    #========================================================
    def getLogger(self, name):
        """ return logger, for filter by name in future """
        return logging.getLogger(name)

    def setupLogging(self, verbose, namePattern):
        from datetime import datetime

        level = [
            logging.CRITICAL, logging.ERROR, logging.WARNING, logging.INFO,
            logging.DEBUG
        ][verbose % 5]

        # File name
        filename = os.path.join(
            self.profile.PMX_LOG_PATH, '%s-%s.log' %
            (logging.getLevelName(level), datetime.now().strftime('%d-%m-%Y')))
        logging.basicConfig(filename=filename, level=level)

        # Console handler
        ch = logging.StreamHandler()
        formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
        ch.setFormatter(formatter)
        ch.setLevel(level)

        if namePattern:
            #Solo al de consola
            ch.addFilter(NameFilter(namePattern))

        logging.root.addHandler(ch)
        logging.root.info("Application startup")
        logging.root.debug("Application startup debug")

        self.logger = logging.root

        # Route Qt output
        Qt.qInstallMsgHandler(self.qtMessageHandler)

    def qtMessageHandler(self, msgType, msgString):
        ''' Route Qt messaging system into Prymatex/Python one'''
        if msgType == Qt.QtDebugMsg:
            self.logger.debug(msgString)
        elif msgType == Qt.QtWarningMsg:
            self.logger.warn(msgString)
        elif msgType == Qt.QtCriticalMsg:
            self.logger.critical(msgString)
        elif msgType == Qt.QtFatalMsg:
            self.logger.fatal(msgString)
        elif msgType == Qt.QtSystemMsg:
            self.logger.debug("System: %s" % msgString)

    #========================================================
    # Managers
    #========================================================
    @logtime
    def setupSupportManager(self):
        from prymatex.managers.support import SupportManager

        self.populateComponent(SupportManager)

        manager = SupportManager(self)
        self.profile.configure(manager)

        #Prepare prymatex namespace
        sharePath = self.profile.value('PMX_SHARE_PATH')
        manager.addNamespace('prymatex', sharePath)
        manager.updateEnvironment({  #TextMate Compatible :P
            'TM_APP_PATH':
            self.profile.value('PMX_APP_PATH'),
            'TM_SUPPORT_PATH':
            manager.environment['PMX_SUPPORT_PATH'],
            'TM_BUNDLES_PATH':
            manager.environment['PMX_BUNDLES_PATH'],
            'TM_THEMES_PATH':
            manager.environment['PMX_THEMES_PATH'],
            'TM_PID':
            self.applicationPid(),
            #Prymatex
            'PMX_APP_NAME':
            self.applicationName().title(),
            'PMX_APP_PATH':
            self.profile.value('PMX_APP_PATH'),
            'PMX_PREFERENCES_PATH':
            self.profile.value('PMX_PREFERENCES_PATH'),
            'PMX_VERSION':
            self.applicationVersion(),
            'PMX_PID':
            self.applicationPid()
        })

        #Prepare user namespace
        homePath = self.profile.value('PMX_HOME_PATH')
        manager.addNamespace('user', homePath)
        manager.updateEnvironment({
            'PMX_HOME_PATH':
            homePath,
            'PMX_PROFILE_PATH':
            self.profile.value('PMX_PROFILE_PATH'),
            'PMX_TMP_PATH':
            self.profile.value('PMX_TMP_PATH'),
            'PMX_LOG_PATH':
            self.profile.value('PMX_LOG_PATH')
        })
        return manager

    def setupFileManager(self):
        from prymatex.managers.files import FileManager

        self.populateComponent(FileManager)

        manager = FileManager(self)
        self.profile.configure(manager)

        manager.filesytemChange.connect(self.on_filesytemChange)
        return manager

    def setupProjectManager(self):
        from prymatex.managers.projects import ProjectManager

        self.populateComponent(ProjectManager)

        manager = ProjectManager(self)
        self.profile.configure(manager)
        return manager

    def setupKernelManager(self):
        kernelManager = None
        try:
            from IPython.frontend.qt.kernelmanager import QtKernelManager
            kernelManager = QtKernelManager()
            kernelManager.start_kernel()
            kernelManager.start_channels()
            if hasattr(kernelManager, "connection_file"):
                ipconnection = kernelManager.connection_file
            else:
                shell_port = kernelManager.shell_address[1]
                iopub_port = kernelManager.sub_address[1]
                stdin_port = kernelManager.stdin_address[1]
                hb_port = kernelManager.hb_address[1]
                ipconnection = "--shell={0} --iopub={1} --stdin={2} --hb={3}".format(
                    shell_port, iopub_port, stdin_port, hb_port)
            self.supportManager.updateEnvironment(
                {"PMX_IPYTHON_CONNECTION": ipconnection})
        except ImportError as e:
            self.logger.warn("Warning: %s" % e)
            kernelManager = None
        return kernelManager

    def setupCacheManager(self):
        from prymatex.managers.cache import CacheManager
        return CacheManager()

    def setupPluginManager(self):
        from prymatex.managers.plugins import PluginManager

        self.populateComponent(PluginManager)

        pluginManager = PluginManager(self)
        self.profile.configure(pluginManager)

        # TODO: Ruta de los plugins ver de aprovechar settings quiza esta sea una ruta base solamente
        pluginManager.addPluginDirectory(
            self.profile.value('PMX_PLUGINS_PATH'))

        pluginManager.loadPlugins()
        return pluginManager

    def setupCoroutines(self):
        self.scheduler = coroutines.Scheduler(self)

    def setupZeroMQContext(self):
        try:
            from prymatex.utils import zeromqt
            self.zmqContext = zeromqt.ZeroMQTContext(parent=self)
        except ImportError as e:
            self.logger.warn("Warning: %s" % e)
            self.zmqContext = None

    def setupMainWindow(self):
        from prymatex.gui.mainwindow import PMXMainWindow
        self.populateComponent(PMXMainWindow)

    def setupServer(self):
        #Seria mejor que esto no falle pero bueno tengo que preguntar por none
        if self.zmqContext is not None:
            from prymatex.core.server import PrymatexServer
            self.populateComponent(PrymatexServer)
            self.server = PrymatexServer(self)

    #========================================================
    # Dialogs
    #========================================================
    def setupDialogs(self):
        # TODO: Creo que esto del bundle editor global asi no va a caminar muy bien
        #Bundle Editor
        from prymatex.gui.support.bundleeditor import PMXBundleEditor
        self.populateComponent(PMXBundleEditor)

        self.bundleEditor = PMXBundleEditor(self)
        #self.bundleEditor.setModal(True)

    def closePrymatex(self):
        self.logger.debug("Close")

        self.profile.saveState(self.mainWindow)
        os.unlink(self.fileLock)

    def commitData(self, manager):
        print "Commit data"

    def saveState(self, manager):
        print "saveState"
        pass

    #========================================================
    # Components
    #========================================================
    def extendComponent(self, componentClass):
        componentClass.application = self
        componentClass.logger = self.getLogger('.'.join(
            [componentClass.__module__, componentClass.__name__]))

    def populateComponent(self, componentClass):
        self.extendComponent(componentClass)
        self.profile.registerConfigurable(componentClass)
        for settingClass in componentClass.contributeToSettings():
            try:
                self.extendComponent(settingClass)
                self.settingsDialog.register(
                    settingClass(componentClass.settings))
            except (RuntimeError, ImportError):
                # TODO: Inform user but dont' prevent pmx from starting
                pass

    def createWidgetInstance(self, widgetClass, parent):
        # TODO Que parent sea opcional y pueda ser la mainWindow si no viene seteado
        return self.pluginManager.createWidgetInstance(widgetClass, parent)

    #========================================================
    # Create Zmq Sockets
    #========================================================
    def zmqSocket(self, socketType, name, interface='tcp://127.0.0.1'):
        # TODO ver la variable aca
        socket = self.zmqContext.socket(socketType)
        port = socket.bind_to_random_port(interface)
        self.supportManager.addToEnvironment("PMX_" + name.upper() + "_PORT",
                                             port)
        return socket

    #========================================================
    # Editors and mainWindow handle
    #========================================================
    def createEditorInstance(self, filePath=None, parent=None):
        editorClass = filePath is not None and self.pluginManager.findEditorClassForFile(
            filePath) or self.pluginManager.defaultEditor()

        if editorClass is not None:
            return self.createWidgetInstance(editorClass, parent)

    def createMainWindow(self):
        """Creates the windows"""
        from prymatex.gui.mainwindow import PMXMainWindow

        #TODO: Testeame con mas de una
        for _ in range(1):
            self.mainWindow = PMXMainWindow(self)

            #Configure and add dockers
            self.pluginManager.populateMainWindow(self.mainWindow)
            self.profile.configure(self.mainWindow)
            self.mainWindow.show()
            self.profile.restoreState(self.mainWindow)

            if not self.mainWindow.editors():
                self.mainWindow.addEmptyEditor()

    def showMessage(self, message):
        #Si tengo mainwindow vamos por este camino, sino hacerlo llegar de otra forma
        self.mainWindow.showMessage(message)

    def currentEditor(self):
        return self.mainWindow.currentEditor()

    def findEditorForFile(self, filePath):
        #Para cada mainwindow buscar el editor
        return self.mainWindow, self.mainWindow.findEditorForFile(filePath)

    def canBeHandled(self, filePath):
        #from prymatex.utils.pyqtdebug import ipdb_set_trace
        #ipdb_set_trace()
        ext = os.path.splitext(filePath)[1].replace('.', '')
        for fileTypes in [
                syntax.item.fileTypes
                for syntax in self.supportManager.getAllSyntaxes()
                if hasattr(syntax.item, 'fileTypes') and syntax.item.fileTypes
        ]:

            if ext in fileTypes:
                return True
        return False

    def openFile(self,
                 filePath,
                 cursorPosition=None,
                 focus=True,
                 mainWindow=None,
                 useTasks=True):
        """Open a editor in current window"""
        filePath = self.fileManager.normcase(filePath)

        if self.fileManager.isOpen(filePath):
            mainWindow, editor = self.findEditorForFile(filePath)
            if editor is not None:
                mainWindow.setCurrentEditor(editor)
                if isinstance(cursorPosition, tuple):
                    editor.setCursorPosition(cursorPosition)
        elif self.fileManager.exists(filePath):
            mainWindow = mainWindow or self.mainWindow
            editor = self.createEditorInstance(filePath, mainWindow)

            def on_editorReady(mainWindow, editor, cursorPosition, focus):
                def editorReady(openResult):
                    if isinstance(cursorPosition, tuple):
                        editor.setCursorPosition(cursorPosition)
                    mainWindow.tryCloseEmptyEditor()
                    mainWindow.addEditor(editor, focus)

                return editorReady

            if useTasks and inspect.isgeneratorfunction(editor.open):
                task = self.scheduler.newTask(editor.open(filePath))
                task.done.connect(
                    on_editorReady(mainWindow, editor, cursorPosition, focus))
            elif inspect.isgeneratorfunction(editor.open):
                on_editorReady(mainWindow, editor, cursorPosition,
                               focus)(list(editor.open(filePath)))
            else:
                on_editorReady(mainWindow, editor, cursorPosition,
                               focus)(editor.open(filePath))

    def openDirectory(self, directoryPath):
        raise NotImplementedError(
            "Directory contents should be opened as files here")

    def handleUrlCommand(self, url):
        if isinstance(url, basestring):
            url = QtCore.QUrl(url)
        if url.scheme() == "txmt":
            #TODO: Controlar que sea un open
            sourceFile = url.queryItemValue('url')
            position = (0, 0)
            line = url.queryItemValue('line')
            if line:
                position = (int(line) - 1, position[1])
            column = url.queryItemValue('column')
            if column:
                position = (position[0], int(column) - 1)
            if sourceFile:
                filePath = QtCore.QUrl(sourceFile,
                                       QtCore.QUrl.TolerantMode).toLocalFile()
                self.openFile(filePath, position)
            else:
                self.currentEditor().setCursorPosition(position)

    def openArgumentFiles(self, args):
        for filePath in filter(lambda f: os.path.exists(f), args):
            if os.path.isfile(filePath):
                self.openFile(filePath)
            else:
                self.openDirectory(filePath)

    def checkExternalAction(self, mainWindow, editor):
        if editor.isExternalChanged():
            message = "The file '%s' has been changed on the file system, Do you want to replace the editor contents with these changes?"
            result = QtGui.QMessageBox.question(
                editor,
                _("File changed"),
                _(message) % editor.filePath,
                buttons=QtGui.QMessageBox.Yes | QtGui.QMessageBox.No,
                defaultButton=QtGui.QMessageBox.Yes
            ) if self.askAboutExternalActions else QtGui.QMessageBox.Yes
            if result == QtGui.QMessageBox.Yes:
                if inspect.isgeneratorfunction(editor.reload):
                    task = self.scheduler.newTask(editor.reload())
                else:
                    editor.reload()
            elif result == QtGui.QMessageBox.No:
                pass
        elif editor.isExternalDeleted():
            message = "The file '%s' has been deleted or is not accessible. Do you want to save your changes or close the editor without saving?"
            result = QtGui.QMessageBox.question(
                editor,
                _("File deleted"),
                _(message) % editor.filePath,
                buttons=QtGui.QMessageBox.Save | QtGui.QMessageBox.Close,
                defaultButton=QtGui.QMessageBox.Close
            ) if self.askAboutExternalActions else QtGui.QMessageBox.Close
            if result == QtGui.QMessageBox.Close:
                mainWindow.closeEditor(editor)
            elif result == QtGui.QMessageBox.Save:
                mainWindow.saveEditor(editor)

    def on_filesytemChange(self, filePath, change):
        mainWindow, editor = self.findEditorForFile(filePath)
        editor.setExternalAction(change)
        if mainWindow.currentEditor() == editor:
            self.checkExternalAction(mainWindow, editor)

    #---------------------------------------------------
    # Exceptions, Print exceptions in a window
    #---------------------------------------------------
    def replaceSysExceptHook(self):
        def displayExceptionDialog(exctype, value, traceback):
            ''' Display a nice dialog showing the python traceback'''
            from prymatex.gui.emergency.tracedialog import PMXTraceBackDialog
            sys.__excepthook__(exctype, value, traceback)
            PMXTraceBackDialog.fromSysExceptHook(exctype, value,
                                                 traceback).exec_()

        sys.excepthook = displayExceptionDialog

    def __str__(self):
        return '<PMXApplication at {} PID: {}>'.format(hash(self), os.getpid())

    __unicode__ = __repr__ = __str__
예제 #6
0
class FileManager(QtCore.QObject, PMXBaseComponent):
    """A File Manager"""
    #=========================================================
    # Signals
    #=========================================================
    fileCreated = QtCore.pyqtSignal(str)
    fileDeleted = QtCore.pyqtSignal(str)
    fileChanged = QtCore.pyqtSignal(str)
    fileRenamed = QtCore.pyqtSignal(str, str)
    directoryCreated = QtCore.pyqtSignal(str)
    directoryDeleted = QtCore.pyqtSignal(str)
    directoryChanged = QtCore.pyqtSignal(str)
    directoryRenamed = QtCore.pyqtSignal(str, str)

    # Generic Signal
    filesytemChange = QtCore.pyqtSignal(str, int)

    #=========================================================
    # Settings
    #=========================================================
    SETTINGS_GROUP = 'FileManager'

    fileHistory = pmxConfigPorperty(default=[])
    fileHistoryLength = pmxConfigPorperty(default=10)
    lineEnding = pmxConfigPorperty(default='unix')
    encoding = pmxConfigPorperty(default='utf-8')

    #=========================================================
    # Constants
    #=========================================================
    CREATED = 1 << 0
    DELETED = 1 << 1
    RENAMED = 1 << 2
    MOVED = 1 << 3
    CHANGED = 1 << 4

    ENCODINGS = ['windows-1253', 'iso-8859-7', 'macgreek']

    def __init__(self, application):
        QtCore.QObject.__init__(self)

        self.last_directory = get_home_dir()
        self.fileWatcher = QtCore.QFileSystemWatcher()
        self.fileWatcher.fileChanged.connect(self.on_fileChanged)
        self.fileWatcher.directoryChanged.connect(self.on_directoryChanged)
        self.connectGenericSignal()

    @classmethod
    def contributeToSettings(cls):
        return []

    #========================================================
    # Signals
    #========================================================
    def connectGenericSignal(self):
        UNARY_SINGAL_CONSTANT_MAP = (
            (self.fileCreated, FileManager.CREATED),
            (self.fileDeleted, FileManager.DELETED),
            (self.fileChanged, FileManager.CHANGED),
            (self.directoryCreated, FileManager.CREATED),
            (self.directoryDeleted, FileManager.DELETED),
            (self.directoryChanged, FileManager.CHANGED),
        )
        BINARY_SINGAL_CONSTANT_MAP = (
            (self.fileRenamed, FileManager.RENAMED),
            (self.directoryRenamed, FileManager.RENAMED),
        )
        for signal, associatedConstant in UNARY_SINGAL_CONSTANT_MAP:
            signal.connect(lambda path, constant=associatedConstant: self.
                           filesytemChange.emit(path, constant))
        for signal, associatedConstant in BINARY_SINGAL_CONSTANT_MAP:
            signal.connect(lambda _x, path, constant=associatedConstant: self.
                           filesytemChange.emit(path, constant))

    def on_fileChanged(self, filePath):
        if not os.path.exists(filePath):
            self.fileDeleted.emit(filePath)
        else:
            self.fileChanged.emit(filePath)

    def on_directoryChanged(self, directoryPath):
        if not os.path.exists(directoryPath):
            self.directoryDeleted.emit(directoryPath)
        else:
            self.directoryChanged.emit(directoryPath)

    #========================================================
    # History
    #========================================================
    def add_file_history(self, filePath):
        if filePath in self.fileHistory:
            self.fileHistory.remove(filePath)
        self.fileHistory.insert(0, filePath)
        if len(self.fileHistory) > self.fileHistoryLength:
            self.fileHistory = self.fileHistory[0:self.fileHistoryLength]
        self.settings.setValue("fileHistory", self.fileHistory)

    def clearFileHistory(self):
        self.fileHistory = []
        self.settings.setValue("fileHistory", self.fileHistory)

    #========================================================
    # Path handling, create, move, copy, link, delete
    #========================================================
    def _onerror(func, path, exc_info):  #@NoSelf
        import stat
        if not os.access(path, os.W_OK):
            # Is the error an access error ?
            os.chmod(path, stat.S_IWUSR)
            func(path)
        else:
            raise

    def createDirectory(self, directory):
        """
        Create a new directory.
        """
        if os.path.exists(directory):
            raise exceptions.FileExistsException("The directory already exist",
                                                 directory)
        os.makedirs(directory)

    def createFile(self, filePath):
        """Create a new file."""
        if os.path.exists(filePath):
            raise exceptions.IOException("The file already exist")
        open(filePath, 'w').close()

    move = lambda self, src, dst: shutil.move(src, dst)
    copytree = lambda self, src, dst: shutil.copytree(src, dst)
    copy = lambda self, src, dst: shutil.copy2(src, dst)
    link = lambda self, src, dst: dst

    def deletePath(self, path):
        if os.path.isfile(path):
            # Mandar señal para cerrar editores
            os.unlink(path)
        else:
            shutil.rmtree(path, onerror=self._onerror)

    #==================================================================
    # Path data
    #==================================================================
    exists = lambda self, path: os.path.exists(path)
    isdir = lambda self, path: os.path.isdir(path)
    isfile = lambda self, path: os.path.isfile(path)
    join = lambda self, *path: os.path.join(*path)
    extension = lambda self, path: os.path.splitext(path.lower())[-1][1:]
    splitext = lambda self, path: os.path.splitext(path)
    dirname = lambda self, path: os.path.dirname(path)
    basename = lambda self, path: os.path.basename(path)
    mimeType = lambda self, path: mimetypes.guess_type(path)[0] or ""
    issubpath = lambda self, childPath, parentPath: osextra.path.issubpath(
        childPath, parentPath)
    fullsplit = lambda self, path: osextra.path.fullsplit(path)
    normcase = lambda self, path: os.path.normcase(path)
    normpath = lambda self, path: os.path.normpath(path)
    realpath = lambda self, path: os.path.realpath(path)
    relpath = lambda self, path: os.path.relpath(path)

    def expandVars(self, text):
        context = self.application.supportManager.buildEnvironment()
        path = osextra.path.expand_shell_var(text, context=context)
        if os.path.exists(path):
            return path

    #==================================================================
    # Handling files for retrieving data. open, read, write, close
    #==================================================================
    def isOpen(self, filePath):
        return filePath in self.fileWatcher.files()

    def openFile(self, filePath):
        """
        Open and read a file, return the content.
        """
        if not os.path.exists(filePath):
            raise exceptions.IOException("The file does not exist: %s" %
                                         filePath)
        if not os.path.isfile(filePath):
            raise exceptions.IOException("%s is not a file" % filePath)
        self.last_directory = os.path.dirname(filePath)
        #Update file history
        self.add_file_history(filePath)
        self.watchPath(filePath)

    def readFile(self, filePath):
        """Read from file"""
        #TODO: Que se pueda hacer una rutina usando yield
        for enc in [self.encoding] + self.ENCODINGS:
            try:
                fileRead = codecs.open(filePath, "r", encoding=enc)
                content = fileRead.read()
                break
            except Exception, e:
                print "File: %s, %s" % (filePath, e)
        fileRead.close()
        return content