Beispiel #1
0
class _FileWatcher(QObject):
    """File watcher.

    QFileSystemWatcher notifies client about any change (file access mode, modification date, etc.)
    But, we need signal, only after file contents had been changed
    """
    modified = pyqtSignal(bool)
    removed = pyqtSignal(bool)

    def __init__(self, path):
        QObject.__init__(self)
        self._contents = None
        self._watcher = QFileSystemWatcher()
        self._timer = None
        self._path = path

        self._lastEmittedModifiedStatus = None
        self._lastEmittedRemovedStatus = None

        self.setPath(path)
        self.enable()


    def __del__(self):
        self._stopTimer()

    def enable(self):
        """Enable signals from the watcher
        """
        self._watcher.fileChanged.connect(self._onFileChanged)

    def disable(self):
        """Disable signals from the watcher
        """
        self._watcher.fileChanged.disconnect(self._onFileChanged)
        self._stopTimer()

    def setContents(self, contents):
        """Set file contents. Watcher uses it to compare old and new contents of the file.
        """
        self._contents = contents
        # Qt File watcher may work incorrectly, if file was not existing, when it started
        if not self._watcher.files():
            self.setPath(self._path)
        self._lastEmittedModifiedStatus = None
        self._lastEmittedRemovedStatus = None

    def setPath(self, path):
        """Path had been changed or file had been created. Set new path
        """
        if self._watcher.files():
            self._watcher.removePaths(self._watcher.files())
        if path is not None and os.path.isfile(path):
            self._watcher.addPath(path)
        self._path = path
        self._lastEmittedModifiedStatus = None
        self._lastEmittedRemovedStatus = None

    def _emitModifiedStatus(self):
        """Emit self.modified signal with right status
        """
        isModified = self._contents != self._safeRead(self._path)
        if isModified != self._lastEmittedModifiedStatus:
            self.modified.emit(isModified)
            self._lastEmittedModifiedStatus = isModified

    def _emitRemovedStatus(self, isRemoved):
        """Emit 'removed', if status changed"""
        if isRemoved != self._lastEmittedRemovedStatus:
            self._lastEmittedRemovedStatus = isRemoved
            self.removed.emit(isRemoved)

    def _onFileChanged(self):
        """File changed. Emit own signal, if contents changed
        """
        if os.path.exists(self._path):
            self._emitModifiedStatus()
        else:
            self._emitRemovedStatus(True)

        # Sometimes QFileSystemWatcher emits only 1 signal for 2 modifications
        # Check once more later
        self._startTimer()

    def _startTimer(self):
        """Init a timer.
        It is used for monitoring file after deletion.
        Git removes file, than restores it.
        """
        if self._timer is None:
            self._timer = QTimer()
            self._timer.setInterval(500)
            self._timer.timeout.connect(self._onCheckIfDeletedTimer)
        self._timer.start()

    def _stopTimer(self):
        """Stop timer, if exists
        """
        if self._timer is not None:
            self._timer.stop()

    def _onCheckIfDeletedTimer(self):
        """Check, if file has been restored
        """
        if os.path.exists(self._path):
            self.setPath(self._path)  # restart Qt file watcher after file has been restored
            self._stopTimer()
            self._emitRemovedStatus(False)
            self._emitModifiedStatus()

    def _safeRead(self, path):
        """Read file. Ignore exceptions
        """
        try:
            with open(path, 'rb') as file:
                return file.read()
        except (OSError, IOError):
            return None
Beispiel #2
0
class ProjectWidget(Ui_Form, QWidget):
    SampleWidgetRole = Qt.UserRole + 1
    projectsaved = pyqtSignal()
    projectupdated = pyqtSignal(object)
    projectloaded = pyqtSignal(object)
    selectlayersupdated = pyqtSignal(list)

    def __init__(self, parent=None):
        super(ProjectWidget, self).__init__(parent)
        self.setupUi(self)
        self.project = None
        self.bar = None
        self.roamapp = None

        menu = QMenu()

        # self.roamVersionLabel.setText("You are running IntraMaps Roam version {}".format(roam.__version__))

        self.openProjectFolderButton.pressed.connect(self.openprojectfolder)
        self.openinQGISButton.pressed.connect(self.openinqgis)
        self.depolyProjectButton.pressed.connect(self.deploy_project)
        self.depolyInstallProjectButton.pressed.connect(
            functools.partial(self.deploy_project, True))

        self.filewatcher = QFileSystemWatcher()
        self.filewatcher.fileChanged.connect(self.qgisprojectupdated)

        self.projectupdatedlabel.linkActivated.connect(self.reloadproject)
        self.projectupdatedlabel.hide()

        # self.setpage(4)
        self.currentnode = None
        self.form = None

        qgislocation = r'C:\OSGeo4W\bin\qgis.bat'
        qgislocation = roam.config.settings.setdefault('configmanager', {}) \
            .setdefault('qgislocation', qgislocation)

        self.qgispathEdit.setText(qgislocation)
        self.qgispathEdit.textChanged.connect(self.save_qgis_path)
        self.filePickerButton.pressed.connect(self.set_qgis_path)

        self.connect_page_events()

    def connect_page_events(self):
        """
        Connect the events from all the pages back to here
        """
        for index in range(self.stackedWidget.count()):
            widget = self.stackedWidget.widget(index)
            if hasattr(widget, "raiseMessage"):
                widget.raiseMessage.connect(self.bar.pushMessage)

    def set_qgis_path(self):
        """
        Set the location of the QGIS install.  We need the path to be able to create Roam
        projects
        """
        path = QFileDialog.getOpenFileName(self,
                                           "Select QGIS install file",
                                           filter="(*.bat)")
        if not path:
            return
        self.qgispathEdit.setText(path)
        self.save_qgis_path(path)

    def save_qgis_path(self, path):
        """
        Save the QGIS path back to the Roam config.
        """
        roam.config.settings['configmanager'] = {'qgislocation': path}
        roam.config.save()

    def setpage(self, page, node):
        """
        Set the current page in the config manager.  We pass the project into the current
        page so that it knows what the project is.
        """
        self.currentnode = node

        self.write_config_currentwidget()

        self.stackedWidget.setCurrentIndex(page)

        widget = self.stackedWidget.currentWidget()
        if hasattr(widget, "set_project"):
            widget.set_project(self.project, self.currentnode)

    def write_config_currentwidget(self):
        """
        Call the write config command on the current widget.
        """
        widget = self.stackedWidget.currentWidget()
        if hasattr(widget, "write_config"):
            widget.write_config()

    def deploy_project(self, with_data=False):
        """
        Run the step to deploy a project. Projects are deplyed as a bundled zip of the project folder.
        """
        if self.roamapp.sourcerun:
            base = os.path.join(self.roamapp.apppath, "..")
        else:
            base = self.roamapp.apppath

        default = os.path.join(base, "roam_serv")
        path = roam.config.settings.get("publish", {}).get("path", '')
        if not path:
            path = default

        path = os.path.join(path, "projects")

        if not os.path.exists(path):
            os.makedirs(path)

        self._saveproject()
        options = {}

        bundle.bundle_project(self.project,
                              path,
                              options,
                              as_install=with_data)

    def setaboutinfo(self):
        """
        Set the current about info on the widget
        """
        self.versionLabel.setText(roam.__version__)
        self.qgisapiLabel.setText(unicode(QGis.QGIS_VERSION))

    def selectlayerschanged(self, *args):
        """
        Run the updates when the selection layers have changed
        """
        self.formlayers.setSelectLayers(self.project.selectlayers)
        self.selectlayersupdated.emit(self.project.selectlayers)

    def reloadproject(self, *args):
        """
        Reload the project. At the moment this will drop any unsaved changes to the config.
        Note: Should look at making sure it doesn't do that because it's not really needed.
        """
        self.projectupdated.emit(self.project)
        # self.setproject(self.project)

    def qgisprojectupdated(self, path):
        """
        Show a message when the QGIS project file has been updated.
        """
        self.projectupdatedlabel.show()
        self.projectupdatedlabel.setText(
            "The QGIS project has been updated. <a href='reload'> "
            "Click to reload</a>. <b style=\"color:red\">Unsaved data will be lost</b>"
        )

    def openinqgis(self):
        """
        Open a QGIS session for the user to config the project layers.
        """
        try:
            openqgis(self.project.projectfile)
        except OSError:
            self.bar.pushMessage("Looks like I couldn't find QGIS",
                                 "Check qgislocation in roam.config",
                                 QgsMessageBar.WARNING)

    def openprojectfolder(self):
        """
        Open the project folder in the file manager for the OS.
        """
        folder = self.project.folder
        openfolder(folder)

    def setproject(self, project, loadqgis=True):
        """
        Set the widgets active project.
        """
        self.filewatcher.removePaths(self.filewatcher.files())
        self.projectupdatedlabel.hide()
        self._closeqgisproject()

        if project.valid:
            self.startsettings = copy.deepcopy(project.settings)
            self.project = project
            self.projectlabel.setText(project.name)
            self.loadqgisproject(project, self.project.projectfile)
            self.filewatcher.addPath(self.project.projectfile)
            self.projectloaded.emit(self.project)

    def loadqgisproject(self, project, projectfile):
        print("Load QGIS Project!!")
        QDir.setCurrent(os.path.dirname(project.projectfile))
        fileinfo = QFileInfo(project.projectfile)
        # No idea why we have to set this each time.  Maybe QGIS deletes it for
        # some reason.
        self.badLayerHandler = BadLayerHandler(callback=self.missing_layers)
        QgsProject.instance().setBadLayerHandler(self.badLayerHandler)
        QgsProject.instance().read(fileinfo)

    def missing_layers(self, missinglayers):
        """
        Handle any and show any missing layers.
        """
        self.project.missing_layers = missinglayers

    def _closeqgisproject(self):
        """
        Close the current QGIS project and clean up after..
        """
        QGIS.close_project()

    def _saveproject(self):
        """
        Save the project config to disk.
        """
        self.write_config_currentwidget()
        # self.project.dump_settings()
        self.project.save(update_version=True)
        self.filewatcher.removePaths(self.filewatcher.files())
        QgsProject.instance().write()
        self.filewatcher.addPath(self.project.projectfile)
        self.projectsaved.emit()
Beispiel #3
0
class ProjectWidget(Ui_Form, QWidget):
    SampleWidgetRole = Qt.UserRole + 1
    projectsaved = pyqtSignal()
    projectupdated = pyqtSignal()
    projectloaded = pyqtSignal(object)
    selectlayersupdated = pyqtSignal(list)

    def __init__(self, parent=None):
        super(ProjectWidget, self).__init__(parent)
        self.setupUi(self)
        self.project = None
        self.mapisloaded = False
        self.bar = None
        self.roamapp = None

        menu = QMenu()
        self.canvas.setCanvasColor(Qt.white)

        self.canvas.enableAntiAliasing(True)
        self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor)
        self.canvas.mapRenderer().setLabelingEngine(QgsPalLabeling())

        # self.roamVersionLabel.setText("You are running IntraMaps Roam version {}".format(roam.__version__))

        self.openProjectFolderButton.pressed.connect(self.openprojectfolder)
        self.openinQGISButton.pressed.connect(self.openinqgis)
        self.depolyProjectButton.pressed.connect(self.deploy_project)
        self.depolyInstallProjectButton.pressed.connect(
            functools.partial(self.deploy_project, True))

        self.filewatcher = QFileSystemWatcher()
        self.filewatcher.fileChanged.connect(self.qgisprojectupdated)

        self.projectupdatedlabel.linkActivated.connect(self.reloadproject)
        self.projectupdatedlabel.hide()

        # self.setpage(4)
        self.currentnode = None
        self.form = None

        qgislocation = r'C:\OSGeo4W\bin\qgis.bat'
        qgislocation = roam.config.settings.setdefault('configmanager', {}) \
            .setdefault('qgislocation', qgislocation)

        self.qgispathEdit.setText(qgislocation)
        self.qgispathEdit.textChanged.connect(self.save_qgis_path)
        self.filePickerButton.pressed.connect(self.set_qgis_path)

    def set_qgis_path(self):
        path = QFileDialog.getOpenFileName(self,
                                           "Select QGIS install file",
                                           filter="(*.bat)")
        if not path:
            return
        self.qgispathEdit.setText(path)
        self.save_qgis_path(path)

    def save_qgis_path(self, path):
        roam.config.settings['configmanager'] = {'qgislocation': path}
        roam.config.save()

    def setpage(self, page, node):
        self.currentnode = node

        self.write_config_currentwidget()

        self.stackedWidget.setCurrentIndex(page)
        if self.project:
            print self.project.dump_settings()

        widget = self.stackedWidget.currentWidget()
        if hasattr(widget, "set_project"):
            widget.set_project(self.project, self.currentnode)

    def write_config_currentwidget(self):
        widget = self.stackedWidget.currentWidget()
        if hasattr(widget, "write_config"):
            widget.write_config()

    def deploy_project(self, with_data=False):
        if self.roamapp.sourcerun:
            base = os.path.join(self.roamapp.apppath, "..")
        else:
            base = self.roamapp.apppath

        default = os.path.join(base, "roam_serv")
        path = roam.config.settings.get("publish", {}).get("path", '')
        if not path:
            path = default

        path = os.path.join(path, "projects")

        if not os.path.exists(path):
            os.makedirs(path)

        self._saveproject()
        options = {}

        bundle.bundle_project(self.project,
                              path,
                              options,
                              as_install=with_data)

    def setaboutinfo(self):
        self.versionLabel.setText(roam.__version__)
        self.qgisapiLabel.setText(str(QGis.QGIS_VERSION))

    def checkcapturelayers(self):
        haslayers = self.project.hascapturelayers()
        self.formslayerlabel.setVisible(not haslayers)
        return haslayers

    def selectlayerschanged(self, *args):
        self.formlayers.setSelectLayers(self.project.selectlayers)
        self.checkcapturelayers()
        self.selectlayersupdated.emit(self.project.selectlayers)

    def reloadproject(self, *args):
        self.setproject(self.project)
        self.projectupdated.emit()

    def qgisprojectupdated(self, path):
        self.projectupdatedlabel.show()
        self.projectupdatedlabel.setText(
            "The QGIS project has been updated. <a href='reload'> "
            "Click to reload</a>. <b style=\"color:red\">Unsaved data will be lost</b>"
        )

    def openinqgis(self):
        projectfile = self.project.projectfile
        qgislocation = r'C:\OSGeo4W\bin\qgis.bat'
        qgislocation = roam.config.settings.setdefault('configmanager', {}) \
            .setdefault('qgislocation', qgislocation)

        try:
            openqgis(projectfile, qgislocation)
        except OSError:
            self.bar.pushMessage("Looks like I couldn't find QGIS",
                                 "Check qgislocation in roam.config",
                                 QgsMessageBar.WARNING)

    def openprojectfolder(self):
        folder = self.project.folder
        openfolder(folder)

    def setproject(self, project, loadqgis=True):
        """
        Set the widgets active project.
        """
        self.mapisloaded = False
        self.filewatcher.removePaths(self.filewatcher.files())
        self.projectupdatedlabel.hide()
        self._closeqgisproject()

        if project.valid:
            self.startsettings = copy.deepcopy(project.settings)
            self.project = project
            self.projectlabel.setText(project.name)
            self.loadqgisproject(project, self.project.projectfile)
            self.filewatcher.addPath(self.project.projectfile)
            self.projectloaded.emit(self.project)

    def loadqgisproject(self, project, projectfile):
        QDir.setCurrent(os.path.dirname(project.projectfile))
        fileinfo = QFileInfo(project.projectfile)
        self.projectLocationLabel.setText("Project File: {}".format(
            os.path.basename(project.projectfile)))
        QgsProject.instance().read(fileinfo)

    def _closeqgisproject(self):
        if self.canvas.isDrawing():
            return

        self.canvas.freeze(True)
        QgsMapLayerRegistry.instance().removeAllMapLayers()
        self.canvas.freeze(False)

    def loadmap(self):
        if self.mapisloaded:
            return

        # This is a dirty hack to work around the timer that is in QgsMapCanvas in 2.2.
        # Refresh will stop the canvas timer
        # Repaint will redraw the widget.
        # loadmap is only called once per project load so it's safe to do this here.
        self.canvas.refresh()
        self.canvas.repaint()

        parser = roam.projectparser.ProjectParser.fromFile(
            self.project.projectfile)
        canvasnode = parser.canvasnode
        self.canvas.mapRenderer().readXML(canvasnode)
        self.canvaslayers = parser.canvaslayers()
        self.canvas.setLayerSet(self.canvaslayers)
        self.canvas.updateScale()
        self.canvas.refresh()

        self.mapisloaded = True

    def _saveproject(self):
        """
        Save the project config to disk.
        """
        self.write_config_currentwidget()
        # self.project.dump_settings()
        self.project.save(update_version=True)
        self.projectsaved.emit()
Beispiel #4
0
class ProjectWidget(Ui_Form, QWidget):
    SampleWidgetRole = Qt.UserRole + 1
    projectsaved = pyqtSignal()
    projectupdated = pyqtSignal()
    projectloaded = pyqtSignal(object)
    selectlayersupdated = pyqtSignal(list)
    projectlocationchanged = pyqtSignal(str)

    def __init__(self, parent=None):
        super(ProjectWidget, self).__init__(parent)
        self.setupUi(self)
        self.project = None
        self.mapisloaded = False
        self.bar = None

        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor)
        self.canvas.mapRenderer().setLabelingEngine(QgsPalLabeling())

        self.fieldsmodel = QgsFieldModel()
        self.widgetmodel = WidgetsModel()
        self.possiblewidgetsmodel = QStandardItemModel()

        self.formlayersmodel = QgsLayerModel(watchregistry=False)
        self.formlayers = CaptureLayerFilter()
        self.formlayers.setSourceModel(self.formlayersmodel)

        self.selectlayermodel = CaptureLayersModel(watchregistry=False)
        self.selectlayerfilter = LayerTypeFilter()
        self.selectlayerfilter.setSourceModel(self.selectlayermodel)
        self.selectlayermodel.dataChanged.connect(self.selectlayerschanged)

        self.layerCombo.setModel(self.formlayers)
        self.widgetCombo.setModel(self.possiblewidgetsmodel)
        self.selectLayers.setModel(self.selectlayerfilter)
        self.selectLayers_2.setModel(self.selectlayerfilter)
        self.fieldList.setModel(self.fieldsmodel)

        self.widgetlist.setModel(self.widgetmodel)
        self.widgetlist.selectionModel().currentChanged.connect(self.updatecurrentwidget)
        self.widgetmodel.rowsRemoved.connect(self.setwidgetconfigvisiable)
        self.widgetmodel.rowsInserted.connect(self.setwidgetconfigvisiable)
        self.widgetmodel.modelReset.connect(self.setwidgetconfigvisiable)

        self.titleText.textChanged.connect(self.updatetitle)

        QgsProject.instance().readProject.connect(self._readproject)

        self.loadwidgettypes()

        self.addWidgetButton.pressed.connect(self.newwidget)
        self.removeWidgetButton.pressed.connect(self.removewidget)

        self.roamVersionLabel.setText("You are running IntraMaps Roam version {}".format(roam.__version__))

        self.openProjectFolderButton.pressed.connect(self.openprojectfolder)
        self.openinQGISButton.pressed.connect(self.openinqgis)

        self.filewatcher = QFileSystemWatcher()
        self.filewatcher.fileChanged.connect(self.qgisprojectupdated)

        self.formfolderLabel.linkActivated.connect(self.openformfolder)
        self.projectupdatedlabel.linkActivated.connect(self.reloadproject)
        self.projectupdatedlabel.hide()
        self.formtab.currentChanged.connect(self.formtabchanged)

        self.expressionButton.clicked.connect(self.opendefaultexpression)

        self.fieldList.currentIndexChanged.connect(self.updatewidgetname)
        self.fieldwarninglabel.hide()

        for item, data in readonlyvalues:
            self.readonlyCombo.addItem(item, data)

        self.setpage(4)
        self.form = None

        self.projectlocations.currentIndexChanged[str].connect(self.projectlocationchanged.emit)

    def setaboutinfo(self):
        self.versionLabel.setText(roam.__version__)
        self.qgisapiLabel.setText(str(QGis.QGIS_VERSION))

    def checkcapturelayers(self):
        haslayers = self.project.hascapturelayers()
        self.formslayerlabel.setVisible(not haslayers)
        return haslayers

    def opendefaultexpression(self):
        layer = self.currentform.QGISLayer
        dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self)
        text = self.defaultvalueText.text().strip('[%').strip('%]').strip()
        dlg.setExpressionText(text)
        if dlg.exec_():
            self.defaultvalueText.setText('[% {} %]'.format(dlg.expressionText()))

    def openformfolder(self, url):
        openfolder(url)

    def selectlayerschanged(self, *args):
        self.formlayers.setSelectLayers(self.project.selectlayers)
        self.checkcapturelayers()
        self.selectlayersupdated.emit(self.project.selectlayers)

    def formtabchanged(self, index):
        # preview
        if index == 1:
            self.form.settings['widgets'] = list(self.widgetmodel.widgets())
            self.setformpreview(self.form)

    def setprojectfolders(self, folders):
        for folder in folders:
            self.projectlocations.addItem(folder)

    def setpage(self, page):
        self.stackedWidget.setCurrentIndex(page)

    def reloadproject(self, *args):
        self.setproject(self.project)

    def qgisprojectupdated(self, path):
        self.projectupdatedlabel.show()
        self.projectupdatedlabel.setText("The QGIS project has been updated. <a href='reload'> "
                                         "Click to reload</a>. <b style=\"color:red\">Unsaved data will be lost</b>")

    def openinqgis(self):
        projectfile = self.project.projectfile
        qgislocation = r'C:\OSGeo4W\bin\qgis.bat'
        qgislocation = roam.config.settings.setdefault('configmanager', {}) \
                                        .setdefault('qgislocation', qgislocation)

        try:
            openqgis(projectfile, qgislocation)
        except WindowsError:
            self.bar.pushMessage("Looks like I couldn't find QGIS",
                               "Check qgislocation in settings.config", QgsMessageBar.WARNING)

    def openprojectfolder(self):
        folder = self.project.folder
        openfolder(folder)

    def setwidgetconfigvisiable(self, *args):
        haswidgets = self.widgetmodel.rowCount() > 0
        self.widgetframe.setEnabled(haswidgets)

    def removewidget(self):
        """
        Remove the selected widget from the widgets list
        """
        widget, index = self.currentuserwidget
        if index.isValid():
            self.widgetmodel.removeRow(index.row(), index.parent())

    def newwidget(self):
        """
        Create a new widget.  The default is a list.
        """
        widget = {}
        widget['widget'] = 'List'
        # Grab the first field.
        widget['field'] = self.fieldsmodel.index(0, 0).data(QgsFieldModel.FieldNameRole)
        currentindex = self.widgetlist.currentIndex()
        currentitem = self.widgetmodel.itemFromIndex(currentindex)
        if currentitem and currentitem.iscontainor():
            parent = currentindex
        else:
            parent = currentindex.parent()
        index = self.widgetmodel.addwidget(widget, parent)
        self.widgetlist.setCurrentIndex(index)

    def loadwidgettypes(self):
        self.widgetCombo.blockSignals(True)
        for widgettype in roam.editorwidgets.core.supportedwidgets():
            try:
                configclass = configmanager.editorwidgets.widgetconfigs[widgettype]
            except KeyError:
                continue

            configwidget = configclass()
            item = QStandardItem(widgettype)
            item.setData(configwidget, Qt.UserRole)
            item.setData(widgettype, Qt.UserRole + 1)
            item.setIcon(QIcon(widgeticon(widgettype)))
            self.widgetCombo.model().appendRow(item)
            self.widgetstack.addWidget(configwidget)
        self.widgetCombo.blockSignals(False)

    def usedfields(self):
        """
        Return the list of fields that have been used by the the current form's widgets
        """
        for widget in self.currentform.widgets:
            yield widget['field']

    @property
    def currentform(self):
        """
        Return the current selected form.
        """
        return self.form

    @property
    def currentuserwidget(self):
        """
        Return the selected user widget.
        """
        index = self.widgetlist.currentIndex()
        return index.data(Qt.UserRole), index

    @property
    def currentwidgetconfig(self):
        """
        Return the selected widget in the widget combo.
        """
        index = self.widgetCombo.currentIndex()
        index = self.possiblewidgetsmodel.index(index, 0)
        return index.data(Qt.UserRole), index, index.data(Qt.UserRole + 1)

    def updatewidgetname(self, index):
        # Only change the edit text on name field if it's not already set to something other then the
        # field name.
        field = self.fieldsmodel.index(index, 0).data(QgsFieldModel.FieldNameRole)
        currenttext = self.nameText.text()
        foundfield = self.fieldsmodel.findfield(currenttext)
        if foundfield:
            self.nameText.setText(field)

    def _save_widgetfield(self, index):
        """
        Save the selected field for the current widget.

        Shows a error if the field is already used but will allow
        the user to still set it in the case of extra logic for that field
        in the forms Python logic.
        """
        widget, index = self.currentuserwidget
        row = self.fieldList.currentIndex()
        field = self.fieldsmodel.index(row, 0).data(QgsFieldModel.FieldNameRole)
        showwarning = field in self.usedfields()
        self.fieldwarninglabel.setVisible(showwarning)
        widget['field'] = field
        self.widgetmodel.setData(index, widget, Qt.UserRole)

    def _save_selectedwidget(self, index):
        configwidget, index, widgettype = self.currentwidgetconfig
        widget, index = self.currentuserwidget
        if not widget:
            return

        widget['widget'] = widgettype
        widget['required'] = self.requiredCheck.isChecked()
        widget['config'] = configwidget.getconfig()
        widget['name'] = self.nameText.text()
        widget['read-only-rules'] = [self.readonlyCombo.itemData(self.readonlyCombo.currentIndex())]
        widget['hidden'] = self.hiddenCheck.isChecked()

        self.widgetmodel.setData(index, widget, Qt.UserRole)

    def _save_default(self):
        widget, index = self.currentuserwidget
        default = self.defaultvalueText.text()
        widget['default'] = default
        self.widgetmodel.setData(index, widget, Qt.UserRole)

    def _save_selectionlayers(self, index, layer, value):
        config = self.project.settings

        self.selectlayermodel.dataChanged.emit(index, index)

    def _save_formtype(self, index):
        formtype = self.formtypeCombo.currentText()
        form = self.currentform
        form.settings['type'] = formtype

    def _save_formname(self, text):
        """
        Save the form label to the settings file.
        """
        try:
            form = self.currentform
            if form is None:
                return
            form.settings['label'] = text
            self.projectupdated.emit()
        except IndexError:
            return

    def _save_layer(self, index):
        """
        Save the selected layer to the settings file.
        """
        index = self.formlayers.index(index, 0)
        layer = index.data(Qt.UserRole)
        if not layer:
            return

        form = self.currentform
        if form is None:
            return

        form.settings['layer'] = layer.name()
        self.updatefields(layer)

    def setsplash(self, splash):
        pixmap = QPixmap(splash)
        w = self.splashlabel.width()
        h = self.splashlabel.height()
        self.splashlabel.setPixmap(pixmap.scaled(w,h, Qt.KeepAspectRatio))

    def setproject(self, project, loadqgis=True):
        """
        Set the widgets active project.
        """
        self.disconnectsignals()
        self.mapisloaded = False
        self.filewatcher.removePaths(self.filewatcher.files())
        self.projectupdatedlabel.hide()
        self._closeqgisproject()

        if project.valid:
            self.startsettings = copy.deepcopy(project.settings)
            self.project = project
            self.projectlabel.setText(project.name)
            self.versionText.setText(project.version)
            self.selectlayermodel.config = project.settings
            self.formlayers.setSelectLayers(self.project.selectlayers)
            self.setsplash(project.splash)
            self.loadqgisproject(project, self.project.projectfile)
            self.filewatcher.addPath(self.project.projectfile)
            self.projectloaded.emit(self.project)

    def loadqgisproject(self, project, projectfile):
        QDir.setCurrent(os.path.dirname(project.projectfile))
        fileinfo = QFileInfo(project.projectfile)
        QgsProject.instance().read(fileinfo)

    def _closeqgisproject(self):
        if self.canvas.isDrawing():
            return

        self.canvas.freeze(True)
        self.formlayersmodel.removeall()
        self.selectlayermodel.removeall()
        QgsMapLayerRegistry.instance().removeAllMapLayers()
        self.canvas.freeze(False)

    def loadmap(self):
        if self.mapisloaded:
            return

        # This is a dirty hack to work around the timer that is in QgsMapCanvas in 2.2.
        # Refresh will stop the canvas timer
        # Repaint will redraw the widget.
        # loadmap is only called once per project load so it's safe to do this here.
        self.canvas.refresh()
        self.canvas.repaint()

        parser = roam.projectparser.ProjectParser.fromFile(self.project.projectfile)
        canvasnode = parser.canvasnode
        self.canvas.mapRenderer().readXML(canvasnode)
        self.canvaslayers = parser.canvaslayers()
        self.canvas.setLayerSet(self.canvaslayers)
        self.canvas.updateScale()
        self.canvas.refresh()

        self.mapisloaded = True

    def _readproject(self, doc):
        self.formlayersmodel.refresh()
        self.selectlayermodel.refresh()
        self._updateforproject(self.project)

    def _updateforproject(self, project):
        self.titleText.setText(project.name)
        self.descriptionText.setPlainText(project.description)

    def swapwidgetconfig(self, index):
        widgetconfig, _, _ = self.currentwidgetconfig
        defaultvalue = widgetconfig.defaultvalue
        self.defaultvalueText.setText(defaultvalue)

        self.updatewidgetconfig({})

    def updatetitle(self, text):
        self.project.settings['title'] = text
        self.projectlabel.setText(text)
        self.projectupdated.emit()

    def updatewidgetconfig(self, config):
        widgetconfig, index, widgettype = self.currentwidgetconfig
        self.setconfigwidget(widgetconfig, config)

    def setformpreview(self, form):
        def removewidget():
            item = self.frame_2.layout().itemAt(0)
            if item and item.widget():
                item.widget().setParent(None)

        removewidget()

        featureform = FeatureForm.from_form(form, form.settings, None, {})

        self.frame_2.layout().addWidget(featureform)

    def connectsignals(self):
        self.formLabelText.textChanged.connect(self._save_formname)
        self.layerCombo.currentIndexChanged.connect(self._save_layer)
        self.formtypeCombo.currentIndexChanged.connect(self._save_formtype)

        #widget settings
        self.fieldList.currentIndexChanged.connect(self._save_widgetfield)
        self.requiredCheck.toggled.connect(self._save_selectedwidget)
        self.defaultvalueText.textChanged.connect(self._save_default)
        self.widgetCombo.currentIndexChanged.connect(self._save_selectedwidget)
        self.widgetCombo.currentIndexChanged.connect(self.swapwidgetconfig)
        self.nameText.textChanged.connect(self._save_selectedwidget)
        self.readonlyCombo.currentIndexChanged.connect(self._save_selectedwidget)
        self.hiddenCheck.toggled.connect(self._save_selectedwidget)

    def disconnectsignals(self):
        try:
            self.formLabelText.textChanged.disconnect(self._save_formname)
            self.layerCombo.currentIndexChanged.disconnect(self._save_layer)
            self.formtypeCombo.currentIndexChanged.disconnect(self._save_formtype)

            #widget settings
            self.fieldList.currentIndexChanged.disconnect(self._save_widgetfield)
            self.requiredCheck.toggled.disconnect(self._save_selectedwidget)
            self.defaultvalueText.textChanged.disconnect(self._save_default)
            self.widgetCombo.currentIndexChanged.disconnect(self._save_selectedwidget)
            self.widgetCombo.currentIndexChanged.disconnect(self.swapwidgetconfig)
            self.nameText.textChanged.disconnect(self._save_selectedwidget)
            self.readonlyCombo.currentIndexChanged.disconnect(self._save_selectedwidget)
            self.hiddenCheck.toggled.disconnect(self._save_selectedwidget)
        except TypeError:
            pass

    def setform(self, form):
        """
        Update the UI with the currently selected form.
        """

        def getfirstlayer():
            index = self.formlayers.index(0,0)
            layer = index.data(Qt.UserRole)
            layer = layer.name()
            return layer

        def loadwidgets(widget):
            """
            Load the widgets into widgets model
            """
            self.widgetmodel.clear()
            self.widgetmodel.loadwidgets(form.widgets)

        def findlayer(layername):
            index = self.formlayersmodel.findlayer(layername)
            index = self.formlayers.mapFromSource(index)
            layer = index.data(Qt.UserRole)
            return index, layer


        self.disconnectsignals()

        self.form = form

        settings = form.settings
        label = form.label
        layername = settings.setdefault('layer', getfirstlayer())
        layerindex, layer = findlayer(layername)
        if not layer or not layerindex.isValid():
            return

        formtype = settings.setdefault('type', 'auto')
        widgets = settings.setdefault('widgets', [])

        self.formLabelText.setText(label)
        folderurl = "<a href='{path}'>{name}</a>".format(path=form.folder, name=os.path.basename(form.folder))
        self.formfolderLabel.setText(folderurl)
        self.layerCombo.setCurrentIndex(layerindex.row())
        self.updatefields(layer)

        index = self.formtypeCombo.findText(formtype)
        if index == -1:
            self.formtypeCombo.insertItem(0, formtype)
            self.formtypeCombo.setCurrentIndex(0)
        else:
            self.formtypeCombo.setCurrentIndex(index)

        loadwidgets(widgets)

        # Set the first widget
        index = self.widgetmodel.index(0, 0)
        if index.isValid():
            self.widgetlist.setCurrentIndex(index)
            self.updatecurrentwidget(index, None)

        self.connectsignals()

    def updatefields(self, layer):
        """
        Update the UI with the fields for the selected layer.
        """
        self.fieldsmodel.setLayer(layer)

    def setconfigwidget(self, configwidget, config):
        """
        Set the active config widget.
        """

        try:
            configwidget.widgetdirty.disconnect(self._save_selectedwidget)
        except TypeError:
            pass

        #self.descriptionLabel.setText(configwidget.description)
        self.widgetstack.setCurrentWidget(configwidget)
        configwidget.setconfig(config)

        configwidget.widgetdirty.connect(self._save_selectedwidget)

    def updatecurrentwidget(self, index, _):
        """
        Update the UI with the config for the current selected widget.
        """
        if not index.isValid():
            return

        widget = index.data(Qt.UserRole)
        widgettype = widget['widget']
        field = widget['field']
        required = widget.setdefault('required', False)
        name = widget.setdefault('name', field)
        default = widget.setdefault('default', '')
        readonly = widget.setdefault('read-only-rules', [])
        hidden = widget.setdefault('hidden', False)

        try:
            data = readonly[0]
        except:
            data = 'never'

        self.readonlyCombo.blockSignals(True)
        index = self.readonlyCombo.findData(data)
        self.readonlyCombo.setCurrentIndex(index)
        self.readonlyCombo.blockSignals(False)

        self.defaultvalueText.blockSignals(True)
        if not isinstance(default, dict):
            self.defaultvalueText.setText(default)
        else:
            # TODO Handle the more advanced default values.
            pass
        self.defaultvalueText.blockSignals(False)

        self.nameText.blockSignals(True)
        self.nameText.setText(name)
        self.nameText.blockSignals(False)

        self.requiredCheck.blockSignals(True)
        self.requiredCheck.setChecked(required)
        self.requiredCheck.blockSignals(False)

        self.hiddenCheck.blockSignals(True)
        self.hiddenCheck.setChecked(hidden)
        self.hiddenCheck.blockSignals(False)

        if not field is None:
            self.fieldList.blockSignals(True)
            index = self.fieldList.findData(field.lower(), QgsFieldModel.FieldNameRole)
            if index > -1:
                self.fieldList.setCurrentIndex(index)
            else:
                self.fieldList.setEditText(field)
            self.fieldList.blockSignals(False)

        index = self.widgetCombo.findText(widgettype)
        self.widgetCombo.blockSignals(True)
        if index > -1:
            self.widgetCombo.setCurrentIndex(index)
        self.widgetCombo.blockSignals(False)

        self.updatewidgetconfig(config=widget.setdefault('config', {}))

    def _saveproject(self):
        """
        Save the project config to disk.
        """
        title = self.titleText.text()
        description = self.descriptionText.toPlainText()
        version = str(self.versionText.text())

        settings = self.project.settings
        settings['title'] = title
        settings['description'] = description
        settings['version'] = version

        form = self.currentform
        if form:
            form.settings['widgets'] = list(self.widgetmodel.widgets())
            logger.debug(form.settings)

        self.project.save()
        self.projectsaved.emit()
Beispiel #5
0
class Console(QObject):

    @property
    def _currentCursor(self):
        return self.widget.textCursor()

    @_currentCursor.setter
    def _currentCursor(self, cursor):
        self.widget.setTextCursor(cursor)

    @property
    def _endCursor(self):
        return self._lastBlock.endCursor()

    @property
    def _currentBlock(self):
        return TextBlock(self._currentCursor)

    @property
    def _document(self):
        return self.widget.document()

    def _canDeletePreviousChar(self):
        if self._currentBlock.type in TextBlock.EDITABLE_TYPES and self._cursorInEditableArea():
            return self._currentBlock.containsCursor(self._currentCursor, in_active_area='strict')
        return False

    def _cursorInEditableArea(self, cursor=None):
        if cursor is None:
            cursor = self._currentCursor
        return self._lastBlock.isCursorInRelatedBlock(cursor) and self._currentBlock.containsCursor(cursor)

    def _canEditCurrentChar(self):
        if not self._currentBlock.type in TextBlock.EDITABLE_TYPES:
            return False
        if not self._currentBlock.containsCursor(self._currentCursor):
            return False

    def _gotoBlockStart(self):
        self._currentCursor = self._currentBlock.startCursor()

    def _gotoBlockEnd(self):
        self._currentCursor = self._currentBlock.endCursor()

    @property
    def _lastBlock(self):
        return TextBlock(self._document.lastBlock())

    def _gotoEnd(self):
        self._currentCursor = self._endCursor

    def _insertBlock(self, cursor=None, block_type=TextBlock.TYPE_CODE_CONTINUED, content=""):
        if cursor is None:
            cursor = self._currentCursor
        b = TextBlock(cursor, create_new=True, typ=block_type)
        if len(content):
            b.appendText(content)
        return b

    def _appendBlock(self, block_type, content="", html=False):
        b = TextBlock(self._endCursor, create_new=True, typ=block_type)
        if len(content) > 0:
            if html:
                b.appendHtml(content)
            else:
                b.appendText(content)
        return b

    def _blocks(self):
        b = TextBlock(self._document.begin())
        ret = []
        while not b.isLast():
            ret.append(b)
            b = b.next()
        ret.append(b)
        return ret

    def _saveBlocksToJSON(self,num_of_blocks=None):
        if num_of_blocks is None:
            num_of_blocks = 0
        ret = []
        for b in self._blocks()[-num_of_blocks:]:
            ret += [json.dumps({'block_type':b.type,'content':b.content()})]
        return '\n'.join(ret)

    def save_history(self, fname=None):
        if fname is None:
            fname = config.history_file
        fname = expanduser(fname)
        if fname.endswith('.gz'):
            f = gzip.open(fname,'wb')
        else:
            f = open(fname,'wb')
        try:
            hist_size = config.history_size
        except:
            hist_size = None
        f.write(self._saveBlocksToJSON(num_of_blocks=hist_size))
        f.close()

    def load_history(self, fname=None):
        if fname is None:
            fname = config.history_file
        fname = expanduser(fname)
        if not os.path.exists(fname):
            return
        if fname.endswith('.gz'):
            f = gzip.open(fname,'rb')
        else:
            f = open(fname,'rb')
        blocks = f.readlines()
        for b in blocks:
            bl = json.loads(b.strip())
            self._appendBlock(**bl)


    def _joinCurrentToPreviousBlock(self):
        logger.debug(msg("Deleting current block"))
        cur = self._currentBlock
        prev = cur.previous()
        prev.appendText(cur.content())
        cur.deleteBlock()

    def wordUnderCursor(self, cursor=None, delta=0):
        """ Returns the word (name) under cursor @cursor (or self._currentCursor)
            if @cursor is None. The cursor is shifted by @delta """
        if cursor is None:
            cursor = self._currentCursor
        if delta > 0:
            cursor.movePosition(QTextCursor.Right, QTextCursor.MoveAnchor, delta)
        elif delta < 0:
            cursor.movePosition(QTextCursor.Left, QTextCursor.MoveAnchor, abs(delta))
        return self._currentBlock.wordUnderCursor(cursor)

    def _containing_function(self):
        """ Determines whether the cursor is located in the argument part of a function.
            If yes, returns a (@cursor,@func_name) where  @cursor points to the opening brace
            and @func_name is the function name. """
        ct = self.textToCursor()
        i=len(ct)-1
        brackets = 0
        logger.debug("ct == " + ct)
        while i >= 0:
            if ct[i] == ')':
                brackets -=1
            if ct[i] == '(':
                if brackets < 0:
                    brackets += 1
                elif i > 0 and ct[i-1] not in TextBlock.WORD_STOP_CHARS:
                    cursor = self._currentBlock.cursorAt(i-1)
                    func_name = self._currentBlock.wordUnderCursor(cursor)
                    cursor = self._currentBlock.cursorAt(i+len(func_name))
                    logger.debug("func_name == "+ func_name)
                    return (cursor,func_name)
            i -= 1
        return None


    def textToCursor(self):
        return self._currentBlock.contentToCursor(self._currentCursor)

    def _guessDictPrefix(self, char):
        """ Tries to determine whether the user is typing a string-key in a dict. If yes
            returns the dict name and the key typed so far. Otherwise returns None. """
        c = self._currentCursor
        block_content = self._currentBlock.content(include_decoration=True)
        pos = min(c.positionInBlock(), len(block_content))
        block_content = unicode(block_content[:pos]+char)

        double_quotes = block_content.count('"')
        single_quotes = block_content.count("'")

        if double_quotes % 2 == 0 and single_quotes % 2 == 0:
            return None

        stop_chars = '"\' '
        ret = []
        while pos >= 0 and block_content[pos] not in stop_chars:
            ret.append(block_content[pos])
            pos -= 1
        ret.reverse()
        if pos > 0 and block_content[pos-1] == '[':
            pos -= 1
            dct = []
            while pos >= 0 and block_content[pos] not in [ ' ', "\n", "\t"]:
                dct.append(block_content[pos])
                pos -= 1
            dct.reverse()
            logger.debug(msg('dct:',''.join(dct[:-1]), 'key:',''.join(ret)))
            return ''.join(dct[:-1]), ''.join(ret)
        else:
            return None

    def _guessStringPrefix(self, char):
        """Tries to guess whether the user is typing a string (i.e. inside quotes)
           and if yes, returns the string typed so far. Otherwise returns None. """
        c = self._currentCursor
        block_content = self._currentBlock.content(include_decoration=True)
        pos = min(c.positionInBlock(), len(block_content))
        block_content = unicode(block_content[:pos]+char)

        double_quotes = block_content.count('"')
        single_quotes = block_content.count("'")

        if double_quotes % 2 == 0 and single_quotes % 2 == 0:
            return None

        stop_chars = '"\' '
        ret = []
        while pos >= 0 and block_content[pos] not in stop_chars:
            ret.append(block_content[pos])
            pos -= 1
        ret.reverse()
        logger.debug(''.join(ret))
        return ''.join(ret)

    @pyqtSlot(unicode)
    def _insertCompletion(self, completion):
        insertion = completion[len(self.completer.completionPrefix()):]
        if len(insertion) == 0:
            self._process_enter()
        else:
            c = self._currentCursor
            c.movePosition(QTextCursor.Left)
            c.movePosition(QTextCursor.EndOfWord)
            c.insertText(insertion)
            self._currentCursor = c

    DEFAULT_INDENT = 4

    MODE_RUNNING = 0
    MODE_CODE_EDITING = 1
    MODE_RAW_INPUT = 2
    MODE_READ_ONLY = 3
    MODE_WAITING_FOR_INTERRUPT = 4

    # run_code = pyqtSignal(unicode)
    run_code = signal(unicode)
    read_line = signal(unicode)
    restart_shell = signal()
    interrupt_shell = signal()
    quit = pyqtSignal()
    file_watched = signal(unicode)

    def __init__(self, widget):
        super(Console, self).__init__()
        self.font_size = 10
        self.font = QFont("Ubuntu Mono", self.font_size)
        self.allow_quit = True
        self.indent = self.DEFAULT_INDENT
        self.widget = widget
        self.hilighter = PythonHighlighter(self.widget.document())
        self.parser = PyParser(self.indent, self.indent)

        self.widget.setFont(self.font)

        # The source file's we are watching
        self.watcher = QFileSystemWatcher()
        self.watcher.fileChanged.connect(self._sourceChanged)
        self._watched_files_menu = QMenu(self.widget.tr("&Watched Files"))
        self._watched_files_actions = {}
        self._lost_files = []

        self._widgetKeyPressEvent = self.widget.keyPressEvent
        self.widget.keyPressEvent = self.keyPressEvent
        self._widgetFocusInEvent = self.widget.focusInEvent
        self.widget.focusInEvent = self.focusInEvent
        self.widget.dragEnterEvent = self.dragEnterEvent
        self.widget.dragMoveEvent = self.dragMoveEvent
        self.widget.dropEvent = self.dropEvent

        # Code Completion
        self.completer = QCompleter(
            [kw for kw in PythonHighlighter.keywords if len(kw) > 3])
        self.completer.setCompletionMode(QCompleter.PopupCompletion)
        self.completer.setCaseSensitivity(Qt.CaseInsensitive)
        self.completer.activated.connect(self._insertCompletion)
        self.completer.popup().setStyleSheet(
            "QWidget {border-width: 1px; border-color: black;}")
        self.completer.popup().setVerticalScrollBarPolicy(
            Qt.ScrollBarAlwaysOff)
        self.completer.setWidget(self.widget)
        self.completion_enabled = True

        if config.history:
            self.load_history()

        self._lastBlock.setType(TextBlock.TYPE_MESSAGE)
        self._lastBlock.appendText("PyShell v 0.5: Starting ...")
        self._write_message("Python version ", sys.version)
        self.start_editing()

        # Restart shell timer
        self.timer = QTimer(self)

    @property
    def watched_files_menu(self):
        return self._watched_files_menu

    @property
    def mode(self):
        return self._mode

    @pyqtSlot()
    def increase_font(self):
        self.font_size += 1
        self.font = QFont("Ubuntu Mono", self.font_size)
        self.widget.setFont(self.font)

    @pyqtSlot()
    def decrease_font(self):
        self.font_size -= 1
        self.font = QFont("Ubuntu Mono", self.font_size)
        self.widget.setFont(self.font)

    @pyqtSlot(unicode, unicode)
    def write(self, stream, string):
        #assert self.mode == Console.MODE_RUNNING or self.mode == Console.MODE_WAITING_FOR_INTERRUPT, "Cannot write " + \
        #    string + " to console stream "+stream+" in "+str(
        #        self.mode) + " (need to be RUNNING/INTERRUPT mode) "
        if string.startswith('<html_snippet>'):
            self._lastBlock.appendHtml(string.replace(
                "<html_snippet>", "").replace("</html_snippet>", ""))
        else:
            lines = string.split('\n')
            block_type = block_type_for_stream(stream)
            for ln in lines[:-1]:
                self._lastBlock.appendText(ln)
                self._lastBlock.setType(block_type)
                self._appendBlock(block_type)
            self._lastBlock.appendText(lines[-1])
            self._gotoEnd()

    def write_object(self, obj):
        logger.debug(msg("Received object", obj, "writing it to stdout"))
        self.write('stdout',print_hooks.html_repr(obj, self._document))

    @pyqtSlot()
    def do_readline(self):
        assert self.mode != Console.MODE_RAW_INPUT, "Cannot call readline before previous call finished"
        assert self.mode == Console.MODE_RUNNING, "readline may be called only in the RUNNING mode"
        self._lastBlock.setType(TextBlock.TYPE_RAW_INPUT)
        self._mode = Console.MODE_RAW_INPUT

    @pyqtSlot()
    def shell_restarted(self):
        logger.debug("Shell restarted!")
        self._write_message("<span style='color:green'>","="*20,
                            "</span> SHELL RESTARTED <span style='color:green'>","="*20,"</span>", html=True)
        self._write_message('Python version ',sys.version, html=True)
        self.start_editing()

    @pyqtSlot()
    def finished_running(self):
        logger.debug("Finished running.")
        if len(self._lastBlock.content()) == 0:
            self._lastBlock.deleteBlock()
        self.start_editing()

    @pyqtSlot()
    def start_editing(self):
        new_code_block = self._appendBlock(TextBlock.TYPE_CODE_START)
        self._gotoEnd()
        self._mode = Console.MODE_CODE_EDITING

    @pyqtSlot()
    def exec_command(self,*args, **kwargs):
        """ Executes a command in the console context.
            The command is given by the cmd keyword argument.
            It is either a predefined command or the name of
            a method of the console object, in which case this
            method is called with *args and **kwargs (where cmd
            is first deleted from kwargs) """
        if 'cmd' in kwargs:
            cmd = kwargs['cmd']
            del kwargs['cmd']
            if cmd == 'watch.list_files':
                self._write_message("Currently watching the following files:")
                self._write_message([unicode(x) for x in self.watcher.files()])
                self._write_message("Total watched:",len(self.watcher.files()))
                logger.debug(msg([x for x in self.watcher.files()]))
            elif cmd == 'watch.reload':
                self._reload_watch_files()
            elif cmd in dir(self):
                attr = self.__getattribute__(cmd)
                if type(attr) == type(self.exec_command):
                    attr(*args, **kwargs)
                else:
                    self.write_object(attr)

    def _write_message(self, *args, **kwargs):
        """ Outputs a "System" message to the console. The message is composed
            of string representations of args. If the keyword argument html
            is present, the message is assumed to be html. """
        if 'html' in kwargs:
            html = kwargs['html']
        else:
            html = True
        msg = u''
        for a in args:
            try:
                msg += unicode(a)
            except Exception, e:
                logger.warn("Exception while writing to console" + str(e))
        self._appendBlock(TextBlock.TYPE_MESSAGE, msg, html)
Beispiel #6
0
class NFile(QObject):
    """
    SIGNALS:
    @neverSavedFileClosing(QString)
    @fileClosing(QString)
    @fileChanged()
    @willDelete(PyQt_PyObject, PyQt_PyObject)
    @willOverWrite(PyQt_PyObject, QString, QString)
    @willMove(Qt_PyQtObject, QString, QString)
    @willSave(QString, QString)
    @savedAsNewFile(PyQt_PyObject, QString, QString)
    @gotAPath(PyQt_PyObject)
    @willAttachToExistingFile(PyQt_PyObject, QString)
    """

    def __init__(self, path=None):
        """
        """
        self._file_path = path
        self.__created = False
        self.__watcher = None
        self.__mtime = None
        super(NFile, self).__init__()
        if not self._exists():
            self.__created = True

    @property
    def file_name(self):
        """"Returns filename of nfile"""
        file_name = None
        if self._file_path is None:
            file_name = translations.TR_NEW_DOCUMENT
        else:
            file_name = get_basename(self._file_path)
        return file_name

    @property
    def display_name(self):
        """Returns a pretty name to be displayed by tabs"""
        display_name = self.file_name
        if not self._file_path is None and not self.has_write_permission():
            display_name += translations.TR_READ_ONLY
        return display_name

    @property
    def is_new_file(self):
        return self.__created

    def file_ext(self):
        """"Returns extension of nfile"""
        if self._file_path is None:
            return ''
        return get_file_extension(self._file_path)

    @property
    def file_path(self):
        """"Returns file path of nfile"""
        return self._file_path

    def start_watching(self):
        self.__watcher = QFileSystemWatcher(self)
        self.connect(self.__watcher, SIGNAL("fileChanged(const QString&)"),
            self._file_changed)
        if self._file_path is not None:
            self.__mtime = os.path.getmtime(self._file_path)
            self.__watcher.addPath(self._file_path)

    def _file_changed(self, path):
        current_mtime = os.path.getmtime(self._file_path)
        if current_mtime != self.__mtime:
            self.__mtime = current_mtime
            self.emit(SIGNAL("fileChanged()"))

    def has_write_permission(self):
        if not self._exists():
            return True
        return os.access(self._file_path, os.W_OK)

    def _exists(self):
        """
        Check if we have been created with a path and if such path exists
        In case there is no path, we are most likely a new file.
        """
        file_exists = False
        if self._file_path and os.path.exists(self._file_path):
            file_exists = True
        return file_exists

    def attach_to_path(self, new_path):
        if os.path.exists(new_path):
            signal_handler = SignalFlowControl()
            self.emit(
                    SIGNAL("willAttachToExistingFile(PyQt_PyObject, QString)"),
                    signal_handler, new_path)
            if signal_handler.stopped():
                    return
        self._file_path = new_path
        self.emit(SIGNAL("gotAPath(PyQt_PyObject)"), self)
        return self._file_path

    def create(self):
        if self.__created:
            self.save("")
        self.__created = False

    def save(self, content, path=None):
        """
        Write a temprorary file with .tnj extension and copy it over the
        original one.
        .nsf = Ninja Swap File
        #FIXME: Where to locate addExtension, does not fit here
        """
        new_path = False
        if path:
            self.attach_to_path(path)
            new_path = True

        save_path = self._file_path

        if not save_path:
            raise NinjaNoFileNameException("I am asked to write a "
                                "file but no one told me where")
        swap_save_path = u"%s.nsp" % save_path

        self.__watcher.removePath(save_path)
        flags = QIODevice.WriteOnly | QIODevice.Truncate
        f = QFile(swap_save_path)
        if settings.use_platform_specific_eol():
            flags |= QIODevice.Text

        if not f.open(flags):
            raise NinjaIOException(f.errorString())

        stream = QTextStream(f)
        encoding = get_file_encoding(content)
        if encoding:
            stream.setCodec(encoding)

        encoded_stream = stream.codec().fromUnicode(content)
        f.write(encoded_stream)
        f.flush()
        f.close()
        #SIGNAL: Will save (temp, definitive) to warn folder to do something
        self.emit(SIGNAL("willSave(QString, QString)"), swap_save_path,
                                                        save_path)
        self.__mtime = os.path.getmtime(swap_save_path)
        shutil.move(swap_save_path, save_path)
        self.__watcher.addPath(save_path)
        self.reset_state()
        if self.__watcher and new_path:
            self.__watcher.removePath(self.__watcher.files()[0])
            self.__watcher.addPath(self._file_path)
        return self

    def reset_state(self):
        """
        #FIXE: to have a ref to changed I need to have the doc here
        """
        self.__created = False

    def read(self, path=None):
        """
        Read the file or fail
        """
        open_path = path and path or self._file_path
        self._file_path = open_path
        if not self._file_path:
            raise NinjaNoFileNameException("I am asked to read a "
                                    "file but no one told me from where")
        try:
            with open(open_path, 'rU') as f:
                content = f.read()
        except IOError as reason:
            raise NinjaIOException(reason)
        return content

    def move(self, new_path):
        """
        Phisically move the file
        """
        if self._exists():
            signal_handler = SignalFlowControl()
            #SIGNALL: WILL MOVE TO, to warn folder to exist
            self.emit(SIGNAL("willMove(Qt_PyQtObject, QString, QString)"),
                                                            signal_handler,
                                                            self._file_path,
                                                            new_path)
            if signal_handler.stopped():
                return
            if os.path.exists(new_path):
                signal_handler = SignalFlowControl()
                self.emit(
                    SIGNAL("willOverWrite(PyQt_PyObject, QString, QString)"),
                                    signal_handler, self._file_path, new_path)
                if signal_handler.stopped():
                    return
            if self.__watcher:
                self.__watcher.removePath(self._file_path)
            shutil.move(self._file_path, new_path)
            if self.__watcher:
                self.__watcher.addPath(new_path)
        self._file_path = new_path
        return

    def copy(self, new_path):
        """
        Copy the file to a new path
        """
        if self._exists():
            signal_handler = SignalFlowControl()
            #SIGNALL: WILL COPY TO, to warn folder to exist
            self.emit(SIGNAL("willCopyTo(Qt_PyQtObject, QString, QString)"),
                                                            signal_handler,
                                                            self._file_path,
                                                            new_path)
            if signal_handler.stopped():
                return
            if os.path.exists(new_path):
                signal_handler = SignalFlowControl()
                self.emit(
                    SIGNAL("willOverWrite(PyQt_PyObject, QString, QString)"),
                                    signal_handler, self._file_path, new_path)
                if signal_handler.stopped():
                    return

            shutil.copy(self._file_path, new_path)

    def delete(self, force=False):
        """
        This deletes the object and closes the file.
        """
        #if created but exists this file migth to someone else
        self.close()
        if ((not self.__created) or force) and self._exists():
            DEBUG("Deleting our own NFile %s" % self._file_path)
            signal_handler = SignalFlowControl()
            self.emit(SIGNAL("willDelete(PyQt_PyObject, PyQt_PyObject)"),
                                                        signal_handler, self)
            if not signal_handler.stopped():
                if self.__watcher:
                    self.__watcher.removePath(self._file_path)
                os.remove(self._file_path)

    def close(self, force_close=False):
        """
        Lets let people know we are going down so they can act upon
        As you can see close does nothing but let everyone know that we are
        not saved yet
        """
        DEBUG("About to close NFile")
        if self.__created and not force_close:
            self.emit(SIGNAL("neverSavedFileClosing(QString)"),
                        self._file_path)
        else:
            self.emit(SIGNAL("fileClosing(QString)"), self._file_path)
            if self.__watcher:
                self.__watcher.removePath(self._file_path)
Beispiel #7
0
class ProjectWidget(Ui_Form, QWidget):
    SampleWidgetRole = Qt.UserRole + 1
    projectsaved = pyqtSignal()
    projectupdated = pyqtSignal()
    projectloaded = pyqtSignal(object)
    selectlayersupdated = pyqtSignal(list)

    def __init__(self, parent=None):
        super(ProjectWidget, self).__init__(parent)
        self.setupUi(self)
        self.project = None
        self.mapisloaded = False
        self.bar = None

        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor)
        self.canvas.mapRenderer().setLabelingEngine(QgsPalLabeling())

        self.fieldsmodel = QgsFieldModel()
        self.widgetmodel = WidgetsModel()
        self.possiblewidgetsmodel = QStandardItemModel()

        self.formlayersmodel = QgsLayerModel(watchregistry=False)
        self.formlayers = CaptureLayerFilter()
        self.formlayers.setSourceModel(self.formlayersmodel)

        self.selectlayermodel = CaptureLayersModel(watchregistry=False)
        self.selectlayerfilter = LayerTypeFilter()
        self.selectlayerfilter.setSourceModel(self.selectlayermodel)
        self.selectlayermodel.dataChanged.connect(self.selectlayerschanged)

        self.layerCombo.setModel(self.formlayers)
        self.widgetCombo.setModel(self.possiblewidgetsmodel)
        self.selectLayers.setModel(self.selectlayerfilter)
        self.selectLayers_2.setModel(self.selectlayerfilter)
        self.fieldList.setModel(self.fieldsmodel)

        self.widgetlist.setModel(self.widgetmodel)
        self.widgetlist.selectionModel().currentChanged.connect(self.updatecurrentwidget)
        self.widgetmodel.rowsRemoved.connect(self.setwidgetconfigvisiable)
        self.widgetmodel.rowsInserted.connect(self.setwidgetconfigvisiable)
        self.widgetmodel.modelReset.connect(self.setwidgetconfigvisiable)

        self.titleText.textChanged.connect(self.updatetitle)

        QgsProject.instance().readProject.connect(self._readproject)

        self.loadwidgettypes()

        self.addWidgetButton.pressed.connect(self.newwidget)
        self.removeWidgetButton.pressed.connect(self.removewidget)

        self.roamVersionLabel.setText("You are running IntraMaps Roam version {}".format(roam.__version__))

        self.openProjectFolderButton.pressed.connect(self.openprojectfolder)
        self.openinQGISButton.pressed.connect(self.openinqgis)

        self.filewatcher = QFileSystemWatcher()
        self.filewatcher.fileChanged.connect(self.qgisprojectupdated)

        self.formfolderLabel.linkActivated.connect(self.openformfolder)
        self.projectupdatedlabel.linkActivated.connect(self.reloadproject)
        self.projectupdatedlabel.hide()
        self.formtab.currentChanged.connect(self.formtabchanged)

        self.expressionButton.clicked.connect(self.opendefaultexpression)

        self.fieldList.currentIndexChanged.connect(self.updatewidgetname)
        self.fieldwarninglabel.hide()

        for item, data in readonlyvalues:
            self.readonlyCombo.addItem(item, data)

        self.setpage(4)
        self.form = None

    def setaboutinfo(self):
        self.versionLabel.setText(roam.__version__)
        self.qgisapiLabel.setText(str(QGis.QGIS_VERSION))

    def checkcapturelayers(self):
        haslayers = self.project.hascapturelayers()
        self.formslayerlabel.setVisible(not haslayers)
        return haslayers

    def opendefaultexpression(self):
        layer = self.currentform.QGISLayer
        dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self)
        text = self.defaultvalueText.text().strip('[%').strip('%]').strip()
        dlg.setExpressionText(text)
        if dlg.exec_():
            self.defaultvalueText.setText('[% {} %]'.format(dlg.expressionText()))

    def openformfolder(self, url):
        openfolder(url)

    def selectlayerschanged(self, *args):
        self.formlayers.setSelectLayers(self.project.selectlayers)
        self.checkcapturelayers()
        self.selectlayersupdated.emit(self.project.selectlayers)

    def formtabchanged(self, index):
        # preview
        if index == 1:
            self.form.settings['widgets'] = list(self.widgetmodel.widgets())
            self.setformpreview(self.form)


    def setpage(self, page):
        self.stackedWidget.setCurrentIndex(page)

    def reloadproject(self, *args):
        self.setproject(self.project)

    def qgisprojectupdated(self, path):
        self.projectupdatedlabel.show()
        self.projectupdatedlabel.setText("The QGIS project has been updated. <a href='reload'> "
                                         "Click to reload</a>. <b style=\"color:red\">Unsaved data will be lost</b>")

    def openinqgis(self):
        projectfile = self.project.projectfile
        qgislocation = r'C:\OSGeo4W\bin\qgis.bat'
        qgislocation = roam.config.settings.setdefault('configmanager', {}) \
                                        .setdefault('qgislocation', qgislocation)

        try:
            openqgis(projectfile, qgislocation)
        except WindowsError:
            self.bar.pushMessage("Looks like I couldn't find QGIS",
                               "Check qgislocation in roam.config", QgsMessageBar.WARNING)

    def openprojectfolder(self):
        folder = self.project.folder
        openfolder(folder)

    def setwidgetconfigvisiable(self, *args):
        haswidgets = self.widgetmodel.rowCount() > 0
        self.widgetframe.setEnabled(haswidgets)

    def removewidget(self):
        """
        Remove the selected widget from the widgets list
        """
        widget, index = self.currentuserwidget
        if index.isValid():
            self.widgetmodel.removeRow(index.row(), index.parent())

    def newwidget(self):
        """
        Create a new widget.  The default is a list.
        """
        widget = {}
        widget['widget'] = 'Text'
        # Grab the first field.
        widget['field'] = self.fieldsmodel.index(0, 0).data(QgsFieldModel.FieldNameRole)
        currentindex = self.widgetlist.currentIndex()
        currentitem = self.widgetmodel.itemFromIndex(currentindex)
        if currentitem and currentitem.iscontainor():
            parent = currentindex
        else:
            parent = currentindex.parent()
        index = self.widgetmodel.addwidget(widget, parent)
        self.widgetlist.setCurrentIndex(index)

    def loadwidgettypes(self):
        self.widgetCombo.blockSignals(True)
        for widgettype in roam.editorwidgets.core.supportedwidgets():
            try:
                configclass = configmanager.editorwidgets.widgetconfigs[widgettype]
            except KeyError:
                continue

            configwidget = configclass()
            item = QStandardItem(widgettype)
            item.setData(configwidget, Qt.UserRole)
            item.setData(widgettype, Qt.UserRole + 1)
            item.setIcon(QIcon(widgeticon(widgettype)))
            self.widgetCombo.model().appendRow(item)
            self.widgetstack.addWidget(configwidget)
        self.widgetCombo.blockSignals(False)

    def usedfields(self):
        """
        Return the list of fields that have been used by the the current form's widgets
        """
        for widget in self.currentform.widgets:
            yield widget['field']

    @property
    def currentform(self):
        """
        Return the current selected form.
        """
        return self.form

    @property
    def currentuserwidget(self):
        """
        Return the selected user widget.
        """
        index = self.widgetlist.currentIndex()
        return index.data(Qt.UserRole), index

    @property
    def currentwidgetconfig(self):
        """
        Return the selected widget in the widget combo.
        """
        index = self.widgetCombo.currentIndex()
        index = self.possiblewidgetsmodel.index(index, 0)
        return index.data(Qt.UserRole), index, index.data(Qt.UserRole + 1)

    def updatewidgetname(self, index):
        # Only change the edit text on name field if it's not already set to something other then the
        # field name.
        field = self.fieldsmodel.index(index, 0).data(QgsFieldModel.FieldNameRole)
        currenttext = self.nameText.text()
        foundfield = self.fieldsmodel.findfield(currenttext)
        if foundfield:
            self.nameText.setText(field)

    def _save_widgetfield(self, index):
        """
        Save the selected field for the current widget.

        Shows a error if the field is already used but will allow
        the user to still set it in the case of extra logic for that field
        in the forms Python logic.
        """
        widget, index = self.currentuserwidget
        row = self.fieldList.currentIndex()
        field = self.fieldsmodel.index(row, 0).data(QgsFieldModel.FieldNameRole)
        showwarning = field in self.usedfields()
        self.fieldwarninglabel.setVisible(showwarning)
        widget['field'] = field
        self.widgetmodel.setData(index, widget, Qt.UserRole)

    def _save_selectedwidget(self, index):
        configwidget, index, widgettype = self.currentwidgetconfig
        widget, index = self.currentuserwidget
        if not widget:
            return

        widget['widget'] = widgettype
        widget['required'] = self.requiredCheck.isChecked()
        widget['config'] = configwidget.getconfig()
        widget['name'] = self.nameText.text()
        widget['read-only-rules'] = [self.readonlyCombo.itemData(self.readonlyCombo.currentIndex())]
        widget['hidden'] = self.hiddenCheck.isChecked()

        self.widgetmodel.setData(index, widget, Qt.UserRole)

    def _save_default(self):
        widget, index = self.currentuserwidget
        default = self.defaultvalueText.text()
        widget['default'] = default
        self.widgetmodel.setData(index, widget, Qt.UserRole)

    def _save_selectionlayers(self, index, layer, value):
        config = self.project.settings

        self.selectlayermodel.dataChanged.emit(index, index)

    def _save_formtype(self, index):
        formtype = self.formtypeCombo.currentText()
        form = self.currentform
        form.settings['type'] = formtype

    def _save_formname(self, text):
        """
        Save the form label to the settings file.
        """
        try:
            form = self.currentform
            if form is None:
                return
            form.settings['label'] = text
            self.projectupdated.emit()
        except IndexError:
            return

    def _save_layer(self, index):
        """
        Save the selected layer to the settings file.
        """
        index = self.formlayers.index(index, 0)
        layer = index.data(Qt.UserRole)
        if not layer:
            return

        form = self.currentform
        if form is None:
            return

        form.settings['layer'] = layer.name()
        self.updatefields(layer)

    def setsplash(self, splash):
        pixmap = QPixmap(splash)
        w = self.splashlabel.width()
        h = self.splashlabel.height()
        self.splashlabel.setPixmap(pixmap.scaled(w,h, Qt.KeepAspectRatio))

    def setproject(self, project, loadqgis=True):
        """
        Set the widgets active project.
        """
        self.disconnectsignals()
        self.mapisloaded = False
        self.filewatcher.removePaths(self.filewatcher.files())
        self.projectupdatedlabel.hide()
        self._closeqgisproject()

        if project.valid:
            self.startsettings = copy.deepcopy(project.settings)
            self.project = project
            self.projectlabel.setText(project.name)
            self.versionText.setText(project.version)
            self.selectlayermodel.config = project.settings
            self.formlayers.setSelectLayers(self.project.selectlayers)
            self.setsplash(project.splash)
            self.loadqgisproject(project, self.project.projectfile)
            self.filewatcher.addPath(self.project.projectfile)
            self.projectloaded.emit(self.project)

    def loadqgisproject(self, project, projectfile):
        QDir.setCurrent(os.path.dirname(project.projectfile))
        fileinfo = QFileInfo(project.projectfile)
        QgsProject.instance().read(fileinfo)

    def _closeqgisproject(self):
        if self.canvas.isDrawing():
            return

        self.canvas.freeze(True)
        self.formlayersmodel.removeall()
        self.selectlayermodel.removeall()
        QgsMapLayerRegistry.instance().removeAllMapLayers()
        self.canvas.freeze(False)

    def loadmap(self):
        if self.mapisloaded:
            return

        # This is a dirty hack to work around the timer that is in QgsMapCanvas in 2.2.
        # Refresh will stop the canvas timer
        # Repaint will redraw the widget.
        # loadmap is only called once per project load so it's safe to do this here.
        self.canvas.refresh()
        self.canvas.repaint()

        parser = roam.projectparser.ProjectParser.fromFile(self.project.projectfile)
        canvasnode = parser.canvasnode
        self.canvas.mapRenderer().readXML(canvasnode)
        self.canvaslayers = parser.canvaslayers()
        self.canvas.setLayerSet(self.canvaslayers)
        self.canvas.updateScale()
        self.canvas.refresh()

        self.mapisloaded = True

    def _readproject(self, doc):
        self.formlayersmodel.refresh()
        self.selectlayermodel.refresh()
        self._updateforproject(self.project)

    def _updateforproject(self, project):
        self.titleText.setText(project.name)
        self.descriptionText.setPlainText(project.description)

    def swapwidgetconfig(self, index):
        widgetconfig, _, _ = self.currentwidgetconfig
        defaultvalue = widgetconfig.defaultvalue
        self.defaultvalueText.setText(defaultvalue)

        self.updatewidgetconfig({})

    def updatetitle(self, text):
        self.project.settings['title'] = text
        self.projectlabel.setText(text)
        self.projectupdated.emit()

    def updatewidgetconfig(self, config):
        widgetconfig, index, widgettype = self.currentwidgetconfig
        self.setconfigwidget(widgetconfig, config)

    def setformpreview(self, form):
        def removewidget():
            item = self.frame_2.layout().itemAt(0)
            if item and item.widget():
                item.widget().setParent(None)

        removewidget()

        featureform = FeatureForm.from_form(form, form.settings, None, {})

        self.frame_2.layout().addWidget(featureform)

    def connectsignals(self):
        self.formLabelText.textChanged.connect(self._save_formname)
        self.layerCombo.currentIndexChanged.connect(self._save_layer)
        self.formtypeCombo.currentIndexChanged.connect(self._save_formtype)

        #widget settings
        self.fieldList.currentIndexChanged.connect(self._save_widgetfield)
        self.requiredCheck.toggled.connect(self._save_selectedwidget)
        self.defaultvalueText.textChanged.connect(self._save_default)
        self.widgetCombo.currentIndexChanged.connect(self._save_selectedwidget)
        self.widgetCombo.currentIndexChanged.connect(self.swapwidgetconfig)
        self.nameText.textChanged.connect(self._save_selectedwidget)
        self.readonlyCombo.currentIndexChanged.connect(self._save_selectedwidget)
        self.hiddenCheck.toggled.connect(self._save_selectedwidget)

    def disconnectsignals(self):
        try:
            self.formLabelText.textChanged.disconnect(self._save_formname)
            self.layerCombo.currentIndexChanged.disconnect(self._save_layer)
            self.formtypeCombo.currentIndexChanged.disconnect(self._save_formtype)

            #widget settings
            self.fieldList.currentIndexChanged.disconnect(self._save_widgetfield)
            self.requiredCheck.toggled.disconnect(self._save_selectedwidget)
            self.defaultvalueText.textChanged.disconnect(self._save_default)
            self.widgetCombo.currentIndexChanged.disconnect(self._save_selectedwidget)
            self.widgetCombo.currentIndexChanged.disconnect(self.swapwidgetconfig)
            self.nameText.textChanged.disconnect(self._save_selectedwidget)
            self.readonlyCombo.currentIndexChanged.disconnect(self._save_selectedwidget)
            self.hiddenCheck.toggled.disconnect(self._save_selectedwidget)
        except TypeError:
            pass

    def setform(self, form):
        """
        Update the UI with the currently selected form.
        """

        def getfirstlayer():
            index = self.formlayers.index(0,0)
            layer = index.data(Qt.UserRole)
            layer = layer.name()
            return layer

        def loadwidgets(widget):
            """
            Load the widgets into widgets model
            """
            self.widgetmodel.clear()
            self.widgetmodel.loadwidgets(form.widgets)

        def findlayer(layername):
            index = self.formlayersmodel.findlayer(layername)
            index = self.formlayers.mapFromSource(index)
            layer = index.data(Qt.UserRole)
            return index, layer


        self.disconnectsignals()

        self.form = form

        settings = form.settings
        label = form.label
        layername = settings.setdefault('layer', getfirstlayer())
        layerindex, layer = findlayer(layername)
        if not layer or not layerindex.isValid():
            return

        formtype = settings.setdefault('type', 'auto')
        widgets = settings.setdefault('widgets', [])

        self.formLabelText.setText(label)
        folderurl = "<a href='{path}'>{name}</a>".format(path=form.folder, name=os.path.basename(form.folder))
        self.formfolderLabel.setText(folderurl)
        self.layerCombo.setCurrentIndex(layerindex.row())
        self.updatefields(layer)

        index = self.formtypeCombo.findText(formtype)
        if index == -1:
            self.formtypeCombo.insertItem(0, formtype)
            self.formtypeCombo.setCurrentIndex(0)
        else:
            self.formtypeCombo.setCurrentIndex(index)

        loadwidgets(widgets)

        # Set the first widget
        index = self.widgetmodel.index(0, 0)
        if index.isValid():
            self.widgetlist.setCurrentIndex(index)
            self.updatecurrentwidget(index, None)

        self.connectsignals()

    def updatefields(self, layer):
        """
        Update the UI with the fields for the selected layer.
        """
        self.fieldsmodel.setLayer(layer)

    def setconfigwidget(self, configwidget, config):
        """
        Set the active config widget.
        """

        try:
            configwidget.widgetdirty.disconnect(self._save_selectedwidget)
        except TypeError:
            pass

        #self.descriptionLabel.setText(configwidget.description)
        self.widgetstack.setCurrentWidget(configwidget)
        configwidget.setconfig(config)

        configwidget.widgetdirty.connect(self._save_selectedwidget)

    def updatecurrentwidget(self, index, _):
        """
        Update the UI with the config for the current selected widget.
        """
        if not index.isValid():
            return

        widget = index.data(Qt.UserRole)
        widgettype = widget['widget']
        field = widget['field']
        required = widget.setdefault('required', False)
        name = widget.setdefault('name', field)
        default = widget.setdefault('default', '')
        readonly = widget.setdefault('read-only-rules', [])
        hidden = widget.setdefault('hidden', False)

        try:
            data = readonly[0]
        except:
            data = 'never'

        self.readonlyCombo.blockSignals(True)
        index = self.readonlyCombo.findData(data)
        self.readonlyCombo.setCurrentIndex(index)
        self.readonlyCombo.blockSignals(False)

        self.defaultvalueText.blockSignals(True)
        if not isinstance(default, dict):
            self.defaultvalueText.setText(default)
            self.defaultvalueText.setEnabled(True)
            self.expressionButton.setEnabled(True)
        else:
            # TODO Handle the more advanced default values.
            self.defaultvalueText.setText("Advanced default set in config")
            self.defaultvalueText.setEnabled(False)
            self.expressionButton.setEnabled(False)
        self.defaultvalueText.blockSignals(False)

        self.nameText.blockSignals(True)
        self.nameText.setText(name)
        self.nameText.blockSignals(False)

        self.requiredCheck.blockSignals(True)
        self.requiredCheck.setChecked(required)
        self.requiredCheck.blockSignals(False)

        self.hiddenCheck.blockSignals(True)
        self.hiddenCheck.setChecked(hidden)
        self.hiddenCheck.blockSignals(False)

        if not field is None:
            self.fieldList.blockSignals(True)
            index = self.fieldList.findData(field.lower(), QgsFieldModel.FieldNameRole)
            if index > -1:
                self.fieldList.setCurrentIndex(index)
            else:
                self.fieldList.setEditText(field)
            self.fieldList.blockSignals(False)

        index = self.widgetCombo.findText(widgettype)
        self.widgetCombo.blockSignals(True)
        if index > -1:
            self.widgetCombo.setCurrentIndex(index)
        self.widgetCombo.blockSignals(False)

        self.updatewidgetconfig(config=widget.setdefault('config', {}))

    def _saveproject(self):
        """
        Save the project config to disk.
        """
        title = self.titleText.text()
        description = self.descriptionText.toPlainText()
        version = str(self.versionText.text())

        settings = self.project.settings
        settings['title'] = title
        settings['description'] = description
        settings['version'] = version

        form = self.currentform
        if form:
            form.settings['widgets'] = list(self.widgetmodel.widgets())
            logger.debug(form.settings)

        self.project.save()
        self.projectsaved.emit()
Beispiel #8
0
class NFile(QObject):
    """
    SIGNALS:
    @askForSaveFileClosing(QString)
    @fileClosing(QString)
    @fileChanged()
    @willDelete(PyQt_PyObject, PyQt_PyObject)
    @willOverWrite(PyQt_PyObject, QString, QString)
    @willMove(Qt_PyQtObject, QString, QString)
    @willSave(QString, QString)
    @savedAsNewFile(PyQt_PyObject, QString, QString)
    @gotAPath(PyQt_PyObject)
    @willAttachToExistingFile(PyQt_PyObject, QString)
    """

    def __init__(self, path=None):
        """
        """
        self._file_path = path
        self.__created = False
        self.__watcher = None
        self.__mtime = None
        super(NFile, self).__init__()
        if not self._exists():
            self.__created = True

    @property
    def file_name(self):
        """"Returns filename of nfile"""
        file_name = None
        if self._file_path is None:
            file_name = translations.TR_NEW_DOCUMENT
        else:
            file_name = get_basename(self._file_path)
        return file_name

    @property
    def display_name(self):
        """Returns a pretty name to be displayed by tabs"""
        display_name = self.file_name
        if not self._file_path is None and not self.has_write_permission():
            display_name += translations.TR_READ_ONLY
        return display_name

    @property
    def is_new_file(self):
        return self.__created

    def file_ext(self):
        """"Returns extension of nfile"""
        if self._file_path is None:
            return ''
        return get_file_extension(self._file_path)

    @property
    def file_path(self):
        """"Returns file path of nfile"""
        return self._file_path

    def start_watching(self):
        """Create a file system watcher and connect its fileChanged
        SIGNAL to our _file_changed SLOT"""
        if not self.__watcher:
            self.__watcher = QFileSystemWatcher(self)
            self.connect(self.__watcher,
                         SIGNAL("fileChanged(const QString&)"),
                         self._file_changed)
        if self._file_path is not None:
            self.__mtime = os.path.getmtime(self._file_path)
            self.__watcher.addPath(self._file_path)

    def _file_changed(self, path):
        current_mtime = os.path.getmtime(self._file_path)
        if current_mtime != self.__mtime:
            self.__mtime = current_mtime
            self.emit(SIGNAL("fileChanged()"))

    def has_write_permission(self):
        if not self._exists():
            return True
        return os.access(self._file_path, os.W_OK)

    def _exists(self):
        """
        Check if we have been created with a path and if such path exists
        In case there is no path, we are most likely a new file.
        """
        file_exists = False
        if self._file_path and os.path.exists(self._file_path):
            file_exists = True
        return file_exists

    def attach_to_path(self, new_path):
        if os.path.exists(new_path):
            signal_handler = SignalFlowControl()
            self.emit(
                SIGNAL("willAttachToExistingFile(PyQt_PyObject, QString)"),
                signal_handler,
                new_path)
            if signal_handler.stopped():
                    return
        self._file_path = new_path
        self.emit(SIGNAL("gotAPath(PyQt_PyObject)"), self)
        return self._file_path

    def create(self):
        if self.__created:
            self.save("")
        self.__created = False

    def save(self, content, path=None):
        """
        Write a temprorary file with .tnj extension and copy it over the
        original one.
        .nsf = Ninja Swap File
        #FIXME: Where to locate addExtension, does not fit here
        """
        new_path = False
        if path:
            self.attach_to_path(path)
            new_path = True

        save_path = self._file_path

        if not save_path:
            raise NinjaNoFileNameException("I am asked to write a "
                                           "file but no one told me where")
        swap_save_path = "%s.nsp" % save_path

        # If we have a file system watcher, remove the file path
        # from its watch list until we are done making changes.
        if self.__watcher:
            self.__watcher.removePath(save_path)

        flags = QIODevice.WriteOnly | QIODevice.Truncate
        f = QFile(swap_save_path)
        if settings.use_platform_specific_eol():
            flags |= QIODevice.Text

        if not f.open(flags):
            raise NinjaIOException(f.errorString())

        stream = QTextStream(f)
        encoding = get_file_encoding(content)
        if encoding:
            stream.setCodec(encoding)

        encoded_stream = stream.codec().fromUnicode(content)
        f.write(encoded_stream)
        f.flush()
        f.close()
        #SIGNAL: Will save (temp, definitive) to warn folder to do something
        self.emit(SIGNAL("willSave(QString, QString)"),
                  swap_save_path, save_path)
        self.__mtime = os.path.getmtime(swap_save_path)
        shutil.move(swap_save_path, save_path)
        self.reset_state()

        # If we have a file system watcher, add the saved path back
        # to its watch list, otherwise create a watcher and start
        # watching
        if self.__watcher:
            if new_path:
                self.__watcher.removePath(self.__watcher.files()[0])
                self.__watcher.addPath(self._file_path)
            else:
                self.__watcher.addPath(save_path)
        else:
            self.start_watching()
        return self

    def reset_state(self):
        """
        #FIXE: to have a ref to changed I need to have the doc here
        """
        self.__created = False

    def read(self, path=None):
        """
        Read the file or fail
        """
        open_path = path and path or self._file_path
        self._file_path = open_path
        if not self._file_path:
            raise NinjaNoFileNameException("I am asked to read a "
                                           "file but no one told me from where")
        try:
            with open(open_path, 'rU') as f:
                content = f.read()
        except IOError as reason:
            raise NinjaIOException(reason)
        return content

    def move(self, new_path):
        """
        Phisically move the file
        """
        if self._exists():
            signal_handler = SignalFlowControl()
            #SIGNALL: WILL MOVE TO, to warn folder to exist
            self.emit(SIGNAL("willMove(Qt_PyQtObject, QString, QString)"),
                      signal_handler,
                      self._file_path,
                      new_path)
            if signal_handler.stopped():
                return
            if os.path.exists(new_path):
                signal_handler = SignalFlowControl()
                self.emit(
                    SIGNAL("willOverWrite(PyQt_PyObject, QString, QString)"),
                    signal_handler, self._file_path, new_path)
                if signal_handler.stopped():
                    return
            if self.__watcher:
                self.__watcher.removePath(self._file_path)
            shutil.move(self._file_path, new_path)
            if self.__watcher:
                self.__watcher.addPath(new_path)
        self._file_path = new_path
        return

    def copy(self, new_path):
        """
        Copy the file to a new path
        """
        if self._exists():
            signal_handler = SignalFlowControl()
            #SIGNALL: WILL COPY TO, to warn folder to exist
            self.emit(SIGNAL("willCopyTo(Qt_PyQtObject, QString, QString)"),
                      signal_handler, self._file_path, new_path)
            if signal_handler.stopped():
                return
            if os.path.exists(new_path):
                signal_handler = SignalFlowControl()
                self.emit(
                    SIGNAL("willOverWrite(PyQt_PyObject, QString, QString)"),
                    signal_handler, self._file_path, new_path)
                if signal_handler.stopped():
                    return

            shutil.copy(self._file_path, new_path)

    def delete(self, force=False):
        """
        This deletes the object and closes the file.
        """
        #if created but exists this file migth to someone else
        self.close()
        if ((not self.__created) or force) and self._exists():
            DEBUG("Deleting our own NFile %s" % self._file_path)
            signal_handler = SignalFlowControl()
            self.emit(SIGNAL("willDelete(PyQt_PyObject, PyQt_PyObject)"),
                      signal_handler, self)
            if not signal_handler.stopped():
                if self.__watcher:
                    self.__watcher.removePath(self._file_path)
                os.remove(self._file_path)

    def close(self, force_close=False):
        """
        Lets let people know we are going down so they can act upon
        As you can see close does nothing but let everyone know that we are
        not saved yet
        """
        DEBUG("About to close NFile")
        self.emit(SIGNAL("fileClosing(QString, bool)"),
                  self._file_path, force_close)

    def remove_watcher(self):
        if self.__watcher:
            self.__watcher.removePath(self._file_path)
Beispiel #9
0
class _FileWatcher(QObject):
    """File watcher.
    
    QFileSystemWatcher notifies client about any change (file access mode, modification date, etc.)
    But, we need signal, only after file contents had been changed
    """
    modified = pyqtSignal(bool)
    removed = pyqtSignal(bool)

    def __init__(self, path):
        QObject.__init__(self)
        self._contents = None
        self._watcher = QFileSystemWatcher()
        self._timer = None
        self._path = path
        self.setPath(path)
        self.enable()

    def __del__(self):
        self._stopTimer()

    def enable(self):
        """Enable signals from the watcher
        """
        self._watcher.fileChanged.connect(self._onFileChanged)

    def disable(self):
        """Disable signals from the watcher
        """
        self._watcher.fileChanged.disconnect(self._onFileChanged)
        self._stopTimer()

    def setContents(self, contents):
        """Set file contents. Watcher uses it to compare old and new contents of the file.
        """
        self._contents = contents
        # Qt File watcher may work incorrectly, if file was not existing, when it started
        self.setPath(self._path)

    def setPath(self, path):
        """Path had been changed or file had been created. Set new path
        """
        if self._watcher.files():
            self._watcher.removePaths(self._watcher.files())
        if path is not None and os.path.isfile(path):
            self._watcher.addPath(path)
        self._path = path

    def _emitModifiedStatus(self):
        """Emit self.modified signal with right status
        """
        isModified = self._contents != self._safeRead(self._path)
        self.modified.emit(isModified)

    def _onFileChanged(self):
        """File changed. Emit own signal, if contents changed
        """
        if os.path.exists(self._path):
            self._emitModifiedStatus()
        else:
            self.removed.emit(True)
            self._startTimer()

    def _startTimer(self):
        """Init a timer.
        It is used for monitoring file after deletion.
        Git removes file, than restores it.
        """
        if self._timer is None:
            self._timer = QTimer()
            self._timer.setInterval(500)
            self._timer.timeout.connect(self._onCheckIfDeletedTimer)
        self._timer.start()

    def _stopTimer(self):
        """Stop timer, if exists
        """
        if self._timer is not None:
            self._timer.stop()

    def _onCheckIfDeletedTimer(self):
        """Check, if file has been restored
        """
        if os.path.exists(self._path):
            self.removed.emit(False)
            self._emitModifiedStatus()
            self.setPath(
                self._path
            )  # restart Qt file watcher after file has been restored
            self._stopTimer()

    def _safeRead(self, path):
        """Read file. Ignore exceptions
        """
        try:
            with open(path, 'rb') as file:
                return file.read()
        except (OSError, IOError):
            return None
Beispiel #10
0
class ProjectWidget(Ui_Form, QWidget):
    SampleWidgetRole = Qt.UserRole + 1
    projectsaved = pyqtSignal()
    projectupdated = pyqtSignal(object)
    projectloaded = pyqtSignal(object)
    selectlayersupdated = pyqtSignal(list)

    def __init__(self, parent=None):
        super(ProjectWidget, self).__init__(parent)
        self.setupUi(self)
        self.project = None
        self.bar = None
        self.roamapp = None

        menu = QMenu()

        # self.roamVersionLabel.setText("You are running IntraMaps Roam version {}".format(roam.__version__))

        self.openProjectFolderButton.pressed.connect(self.openprojectfolder)
        self.openinQGISButton.pressed.connect(self.openinqgis)
        self.depolyProjectButton.pressed.connect(self.deploy_project)
        self.depolyInstallProjectButton.pressed.connect(functools.partial(self.deploy_project, True))

        self.filewatcher = QFileSystemWatcher()
        self.filewatcher.fileChanged.connect(self.qgisprojectupdated)

        self.projectupdatedlabel.linkActivated.connect(self.reloadproject)
        self.projectupdatedlabel.hide()

        # self.setpage(4)
        self.currentnode = None
        self.form = None

        qgislocation = r'C:\OSGeo4W\bin\qgis.bat'
        qgislocation = roam.config.settings.setdefault('configmanager', {}) \
            .get('qgislocation', "")

        self.qgispathEdit.setText(qgislocation)
        self.qgispathEdit.textChanged.connect(self.save_qgis_path) 
        self.filePickerButton.pressed.connect(self.set_qgis_path)

        self.connect_page_events()

        QgsProject.instance().readProject.connect(self.projectLoaded)

    def connect_page_events(self):
        """
        Connect the events from all the pages back to here
        """
        for index in range(self.stackedWidget.count()):
            widget = self.stackedWidget.widget(index)
            if hasattr(widget, "raiseMessage"):
                widget.raiseMessage.connect(self.bar.pushMessage)

    def set_qgis_path(self):
        """
        Set the location of the QGIS install.  We need the path to be able to create Roam
        projects
        """
        path = QFileDialog.getOpenFileName(self, "Select QGIS install file", filter="(*.bat)")
        if not path:
            return
        self.qgispathEdit.setText(path)
        self.save_qgis_path(path)

    def save_qgis_path(self, path):
        """
        Save the QGIS path back to the Roam config.
        """
        roam.config.settings['configmanager'] = {'qgislocation': path}
        roam.config.save()

    def setpage(self, page, node, refreshingProject=False):
        """
        Set the current page in the config manager.  We pass the project into the current
        page so that it knows what the project is.
        """
        self.currentnode = node

        if not refreshingProject and self.project:
            self.write_config_currentwidget()
        else:
            roam.utils.info("Reloading project. Not saving current config values")

        self.unload_current_widget()

        # Set the new widget for the selected page
        self.stackedWidget.setCurrentIndex(page)

        widget = self.stackedWidget.currentWidget()
        print "New widget {}".format(widget.objectName())
        if hasattr(widget, "set_project"):
            widget.set_project(self.project, self.currentnode)

    def unload_current_widget(self):
        print "NOTIFY!!"
        widget = self.stackedWidget.currentWidget()
        print widget
        if hasattr(widget, "unload_project"):
            widget.unload_project()

    def write_config_currentwidget(self):
        """
        Call the write config command on the current widget.
        """
        widget = self.stackedWidget.currentWidget()
        if hasattr(widget, "write_config"):
            roam.utils.debug("Write config for {} in project {}".format(widget.objectName(), self.project.name))
            widget.write_config()

    def deploy_project(self, with_data=False):
        """
        Run the step to deploy a project. Projects are deplyed as a bundled zip of the project folder.
        """
        if self.roamapp.sourcerun:
            base = os.path.join(self.roamapp.apppath, "..")
        else:
            base = self.roamapp.apppath

        default = os.path.join(base, "roam_serv")
        path = roam.config.settings.get("publish", {}).get("path", '')
        if not path:
            path = default

        path = os.path.join(path, "projects")

        if not os.path.exists(path):
            os.makedirs(path)

        self._saveproject(update_version=True, reset_save_point=True)
        options = {}

        bundle.bundle_project(self.project, path, options, as_install=with_data)

    def setaboutinfo(self):
        """
        Set the current about info on the widget
        """
        self.versionLabel.setText(roam.__version__)
        self.qgisapiLabel.setText(unicode(QGis.QGIS_VERSION))

    def selectlayerschanged(self, *args):
        """
        Run the updates when the selection layers have changed
        """
        self.formlayers.setSelectLayers(self.project.selectlayers)
        self.selectlayersupdated.emit(self.project.selectlayers)

    def reloadproject(self, *args):
        """
        Reload the project. At the moment this will drop any unsaved changes to the config.
        Note: Should look at making sure it doesn't do that because it's not really needed.
        """
        self.projectupdated.emit(self.project)
        # self.setproject(self.project)

    def qgisprojectupdated(self, path):
        """
        Show a message when the QGIS project file has been updated.
        """
        self.projectupdatedlabel.show()
        self.projectupdatedlabel.setText("The QGIS project has been updated. <a href='reload'> "
                                         "Click to reload</a>. <b style=\"color:red\">Unsaved data will be lost</b>")

    def openinqgis(self):
        """
        Open a QGIS session for the user to config the project layers.
        """
        try:
            openqgis(self.project.projectfile)
        except OSError:
            self.bar.pushMessage("Looks like I couldn't find QGIS",
                                 "Check qgislocation in roam.config", QgsMessageBar.WARNING)

    def openprojectfolder(self):
        """
        Open the project folder in the file manager for the OS.
        """
        folder = self.project.folder
        openfolder(folder)

    def setproject(self, project):
        """
        Set the widgets active project.
        """
        self.unload_current_widget()

        if self.project:
            savelast = QMessageBox.question(self,
                                    "Save Current Project",
                                    "Save {}?".format(self.project.name),
                                    QMessageBox.Save | QMessageBox.No)
            if savelast == QMessageBox.Accepted:
                self._saveproject()

        self.filewatcher.removePaths(self.filewatcher.files())
        self.projectupdatedlabel.hide()
        self._closeqgisproject()

        if project.valid:
            self.startsettings = copy.deepcopy(project.settings)
            self.project = project
            self.projectlabel.setText(project.name)
            self.loadqgisproject(project, self.project.projectfile)

    def projectLoaded(self):
        self.filewatcher.addPath(self.project.projectfile)
        self.projectloaded.emit(self.project)

    def loadqgisproject(self, project, projectfile):
        QDir.setCurrent(os.path.dirname(project.projectfile))
        fileinfo = QFileInfo(project.projectfile)

        # No idea why we have to set this each time.  Maybe QGIS deletes it for
        # some reason.
        self.badLayerHandler = BadLayerHandler(callback=self.missing_layers)
        QgsProject.instance().setBadLayerHandler(self.badLayerHandler)
        QgsProject.instance().read(fileinfo)

    def missing_layers(self, missinglayers):
        """
        Handle any and show any missing layers.
        """
        self.project.missing_layers = missinglayers

    def _closeqgisproject(self):
        """
        Close the current QGIS project and clean up after..
        """
        QGIS.close_project()

    def _saveproject(self, update_version=False, reset_save_point=False):
        """
        Save the project config to disk.
        """
        if not self.project:
            return

        roam.utils.info("Saving project: {}".format(self.project.name))

        self.write_config_currentwidget()
        # self.project.dump_settings()
        self.project.save(update_version=update_version, reset_save_point=reset_save_point)
        self.filewatcher.removePaths(self.filewatcher.files())
        QgsProject.instance().write()
        self.filewatcher.addPath(self.project.projectfile)
        self.projectsaved.emit()
Beispiel #11
0
class ProjectWidget(Ui_Form, QWidget):
    SampleWidgetRole = Qt.UserRole + 1
    projectsaved = pyqtSignal()
    projectupdated = pyqtSignal()
    projectloaded = pyqtSignal(object)
    selectlayersupdated = pyqtSignal(list)

    def __init__(self, parent=None):
        super(ProjectWidget, self).__init__(parent)
        self.setupUi(self)
        self.project = None
        self.mapisloaded = False
        self.bar = None
        self.roamapp = None

        menu = QMenu()
        self.canvas.setCanvasColor(Qt.white)

        self.canvas.enableAntiAliasing(True)
        self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor)
        self.canvas.mapRenderer().setLabelingEngine(QgsPalLabeling())

        # self.roamVersionLabel.setText("You are running IntraMaps Roam version {}".format(roam.__version__))

        self.openProjectFolderButton.pressed.connect(self.openprojectfolder)
        self.openinQGISButton.pressed.connect(self.openinqgis)
        self.depolyProjectButton.pressed.connect(self.deploy_project)
        self.depolyInstallProjectButton.pressed.connect(functools.partial(self.deploy_project, True))

        self.filewatcher = QFileSystemWatcher()
        self.filewatcher.fileChanged.connect(self.qgisprojectupdated)

        self.projectupdatedlabel.linkActivated.connect(self.reloadproject)
        self.projectupdatedlabel.hide()

        # self.setpage(4)
        self.currentnode = None
        self.form = None

        qgislocation = r'C:\OSGeo4W\bin\qgis.bat'
        qgislocation = roam.config.settings.setdefault('configmanager', {}) \
            .setdefault('qgislocation', qgislocation)

        self.qgispathEdit.setText(qgislocation)
        self.qgispathEdit.textChanged.connect(self.save_qgis_path) 
        self.filePickerButton.pressed.connect(self.set_qgis_path)

    def set_qgis_path(self):
        path = QFileDialog.getOpenFileName(self, "Select QGIS install file", filter="(*.bat)")
        if not path:
            return
        self.qgispathEdit.setText(path)
        self.save_qgis_path(path)

    def save_qgis_path(self, path):
        roam.config.settings['configmanager'] = {'qgislocation': path}
        roam.config.save()

    def setpage(self, page, node):
        self.currentnode = node

        self.write_config_currentwidget()

        self.stackedWidget.setCurrentIndex(page)
        if self.project:
            print self.project.dump_settings()

        widget = self.stackedWidget.currentWidget()
        if hasattr(widget, "set_project"):
            widget.set_project(self.project, self.currentnode)

    def write_config_currentwidget(self):
        widget = self.stackedWidget.currentWidget()
        if hasattr(widget, "write_config"):
            widget.write_config()

    def deploy_project(self, with_data=False):
        if self.roamapp.sourcerun:
            base = os.path.join(self.roamapp.apppath, "..")
        else:
            base = self.roamapp.apppath

        default = os.path.join(base, "roam_serv")
        path = roam.config.settings.get("publish", {}).get("path", '')
        if not path:
            path = default

        path = os.path.join(path, "projects")

        if not os.path.exists(path):
            os.makedirs(path)

        self._saveproject()
        options = {}

        bundle.bundle_project(self.project, path, options, as_install=with_data)

    def setaboutinfo(self):
        self.versionLabel.setText(roam.__version__)
        self.qgisapiLabel.setText(str(QGis.QGIS_VERSION))

    def checkcapturelayers(self):
        haslayers = self.project.hascapturelayers()
        self.formslayerlabel.setVisible(not haslayers)
        return haslayers


    def selectlayerschanged(self, *args):
        self.formlayers.setSelectLayers(self.project.selectlayers)
        self.checkcapturelayers()
        self.selectlayersupdated.emit(self.project.selectlayers)

    def reloadproject(self, *args):
        self.setproject(self.project)
        self.projectupdated.emit()

    def qgisprojectupdated(self, path):
        self.projectupdatedlabel.show()
        self.projectupdatedlabel.setText("The QGIS project has been updated. <a href='reload'> "
                                         "Click to reload</a>. <b style=\"color:red\">Unsaved data will be lost</b>")

    def openinqgis(self):
        projectfile = self.project.projectfile
        qgislocation = r'C:\OSGeo4W\bin\qgis.bat'
        qgislocation = roam.config.settings.setdefault('configmanager', {}) \
            .setdefault('qgislocation', qgislocation)

        try:
            openqgis(projectfile, qgislocation)
        except OSError:
            self.bar.pushMessage("Looks like I couldn't find QGIS",
                                 "Check qgislocation in roam.config", QgsMessageBar.WARNING)

    def openprojectfolder(self):
        folder = self.project.folder
        openfolder(folder)

    def setproject(self, project, loadqgis=True):
        """
        Set the widgets active project.
        """
        self.mapisloaded = False
        self.filewatcher.removePaths(self.filewatcher.files())
        self.projectupdatedlabel.hide()
        self._closeqgisproject()

        if project.valid:
            self.startsettings = copy.deepcopy(project.settings)
            self.project = project
            self.projectlabel.setText(project.name)
            self.loadqgisproject(project, self.project.projectfile)
            self.filewatcher.addPath(self.project.projectfile)
            self.projectloaded.emit(self.project)

    def loadqgisproject(self, project, projectfile):
        QDir.setCurrent(os.path.dirname(project.projectfile))
        fileinfo = QFileInfo(project.projectfile)
        self.projectLocationLabel.setText("Project File: {}".format(os.path.basename(project.projectfile)))
        QgsProject.instance().read(fileinfo)

    def _closeqgisproject(self):
        if self.canvas.isDrawing():
            return

        self.canvas.freeze(True)
        QgsMapLayerRegistry.instance().removeAllMapLayers()
        self.canvas.freeze(False)

    def loadmap(self):
        if self.mapisloaded:
            return

        # This is a dirty hack to work around the timer that is in QgsMapCanvas in 2.2.
        # Refresh will stop the canvas timer
        # Repaint will redraw the widget.
        # loadmap is only called once per project load so it's safe to do this here.
        self.canvas.refresh()
        self.canvas.repaint()

        parser = roam.projectparser.ProjectParser.fromFile(self.project.projectfile)
        canvasnode = parser.canvasnode
        self.canvas.mapRenderer().readXML(canvasnode)
        self.canvaslayers = parser.canvaslayers()
        self.canvas.setLayerSet(self.canvaslayers)
        self.canvas.updateScale()
        self.canvas.refresh()

        self.mapisloaded = True

    def _saveproject(self):
        """
        Save the project config to disk.
        """
        self.write_config_currentwidget()
        # self.project.dump_settings()
        self.project.save(update_version=True)
        self.projectsaved.emit()