コード例 #1
0
ファイル: projectwidget.py プロジェクト: transformaps/Roam
    def __init__(self, parent=None):
        super(ProjectWidget, self).__init__(parent)
        self.setupUi(self)
        self.project = None
        self.bar = None
        self.roamapp = None
        self.logger = roam.utils.logger

        self.openProjectFolderButton.pressed.connect(self.openprojectfolder)
        self.openinQGISButton.pressed.connect(self.openinqgis)
        # TODO Move these to the publish page

        self.savePageButton.pressed.connect(self.savePage)

        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

        self.connect_page_events()

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

        self.btnNewForm.pressed.connect(self._new_form)
        self.lblSelectLayersError.setVisible(False)
        self.formNameEdit.textChanged.connect(self._update_form_button)

        self.btnNewForm.setEnabled(False)
コード例 #2
0
ファイル: projectwidget.py プロジェクト: skylning/Roam
    def __init__(self, parent=None):
        super(ProjectWidget, self).__init__(parent)
        self.setupUi(self)
        self.project = None
        self.bar = None
        self.roamapp = None
        self.logger = roam.utils.logger

        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)
        # TODO Move these to the publish page

        # self.depolyProjectButton.pressed.connect(self.deploy_project)
        # self.depolyInstallProjectButton.pressed.connect(functools.partial(self.deploy_project, True))
        self.savePageButton.pressed.connect(self.savePage)

        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

        self.connect_page_events()

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

        self.btnNewForm.pressed.connect(self._new_form)
        self.lblSelectLayersError.setVisible(False)
        self.formNameEdit.textChanged.connect(self._update_form_button)

        self.btnNewForm.setEnabled(False)
コード例 #3
0
ファイル: projectwidget.py プロジェクト: skylning/Roam
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
        self.logger = roam.utils.logger

        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)
        # TODO Move these to the publish page

        # self.depolyProjectButton.pressed.connect(self.deploy_project)
        # self.depolyInstallProjectButton.pressed.connect(functools.partial(self.deploy_project, True))
        self.savePageButton.pressed.connect(self.savePage)

        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

        self.connect_page_events()

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

        self.btnNewForm.pressed.connect(self._new_form)
        self.lblSelectLayersError.setVisible(False)
        self.formNameEdit.textChanged.connect(self._update_form_button)

        self.btnNewForm.setEnabled(False)

    def _update_form_button(self, value):
        if not value:
            self.btnNewForm.setEnabled(False)

        if configmanager.projects.directory_exsits(value, self.project.folder):
            self.btnNewForm.setEnabled(False)
        else:
            self.btnNewForm.setEnabled(True)

    def _new_form(self):
        """
        Create a new form for the current project.
        :return:
        """

        if not self.project.selectlayers:
            QMessageBox.question(None,
                                 "Select layers required",
                                 "Use the Select Layers item to select layers that can be used for forms",
                                 QMessageBox.Ok)
            return

        if not self.project:
            return

        name = configmanager.projects.new_directory_name(self.formNameEdit.text(), "Form")

        if configmanager.projects.directory_exsits(name, self.project.folder):
            return

        form = configmanager.projects.create_form(self.project, name)

        self.formNameEdit.clear()

        ConfigEvents.emit_formCreated(form)

    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 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)

        self.project = node.project


        widget = self.stackedWidget.currentWidget()
        widget.roamapp = self.roamapp
        if hasattr(widget, "set_project"):
            widget.set_project(self.project, self.currentnode)
        if hasattr(widget, "set_data"):
            widget.set_data({
                "project": node.project,
                "config": self.roamapp.config,
                "node": self.currentnode,
                "app_root": self.roamapp.approot,
                "data_root": self.roamapp.data_folder,
                "projects_root": self.roamapp.projectsroot,
                "profile_root": self.roamapp.profileroot
            })

        if node.title:
            title = node.title
        elif self.project:
            title = self.project.name
        else:
            title = "No title set"
        self.projectlabel.setText(title)

        if node.project:
            self.openProjectFolderButton.show()
            self.openinQGISButton.show()
        else:
            self.openProjectFolderButton.hide()
            self.openinQGISButton.hide()

        self.savePageButton.setVisible(node.saveable)
        self.formNameEdit.clear()

    @property
    def widget(self):
        """
        :return: The current widget that is set.
        """
        return self.stackedWidget.currentWidget()

    def unload_current_widget(self):
        widget = self.stackedWidget.currentWidget()
        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(str(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 QGISNotFound as ex:
            self.bar.pushMessage(ex.message, Qgis.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()

        self.startsettings = copy.deepcopy(project.settings)
        self.project = project
        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)
        project.load_project()

    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 savePage(self, closing=False):
        """
        Save the current page settings.
        """
        if hasattr(self.widget, "write_config"):
            self.logger.debug("Saving widget")
            if closing:
                self.widget.on_closing()
            self.widget.write_config()
        else:
            self.logger.debug("Missing write_config for {}".format(self.widget.__class__.__name__))

        if self.project:
            self._saveproject()
            return

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

        self.logger.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()
コード例 #4
0
 def activateFileWatcher(self):
     self.filewatcher = QFileSystemWatcher([self.excelPath])
     self.filewatcher.fileChanged.connect(self.excel_changed)
コード例 #5
0
class Syncer(QObject):
    def __init__(self, settings):
        QObject.__init__(self)
        self.s = settings
        self.filewatcher = None
        self.excelName = settings.excelName  # the layer name
        self.excelSheetName = settings.excelSheetName
        self.excelKeyName = settings.excelKeyName
        self.excelFkIdx = field_idx_from_name(self.excelName,
                                              self.excelKeyName)
        self.excelPath = layer_from_name(
            self.excelName).publicSource().split('|')[0]
        self.excelKeyName = field_name_from_idx(self.excelName,
                                                self.excelFkIdx)
        # shpfile layer
        self.shpName = settings.shpName
        self.shpKeyName = settings.shpKeyName
        self.shpKeyIdx = field_idx_from_name(self.shpName, self.shpKeyName)
        self.skipLines = settings.skipLines

        layer = layer_from_name(self.shpName)
        editFormConfig = layer.editFormConfig()
        editFormConfig.setSuppress(
            QgsEditFormConfig.SuppressOn if settings.
            hideDialog else QgsEditFormConfig.SuppressOff)
        layer.setEditFormConfig(editFormConfig)

        self.join()
        self.clear_edit_state()
        self.initialSync()

    def join(self):
        # join the shp layer to the excel layer, non cached
        # TODO: Ignore if already joined?
        shpLayer = layer_from_name(self.shpName)
        jinfo = QgsVectorLayerJoinInfo()
        jinfo.setJoinFieldName(self.excelKeyName)
        jinfo.setTargetFieldName(self.shpKeyName)
        jinfo.setJoinLayer(layer_from_name(self.excelName))
        jinfo.setUsingMemoryCache(False)
        jinfo.setPrefix('')
        for jinfo2 in shpLayer.vectorJoins():
            if jinfo2 == jinfo:
                info("Join already exists. Will not create it again")
                return
        info("Adding join between master and slave layers")
        shpLayer.addJoin(jinfo)

    def reload_excel(self):
        layer = layer_from_name(self.excelName)
        fsize = os.stat(self.excelPath).st_size
        info("fsize " + str(fsize))
        if fsize == 0:
            info("File empty. Won't reload yet")
            return
        layer.dataProvider().forceReload()
        show_message_bar("Excel reloaded from disk.")

    def excel_changed(self):
        info("Excel changed on disk - need to sync")
        self.reload_excel()
        self.update_shp_from_excel()

    def get_max_id(self):

        layer = layer_from_name(self.shpName)
        if layer.dataProvider().featureCount() == 0:
            return 0
        maxVal = layer.maximumValue(self.shpKeyIdx)
        if maxVal is None:
            return 0
        elif type(maxVal) == QVariant and maxVal.isNull():
            return 0
        else:
            return maxVal

    def renameIds(self, fidToId):
        layer = layer_from_name(self.shpName)
        layer.startEditing()
        feats = query_layer_for_fids(self.shpName, fidToId.keys())
        for f in feats:
            layer.changeAttributeValue(f.id(), self.shpKeyIdx, fidToId[f.id()])
        layer.commitChanges()

    def added_geom(self, layerId, feats):
        info("added feats " + str(feats))
        layer = layer_from_name(self.shpName)
        maxFk = self.get_max_id()
        for i, new_feat in enumerate(
                layer.getFeatures(QgsFeatureRequest().setFilterFids(
                    [f.id() for f in feats]))):
            _id = maxFk + i + 1
            new_feat.setAttribute(self.shpKeyName, _id)
            self.shpAdd.append(new_feat)

    def removed_geom_precommit(self, fids):
        # info("Removed fids"+str(fids))
        fks_to_remove = get_fk_set(self.shpName,
                                   self.shpKeyName,
                                   skipFirst=0,
                                   fids=fids,
                                   useProvider=True)
        self.shpRemove = self.shpRemove.union(fks_to_remove)
        info("feat ids to remove" + str(self.shpRemove))

    def changed_geom(self, layerId, geoms):
        fids = list(geoms.keys())
        feats = query_layer_for_fids(self.shpName, fids)
        fks_to_change = get_fk_set(self.shpName,
                                   self.shpKeyName,
                                   skipFirst=0,
                                   fids=fids)
        self.shpChange = {k: v for (k, v) in zip(fks_to_change, feats)}
        # info("changed"+str(shpChange))

    def get_ignore_indices(self):
        return [
            field_idx_from_name(self.excelName, field)
            for field in self.s.expressions.keys()
        ]

    def write_feature_to_excel(self, sheet, idx, feat):
        sheet.write(idx, self.excelFkIdx, feat[self.shpKeyName])
        for (fieldName, exp) in self.s.expressions.items():
            fieldIdx = field_idx_from_name(self.excelName, fieldName)
            exp = QgsExpression(exp)
            context = QgsExpressionContext()
            context.setFeature(feat)
            sheet.write(idx, fieldIdx, exp.evaluate(context))

    def write_rowvals_to_excel(self, sheet, idx, vals, ignore=None):
        if ignore is None:
            ignore = []
        for i, v in enumerate(vals):
            if i not in ignore:
                sheet.write(idx, i, v)

    def update_excel_programmatically(self):

        status_msgs = []

        rb = open_workbook(self.excelPath, formatting_info=True)
        r_sheet = rb.sheet_by_name(self.excelSheetName)  # read only copy
        wb = xlwt.Workbook()
        w_sheet = wb.add_sheet(self.excelSheetName, cell_overwrite_ok=True)
        write_idx = 0

        for row_index in range(r_sheet.nrows):
            if row_index < self.skipLines:  # copy header and/or dummy lines
                vals = r_sheet.row_values(row_index)
                self.write_rowvals_to_excel(w_sheet, write_idx, vals)
                write_idx += 1
                continue
            # print(r_sheet.cell(row_index,1).value)
            fk = r_sheet.cell(row_index, self.excelFkIdx).value
            if fk in self.shpRemove:
                status_msgs.append("Removing feature with id {}".format(fk))
                continue
            if fk in self.shpChange.keys():
                status_msgs.append(
                    "Syncing geometry change to feature with id {}".format(fk))
                shpf = self.shpChange[fk]
                self.write_feature_to_excel(w_sheet, write_idx, shpf)
                vals = r_sheet.row_values(row_index)
                self.write_rowvals_to_excel(w_sheet,
                                            write_idx,
                                            vals,
                                            ignore=self.get_ignore_indices())
            else:  # else just copy the row
                vals = r_sheet.row_values(row_index)
                self.write_rowvals_to_excel(w_sheet, write_idx, vals)

            write_idx += 1

        fidToId = {}
        for shpf in self.shpAdd:
            status_msgs.append("Adding new feature with id {}".format(
                shpf.attribute(self.shpKeyName)))
            fidToId[shpf.id()] = shpf.attribute(self.shpKeyName)
            self.write_feature_to_excel(w_sheet, write_idx, shpf)
            write_idx += 1

        info('\n'.join(status_msgs))
        wb.save(self.excelPath)
        if status_msgs:
            show_message_bar(status_msgs)
        else:
            show_message_bar("No changes to shapefile to sync.")
        return fidToId

    def clear_edit_state(self):
        info("Clearing edit state")
        self.shpAdd = []
        self.shpChange = {}
        self.shpRemove = set([])

    def update_excel_from_shp(self):
        info("Will now update excel from edited shapefile")
        info("changing:" + str(self.shpChange))
        info("adding:" + str(self.shpAdd))
        info("removing" + str(self.shpRemove))
        self.deactivateFileWatcher()  # so that we don't sync back and forth
        fidToId = self.update_excel_programmatically()
        # need to alter the ids(not fids) of the new features after, because
        # editing the features after they've been commited doesn't work
        if fidToId:
            self.deactivateShpConnections()
            self.renameIds(fidToId)
            self.activateShpConnections()
        self.reload_excel()
        self.activateFileWatcher()
        self.clear_edit_state()

    def updateShpLayer(self, fksToRemove):
        if not fksToRemove:
            return

        prompt_msg = ("Attempt to synchronize between Excel and Shapefile. "
                      "Shapefile has features with ids: ({}) that don't "
                      "appear in the Excel. Delete those features from the "
                      "shapefile? ").format(','.join(
                          [str(fk) for fk in fksToRemove]))
        reply = QMessageBox.question(iface.mainWindow(), 'Message', prompt_msg,
                                     QMessageBox.Yes, QMessageBox.No)

        if reply == QMessageBox.Yes:
            layer = layer_from_name(self.shpName)
            feats = [f for f in layer.getFeatures()]
            layer.startEditing()
            for f in feats:
                if f.attribute(self.shpKeyName) in fksToRemove:
                    layer.deleteFeature(f.id())
            layer.commitChanges()
        else:
            return

    def update_shp_from_excel(self):
        excelFks = set(
            get_fk_set(self.excelName,
                       self.excelKeyName,
                       skipFirst=self.skipLines))
        shpFks = set(get_fk_set(self.shpName, self.shpKeyName, skipFirst=0))
        # TODO also special warning if shp layer is in edit mode
        info("Keys in excel" + str(excelFks))
        info("Keys in shp" + str(shpFks))
        if shpFks == excelFks:
            info("Excel and Shp layer have the same rows. No update necessary")
            return
        inShpButNotInExcel = shpFks - excelFks
        inExcelButNotInShp = excelFks - shpFks
        if inExcelButNotInShp:
            warn(("There are rows in the excel file with no matching "
                  "geometry {}.").format(inExcelButNotInShp))
            # FIXME: if those are added later then they will be added twice..
            # However, having an autoincrement id suggests features would be
            # added first from shp only?

        if inShpButNotInExcel:
            self.updateShpLayer(inShpButNotInExcel)

    def activateFileWatcher(self):
        self.filewatcher = QFileSystemWatcher([self.excelPath])
        self.filewatcher.fileChanged.connect(self.excel_changed)

    def deactivateFileWatcher(self):
        self.filewatcher.fileChanged.disconnect(self.excel_changed)
        self.filewatcher.removePath(self.excelPath)

    def activateShpConnections(self):
        shpLayer = layer_from_name(self.shpName)
        shpLayer.committedFeaturesAdded.connect(self.added_geom)
        shpLayer.featuresDeleted.connect(self.removed_geom_precommit)
        shpLayer.committedGeometriesChanges.connect(self.changed_geom)
        shpLayer.editingStopped.connect(self.update_excel_from_shp)
        shpLayer.beforeRollBack.connect(self.clear_edit_state)

    def deactivateShpConnections(self):
        shpLayer = layer_from_name(self.shpName)
        shpLayer.committedFeaturesAdded.disconnect(self.added_geom)
        # shpLayer.featureAdded.disconnect(added_geom_precommit)
        shpLayer.featuresDeleted.disconnect(self.removed_geom_precommit)
        shpLayer.committedGeometriesChanges.disconnect(self.changed_geom)
        shpLayer.editingStopped.disconnect(self.update_excel_from_shp)
        shpLayer.beforeRollBack.disconnect(self.clear_edit_state)

    def initialSync(self):
        info("Initial Syncing excel to shp")
        self.update_shp_from_excel()
        self.activateFileWatcher()
        self.activateShpConnections()

    def __del__(self):
        self.deactivateFileWatcher()
        self.deactivateShpConnections()
コード例 #6
0

def reload_style(path):
    # Some applications will remove a file and rewrite it.  QFileSystemWatcher will
    # ignore the file if the file handle goes away so we have to keep adding it.
    watch.removePaths(watch.files())
    watch.addPath(path)
    with open(path, "r") as f:
        stylesheet = f.read()
        # Update the image paths to use full paths. Fixes image loading in styles
        path = os.path.dirname(path).replace("\\", "/")
        stylesheet = re.sub(r"url\((.*?)\)", r'url("{}/\1")'.format(path), stylesheet)
        QApplication.instance().setStyleSheet(stylesheet)


watch = QFileSystemWatcher()
watch.fileChanged.connect(reload_style)


class About3DiMIDialog(QDialog):
    def __init__(self, parent):
        super(About3DiMIDialog, self).__init__(parent)
        ui_fn = os.path.join(os.path.dirname(__file__), 'ui', 'About3DiMIDialog.ui')
        loadUi(ui_fn, self)


class SplashScreen(object):
    def __init__(self, iface):
        self.iface = iface
        self.plugin_dir = os.path.dirname(__file__)