Ejemplo n.º 1
0
        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>')))
Ejemplo n.º 2
0
        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>')))
Ejemplo n.º 3
0
	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()
Ejemplo n.º 4
0
 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
Ejemplo n.º 6
0
    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>"))
            )
Ejemplo n.º 7
0
 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)
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
 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)
Ejemplo n.º 10
0
    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")
Ejemplo n.º 11
0
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 "")))