class ProgramCache(object): def __init__(self, ctx): from PyQt4.QtCore import QFileSystemWatcher self.ctx = ctx self.cache = {} self.watcher = QFileSystemWatcher() self.watcher.fileChanged.connect(self._file_changed) self.options = '-I ' + os.path.join(os.path.dirname(__file__), 'kernels') def _file_changed(self, path): path = str(path) del self.cache[path] self.watcher.removePath(path) def get(self, path): prog = self.cache.get(path, None) if not prog: src = open(path).read() prog = cl.Program(self.ctx, src).build(self.options) log.info('%s succesfully compiled' % path) self.cache[path] = prog self.watcher.addPath(path) return prog
class ProgramCache(object): def __init__(self, ctx): from PyQt4.QtCore import QFileSystemWatcher self.ctx = ctx self.cache = {} self.watcher = QFileSystemWatcher() self.watcher.fileChanged.connect(self._file_changed) self.options = '-I ' + os.path.join(os.path.dirname(__file__), 'kernels') def _file_changed(self, path): path = str(path) del self.cache[path] self.watcher.removePath(path) def get(self, path): prog = self.cache.get(path, None) if not prog: src = open(path).read() prog = cl.Program(self.ctx, src).build(self.options) log.info('%s succesfully compiled' % path) self.cache[path] = prog self.watcher.addPath(path) return prog
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 ImageBrowser(QWidget, Ui_ImageBrowser): """Widget to browse absorption and reference images""" windowTitleChanged = pyqtSignal(str) imageChanged = pyqtSignal(dict) def __init__(self, settings, parent): super(ImageBrowser, self).__init__(parent=parent) self.settings = settings self.main_window = parent self.current_directory = default_image_dir self.path_to_dark_file = default_dark_path self.path_to_json_db = os.path.join(main_package_dir, 'image_save_info.json') self.current_image_info = {} self.setupUi(self) self.loadSettings() self.is_cleaned = False self.use_roi_while_cleaning = False self.connectSignalsToSlots() self.image_list = clt.ImageList() self.updateFileList(new_dir=True) self.current_image_index = 0 self.watcher = QFileSystemWatcher(self) self.watcher.addPath(self.current_directory) self.watcher.directoryChanged.connect( self.handleWatcherDirectoryChanged) self.updateCommentBox() def initialEmit(self): self.populateAndEmitImageInfo() def populateAndEmitImageInfo(self): """Populates current_image_info with all the required information about the current image. It then emits the imageChanged signal""" d = self.current_image_info index = self.imageListCombo.currentIndex() d['index'] = index d['path_to_abs'] = self.image_list.absorption_files[index] d['path_to_ref'] = self.image_list.reference_files[index] d['path_to_dark'] = self.path_to_dark_file d['abs_image'] = clt.readImageFile(d['path_to_abs']) d['ref_image'] = clt.readImageFile(d['path_to_ref']) d['dark_image'] = clt.readImageFile(d['path_to_dark']) modified_time = time.ctime(os.path.getmtime(d['path_to_abs'])) self.fileDateTime.setText(str(modified_time)) if self.is_cleaned and self.useCleanedCheck.checkState() == 2: ref_image = self.clean_ref_images[index] else: ref_image = d['ref_image'] d['div_image'] = clt.dividedImage(d['abs_image'], ref_image, d['dark_image'], od_minmax=self.getODMinMax(), correct_od_saturation=self.getODSaturationParms(), correct_saturation=self.getSaturationParms()) d['image_type'] = self.getImageType() key = d['path_to_abs'] if key not in self.global_save_info: self.global_save_info[key] = {} d['save_info'] = self.global_save_info[key] # d['save_info']['comment'] = str(self.commentTextEdit.toPlainText()) self.imageChanged.emit(d) def getImageType(self): imtype = str(self.imageTypeCombo.currentText()) imcode = {'Absorption': 'abs_image', 'Reference': 'ref_image', 'Divided': 'div_image', 'Dark': 'dark_image'} return imcode[imtype] def handleImageIndexValueChanged(self, new_index): """Slot: called when the user changes the current index.""" # just update imageList. handleImageListIndexChanged will take care of # the rest self.imageListCombo.setCurrentIndex(new_index) def handleImageListIndexChanged(self, new_index): """Slot: called when the user changes the current image in the combo box.""" # we need to update imageIndexSpin, but also want to avoid recursive # updates. Hence we disconnect slots before updating. self.saveImageInfo() self.imageIndexSpin.valueChanged.disconnect( self.handleImageIndexValueChanged) self.imageIndexSpin.setValue(new_index) self.imageIndexSpin.valueChanged.connect( self.handleImageIndexValueChanged) self.current_image_index = new_index self.updateCommentBox() self.populateAndEmitImageInfo() def saveImageInfo(self): """Get save_info contents of current_image_info and save it in global_save_info. TODO: write better description of this function. """ comment = str(self.commentTextEdit.toPlainText()) print(self.current_image_index) key = self.image_list.absorption_files[self.current_image_index] # if key not in self.global_save_info: # self.global_save_info[key] = {} self.global_save_info[key] = self.current_image_info['save_info'] self.global_save_info[key]['comment'] = comment def updateCommentBox(self): """Updates comment box to display comment for the current image.""" key = self.image_list.absorption_files[self.current_image_index] if key not in self.global_save_info: self.commentTextEdit.setPlainText('') else: if 'comment' not in self.global_save_info[key]: self.global_save_info[key]['comment'] = '' comment = self.global_save_info[key]['comment'] self.commentTextEdit.setPlainText(comment) def connectSignalsToSlots(self): self.imageIndexSpin.valueChanged.connect( self.handleImageIndexValueChanged) self.imageListCombo.currentIndexChanged.connect( self.handleImageListIndexChanged) def updateFileList(self, new_dir=False): """Updates image_list to reflect files in current_directory. Pass new_dir=True if current_directory has changed. If an error occured, gives the user the option to select a different directory.""" done = False while not done: try: if new_dir: self.image_list.updateFileList(self.current_directory) else: self.image_list.updateFileList() except clt.ImageListError as err: info = (' Would you like to open a different directory?' ' Press cancel to quit') pressed = QMessageBox.question(self, 'Error opening directory', str(err) + info, QMessageBox.No | QMessageBox.Yes | QMessageBox.Cancel) if pressed == QMessageBox.Yes: self.setCurrentDirectory(self.openDirectoryDialog()) new_dir = True elif pressed == QMessageBox.No: done = True elif pressed == QMessageBox.Cancel: # user pressed cancel, quit! done = True self.main_window.close() # TODO: find graceful way to quit else: # file updating was successful done = True if new_dir is False: # This means we are just refreshing the current directory # we probably want to keep the current index previous_text = self.imageListCombo.currentText() # disconnect slots before adding items self.imageListCombo.currentIndexChanged.disconnect( self.handleImageListIndexChanged) self.imageIndexSpin.valueChanged.disconnect( self.handleImageIndexValueChanged) # update image List combo self.imageListCombo.clear() self.imageListCombo.addItems(self.image_list.short_names) # update image index max_index = self.image_list.n_images - 1 self.imageIndexSpin.setMaximum(max_index) labelString = 'of' + str(max_index) self.maxImageIndexLabel.setText(labelString) if new_dir is False: # find if the previous image is still around try: ci = self.image_list.short_names.index(previous_text) except ValueError: # nope, it's not there. set current index to max index ci = max_index else: # if we have a new folder, then set the index to 0 ci = 0 self.current_image_index = ci self.imageListCombo.setCurrentIndex(ci) self.imageIndexSpin.setValue(ci) # connect slot again once done adding self.imageListCombo.currentIndexChanged.connect( self.handleImageListIndexChanged) self.imageIndexSpin.valueChanged.connect( self.handleImageIndexValueChanged) self.is_cleaned = False self.useCleanedCheck.setCheckState(0) self.populateAndEmitImageInfo() self.purgeNonExistentEntries() def setCurrentDirectory(self, new_directory): """Sets the current directory. Never change self.current_directory directly. Use this function instead.""" self.current_directory = new_directory self.windowTitleChanged.emit(self.current_directory) def loadSettings(self): self.settings.beginGroup('imagebrowser') self.setCurrentDirectory( str(self.settings.value('current_directory', default_image_dir).toString())) self.path_to_dark_file = str( self.settings.value('path_to_dark_file', self.path_to_dark_file).toString()) self.path_to_json_db = str(self.settings.value( 'path_to_json_db', './image_save_info.json').toString()) self.settings.endGroup() self.loadJsonFile() def loadJsonFile(self): info = ('\n\nSomething bad has happened and I am not equipped to ' 'handle it. Please check if ' + self.path_to_json_db + ' exists. If it does, then make a backup of this file as ' 'it has a lot of information. Press yes if you would like ' 'to select a new database file. This has to be a valid JSON ' 'file. {} is an empty but valid JSON file. ' 'Press no if you want to ' 'create a new empty database at the same location. ' 'Press cancel if you want me to crash horribly but ' 'without touching the database file.') done = False while not done: try: with open(self.path_to_json_db, 'r') as f: self.global_save_info = json.loads(f.read()) except IOError as err: (errno, strerror) = err # something bad wrong msg = str(strerror) except ValueError as err: msg = str(err) except: msg = "Unknown error." else: done = True if not done: pressed = QMessageBox.critical(self, 'Error opening database', msg + info, QMessageBox.No | QMessageBox.Yes | QMessageBox.Cancel) if pressed == QMessageBox.Yes: # file dialog new_path = str(QFileDialog.getOpenFileName(self, "Select new JSON file", self.path_to_json_db)) self.path_to_json_db = new_path done = False elif pressed == QMessageBox.No: self.global_save_info = {} done = True elif pressed == QMessageBox.Cancel: done = True self.global_save_info = {} self.path_to_json_db = './temp_crash_json' self.main_window.close() def saveSettings(self): self.saveImageInfo() self.settings.beginGroup('imagebrowser') self.settings.setValue('current_directory', self.current_directory) self.settings.setValue('path_to_dark_file', self.path_to_dark_file) self.settings.setValue('path_to_json_db', self.path_to_json_db) self.settings.endGroup() json_file_as_string = json.dumps(self.global_save_info, indent=4, separators=(',', ': ')) with open(self.path_to_json_db, 'w') as f: f.write(json_file_as_string) def openDirectoryDialog(self): """Opens a dialog to select and new directory and returns path to selected directory.""" return str(QFileDialog.getExistingDirectory(self, "Open Directory", self.current_directory, QFileDialog.ShowDirsOnly)) def handleOpenDirectoryAction(self): """Called when the user clicks the Open Directory button.""" new_directory = self.openDirectoryDialog() if new_directory is not '' and new_directory != self.current_directory: self.watcher.removePath(self.current_directory) self.setCurrentDirectory(new_directory) self.updateFileList(new_dir=True) self.watcher.addPath(self.current_directory) def handleDarkFileAction(self): """Called when the user clicks the Dark File menu option.""" new_path_to_dark_file = str(QFileDialog.getOpenFileName(self, "Select dark file", self.path_to_dark_file)) if new_path_to_dark_file != '': self.path_to_dark_file = new_path_to_dark_file def handleRefreshAction(self): self.updateFileList(new_dir=False) def handleCleanAction(self): progress = QProgressDialog('Reading Reference images', 'Abort', 0, 4.0*self.image_list.n_images, self) progress.setWindowModality(Qt.WindowModal) progress.setMinimumDuration(1) if self.readAllRefImages(progress): return if self.generateBasis(progress): return if self.readAllAbsImages(progress): return if self.generateCleanRefs(progress): return progress.setValue(4.0*self.image_list.n_images) self.populateAndEmitImageInfo() def readAllRefImages(self, progress): progress.setLabelText('Reading Reference Images') self.ref_images = [] for path_to_ref in self.image_list.reference_files: if progress.wasCanceled(): return 1 progress.setValue(progress.value() + 1) im = clt.normalize(clt.readImageFile(path_to_ref)) self.ref_images.append(im) def generateBasis(self, progress): progress.setLabelText('Generating basis vectors') self.basis = [] for b in clt.generateBasis(self.ref_images): progress.setValue(progress.value() + 1) if progress.wasCanceled(): return 1 self.basis.append(b) def readAllAbsImages(self, progress): progress.setLabelText('Reading absorption images') self.abs_images = [] for path_to_abs in self.image_list.absorption_files: progress.setValue(progress.value() + 1) if progress.wasCanceled(): return 1 im = clt.readImageFile(path_to_abs) self.abs_images.append(im) def generateCleanRefs(self, progress): progress.setLabelText('Generating clean reference images') self.clean_ref_images = [] abs_shape = self.abs_images[0].shape # TODO: insert code to get actual mask mask = self.getROIMask(abs_shape) self.is_cleaned = False for im in clt.generateCleanRefs(self.abs_images, self.basis, mask): progress.setValue(progress.value() + 1) if progress.wasCanceled(): return 1 self.clean_ref_images.append(im) else: self.is_cleaned = True def handleUseCleanedAction(self, state): if self.is_cleaned is False and state == 2: self.handleCleanAction() self.populateAndEmitImageInfo() def handleUseRoiWhileCleaningAction(self, state): self.use_roi_while_cleaning = bool(state) def handleImageTypeChanged(self, new_state_string): self.populateAndEmitImageInfo() def odMinMaxStateChanged(self, new_state): print(new_state) def correctSaturationStateChanged(self, new_state): print(new_state) def handleWatcherDirectoryChanged(self, newDir): try: # see if updating the list would generate any errors clt.ImageList(self.current_directory) except clt.ImageListError: # if they do, then we probably are in the middle of a refresh # process, do nothing. return else: self.updateFileList(new_dir=False) def getODMinMax(self): """Return a tuple with min and max OD values read from the UI. returns None if odMinMax check button is not checked.""" if self.odMinMaxCheck.checkState() == 0: return None else: return (self.odMinSpin.value(), self.odMaxSpin.value()) def getSaturationParms(self): if self.correctSaturationCheckBox.checkState() == 0: return None else: gamma = 6.0666 # MHz return (self.satPixCountsSpin.value(), 2.0*self.detuningSpin.value()/gamma) def getODSaturationParms(self): if self.correctODSaturationCheckBox.checkState() == 0: return None else: return float(self.odSatSpin.value()) def handleRoiChanged(self, new_roi): """Slot: Changes ROI used for cleaning images.""" self.cleaning_roi = new_roi def handleROIHChanged(self, new_roi): self.roi_h = new_roi def handleROIVChanged(self, new_roi): self.roi_v = new_roi def getROIMask(self, abs_shape): mask = np.ones(abs_shape) if self.use_roi_while_cleaning: roi = self.cleaning_roi[0] # 0th component is roi list mask[roi[0]:roi[2], roi[1]:roi[3]] = 0.0 return mask def purgeNonExistentEntries(self): """Scan JSON database for files in current directory. If database has entries for which there are no files, then delete those entries.""" for key in self.global_save_info.keys(): if path.dirname(key) == self.current_directory: if not path.isfile(key): # remove entry if this file does not exis del self.global_save_info[key] print('deleting: ' + key) def handleSaveAnalysis(self): save_dialog = SaveDialog(self.settings, self.global_save_info, self.image_list) save_dialog.exec_() def handleMakeMovie(self): print('handle') image_list = [] for index in range(self.image_list.n_images): d = {} d['abs_image'] = clt.readImageFile(self.image_list.absorption_files[index]) d['ref_image'] = clt.readImageFile(self.image_list.reference_files[index]) d['dark_image'] = clt.readImageFile(self.path_to_dark_file) if self.is_cleaned and self.useCleanedCheck.checkState() == 2: ref_image = self.clean_ref_images[index] else: ref_image = d['ref_image'] d['div_image'] = clt.dividedImage(d['abs_image'], ref_image, d['dark_image'], od_minmax=self.getODMinMax(), correct_od_saturation=self.getODSaturationParms(), correct_saturation=self.getSaturationParms()) d['image_type'] = self.getImageType() image_in_roi = getSubImage(d['div_image'], self.cleaning_roi) image_list.append(image_in_roi) fig = plt.figure() ax = fig.add_subplot(111) ax.set_aspect('equal') ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) im = ax.imshow(image_list[0], cmap='gray', interpolation='nearest') im.set_clim([0, 1]) fig.set_size_inches([5, 5]) plt.tight_layout() def update_img(n): im.set_data(image_list[n]) return im #legend(loc=0) ani = animation.FuncAnimation(fig, update_img, len(image_list), interval=30) #writer = animation.writers['gif'](fps=10) ani.save('demo.gif') def handlePluginClicked(self, plugin_name): for p in plugin_list: if p.name == plugin_name: print("found") plugin_dialog = p(self.settings, self.makePluginDataPackage()) plugin_dialog.exec_() def makePluginDataPackage(self): """Returns a dict with all the data a plugin would need.""" data_dict = {} data_dict['roi_int'] = self.cleaning_roi data_dict['roi_h'] = self.roi_h data_dict['roi_v'] = self.roi_v abs_images = [clt.readImageFile(p) for p in self.image_list.absorption_files] ref_images = [clt.readImageFile(p) for p in self.image_list.reference_files] dark_image = clt.readImageFile(self.path_to_dark_file) if self.is_cleaned and self.useCleanedCheck.checkState() == 2: use_refs = self.clean_ref_images else: use_refs = ref_images sat_od_parms = self.getODSaturationParms() corr_sat_parms = self.getSaturationParms() div_images = [clt.dividedImage(ai, ri, dark_image, od_minmax=self.getODMinMax(), correct_od_saturation=sat_od_parms, correct_saturation=corr_sat_parms) for ai, ri in zip(abs_images, use_refs)] data_dict['abs_images'] = abs_images data_dict['ref_images'] = use_refs data_dict['dark_image'] = dark_image data_dict['div_images'] = div_images return data_dict
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 PreferencesWindow(BaseWindow): def __init__(self, *args, **kwargs): super(PreferencesWindow, self).__init__(layoutCls=QVBoxLayout, *args, **kwargs) self.setWindowTitle("Settings") self.setWindowIcon(QIcon(SETTINGS['icon'])) self._add_steam_box() self._add_dota_box() self._add_additional_prefs() self._add_log_box() self._add_log_watcher() Settings().signals.changed.connect(self.update_path) def _add_log_watcher(self): self.watcher = QFileSystemWatcher() self.watcher.addPath(abspath(log.file_name)) self.watcher.fileChanged.connect(self.update_log) def show(self): self.update_log(abspath(log.file_name)) return super(PreferencesWindow, self).show() def update_path(self, setting_key, new_value): if setting_key == "dota_path": self.dota_path.setText(new_value) elif setting_key == "steam_path": self.steam_path.setText(new_value) def _add_steam_box(self): box = QGroupBox("Steam Location") box.setLayout(QHBoxLayout(box)) self.steam_path = QLineEdit(box) self.steam_path.setReadOnly(True) self.steam_path.setText(Settings().get("steam_path")) change_btn = QPushButton("Change...", box) change_btn.clicked.connect(self.change_steam_path) box.layout().addWidget(self.steam_path) box.layout().addWidget(change_btn) self.layout().addWidget(box) def _add_dota_box(self): box = QGroupBox("Dota Location") box.setLayout(QHBoxLayout(box)) self.dota_path = QLineEdit(box) self.dota_path.setReadOnly(True) self.dota_path.setText(Settings().get("dota_path")) change_btn = QPushButton("Change...", box) change_btn.clicked.connect(self.change_dota_path) box.layout().addWidget(self.dota_path) box.layout().addWidget(change_btn) self.layout().addWidget(box) def change_steam_path(self): self._change_path("steam_path", is_steam_path_valid) def change_dota_path(self): self._change_path("dota_path", is_dota_path_valid) def _change_path(self, path_key, is_valid): new_folder = str( QFileDialog.getExistingDirectory( parent=self, caption="Select new path", directory=Settings().get(path_key))) if new_folder and exists(new_folder): if is_valid(new_folder): Settings().set(path_key, new_folder) else: from d2mp.ui import Message Message.critical( "Path is not valid", "Path was not saved in settings.\nPlease select a directory with the right executable in it!" ) def _add_additional_prefs(self): box = QGroupBox("Additional Preferences") box.setLayout(QHBoxLayout(box)) log_btn = QPushButton("View Log", box) log_btn.clicked.connect(self.open_log_file) reset_btn = QPushButton("Reset Settings", box) reset_btn.clicked.connect(Settings().reset) box.layout().addWidget(log_btn) box.layout().addWidget(reset_btn) self.layout().addWidget(box) def _add_log_box(self): box = QGroupBox("Application log") box.setLayout(QHBoxLayout(box)) self.log_area = QTextBrowser(box) self.log_area.setLineWrapMode(QTextEdit.NoWrap) box.layout().addWidget(self.log_area) self.layout().addWidget(box) def open_log_file(self): log.INFO("TODO: open file in standard editor") print(abspath(log.file_name)) def update_log(self, log_file): content = "" for line in open(log_file): if "========= new programm start =========" in line: content = "" else: content += line[37:] if content: self.log_area.setText(content)
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 SingleApplication(QApplication): def __init__(self, *args): QApplication.__init__(self, *args) self._memory = QSharedMemory(self) self._memory.setKey("d2mp") if self._memory.attach(): self._running = True else: self._running = False if not self._memory.create(1): raise RuntimeError(self._memory.errorString().toLocal8Bit().data()) def is_running(self): return self._running def exec_(self): self._create_tray_icon() self._create_mod_manager() self._start_file_watcher() self._create_socket() Settings() return super(SingleApplication, self).exec_() def _create_mod_manager(self): self.manager = ModManager() self.manager.mod_game_info() self.manager.signals.message.connect(self.show_message_from_mod_manager) self.manager.signals.error.connect(self.show_error_from_mod_manager) def _create_socket(self): self.socket = ConnectionManager() self.manager.signals.contact_server.connect(self.socket.send) self.socket.message.connect(self.show_message_from_socket) self.socket.error.connect(self.show_error_from_socket) @property def _watcher_file_name(self): return "d2mp.pid" def _start_file_watcher(self): self.watcher = QFileSystemWatcher() self.watcher_file_path = join(abspath("."), self._watcher_file_name) log.DEBUG("creating watcher file: %s" %(self.watcher_file_path)) write_to_file(self.watcher_file_path, "Delete this file to shutdown D2MP\n") self.watcher.addPath(abspath(".")) self.watcher.directoryChanged.connect(self._watcher_changed_callback) def _watcher_changed_callback(self, val): if self._watcher_file_name not in os.listdir(val): secs = 3 self.show_message("Shutdown", "Watcher file was deleted. D2MP will shotdown in %d seconds." %(secs)) sleep(secs) self.exit() def _create_tray_icon(self): self.tray = QSystemTrayIcon(self) self.tray.setToolTip("D2Moddin Manager") self.tray.setIcon(QIcon(SETTINGS['icon'])) traymenu = QMenu() traymenu.addAction("Restart", self.restart) traymenu.addAction("Uninstall", self.uninstall) traymenu.addAction("Preferences", UIManager().open_preferences) traymenu.addAction("Show mod list", self.show_mod_list) traymenu.addSeparator() traymenu.addAction("Exit", self.exit) self.tray.setContextMenu(traymenu) self.tray.show() def restart(self): python = sys.executable args = set(sys.argv) args.add("restart") os.execl(python, python, *list(sys.argv)) self.exit() def uninstall(self): ModManager().delete_mods() # ModManager().uninstall_d2mp() self.exit() def exit(self): # do some cleanup return super(SingleApplication, self).exit() def show_mod_list(self): self.show_message("Mod List", ModManager().mod_names_as_string()) def show_message_from_socket(self, message): self.show_message("Server message", message) def show_error_from_socket(self, message): self.show_message("Server error", message, QSystemTrayIcon.Critical) def show_message_from_mod_manager(self, message): self.show_message("ModManager message", message) def show_error_from_mod_manager(self, message): self.show_message("ModManager error", message, QSystemTrayIcon.Critical) def show_message(self, title, message, icon = QSystemTrayIcon.Information): self.tray.showMessage(title, message, icon)
class OpenedFileView(QObject): MARGIN_NUMBERS, MARGIN_MARKER_BP, MARGIN_MARKER_TP, MARGIN_MARKER_EXEC, \ MARGIN_MARKER_EXEC_SIGNAL, MARGIN_MARKER_STACK = range(6) def __init__(self, distributedObjects, filename): QObject.__init__(self) filename = str(filename) self.distributedObjects = distributedObjects self.debugController = self.distributedObjects.debugController self.breakpointController = self.distributedObjects.breakpointController self.tracepointController = self.distributedObjects.tracepointController self.signalProxy = self.distributedObjects.signalProxy self.filename = filename self.lastContexMenuLine = 0 self.markerBp = QPixmap(":/markers/bp.png") self.markerTp = QPixmap(":/markers/tp.png") self.markerExec = QPixmap(":/markers/exec_pos.png") self.markerExecSignal = QPixmap(":/markers/exec_pos_signal.png") self.shown = False self.FileWatcher = QFileSystemWatcher() self.FileWatcher.addPath(self.filename) self.FileWatcher.fileChanged.connect(self.fileChanged) self.tab = QtGui.QWidget() self.gridLayout = QtGui.QGridLayout(self.tab) self.gridLayout.setMargin(0) self.edit = Qsci.QsciScintilla(self.tab) self.font = QFont("DejaVu Sans Mono", 10) self.font.setStyleHint(QFont.TypeWriter) self.lexer = Qsci.QsciLexerCPP() self.lexer.setFont(self.font) self.edit.setToolTip("") self.edit.setWhatsThis("") self.edit.setTabWidth(4) self.edit.setLexer(self.lexer) self.edit.setWhitespaceVisibility(Qsci.QsciScintilla.WsVisible) self.edit.setIndentationGuides(True) self.edit.setMarginLineNumbers(self.MARGIN_NUMBERS, True) # set sensitivity self.edit.setMarginSensitivity(self.MARGIN_NUMBERS, True) self.edit.setMarginSensitivity(self.MARGIN_MARKER_BP, True) self.edit.setMarginSensitivity(self.MARGIN_MARKER_TP, True) # define symbol self.edit.markerDefine(self.markerBp, self.MARGIN_MARKER_BP) self.edit.markerDefine(self.markerTp, self.MARGIN_MARKER_TP) self.edit.markerDefine(self.markerExec, self.MARGIN_MARKER_EXEC) self.edit.markerDefine(self.markerExecSignal, self.MARGIN_MARKER_EXEC_SIGNAL) self.edit.markerDefine(Qsci.QsciScintilla.Background, self.MARGIN_MARKER_STACK) # define width and mask to show margin self.edit.setMarginWidth(self.MARGIN_MARKER_BP, 10) self.edit.setMarginMarkerMask(self.MARGIN_MARKER_BP, 1 << self.MARGIN_MARKER_BP) self.edit.setMarginWidth(self.MARGIN_MARKER_TP, 10) self.edit.setMarginMarkerMask(self.MARGIN_MARKER_TP, 1 << self.MARGIN_MARKER_TP) self.edit.setMarginWidth(self.MARGIN_MARKER_EXEC, 10) self.edit.setMarginMarkerMask(self.MARGIN_MARKER_EXEC, 1 << self.MARGIN_MARKER_EXEC | 1 << self.MARGIN_MARKER_EXEC_SIGNAL) self.edit.setMarginWidth(self.MARGIN_MARKER_STACK, 0) self.edit.setMarkerBackgroundColor(QColor(Qt.yellow), self.MARGIN_MARKER_STACK) self.edit.setMarginMarkerMask(self.MARGIN_MARKER_STACK, 1 << self.MARGIN_MARKER_STACK) # ... self.edit.setReadOnly(False) self.gridLayout.addWidget(self.edit, 0, 0, 1, 1) self.breakpoints = [] if not (QtCore.QFile.exists(filename)): logging.error("could not open file", filename) self.file_ = QtCore.QFile(filename) self.file_.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text) self.edit.read(self.file_) self.file_.close() self.changed = False self.edit.modificationChanged.connect(self.__setFileModified) self.setMarginWidthByLineNumbers() self.edit.SendScintilla(Qsci.QsciScintilla.SCI_SETMOUSEDWELLTIME, 500) # override scintillas context menu with our own self.edit.SendScintilla(Qsci.QsciScintilla.SCI_USEPOPUP, 0) self.edit.setContextMenuPolicy(Qt.CustomContextMenu) self.edit.customContextMenuRequested.connect(self.showContextMenu) self.edit.marginClicked.connect(self.marginClicked) self.edit.SCN_DOUBLECLICK.connect(self.editDoubleClicked) self.edit.SCN_DWELLSTART.connect(self.dwellStart) self.edit.SCN_DWELLEND.connect(self.dwellEnd) # initially, read all breakpoints and tracepoints from the model self.getBreakpointsFromModel() self.getTracepointsFromModel() _model = self.breakpointController.model() _model.rowsInserted.connect(self.getBreakpointsFromModel) _model.rowsRemoved.connect(self.getBreakpointsFromModel) _model = self.tracepointController.model() _model.rowsInserted.connect(self.getTracepointsFromModel) _model.rowsRemoved.connect(self.getTracepointsFromModel) act = self.distributedObjects.actions act.ToggleTrace.triggered.connect(self.toggleTracepoint) def fileChanged(self): logging.warning("Source file %s modified. Recompile executable for \ correct debugging.", self.filename) def saveFile(self): ''' Save source file ''' if (QtCore.QFile.exists(self.filename)): f = open(self.filename, 'w') f.write(self.edit.text()) f.close() self.file_.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text) self.edit.read(self.file_) self.file_.close() self.__setFileModified(False) def __setFileModified(self, modified): ''' Method called whenever current file is marked as modified ''' self.distributedObjects.signalProxy.emitFileModified(self.filename, modified) def dwellStart(self, pos, x, y): if self.edit.frameGeometry().contains(x, y): name = self.getWordOrSelectionFromPosition(pos) val = self.debugController.evaluateExpression(name.strip()) if val != None: name = cgi.escape(name) val = cgi.escape(val) QToolTip.showText(self.edit.mapToGlobal(QtCore.QPoint(x, y)), "<b>" + name + "</b> = " + val, self.edit, QtCore.QRect()) def dwellEnd(self, position, x, y): QToolTip.showText(self.edit.mapToGlobal(QtCore.QPoint(x, y)), "", self.edit, QtCore.QRect()) def showContextMenu(self, point): scipos = self.edit.SendScintilla( Qsci.QsciScintilla.SCI_POSITIONFROMPOINT, point.x(), point.y()) point = self.edit.mapToGlobal(point) exp = self.getWordOrSelectionFromPosition(scipos) # self.edit.lineIndexFromPosition(..) returns tupel. first of tupel is line self.lastContexMenuLine = int(self.edit.lineIndexFromPosition(scipos)[0]) listOfTracepoints = self.tracepointController.getTracepointsFromModel() self.subPopupMenu = QtGui.QMenu(self.edit) self.subPopupMenu.setTitle("Add variable " + exp + " to...") for tp in listOfTracepoints: # dynamic actions, not in actiony.py Action class self.subPopupMenu.addAction(self.distributedObjects.actions.getAddToTracepointAction(exp, tp.name, tp.addVar)) self.popupMenu = QtGui.QMenu(self.edit) # add watch and toggle breakpoint to menu self.popupMenu.addAction(self.distributedObjects.actions.getAddToWatchAction(exp, self.signalProxy.addWatch)) self.popupMenu.addAction(self.distributedObjects.actions.ToggleTrace) self.popupMenu.addAction(self.distributedObjects.actions.getAddToDatagraphAction(exp, self.distributedObjects.datagraphController.addWatch)) # add separator and self.subPopupMenu self.popupMenu.addSeparator() self.popupMenu.addMenu(self.subPopupMenu) self.popupMenu.popup(point) def isPositionInsideSelection(self, position): lf, cf, lt, ct = self.edit.getSelection() pl, pc = self.edit.lineIndexFromPosition(position) if lf < pl and pl < lt: return True elif lf == pl and pl < lt: return True if cf <= pc else False elif lf < pl and pl == lt: return True if pc <= ct else False elif lf == pl and pl == lt: return True if (cf <= pc and pc <= ct) else False else: return False def getWordOrSelectionFromPosition(self, position): if self.isPositionInsideSelection(position): return str(self.edit.selectedText()) else: return self.getWordFromPosition(position) def getWordFromPosition(self, position): line, col = self.edit.lineIndexFromPosition(position) s = str(self.edit.text(line)) start = col end = col r = re.compile(r'[\w\d_]') while start >= 0: if not r.match(s[start]): break start -= 1 start += 1 while end < len(s): if not r.match(s[end]): break end += 1 return s[start:end] def editDoubleClicked(self, position, line, modifiers): w = self.getWordFromPosition(position) self.signalProxy.addWatch(str(w)) def showExecutionPosition(self, line): self.edit.markerAdd(line, self.MARGIN_MARKER_EXEC) self.showLine(line) def showSignalPosition(self, line): self.edit.markerAdd(line, self.MARGIN_MARKER_EXEC_SIGNAL) self.showLine(line) def showLine(self, line): self.edit.setCursorPosition(line, 1) self.edit.ensureLineVisible(line) def clearExecutionPositionMarkers(self): self.edit.markerDeleteAll(self.MARGIN_MARKER_EXEC) def setMarginWidthByLineNumbers(self): self.edit.setMarginWidth(0, ceil(log(self.edit.lines(), 10)) * 10 + 5) def marginClicked(self, margin, line, state): # if breakpoint should be toggled if margin == self.MARGIN_NUMBERS or margin == self.MARGIN_MARKER_BP: self.toggleBreakpointWithLine(line) elif margin == self.MARGIN_MARKER_TP: self.toggleTracepointWithLine(line) def toggleBreakpointWithLine(self, line): self.breakpointController.toggleBreakpoint(self.filename, line + 1) def toggleTracepointWithLine(self, line): self.tracepointController.toggleTracepoint(self.filename, line + 1) def toggleTracepoint(self): self.toggleTracepointWithLine(self.lastContexMenuLine) def getBreakpointsFromModel(self, parent=None, start=None, end=None): """Get breakpoints from model.""" # TODO: don't reload all breakpoints, just the one referenced by parent/start/end self.edit.markerDeleteAll(self.MARGIN_MARKER_BP) for bp in self.breakpointController.getBreakpointsFromModel(): if bp.fullname == self.filename: self.edit.markerAdd(int(bp.line) - 1, self.MARGIN_MARKER_BP) def getTracepointsFromModel(self): """Get tracepoints from model.""" self.edit.markerDeleteAll(self.MARGIN_MARKER_TP) for tp in self.tracepointController.getTracepointsFromModel(): if tp.fullname == self.filename: self.edit.markerAdd(int(tp.line) - 1, self.MARGIN_MARKER_TP)
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 EdisFile(QObject): """ Representación de un objeto archivo """ def __init__(self, filename=''): QObject.__init__(self) self._is_new = True if not filename: self._filename = "Untitled" else: self._filename = filename self._is_new = False self._last_modification = None self._system_watcher = None @property def filename(self): return self._filename @property def is_new(self): return self._is_new def read(self): """ Itenta leer el contenido del archivo, si ocurre un error se lanza una excepción. """ try: with open(self.filename, mode='r') as f: content = f.read() return content except IOError as reason: raise exceptions.EdisIOError(reason) def write(self, content, new_filename=''): """ Escribe los datos en el archivo """ DEBUG("Saving file...") # Por defecto, si el archivo no tiene extensión se agrega .c ext = os.path.splitext(new_filename) if not ext[-1]: new_filename += '.c' if self.is_new: self._filename = new_filename self._is_new = False _file = QFile(self.filename) if not _file.open(QIODevice.WriteOnly | QIODevice.Truncate): raise exceptions.EdisIOError out_file = QTextStream(_file) out_file << content if self._system_watcher is not None: if self._filename is not None: archivos = self._system_watcher.files() self._system_watcher.removePath(archivos[0]) else: self.run_system_watcher() def run_system_watcher(self): """ Inicializa el control de monitoreo para modificaciones """ if self._system_watcher is None: self._system_watcher = QFileSystemWatcher() self.connect(self._system_watcher, SIGNAL("fileChanged(const QString&)"), self._on_file_changed) self._last_modification = os.lstat(self.filename).st_mtime self._system_watcher.addPath(self.filename) DEBUG("Watching {0}".format(self.filename)) def stop_system_watcher(self): if self._system_watcher is not None: self._system_watcher.removePath(self.filename) DEBUG("Stoping watching {0}".format(self.filename)) def _on_file_changed(self, filename): mtime = os.lstat(filename).st_mtime if mtime != self._last_modification: # Actualizo la última modificación self._last_modification = mtime self.emit(SIGNAL("fileChanged(PyQt_PyObject)"), self)
isDone = True ct = 0 def watch(): global isDone, ct if isDone: isDone = False ct += 1 iface.mapCanvas().refresh() iface.mapCanvas().saveAsImage("/tmp/image{0:04d}.png".format(ct)) isDone = True watcher = QFileSystemWatcher() watcher.addPath('/tmp/wavefront_edges_progress.wkt') watcher.fileChanged.connect(watch) from PyQt4.QtCore import QFileSystemWatcher watcher = QFileSystemWatcher() watcher.addPath('/tmp/rays.wkt') watcher.fileChanged.connect(iface.mapCanvas().refresh) from PyQt4.QtCore import QFileSystemWatcher watcher = QFileSystemWatcher() iface.mapCanvas().setCachingEnabled(False) isDone = True def watch(): global isDone
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 ScanPage(QWizardPage): scanError = pyqtSignal() # name scanSuccess = pyqtSignal() path = "qtui/ui/page3_scan.ui" def __init__(self, project, parentW=None): super(ScanPage, self).__init__() self.ui = uic.loadUi(join(os.environ['AUTOEXAM_FOLDER'], self.path), self) self.project = project self.scan_thread = None self.ui.treeWidget.currentItemChanged.connect(self.change_tree_item) self.ui.openCameraButton.clicked.connect(self.open_camera) self.question_item_to_question = {} self.exams = [] self.order = None self.results = None self.parentWizard = parentW self.scanError.connect(self.on_scan_error) self.scanSuccess.connect(self.on_scan_success) self.scanning = False def initializePage(self): super(ScanPage, self).initializePage() # api.add_scan_event_subscriber(self) self.open_camera() if os.path.exists(ORDER_FILE_PATH): self.order = scanresults.parse(ORDER_FILE_PATH) self.watcher = QFileSystemWatcher() self.watcher.fileChanged.connect(self.on_scan_file_change) if os.path.exists(TESTS_RESULTS_FILE_PATH): self.results = scanresults.parse(TESTS_RESULTS_FILE_PATH) self.update_question_tree_widget() else: with open(TESTS_RESULTS_FILE_PATH,'w') as f: f.write('{}') self.results = scanresults.parse(TESTS_RESULTS_FILE_PATH) # TODO: Check why this doesn't always work self.watcher.addPath(TESTS_RESULTS_FILE_PATH) self.last_load_time = time.time() def update_question_tree_widget(self): tree = self.ui.treeWidget tree.clear() for i in range(len(self.order)): incomplete_test = False exam_item = QTreeWidgetItem(tree, ['Exam %d' % i]) for j in range(len(self.order[i].questions)): question_item = QTreeWidgetItem(exam_item, ['Question %d' % (j + 1)]) if i in self.results: question_item.question = self.project.questions[self.order[i].questions[j].id - 1] else: incomplete_test = True if incomplete_test: exam_item.setBackground(0, QBrush(Qt.lightGray)) first_exam_item = tree.topLevelItem(0) first_exam_item.setExpanded(True) tree.setCurrentItem( tree.itemBelow(first_exam_item)) def validatePage(self): if self.scanning: self.show_modal_message( 'Please close the scanner window first by pressing \'q\'') return False # TODO: Warning validation here!!! if self.results is not None: scanresults.dump(self.results, TESTS_RESULTS_FILE_PATH, overwrite=False) self.parentWizard.results = self.results return True else: self.show_modal_message('There are still no results to save') return False def cleanupPage(self): super(ScanPage, self).cleanupPage() # api.remove_scan_event_subscriber(self) # TODO: Do proper shutdown # self.scan_thread.__stop() # def on_scan_event(self, report): # if report.success: # print 'successful report: ', report # self.process_report(report) # else: # print 'failed report: ', report def on_scan_file_change(self, filename): if time.time() - self.last_load_time > 2: try: print('Reloading results...') self.results = scanresults.parse(TESTS_RESULTS_FILE_PATH) self.update_question_tree_widget() self.last_load_time = time.time() print('Results reloaded') except: print('Could not load results.') else: # print('Ignoring repetitive filewatcher event (this is normal)') pass # def process_report(self, report): # current = self.ui.treeWidget.topLevelItem(report.test.id) # # if current: # if len(report.test.warnings) == 0: # current.setForeground(0, ok_color) # elif len(report.test.warnings) > 0: # current.setForeground(0, warn_color) def change_tree_item(self): currentItem = self.ui.treeWidget.currentItem() if currentItem is not None: self.cleanupPanel() self.current_item = currentItem if currentItem.parent() is not None: # i.e. it is a question self.update_question_panel_with_question() else: self.update_question_panel_with_exam() def update_question_panel_with_question(self): current_exam_item = self.current_item.parent() exam_no = self.ui.treeWidget.indexOfTopLevelItem(current_exam_item) question_no = current_exam_item.indexOfChild(self.current_item) question_info = self.project.questions[self.order[exam_no].questions[question_no].id - 1] self.ui.questionTextLabel.setText(question_info.text) # self.ui.questionDataLayout.addWidget(QLabel(question_info.text)) order_info = self.order[exam_no].questions[question_no].order for answer_no in order_info: answer = question_info.answers[answer_no] answer_text = answer.text answer_text += ' (x)' if answer.valid else '' question_answer_check = QCheckBox(answer_text) question_answer_check.setChecked( self.is_answer_checked(exam_no, question_no, answer_no)) question_answer_check.stateChanged.connect( self.update_current_question_state) self.ui.questionDataLayout.addWidget(question_answer_check) def update_question_panel_with_exam(self): current_exam_item = self.current_item exam_no = self.ui.treeWidget.indexOfTopLevelItem(current_exam_item) if self.results is not None: if exam_no in self.results: question_text_label = QLabel('TODO: Put here the warnings...') else: question_text_label = QLabel('This exam has not been scanned!') else: print('results null! please check!') self.ui.questionDataLayout.addWidget(question_text_label) def is_answer_checked(self, exam_no, question_no, answer_no): # print 'is_answer_checked' # print(exam_no,question_no,answer_no) try: results_data = self.results exam_data = results_data[exam_no] question_data = exam_data.questions[question_no] return answer_no in question_data.answers except: # print('answer not scanned exception') return False def update_current_question_state(self, state): current_exam_item = self.current_item.parent() exam_no = self.ui.treeWidget.indexOfTopLevelItem(current_exam_item) question_no = current_exam_item.indexOfChild(self.current_item) results_data = self.results if exam_no in results_data: question_data = results_data[exam_no].questions[question_no] order_data = self.order[exam_no].questions[question_no].order self.synchronize_answers_with_model(order_data,question_data) else: # If we get here, we're trying to manually enter info for an # exam that has not been scanned yet. # We should create an entry in results_data similar confirm_edit = QMessageBox.question(None, "Manual input?", "Do you want to manually enter this test's results?", QMessageBox.Yes | QMessageBox.No ) regen = confirm_edit == QMessageBox.Yes if confirm_edit: self.results[exam_no] = self.order[exam_no] question_data = results_data[exam_no].questions[question_no] order_data = self.order[exam_no].questions[question_no].order self.synchronize_answers_with_model(order_data,question_data) current_exam_item.setBackground(0, QBrush(Qt.white)) def synchronize_answers_with_model(self, order_data, question_data): for i in range(len(order_data)): checked = self.ui.questionDataLayout.itemAt(i).widget().isChecked() question_idx = order_data[i] if checked and question_idx not in question_data.answers: question_data.answers.append(question_idx) elif not checked and question_idx in question_data.answers: question_data.answers.remove(question_idx) def cleanupPage(self): self.watcher.fileChanged.disconnect(self.on_scan_file_change) del self.watcher def cleanupPanel(self): for i in reversed(range(self.ui.questionDataLayout.count())): elem = self.ui.questionDataLayout.itemAt(i) if not elem: break elem.widget().deleteLater() def start_scan(self): class _args: outfile = TESTS_RESULTS_FILE_PATH cameras = [self.ui.cameraIndexSpin.value()] #TODO: UN-WIRE THIS !!!! folder = IMAGES_FOLDER time = None autowrite = True poll = None debug = False self.scanning = True ok = api.scan(_args()) # TODO: Fill dictionary properly if ok: self.scanSuccess.emit() else: self.scanError.emit() self.scanning = False def open_camera(self): self.ui.openCameraButton.setEnabled(False) self.scan_thread = Thread(target=self.start_scan) self.scan_thread.setDaemon(True) self.scan_thread.start() # self.start_scan() def on_scan_error(self): # self.parentWizard.back() self.show_modal_message( "There was an error in the scanning process.\ Please, check if the right camera is selected and try again.") self.ui.openCameraButton.setEnabled(True) def show_modal_message(self, msg): """ Shows a modal MessageBox displaying the argument string """ msgBox = QMessageBox() msgBox.setText(msg) msgBox.setModal(True) msgBox.setIcon(QMessageBox.Warning) msgBox.exec_() def on_scan_success(self): """ Reloads results one last time in case the filewatcher doesn't work """ # Do another dump first to store manually entered results scanresults.dump(self.results, TESTS_RESULTS_FILE_PATH) # Reload merged results self.results = scanresults.parse(TESTS_RESULTS_FILE_PATH) # Update UI self.update_question_tree_widget() # Reenable the button in case someone needs to scan again self.ui.openCameraButton.setEnabled(True) # Msg for debugging purposes print 'All OK with the scanning!'
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 ScanPage(QWizardPage): scanError = pyqtSignal() # name scanSuccess = pyqtSignal() path = "qtui/ui/page3_scan.ui" def __init__(self, project, parentW=None): super(ScanPage, self).__init__() self.ui = uic.loadUi(join(os.environ['AUTOEXAM_FOLDER'], self.path), self) self.project = project self.scan_thread = None self.ui.treeWidget.currentItemChanged.connect(self.change_tree_item) self.ui.openCameraButton.clicked.connect(self.open_camera) self.question_item_to_question = {} self.exams = [] self.order = None self.results = None self.parentWizard = parentW self.scanError.connect(self.on_scan_error) self.scanSuccess.connect(self.on_scan_success) self.scanning = False def initializePage(self): super(ScanPage, self).initializePage() # api.add_scan_event_subscriber(self) self.open_camera() if os.path.exists(ORDER_FILE_PATH): self.order = scanresults.parse(ORDER_FILE_PATH) self.watcher = QFileSystemWatcher() self.watcher.fileChanged.connect(self.on_scan_file_change) if os.path.exists(TESTS_RESULTS_FILE_PATH): self.results = scanresults.parse(TESTS_RESULTS_FILE_PATH) self.update_question_tree_widget() else: with open(TESTS_RESULTS_FILE_PATH, 'w') as f: f.write('{}') self.results = scanresults.parse(TESTS_RESULTS_FILE_PATH) # TODO: Check why this doesn't always work self.watcher.addPath(TESTS_RESULTS_FILE_PATH) self.last_load_time = time.time() def update_question_tree_widget(self): tree = self.ui.treeWidget tree.clear() for i in range(len(self.order)): incomplete_test = False exam_item = QTreeWidgetItem(tree, ['Exam %d' % i]) for j in range(len(self.order[i].questions)): question_item = QTreeWidgetItem(exam_item, ['Question %d' % (j + 1)]) if i in self.results: question_item.question = self.project.questions[ self.order[i].questions[j].id - 1] else: incomplete_test = True if incomplete_test: exam_item.setBackground(0, QBrush(Qt.lightGray)) first_exam_item = tree.topLevelItem(0) first_exam_item.setExpanded(True) tree.setCurrentItem(tree.itemBelow(first_exam_item)) def validatePage(self): if self.scanning: self.show_modal_message( 'Please close the scanner window first by pressing \'q\'') return False # TODO: Warning validation here!!! if self.results is not None: scanresults.dump(self.results, TESTS_RESULTS_FILE_PATH, overwrite=False) self.parentWizard.results = self.results return True else: self.show_modal_message('There are still no results to save') return False def cleanupPage(self): super(ScanPage, self).cleanupPage() # api.remove_scan_event_subscriber(self) # TODO: Do proper shutdown # self.scan_thread.__stop() # def on_scan_event(self, report): # if report.success: # print 'successful report: ', report # self.process_report(report) # else: # print 'failed report: ', report def on_scan_file_change(self, filename): if time.time() - self.last_load_time > 2: try: print('Reloading results...') self.results = scanresults.parse(TESTS_RESULTS_FILE_PATH) self.update_question_tree_widget() self.last_load_time = time.time() print('Results reloaded') except: print('Could not load results.') else: # print('Ignoring repetitive filewatcher event (this is normal)') pass # def process_report(self, report): # current = self.ui.treeWidget.topLevelItem(report.test.id) # # if current: # if len(report.test.warnings) == 0: # current.setForeground(0, ok_color) # elif len(report.test.warnings) > 0: # current.setForeground(0, warn_color) def change_tree_item(self): currentItem = self.ui.treeWidget.currentItem() if currentItem is not None: self.cleanupPanel() self.current_item = currentItem if currentItem.parent() is not None: # i.e. it is a question self.update_question_panel_with_question() else: self.update_question_panel_with_exam() def update_question_panel_with_question(self): current_exam_item = self.current_item.parent() exam_no = self.ui.treeWidget.indexOfTopLevelItem(current_exam_item) question_no = current_exam_item.indexOfChild(self.current_item) question_info = self.project.questions[ self.order[exam_no].questions[question_no].id - 1] self.ui.questionTextLabel.setText(question_info.text) # self.ui.questionDataLayout.addWidget(QLabel(question_info.text)) order_info = self.order[exam_no].questions[question_no].order for answer_no in order_info: answer = question_info.answers[answer_no] answer_text = answer.text answer_text += ' (x)' if answer.valid else '' question_answer_check = QCheckBox(answer_text) question_answer_check.setChecked( self.is_answer_checked(exam_no, question_no, answer_no)) question_answer_check.stateChanged.connect( self.update_current_question_state) self.ui.questionDataLayout.addWidget(question_answer_check) def update_question_panel_with_exam(self): current_exam_item = self.current_item exam_no = self.ui.treeWidget.indexOfTopLevelItem(current_exam_item) if self.results is not None: if exam_no in self.results: question_text_label = QLabel('TODO: Put here the warnings...') else: question_text_label = QLabel('This exam has not been scanned!') else: print('results null! please check!') self.ui.questionDataLayout.addWidget(question_text_label) def is_answer_checked(self, exam_no, question_no, answer_no): # print 'is_answer_checked' # print(exam_no,question_no,answer_no) try: results_data = self.results exam_data = results_data[exam_no] question_data = exam_data.questions[question_no] return answer_no in question_data.answers except: # print('answer not scanned exception') return False def update_current_question_state(self, state): current_exam_item = self.current_item.parent() exam_no = self.ui.treeWidget.indexOfTopLevelItem(current_exam_item) question_no = current_exam_item.indexOfChild(self.current_item) results_data = self.results if exam_no in results_data: question_data = results_data[exam_no].questions[question_no] order_data = self.order[exam_no].questions[question_no].order self.synchronize_answers_with_model(order_data, question_data) else: # If we get here, we're trying to manually enter info for an # exam that has not been scanned yet. # We should create an entry in results_data similar confirm_edit = QMessageBox.question( None, "Manual input?", "Do you want to manually enter this test's results?", QMessageBox.Yes | QMessageBox.No) regen = confirm_edit == QMessageBox.Yes if confirm_edit: self.results[exam_no] = self.order[exam_no] question_data = results_data[exam_no].questions[question_no] order_data = self.order[exam_no].questions[question_no].order self.synchronize_answers_with_model(order_data, question_data) current_exam_item.setBackground(0, QBrush(Qt.white)) def synchronize_answers_with_model(self, order_data, question_data): for i in range(len(order_data)): checked = self.ui.questionDataLayout.itemAt(i).widget().isChecked() question_idx = order_data[i] if checked and question_idx not in question_data.answers: question_data.answers.append(question_idx) elif not checked and question_idx in question_data.answers: question_data.answers.remove(question_idx) def cleanupPage(self): self.watcher.fileChanged.disconnect(self.on_scan_file_change) del self.watcher def cleanupPanel(self): for i in reversed(range(self.ui.questionDataLayout.count())): elem = self.ui.questionDataLayout.itemAt(i) if not elem: break elem.widget().deleteLater() def start_scan(self): class _args: outfile = TESTS_RESULTS_FILE_PATH cameras = [self.ui.cameraIndexSpin.value() ] #TODO: UN-WIRE THIS !!!! folder = IMAGES_FOLDER time = None autowrite = True poll = None debug = False self.scanning = True ok = api.scan(_args()) # TODO: Fill dictionary properly if ok: self.scanSuccess.emit() else: self.scanError.emit() self.scanning = False def open_camera(self): self.ui.openCameraButton.setEnabled(False) self.scan_thread = Thread(target=self.start_scan) self.scan_thread.setDaemon(True) self.scan_thread.start() # self.start_scan() def on_scan_error(self): # self.parentWizard.back() self.show_modal_message("There was an error in the scanning process.\ Please, check if the right camera is selected and try again.") self.ui.openCameraButton.setEnabled(True) def show_modal_message(self, msg): """ Shows a modal MessageBox displaying the argument string """ msgBox = QMessageBox() msgBox.setText(msg) msgBox.setModal(True) msgBox.setIcon(QMessageBox.Warning) msgBox.exec_() def on_scan_success(self): """ Reloads results one last time in case the filewatcher doesn't work """ # Do another dump first to store manually entered results scanresults.dump(self.results, TESTS_RESULTS_FILE_PATH) # Reload merged results self.results = scanresults.parse(TESTS_RESULTS_FILE_PATH) # Update UI self.update_question_tree_widget() # Reenable the button in case someone needs to scan again self.ui.openCameraButton.setEnabled(True) # Msg for debugging purposes print 'All OK with the scanning!'
class MainWindow(QMainWindow): def __init__(self, parent=None): """ init UI """ QMainWindow.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.actionSaveSession.setEnabled(False) self.distributedObjects = DistributedObjects(self) self.act = self.distributedObjects.actions self.debugController = self.distributedObjects.debugController self.settings = self.distributedObjects.settings self.signalproxy = self.distributedObjects.signalProxy self.pluginloader = PluginLoader(self.distributedObjects) self.editorController = self.distributedObjects.editorController self.act = self.distributedObjects.actions # init RecentFileHandler self.recentFileHandler = RecentFileHandler(self, self.ui.menuRecentlyUsedFiles, self.distributedObjects) self.debugController.executableOpened.connect(self.recentFileHandler.addToRecentFiles) self.debugController.executableOpened.connect(self.__observeWorkingBinary) self.debugController.executableOpened.connect(self.showExecutableName) self.debugController.executableOpened.connect(self.disableButtons) # signal proxy self.signalproxy.inferiorIsRunning.connect(self.targetStartedRunning) self.signalproxy.inferiorStoppedNormally.connect(self.targetStopped) self.signalproxy.inferiorReceivedSignal.connect(self.targetStopped) self.signalproxy.inferiorHasExited.connect(self.targetExited) # Plugin Loader self.pluginloader.insertPluginAction.connect(self.addPluginAction) self.ui.actionSavePlugins.triggered.connect(self.showSavePluginsDialog) self.ui.actionLoadPlugins.triggered.connect(self.showLoadPluginsDialog) # Add editor to main window. self.ui.gridLayout.addWidget(self.distributedObjects.editorController.editor_view, 0, 0, 1, 1) self.pluginloader.addAvailablePlugins() self.setWindowFilePath("<none>") self.setupUi() self.readSettings() self.quickwatch = QuickWatch(self, self.distributedObjects) self.binaryName = None self.fileWatcher = QFileSystemWatcher() self.fileWatcher.fileChanged.connect(self.__binaryChanged) self.__runWithArgumentsMenu = None self.__argumentsEdit = None self.__makeRunWithArgumentsMenu() def __makeRunWithArgumentsMenu(self): self.__runWithArgumentsMenu = QMenu(self) self.__argumentsEdit = QLineEdit() self.__argumentsEdit.returnPressed.connect(self.__runWithArgumentsTriggered) hl = QHBoxLayout(self.__runWithArgumentsMenu) hl.addWidget(QLabel("Run with arguments:")) hl.addWidget(self.__argumentsEdit) w = QWidget(self.__runWithArgumentsMenu) w.setLayout(hl) wa = QWidgetAction(self.__runWithArgumentsMenu) wa.setDefaultWidget(w) self.__runWithArgumentsMenu.addAction(wa) self.act.Run.setMenu(self.__runWithArgumentsMenu) def __runTriggered(self): self.debugController.run() def __runWithArgumentsTriggered(self): self.__runWithArgumentsMenu.close() self.debugController.run(str(self.__argumentsEdit.text())) def setupUi(self): self.__initActions() self.ui.statusLabel = QLabel() self.ui.statusLabel.setText("Not running") self.ui.statusbar.addPermanentWidget(self.ui.statusLabel) self.ui.statusIcon = QLabel() self.ui.statusIcon.setPixmap(QPixmap(":/icons/images/22x22/not_running.png")) self.ui.statusbar.addPermanentWidget(self.ui.statusIcon) def __initActions(self): self.disableButtons() self.act.Record.setCheckable(True) self.act.ReverseNext.setEnabled(False) self.act.ReverseStep.setEnabled(False) self.act.SaveFile.setEnabled(False) self.act.Beautify.setCheckable(True) self.act.Beautify.setChecked(True) # debug actions self.ui.menuDebug.addAction(self.act.Run) self.ui.menuDebug.addAction(self.act.Continue) self.ui.menuDebug.addAction(self.act.Interrupt) self.ui.menuDebug.addAction(self.act.Next) self.ui.menuDebug.addAction(self.act.Step) self.ui.menuDebug.addAction(self.act.Finish) self.ui.menuDebug.addAction(self.act.RunToCursor) self.ui.menuDebug.addAction(self.act.Record) self.ui.menuDebug.addAction(self.act.ReverseNext) self.ui.menuDebug.addAction(self.act.ReverseStep) self.ui.menuDebug.addAction(self.act.Beautify) # file actions self.ui.menuFile.insertAction(self.ui.actionSaveSession, self.act.OpenMenu) self.ui.menuFile.addAction(self.act.SaveFile) self.ui.menuFile.addAction(self.act.Exit) # add them to menubar and also menuView to respect order self.ui.menubar.addAction(self.ui.menuFile.menuAction()) self.ui.menubar.addAction(self.ui.menuView.menuAction()) self.ui.menubar.addAction(self.ui.menuDebug.menuAction()) self.ui.menubar.addAction(self.ui.menuHelp.menuAction()) # now make toolbar actions self.act.Open.setMenu(self.ui.menuRecentlyUsedFiles) self.ui.Main.addAction(self.act.Open) self.ui.Main.addAction(self.act.SaveFile) self.ui.Main.addSeparator() self.ui.Main.addAction(self.act.Run) self.ui.Main.addAction(self.act.Continue) self.ui.Main.addAction(self.act.Interrupt) self.ui.Main.addAction(self.act.Next) self.ui.Main.addAction(self.act.Step) self.ui.Main.addAction(self.act.Record) self.ui.Main.addAction(self.act.ReverseNext) self.ui.Main.addAction(self.act.ReverseStep) self.ui.Main.addAction(self.act.Finish) self.ui.Main.addAction(self.act.RunToCursor) self.ui.Main.addAction(self.act.Beautify) self.ui.Main.addSeparator() self.ui.Main.addAction(self.act.Exit) # connect actions self.__connectActions() def __connectActions(self): # file menu self.act.Open.triggered.connect(self.showOpenExecutableDialog) self.act.OpenMenu.triggered.connect(self.showOpenExecutableDialog) self.act.Exit.triggered.connect(self.close) self.act.SaveFile.triggered.connect(self.signalproxy.emitSaveCurrentFile) # debug menu self.act.Run.triggered.connect(self.__runTriggered) self.act.Next.triggered.connect(self.debugController.next_) self.act.Step.triggered.connect(self.debugController.step) self.act.Continue.triggered.connect(self.debugController.cont) self.act.Record.triggered.connect(self.toggleRecord) self.act.ReverseStep.triggered.connect(self.debugController.reverse_step) self.act.ReverseNext.triggered.connect(self.debugController.reverse_next) self.act.Beautify.triggered.connect(self.__askBeautify) self.act.Interrupt.triggered.connect(self.debugController.interrupt) self.act.Finish.triggered.connect(self.debugController.finish) self.act.RunToCursor.triggered.connect(self.debugController.inferiorUntil) self.ui.actionRestoreSession.triggered.connect(self.distributedObjects.sessionManager.showRestoreSessionDialog) self.ui.actionSaveSession.triggered.connect(self.distributedObjects.sessionManager.showSaveSessionDialog) self.ui.actionConfigure.triggered.connect(self.distributedObjects.configStore.edit) def insertDockWidget(self, widget, name, area, addToggleViewAction): d = AlertableDockWidget(name, self) d.setObjectName(name) d.setWidget(widget) self.addDockWidget(area, d) if addToggleViewAction: self.ui.menuShow_View.addAction(d.toggleViewAction()) return d def insertStatusbarWidget(self, widget): w = QFrame() w.setLayout(QHBoxLayout()) w.layout().addWidget(widget) f = QFrame() f.setFrameStyle(QFrame.Plain | QFrame.VLine) w.layout().addWidget(f) self.ui.statusbar.insertPermanentWidget(0, w) return w def removeStatusbarWidget(self, widget): self.ui.statusbar.removeWidget(widget) def addPluginAction(self, Action): """ show plugin as menu entry """ self.ui.menuPlugins.addAction(Action) def showOpenExecutableDialog(self): filename = str(QFileDialog.getOpenFileName(self, "Open Executable", self.recentFileHandler.getDirOfLastFile())) if filename != "": self.debugController.openExecutable(filename) def showLoadPluginsDialog(self): dialog = QFileDialog() dialog.setNameFilter("*.xml") filename = str(dialog.getOpenFileName(self, "Load plugin configuration")) if filename != "": self.pluginloader.getActivePlugins(filename) def showSavePluginsDialog(self): dialog = QFileDialog() dialog.setNameFilter("*.xml") filename = str(dialog.getSaveFileName(self, "Save plugin configuration")) if filename != "": self.pluginloader.savePluginInfo(filename) def showExecutableName(self, filename): self.ui.actionSaveSession.setEnabled(True) # enable saving session self.setWindowFilePath(filename) def targetStartedRunning(self): self.ui.statusLabel.setText("Running") self.ui.statusIcon.setPixmap(QPixmap(":/icons/images/22x22/running.png")) self.disableButtons() self.act.Interrupt.setEnabled(True) def targetStopped(self, rec): self.ui.statusLabel.setText("Stopped") self.ui.statusIcon.setPixmap(QPixmap(":/icons/images/22x22/stopped.png")) self.enableButtons() self.act.Interrupt.setEnabled(False) def targetExited(self): self.ui.statusLabel.setText("Not running") self.disableButtons() self.ui.statusIcon.setPixmap(QPixmap(":/icons/images/22x22/not_running.png")) def closeEvent(self, event): if not self.distributedObjects.editorController.closeOpenedFiles(): event.ignore() # closing source files may be canceled by user else: self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) QMainWindow.closeEvent(self, event) self.pluginloader.savePluginInfo() def readSettings(self): self.restoreGeometry(self.settings.value("geometry").toByteArray()) self.restoreState(self.settings.value("windowState").toByteArray()) def toggleRecord(self, check): if check: self.debugController.record_start() self.act.ReverseNext.setEnabled(True) self.act.ReverseStep.setEnabled(True) else: self.debugController.record_stop() self.act.ReverseNext.setEnabled(False) self.act.ReverseStep.setEnabled(False) def enableButtons(self): self.act.Continue.setEnabled(True) self.act.Interrupt.setEnabled(True) self.act.Next.setEnabled(True) self.act.Step.setEnabled(True) self.act.Finish.setEnabled(True) self.act.RunToCursor.setEnabled(True) self.act.Record.setEnabled(True) self.act.Beautify.setEnabled(True) def disableButtons(self): self.act.Continue.setEnabled(False) self.act.Interrupt.setEnabled(False) self.act.Next.setEnabled(False) self.act.Step.setEnabled(False) self.act.Finish.setEnabled(False) self.act.RunToCursor.setEnabled(False) self.act.Record.setChecked(False) self.act.Record.setEnabled(False) self.act.Beautify.setEnabled(False) def __observeWorkingBinary(self, filename): """ Private Method to Observe Debugged Binary """ if self.binaryName != None: self.fileWatcher.removePath(self.binaryName) self.fileWatcher.addPath(filename) self.binaryName = filename def __binaryChanged(self): """ Slot for FileWatcher - Using QtMessagebox for interaction""" box = QtGui.QMessageBox() if ( box.question(self, "Binary Changed!", "Reload File?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes ): self.debugController.openExecutable(self.binaryName) else: self.fileWatcher.removePath(self.binaryName) self.fileWatcher.addPath(self.binaryName) def __askBeautify(self): """ Warn user before Beautify - Using QtMessagebox for interaction""" box = QtGui.QMessageBox() if ( box.question( self, "Warning!", "This deletes the objects in watch and datagraphview.", QtGui.QMessageBox.Ok, QtGui.QMessageBox.Cancel, ) == QtGui.QMessageBox.Ok ): self.debugController.beautify() else: self.act.Beautify.toggle()
class DebugController(QObject): executableOpened = pyqtSignal('PyQt_PyObject') def __init__(self, do): QObject.__init__(self) self.ptyhandler = PtyHandler() self.do = do self.connector = self.do.gdb_connector self.signalProxy = self.do.signalProxy self.executableName = None self.lastCmdWasStep = False self.ptyhandler.start() self.connector.start() self.connector.reader.asyncRecordReceived.connect(self.handleAsyncRecord, Qt.QueuedConnection) self.__config = DebugConfig() self.do.configStore.registerConfigSet(self.__config) self.__config.itemsHaveChanged.connect(self.updateConfig) self.__binaryWatcher = QFileSystemWatcher() self.__binaryWatcher.fileChanged.connect(self.__binaryChanged) def __reloadAction(self): a = QAction("Reload", self) a.triggered.connect(lambda: self.openExecutable(self.executableName)) return a def __binaryChanged(self): """ Slot for FileWatcher - Using QtMessagebox for interaction""" logging.warning("The executable was changed on the disc. Please reload the file.", extra={"actions": [self.__reloadAction()]}) def updateConfig(self): if self.executableName: logging.warning("Configuration changed. Please reload executable for changes to take effect!", extra={"actions": [self.__reloadAction()]}) def openExecutable(self, filename): # make sure we only open absolute paths, otherwise eg. RecentFileHandler # will not know _where_ the file was we opened and store different # relative paths for the same file filename = os.path.abspath(filename) if not os.path.exists(filename): logging.error("File %s was not found.", filename) return if self.do.editorController.closeOpenedFiles(): # closing source files may be canceled by user if self.executableName is not None: # clear variables, tracepoints, watches,... by connecting to this signal self.signalProxy.cleanupModels.emit() self.__binaryWatcher.removePath(self.executableName) self.connector.changeWorkingDirectory(os.path.dirname(filename)) self.connector.openFile(filename) if self.__config.breakAtMain.value: self.do.breakpointModel.insertBreakpoint("main", None) self.executableOpened.emit(filename) self.executableName = filename self.__binaryWatcher.addPath(self.executableName) def run(self, args=None): self.connector.setTty(self.ptyhandler.ptyname) if not args: args = "" self.connector.setArgs(args) try: self.connector.run() self.lastCmdWasStep = False self.signalProxy.runClicked.emit() except GdbError: pass def setRecord(self, state): if state: self.connector.record_start() else: self.connector.record_stop() self.signalProxy.recordStateChanged.emit(state) def next_(self): self.connector.next_() self.lastCmdWasStep = True def reverse_next(self): self.connector.next_(True) self.lastCmdWasStep = True def step(self): self.connector.step() self.lastCmdWasStep = True def reverse_step(self): self.connector.step(True) self.lastCmdWasStep = True def cont(self): self.connector.cont() self.lastCmdWasStep = False def interrupt(self): self.connector.interrupt() self.lastCmdWasStep = False def finish(self): self.connector.finish() self.lastCmdWasStep = False def reverse_finish(self): self.connector.finish(True) self.lastCmdWasStep = False def evaluateExpression(self, exp): if exp == "": return None exp = exp.replace('"', '\"') return self.connector.evaluate("\"" + exp + "\"") def executeCliCommand(self, cmd): return self.connector.executeCliCommand(cmd) def handleAsyncRecord(self, rec): if rec.type_ == GdbOutput.EXEC_ASYN and rec.class_ == GdbOutput.STOPPED: self.handleStoppedRecord(rec) elif rec.type_ == GdbOutput.EXEC_ASYN and rec.class_ == GdbOutput.RUNNING: self.signalProxy.inferiorIsRunning.emit(rec) elif rec.type_ == GdbOutput.NOTIFY_ASYN and rec.class_ == GdbOutput.THREAD_CREATED: self.signalProxy.threadCreated.emit(rec) elif rec.type_ == GdbOutput.NOTIFY_ASYN and rec.class_ == GdbOutput.THREAD_EXITED: self.signalProxy.threadExited.emit(rec) elif rec.type_ == GdbOutput.NOTIFY_ASYN and rec.class_ == GdbOutput.BREAKPOINT_MODIFIED: self.signalProxy.breakpointModified.emit(rec) def handleStoppedRecord(self, rec): # With reverse debugging, some stopped records might not contain a # reason. Predefine it as None, since all unknown reasons will be # handled as the inferior having stopped normally. fields = ["reason", "frame", "signal-name", "signal-meaning", "bkptno", "wpt", "value"] field = defaultdict(None) for r in rec.results: if r.dest in fields: field[r.dest] = r.src if field["reason"] in ['exited-normally', 'exited']: self.signalProxy.inferiorHasExited.emit(rec) elif field["reason"] == 'breakpoint-hit': # Ok, we're kind of frantically trying to cover all bases here. We # cannot simply check for file:line combination reported in the # stopped message, since breakpoints may be set to a certain line # (which GDB also reports back as the line where the breakpoint is # really located), but one of the following lines may be reported in # the stopped message (eg., if the breakpoint is set to a function # header, the line reported here will be the first line of the # function's body). # Therefore, we're checking what was hit using the reported # breakpoint number. However, if the user sets both a breakpoint and # a tracepoint in the same line, only one number will be reported # here, but we need to handle both. Therefore, check the location # where what we found was supposed to be, and check if something # else was supposed to be there too. This still might be a problem # (eg. setting a breakpoint and a tracepoint in the line following # the breakpoint, both of which would cause the program to suspend # on yet another line), but that's about as good as our guessing # currently gets. tp = self.do.tracepointController.model().breakpointByNumber(int(field["bkptno"])) bp = self.do.breakpointModel.breakpointByNumber(int(field["bkptno"])) assert tp or bp # either a TP or a BP must have been hit # now that we have one, check if the other is here too if bp and not tp: tp = self.do.tracepointController.model().breakpointByLocation(bp.fullname, bp.line) elif tp and not bp: bp = self.do.breakpointModel.breakpointByLocation(tp.fullname, tp.line) if tp: # this will cause the variable pool to update all variables self.do.signalProxy.tracepointOccurred.emit() tp.recordData() if self.lastCmdWasStep or bp: self.signalProxy.inferiorStoppedNormally.emit(rec) self.lastCmdWasStep = False else: assert tp # if this was not a breakpoint, it must have been a tracepoint self.connector.cont() elif field["reason"] == "signal-received": logging.warning("Inferior received signal <b>%s</b> (%s) at <b>%s:%s</b>.", field["signal-name"], field["signal-meaning"], field["frame"].file, field["frame"].line) self.signalProxy.inferiorReceivedSignal.emit(rec) elif field["reason"] == "watchpoint-trigger": logging.warning("Watchpoint %s on expression <b>%s</b> triggered; old value: %s, new value: %s.", field["wpt"].number, self.do.breakpointModel.breakpointByNumber(field["wpt"].number).where, field["value"].old, field["value"].new) self.signalProxy.inferiorStoppedNormally.emit(rec) else: self.signalProxy.inferiorStoppedNormally.emit(rec) def executePythonCode(self, code): exec(code, {'do': self.do}) def inferiorUntil(self): current_opened_file = self.do.editorController.editor_view.getCurrentOpenedFile() line, _ = current_opened_file.getCursorPosition() self.connector.until(current_opened_file.filename, line + 1) self.lastCmdWasStep = False def getExecutableName(self): return self.executableName def getStackDepth(self): return self.connector.getStackDepth() def selectStackFrame(self, exp): return self.connector.selectStackFrame(exp)
class MainWindow(QMainWindow): workingDirectory = '' fileNames = [] supportedExtensions = QStringList(('*.txt','*.csv')) def __init__(self): QMainWindow.__init__(self) self.settings = QSettings("greyltc", "batch-iv-analysis") self.rows = 0 #keep track of how many rows there are in the table self.cols = OrderedDict() thisKey = 'plotBtn' self.cols[thisKey] = col() self.cols[thisKey].header = 'Draw Plot' self.cols[thisKey].tooltip = 'Click this button to draw a plot for that row' thisKey = 'exportBtn' self.cols[thisKey] = col() self.cols[thisKey].header = 'Export' self.cols[thisKey].tooltip = 'Click this button to export\ninterpolated data points from fits' thisKey = 'file' self.cols[thisKey] = col() self.cols[thisKey].header = 'File' self.cols[thisKey].tooltip = 'File name\nHover to see header from data file' thisKey = 'pce' self.cols[thisKey] = col() self.cols[thisKey].header = 'PCE\n[%]' self.cols[thisKey].tooltip = 'Power conversion efficiency as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'pmax' self.cols[thisKey] = col() self.cols[thisKey].header = 'P_max\n[mW/cm^2]' self.cols[thisKey].tooltip = 'Maximum power density as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'jsc' self.cols[thisKey] = col() self.cols[thisKey].header = 'J_sc\n[mA/cm^2]' self.cols[thisKey].tooltip = 'Short-circuit current density as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'voc' self.cols[thisKey] = col() self.cols[thisKey].header = 'V_oc\n[mV]' self.cols[thisKey].tooltip = 'Open-circuit voltage as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'ff' self.cols[thisKey] = col() self.cols[thisKey].header = 'FF' self.cols[thisKey].tooltip = 'Fill factor as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'rs' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_s\n[ohm*cm^2]' self.cols[thisKey].tooltip = 'Specific series resistance as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'rsh' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_sh\n[ohm*cm^2]' self.cols[thisKey].tooltip = 'Specific shunt resistance as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'jph' self.cols[thisKey] = col() self.cols[thisKey].header = 'J_ph\n[mA/cm^2]' self.cols[thisKey].tooltip = 'Photogenerated current density as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'j0' self.cols[thisKey] = col() self.cols[thisKey].header = 'J_0\n[nA/cm^2]' self.cols[thisKey].tooltip = 'Reverse saturation current density as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'n' self.cols[thisKey] = col() self.cols[thisKey].header = 'n' self.cols[thisKey].tooltip = 'Diode ideality factor as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'Vmax' self.cols[thisKey] = col() self.cols[thisKey].header = 'V_max\n[mV]' self.cols[thisKey].tooltip = 'Voltage at maximum power point as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'area' self.cols[thisKey] = col() self.cols[thisKey].header = 'Area\n[cm^2]' self.cols[thisKey].tooltip = 'Device area' thisKey = 'pmax2' self.cols[thisKey] = col() self.cols[thisKey].header = 'P_max\n[mW]' self.cols[thisKey].tooltip = 'Maximum power as found from spline fit\nHover for value from characteristic equation fit' thisKey = 'isc' self.cols[thisKey] = col() self.cols[thisKey].header = 'I_sc\n[mA]' self.cols[thisKey].tooltip = 'Short-circuit current as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'iph' self.cols[thisKey] = col() self.cols[thisKey].header = 'I_ph\n[mA]' self.cols[thisKey].tooltip = 'Photogenerated current as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'i0' self.cols[thisKey] = col() self.cols[thisKey].header = 'I_0\n[nA]' self.cols[thisKey].tooltip = 'Reverse saturation current as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'rs2' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_s\n[ohm]' self.cols[thisKey].tooltip = 'Series resistance as found from characteristic equation fit\nHover for 95% confidence interval' thisKey = 'rsh2' self.cols[thisKey] = col() self.cols[thisKey].header = 'R_sh\n[ohm]' self.cols[thisKey].tooltip = 'Shunt resistance as found from characteristic equation fit\nHover for 95% confidence interval' #how long status messages show for self.messageDuration = 2500#ms # Set up the user interface from Designer. self.ui = Ui_batch_iv_analysis() self.ui.setupUi(self) #insert cols for item in self.cols: blankItem = QTableWidgetItem() thisCol = self.cols.keys().index(item) self.ui.tableWidget.insertColumn(thisCol) blankItem.setToolTip(self.cols[item].tooltip) blankItem.setText(self.cols[item].header) self.ui.tableWidget.setHorizontalHeaderItem(thisCol,blankItem) #file system watcher self.watcher = QFileSystemWatcher(self) self.watcher.directoryChanged.connect(self.handleWatchUpdate) #connect signals generated by gui elements to proper functions self.ui.actionOpen.triggered.connect(self.openCall) self.ui.actionEnable_Watching.triggered.connect(self.watchCall) self.ui.actionSave.triggered.connect(self.handleSave) self.ui.actionWatch_2.triggered.connect(self.handleWatchAction) self.ui.actionClear_Table.triggered.connect(self.clearTableCall) def exportInterp(self,row): thisGraphData = self.ui.tableWidget.item(row,self.cols.keys().index('plotBtn')).data(Qt.UserRole).toPyObject() fitX = thisGraphData[QString(u'fitX')] modelY = thisGraphData[QString(u'modelY')] splineY = thisGraphData[QString(u'splineY')] a = np.asarray([fitX, modelY, splineY]) a = np.transpose(a) destinationFolder = os.path.join(self.workingDirectory,'exports') QDestinationFolder = QDir(destinationFolder) if not QDestinationFolder.exists(): QDir().mkdir(destinationFolder) saveFile = os.path.join(destinationFolder,str(self.ui.tableWidget.item(row,self.cols.keys().index('file')).text())+'.csv') header = 'Voltage [V],CharEqn Current [mA/cm^2],Spline Current [mA/cm^2]' try: np.savetxt(saveFile, a, delimiter=",",header=header) self.ui.statusbar.showMessage("Exported " + saveFile,5000) except: self.ui.statusbar.showMessage("Could not export " + saveFile,self.messageDuration) def handleButton(self): btn = self.sender() #kinda hacky: row = self.ui.tableWidget.indexAt(btn.pos()).row() col = self.ui.tableWidget.indexAt(btn.pos()).column() if col == 0: self.rowGraph(row) if col == 1: self.exportInterp(row) def rowGraph(self,row): thisGraphData = self.ui.tableWidget.item(row,self.cols.keys().index('plotBtn')).data(Qt.UserRole).toPyObject() filename = str(self.ui.tableWidget.item(row,self.cols.keys().index('file')).text()) v = thisGraphData[QString(u'v')] i = thisGraphData[QString(u'i')] if not thisGraphData[QString(u'vsTime')]: plt.plot(v, i, c='b', marker='o', ls="None",label='I-V Data') plt.scatter(thisGraphData[QString(u'Vmax')], thisGraphData[QString(u'Imax')], c='g',marker='x',s=100) plt.scatter(thisGraphData[QString(u'Voc')], 0, c='g',marker='x',s=100) plt.scatter(0, thisGraphData[QString(u'Isc')], c='g',marker='x',s=100) fitX = thisGraphData[QString(u'fitX')] modelY = thisGraphData[QString(u'modelY')] splineY = thisGraphData[QString(u'splineY')] if not np.isnan(modelY[0]): plt.plot(fitX, modelY,c='k', label='CharEqn Best Fit') plt.plot(fitX, splineY,c='g', label='Spline Fit') plt.autoscale(axis='x', tight=True) plt.grid(b=True) ax = plt.gca() handles, labels = ax.get_legend_handles_labels() ax.legend(handles, labels, loc=3) plt.annotate( thisGraphData[QString(u'Voc')].__format__('0.4f')+ ' V', xy = (thisGraphData[QString(u'Voc')], 0), xytext = (40, 20), textcoords = 'offset points', ha = 'right', va = 'bottom', bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5), arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0')) plt.annotate( float(thisGraphData[QString(u'Isc')]).__format__('0.4f') + ' mA/cm^2', xy = (0,thisGraphData[QString(u'Isc')]), xytext = (40, 20), textcoords = 'offset points', ha = 'right', va = 'bottom', bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5), arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0')) plt.annotate( float(thisGraphData[QString(u'Imax')]*thisGraphData[QString(u'Vmax')]).__format__('0.4f') + '% @(' + float(thisGraphData[QString(u'Vmax')]).__format__('0.4f') + ',' + float(thisGraphData[QString(u'Imax')]).__format__('0.4f') + ')', xy = (thisGraphData[QString(u'Vmax')],thisGraphData[QString(u'Imax')]), xytext = (80, 40), textcoords = 'offset points', ha = 'right', va = 'bottom', bbox = dict(boxstyle = 'round,pad=0.5', fc = 'yellow', alpha = 0.5), arrowprops = dict(arrowstyle = '->', connectionstyle = 'arc3,rad=0')) plt.ylabel('Current [mA/cm^2]') plt.xlabel('Voltage [V]') else: #vs time time = thisGraphData[QString(u'time')] fig, ax1 = plt.subplots() ax1.plot(time, v, 'b-',label='Voltage [V]') ax1.set_xlabel('Time [s]') # Make the y-axis label and tick labels match the line color. ax1.set_ylabel('Voltage [V]', color='b') for tl in ax1.get_yticklabels(): tl.set_color('b') #fdsf ax2 = ax1.twinx() ax2.plot(time, i, 'r-') ax2.set_ylabel('Current [mA/cm^2]', color='r') for tl in ax2.get_yticklabels(): tl.set_color('r') plt.title(filename) plt.draw() plt.show() def handleSave(self): if self.settings.contains('lastFolder'): saveDir = self.settings.value('lastFolder').toString() else: saveDir = '.' path = QFileDialog.getSaveFileName(self, caption='Save File', directory=saveDir) if not str(path[0]) == '': with open(unicode(path), 'wb') as stream: writer = csv.writer(stream) rowdata = [] for column in range(self.ui.tableWidget.columnCount()): item = self.ui.tableWidget.horizontalHeaderItem(column) if item is not None: rowdata.append(unicode(item.text()).encode('utf8').replace('\n',' ')) else: rowdata.append('') writer.writerow(rowdata) for row in range(self.ui.tableWidget.rowCount()): rowdata = [] for column in range(self.ui.tableWidget.columnCount()): item = self.ui.tableWidget.item(row, column) if item is not None: rowdata.append(unicode(item.text()).encode('utf8')) else: rowdata.append('') writer.writerow(rowdata) stream.close() def clearTableCall(self): for ii in range(self.rows): self.ui.tableWidget.removeRow(0) self.ui.tableWidget.clearContents() self.rows = 0 self.fileNames = [] def processFile(self,fullPath): fileName, fileExtension = os.path.splitext(fullPath) fileName = os.path.basename(fullPath) self.fileNames.append(fileName) if fileExtension == '.csv': delimiter = ',' else: delimiter = None self.ui.statusbar.showMessage("processing: "+ fileName,2500) #wait here for the file to be completely written to disk and closed before trying to read it fi = QFileInfo(fullPath) while (not fi.isWritable()): time.sleep(0.001) fi.refresh() fp = open(fullPath, mode='r') fileBuffer = fp.read() fp.close() first10 = fileBuffer[0:10] nMcHeaderLines = 25 #number of header lines in mcgehee IV file format isMcFile = False #true if this is a McGehee iv file format if (not first10.__contains__('#')) and (first10.__contains__('/')) and (first10.__contains__('\t')):#the first line is not a comment #the first 8 chars do not contain comment symbol and do contain / and a tab, it's safe to assume mcgehee iv file format isMcFile = True #comment out the first 25 rows here fileBuffer = '#'+fileBuffer fileBuffer = fileBuffer.replace('\n', '\n#',nMcHeaderLines-1) splitBuffer = fileBuffer.splitlines(True) area = 1 noArea = True vsTime = False #this is not an i,v vs t data file #extract header lines and search for area header = [] for line in splitBuffer: if line.startswith('#'): header.append(line) if line.__contains__('Area'): area = float(line.split(' ')[3]) noArea = False if line.__contains__('I&V vs t'): if float(line.split(' ')[5]) == 1: vsTime = True else: break outputScaleFactor = np.array(1000/area) #for converstion to [mA/cm^2] tempFile = QTemporaryFile() tempFile.open() tempFile.writeData(fileBuffer) tempFile.flush() #read in data try: data = np.loadtxt(str(tempFile.fileName()),delimiter=delimiter) VV = data[:,0] II = data[:,1] if vsTime: time = data[:,2] except: self.ui.statusbar.showMessage('Could not read' + fileName +'. Prepend # to all non-data lines and try again',2500) return tempFile.close() tempFile.remove() if isMcFile: #convert to amps II = II/1000*area if not vsTime: #sort data by ascending voltage newOrder = VV.argsort() VV=VV[newOrder] II=II[newOrder] #remove duplicate voltage entries VV, indices = np.unique(VV, return_index =True) II = II[indices] else: #sort data by ascending time newOrder = time.argsort() VV=VV[newOrder] II=II[newOrder] time=time[newOrder] time=time-time[0]#start time at t=0 #catch and fix flipped current sign: if II[0] < II[-1]: II = II * -1 indexInQuad1 = np.logical_and(VV>0,II>0) if any(indexInQuad1): #enters statement if there is at least one datapoint in quadrant 1 isDarkCurve = False else: self.ui.statusbar.showMessage("Dark curve detected",500) isDarkCurve = True #put items in table self.ui.tableWidget.insertRow(self.rows) for ii in range(len(self.cols)): self.ui.tableWidget.setItem(self.rows,ii,QTableWidgetItem()) if not vsTime: fitParams, fitCovariance, infodict, errmsg, ier = self.bestEffortFit(VV,II) #print errmsg I0_fit = fitParams[0] Iph_fit = fitParams[1] Rs_fit = fitParams[2] Rsh_fit = fitParams[3] n_fit = fitParams[4] #0 -> LS-straight line #1 -> cubic spline interpolant smoothingParameter = 1-2e-6 iFitSpline = SmoothSpline(VV, II, p=smoothingParameter) def cellModel(voltageIn): #voltageIn = np.array(voltageIn) return vectorizedCurrent(voltageIn, I0_fit, Iph_fit, Rs_fit, Rsh_fit, n_fit) def invCellPowerSpline(voltageIn): if voltageIn < 0: return 0 else: return -1*voltageIn*iFitSpline(voltageIn) def invCellPowerModel(voltageIn): if voltageIn < 0: return 0 else: return -1*voltageIn*cellModel(voltageIn) if not isDarkCurve: VVq1 = VV[indexInQuad1] IIq1 = II[indexInQuad1] vMaxGuess = VVq1[np.array(VVq1*IIq1).argmax()] powerSearchResults = optimize.minimize(invCellPowerSpline,vMaxGuess) #catch a failed max power search: if not powerSearchResults.status == 0: print "power search exit code = " + str(powerSearchResults.status) print powerSearchResults.message vMax = nan iMax = nan pMax = nan else: vMax = powerSearchResults.x[0] iMax = iFitSpline([vMax])[0] pMax = vMax*iMax #only do this stuff if the char eqn fit was good if ier < 5: powerSearchResults_charEqn = optimize.minimize(invCellPowerModel,vMaxGuess) #catch a failed max power search: if not powerSearchResults_charEqn.status == 0: print "power search exit code = " + str(powerSearchResults_charEqn.status) print powerSearchResults_charEqn.message vMax_charEqn = nan else: vMax_charEqn = powerSearchResults_charEqn.x[0] #dude try: Voc_nn_charEqn=optimize.brentq(cellModel, VV[0], VV[-1]) except: Voc_nn_charEqn = nan else: Voc_nn_charEqn = nan vMax_charEqn = nan try: Voc_nn = optimize.brentq(iFitSpline, VV[0], VV[-1]) except: Voc_nn = nan else: Voc_nn = nan vMax = nan iMax = nan pMax = nan Voc_nn_charEqn = nan vMax_charEqn = nan iMax_charEqn = nan pMax_charEqn = nan if ier < 5: dontFindBounds = False iMax_charEqn = cellModel([vMax_charEqn])[0] pMax_charEqn = vMax_charEqn*iMax_charEqn Isc_nn_charEqn = cellModel(0) FF_charEqn = pMax_charEqn/(Voc_nn_charEqn*Isc_nn_charEqn) else: dontFindBounds = True iMax_charEqn = nan pMax_charEqn = nan Isc_nn_charEqn = nan FF_charEqn = nan #there is a maddening bug in SmoothingSpline: it can't evaluate 0 alone, so I have to do this: try: Isc_nn = iFitSpline([0,1e-55])[0] except: Isc_nn = nan FF = pMax/(Voc_nn*Isc_nn) if (ier != 7) and (ier != 6) and (not dontFindBounds) and (type(fitCovariance) is not float): #error estimation: alpha = 0.05 # 95% confidence interval = 100*(1-alpha) nn = len(VV) # number of data points p = len(fitParams) # number of parameters dof = max(0, nn - p) # number of degrees of freedom # student-t value for the dof and confidence level tval = t.ppf(1.0-alpha/2., dof) lowers = [] uppers = [] #calculate 95% confidence interval for a, p,var in zip(range(nn), fitParams, np.diag(fitCovariance)): sigma = var**0.5 lower = p - sigma*tval upper = p + sigma*tval lowers.append(lower) uppers.append(upper) else: uppers = [nan,nan,nan,nan,nan] lowers = [nan,nan,nan,nan,nan] plotPoints = 1000 fitX = np.linspace(VV[0],VV[-1],plotPoints) if ier < 5: modelY = cellModel(fitX)*outputScaleFactor else: modelY = np.empty(plotPoints)*nan splineY = iFitSpline(fitX)*outputScaleFactor graphData = {'vsTime':vsTime,'origRow':self.rows,'fitX':fitX,'modelY':modelY,'splineY':splineY,'i':II*outputScaleFactor,'v':VV,'Voc':Voc_nn,'Isc':Isc_nn*outputScaleFactor,'Vmax':vMax,'Imax':iMax*outputScaleFactor} #export button exportBtn = QPushButton(self.ui.tableWidget) exportBtn.setText('Export') exportBtn.clicked.connect(self.handleButton) self.ui.tableWidget.setCellWidget(self.rows,self.cols.keys().index('exportBtn'), exportBtn) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pce')).setData(Qt.DisplayRole,round(pMax/area*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pce')).setToolTip(str(round(pMax_charEqn/area*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pmax')).setData(Qt.DisplayRole,round(pMax/area*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pmax')).setToolTip(str(round(pMax_charEqn/area*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('jsc')).setData(Qt.DisplayRole,round(Isc_nn/area*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('jsc')).setToolTip(str(round(Isc_nn_charEqn/area*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('voc')).setData(Qt.DisplayRole,round(Voc_nn*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('voc')).setToolTip(str(round(Voc_nn_charEqn*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('ff')).setData(Qt.DisplayRole,round(FF,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('ff')).setToolTip(str(round(FF_charEqn,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rs')).setData(Qt.DisplayRole,round(Rs_fit*area,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rs')).setToolTip('[{0} {1}]'.format(lowers[2]*area, uppers[2]*area)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rsh')).setData(Qt.DisplayRole,round(Rsh_fit*area,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rsh')).setToolTip('[{0} {1}]'.format(lowers[3]*area, uppers[3]*area)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('jph')).setData(Qt.DisplayRole,round(Iph_fit/area*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('jph')).setToolTip('[{0} {1}]'.format(lowers[1]/area*1e3, uppers[1]/area*1e3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('j0')).setData(Qt.DisplayRole,round(I0_fit/area*1e9,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('j0')).setToolTip('[{0} {1}]'.format(lowers[0]/area*1e9, uppers[0]/area*1e9)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('n')).setData(Qt.DisplayRole,round(n_fit,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('n')).setToolTip('[{0} {1}]'.format(lowers[4], uppers[4])) self.ui.tableWidget.item(self.rows,self.cols.keys().index('Vmax')).setData(Qt.DisplayRole,round(vMax*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('Vmax')).setToolTip(str(round(vMax_charEqn*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('area')).setData(Qt.DisplayRole,round(area,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pmax2')).setData(Qt.DisplayRole,round(pMax*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('pmax2')).setToolTip(str(round(pMax_charEqn*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('isc')).setData(Qt.DisplayRole,round(Isc_nn*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('isc')).setToolTip(str(round(Isc_nn_charEqn*1e3,3))) self.ui.tableWidget.item(self.rows,self.cols.keys().index('iph')).setData(Qt.DisplayRole,round(Iph_fit*1e3,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('iph')).setToolTip('[{0} {1}]'.format(lowers[1]*1e3, uppers[1]*1e3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('i0')).setData(Qt.DisplayRole,round(I0_fit*1e9,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('i0')).setToolTip('[{0} {1}]'.format(lowers[0]*1e9, uppers[0]*1e9)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rs2')).setData(Qt.DisplayRole,round(Rs_fit,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rs2')).setToolTip('[{0} {1}]'.format(lowers[2], uppers[2])) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rsh2')).setData(Qt.DisplayRole,round(Rsh_fit,3)) self.ui.tableWidget.item(self.rows,self.cols.keys().index('rsh2')).setToolTip('[{0} {1}]'.format(lowers[3], uppers[3])) else:#vs time graphData = {'vsTime':vsTime,'origRow':self.rows,'time':time,'i':II*outputScaleFactor,'v':VV} #file name self.ui.tableWidget.item(self.rows,self.cols.keys().index('file')).setText(fileName) self.ui.tableWidget.item(self.rows,self.cols.keys().index('file')).setToolTip(''.join(header)) #plot button plotBtn = QPushButton(self.ui.tableWidget) plotBtn.setText('Plot') plotBtn.clicked.connect(self.handleButton) self.ui.tableWidget.setCellWidget(self.rows,self.cols.keys().index('plotBtn'), plotBtn) self.ui.tableWidget.item(self.rows,self.cols.keys().index('plotBtn')).setData(Qt.UserRole,graphData) self.ui.tableWidget.resizeColumnsToContents() self.rows = self.rows + 1 def bestEffortFit(self,VV,II): #splineTestVV=np.linspace(VV[0],VV[-1],1000) #splineTestII=iFitSpline(splineTestVV) #p1, = plt.plot(splineTestVV,splineTestII) #p3, = plt.plot(VV,II,ls='None',marker='o', label='Data') #plt.draw() #plt.show() #data point selection: #lowest voltage (might be same as Isc) V_start_n = VV[0] I_start_n = II[0] #highest voltage V_end_n = VV[-1] I_end_n = II[-1] #Isc iFit = interpolate.interp1d(VV,II) V_sc_n = 0 try: I_sc_n = float(iFit(V_sc_n)) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) #mpp VVcalc = VV-VV[0] IIcalc = II-min(II) Pvirtual= np.array(VVcalc*IIcalc) vMaxIndex = Pvirtual.argmax() V_vmpp_n = VV[vMaxIndex] I_vmpp_n = II[vMaxIndex] #Vp: half way in voltage between vMpp and the start of the dataset: V_vp_n = (V_vmpp_n-V_start_n)/2 +V_start_n try: I_vp_n = float(iFit(V_vp_n)) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) #Ip: half way in current between vMpp and the end of the dataset: I_ip_n = (I_vmpp_n-I_end_n)/2 + I_end_n iFit2 = interpolate.interp1d(VV,II-I_ip_n) try: V_ip_n = optimize.brentq(iFit2, VV[0], VV[-1]) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) diaplayAllGuesses = False def evaluateGuessPlot(dataX, dataY, myguess): myguess = [float(x) for x in myguess] print "myguess:" print myguess vv=np.linspace(min(dataX),max(dataX),1000) ii=vectorizedCurrent(vv,myguess[0],myguess[1],myguess[2],myguess[3],myguess[4]) plt.title('Guess and raw data') plt.plot(vv,ii) plt.scatter(dataX,dataY) plt.grid(b=True) plt.draw() plt.show() #phase 1 guesses: I_L_initial_guess = I_sc_n R_sh_initial_guess = 1e6 #compute intellegent guesses for Iph, Rsh by forcing the curve through several data points and numerically solving the resulting system of eqns newRhs = rhs - I aLine = Rsh*V+Iph-I eqnSys1 = aLine.subs([(V,V_start_n),(I,I_start_n)]) eqnSys2 = aLine.subs([(V,V_vp_n),(I,I_vp_n)]) eqnSys = (eqnSys1,eqnSys2) try: nGuessSln = sympy.nsolve(eqnSys,(Iph,Rsh),(I_L_initial_guess,R_sh_initial_guess),maxsteps=10000) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) I_L_guess = nGuessSln[0] R_sh_guess = -1*1/nGuessSln[1] R_s_guess = -1*(V_end_n-V_ip_n)/(I_end_n-I_ip_n) n_initial_guess = 2 #TODO: maybe a more intelegant guess for n can be found using http://pvcdrom.pveducation.org/CHARACT/IDEALITY.HTM I0_initial_guess = eyeNot[0].evalf(subs={Vth:thermalVoltage,Rs:R_s_guess,Rsh:R_sh_guess,Iph:I_L_guess,n:n_initial_guess,I:I_ip_n,V:V_ip_n}) initial_guess = [I0_initial_guess, I_L_guess, R_s_guess, R_sh_guess, n_initial_guess] if diaplayAllGuesses: evaluateGuessPlot(VV, II, initial_guess) # let's try the fit now, if it works great, we're done, otherwise we can continue #try: #guess = initial_guess #fitParams, fitCovariance, infodict, errmsg, ier = optimize.curve_fit(optimizeThis, VV, II,p0=guess,full_output = True,xtol=1e-13,ftol=1e-15) #return(fitParams, fitCovariance, infodict, errmsg, ier) #except: #pass #refine guesses for I0 and Rs by forcing the curve through several data points and numerically solving the resulting system of eqns eqnSys1 = newRhs.subs([(Vth,thermalVoltage),(Iph,I_L_guess),(V,V_ip_n),(I,I_ip_n),(n,n_initial_guess),(Rsh,R_sh_guess)]) eqnSys2 = newRhs.subs([(Vth,thermalVoltage),(Iph,I_L_guess),(V,V_end_n),(I,I_end_n),(n,n_initial_guess),(Rsh,R_sh_guess)]) eqnSys = (eqnSys1,eqnSys2) try: nGuessSln = sympy.nsolve(eqnSys,(I0,Rs),(I0_initial_guess,R_s_guess),maxsteps=10000) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) I0_guess = nGuessSln[0] R_s_guess = nGuessSln[1] #Rs_initial_guess = RsEqn[0].evalf(subs={I0:I0_initial_guess,Vth:thermalVoltage,Rsh:R_sh_guess,Iph:I_L_guess,n:n_initial_guess,I:I_end_n,V:V_end_n}) #I0_guess = I0_initial_guess #R_s_guess = Rs_initial_guess guess = [I0_guess, I_L_guess, R_s_guess, R_sh_guess, n_initial_guess] if diaplayAllGuesses: evaluateGuessPlot(VV, II, guess) #nidf #give 5x weight to data around mpp #nP = II*VV #maxIndex = np.argmax(nP) #weights = np.ones(len(II)) #halfRange = (V_ip_n-VV[vMaxIndex])/2 #upperTarget = VV[vMaxIndex] + halfRange #lowerTarget = VV[vMaxIndex] - halfRange #lowerTarget = 0 #upperTarget = V_oc_n #lowerI = np.argmin(abs(VV-lowerTarget)) #upperI = np.argmin(abs(VV-upperTarget)) #weights[range(lowerI,upperI)] = 3 #weights[maxnpi] = 10 #todo: play with setting up "key points" guess = [float(x) for x in guess] #odrMod = odr.Model(odrThing) #myData = odr.Data(VV,II) #myodr = odr.ODR(myData, odrMod, beta0=guess,maxit=5000,sstol=1e-20,partol=1e-20)# #myoutput = myodr.run() #myoutput.pprint() #see http://docs.scipy.org/doc/external/odrpack_guide.pdf try: #myoutput = myodr.run() #fitParams = myoutput.beta #print myoutput.stopreason #print myoutput.info #ier = 1 fitParams, fitCovariance, infodict, errmsg, ier = optimize.curve_fit(optimizeThis, VV, II,p0=guess,full_output = True,xtol=1e-13,ftol=1e-15) #fitParams, fitCovariance, infodict, errmsg, ier = optimize.leastsq(func=residual, args=(VV, II, np.ones(len(II))),x0=guess,full_output=1,xtol=1e-12,ftol=1e-14)#,xtol=1e-12,ftol=1e-14,maxfev=12000 #fitParams, fitCovariance, infodict, errmsg, ier = optimize.leastsq(func=residual, args=(VV, II, weights),x0=fitParams,full_output=1,ftol=1e-15,xtol=0)#,xtol=1e-12,ftol=1e-14 alwaysShowRecap = False if alwaysShowRecap: vv=np.linspace(VV[0],VV[-1],1000) print "fit:" print fitParams print "guess:" print guess print ier print errmsg ii=vectorizedCurrent(vv,guess[0],guess[1],guess[2],guess[3],guess[4]) ii2=vectorizedCurrent(vv,fitParams[0],fitParams[1],fitParams[2],fitParams[3],fitParams[4]) plt.title('Fit analysis') p1, = plt.plot(vv,ii, label='Guess',ls='--') p2, = plt.plot(vv,ii2, label='Fit') p3, = plt.plot(VV,II,ls='None',marker='o', label='Data') #p4, = plt.plot(VV[range(lowerI,upperI)],II[range(lowerI,upperI)],ls="None",marker='o', label='5x Weight Data') ax = plt.gca() handles, labels = ax.get_legend_handles_labels() ax.legend(handles, labels, loc=3) plt.grid(b=True) plt.draw() plt.show() return(fitParams, fitCovariance, infodict, errmsg, ier) except: return([[nan,nan,nan,nan,nan], [nan,nan,nan,nan,nan], nan, "hard fail", 10]) def openCall(self): #remember the last path th user opened if self.settings.contains('lastFolder'): openDir = self.settings.value('lastFolder').toString() else: openDir = '.' fileNames = QFileDialog.getOpenFileNamesAndFilter(directory = openDir, caption="Select one or more files to open", filter = '(*.txt *.csv);;Folders (*)') #fileNames = QFileDialog.getExistingDirectory(directory = openDir, caption="Select one or more files to open") if len(fileNames[0])>0:#check if user clicked cancel self.workingDirectory = os.path.dirname(str(fileNames[0][0])) self.settings.setValue('lastFolder',self.workingDirectory) for fullPath in fileNames[0]: fullPath = str(fullPath) self.processFile(fullPath) if self.ui.actionEnable_Watching.isChecked(): watchedDirs = self.watcher.directories() self.watcher.removePaths(watchedDirs) self.watcher.addPath(self.workingDirectory) self.handleWatchUpdate(self.workingDirectory) #user chose file --> watch def handleWatchAction(self): #remember the last path th user opened if self.settings.contains('lastFolder'): openDir = self.settings.value('lastFolder').toString() else: openDir = '.' myDir = QFileDialog.getExistingDirectory(directory = openDir, caption="Select folder to watch") if len(myDir)>0:#check if user clicked cancel self.workingDirectory = str(myDir) self.settings.setValue('lastFolder',self.workingDirectory) self.ui.actionEnable_Watching.setChecked(True) watchedDirs = self.watcher.directories() self.watcher.removePaths(watchedDirs) self.watcher.addPath(self.workingDirectory) self.handleWatchUpdate(self.workingDirectory) #user toggeled Tools --> Enable Watching def watchCall(self): watchedDirs = self.watcher.directories() self.watcher.removePaths(watchedDirs) if self.ui.actionEnable_Watching.isChecked(): if (self.workingDirectory != ''): self.watcher.addPath(self.workingDirectory) self.handleWatchUpdate(self.workingDirectory) def handleWatchUpdate(self,path): myDir = QDir(path) myDir.setNameFilters(self.supportedExtensions) allFilesNow = myDir.entryList() allFilesNow = list(allFilesNow) allFilesNow = [str(item) for item in allFilesNow] differentFiles = list(set(allFilesNow) ^ set(self.fileNames)) if differentFiles != []: for aFile in differentFiles: if self.fileNames.__contains__(aFile): #TODO: delete the file from the table self.ui.statusbar.showMessage('Removed' + aFile,2500) else: #process the new file self.processFile(os.path.join(self.workingDirectory,aFile))
class MainWindow(QMainWindow): def __init__(self, parent=None): """ init UI """ QMainWindow.__init__(self, parent) self.ui = Ui_MainWindow() self.ui.setupUi(self) self.ui.actionSaveSession.setEnabled(False) self.distributedObjects = DistributedObjects() self.act = self.distributedObjects.actions self.debugController = self.distributedObjects.debugController self.settings = self.debugController.settings self.signalproxy = self.distributedObjects.signalProxy self.pluginloader = PluginLoader(self.distributedObjects) self.editorController = self.distributedObjects.editorController self.act = self.distributedObjects.actions # init RecentFileHandler self.recentFileHandler = RecentFileHandler(self, self.ui.menuRecentlyUsedFiles, self.distributedObjects) self.debugController.executableOpened.connect(self.recentFileHandler.addToRecentFiles) self.debugController.executableOpened.connect(self.__observeWorkingBinary) self.debugController.executableOpened.connect(self.showExecutableName) self.debugController.executableOpened.connect(self.disableButtons) # signal proxy self.signalproxy.inferiorIsRunning.connect(self.targetStartedRunning, Qt.QueuedConnection) self.signalproxy.inferiorStoppedNormally.connect(self.targetStopped, Qt.QueuedConnection) self.signalproxy.inferiorReceivedSignal.connect(self.targetStopped, Qt.QueuedConnection) self.signalproxy.inferiorHasExited.connect(self.targetExited, Qt.QueuedConnection) self.signalproxy.addDockWidget.connect(self.addPluginDockWidget) self.signalproxy.removeDockWidget.connect(self.removeDockWidget) # Plugin Loader self.pluginloader.insertPluginAction.connect(self.addPluginAction) self.ui.actionSavePlugins.triggered.connect(self.showSavePluginsDialog) self.ui.actionLoadPlugins.triggered.connect(self.showLoadPluginsDialog) # Add editor to main window. self.ui.gridLayout.addWidget(self.distributedObjects.editorController.editor_view, 0, 0, 1, 1) self.pluginloader.addAvailablePlugins() # Tell everyone to insert their dock widgets into the main window self.signalproxy.emitInsertDockWidgets() # get filelist dockwidget self.filelist_dockwidget = self.findChild(QDockWidget, "FileListView") self.setWindowFilePath("<none>") self.setupUi() self.createInitialWindowPlacement() self.readSettings() self.quickwatch = QuickWatch(self, self.distributedObjects) self.binaryName = None self.fileWatcher = QFileSystemWatcher() self.fileWatcher.fileChanged.connect(self.__binaryChanged) def setupUi(self): self.__initActions() self.ui.statusLabel = QLabel() self.ui.statusLabel.setText("Not running") self.ui.statusbar.addPermanentWidget(self.ui.statusLabel) self.ui.statusIcon = QLabel() self.ui.statusIcon.setPixmap(QPixmap(":/icons/images/inferior_not_running.png")) self.ui.statusbar.addPermanentWidget(self.ui.statusIcon) def __initActions(self): self.disableButtons() self.act.Record.setCheckable(True) self.act.ReverseNext.setEnabled(False) self.act.ReverseStep.setEnabled(False) self.act.SaveFile.setEnabled(False) # debug actions self.ui.menuDebug.addAction(self.act.Run) self.ui.menuDebug.addAction(self.act.Continue) self.ui.menuDebug.addAction(self.act.Interrupt) self.ui.menuDebug.addAction(self.act.Next) self.ui.menuDebug.addAction(self.act.Step) self.ui.menuDebug.addAction(self.act.Finish) self.ui.menuDebug.addAction(self.act.RunToCursor) self.ui.menuDebug.addAction(self.act.Record) self.ui.menuDebug.addAction(self.act.ReverseNext) self.ui.menuDebug.addAction(self.act.ReverseStep) # file actions self.ui.menuFile.insertAction(self.ui.actionSaveSession, self.act.OpenMenu) self.ui.menuFile.addAction(self.act.SaveFile) self.ui.menuFile.addAction(self.act.Exit) # add them to menubar and also menuView to respect order self.ui.menubar.addAction(self.ui.menuFile.menuAction()) self.ui.menubar.addAction(self.ui.menuView.menuAction()) self.ui.menubar.addAction(self.ui.menuDebug.menuAction()) self.ui.menubar.addAction(self.ui.menuHelp.menuAction()) # now make toolbar actions self.act.Open.setMenu(self.ui.menuRecentlyUsedFiles) self.ui.Main.addAction(self.act.Open) self.ui.Main.addAction(self.act.SaveFile) self.ui.Main.addSeparator() self.ui.Main.addAction(self.act.Run) self.ui.Main.addAction(self.act.Continue) self.ui.Main.addAction(self.act.Interrupt) self.ui.Main.addAction(self.act.Next) self.ui.Main.addAction(self.act.Step) self.ui.Main.addAction(self.act.Record) self.ui.Main.addAction(self.act.ReverseNext) self.ui.Main.addAction(self.act.ReverseStep) self.ui.Main.addAction(self.act.Finish) self.ui.Main.addAction(self.act.RunToCursor) self.ui.Main.addSeparator() self.ui.Main.addAction(self.act.Exit) # connect actions self.__connectActions() def __connectActions(self): # file menu self.act.Open.triggered.connect(self.showOpenExecutableDialog) self.act.OpenMenu.triggered.connect(self.showOpenExecutableDialog) self.act.Exit.triggered.connect(self.close) self.act.SaveFile.triggered.connect(self.signalproxy.emitSaveCurrentFile) # debug menu self.act.Run.triggered.connect(self.debugController.run) self.act.Next.triggered.connect(self.debugController.next_) self.act.Step.triggered.connect(self.debugController.step) self.act.Continue.triggered.connect(self.debugController.cont) self.act.Record.triggered.connect(self.toggleRecord) self.act.ReverseStep.triggered.connect(self.debugController.reverse_step) self.act.ReverseNext.triggered.connect(self.debugController.reverse_next) self.act.Interrupt.triggered.connect(self.debugController.interrupt) self.act.Finish.triggered.connect(self.debugController.finish) self.act.RunToCursor.triggered.connect(self.debugController.inferiorUntil) self.ui.actionRestoreSession.triggered.connect(self.distributedObjects.sessionManager.showRestoreSessionDialog) self.ui.actionSaveSession.triggered.connect(self.distributedObjects.sessionManager.showSaveSessionDialog) def addPluginDockWidget(self, area, widget, addToggleViewAction): self.addDockWidget(area, widget) if addToggleViewAction: self.ui.menuShow_View.addAction(widget.toggleViewAction()) def addPluginAction(self, Action): """ show plugin as menu entry """ self.ui.menuPlugins.addAction(Action) def createInitialWindowPlacement(self): """ Saves the window and widget placement after first start of program. """ # check if settings do not exist initExists = self.settings.contains("InitialWindowPlacement/geometry") if not initExists: self.breakpointWidget = self.findChild(QDockWidget, "BreakpointView") self.fileListWidget = self.findChild(QDockWidget, "FileListView") self.dataGraphWidget = self.findChild(QDockWidget, "DataGraphView") self.watchWidget = self.findChild(QDockWidget, "WatchView") self.localsWidget = self.findChild(QDockWidget, "LocalsView") self.stackWidget = self.findChild(QDockWidget, "StackView") self.tracepointWidget = self.findChild(QDockWidget, "TracepointView") self.gdbIoWidget = self.findChild(QDockWidget, "GdbIoView") self.pyIoWidget = self.findChild(QDockWidget, "PyIoView") self.inferiorIoWidget = self.findChild(QDockWidget, "InferiorIoView") # tabify widgets to initial state and save settings self.tabifyDockWidget(self.fileListWidget, self.dataGraphWidget) self.tabifyDockWidget(self.watchWidget, self.localsWidget) self.tabifyDockWidget(self.localsWidget, self.stackWidget) self.tabifyDockWidget(self.stackWidget, self.breakpointWidget) self.tabifyDockWidget(self.breakpointWidget, self.tracepointWidget) self.tabifyDockWidget(self.gdbIoWidget, self.pyIoWidget) self.tabifyDockWidget(self.pyIoWidget, self.inferiorIoWidget) self.settings.setValue("InitialWindowPlacement/geometry", self.saveGeometry()) self.settings.setValue("InitialWindowPlacement/windowState", self.saveState()) def restoreInitialWindowPlacement(self): """ Restores the window placement created by createInitialWindowPlacement(). """ self.restoreGeometry(self.settings.value("InitialWindowPlacement/geometry").toByteArray()) self.restoreState(self.settings.value("InitialWindowPlacement/windowState").toByteArray()) def showOpenExecutableDialog(self): filename = str(QFileDialog.getOpenFileName(self, "Open Executable", self.recentFileHandler.getDirOfLastFile())) if filename != "": self.debugController.openExecutable(filename) def showLoadPluginsDialog(self): dialog = QFileDialog() dialog.setNameFilter("*.xml") filename = str(dialog.getOpenFileName(self, "Load plugin configuration")) if filename != "": self.pluginloader.getActivePlugins(filename) def showSavePluginsDialog(self): dialog = QFileDialog() dialog.setNameFilter("*.xml") filename = str(dialog.getSaveFileName(self, "Save plugin configuration")) if filename != "": self.pluginloader.savePluginInfo(filename) def showExecutableName(self, filename): self.ui.actionSaveSession.setEnabled(True) # enable saving session self.setWindowFilePath(filename) def targetStartedRunning(self): self.ui.statusLabel.setText("Running") self.ui.statusIcon.setPixmap(QPixmap(":/icons/images/inferior_running.png")) self.enableButtons() def targetStopped(self, rec): self.ui.statusLabel.setText("Stopped") self.ui.statusIcon.setPixmap(QPixmap(":/icons/images/inferior_stopped.png")) def targetExited(self): self.ui.statusLabel.setText("Not running") self.disableButtons() self.ui.statusIcon.setPixmap(QPixmap(":/icons/images/inferior_not_running.png")) def closeEvent(self, event): if not self.distributedObjects.editorController.closeOpenedFiles(): event.ignore() # closing source files may be canceled by user else: self.settings.setValue("geometry", self.saveGeometry()) self.settings.setValue("windowState", self.saveState()) QMainWindow.closeEvent(self, event) self.pluginloader.savePluginInfo() def readSettings(self): self.restoreGeometry(self.settings.value("geometry").toByteArray()) self.restoreState(self.settings.value("windowState").toByteArray()) def toggleRecord(self, check): if check: self.debugController.record_start() self.act.ReverseNext.setEnabled(True) self.act.ReverseStep.setEnabled(True) else: self.debugController.record_stop() self.act.ReverseNext.setEnabled(False) self.act.ReverseStep.setEnabled(False) def enableButtons(self): self.act.Continue.setEnabled(True) self.act.Interrupt.setEnabled(True) self.act.Next.setEnabled(True) self.act.Step.setEnabled(True) self.act.Finish.setEnabled(True) self.act.RunToCursor.setEnabled(True) self.act.Record.setEnabled(True) def disableButtons(self): self.act.Continue.setEnabled(False) self.act.Interrupt.setEnabled(False) self.act.Next.setEnabled(False) self.act.Step.setEnabled(False) self.act.Finish.setEnabled(False) self.act.RunToCursor.setEnabled(False) self.act.Record.setChecked(False) self.act.Record.setEnabled(False) def __observeWorkingBinary(self, filename): """ Private Method to Observe Debugged Binary """ if self.binaryName != None: self.fileWatcher.removePath(self.binaryName) self.fileWatcher.addPath(filename) self.binaryName = filename def __binaryChanged(self): """ Slot for FileWatcher - Using QtMessagebox for interaction""" box = QtGui.QMessageBox() if ( box.question(self, "Binary Changed!", "Reload File?", QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes ): self.debugController.openExecutable(self.binaryName) else: self.fileWatcher.removePath(self.binaryName) self.fileWatcher.addPath(self.binaryName)
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 Infoboard(object): def __init__(self, schedule_dir): self.schedule_dir = schedule_dir self.videos = {} self.playlist = [] self.app = QApplication(sys.argv) self.view = QDeclarativeView() self.view.setSource(QUrl('scene.qml')) self.view.setResizeMode(QDeclarativeView.SizeRootObjectToView) self.viewRoot = self.view.rootObject() self.viewRoot.quit.connect(self.app.quit) self.viewRoot.finished.connect(self.show_next) self.view.setGeometry(100, 100, 400, 240) self.view.showFullScreen() self.watcher = QFileSystemWatcher() def schedule_dir_changed(self, filename): self.process_all_schedules() self.playlist = [] def run(self): self.watcher.addPath(self.schedule_dir) self.watcher.directoryChanged.connect(self.schedule_dir_changed) self.process_all_schedules() self.show_next() self.app.exec_() def process_schedule(self, key, fobj): self.videos[key] = [] for line in fobj: line = line.strip() if (not line) or line.startswith('#'): continue try: self.videos[key].append(Video(line)) except: pass def process_all_schedules(self): for filename in glob.glob(self.schedule_dir + '/*.txt'): filename = os.path.abspath(filename) with open(filename, 'rb') as f: self.process_schedule(filename, f) def show_next(self): item = self.playlist_next() if not item: return if item.type == 'image': self.viewRoot.showImage(item.filename) QTimer.singleShot(item.duration * 1000, self.show_next) elif item.type == 'video': self.viewRoot.showVideo(item.filename) def playlist_next(self): if len(self.playlist) == 0: self.process_all_schedules() today = date.today() all_videos = [] for key in sorted(self.videos.keys()): all_videos += self.videos[key] self.playlist = [video for video in all_videos if video.start_date <= today and video.end_date >= today] if len(self.playlist) == 0: return None return self.playlist.pop(0)
class ProjectWidget(Ui_Form, QWidget): SampleWidgetRole = Qt.UserRole + 1 projectsaved = pyqtSignal() projectupdated = pyqtSignal() projectloaded = pyqtSignal(object) selectlayersupdated = pyqtSignal(list) projectlocationchanged = pyqtSignal(str) def __init__(self, parent=None): super(ProjectWidget, self).__init__(parent) self.setupUi(self) self.project = None self.mapisloaded = False self.bar = None self.canvas.setCanvasColor(Qt.white) self.canvas.enableAntiAliasing(True) self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor) self.canvas.mapRenderer().setLabelingEngine(QgsPalLabeling()) self.fieldsmodel = QgsFieldModel() self.widgetmodel = WidgetsModel() self.possiblewidgetsmodel = QStandardItemModel() self.formlayersmodel = QgsLayerModel(watchregistry=False) self.formlayers = CaptureLayerFilter() self.formlayers.setSourceModel(self.formlayersmodel) self.selectlayermodel = CaptureLayersModel(watchregistry=False) self.selectlayerfilter = LayerTypeFilter() self.selectlayerfilter.setSourceModel(self.selectlayermodel) self.selectlayermodel.dataChanged.connect(self.selectlayerschanged) self.layerCombo.setModel(self.formlayers) self.widgetCombo.setModel(self.possiblewidgetsmodel) self.selectLayers.setModel(self.selectlayerfilter) self.selectLayers_2.setModel(self.selectlayerfilter) self.fieldList.setModel(self.fieldsmodel) self.widgetlist.setModel(self.widgetmodel) self.widgetlist.selectionModel().currentChanged.connect(self.updatecurrentwidget) self.widgetmodel.rowsRemoved.connect(self.setwidgetconfigvisiable) self.widgetmodel.rowsInserted.connect(self.setwidgetconfigvisiable) self.widgetmodel.modelReset.connect(self.setwidgetconfigvisiable) self.titleText.textChanged.connect(self.updatetitle) QgsProject.instance().readProject.connect(self._readproject) self.loadwidgettypes() self.addWidgetButton.pressed.connect(self.newwidget) self.removeWidgetButton.pressed.connect(self.removewidget) self.roamVersionLabel.setText("You are running IntraMaps Roam version {}".format(roam.__version__)) self.openProjectFolderButton.pressed.connect(self.openprojectfolder) self.openinQGISButton.pressed.connect(self.openinqgis) self.filewatcher = QFileSystemWatcher() self.filewatcher.fileChanged.connect(self.qgisprojectupdated) self.formfolderLabel.linkActivated.connect(self.openformfolder) self.projectupdatedlabel.linkActivated.connect(self.reloadproject) self.projectupdatedlabel.hide() self.formtab.currentChanged.connect(self.formtabchanged) self.expressionButton.clicked.connect(self.opendefaultexpression) self.fieldList.currentIndexChanged.connect(self.updatewidgetname) self.fieldwarninglabel.hide() for item, data in readonlyvalues: self.readonlyCombo.addItem(item, data) self.setpage(4) self.form = None self.projectlocations.currentIndexChanged[str].connect(self.projectlocationchanged.emit) def setaboutinfo(self): self.versionLabel.setText(roam.__version__) self.qgisapiLabel.setText(str(QGis.QGIS_VERSION)) def checkcapturelayers(self): haslayers = self.project.hascapturelayers() self.formslayerlabel.setVisible(not haslayers) return haslayers def opendefaultexpression(self): layer = self.currentform.QGISLayer dlg = QgsExpressionBuilderDialog(layer, "Create default value expression", self) text = self.defaultvalueText.text().strip('[%').strip('%]').strip() dlg.setExpressionText(text) if dlg.exec_(): self.defaultvalueText.setText('[% {} %]'.format(dlg.expressionText())) def openformfolder(self, url): openfolder(url) def selectlayerschanged(self, *args): self.formlayers.setSelectLayers(self.project.selectlayers) self.checkcapturelayers() self.selectlayersupdated.emit(self.project.selectlayers) def formtabchanged(self, index): # preview if index == 1: self.form.settings['widgets'] = list(self.widgetmodel.widgets()) self.setformpreview(self.form) def setprojectfolders(self, folders): for folder in folders: self.projectlocations.addItem(folder) def setpage(self, page): self.stackedWidget.setCurrentIndex(page) def reloadproject(self, *args): self.setproject(self.project) def qgisprojectupdated(self, path): self.projectupdatedlabel.show() self.projectupdatedlabel.setText("The QGIS project has been updated. <a href='reload'> " "Click to reload</a>. <b style=\"color:red\">Unsaved data will be lost</b>") def openinqgis(self): projectfile = self.project.projectfile qgislocation = r'C:\OSGeo4W\bin\qgis.bat' qgislocation = roam.config.settings.setdefault('configmanager', {}) \ .setdefault('qgislocation', qgislocation) try: openqgis(projectfile, qgislocation) except WindowsError: self.bar.pushMessage("Looks like I couldn't find QGIS", "Check qgislocation in settings.config", QgsMessageBar.WARNING) def openprojectfolder(self): folder = self.project.folder openfolder(folder) def setwidgetconfigvisiable(self, *args): haswidgets = self.widgetmodel.rowCount() > 0 self.widgetframe.setEnabled(haswidgets) def removewidget(self): """ Remove the selected widget from the widgets list """ widget, index = self.currentuserwidget if index.isValid(): self.widgetmodel.removeRow(index.row(), index.parent()) def newwidget(self): """ Create a new widget. The default is a list. """ widget = {} widget['widget'] = 'List' # Grab the first field. widget['field'] = self.fieldsmodel.index(0, 0).data(QgsFieldModel.FieldNameRole) currentindex = self.widgetlist.currentIndex() currentitem = self.widgetmodel.itemFromIndex(currentindex) if currentitem and currentitem.iscontainor(): parent = currentindex else: parent = currentindex.parent() index = self.widgetmodel.addwidget(widget, parent) self.widgetlist.setCurrentIndex(index) def loadwidgettypes(self): self.widgetCombo.blockSignals(True) for widgettype in roam.editorwidgets.core.supportedwidgets(): try: configclass = configmanager.editorwidgets.widgetconfigs[widgettype] except KeyError: continue configwidget = configclass() item = QStandardItem(widgettype) item.setData(configwidget, Qt.UserRole) item.setData(widgettype, Qt.UserRole + 1) item.setIcon(QIcon(widgeticon(widgettype))) self.widgetCombo.model().appendRow(item) self.widgetstack.addWidget(configwidget) self.widgetCombo.blockSignals(False) def usedfields(self): """ Return the list of fields that have been used by the the current form's widgets """ for widget in self.currentform.widgets: yield widget['field'] @property def currentform(self): """ Return the current selected form. """ return self.form @property def currentuserwidget(self): """ Return the selected user widget. """ index = self.widgetlist.currentIndex() return index.data(Qt.UserRole), index @property def currentwidgetconfig(self): """ Return the selected widget in the widget combo. """ index = self.widgetCombo.currentIndex() index = self.possiblewidgetsmodel.index(index, 0) return index.data(Qt.UserRole), index, index.data(Qt.UserRole + 1) def updatewidgetname(self, index): # Only change the edit text on name field if it's not already set to something other then the # field name. field = self.fieldsmodel.index(index, 0).data(QgsFieldModel.FieldNameRole) currenttext = self.nameText.text() foundfield = self.fieldsmodel.findfield(currenttext) if foundfield: self.nameText.setText(field) def _save_widgetfield(self, index): """ Save the selected field for the current widget. Shows a error if the field is already used but will allow the user to still set it in the case of extra logic for that field in the forms Python logic. """ widget, index = self.currentuserwidget row = self.fieldList.currentIndex() field = self.fieldsmodel.index(row, 0).data(QgsFieldModel.FieldNameRole) showwarning = field in self.usedfields() self.fieldwarninglabel.setVisible(showwarning) widget['field'] = field self.widgetmodel.setData(index, widget, Qt.UserRole) def _save_selectedwidget(self, index): configwidget, index, widgettype = self.currentwidgetconfig widget, index = self.currentuserwidget if not widget: return widget['widget'] = widgettype widget['required'] = self.requiredCheck.isChecked() widget['config'] = configwidget.getconfig() widget['name'] = self.nameText.text() widget['read-only-rules'] = [self.readonlyCombo.itemData(self.readonlyCombo.currentIndex())] widget['hidden'] = self.hiddenCheck.isChecked() self.widgetmodel.setData(index, widget, Qt.UserRole) def _save_default(self): widget, index = self.currentuserwidget default = self.defaultvalueText.text() widget['default'] = default self.widgetmodel.setData(index, widget, Qt.UserRole) def _save_selectionlayers(self, index, layer, value): config = self.project.settings self.selectlayermodel.dataChanged.emit(index, index) def _save_formtype(self, index): formtype = self.formtypeCombo.currentText() form = self.currentform form.settings['type'] = formtype def _save_formname(self, text): """ Save the form label to the settings file. """ try: form = self.currentform if form is None: return form.settings['label'] = text self.projectupdated.emit() except IndexError: return def _save_layer(self, index): """ Save the selected layer to the settings file. """ index = self.formlayers.index(index, 0) layer = index.data(Qt.UserRole) if not layer: return form = self.currentform if form is None: return form.settings['layer'] = layer.name() self.updatefields(layer) def setsplash(self, splash): pixmap = QPixmap(splash) w = self.splashlabel.width() h = self.splashlabel.height() self.splashlabel.setPixmap(pixmap.scaled(w,h, Qt.KeepAspectRatio)) def setproject(self, project, loadqgis=True): """ Set the widgets active project. """ self.disconnectsignals() self.mapisloaded = False self.filewatcher.removePaths(self.filewatcher.files()) self.projectupdatedlabel.hide() self._closeqgisproject() if project.valid: self.startsettings = copy.deepcopy(project.settings) self.project = project self.projectlabel.setText(project.name) self.versionText.setText(project.version) self.selectlayermodel.config = project.settings self.formlayers.setSelectLayers(self.project.selectlayers) self.setsplash(project.splash) self.loadqgisproject(project, self.project.projectfile) self.filewatcher.addPath(self.project.projectfile) self.projectloaded.emit(self.project) def loadqgisproject(self, project, projectfile): QDir.setCurrent(os.path.dirname(project.projectfile)) fileinfo = QFileInfo(project.projectfile) QgsProject.instance().read(fileinfo) def _closeqgisproject(self): if self.canvas.isDrawing(): return self.canvas.freeze(True) self.formlayersmodel.removeall() self.selectlayermodel.removeall() QgsMapLayerRegistry.instance().removeAllMapLayers() self.canvas.freeze(False) def loadmap(self): if self.mapisloaded: return # This is a dirty hack to work around the timer that is in QgsMapCanvas in 2.2. # Refresh will stop the canvas timer # Repaint will redraw the widget. # loadmap is only called once per project load so it's safe to do this here. self.canvas.refresh() self.canvas.repaint() parser = roam.projectparser.ProjectParser.fromFile(self.project.projectfile) canvasnode = parser.canvasnode self.canvas.mapRenderer().readXML(canvasnode) self.canvaslayers = parser.canvaslayers() self.canvas.setLayerSet(self.canvaslayers) self.canvas.updateScale() self.canvas.refresh() self.mapisloaded = True def _readproject(self, doc): self.formlayersmodel.refresh() self.selectlayermodel.refresh() self._updateforproject(self.project) def _updateforproject(self, project): self.titleText.setText(project.name) self.descriptionText.setPlainText(project.description) def swapwidgetconfig(self, index): widgetconfig, _, _ = self.currentwidgetconfig defaultvalue = widgetconfig.defaultvalue self.defaultvalueText.setText(defaultvalue) self.updatewidgetconfig({}) def updatetitle(self, text): self.project.settings['title'] = text self.projectlabel.setText(text) self.projectupdated.emit() def updatewidgetconfig(self, config): widgetconfig, index, widgettype = self.currentwidgetconfig self.setconfigwidget(widgetconfig, config) def setformpreview(self, form): def removewidget(): item = self.frame_2.layout().itemAt(0) if item and item.widget(): item.widget().setParent(None) removewidget() featureform = FeatureForm.from_form(form, form.settings, None, {}) self.frame_2.layout().addWidget(featureform) def connectsignals(self): self.formLabelText.textChanged.connect(self._save_formname) self.layerCombo.currentIndexChanged.connect(self._save_layer) self.formtypeCombo.currentIndexChanged.connect(self._save_formtype) #widget settings self.fieldList.currentIndexChanged.connect(self._save_widgetfield) self.requiredCheck.toggled.connect(self._save_selectedwidget) self.defaultvalueText.textChanged.connect(self._save_default) self.widgetCombo.currentIndexChanged.connect(self._save_selectedwidget) self.widgetCombo.currentIndexChanged.connect(self.swapwidgetconfig) self.nameText.textChanged.connect(self._save_selectedwidget) self.readonlyCombo.currentIndexChanged.connect(self._save_selectedwidget) self.hiddenCheck.toggled.connect(self._save_selectedwidget) def disconnectsignals(self): try: self.formLabelText.textChanged.disconnect(self._save_formname) self.layerCombo.currentIndexChanged.disconnect(self._save_layer) self.formtypeCombo.currentIndexChanged.disconnect(self._save_formtype) #widget settings self.fieldList.currentIndexChanged.disconnect(self._save_widgetfield) self.requiredCheck.toggled.disconnect(self._save_selectedwidget) self.defaultvalueText.textChanged.disconnect(self._save_default) self.widgetCombo.currentIndexChanged.disconnect(self._save_selectedwidget) self.widgetCombo.currentIndexChanged.disconnect(self.swapwidgetconfig) self.nameText.textChanged.disconnect(self._save_selectedwidget) self.readonlyCombo.currentIndexChanged.disconnect(self._save_selectedwidget) self.hiddenCheck.toggled.disconnect(self._save_selectedwidget) except TypeError: pass def setform(self, form): """ Update the UI with the currently selected form. """ def getfirstlayer(): index = self.formlayers.index(0,0) layer = index.data(Qt.UserRole) layer = layer.name() return layer def loadwidgets(widget): """ Load the widgets into widgets model """ self.widgetmodel.clear() self.widgetmodel.loadwidgets(form.widgets) def findlayer(layername): index = self.formlayersmodel.findlayer(layername) index = self.formlayers.mapFromSource(index) layer = index.data(Qt.UserRole) return index, layer self.disconnectsignals() self.form = form settings = form.settings label = form.label layername = settings.setdefault('layer', getfirstlayer()) layerindex, layer = findlayer(layername) if not layer or not layerindex.isValid(): return formtype = settings.setdefault('type', 'auto') widgets = settings.setdefault('widgets', []) self.formLabelText.setText(label) folderurl = "<a href='{path}'>{name}</a>".format(path=form.folder, name=os.path.basename(form.folder)) self.formfolderLabel.setText(folderurl) self.layerCombo.setCurrentIndex(layerindex.row()) self.updatefields(layer) index = self.formtypeCombo.findText(formtype) if index == -1: self.formtypeCombo.insertItem(0, formtype) self.formtypeCombo.setCurrentIndex(0) else: self.formtypeCombo.setCurrentIndex(index) loadwidgets(widgets) # Set the first widget index = self.widgetmodel.index(0, 0) if index.isValid(): self.widgetlist.setCurrentIndex(index) self.updatecurrentwidget(index, None) self.connectsignals() def updatefields(self, layer): """ Update the UI with the fields for the selected layer. """ self.fieldsmodel.setLayer(layer) def setconfigwidget(self, configwidget, config): """ Set the active config widget. """ try: configwidget.widgetdirty.disconnect(self._save_selectedwidget) except TypeError: pass #self.descriptionLabel.setText(configwidget.description) self.widgetstack.setCurrentWidget(configwidget) configwidget.setconfig(config) configwidget.widgetdirty.connect(self._save_selectedwidget) def updatecurrentwidget(self, index, _): """ Update the UI with the config for the current selected widget. """ if not index.isValid(): return widget = index.data(Qt.UserRole) widgettype = widget['widget'] field = widget['field'] required = widget.setdefault('required', False) name = widget.setdefault('name', field) default = widget.setdefault('default', '') readonly = widget.setdefault('read-only-rules', []) hidden = widget.setdefault('hidden', False) try: data = readonly[0] except: data = 'never' self.readonlyCombo.blockSignals(True) index = self.readonlyCombo.findData(data) self.readonlyCombo.setCurrentIndex(index) self.readonlyCombo.blockSignals(False) self.defaultvalueText.blockSignals(True) if not isinstance(default, dict): self.defaultvalueText.setText(default) else: # TODO Handle the more advanced default values. pass self.defaultvalueText.blockSignals(False) self.nameText.blockSignals(True) self.nameText.setText(name) self.nameText.blockSignals(False) self.requiredCheck.blockSignals(True) self.requiredCheck.setChecked(required) self.requiredCheck.blockSignals(False) self.hiddenCheck.blockSignals(True) self.hiddenCheck.setChecked(hidden) self.hiddenCheck.blockSignals(False) if not field is None: self.fieldList.blockSignals(True) index = self.fieldList.findData(field.lower(), QgsFieldModel.FieldNameRole) if index > -1: self.fieldList.setCurrentIndex(index) else: self.fieldList.setEditText(field) self.fieldList.blockSignals(False) index = self.widgetCombo.findText(widgettype) self.widgetCombo.blockSignals(True) if index > -1: self.widgetCombo.setCurrentIndex(index) self.widgetCombo.blockSignals(False) self.updatewidgetconfig(config=widget.setdefault('config', {})) def _saveproject(self): """ Save the project config to disk. """ title = self.titleText.text() description = self.descriptionText.toPlainText() version = str(self.versionText.text()) settings = self.project.settings settings['title'] = title settings['description'] = description settings['version'] = version form = self.currentform if form: form.settings['widgets'] = list(self.widgetmodel.widgets()) logger.debug(form.settings) self.project.save() self.projectsaved.emit()
class WebDesktopEntry(xdg.DesktopEntry.DesktopEntry): def __init__(self, appid, *args, **kwargs): import webplier self.appid = appid xdg.DesktopEntry.DesktopEntry.__init__(self, *args, **kwargs) self.setWindowWidth = partial(xdg.DesktopEntry.DesktopEntry.set, self, 'X-%s-Window-Width' % APP_NAME) self.getWindowWidth = partial(xdg.DesktopEntry.DesktopEntry.get, self, 'X-%s-Window-Width' % APP_NAME) self.setWindowHeight = partial(xdg.DesktopEntry.DesktopEntry.set, self, 'X-%s-Window-Height' % APP_NAME) self.getWindowHeight = partial(xdg.DesktopEntry.DesktopEntry.get, self, 'X-%s-Window-Height' % APP_NAME) self.setBaseUrl = partial(xdg.DesktopEntry.DesktopEntry.set, self, 'X-%s-BaseUrl' % APP_NAME) self.getBaseUrl = partial(xdg.DesktopEntry.DesktopEntry.get, self, 'X-%s-BaseUrl' % APP_NAME) self.setName = partial(xdg.DesktopEntry.DesktopEntry.set, self, 'Name') if not self.hasGroup('Desktop Entry'): self.addGroup('Desktop Entry') pythonPath = 'python2.7' for p in os.environ["PATH"].split(os.pathsep): if os.path.exists(os.path.join(p, 'python2.7')): pythonPath = os.path.join(p, 'python2.7') break webplierPath = 'plier' for p in os.environ["PATH"].split(os.pathsep): if os.path.exists(os.path.join(p, 'plier')): webplierPath = os.path.join(p, 'plier') break if not self.get('Exec'): self.set('Exec', '%s %s %s' % (pythonPath, webplierPath, self.appid)) self.set('X-%s-Type' % APP_NAME, 'Webapp') self.set('X-%s-AppId' % APP_NAME, appid) self.set('StartupWMClass', appid) # TODO: Get this working. self.watcher = QFileSystemWatcher() self.addPath(getPath('%s-%s.desktop' % (APP_NAME.lower(), self.appid))) self.watcher.fileChanged.connect(self._refreshFromFilesystem) self.onRefresh = None def _refreshFromFilesystem(self): path = getPath('%s-%s.desktop' % (APP_NAME.lower(), self.appid)) if not (os.path.exists(path) and len(open(path).read()) > 1): return self.parse(path) if self.onRefresh: self.onRefresh() self.watcher.removePath(path) self.addPath(path) def write(self, *args, **kwargs): path = getPath('%s-%s.desktop' % (APP_NAME.lower(), self.appid)) self.watcher.removePath(path) v = xdg.DesktopEntry.DesktopEntry.write(self, *args, **kwargs) self.addPath(path) return v @classmethod def fromPath(cls, path): de = xdg.DesktopEntry.DesktopEntry(path) appid = de.get('X-%s-AppId' % APP_NAME) return cls(appid, path) def addPath(self, path): self.watcher.addPath(path) @classmethod def getAll(cls): """ Returns all found desktop entries that are webapps we created. """ # TODO: Should we hardcode ".desktop"? path = getPath() filenames = filter(lambda x: x.endswith('.desktop'), os.listdir(path)) filenames = map(lambda x: os.path.join(path, x), filenames) filenames = filter(cls.isWebDesktopEntry, filenames) return map(cls.fromPath, filenames) @classmethod def isWebDesktopEntry(cls, filename): de = xdg.DesktopEntry.DesktopEntry(filename) if de.hasKey('X-%s-Type' % APP_NAME) and de.get('X-%s-Type' % APP_NAME) == 'Webapp': return True else: return False
class Edytor(QMainWindow, Ui_edytor): fpath = "" def __init__(self): super(Edytor,self).__init__() self.setupUi(self) # Create watcher self.watcher = QFileSystemWatcher(self) # Register slots self.button_close.clicked.connect(self.close) self.button_open.clicked.connect(self.file_open) self.button_save.clicked.connect(self.file_save) self.editor_window.textChanged.connect(self.text_changed) self.watcher.fileChanged.connect(self.file_changed) @pyqtSlot() def file_open(self, fpath=""): if self.ask_discard(): return fpath = fpath or QFileDialog.getOpenFileName(self, "Open file...") if isfile(fpath): # Switch watcher files if self.fpath: self.watcher.removePath(self.fpath) self.watcher.addPath(fpath) with open(fpath) as f: text = f.read() self.editor_window.setText(text) # Disable afterwards since `setText` triggers "textChanged" signal self.button_save.setEnabled(False) # Finally save the path self.fpath = fpath @pyqtSlot() def file_save(self): if isfile(self.fpath): # Do not trigger fileChanged when saving ourselves self.watcher.removePath(self.fpath) text = self.editor_window.toPlainText() with open(self.fpath, 'w') as f: f.write(text) self.button_save.setEnabled(False) self.watcher.addPath(self.fpath) @pyqtSlot() def text_changed(self): if self.fpath: self.button_save.setEnabled(True) @pyqtSlot(str) def file_changed(self, path): res = QMessageBox.question( self, "%s - File has been changed" % self.objectName(), "The opened document has been modified by another program.\n" + "Do you want to reload the file?", QMessageBox.Yes | QMessageBox.No | (QMessageBox.Save if self.button_save.isEnabled() else 0), QMessageBox.Yes ) if res == QMessageBox.Yes: self.file_open(self.fpath) elif res == QMessageBox.Save: self.file_save() def ask_discard(self): if not self.button_save.isEnabled(): return res = QMessageBox.question( self, "%s - Unsaved changes" % self.objectName(), "The document has been modified\n" + "Do you want to save your changes?", QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, QMessageBox.Save ) print(res) if res == QMessageBox.Save: self.file_save() return res == QMessageBox.Cancel def closeEvent(self, event): # For some reason this is called twice when clicking the "Close" button SOMETIMES if self.ask_discard(): event.ignore() else: event.accept()
class OpenedFileView(QObject): MARGIN_NUMBERS, MARGIN_MARKER_FOLD, MARGIN_MARKER_BP, MARGIN_MARKER_TP, MARGIN_MARKER_EXEC, \ MARGIN_MARKER_EXEC_SIGNAL, MARKER_HIGHLIGHTED_LINE, MARGIN_MARKER_STACK = range(8) def __init__(self, distributedObjects, filename, parent): QObject.__init__(self, parent) filename = str(filename) self.distributedObjects = distributedObjects self.debugController = self.distributedObjects.debugController self.breakpointController = self.distributedObjects.breakpointController self.tracepointController = self.distributedObjects.tracepointController self.signalProxy = self.distributedObjects.signalProxy self.filename = filename self.lastContexMenuLine = 0 self.markerBp = QPixmap(":/markers/bp.png") self.markerTp = QPixmap(":/markers/tp.png") self.markerExec = QPixmap(":/markers/exec_pos.png") self.markerStack = QPixmap(":/markers/stack_pos.png") self.markerExecSignal = QPixmap(":/markers/exec_pos_signal.png") self.shown = False self.FileWatcher = QFileSystemWatcher() self.FileWatcher.addPath(self.filename) self.FileWatcher.fileChanged.connect(self.fileChanged) self.tab = QtGui.QWidget() self.gridLayout = QtGui.QGridLayout(self.tab) self.gridLayout.setMargin(0) self.edit = ScintillaWrapper(self.tab) self.font = QFont("DejaVu Sans Mono", 10) self.font.setStyleHint(QFont.TypeWriter) self.lexer = Qsci.QsciLexerCPP() self.lexer.setFont(self.font) self.edit.setToolTip("") self.edit.setWhatsThis("") self.edit.setLexer(self.lexer) self.edit.setMarginLineNumbers(self.MARGIN_NUMBERS, True) # set sensitivity self.edit.setMarginSensitivity(self.MARGIN_NUMBERS, True) self.edit.setMarginSensitivity(self.MARGIN_MARKER_BP, True) self.edit.setMarginSensitivity(self.MARGIN_MARKER_TP, True) # define symbol self.edit.markerDefine(self.markerBp, self.MARGIN_MARKER_BP) self.edit.markerDefine(self.markerTp, self.MARGIN_MARKER_TP) self.edit.markerDefine(self.markerExec, self.MARGIN_MARKER_EXEC) self.edit.markerDefine(self.markerStack, self.MARGIN_MARKER_STACK) self.edit.markerDefine(self.markerExecSignal, self.MARGIN_MARKER_EXEC_SIGNAL) self.edit.markerDefine(Qsci.QsciScintilla.Background, self.MARKER_HIGHLIGHTED_LINE) # define width and mask to show margin self.edit.setMarginWidth(self.MARGIN_MARKER_BP, 10) self.edit.setMarginMarkerMask(self.MARGIN_MARKER_BP, 1 << self.MARGIN_MARKER_BP) self.edit.setMarginWidth(self.MARGIN_MARKER_TP, 10) self.edit.setMarginMarkerMask(self.MARGIN_MARKER_TP, 1 << self.MARGIN_MARKER_TP) self.edit.setMarginWidth(self.MARGIN_MARKER_EXEC, 10) self.edit.setMarginMarkerMask(self.MARGIN_MARKER_EXEC, 1 << self.MARGIN_MARKER_EXEC | 1 << self.MARGIN_MARKER_EXEC_SIGNAL | 1 << self.MARGIN_MARKER_STACK) self.edit.setMarginWidth(self.MARKER_HIGHLIGHTED_LINE, 0) self.edit.setMarginMarkerMask(self.MARKER_HIGHLIGHTED_LINE, 1 << self.MARKER_HIGHLIGHTED_LINE) self.INDICATOR_TOOLTIP = self.edit.indicatorDefine(self.edit.BoxIndicator, -1) self.edit.setReadOnly(False) self.gridLayout.addWidget(self.edit, 0, 0, 1, 1) self.breakpoints = [] if not (QtCore.QFile.exists(filename)): logging.error("could not open file", filename) self.file_ = QtCore.QFile(filename) self.file_.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text) self.edit.read(self.file_) self.file_.close() self.changed = False self.edit.modificationChanged.connect(self.__setFileModified) self.setMarginWidthByLineNumbers() self.edit.SendScintilla(Qsci.QsciScintilla.SCI_SETMOUSEDWELLTIME, 500) # override scintillas context menu with our own self.edit.SendScintilla(Qsci.QsciScintilla.SCI_USEPOPUP, 0) self.edit.setContextMenuPolicy(Qt.CustomContextMenu) self.edit.customContextMenuRequested.connect(self.showContextMenu) self.edit.marginClicked.connect(self.marginClicked) self.edit.SCN_DOUBLECLICK.connect(self.editDoubleClicked) self.edit.dwellStart.connect(self.dwellStart) self.edit.dwellEnd.connect(self.dwellEnd) # initially, read all breakpoints and tracepoints from the model self.getBreakpointsFromModel() self.getTracepointsFromModel() _model = self.breakpointController.model() _model.rowsInserted.connect(self.getBreakpointsFromModel) _model.rowsRemoved.connect(self.getBreakpointsFromModel) _model = self.tracepointController.model() _model.rowsInserted.connect(self.getTracepointsFromModel) _model.rowsRemoved.connect(self.getTracepointsFromModel) act = self.distributedObjects.actions act.ToggleTrace.triggered.connect(self.toggleTracepoint) self.distributedObjects.editorController.config.itemsHaveChanged.connect(self.updateConfig) self.updateConfig() self.__allowToolTip = True self.__enableToolTip(True) def updateConfig(self): qs = Qsci.QsciScintilla c = self.distributedObjects.editorController.config self.edit.setWhitespaceVisibility(qs.WsVisible if c.showWhiteSpaces.value else qs.WsInvisible) self.edit.setIndentationGuides(c.showIndentationGuides.value) self.edit.setTabWidth(int(c.tabWidth.value)) self.edit.setWrapMode(qs.WrapWord if c.wrapLines.value else qs.WrapNone) self.edit.setFolding(qs.BoxedTreeFoldStyle if c.folding.value else qs.NoFoldStyle, self.MARGIN_MARKER_FOLD) self.lexer.setPaper(QColor(c.backgroundColor.value)) self.lexer.setColor(QColor(c.identifierColor.value), self.lexer.Identifier) self.lexer.setColor(QColor(c.identifierColor.value), self.lexer.Operator) self.edit.setCaretForegroundColor(QColor(c.identifierColor.value)) self.lexer.setColor(QColor(c.keywordColor.value), self.lexer.Keyword) self.lexer.setColor(QColor(c.stringColor.value), self.lexer.SingleQuotedString) self.lexer.setColor(QColor(c.stringColor.value), self.lexer.DoubleQuotedString) self.lexer.setColor(QColor(c.numberColor.value), self.lexer.Number) self.lexer.setColor(QColor(c.preprocessorColor.value), self.lexer.PreProcessor) self.lexer.setColor(QColor(c.commentColor.value), self.lexer.Comment) self.lexer.setColor(QColor(c.commentColor.value), self.lexer.CommentLine) self.lexer.setColor(QColor(c.commentColor.value), self.lexer.CommentDoc) self.edit.setIndicatorForegroundColor(QColor(c.tooltipIndicatorColor.value)) self.edit.setMarkerBackgroundColor(QColor(c.highlightColor.value), self.MARKER_HIGHLIGHTED_LINE) def fileChanged(self): logging.warning("Source file %s modified. Recompile executable for \ correct debugging.", self.filename) def saveFile(self): ''' Save source file ''' if (QtCore.QFile.exists(self.filename)): f = open(self.filename, 'w') f.write(self.edit.text()) f.close() self.file_.open(QtCore.QIODevice.ReadOnly | QtCore.QIODevice.Text) self.edit.read(self.file_) self.file_.close() self.__setFileModified(False) def __setFileModified(self, modified): ''' Method called whenever current file is marked as modified ''' self.distributedObjects.signalProxy.emitFileModified(self.filename, modified) def dwellStart(self, pos, x, y): if self.__allowToolTip: exp, (line, start, end) = self.getWordOrSelectionAndRangeFromPosition(pos) # try evaluating the expression before doing anything else: this will return None if the # expression is not valid (ie. something that is not a variable) if self.debugController.evaluateExpression(exp.strip()) is not None: self.edit.fillIndicatorRange(line, start, line, end, self.INDICATOR_TOOLTIP) startPos = self.edit.positionFromLineIndex(line, start) x = self.edit.SendScintilla(Qsci.QsciScintilla.SCI_POINTXFROMPOSITION, 0, startPos) y = self.edit.SendScintilla(Qsci.QsciScintilla.SCI_POINTYFROMPOSITION, 0, startPos) self.distributedObjects.toolTipController.showToolTip(exp, QtCore.QPoint(x + 3, y + 3 + self.edit.textHeight(line)), self.edit) def dwellEnd(self, position, x, y): self.distributedObjects.toolTipController.hideToolTip() self.edit.clearIndicatorRange(0, 0, self.edit.lines(), 1, self.INDICATOR_TOOLTIP) def showContextMenu(self, point): scipos = self.edit.SendScintilla( Qsci.QsciScintilla.SCI_POSITIONFROMPOINT, point.x(), point.y()) point = self.edit.mapToGlobal(point) exp, (line, start, end) = self.getWordOrSelectionAndRangeFromPosition(scipos) self.edit.fillIndicatorRange(line, start, line, end, self.INDICATOR_TOOLTIP) # self.edit.lineIndexFromPosition(..) returns tuple. first element is line self.lastContexMenuLine = int(self.edit.lineIndexFromPosition(scipos)[0]) listOfTracepoints = self.tracepointController.getTracepointsFromModel() self.subPopupMenu = QtGui.QMenu(self.edit) self.subPopupMenu.setTitle("Add variable " + exp + " to...") for tp in listOfTracepoints: self.subPopupMenu.addAction(self.distributedObjects.actions.getAddToTracepointAction(exp, tp.name, tp.addVar)) self.popupMenu = QtGui.QMenu(self.edit) self.popupMenu.addAction(self.distributedObjects.actions.getAddToWatchAction(exp, self.signalProxy.addWatch)) self.popupMenu.addAction(self.distributedObjects.actions.ToggleTrace) self.popupMenu.addAction(self.distributedObjects.actions.getAddToDatagraphAction(exp, self.distributedObjects.datagraphController.addWatch)) self.popupMenu.addSeparator() self.popupMenu.addMenu(self.subPopupMenu) self.popupMenu.popup(point) # disable the tooltips while the menu is shown self.__enableToolTip(False) self.popupMenu.aboutToHide.connect(lambda: self.__enableToolTip(True)) def __enableToolTip(self, enable): self.__allowToolTip = enable def isPositionInsideSelection(self, position): lf, cf, lt, ct = self.edit.getSelection() pl, pc = self.edit.lineIndexFromPosition(position) if lf < pl and pl < lt: return True elif lf == pl and pl < lt: return True if cf <= pc else False elif lf < pl and pl == lt: return True if pc <= ct else False elif lf == pl and pl == lt: return True if (cf <= pc and pc <= ct) else False else: return False def getWordOrSelectionAndRangeFromPosition(self, position): if self.isPositionInsideSelection(position): line, start, lineTo, end = self.edit.getSelection() if line != lineTo: return "" else: line, start, end = self.getWordRangeFromPosition(position) l = str(self.edit.text(line)) return l[start:end], (line, start, end) def getWordRangeFromPosition(self, position): line, col = self.edit.lineIndexFromPosition(position) s = str(self.edit.text(line)) start = col - 1 end = col r = re.compile(r'[\w\d_\.]') # FIXME: also scan over -> while start >= 0: if not r.match(s[start]): break start -= 1 start += 1 r = re.compile(r'[\w\d_]') while end < len(s): if not r.match(s[end]): break end += 1 return (line, start, end) def editDoubleClicked(self, position, line, modifiers): line, start, end = self.getWordRangeFromPosition(position) l = str(self.edit.text(line)) self.signalProxy.addWatch(str(l[start:end])) def showExecutionPosition(self, line): self.edit.markerAdd(line, self.MARGIN_MARKER_EXEC) self.showLine(line) def showSignalPosition(self, line): self.edit.markerAdd(line, self.MARGIN_MARKER_EXEC_SIGNAL) self.showLine(line) def showLine(self, line): self.edit.setCursorPosition(line, 1) self.edit.ensureLineVisible(line) def clearExecutionPositionMarkers(self): self.edit.markerDeleteAll(self.MARGIN_MARKER_EXEC) def setMarginWidthByLineNumbers(self): self.edit.setMarginWidth(0, ceil(log(self.edit.lines(), 10)) * 10 + 5) def marginClicked(self, margin, line, state): # if breakpoint should be toggled if margin == self.MARGIN_NUMBERS or margin == self.MARGIN_MARKER_BP: self.toggleBreakpointWithLine(line) elif margin == self.MARGIN_MARKER_TP: self.toggleTracepointWithLine(line) def toggleBreakpointWithLine(self, line): self.breakpointController.toggleBreakpoint(self.filename, line + 1) def toggleTracepointWithLine(self, line): self.tracepointController.toggleTracepoint(self.filename, line + 1) def toggleTracepoint(self): self.toggleTracepointWithLine(self.lastContexMenuLine) def getBreakpointsFromModel(self, parent=None, start=None, end=None): """Get breakpoints from model.""" # TODO: don't reload all breakpoints, just the one referenced by parent/start/end self.edit.markerDeleteAll(self.MARGIN_MARKER_BP) for bp in self.breakpointController.getBreakpointsFromModel(): if bp.fullname == self.filename: self.edit.markerAdd(int(bp.line) - 1, self.MARGIN_MARKER_BP) def getTracepointsFromModel(self): """Get tracepoints from model.""" self.edit.markerDeleteAll(self.MARGIN_MARKER_TP) for tp in self.tracepointController.getTracepointsFromModel(): if tp.fullname == self.filename: self.edit.markerAdd(int(tp.line) - 1, self.MARGIN_MARKER_TP) def highlightLine(self, line): self.removeHighlightedLines() self.edit.markerAdd(line, self.MARKER_HIGHLIGHTED_LINE) QTimer.singleShot(int(self.distributedObjects.editorController.config.highlightingDuration.value), self.removeHighlightedLines) def removeHighlightedLines(self): self.edit.markerDeleteAll(self.MARKER_HIGHLIGHTED_LINE)
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.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 PreferencesWindow(BaseWindow): def __init__(self, *args, **kwargs): super(PreferencesWindow, self).__init__(layoutCls = QVBoxLayout, *args, **kwargs) self.setWindowTitle("Settings") self.setWindowIcon(QIcon(SETTINGS['icon'])) self._add_steam_box() self._add_dota_box() self._add_additional_prefs() self._add_log_box() self._add_log_watcher() Settings().signals.changed.connect(self.update_path) def _add_log_watcher(self): self.watcher = QFileSystemWatcher() self.watcher.addPath(abspath(log.file_name)) self.watcher.fileChanged.connect(self.update_log) def show(self): self.update_log(abspath(log.file_name)) return super(PreferencesWindow, self).show() def update_path(self, setting_key, new_value): if setting_key == "dota_path": self.dota_path.setText(new_value) elif setting_key == "steam_path": self.steam_path.setText(new_value) def _add_steam_box(self): box = QGroupBox("Steam Location") box.setLayout(QHBoxLayout(box)) self.steam_path = QLineEdit(box) self.steam_path.setReadOnly(True) self.steam_path.setText(Settings().get("steam_path")) change_btn = QPushButton("Change...", box) change_btn.clicked.connect(self.change_steam_path) box.layout().addWidget(self.steam_path) box.layout().addWidget(change_btn) self.layout().addWidget(box) def _add_dota_box(self): box = QGroupBox("Dota Location") box.setLayout(QHBoxLayout(box)) self.dota_path = QLineEdit(box) self.dota_path.setReadOnly(True) self.dota_path.setText(Settings().get("dota_path")) change_btn = QPushButton("Change...", box) change_btn.clicked.connect(self.change_dota_path) box.layout().addWidget(self.dota_path) box.layout().addWidget(change_btn) self.layout().addWidget(box) def change_steam_path(self): self._change_path("steam_path", is_steam_path_valid) def change_dota_path(self): self._change_path("dota_path", is_dota_path_valid) def _change_path(self, path_key, is_valid): new_folder = str(QFileDialog.getExistingDirectory(parent=self, caption="Select new path", directory=Settings().get(path_key)) ) if new_folder and exists(new_folder): if is_valid(new_folder): Settings().set(path_key, new_folder) else: from d2mp.ui import Message Message.critical("Path is not valid", "Path was not saved in settings.\nPlease select a directory with the right executable in it!") def _add_additional_prefs(self): box = QGroupBox("Additional Preferences") box.setLayout(QHBoxLayout(box)) log_btn = QPushButton("View Log", box) log_btn.clicked.connect(self.open_log_file) reset_btn = QPushButton("Reset Settings", box) reset_btn.clicked.connect(Settings().reset) box.layout().addWidget(log_btn) box.layout().addWidget(reset_btn) self.layout().addWidget(box) def _add_log_box(self): box = QGroupBox("Application log") box.setLayout(QHBoxLayout(box)) self.log_area = QTextBrowser(box) self.log_area.setLineWrapMode(QTextEdit.NoWrap) box.layout().addWidget(self.log_area) self.layout().addWidget(box) def open_log_file(self): log.INFO("TODO: open file in standard editor") print(abspath(log.file_name)) def update_log(self, log_file): content = "" for line in open(log_file): if "========= new programm start =========" in line: content = "" else: content += line[37:] if content: self.log_area.setText(content)
class EdisFile(QObject): """ Representación de un objeto archivo """ def __init__(self, filename=''): QObject.__init__(self) self._is_new = True if not filename: self._filename = "Untitled" else: self._filename = filename self._is_new = False self._last_modification = None self._system_watcher = None @property def filename(self): return self._filename @property def is_new(self): return self._is_new def read(self): """ Itenta leer el contenido del archivo, si ocurre un error se lanza una excepción. """ try: with open(self.filename, mode='r') as f: content = f.read() return content except IOError as reason: raise exceptions.EdisIOError(reason) def write(self, content, new_filename=''): """ Escribe los datos en el archivo """ DEBUG("Saving file...") # Por defecto, si el archivo no tiene extensión se agrega .c ext = os.path.splitext(new_filename) if not ext[-1]: new_filename += '.c' if self.is_new: self._filename = new_filename self._is_new = False _file = QFile(self.filename) if not _file.open(QIODevice.WriteOnly | QIODevice.Truncate): raise exceptions.EdisIOError out_file = QTextStream(_file) out_file << content if self._system_watcher is not None: if self._filename is not None: archivos = self._system_watcher.files() self._system_watcher.removePath(archivos[0]) else: self.run_system_watcher() def run_system_watcher(self): """ Inicializa el control de monitoreo para modificaciones """ if self._system_watcher is None: self._system_watcher = QFileSystemWatcher() self.connect(self._system_watcher, SIGNAL("fileChanged(const QString&)"), self._on_file_changed) self._last_modification = os.lstat(self.filename).st_mtime self._system_watcher.addPath(self.filename) DEBUG("Watching {0}".format(self.filename)) def stop_system_watcher(self): if self._system_watcher is not None: self._system_watcher.removePath(self.filename) DEBUG("Stoping watching {0}".format(self.filename)) def _on_file_changed(self, filename): mtime = os.lstat(filename).st_mtime if mtime != self._last_modification: # Actualizo la última modificación self._last_modification = mtime self.emit(SIGNAL("fileChanged(PyQt_PyObject)"), self)