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