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)
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)
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()
def activateFileWatcher(self): self.filewatcher = QFileSystemWatcher([self.excelPath]) self.filewatcher.fileChanged.connect(self.excel_changed)
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()
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__)