listener = create_listener() except socket.error: # Good singleinstance is correct (on UNIX) otherinstance = True else: # On windows only singleinstance can be trusted otherinstance = True if iswindows else False if not otherinstance and not opts.shutdown_running_calibre: return run_gui(opts, args, listener, app, gui_debug=gui_debug) communicate(opts, args) return 0 if __name__ == '__main__': try: sys.exit(main()) except Exception as err: if not iswindows: raise tb = traceback.format_exc() from PyQt5.Qt import QErrorMessage logfile = os.path.join(os.path.expanduser('~'), 'calibre.log') if os.path.exists(logfile): log = open(logfile).read().decode('utf-8', 'ignore') d = QErrorMessage() d.showMessage( ('<b>Error:</b>%s<br><b>Traceback:</b><br>' '%s<b>Log:</b><br>%s') % (unicode(err), unicode(tb).replace( '\n', '<br>'), log.replace('\n', '<br>')))
listener = create_listener() except socket.error: # Good singleinstance is correct (on UNIX) otherinstance = True else: # On windows only singleinstance can be trusted otherinstance = True if iswindows else False if not otherinstance and not opts.shutdown_running_calibre: return run_gui(opts, args, listener, app, gui_debug=gui_debug) communicate(opts, args) return 0 if __name__ == '__main__': try: sys.exit(main()) except Exception as err: if not iswindows: raise tb = traceback.format_exc() from PyQt5.Qt import QErrorMessage logfile = os.path.join(os.path.expanduser('~'), 'calibre.log') if os.path.exists(logfile): log = open(logfile).read().decode('utf-8', 'ignore') d = QErrorMessage() d.showMessage(('<b>Error:</b>%s<br><b>Traceback:</b><br>' '%s<b>Log:</b><br>%s')%(unicode(err), unicode(tb).replace('\n', '<br>'), log.replace('\n', '<br>')))
def onSave(self): """导出保存""" ccompany = self.lineEdit.text().strip() cman = self.lineEdit_2.text().strip() cphone = self.lineEdit_3.text().replace('-', '').strip() caddress = self.lineEdit_4.text().strip() csigndate = self.lineEdit_6.text() cgive = self.lineEdit_7.text() cpaydate = self.lineEdit_11.text() myname = self.lineEdit_8.text().strip() myphone = self.lineEdit_9.text().replace('-', '') myaddres = self.lineEdit_10.text().strip() cpaymethod = self.comboBox.currentText() ctotalprice = self.lineEdit_5.text() cmaterials = "" for i in self.materiallist: cmaterials += "{}x{}x{};".format(i[0], i[1], i[2]) cmaterials = cmaterials[:-1] print(cmaterials) if ccompany and cman and cphone and caddress and csigndate and cgive and cpaydate and myname and myphone and myaddres and cpaymethod and ctotalprice and cmaterials: cid = "H{}{}".format(csigndate.replace('-', ''), getToday(csigndate)) print(cid) dd = [cid, cmaterials, ctotalprice, cpaymethod, ccompany, cman, cphone, caddress, csigndate, cgive, myname, myphone, myaddres, cpaydate] try: sc = SaveContract(dd) sc.saveContract() # 制作合同 f = open("resource/static/contractmodel.html", 'r', encoding="utf-8") m = f.read() f.close() temp = "" cmaterials = cmaterials.split("x")[0] l = [cid, cmaterials, ctotalprice, cpaymethod, ccompany, cman, cphone, caddress, csigndate, cgive, cpaydate] n = 0 for i in m: if i == "{": temp += "{}".format(l[n]) n += 1 else: temp += i with open('resource/static/contractres.html', 'w', encoding='utf-8') as f: f.write(temp) print("制作完成") # 初始化 self.materiallist = list() self.showTable() self.lineEdit.clear() self.lineEdit_2.clear() self.lineEdit_3.clear() self.lineEdit_4.clear() self.lineEdit_5.clear() self.lineEdit_6.clear() self.lineEdit_7.clear() self.lineEdit_8.clear() self.lineEdit_9.clear() self.lineEdit_10.clear() self.lineEdit_11.clear() self.previewpane = PreviewPane("resource/static/contractres.html") self.previewpane.show() except Exception as ret: print(ret) else: mes = QErrorMessage(self) mes.setWindowTitle("提示") mes.showMessage("请填写完整信息!") mes.show()
def checkText(self): if not self.textLine.text().isalpha(): message = QErrorMessage(self) message.showMessage('Invalid Entry! Try Again.') message.setWindowTitle('Warning') message.setWindowIcon(QIcon('error.png')) else: location = self.textLine.text() url = weather_url + location global json json = requests.get(url).json() try: json['message'] except Exception: self.obj = ShowWeather() else: message = QErrorMessage(self) message.showMessage('Invalid City Name! Try Again.') message.setWindowTitle('Warning') message.setWindowIcon(QIcon('error.png'))
def make_search_para_list(self): linear_flag = self.linear_kernel_cb.checkState() poly_flag = self.poly_kernel_cb.checkState() rbf_flag = self.rbf_kernel_cb.checkState() sigmoid_flag = self.sigmoid_kernel_cb.checkState() if not linear_flag and not poly_flag and not rbf_flag and not sigmoid_flag: QErrorMessage.qtHandler() qErrnoWarning("you didn't choose a kernel") return True if self.c_group.isEnabled(): c_start = self.c_start_sp.value() c_end = self.c_end_sp.value() c_num = self.c_num_sp.value() c_list = np.logspace(start=c_start, stop=c_end, num=c_num, base=10) if self.gamma_group.isEnabled(): gamma_start = self.gamma_start_sp.value() gamma_end = self.gamma_end_sp.value() gamma_num = self.gamma_num_sp.value() gamma_list = np.logspace(start=gamma_start, stop=gamma_end, num=gamma_num, base=10) if self.degree_group.isEnabled(): degree_start = self.degree_start_sp.value() degree_end = self.degree_end_sp.value() degree_num = self.degree_num_sp.value() if degree_num > degree_end - degree_start + 1: degree_num = degree_end - degree_start + 1 if degree_num == 0: degree_list = None else: degree_list = np.linspace(start=degree_start, stop=degree_end, num=degree_num) if self.coef_group.isEnabled(): coef_start = self.coef_start_sp.value() coef_end = self.coef_end_sp.value() coef_num = self.coef_num_sp.value() coef_list = np.logspace(start=coef_start, stop=coef_end, num=coef_num, base=10) grid_list = [] if linear_flag: linear_grid = {'kernel': ['linear'], 'C': c_list} grid_list.append(linear_grid) if poly_flag: if degree_list is None: poly_grid = { 'kernel': ['poly'], 'gamma': gamma_list, 'C': c_list } else: poly_grid = { 'kernel': ['poly'], 'gamma': gamma_list, 'C': c_list, "degree": degree_list } grid_list.append(poly_grid) if rbf_flag: rbf_grid = {'kernel': ['rbf'], 'gamma': gamma_list, 'C': c_list} grid_list.append(rbf_grid) if sigmoid_flag: sigmoid_grid = { 'kernel': ['sigmoid'], 'gamma': gamma_list, 'C': c_list } grid_list.append(sigmoid_grid) return grid_list
except socket.error: # Good singleinstance is correct (on UNIX) otherinstance = True else: # On windows only singleinstance can be trusted otherinstance = True if iswindows else False if not otherinstance and not opts.shutdown_running_calibre: return run_gui(opts, args, listener, app, gui_debug=gui_debug) communicate(opts, args) return 0 if __name__ == "__main__": try: sys.exit(main()) except Exception as err: if not iswindows: raise tb = traceback.format_exc() from PyQt5.Qt import QErrorMessage logfile = os.path.join(os.path.expanduser("~"), "calibre.log") if os.path.exists(logfile): log = open(logfile).read().decode("utf-8", "ignore") d = QErrorMessage() d.showMessage( ("<b>Error:</b>%s<br><b>Traceback:</b><br>" "%s<b>Log:</b><br>%s") % (unicode(err), unicode(tb).replace("\n", "<br>"), log.replace("\n", "<br>")) )
def __init__(self, parent, modal=True, flags=Qt.WindowFlags()): QDialog.__init__(self, parent, flags) self.model = None self.setModal(modal) self.setWindowTitle("Restore model into image") lo = QVBoxLayout(self) lo.setContentsMargins(10, 10, 10, 10) lo.setSpacing(5) # file selector self.wfile_in = FileSelector(self, label="Input FITS file:", dialog_label="Input FITS file", default_suffix="fits", file_types="FITS files (*.fits *.FITS)", file_mode=QFileDialog.ExistingFile) lo.addWidget(self.wfile_in) self.wfile_out = FileSelector(self, label="Output FITS file:", dialog_label="Output FITS file", default_suffix="fits", file_types="FITS files (*.fits *.FITS)", file_mode=QFileDialog.AnyFile) lo.addWidget(self.wfile_out) # beam size lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) lo1.addWidget(QLabel("Restoring beam FWHM, major axis:", self)) self.wbmaj = QLineEdit(self) lo1.addWidget(self.wbmaj) lo1.addWidget(QLabel("\" minor axis:", self)) self.wbmin = QLineEdit(self) lo1.addWidget(self.wbmin) lo1.addWidget(QLabel("\" P.A.:", self)) self.wbpa = QLineEdit(self) lo1.addWidget(self.wbpa) lo1.addWidget(QLabel("\u00B0", self)) for w in self.wbmaj, self.wbmin, self.wbpa: w.setValidator(QDoubleValidator(self)) lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) self.wfile_psf = FileSelector( self, label="Set restoring beam by fitting PSF image:", dialog_label="PSF FITS file", default_suffix="fits", file_types="FITS files (*.fits *.FITS)", file_mode=QFileDialog.ExistingFile) lo1.addSpacing(32) lo1.addWidget(self.wfile_psf) # selection only self.wselonly = QCheckBox("restore selected model sources only", self) lo.addWidget(self.wselonly) # OK/cancel buttons lo.addSpacing(10) lo2 = QHBoxLayout() lo.addLayout(lo2) lo2.setContentsMargins(0, 0, 0, 0) lo2.setContentsMargins(5, 5, 5, 5) self.wokbtn = QPushButton("OK", self) self.wokbtn.setMinimumWidth(128) self.wokbtn.clicked.connect(self.accept) self.wokbtn.setEnabled(False) cancelbtn = QPushButton("Cancel", self) cancelbtn.setMinimumWidth(128) cancelbtn.clicked.connect(self.reject) lo2.addWidget(self.wokbtn) lo2.addStretch(1) lo2.addWidget(cancelbtn) self.setMinimumWidth(384) # signals self.wfile_in.filenameSelected.connect(self._fileSelected) self.wfile_in.filenameSelected.connect(self._inputFileSelected) self.wfile_out.filenameSelected.connect(self._fileSelected) self.wfile_psf.filenameSelected.connect(self._psfFileSelected) # internal state self.qerrmsg = QErrorMessage(self)
class RestoreImageDialog(QDialog): def __init__(self, parent, modal=True, flags=Qt.WindowFlags()): QDialog.__init__(self, parent, flags) self.model = None self.setModal(modal) self.setWindowTitle("Restore model into image") lo = QVBoxLayout(self) lo.setContentsMargins(10, 10, 10, 10) lo.setSpacing(5) # file selector self.wfile_in = FileSelector(self, label="Input FITS file:", dialog_label="Input FITS file", default_suffix="fits", file_types="FITS files (*.fits *.FITS)", file_mode=QFileDialog.ExistingFile) lo.addWidget(self.wfile_in) self.wfile_out = FileSelector(self, label="Output FITS file:", dialog_label="Output FITS file", default_suffix="fits", file_types="FITS files (*.fits *.FITS)", file_mode=QFileDialog.AnyFile) lo.addWidget(self.wfile_out) # beam size lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) lo1.addWidget(QLabel("Restoring beam FWHM, major axis:", self)) self.wbmaj = QLineEdit(self) lo1.addWidget(self.wbmaj) lo1.addWidget(QLabel("\" minor axis:", self)) self.wbmin = QLineEdit(self) lo1.addWidget(self.wbmin) lo1.addWidget(QLabel("\" P.A.:", self)) self.wbpa = QLineEdit(self) lo1.addWidget(self.wbpa) lo1.addWidget(QLabel("\u00B0", self)) for w in self.wbmaj, self.wbmin, self.wbpa: w.setValidator(QDoubleValidator(self)) lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) self.wfile_psf = FileSelector( self, label="Set restoring beam by fitting PSF image:", dialog_label="PSF FITS file", default_suffix="fits", file_types="FITS files (*.fits *.FITS)", file_mode=QFileDialog.ExistingFile) lo1.addSpacing(32) lo1.addWidget(self.wfile_psf) # selection only self.wselonly = QCheckBox("restore selected model sources only", self) lo.addWidget(self.wselonly) # OK/cancel buttons lo.addSpacing(10) lo2 = QHBoxLayout() lo.addLayout(lo2) lo2.setContentsMargins(0, 0, 0, 0) lo2.setContentsMargins(5, 5, 5, 5) self.wokbtn = QPushButton("OK", self) self.wokbtn.setMinimumWidth(128) self.wokbtn.clicked.connect(self.accept) self.wokbtn.setEnabled(False) cancelbtn = QPushButton("Cancel", self) cancelbtn.setMinimumWidth(128) cancelbtn.clicked.connect(self.reject) lo2.addWidget(self.wokbtn) lo2.addStretch(1) lo2.addWidget(cancelbtn) self.setMinimumWidth(384) # signals self.wfile_in.filenameSelected.connect(self._fileSelected) self.wfile_in.filenameSelected.connect(self._inputFileSelected) self.wfile_out.filenameSelected.connect(self._fileSelected) self.wfile_psf.filenameSelected.connect(self._psfFileSelected) # internal state self.qerrmsg = QErrorMessage(self) def setModel(self, model): nsel = len([src for src in model.sources if src.selected]) self.wselonly.setVisible(nsel > 0 and nsel < len(model.sources)) self.model = model self._fileSelected(None) def _fileSelected(self, filename): self.wokbtn.setEnabled( bool(self.wfile_in.filename() and self.wfile_out.filename())) def _inputFileSelected(self, filename): if filename: try: header = pyfits.open(filename)[0].header except Exception as err: self.qerrmsg.showMessage("Error reading FITS file %s: %s" % (filename, str(err))) self.wfile_in.setFilename("") return # try to get beam extents gx, gy, grot = [ header.get(x, None) for x in ('BMAJ', 'BMIN', 'BPA') ] if all([x is not None for x in (gx, gy, grot)]): # if beam size is already set, ask before overwriting print([ str(x.text()) for x in (self.wbmaj, self.wbmin, self.wbpa) ]) if any([bool(str(x.text())) for x in (self.wbmaj, self.wbmin, self.wbpa)]) and \ QMessageBox.question(self, "Set restoring beam", "Also reset restoring beam size from this FITS file?", QMessageBox.Yes | QMessageBox.No) != QMessageBox.Yes: return self.wbmaj.setText("%.2f" % (gx * 3600)) self.wbmin.setText("%.2f" % (gy * 3600)) self.wbpa.setText("%.2f" % grot) def _psfFileSelected(self, filename): busy = BusyIndicator() filename = str(filename) self.parent().showMessage("Fitting gaussian to PSF file %s" % filename) try: bmaj, bmin, pa = [x / DEG for x in Imaging.fitPsf(filename)] except Exception as err: busy.reset_cursor() self.qerrmsg.showMessage("Error fitting PSF file %s: %s" % (filename, str(err))) return bmaj *= 3600 * Imaging.FWHM bmin *= 3600 * Imaging.FWHM self.wbmaj.setText(str(bmaj)) self.wbmin.setText(str(bmin)) self.wbpa.setText(str(pa)) busy.reset_cursor() def accept(self): """Tries to restore the image, and closes the dialog if successful.""" # get list of sources to restore sources = self.model.sources sel_sources = [src for src in sources if src.selected] if len(sel_sources) > 0 and len(sel_sources) < len( sources) and self.wselonly.isChecked(): sources = sel_sources if not sources: self.qerrmsg.showMessage("No sources to restore.") return busy = BusyIndicator() # get filenames infile = self.wfile_in.filename() outfile = self.wfile_out.filename() self.parent().showMessage( "Restoring %d model sources to image %s, writing to %s" % (len(sources), infile, outfile)) # read fits file try: input_hdu = pyfits.open(infile)[0] except Exception as err: busy.reset_cursor() self.qerrmsg.showMessage("Error reading FITS file %s: %s" % (infile, str(err))) return # get beam sizes try: bmaj = float(str(self.wbmaj.text())) bmin = float(str(self.wbmin.text())) pa = float(str(self.wbpa.text()) or "0") except Exception as err: busy.reset_cursor() self.qerrmsg.showMessage("Invalid beam size specified") return bmaj = bmaj / (Imaging.FWHM * 3600) * DEG bmin = bmin / (Imaging.FWHM * 3600) * DEG pa = pa * DEG # restore try: Imaging.restoreSources(input_hdu, sources, bmaj, bmin, pa) except Exception as err: busy.reset_cursor() self.qerrmsg.showMessage("Error restoring model into image: %s" % str(err)) return # save fits file try: input_hdu.writeto(outfile, overwrite=True) except Exception as err: busy.reset_cursor() self.qerrmsg.showMessage("Error writing FITS file %s: %s" % (outfile, str(err))) return self.parent().loadImage(outfile) busy.reset_cursor() return QDialog.accept(self)
def __init__(self, parent, modal=True, flags=Qt.WindowFlags()): QDialog.__init__(self, parent, flags) self.model = None self._model_dir = None self.setModal(modal) self.setWindowTitle("Convert sources to FITS brick") lo = QVBoxLayout(self) lo.setContentsMargins(10, 10, 10, 10) lo.setSpacing(5) # file selector self.wfile = FileSelector(self, label="FITS filename:", dialog_label="Output FITS file", default_suffix="fits", file_types="FITS files (*.fits *.FITS)", file_mode=QFileDialog.ExistingFile) lo.addWidget(self.wfile) # reference frequency lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) label = QLabel("Frequency, MHz:", self) lo1.addWidget(label) tip = """<P>If your sky model contains spectral information (such as spectral indices), then a brick may be generated for a specific frequency. If a frequency is not specified here, the reference frequency of the model sources will be assumed.</P>""" self.wfreq = QLineEdit(self) self.wfreq.setValidator(QDoubleValidator(self)) label.setToolTip(tip) self.wfreq.setToolTip(tip) lo1.addWidget(self.wfreq) # beam gain lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) self.wpb_apply = QCheckBox("Apply primary beam expression:", self) self.wpb_apply.setChecked(True) lo1.addWidget(self.wpb_apply) tip = """<P>If this option is specified, a primary power beam gain will be applied to the sources before inserting them into the brick. This can be any valid Python expression making use of the variables 'r' (corresponding to distance from field centre, in radians) and 'fq' (corresponding to frequency.)</P>""" self.wpb_exp = QLineEdit(self) self.wpb_apply.setToolTip(tip) self.wpb_exp.setToolTip(tip) lo1.addWidget(self.wpb_exp) # overwrite or add mode lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) self.woverwrite = QRadioButton("overwrite image", self) self.woverwrite.setChecked(True) lo1.addWidget(self.woverwrite) self.waddinto = QRadioButton("add into image", self) lo1.addWidget(self.waddinto) # add to model self.wadd = QCheckBox( "Add resulting brick to sky model as a FITS image component", self) lo.addWidget(self.wadd) lo1 = QHBoxLayout() lo.addLayout(lo1) lo1.setContentsMargins(0, 0, 0, 0) self.wpad = QLineEdit(self) self.wpad.setValidator(QDoubleValidator(self)) self.wpad.setText("1.1") lab = QLabel("...with padding factor:", self) lab.setToolTip( """<P>The padding factor determines the amount of null padding inserted around the image during the prediction stage. Padding alleviates the effects of tapering and detapering in the uv-brick, which can show up towards the edges of the image. For a factor of N, the image will be padded out to N times its original size. This increases memory use, so if you have no flux at the edges of the image anyway, then a pad factor of 1 is perfectly fine.</P>""") self.wpad.setToolTip(lab.toolTip()) self.wadd.toggled[bool].connect(self.wpad.setEnabled) self.wadd.toggled[bool].connect(lab.setEnabled) self.wpad.setEnabled(False) lab.setEnabled(False) lo1.addStretch(1) lo1.addWidget(lab, 0) lo1.addWidget(self.wpad, 1) self.wdel = QCheckBox( "Remove from the sky model sources that go into the brick", self) lo.addWidget(self.wdel) # OK/cancel buttons lo.addSpacing(10) lo2 = QHBoxLayout() lo.addLayout(lo2) lo2.setContentsMargins(5, 5, 5, 5) self.wokbtn = QPushButton("OK", self) self.wokbtn.setMinimumWidth(128) self.wokbtn.clicked.connect(self.accept) self.wokbtn.setEnabled(False) cancelbtn = QPushButton("Cancel", self) cancelbtn.setMinimumWidth(128) cancelbtn.clicked.connect(self.reject) lo2.addWidget(self.wokbtn) lo2.addStretch(1) lo2.addWidget(cancelbtn) self.setMinimumWidth(384) # signals self.wfile.filenameSelected.connect(self._fileSelected) # internal state self.qerrmsg = QErrorMessage(self)
def __init__(self, parent, max_width=None, max_height=None, hide_on_close=False): QMainWindow.__init__(self, parent) self.signalShowMessage.connect(self.showMessage, type=Qt.QueuedConnection) self.signalShowErrorMessage.connect(self.showErrorMessage, type=Qt.QueuedConnection) self.setWindowIcon(pixmaps.tigger_starface.icon()) self._currier = PersistentCurrier() self.hide() # init column constants for icol, col in enumerate(self.ViewModelColumns): setattr(self, "Column%s" % col.capitalize(), icol) # init GUI self.setWindowTitle("Tigger") self.setWindowIcon(QIcon(pixmaps.purr_logo.pm())) # central widget setup self.cw = QWidget(self) # The actual min width of the control dialog is ~396 self._ctrl_dialog_min_size = 400 # approx value # The actual min width of the profile/zoom windows is ~256 self._profile_and_zoom_widget_min_size = 300 # approx value # set usable screen space (90% of available) self.max_width = max_width self.max_height = max_height self.setCentralWidget(self.cw) cwlo = QVBoxLayout(self.cw) cwlo.setContentsMargins(5, 5, 5, 5) # make splitter spl1 = self._splitter1 = QSplitter(Qt.Vertical, self.cw) spl1.setOpaqueResize(False) cwlo.addWidget(spl1) # Create listview of LSM entries self.tw = SkyModelTreeWidget(spl1) self.tw.hide() # split bottom pane spl2 = self._splitter2 = QSplitter(Qt.Horizontal, spl1) spl2.setOpaqueResize(False) self._skyplot_stack = QWidget(spl2) self._skyplot_stack_lo = QVBoxLayout(self._skyplot_stack) self._skyplot_stack_lo.setContentsMargins(0, 0, 0, 0) # add plot self.skyplot = SkyModelPlotter(self._skyplot_stack, self) self.skyplot.resize(128, 128) self.skyplot.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self._skyplot_stack_lo.addWidget(self.skyplot, 1000) self.skyplot.hide() self.skyplot.imagesChanged.connect(self._imagesChanged) self.skyplot.setupShowMessages(self.signalShowMessage) self.skyplot.setupShowErrorMessages(self.signalShowErrorMessage) self._grouptab_stack = QWidget(spl2) self._grouptab_stack_lo = lo = QVBoxLayout(self._grouptab_stack) self._grouptab_stack_lo.setContentsMargins(0, 0, 0, 0) # add groupings table self.grouptab = ModelGroupsTable(self._grouptab_stack) self.grouptab.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.hasSkyModel.connect(self.grouptab.setEnabled) lo.addWidget(self.grouptab, 1000) lo.addStretch(1) self.grouptab.hide() # add image controls -- parentless for now (setLayout will reparent them anyway) self.imgman = ImageManager() self.imgman.setMainWindow(self) self.imgman.setShowMessageSignal(self.signalShowMessage) self.imgman.setShowErrorMessageSignal(self.signalShowErrorMessage) self.skyplot.setImageManager(self.imgman) self.imgman.imagesChanged.connect(self._imagesChanged) # enable status line self.statusBar().show() # Create and populate main menu menubar = self.menuBar() # File menu file_menu = menubar.addMenu("&File") qa_open = file_menu.addAction("&Open model...", self._openFileCallback, Qt.CTRL + Qt.Key_O) qa_merge = file_menu.addAction("&Merge in model...", self._mergeFileCallback, Qt.CTRL + Qt.SHIFT + Qt.Key_O) self.hasSkyModel.connect(qa_merge.setEnabled) file_menu.addSeparator() qa_save = file_menu.addAction("&Save model", self.saveFile, Qt.CTRL + Qt.Key_S) self.isUpdated.connect(qa_save.setEnabled) qa_save_as = file_menu.addAction("Save model &as...", self.saveFileAs) self.hasSkyModel.connect(qa_save_as.setEnabled) qa_save_selection_as = file_menu.addAction("Save selection as...", self.saveSelectionAs) self.hasSelection.connect(qa_save_selection_as.setEnabled) file_menu.addSeparator() qa_close = file_menu.addAction("&Close model", self.closeFile, Qt.CTRL + Qt.Key_W) self.hasSkyModel.connect(qa_close.setEnabled) qa_quit = file_menu.addAction("Quit", self.close, Qt.CTRL + Qt.Key_Q) # Image menu menubar.addMenu(self.imgman.getMenu()) # Plot menu menubar.addMenu(self.skyplot.getMenu()) # LSM Menu em = QMenu("&LSM", self) self._qa_em = menubar.addMenu(em) self._qa_em.setVisible(False) self.hasSkyModel.connect(self._qa_em.setVisible) self._column_view_menu = QMenu("&Show columns", self) self._qa_cv_menu = em.addMenu(self._column_view_menu) em.addSeparator() em.addAction("Select &all", self._selectAll, Qt.CTRL + Qt.Key_A) em.addAction("U&nselect all", self._unselectAll, Qt.CTRL + Qt.Key_N) em.addAction("&Invert selection", self._selectInvert, Qt.CTRL + Qt.Key_I) em.addAction("Select b&y attribute...", self._showSourceSelector, Qt.CTRL + Qt.Key_Y) em.addSeparator() qa_add_tag = em.addAction("&Tag selection...", self.addTagToSelection, Qt.CTRL + Qt.Key_T) self.hasSelection.connect(qa_add_tag.setEnabled) qa_del_tag = em.addAction("&Untag selection...", self.removeTagsFromSelection, Qt.CTRL + Qt.Key_U) self.hasSelection.connect(qa_del_tag.setEnabled) qa_del_sel = em.addAction("&Delete selection", self._deleteSelection) self.hasSelection.connect(qa_del_sel.setEnabled) # Tools menu tm = self._tools_menu = QMenu("&Tools", self) self._qa_tm = menubar.addMenu(tm) self._qa_tm.setVisible(False) self.hasSkyModel.connect(self._qa_tm.setVisible) # Help menu menubar.addSeparator() hm = self._help_menu = menubar.addMenu("&Help") hm.addAction("&About...", self._showAboutDialog) self._about_dialog = None # message handlers self.qerrmsg = QErrorMessage(self) # set initial state self.setAcceptDrops(True) self.model = None self.filename = None self._display_filename = None self._open_file_dialog = self._merge_file_dialog = self._save_as_dialog = self._save_sel_as_dialog = self._open_image_dialog = None self.isUpdated.emit(False) self.hasSkyModel.emit(False) self.hasSelection.emit(False) self._exiting = False # set initial layout self._current_layout = None self.setLayout(self.LayoutEmpty) dprint(1, "init complete")
class MainWindow(QMainWindow): isUpdated = pyqtSignal(bool) hasSkyModel = pyqtSignal(bool) hasSelection = pyqtSignal(bool) modelChanged = pyqtSignal(object) closing = pyqtSignal() signalShowMessage = pyqtSignal([str, int], [str]) signalShowErrorMessage = pyqtSignal([str], [str, int]) ViewModelColumns = [ "name", "RA", "Dec", "type", "Iapp", "I", "Q", "U", "V", "RM", "spi", "shape" ] def __init__(self, parent, max_width=None, max_height=None, hide_on_close=False): QMainWindow.__init__(self, parent) self.signalShowMessage.connect(self.showMessage, type=Qt.QueuedConnection) self.signalShowErrorMessage.connect(self.showErrorMessage, type=Qt.QueuedConnection) self.setWindowIcon(pixmaps.tigger_starface.icon()) self._currier = PersistentCurrier() self.hide() # init column constants for icol, col in enumerate(self.ViewModelColumns): setattr(self, "Column%s" % col.capitalize(), icol) # init GUI self.setWindowTitle("Tigger") self.setWindowIcon(QIcon(pixmaps.purr_logo.pm())) # central widget setup self.cw = QWidget(self) # The actual min width of the control dialog is ~396 self._ctrl_dialog_min_size = 400 # approx value # The actual min width of the profile/zoom windows is ~256 self._profile_and_zoom_widget_min_size = 300 # approx value # set usable screen space (90% of available) self.max_width = max_width self.max_height = max_height self.setCentralWidget(self.cw) cwlo = QVBoxLayout(self.cw) cwlo.setContentsMargins(5, 5, 5, 5) # make splitter spl1 = self._splitter1 = QSplitter(Qt.Vertical, self.cw) spl1.setOpaqueResize(False) cwlo.addWidget(spl1) # Create listview of LSM entries self.tw = SkyModelTreeWidget(spl1) self.tw.hide() # split bottom pane spl2 = self._splitter2 = QSplitter(Qt.Horizontal, spl1) spl2.setOpaqueResize(False) self._skyplot_stack = QWidget(spl2) self._skyplot_stack_lo = QVBoxLayout(self._skyplot_stack) self._skyplot_stack_lo.setContentsMargins(0, 0, 0, 0) # add plot self.skyplot = SkyModelPlotter(self._skyplot_stack, self) self.skyplot.resize(128, 128) self.skyplot.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self._skyplot_stack_lo.addWidget(self.skyplot, 1000) self.skyplot.hide() self.skyplot.imagesChanged.connect(self._imagesChanged) self.skyplot.setupShowMessages(self.signalShowMessage) self.skyplot.setupShowErrorMessages(self.signalShowErrorMessage) self._grouptab_stack = QWidget(spl2) self._grouptab_stack_lo = lo = QVBoxLayout(self._grouptab_stack) self._grouptab_stack_lo.setContentsMargins(0, 0, 0, 0) # add groupings table self.grouptab = ModelGroupsTable(self._grouptab_stack) self.grouptab.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.hasSkyModel.connect(self.grouptab.setEnabled) lo.addWidget(self.grouptab, 1000) lo.addStretch(1) self.grouptab.hide() # add image controls -- parentless for now (setLayout will reparent them anyway) self.imgman = ImageManager() self.imgman.setMainWindow(self) self.imgman.setShowMessageSignal(self.signalShowMessage) self.imgman.setShowErrorMessageSignal(self.signalShowErrorMessage) self.skyplot.setImageManager(self.imgman) self.imgman.imagesChanged.connect(self._imagesChanged) # enable status line self.statusBar().show() # Create and populate main menu menubar = self.menuBar() # File menu file_menu = menubar.addMenu("&File") qa_open = file_menu.addAction("&Open model...", self._openFileCallback, Qt.CTRL + Qt.Key_O) qa_merge = file_menu.addAction("&Merge in model...", self._mergeFileCallback, Qt.CTRL + Qt.SHIFT + Qt.Key_O) self.hasSkyModel.connect(qa_merge.setEnabled) file_menu.addSeparator() qa_save = file_menu.addAction("&Save model", self.saveFile, Qt.CTRL + Qt.Key_S) self.isUpdated.connect(qa_save.setEnabled) qa_save_as = file_menu.addAction("Save model &as...", self.saveFileAs) self.hasSkyModel.connect(qa_save_as.setEnabled) qa_save_selection_as = file_menu.addAction("Save selection as...", self.saveSelectionAs) self.hasSelection.connect(qa_save_selection_as.setEnabled) file_menu.addSeparator() qa_close = file_menu.addAction("&Close model", self.closeFile, Qt.CTRL + Qt.Key_W) self.hasSkyModel.connect(qa_close.setEnabled) qa_quit = file_menu.addAction("Quit", self.close, Qt.CTRL + Qt.Key_Q) # Image menu menubar.addMenu(self.imgman.getMenu()) # Plot menu menubar.addMenu(self.skyplot.getMenu()) # LSM Menu em = QMenu("&LSM", self) self._qa_em = menubar.addMenu(em) self._qa_em.setVisible(False) self.hasSkyModel.connect(self._qa_em.setVisible) self._column_view_menu = QMenu("&Show columns", self) self._qa_cv_menu = em.addMenu(self._column_view_menu) em.addSeparator() em.addAction("Select &all", self._selectAll, Qt.CTRL + Qt.Key_A) em.addAction("U&nselect all", self._unselectAll, Qt.CTRL + Qt.Key_N) em.addAction("&Invert selection", self._selectInvert, Qt.CTRL + Qt.Key_I) em.addAction("Select b&y attribute...", self._showSourceSelector, Qt.CTRL + Qt.Key_Y) em.addSeparator() qa_add_tag = em.addAction("&Tag selection...", self.addTagToSelection, Qt.CTRL + Qt.Key_T) self.hasSelection.connect(qa_add_tag.setEnabled) qa_del_tag = em.addAction("&Untag selection...", self.removeTagsFromSelection, Qt.CTRL + Qt.Key_U) self.hasSelection.connect(qa_del_tag.setEnabled) qa_del_sel = em.addAction("&Delete selection", self._deleteSelection) self.hasSelection.connect(qa_del_sel.setEnabled) # Tools menu tm = self._tools_menu = QMenu("&Tools", self) self._qa_tm = menubar.addMenu(tm) self._qa_tm.setVisible(False) self.hasSkyModel.connect(self._qa_tm.setVisible) # Help menu menubar.addSeparator() hm = self._help_menu = menubar.addMenu("&Help") hm.addAction("&About...", self._showAboutDialog) self._about_dialog = None # message handlers self.qerrmsg = QErrorMessage(self) # set initial state self.setAcceptDrops(True) self.model = None self.filename = None self._display_filename = None self._open_file_dialog = self._merge_file_dialog = self._save_as_dialog = self._save_sel_as_dialog = self._open_image_dialog = None self.isUpdated.emit(False) self.hasSkyModel.emit(False) self.hasSelection.emit(False) self._exiting = False # set initial layout self._current_layout = None self.setLayout(self.LayoutEmpty) dprint(1, "init complete") # layout identifiers LayoutEmpty = "empty" LayoutImage = "image" LayoutImageModel = "model" LayoutSplit = "split" def _getFilenamesFromDropEvent(self, event): """Checks if drop event is valid (i.e. contains a local URL to a FITS file), and returns list of filenames contained therein.""" dprint(1, "drop event:", event.mimeData().text()) if not event.mimeData().hasUrls(): dprint(1, "drop event: no urls") return None filenames = [] for url in event.mimeData().urls(): name = str(url.toLocalFile()) dprint(2, "drop event: name is", name) if name and Images.isFITS(name): filenames.append(name) dprint(2, "drop event: filenames are", filenames) return filenames def dragEnterEvent(self, event): if self._getFilenamesFromDropEvent(event): dprint(1, "drag-enter accepted") event.acceptProposedAction() else: dprint(1, "drag-enter rejected") def dropEvent(self, event): busy = None filenames = self._getFilenamesFromDropEvent(event) dprint(1, "dropping", filenames) if filenames: event.acceptProposedAction() busy = BusyIndicator() for name in filenames: self.imgman.loadImage(name) if busy is not None: busy.reset_cursor() def saveSizes(self): if self._current_layout is not None: dprint(1, "saving sizes for layout", self._current_layout) # save main window size and splitter dimensions sz = self.size() Config.set('%s-main-window-width' % self._current_layout, sz.width()) Config.set('%s-main-window-height' % self._current_layout, sz.height()) for spl, name in ((self._splitter1, "splitter1"), (self._splitter2, "splitter2")): ssz = spl.sizes() for i, sz in enumerate(ssz): Config.set( '%s-%s-size%d' % (self._current_layout, name, i), sz) def loadSizes(self): if self._current_layout is not None: dprint(1, "loading sizes for layout", self._current_layout) # get main window size and splitter dimensions w = Config.getint('%s-main-window-width' % self._current_layout, 0) h = Config.getint('%s-main-window-height' % self._current_layout, 0) dprint(2, "window size is", w, h) if not (w and h): return None self.resize(QSize(w, h)) for spl, name in (self._splitter1, "splitter1"), (self._splitter2, "splitter2"): ssz = [ Config.getint( '%s-%s-size%d' % (self._current_layout, name, i), -1) for i in (0, 1) ] dprint(2, "splitter", name, "sizes", ssz) if all([sz >= 0 for sz in ssz]): spl.setSizes(ssz) else: return None return True def setLayout(self, layout): """Changes the current window layout. Restores sizes etc. from config file.""" if self._current_layout is layout: return dprint(1, "switching to layout", layout) # save sizes to config file self.saveSizes() # remove imgman widget from all layouts for lo in self._skyplot_stack_lo, self._grouptab_stack_lo: if lo.indexOf(self.imgman) >= 0: lo.removeWidget(self.imgman) # assign it to appropriate parent and parent's layout if layout is self.LayoutImage: lo = self._skyplot_stack_lo self.setMaximumSize(self.max_width, self.max_height) self.setBaseSize(self.max_width, self.max_height) size_policy = QSizePolicy() size_policy.setVerticalPolicy(QSizePolicy.Minimum) size_policy.setHorizontalPolicy(QSizePolicy.Expanding) self.setSizePolicy(size_policy) # set central widget size - workaround for bug #164 # self.cw.setFixedSize(self.max_width - self._ctrl_dialog_min_size - self._profile_and_zoom_widget_min_size, self.max_height) # self.cw.setGeometry(0, self.max_width - self._ctrl_dialog_min_size - self._profile_and_zoom_widget_min_size / 2, # self.max_width - self._ctrl_dialog_min_size - self._profile_and_zoom_widget_min_size, self.max_height) elif layout is self.LayoutEmpty: lo = self._skyplot_stack_lo else: lo = self._grouptab_stack_lo self.imgman.setParent(lo.parentWidget()) lo.addWidget(self.imgman, 0) # show/hide panels if layout is self.LayoutEmpty: self.tw.hide() self.grouptab.hide() # self.skyplot.show() elif layout is self.LayoutImage: self.tw.hide() self.grouptab.hide() self.skyplot.show() # setup dockable state from config file if Config.getbool('livezoom-show'): self.skyplot._livezoom.setVisible(True) self.skyplot._dockable_livezoom.setVisible(True) self.addDockWidget(Qt.LeftDockWidgetArea, self.skyplot._dockable_livezoom) if Config.getbool('liveprofile-show'): self.skyplot._liveprofile.setVisible(True) self.skyplot._dockable_liveprofile.setVisible(True) self.addDockWidget(Qt.LeftDockWidgetArea, self.skyplot._dockable_liveprofile) # resize dock areas widget_list = self.findChildren(QDockWidget) size_list = [] result = [] for widget in widget_list: if not isinstance(widget.bind_widget, ImageControlDialog): size_list.append(widget.bind_widget.width()) result.append(widget) dprint(2, f"{widget} width {widget.width()}") dprint( 2, f"{widget} bind_widget width {widget.bind_widget.width()}" ) if isinstance(widget.bind_widget, LiveImageZoom): widget.bind_widget.setMinimumWidth(widget.width()) widget_list = result # resize dock areas self.resizeDocks(widget_list, size_list, Qt.Horizontal) elif layout is self.LayoutImageModel: self.tw.show() self.grouptab.show() self.skyplot.show() # reload sizes self._current_layout = layout if not self.loadSizes(): dprint(1, "no sizes loaded, setting defaults") if layout is self.LayoutEmpty: self.resize(QSize(512, 256)) elif layout is self.LayoutImage: self.resize(QSize(512, 512)) self._splitter2.setSizes([512, 0]) elif layout is self.LayoutImageModel: self.resize(QSize(1024, 512)) self._splitter1.setSizes([256, 256]) self._splitter2.setSizes([256, 256]) def enableUpdates(self, enable=True): """Enables updates of the child widgets. Usually called after startup is completed (i.e. all data loaded)""" self.skyplot.enableUpdates(enable) if enable: if self.model: self.setLayout(self.LayoutImageModel) elif self.imgman.getImages(): self.setLayout(self.LayoutImage) else: self.setLayout(self.LayoutEmpty) self.show() def _showAboutDialog(self): if not self._about_dialog: self._about_dialog = AboutDialog.AboutDialog(self) self._about_dialog.show() def addTool(self, name, callback): """Adds a tool to the Tools menu""" self._tools_menu.addAction( name, self._currier.curry(self._callTool, callback)) def _callTool(self, callback): callback(self, self.model) def _imagesChanged(self): """Called when the set of loaded images has changed""" if self.imgman.getImages(): if self._current_layout is self.LayoutEmpty: self.setLayout(self.LayoutImage) else: if not self.model: self.setLayout(self.LayoutEmpty) def _selectAll(self): if not self.model: return busy = BusyIndicator() for src in self.model.sources: src.selected = True self.model.emitSelection(self) busy.reset_cursor() def _unselectAll(self): if not self.model: return busy = BusyIndicator() for src in self.model.sources: src.selected = False self.model.emitSelection(self) busy.reset_cursor() def _selectInvert(self): if not self.model: return busy = BusyIndicator() for src in self.model.sources: src.selected = not src.selected self.model.emitSelection(self) busy.reset_cursor() def _deleteSelection(self): unselected = [src for src in self.model.sources if not src.selected] nsel = len(self.model.sources) - len(unselected) if QMessageBox.question( self, "Delete selection", """<P>Really deleted %d selected source(s)? %d unselected sources will remain in the model.</P>""" % (nsel, len(unselected)), QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Cancel) != QMessageBox.Ok: return self.model.setSources(unselected) self.signalShowMessage[str].emit("""Deleted %d sources""" % nsel) self.model.emitUpdate(SkyModel.SkyModel.UpdateAll, origin=self) def _showSourceSelector(self): TigGUI.Tools.source_selector.show_source_selector(self, self.model) def _updateModelSelection(self, num, origin=None): """Called when the model selection has been updated.""" self.hasSelection.emit(bool(num)) import Tigger.Models.Formats _formats = [f[1] for f in Tigger.Models.Formats.listFormatsFull()] _load_file_types = [(doc, ["*" + ext for ext in extensions], load) for load, save, doc, extensions in _formats if load] _save_file_types = [(doc, ["*" + ext for ext in extensions], save) for load, save, doc, extensions in _formats if save] def showMessage(self, msg, time=3000): self.statusBar().showMessage(msg, time) def showErrorMessage(self, msg, time=3000): self.qerrmsg.showMessage(msg) def loadImage(self, filename): return self.imgman.loadImage(filename) def setModel(self, model): if model is not None: self.modelChanged.emit(model) if model: self.model = model self.hasSkyModel.emit(True) self.hasSelection.emit(False) self.isUpdated.emit(False) self.model.enableSignals() self.model.connect("updated", self._indicateModelUpdated) self.model.connect("selected", self._updateModelSelection) # pass to children self.tw.setModel(self.model) self.grouptab.setModel(self.model) self.skyplot.setModel(self.model) # add items to View menu self._column_view_menu.clear() self.tw.addColumnViewActionsTo(self._column_view_menu) else: self.model = None self.setWindowTitle("Tigger") self.hasSelection.emit(False) self.isUpdated.emit(False) self.hasSkyModel.emit(False) self.tw.clear() self.grouptab.clear() self.skyplot.setModel(None) def _openFileCallback(self): if not self._open_file_dialog: filters = ";;".join([ "%s (%s)" % (name, " ".join(patterns)) for name, patterns, func in self._load_file_types ]) dialog = self._open_file_dialog = QFileDialog( self, "Open sky model", ".", filters) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setModal(True) dialog.filesSelected['QStringList'].connect(self.openFile) self._open_file_dialog.exec_() return def _mergeFileCallback(self): if not self._merge_file_dialog: filters = ";;".join([ "%s (%s)" % (name, " ".join(patterns)) for name, patterns, func in self._load_file_types ]) dialog = self._merge_file_dialog = QFileDialog( self, "Merge in sky model", ".", filters) dialog.setFileMode(QFileDialog.ExistingFile) dialog.setModal(True) dialog.filesSelected['QStringList'].connect( self._currier.curry(self.openFile, merge=True)) self._merge_file_dialog.exec_() return def openFile(self, _filename=None, _format=None, _merge=False, _show=True): # check that we can close existing model if not _merge and not self._canCloseExistingModel(): return False if isinstance(_filename, QStringList): _filename = _filename[0] _filename = str(_filename) # try to determine the file type filetype, import_func, export_func, doc = Tigger.Models.Formats.resolveFormat( _filename, _format) if import_func is None: self.signalShowErrorMessage.emit( """Error loading model file %s: unknown file format""" % _filename) return # try to load the specified file busy = BusyIndicator() self.signalShowMessage.emit( """Reading %s file %s""" % (filetype, _filename), 3000) QApplication.flush() try: model = import_func(_filename) model.setFilename(_filename) except: busy.reset_cursor() self.signalShowErrorMessage.emit( """Error loading '%s' file %s: %s""" % (filetype, _filename, str(sys.exc_info()[1]))) return else: # set the layout if _show: self.setLayout(self.LayoutImageModel) # add to content if _merge and self.model: self.model.addSources(model.sources) self.signalShowMessage.emit( """Merged in %d sources from '%s' file %s""" % (len(model.sources), filetype, _filename), 3000) self.model.emitUpdate(SkyModel.SkyModel.UpdateAll) else: print("""Loaded %d sources from '%s' file %s""" % (len(model.sources), filetype, _filename)) self.signalShowMessage.emit( """Loaded %d sources from '%s' file %s""" % (len(model.sources), filetype, _filename), 3000) self._display_filename = os.path.basename(_filename) self.setModel(model) self._indicateModelUpdated(updated=False) # only set self.filename if an export function is available for this format. Otherwise set it to None, so that trying to save # the file results in a save-as operation (so that we don't save to a file in an unsupported format). self.filename = _filename if export_func else None finally: busy.reset_cursor() def closeEvent(self, event): dprint(1, "closing") self._exiting = True self.saveSizes() if not self.closeFile(): self._exiting = False event.ignore() return self.skyplot.close() self.imgman.close() self.closing.emit() dprint(1, "invoking os._exit(0)") os._exit(0) QMainWindow.closeEvent(self, event) def _canCloseExistingModel(self): # save model if modified if self.model and self._model_updated: res = QMessageBox.question( self, "Closing sky model", "<P>Model has been modified, would you like to save the changes?</P>", QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel, QMessageBox.Save) if res == QMessageBox.Cancel: return False elif res == QMessageBox.Save: if not self.saveFile(confirm=False, overwrite=True): return False # unload model images, unless we are already exiting anyway if not self._exiting: self.imgman.unloadModelImages() return True def closeFile(self): if not self._canCloseExistingModel(): return False # close model self._display_filename = None self.setModel(None) # set the layout self.setLayout(self.LayoutImage if self.imgman.getTopImage() else self. LayoutEmpty) return True def saveFile(self, filename=None, confirm=False, overwrite=True, non_native=False): """Saves file using the specified 'filename'. If filename is None, uses current filename, if that is not set, goes to saveFileAs() to open dialog and get a filename. If overwrite=False, will ask for confirmation before overwriting an existing file. If non_native=False, will ask for confirmation before exporting in non-native format. If confirm=True, will ask for confirmation regardless. Returns True if saving succeeded, False on error (or if cancelled by user). """ if isinstance(filename, QStringList): filename = filename[0] filename = (filename and str(filename)) or self.filename if filename is None: return self.saveFileAs() else: warning = '' # try to determine the file type filetype, import_func, export_func, doc = Tigger.Models.Formats.resolveFormat( filename, None) if export_func is None: self.signalShowErrorMessage.emit( """Error saving model file %s: unsupported output format""" % filename) return if os.path.exists(filename) and not overwrite: warning += "<P>The file already exists and will be overwritten.</P>" if filetype != 'Tigger' and not non_native: warning += """<P>Please note that you are exporting the model using the external format '%s'. Source types, tags and other model features not supported by this format will be omitted during the export.</P>""" % filetype # get confirmation if confirm or warning: dialog = QMessageBox.warning if warning else QMessageBox.question if dialog(self, "Saving sky model", "<P>Save model to %s?</P>%s" % (filename, warning), QMessageBox.Save | QMessageBox.Cancel, QMessageBox.Save) != QMessageBox.Save: return False busy = BusyIndicator() try: export_func(self.model, filename) self.model.setFilename(filename) except: busy.reset_cursor() self.signalShowErrorMessage.emit( """Error saving model file %s: %s""" % (filename, str(sys.exc_info()[1]))) return False else: self.signalShowMessage.emit( """Saved model to file %s""" % filename, 3000) self._display_filename = os.path.basename(filename) self._indicateModelUpdated(updated=False) self.filename = filename return True finally: busy.reset_cursor() def saveFileAs(self, filename=None): """Saves file using the specified 'filename'. If filename is None, opens dialog to get a filename. Returns True if saving succeeded, False on error (or if cancelled by user). """ if filename is None: if not self._save_as_dialog: filters = ";;".join([ "%s (%s)" % (name, " ".join(patterns)) for name, patterns, func in self._save_file_types ]) dialog = self._save_as_dialog = QFileDialog( self, "Save sky model", ".", filters) dialog.setDefaultSuffix(ModelHTML.DefaultExtension) dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setOption(QFileDialog.DontConfirmOverwrite, True) dialog.setModal(True) dialog.filesSelected['QStringList'].connect(self.saveFileAs) return self._save_as_dialog.exec_() == QDialog.Accepted # filename supplied, so save return self.saveFile(filename, confirm=False) def saveSelectionAs(self, filename=None, force=False): if not self.model: return if filename is None: if not self._save_sel_as_dialog: filters = ";;".join([ "%s (%s)" % (name, " ".join(patterns)) for name, patterns, func in self._save_file_types ]) dialog = self._save_sel_as_dialog = QFileDialog( self, "Save sky model", ".", filters) dialog.setDefaultSuffix(ModelHTML.DefaultExtension) dialog.setFileMode(QFileDialog.AnyFile) dialog.setAcceptMode(QFileDialog.AcceptSave) dialog.setOption(QFileDialog.DontConfirmOverwrite, False) dialog.setModal(True) dialog.filesSelected['QStringList'].connect( self.saveSelectionAs) return self._save_sel_as_dialog.exec_() == QDialog.Accepted # save selection if isinstance(filename, QStringList): filename = filename[0] filename = str(filename) selmodel = self.model.copy() sources = [src for src in self.model.sources if src.selected] if not sources: self.signalShowErrorMessage.emit( """You have not selected any sources to save.""") return # try to determine the file type filetype, import_func, export_func, doc = Tigger.Models.Formats.resolveFormat( filename, None) if export_func is None: self.signalShowErrorMessage.emit( """Error saving model file %s: unsupported output format""" % filename) return busy = BusyIndicator() try: export_func(self.model, filename, sources=sources) except: busy.reset_cursor() self.signalShowErrorMessage.emit( """Error saving selection to model file %s: %s""" % (filename, str(sys.exc_info()[1]))) return False else: self.signalShowMessage.emit( """Wrote %d selected source%s to file %s""" % (len(selmodel.sources), "" if len(selmodel.sources) == 1 else "s", filename), 3000) finally: busy.reset_cursor() pass def addTagToSelection(self): if not hasattr(self, '_add_tag_dialog'): self._add_tag_dialog = Widgets.AddTagDialog(self, modal=True) self._add_tag_dialog.setTags(self.model.tagnames) self._add_tag_dialog.setValue(True) if self._add_tag_dialog.exec_() != QDialog.Accepted: return tagname, value = self._add_tag_dialog.getTag() if tagname is None or value is None: return None dprint(1, "tagging selected sources with", tagname, value) # tag selected sources for src in self.model.sources: if src.selected: src.setAttribute(tagname, value) # If tag is not new, set a UpdateSelectionOnly flag on the signal dprint(1, "adding tag to model") self.model.addTag(tagname) dprint(1, "recomputing totals") self.model.getTagGrouping(tagname).computeTotal(self.model.sources) dprint(1, "emitting update signal") what = SkyModel.SkyModel.UpdateSourceContent + SkyModel.SkyModel.UpdateTags + SkyModel.SkyModel.UpdateSelectionOnly self.model.emitUpdate(what, origin=self) def removeTagsFromSelection(self): if not hasattr(self, '_remove_tag_dialog'): self._remove_tag_dialog = Widgets.SelectTagsDialog( self, modal=True, caption="Remove Tags", ok_button="Remove") # get set of all tags in selected sources tags = set() for src in self.model.sources: if src.selected: tags.update(src.getTagNames()) if not tags: return tags = list(tags) tags.sort() # show dialog self._remove_tag_dialog.setTags(tags) if self._remove_tag_dialog.exec_() != QDialog.Accepted: return tags = self._remove_tag_dialog.getSelectedTags() if not tags: return # ask for confirmation plural = (len(tags) > 1 and "s") or "" if QMessageBox.question( self, "Removing tags", "<P>Really remove the tag%s '%s' from selected sources?</P>" % (plural, "', '".join(tags)), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) != QMessageBox.Yes: return # remove the tags for src in self.model.sources: if src.selected: for tag in tags: src.removeAttribute(tag) # update model self.model.scanTags() self.model.initGroupings() # emit signal what = SkyModel.SkyModel.UpdateSourceContent + SkyModel.SkyModel.UpdateTags + SkyModel.SkyModel.UpdateSelectionOnly self.model.emitUpdate(what, origin=self) def _indicateModelUpdated(self, what=None, origin=None, updated=True): """Marks model as updated.""" self._model_updated = updated self.isUpdated.emit(updated) if self.model: self.setWindowTitle("Tigger - %s%s" % ((self._display_filename or "(unnamed)", " (modified)" if updated else "")))