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 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 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 Syncer(QObject): def __init__(self, settings): QObject.__init__(self) self.s = settings self.filewatcher = None self.excelName = settings.excelName # the layer name self.excelSheetName = settings.excelSheetName self.excelKeyName = settings.excelKeyName self.excelFkIdx = field_idx_from_name(self.excelName, self.excelKeyName) self.excelPath = layer_from_name(self.excelName).publicSource() self.excelKeyName = field_name_from_idx(self.excelName, self.excelFkIdx) # shpfile layer self.shpName = settings.shpName self.shpKeyName = settings.shpKeyName self.shpKeyIdx = field_idx_from_name(self.shpName, self.shpKeyName) self.skipLines = settings.skipLines layer_from_name(self.shpName).setFeatureFormSuppress( QgsVectorLayer.SuppressOn if settings. hideDialog else QgsVectorLayer.SuppressOff) self.join() self.clear_edit_state() self.initialSync() def join(self): # join the shp layer to the excel layer, non cached # TODO: Ignore if already joined? shpLayer = layer_from_name(self.shpName) jinfo = QgsVectorJoinInfo() jinfo.joinFieldName = self.excelKeyName jinfo.targetFieldName = self.shpKeyName jinfo.joinLayerId = layer_from_name(self.excelName).id() jinfo.memoryCache = False jinfo.prefix = '' for jinfo2 in shpLayer.vectorJoins(): if jinfo2 == jinfo: info("Join already exists. Will not create it again") return info("Adding join between master and slave layers") shpLayer.addJoin(jinfo) def reload_excel(self): path = self.excelPath layer = layer_from_name(self.excelName) fsize = os.stat(self.excelPath).st_size info("fsize " + str(fsize)) if fsize == 0: info("File empty. Won't reload yet") return layer.dataProvider().forceReload() show_message_bar("Excel reloaded from disk.") def excel_changed(self): info("Excel changed on disk - need to sync") self.reload_excel() self.update_shp_from_excel() def get_max_id(self): layer = layer_from_name(self.shpName) if layer.dataProvider().featureCount() == 0: return 0 maxVal = layer.maximumValue(self.shpKeyIdx) if maxVal == None: return 0 else: return maxVal def renameIds(self, fidToId): layer = layer_from_name(self.shpName) layer.startEditing() feats = query_layer_for_fids(self.shpName, fidToId.keys()) for f in feats: res = layer.changeAttributeValue(f.id(), self.shpKeyIdx, fidToId[f.id()]) layer.commitChanges() def added_geom(self, layerId, feats): info("added feats " + str(feats)) layer = layer_from_name(self.shpName) maxFk = self.get_max_id() for i, _ in enumerate(feats): _id = maxFk + i + 1 feats[i].setAttribute(self.shpKeyName, _id) self.shpAdd = feats def removed_geom_precommit(self, fids): #info("Removed fids"+str(fids)) fks_to_remove = get_fk_set(self.shpName, self.shpKeyName, skipFirst=0, fids=fids, useProvider=True) self.shpRemove = self.shpRemove.union(fks_to_remove) info("feat ids to remove" + str(self.shpRemove)) def changed_geom(self, layerId, geoms): fids = geoms.keys() feats = query_layer_for_fids(self.shpName, fids) fks_to_change = get_fk_set(self.shpName, self.shpKeyName, skipFirst=0, fids=fids) self.shpChange = {k: v for (k, v) in zip(fks_to_change, feats)} # info("changed"+str(shpChange)) def get_ignore_indices(self): return [ field_idx_from_name(self.excelName, field) for field in self.s.expressions.keys() ] def write_feature_to_excel(self, sheet, idx, feat): sheet.write(idx, self.excelFkIdx, feat[self.shpKeyName]) for (fieldName, exp) in self.s.expressions.iteritems(): fieldIdx = field_idx_from_name(self.excelName, fieldName) exp = QgsExpression(exp) sheet.write(idx, fieldIdx, exp.evaluate(feat)) def write_rowvals_to_excel(self, sheet, idx, vals, ignore=None): if ignore is None: ignore = [] for i, v in enumerate(vals): if i not in ignore: sheet.write(idx, i, v) def update_excel_programmatically(self): status_msgs = [] rb = open_workbook(self.excelPath, formatting_info=True) r_sheet = rb.sheet_by_name(self.excelSheetName) # read only copy wb = xlwt.Workbook() w_sheet = wb.add_sheet(self.excelSheetName, cell_overwrite_ok=True) write_idx = 0 for row_index in range(r_sheet.nrows): if row_index < self.skipLines: # copy header and/or dummy lines vals = r_sheet.row_values(row_index) self.write_rowvals_to_excel(w_sheet, write_idx, vals) write_idx += 1 continue # print(r_sheet.cell(row_index,1).value) fk = r_sheet.cell(row_index, self.excelFkIdx).value if fk in self.shpRemove: status_msgs.append("Removing feature with id {}".format(fk)) continue if fk in self.shpChange.keys(): status_msgs.append( "Syncing geometry change to feature with id {}".format(fk)) shpf = self.shpChange[fk] self.write_feature_to_excel(w_sheet, write_idx, shpf) vals = r_sheet.row_values(row_index) self.write_rowvals_to_excel(w_sheet, write_idx, vals, ignore=self.get_ignore_indices()) else: # else just copy the row vals = r_sheet.row_values(row_index) self.write_rowvals_to_excel(w_sheet, write_idx, vals) write_idx += 1 fidToId = {} for shpf in self.shpAdd: status_msgs.append("Adding new feature with id {}".format( shpf.attribute(self.shpKeyName))) fidToId[shpf.id()] = shpf.attribute(self.shpKeyName) self.write_feature_to_excel(w_sheet, write_idx, shpf) write_idx += 1 info('\n'.join(status_msgs)) wb.save(self.excelPath) if status_msgs: show_message_bar(status_msgs) else: show_message_bar("No changes to shapefile to sync.") return fidToId def clear_edit_state(self): info("Clearing edit state") self.shpAdd = [] self.shpChange = {} self.shpRemove = Set([]) def update_excel_from_shp(self): info("Will now update excel from edited shapefile") info("changing:" + str(self.shpChange)) info("adding:" + str(self.shpAdd)) info("removing" + str(self.shpRemove)) self.deactivateFileWatcher() # so that we don't sync back and forth fidToId = self.update_excel_programmatically() # need to alter the ids(not fids) of the new features after, because # editing the features after they've been commited doesn't work if fidToId: self.deactivateShpConnections() self.renameIds(fidToId) self.activateShpConnections() self.reload_excel() self.activateFileWatcher() self.clear_edit_state() def updateShpLayer(self, fksToRemove): if not fksToRemove: return prompt_msg = "Attempt to synchronize between Excel and Shapefile. Shapefile has features with ids: ({}) that don't appear in the Excel. Delete those features from the shapefile? ".format( ','.join([str(fk) for fk in fksToRemove])) reply = QtGui.QMessageBox.question(iface.mainWindow(), 'Message', prompt_msg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: layer = layer_from_name(self.shpName) feats = [f for f in layer.getFeatures()] layer.startEditing() for f in feats: if f.attribute(self.shpKeyName) in fksToRemove: layer.deleteFeature(f.id()) layer.commitChanges() else: return def update_shp_from_excel(self): excelFks = Set( get_fk_set(self.excelName, self.excelKeyName, skipFirst=self.skipLines)) shpFks = Set(get_fk_set(self.shpName, self.shpKeyName, skipFirst=0)) # TODO also special warning if shp layer is in edit mode info("Keys in excel" + str(excelFks)) info("Keys in shp" + str(shpFks)) if shpFks == excelFks: info("Excel and Shp layer have the same rows. No update necessary") return inShpButNotInExcel = shpFks - excelFks inExcelButNotInShp = excelFks - shpFks if inExcelButNotInShp: warn( "There are rows in the excel file with no matching geometry {}." .format(inExcelButNotInShp)) # FIXME: if those are added later then they will be added twice.. # However, having an autoincrement id suggests features would be # added first from shp only? if inShpButNotInExcel: self.updateShpLayer(inShpButNotInExcel) def activateFileWatcher(self): self.filewatcher = QFileSystemWatcher([self.excelPath]) self.filewatcher.fileChanged.connect(self.excel_changed) def deactivateFileWatcher(self): self.filewatcher.fileChanged.disconnect(self.excel_changed) self.filewatcher.removePath(self.excelPath) def activateShpConnections(self): shpLayer = layer_from_name(self.shpName) shpLayer.committedFeaturesAdded.connect(self.added_geom) shpLayer.featuresDeleted.connect(self.removed_geom_precommit) shpLayer.committedGeometriesChanges.connect(self.changed_geom) shpLayer.editingStopped.connect(self.update_excel_from_shp) shpLayer.beforeRollBack.connect(self.clear_edit_state) def deactivateShpConnections(self): shpLayer = layer_from_name(self.shpName) shpLayer.committedFeaturesAdded.disconnect(self.added_geom) # shpLayer.featureAdded.disconnect(added_geom_precommit) shpLayer.featuresDeleted.disconnect(self.removed_geom_precommit) shpLayer.committedGeometriesChanges.disconnect(self.changed_geom) shpLayer.editingStopped.disconnect(self.update_excel_from_shp) shpLayer.beforeRollBack.disconnect(self.clear_edit_state) def initialSync(self): info("Initial Syncing excel to shp") self.update_shp_from_excel() self.activateFileWatcher() self.activateShpConnections() def __del__(self): self.deactivateFileWatcher() self.deactivateShpConnections()
class Syncer(QObject): def __init__(self, settings): QObject.__init__(self) self.s = settings self.filewatcher = None self.excelName = settings.excelName # the layer name self.excelSheetName = settings.excelSheetName self.excelKeyName = settings.excelKeyName self.excelFkIdx = field_idx_from_name( self.excelName, self.excelKeyName) self.excelPath = layer_from_name(self.excelName).publicSource() self.excelKeyName = field_name_from_idx( self.excelName, self.excelFkIdx) # shpfile layer self.shpName = settings.shpName self.shpKeyName = settings.shpKeyName self.shpKeyIdx = field_idx_from_name(self.shpName, self.shpKeyName) self.skipLines = settings.skipLines layer_from_name(self.shpName).setFeatureFormSuppress(QgsVectorLayer.SuppressOn if settings.hideDialog else QgsVectorLayer.SuppressOff) self.join() self.clear_edit_state() self.initialSync() def join(self): # join the shp layer to the excel layer, non cached # TODO: Ignore if already joined? shpLayer = layer_from_name(self.shpName) jinfo = QgsVectorJoinInfo() jinfo.joinFieldName = self.excelKeyName jinfo.targetFieldName = self.shpKeyName jinfo.joinLayerId = layer_from_name(self.excelName).id() jinfo.memoryCache = False jinfo.prefix = '' for jinfo2 in shpLayer.vectorJoins(): if jinfo2 == jinfo: info("Join already exists. Will not create it again") return info("Adding join between master and slave layers") shpLayer.addJoin(jinfo) def reload_excel(self): path = self.excelPath layer = layer_from_name(self.excelName) fsize = os.stat(self.excelPath).st_size info("fsize " + str(fsize)) if fsize == 0: info("File empty. Won't reload yet") return layer.dataProvider().forceReload() show_message_bar("Excel reloaded from disk.") def excel_changed(self): info("Excel changed on disk - need to sync") self.reload_excel() self.update_shp_from_excel() def get_max_id(self): layer = layer_from_name(self.shpName) if layer.dataProvider().featureCount() == 0: return 0 maxVal = layer.maximumValue(self.shpKeyIdx) if maxVal == None: return 0 else: return maxVal def renameIds(self, fidToId): layer = layer_from_name(self.shpName) layer.startEditing() feats = query_layer_for_fids(self.shpName, fidToId.keys()) for f in feats: res = layer.changeAttributeValue( f.id(), self.shpKeyIdx, fidToId[f.id()]) layer.commitChanges() def added_geom(self, layerId, feats): info("added feats " + str(feats)) layer = layer_from_name(self.shpName) maxFk = self.get_max_id() for i, _ in enumerate(feats): _id = maxFk + i + 1 feats[i].setAttribute(self.shpKeyName, _id) self.shpAdd = feats def removed_geom_precommit(self, fids): #info("Removed fids"+str(fids)) fks_to_remove = get_fk_set( self.shpName, self.shpKeyName, skipFirst=0, fids=fids, useProvider=True) self.shpRemove = self.shpRemove.union(fks_to_remove) info("feat ids to remove" + str(self.shpRemove)) def changed_geom(self, layerId, geoms): fids = geoms.keys() feats = query_layer_for_fids(self.shpName, fids) fks_to_change = get_fk_set( self.shpName, self.shpKeyName, skipFirst=0, fids=fids) self.shpChange = {k: v for (k, v) in zip(fks_to_change, feats)} # info("changed"+str(shpChange)) def get_ignore_indices(self): return [field_idx_from_name(self.excelName, field) for field in self.s.expressions.keys()] def write_feature_to_excel(self, sheet, idx, feat): sheet.write(idx, self.excelFkIdx, feat[self.shpKeyName]) for (fieldName, exp) in self.s.expressions.iteritems(): fieldIdx = field_idx_from_name(self.excelName, fieldName) exp = QgsExpression(exp) sheet.write(idx, fieldIdx, exp.evaluate(feat)) def write_rowvals_to_excel(self, sheet, idx, vals, ignore=None): if ignore is None: ignore = [] for i, v in enumerate(vals): if i not in ignore: sheet.write(idx, i, v) def update_excel_programmatically(self): status_msgs = [] rb = open_workbook(self.excelPath, formatting_info=True) r_sheet = rb.sheet_by_name(self.excelSheetName) # read only copy wb = xlwt.Workbook() w_sheet = wb.add_sheet(self.excelSheetName, cell_overwrite_ok=True) write_idx = 0 for row_index in range(r_sheet.nrows): if row_index < self.skipLines: # copy header and/or dummy lines vals = r_sheet.row_values(row_index) self.write_rowvals_to_excel(w_sheet, write_idx, vals) write_idx += 1 continue # print(r_sheet.cell(row_index,1).value) fk = r_sheet.cell(row_index, self.excelFkIdx).value if fk in self.shpRemove: status_msgs.append("Removing feature with id {}".format(fk)) continue if fk in self.shpChange.keys(): status_msgs.append( "Syncing geometry change to feature with id {}".format(fk)) shpf = self.shpChange[fk] self.write_feature_to_excel(w_sheet, write_idx, shpf) vals = r_sheet.row_values(row_index) self.write_rowvals_to_excel(w_sheet, write_idx, vals, ignore=self.get_ignore_indices()) else: # else just copy the row vals = r_sheet.row_values(row_index) self.write_rowvals_to_excel(w_sheet, write_idx, vals) write_idx += 1 fidToId = {} for shpf in self.shpAdd: status_msgs.append( "Adding new feature with id {}".format(shpf.attribute(self.shpKeyName))) fidToId[shpf.id()] = shpf.attribute(self.shpKeyName) self.write_feature_to_excel(w_sheet, write_idx, shpf) write_idx += 1 info('\n'.join(status_msgs)) wb.save(self.excelPath) if status_msgs: show_message_bar(status_msgs) else: show_message_bar("No changes to shapefile to sync.") return fidToId def clear_edit_state(self): info("Clearing edit state") self.shpAdd = [] self.shpChange = {} self.shpRemove = Set([]) def update_excel_from_shp(self): info("Will now update excel from edited shapefile") info("changing:" + str(self.shpChange)) info("adding:" + str(self.shpAdd)) info("removing" + str(self.shpRemove)) self.deactivateFileWatcher() # so that we don't sync back and forth fidToId = self.update_excel_programmatically() # need to alter the ids(not fids) of the new features after, because # editing the features after they've been commited doesn't work if fidToId: self.deactivateShpConnections() self.renameIds(fidToId) self.activateShpConnections() self.reload_excel() self.activateFileWatcher() self.clear_edit_state() def updateShpLayer(self, fksToRemove): if not fksToRemove: return prompt_msg = "Attempt to synchronize between Excel and Shapefile. Shapefile has features with ids: ({}) that don't appear in the Excel. Delete those features from the shapefile? ".format( ','.join([str(fk) for fk in fksToRemove])) reply = QtGui.QMessageBox.question(iface.mainWindow(), 'Message', prompt_msg, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: layer = layer_from_name(self.shpName) feats = [f for f in layer.getFeatures()] layer.startEditing() for f in feats: if f.attribute(self.shpKeyName) in fksToRemove: layer.deleteFeature(f.id()) layer.commitChanges() else: return def update_shp_from_excel(self): excelFks = Set( get_fk_set(self.excelName, self.excelKeyName, skipFirst=self.skipLines)) shpFks = Set(get_fk_set(self.shpName, self.shpKeyName, skipFirst=0)) # TODO also special warning if shp layer is in edit mode info("Keys in excel" + str(excelFks)) info("Keys in shp" + str(shpFks)) if shpFks == excelFks: info("Excel and Shp layer have the same rows. No update necessary") return inShpButNotInExcel = shpFks - excelFks inExcelButNotInShp = excelFks - shpFks if inExcelButNotInShp: warn("There are rows in the excel file with no matching geometry {}.".format( inExcelButNotInShp)) # FIXME: if those are added later then they will be added twice.. # However, having an autoincrement id suggests features would be # added first from shp only? if inShpButNotInExcel: self.updateShpLayer(inShpButNotInExcel) def activateFileWatcher(self): self.filewatcher = QFileSystemWatcher([self.excelPath]) self.filewatcher.fileChanged.connect(self.excel_changed) def deactivateFileWatcher(self): self.filewatcher.fileChanged.disconnect(self.excel_changed) self.filewatcher.removePath(self.excelPath) def activateShpConnections(self): shpLayer = layer_from_name(self.shpName) shpLayer.committedFeaturesAdded.connect(self.added_geom) shpLayer.featuresDeleted.connect(self.removed_geom_precommit) shpLayer.committedGeometriesChanges.connect(self.changed_geom) shpLayer.editingStopped.connect(self.update_excel_from_shp) shpLayer.beforeRollBack.connect(self.clear_edit_state) def deactivateShpConnections(self): shpLayer = layer_from_name(self.shpName) shpLayer.committedFeaturesAdded.disconnect(self.added_geom) # shpLayer.featureAdded.disconnect(added_geom_precommit) shpLayer.featuresDeleted.disconnect(self.removed_geom_precommit) shpLayer.committedGeometriesChanges.disconnect(self.changed_geom) shpLayer.editingStopped.disconnect(self.update_excel_from_shp) shpLayer.beforeRollBack.disconnect(self.clear_edit_state) def initialSync(self): info("Initial Syncing excel to shp") self.update_shp_from_excel() self.activateFileWatcher() self.activateShpConnections() def __del__(self): self.deactivateFileWatcher() self.deactivateShpConnections()
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 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 TreeProjectsWidget(QTreeWidget): ############################################################################### # TreeProjectsWidget SIGNALS ############################################################################### """ runProject() closeProject(QString) addProjectToConsole(QString) removeProjectFromConsole(QString) """ ############################################################################### #Extra context menu 'all' indicate a menu for ALL LANGUAGES! EXTRA_MENUS = {'all': []} images = { 'py': resources.IMAGES['tree-python'], 'java': resources.IMAGES['tree-java'], 'fn': resources.IMAGES['tree-code'], 'c': resources.IMAGES['tree-code'], 'cs': resources.IMAGES['tree-code'], 'jpg': resources.IMAGES['tree-image'], 'png': resources.IMAGES['tree-image'], 'html': resources.IMAGES['tree-html'], 'css': resources.IMAGES['tree-css'], 'ui': resources.IMAGES['designer']} def __init__(self): QTreeWidget.__init__(self) self.header().setHidden(True) self.setSelectionMode(QTreeWidget.SingleSelection) self.setAnimated(True) self._actualProject = None self._projects = {} self.__enableCloseNotification = True self._fileWatcher = QFileSystemWatcher() self.header().setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.header().setResizeMode(0, QHeaderView.ResizeToContents) self.header().setStretchLastSection(False) self.setContextMenuPolicy(Qt.CustomContextMenu) self.connect(self, SIGNAL( "customContextMenuRequested(const QPoint &)"), self._menu_context_tree) self.connect(self, SIGNAL("itemClicked(QTreeWidgetItem *, int)"), self._open_file) self.connect(self._fileWatcher, SIGNAL("directoryChanged(QString)"), self._refresh_project_by_path) def add_extra_menu(self, menu, lang='all'): ''' Add an extra menu for the given language @lang: string with the form 'py', 'php', 'json', etc ''' #remove blanks and replace dots Example(.py => py) lang = lang.strip().replace('.', '') self.EXTRA_MENUS.setdefault(lang, []) self.EXTRA_MENUS[lang].append(menu) def _menu_context_tree(self, point): index = self.indexAt(point) if not index.isValid(): return item = self.itemAt(point) handler = None menu = QMenu(self) if item.isFolder or item.parent() is None: action_add_file = menu.addAction(QIcon(resources.IMAGES['new']), self.tr("Add New File")) self.connect(action_add_file, SIGNAL("triggered()"), self._add_new_file) action_add_folder = menu.addAction(QIcon( resources.IMAGES['openProj']), self.tr("Add New Folder")) self.connect(action_add_folder, SIGNAL("triggered()"), self._add_new_folder) action_create_init = menu.addAction( self.tr("Create '__init__' Complete")) self.connect(action_create_init, SIGNAL("triggered()"), self._create_init) if item.isFolder and (item.parent() != None): action_remove_folder = menu.addAction(self.tr("Remove Folder")) self.connect(action_remove_folder, SIGNAL("triggered()"), self._delete_folder) elif not item.isFolder: action_rename_file = menu.addAction(self.tr("Rename File")) action_move_file = menu.addAction(self.tr("Move File")) action_copy_file = menu.addAction(self.tr("Copy File")) action_remove_file = menu.addAction( self.style().standardIcon(QStyle.SP_DialogCloseButton), self.tr("Delete File")) self.connect(action_remove_file, SIGNAL("triggered()"), self._delete_file) self.connect(action_rename_file, SIGNAL("triggered()"), self._rename_file) self.connect(action_copy_file, SIGNAL("triggered()"), self._copy_file) self.connect(action_move_file, SIGNAL("triggered()"), self._move_file) #menu per file language! for m in self.EXTRA_MENUS.get(item.lang(), ()): menu.addSeparator() menu.addMenu(m) if item.parent() is None: menu.addSeparator() actionRunProject = menu.addAction(QIcon( resources.IMAGES['play']), self.tr("Run Project")) self.connect(actionRunProject, SIGNAL("triggered()"), SIGNAL("runProject()")) actionMainProject = menu.addAction(self.tr("Set as Main Project")) self.connect(actionMainProject, SIGNAL("triggered()"), lambda: self.set_default_project(item)) if item.addedToConsole: actionRemoveFromConsole = menu.addAction( self.tr("Remove this Project from the Python Console")) self.connect(actionRemoveFromConsole, SIGNAL("triggered()"), self._remove_project_from_console) else: actionAdd2Console = menu.addAction( self.tr("Add this Project to the Python Console")) self.connect(actionAdd2Console, SIGNAL("triggered()"), self._add_project_to_console) actionProperties = menu.addAction(QIcon(resources.IMAGES['pref']), self.tr("Project Properties")) self.connect(actionProperties, SIGNAL("triggered()"), self.open_project_properties) #get the extra context menu for this projectType handler = settings.get_project_type_handler(item.projectType) # if handler: # for m in handler.get_context_menus(): # menu.addSeparator() # menu.addMenu(m) menu.addSeparator() action_refresh = menu.addAction( self.style().standardIcon(QStyle.SP_BrowserReload), self.tr("Refresh Project")) self.connect(action_refresh, SIGNAL("triggered()"), self._refresh_project) action_close = menu.addAction( self.style().standardIcon(QStyle.SP_DialogCloseButton), self.tr("Close Project")) self.connect(action_close, SIGNAL("triggered()"), self._close_project) #menu for all items! for m in self.EXTRA_MENUS.get('all', ()): menu.addSeparator() menu.addMenu(m) #menu for the Project Type(if present) if handler: for m in handler.get_context_menus(): menu.addSeparator() menu.addMenu(m) #show the menu! menu.exec_(QCursor.pos()) def _add_project_to_console(self): item = self.currentItem() if isinstance(item, ProjectTree): self.emit(SIGNAL("addProjectToConsole(QString)"), item.path) item.addedToConsole = True def _remove_project_from_console(self): item = self.currentItem() if isinstance(item, ProjectTree): self.emit(SIGNAL("removeProjectFromConsole(QString)"), item.path) item.addedToConsole = False def _open_file(self, item, column): if item.childCount() == 0 and not item.isFolder: fileName = os.path.join(item.path, unicode(item.text(column))) main_container.MainContainer().open_file(fileName) def _get_project_root(self): item = self.currentItem() while item is not None and item.parent() is not None: item = item.parent() return item def set_default_project(self, item): item.setForeground(0, QBrush(QColor(0, 204, 82))) if self._actualProject: self._actualProject.setForeground(0, QBrush(Qt.darkGray)) self._actualProject = item def open_project_properties(self): item = self._get_project_root() proj = project_properties_widget.ProjectProperties(item, self) proj.show() def _refresh_project_by_path(self, project_folder): project_folder = unicode(project_folder) project = [path for path in self._projects if \ file_manager.belongs_to_folder(path, project_folder)] if project: item = self._projects[unicode(project[0])] self._refresh_project(item) def _refresh_project(self, item=None): if item is None: item = self.currentItem() item.takeChildren() parentItem = self._get_project_root() if parentItem is None: return if item.parent() is None: path = item.path else: path = file_manager.create_path(item.path, unicode(item.text(0))) if parentItem.extensions != settings.SUPPORTED_EXTENSIONS: folderStructure = file_manager.open_project_with_extensions( path, parentItem.extensions) else: folderStructure = file_manager.open_project(path) if folderStructure[path][1] is not None: folderStructure[path][1].sort() else: return self._load_folder(folderStructure, path, item) item.setExpanded(True) def _close_project(self): item = self.currentItem() index = self.indexOfTopLevelItem(item) pathKey = item.path if self.__enableCloseNotification: self.emit(SIGNAL("closeProject(QString)"), pathKey) self._fileWatcher.removePath(pathKey) self.takeTopLevelItem(index) self._projects.pop(pathKey) item = self.currentItem() if item: self.set_default_project(item) def _create_init(self): item = self.currentItem() if item.parent() is None: pathFolder = item.path else: pathFolder = os.path.join(item.path, str(item.text(0))) try: file_manager.create_init_file_complete(pathFolder) except file_manager.NinjaFileExistsException, ex: QMessageBox.information(self, self.tr("Create INIT fail"), ex.message) self._refresh_project(item)
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 TreeProjectsWidget(QTreeWidget): ############################################################################### # TreeProjectsWidget SIGNALS ############################################################################### """ runProject() closeProject(QString) closeFilesFromProjectClosed(QString) addProjectToConsole(QString) removeProjectFromConsole(QString) """ ############################################################################### #Extra context menu 'all' indicate a menu for ALL LANGUAGES! EXTRA_MENUS = {'all': []} images = { 'py': resources.IMAGES['tree-python'], 'java': resources.IMAGES['tree-java'], 'fn': resources.IMAGES['tree-code'], 'c': resources.IMAGES['tree-code'], 'cs': resources.IMAGES['tree-code'], 'jpg': resources.IMAGES['tree-image'], 'png': resources.IMAGES['tree-image'], 'html': resources.IMAGES['tree-html'], 'css': resources.IMAGES['tree-css'], 'ui': resources.IMAGES['designer']} def __init__(self): QTreeWidget.__init__(self) self.header().setHidden(True) self.setSelectionMode(QTreeWidget.SingleSelection) self.setAnimated(True) self._actualProject = None #self._projects -> key: [Item, folderStructure] self._projects = {} self.__enableCloseNotification = True self._fileWatcher = QFileSystemWatcher() self.header().setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.header().setResizeMode(0, QHeaderView.ResizeToContents) self.header().setStretchLastSection(False) self.setContextMenuPolicy(Qt.CustomContextMenu) self.connect(self, SIGNAL( "customContextMenuRequested(const QPoint &)"), self._menu_context_tree) self.connect(self, SIGNAL("itemClicked(QTreeWidgetItem *, int)"), self._open_file) self.connect(self._fileWatcher, SIGNAL("directoryChanged(QString)"), self._refresh_project_by_path) def add_extra_menu(self, menu, lang='all'): ''' Add an extra menu for the given language @lang: string with the form 'py', 'php', 'json', etc ''' #remove blanks and replace dots Example(.py => py) lang = lang.strip().replace('.', '') self.EXTRA_MENUS.setdefault(lang, []) self.EXTRA_MENUS[lang].append(menu) def _menu_context_tree(self, point): index = self.indexAt(point) if not index.isValid(): return item = self.itemAt(point) handler = None menu = QMenu(self) if item.isFolder or item.parent() is None: action_add_file = menu.addAction(QIcon(resources.IMAGES['new']), self.tr("Add New File")) self.connect(action_add_file, SIGNAL("triggered()"), self._add_new_file) action_add_folder = menu.addAction(QIcon( resources.IMAGES['openProj']), self.tr("Add New Folder")) self.connect(action_add_folder, SIGNAL("triggered()"), self._add_new_folder) action_create_init = menu.addAction( self.tr("Create '__init__' Complete")) self.connect(action_create_init, SIGNAL("triggered()"), self._create_init) if item.isFolder and (item.parent() != None): action_remove_folder = menu.addAction(self.tr("Remove Folder")) self.connect(action_remove_folder, SIGNAL("triggered()"), self._delete_folder) elif not item.isFolder: action_rename_file = menu.addAction(self.tr("Rename File")) action_move_file = menu.addAction(self.tr("Move File")) action_copy_file = menu.addAction(self.tr("Copy File")) action_remove_file = menu.addAction( self.style().standardIcon(QStyle.SP_DialogCloseButton), self.tr("Delete File")) self.connect(action_remove_file, SIGNAL("triggered()"), self._delete_file) self.connect(action_rename_file, SIGNAL("triggered()"), self._rename_file) self.connect(action_copy_file, SIGNAL("triggered()"), self._copy_file) self.connect(action_move_file, SIGNAL("triggered()"), self._move_file) #Allow to edit Qt UI files with the appropiate program if item.lang() == 'ui': action_edit_ui_file = menu.addAction(self.tr("Edit UI File")) self.connect(action_edit_ui_file, SIGNAL("triggered()"), self._edit_ui_file) #menu per file language! for m in self.EXTRA_MENUS.get(item.lang(), ()): menu.addSeparator() menu.addMenu(m) if item.parent() is None: menu.addSeparator() actionRunProject = menu.addAction(QIcon( resources.IMAGES['play']), self.tr("Run Project")) self.connect(actionRunProject, SIGNAL("triggered()"), SIGNAL("runProject()")) actionMainProject = menu.addAction(self.tr("Set as Main Project")) self.connect(actionMainProject, SIGNAL("triggered()"), lambda: self.set_default_project(item)) if item.addedToConsole: actionRemoveFromConsole = menu.addAction( self.tr("Remove this Project from the Python Console")) self.connect(actionRemoveFromConsole, SIGNAL("triggered()"), self._remove_project_from_console) else: actionAdd2Console = menu.addAction( self.tr("Add this Project to the Python Console")) self.connect(actionAdd2Console, SIGNAL("triggered()"), self._add_project_to_console) actionProperties = menu.addAction(QIcon(resources.IMAGES['pref']), self.tr("Project Properties")) self.connect(actionProperties, SIGNAL("triggered()"), self.open_project_properties) #get the extra context menu for this projectType handler = settings.get_project_type_handler(item.projectType) menu.addSeparator() action_refresh = menu.addAction( self.style().standardIcon(QStyle.SP_BrowserReload), self.tr("Refresh Project")) self.connect(action_refresh, SIGNAL("triggered()"), self._refresh_project) action_close = menu.addAction( self.style().standardIcon(QStyle.SP_DialogCloseButton), self.tr("Close Project")) self.connect(action_close, SIGNAL("triggered()"), self._close_project) #menu for all items! for m in self.EXTRA_MENUS.get('all', ()): menu.addSeparator() menu.addMenu(m) #menu for the Project Type(if present) if handler: for m in handler.get_context_menus(): menu.addSeparator() menu.addMenu(m) #show the menu! menu.exec_(QCursor.pos()) def _add_project_to_console(self): item = self.currentItem() if isinstance(item, ProjectTree): self.emit(SIGNAL("addProjectToConsole(QString)"), item.path) item.addedToConsole = True def _remove_project_from_console(self): item = self.currentItem() if isinstance(item, ProjectTree): self.emit(SIGNAL("removeProjectFromConsole(QString)"), item.path) item.addedToConsole = False def _open_file(self, item, column): if item.childCount() == 0 and not item.isFolder: fileName = os.path.join(item.path, unicode(item.text(column))) main_container.MainContainer().open_file(fileName) def _get_project_root(self, item=None): if item is None: item = self.currentItem() while item is not None and item.parent() is not None: item = item.parent() return item def set_default_project(self, item): item.setForeground(0, QBrush(QColor(0, 204, 82))) if self._actualProject: self._actualProject.setForeground(0, QBrush(Qt.darkGray)) self._actualProject = item def open_project_properties(self): item = self._get_project_root() proj = project_properties_widget.ProjectProperties(item, self) proj.show() def _refresh_project_by_path(self, folder): item = self.get_item_for_path(unicode(folder)) self._refresh_project(item) def _refresh_project(self, item=None): if item is None: item = self.currentItem() parentItem = self._get_project_root(item) if parentItem is None: return if item.parent() is None: path = item.path else: path = file_manager.create_path(item.path, unicode(item.text(0))) if parentItem.extensions != settings.SUPPORTED_EXTENSIONS: folderStructure = file_manager.open_project_with_extensions( path, parentItem.extensions) else: folderStructure = file_manager.open_project(path) if folderStructure.get(path, [None, None])[1] is not None: folderStructure[path][1].sort() else: return item.takeChildren() self._load_folder(folderStructure, path, item) item.setExpanded(True) def _close_project(self): item = self.currentItem() index = self.indexOfTopLevelItem(item) pathKey = item.path for directory in self._fileWatcher.directories(): directory = unicode(directory) if file_manager.belongs_to_folder(pathKey, directory): self._fileWatcher.removePath(directory) self._fileWatcher.removePath(pathKey) self.takeTopLevelItem(index) self._projects.pop(pathKey) if self.__enableCloseNotification: self.emit(SIGNAL("closeProject(QString)"), pathKey) self.emit(SIGNAL("closeFilesFromProjectClosed(QString)"), pathKey) item = self.currentItem() if item: self.set_default_project(item) def _create_init(self): item = self.currentItem() if item.parent() is None: pathFolder = item.path else: pathFolder = os.path.join(item.path, str(item.text(0))) try: file_manager.create_init_file_complete(pathFolder) except file_manager.NinjaFileExistsException, ex: QMessageBox.information(self, self.tr("Create INIT fail"), ex.message) self._refresh_project(item)
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 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 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)
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)