Ejemplo n.º 1
0
    def __init__(self):
        QGroupBox.__init__(self)

        self.treemodel = treemodel = TestNe1Model()

        self.view = view = ModelTree("Model tree", treemodel, self)
        view.mt_update()

        def thunk(str):
            def _thunk(str=str):
                print str

            return _thunk

        self.chunkNum = 2
        self.gbox = QGroupBox()
        vl = QVBoxLayout(self)
        vl.setSpacing(0)
        vl.setMargin(0)
        vl.addWidget(self.view)
        self.buttonLayout = hl = QHBoxLayout()
        hl.setSpacing(0)
        hl.setMargin(0)
        vl.addLayout(hl)
        self.buttonNum = 1
        for func in (self.addmol, self.addjig, self.selected):
            self.addButton(func)
Ejemplo n.º 2
0
class DemoApp(QWidget):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.bottomwidget = QWidget(self)
        self.bottomwidget.setMaximumHeight(200)
        self.bottomwidget.setMinimumHeight(0)

        self.glwidget = DemoWidget(self)

        self.mlayout = QVBoxLayout()
        self.setLayout(self.mlayout)
        self.mlayout.addWidget(self.glwidget)
        self.mlayout.addWidget(self.bottomwidget)
        self.mlayout.setSpacing(0)
        self.mlayout.setContentsMargins(0, 0, 0, 0)

        self.bottomwidget.hide()

        self.frame = self.glwidget

    def appendView(self, view):
        return self.glwidget.appendView(view)

    def appendAboutView(self, view):
        return self.glwidget.appendAboutView(view)

    def appendInitialView(self, view):
        return self.glwidget.appendInitialView(view)
Ejemplo n.º 3
0
 def __init__(self, parent, modal=True, flags=Qt.WindowFlags(), caption="Select Tags", ok_button="Select"):
     QDialog.__init__(self, parent, flags)
     self.setModal(modal)
     self.setWindowTitle(caption)
     lo = QVBoxLayout(self)
     lo.setMargin(10)
     lo.setSpacing(5)
     # tag selector
     self.wtagsel = QListWidget(self)
     lo.addWidget(self.wtagsel)
     #    self.wtagsel.setColumnMode(QListBox.FitToWidth)
     self.wtagsel.setSelectionMode(QListWidget.MultiSelection)
     QObject.connect(self.wtagsel, SIGNAL("itemSelectionChanged()"), self._check_tag)
     # buttons
     lo.addSpacing(10)
     lo2 = QHBoxLayout()
     lo.addLayout(lo2)
     lo2.setContentsMargins(0, 0, 0, 0)
     lo2.setMargin(5)
     self.wokbtn = QPushButton(ok_button, self)
     self.wokbtn.setMinimumWidth(128)
     QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
     self.wokbtn.setEnabled(False)
     cancelbtn = QPushButton("Cancel", self)
     cancelbtn.setMinimumWidth(128)
     QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
     lo2.addWidget(self.wokbtn)
     lo2.addStretch(1)
     lo2.addWidget(cancelbtn)
     self.setMinimumWidth(384)
     self._tagnames = []
Ejemplo n.º 4
0
    def __init__(self, parent = None, name = None, modal = 1, fl = 0):
        #QDialog.__init__(self,parent,name,modal,fl)
        QDialog.__init__(self,parent)
        self.setModal(modal)
        qt4todo("handle flags in TextMessageBox.__init__")

        if name is None: name = "TextMessageBox"
        self.setObjectName(name)
        self.setWindowTitle(name)

        TextMessageLayout = QVBoxLayout(self)
        TextMessageLayout.setMargin(5)
        TextMessageLayout.setSpacing(1)

        self.text_edit = QTextEdit(self)

        TextMessageLayout.addWidget(self.text_edit)

        self.close_button = QPushButton(self)
        self.close_button.setText("Close")
        TextMessageLayout.addWidget(self.close_button)

        self.resize(QSize(350, 300).expandedTo(self.minimumSizeHint()))
            # Width changed from 300 to 350. Now hscrollbar doesn't appear in
            # Help > Graphics Info textbox. mark 060322
        qt4todo('self.clearWState(Qt.WState_Polished)') # what is this?

        self.connect(self.close_button, SIGNAL("clicked()"),self.close)
    def __init__(self):
        QGroupBox.__init__(self)

        self.treemodel = treemodel = TestNe1Model()

        self.view = view = ModelTree("Model tree", treemodel, self)
        view.mt_update()

        def thunk(str):
            def _thunk(str=str):
                print str
            return _thunk

        self.chunkNum = 2
        self.gbox = QGroupBox()
        vl = QVBoxLayout(self)
        vl.setSpacing(0)
        vl.setMargin(0)
        vl.addWidget(self.view)
        self.buttonLayout = hl = QHBoxLayout()
        hl.setSpacing(0)
        hl.setMargin(0)
        vl.addLayout(hl)
        self.buttonNum = 1
        for func in (self.addmol, self.addjig, self.selected):
            self.addButton(func)
Ejemplo n.º 6
0
    def __init__(self, parent=None, name=None, modal=1, fl=0):
        #QDialog.__init__(self,parent,name,modal,fl)
        QDialog.__init__(self, parent)
        self.setModal(modal)
        qt4todo("handle flags in TextMessageBox.__init__")

        if name is None: name = "TextMessageBox"
        self.setObjectName(name)
        self.setWindowTitle(name)

        TextMessageLayout = QVBoxLayout(self)
        TextMessageLayout.setMargin(5)
        TextMessageLayout.setSpacing(1)

        self.text_edit = QTextEdit(self)

        TextMessageLayout.addWidget(self.text_edit)

        self.close_button = QPushButton(self)
        self.close_button.setText("Close")
        TextMessageLayout.addWidget(self.close_button)

        self.resize(QSize(350, 300).expandedTo(self.minimumSizeHint()))
        # Width changed from 300 to 350. Now hscrollbar doesn't appear in
        # Help > Graphics Info textbox. mark 060322
        qt4todo('self.clearWState(Qt.WState_Polished)')  # what is this?

        self.connect(self.close_button, SIGNAL("clicked()"), self.close)
Ejemplo n.º 7
0
 def __init__(self, parent, flags=Qt.WindowFlags()):
     QDialog.__init__(self, parent, flags)
     self.setModal(False)
     self.setWindowTitle("Select sources by...")
     lo = QVBoxLayout(self)
     lo.setMargin(10)
     lo.setSpacing(5)
     # select by
     lo1 = QHBoxLayout()
     lo.addLayout(lo1)
     lo1.setContentsMargins(0, 0, 0, 0)
     #    lab = QLabel("Select:")
     #   lo1.addWidget(lab)
     self.wselby = QComboBox(self)
     lo1.addWidget(self.wselby, 0)
     QObject.connect(self.wselby, SIGNAL("activated(const QString &)"), self._setup_selection_by)
     # under/over
     self.wgele = QComboBox(self)
     lo1.addWidget(self.wgele, 0)
     self.wgele.addItems([">", ">=", "<=", "<", "sum<=", "sum>"])
     QObject.connect(self.wgele, SIGNAL("activated(const QString &)"), self._select_threshold)
     # threshold value
     self.wthreshold = QLineEdit(self)
     QObject.connect(self.wthreshold, SIGNAL("editingFinished()"), self._select_threshold)
     lo1.addWidget(self.wthreshold, 1)
     # min and max label
     self.wminmax = QLabel(self)
     lo.addWidget(self.wminmax)
     # selection slider
     lo1 = QHBoxLayout()
     lo.addLayout(lo1)
     self.wpercent = QSlider(self)
     self.wpercent.setTracking(False)
     QObject.connect(self.wpercent, SIGNAL("valueChanged(int)"), self._select_percentile)
     QObject.connect(self.wpercent, SIGNAL("sliderMoved(int)"), self._select_percentile_threshold)
     self.wpercent.setRange(0, 100)
     self.wpercent.setOrientation(Qt.Horizontal)
     lo1.addWidget(self.wpercent)
     self.wpercent_lbl = QLabel("0%", self)
     self.wpercent_lbl.setMinimumWidth(64)
     lo1.addWidget(self.wpercent_lbl)
     #    # hide button
     #    lo.addSpacing(10)
     #    lo2 = QHBoxLayout()
     #    lo.addLayout(lo2)
     #    lo2.setContentsMargins(0,0,0,0)
     #    hidebtn = QPushButton("Close",self)
     #    hidebtn.setMinimumWidth(128)
     #    QObject.connect(hidebtn,SIGNAL("clicked()"),self.hide)
     #    lo2.addStretch(1)
     #    lo2.addWidget(hidebtn)
     #    lo2.addStretch(1)
     #    self.setMinimumWidth(384)
     self._in_select_threshold = False
     self._sort_index = None
     self.qerrmsg = QErrorMessage(self)
Ejemplo n.º 8
0
 def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
     QDialog.__init__(self, parent, flags)
     self.setModal(modal)
     self.setWindowTitle("Add FITS brick")
     lo = QVBoxLayout(self)
     lo.setMargin(10)
     lo.setSpacing(5)
     # file selector
     self.wfile = FileSelector(self,
                               label="FITS filename:",
                               dialog_label="FITS file",
                               default_suffix="fits",
                               file_types="FITS files (*.fits *.FITS)",
                               file_mode=QFileDialog.ExistingFile)
     lo.addWidget(self.wfile)
     # overwrite or add mode
     lo1 = QGridLayout()
     lo.addLayout(lo1)
     lo1.setContentsMargins(0, 0, 0, 0)
     lo1.addWidget(QLabel("Padding factor:", self), 0, 0)
     self.wpad = QLineEdit("2", self)
     self.wpad.setValidator(QDoubleValidator(self))
     lo1.addWidget(self.wpad, 0, 1)
     lo1.addWidget(QLabel("Assign source name:", self), 1, 0)
     self.wname = QLineEdit(self)
     lo1.addWidget(self.wname, 1, 1)
     # OK/cancel buttons
     lo.addSpacing(10)
     lo2 = QHBoxLayout()
     lo.addLayout(lo2)
     lo2.setContentsMargins(0, 0, 0, 0)
     lo2.setMargin(5)
     self.wokbtn = QPushButton("OK", self)
     self.wokbtn.setMinimumWidth(128)
     QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
     self.wokbtn.setEnabled(False)
     cancelbtn = QPushButton("Cancel", self)
     cancelbtn.setMinimumWidth(128)
     QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
     lo2.addWidget(self.wokbtn)
     lo2.addStretch(1)
     lo2.addWidget(cancelbtn)
     self.setMinimumWidth(384)
     # signals
     QObject.connect(self.wfile, SIGNAL("filenameSelected"),
                     self._fileSelected)
     # internal state
     self.qerrmsg = QErrorMessage(self)
Ejemplo n.º 9
0
    def __init__(self, parent=None):
        QWidget.__init__(self)
        self.parent = parent
        self.setWindowTitle("My Part Window")
        self.setSizePolicy(QSizePolicy.MinimumExpanding,
                           QSizePolicy.MinimumExpanding)
        layout = QtGui.QHBoxLayout(self)
        layout.setMargin(0)
        layout.setSpacing(0)

        #########################

        holder = QWidget()
        holder.setMaximumWidth(200)
        sublayout = QVBoxLayout(holder)
        sublayout.setMargin(0)
        sublayout.setSpacing(0)
        layout.addWidget(holder)

        ###

        self.featureManager = QtGui.QTabWidget()
        self.featureManager.setCurrentIndex(0)
        self.featureManager.setMaximumWidth(200)

        self.modelTreeTab = QtGui.QWidget()
        self.featureManager.addTab(self.modelTreeTab, "Model Tree")
        modelTreeTabLayout = QtGui.QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        self.propertyManagerTab = QtGui.QWidget()
        self.featureManager.addTab(self.propertyManagerTab, "Property Manager")

        sublayout.addWidget(self.featureManager)

        ###

        self.modelTree = modelTreeGui.TestWrapper()
        self.modelTree.addIconButton(
            QIcon(icons + '/GrapheneGeneratorDialog_image3.png'), self.dismiss)
        modelTreeTabLayout.addWidget(self.modelTree)

        self.glpane = GLPane()
        layout.addWidget(self.glpane)
Ejemplo n.º 10
0
    def _setNewView(self, viewClassName):
        # Put the GL widget inside the frame
        if not self.flayout:
            self.flayout = QVBoxLayout(self.elementFrame)
            self.flayout.setMargin(1)
            self.flayout.setSpacing(1)
        else:
            if self.elemGLPane:
                self.flayout.removeChild(self.elemGLPane)
                self.elemGLPane = None

        if viewClassName == 'ChunkView':
            # We never come here! How odd.
            self.elemGLPane = ChunkView(self.elementFrame, "chunk glPane", self.w.glpane)
        elif viewClassName == 'MMKitView':
            self.elemGLPane = MMKitView(self.elementFrame, "MMKitView glPane", self.w.glpane)

        self.flayout.addWidget(self.elemGLPane,1)


        #ninad 070326. Note that self.DirView inherits QTreeView.
        #It has got nothing to do with the experimental class DirView in file Dirview.py
        self.dirView = self.DirView(self, self.libraryPage)
        self.dirView.setSortingEnabled(False) #bruce 070521 changed True to False -- fixes "reverse order" bug on my Mac
        ##self.dirView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

        libraryPageLayout = QVBoxLayout(self.libraryPage)
        libraryPageLayout.setMargin(pmMMKitPageMargin) # Was 4. Mark 2007-05-30
        libraryPageLayout.setSpacing(2)
        libraryPageLayout.addWidget(self.dirView)


        filePath = os.path.dirname(os.path.abspath(sys.argv[0]))
        libDir = os.path.normpath(filePath + '/../partlib')

        self.libPathKey = '/nanorex/nE-1/libraryPath'
        libDir = env.prefs.get(self.libPathKey, libDir)

        if os.path.isdir(libDir):
            self.rootDir = libDir
            self.dirView.setRootPath(libDir)
        else:
            self.rootDir = None
            from history.HistoryWidget import redmsg
            env.history.message(redmsg("The part library directory: %s doesn't exist." %libDir))
Ejemplo n.º 11
0
    def __init__(self, parent=None):
        QWidget.__init__(self)
        self.parent = parent
        self.setWindowTitle("My Part Window")
        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)
        layout = QtGui.QHBoxLayout(self)
        layout.setMargin(0)
        layout.setSpacing(0)

        #########################

        holder = QWidget()
        holder.setMaximumWidth(200)
        sublayout = QVBoxLayout(holder)
        sublayout.setMargin(0)
        sublayout.setSpacing(0)
        layout.addWidget(holder)

        ###

        self.featureManager = QtGui.QTabWidget()
        self.featureManager.setCurrentIndex(0)
        self.featureManager.setMaximumWidth(200)

        self.modelTreeTab = QtGui.QWidget()
        self.featureManager.addTab(self.modelTreeTab, "Model Tree")
        modelTreeTabLayout = QtGui.QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        self.propertyManagerTab = QtGui.QWidget()
        self.featureManager.addTab(self.propertyManagerTab, "Property Manager")

        sublayout.addWidget(self.featureManager)

        ###

        self.modelTree = modelTreeGui.TestWrapper()
        self.modelTree.addIconButton(QIcon(icons + '/GrapheneGeneratorDialog_image3.png'),
                                     self.dismiss)
        modelTreeTabLayout.addWidget(self.modelTree)

        self.glpane = GLPane()
        layout.addWidget(self.glpane)
Ejemplo n.º 12
0
 def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
     QDialog.__init__(self, parent, flags)
     self.setModal(modal)
     self.setWindowTitle("Add Tag")
     lo = QVBoxLayout(self)
     lo.setMargin(10)
     lo.setSpacing(5)
     # tag selector
     lo1 = QHBoxLayout()
     lo.addLayout(lo1)
     lo1.setSpacing(5)
     self.wtagsel = QComboBox(self)
     self.wtagsel.setEditable(True)
     wtagsel_lbl = QLabel("&Tag:", self)
     wtagsel_lbl.setBuddy(self.wtagsel)
     lo1.addWidget(wtagsel_lbl, 0)
     lo1.addWidget(self.wtagsel, 1)
     QObject.connect(self.wtagsel, SIGNAL("activated(int)"),
                     self._check_tag)
     QObject.connect(self.wtagsel,
                     SIGNAL("editTextChanged(const QString &)"),
                     self._check_tag_text)
     # value editor
     self.valedit = ValueTypeEditor(self)
     lo.addWidget(self.valedit)
     # buttons
     lo.addSpacing(10)
     lo2 = QHBoxLayout()
     lo.addLayout(lo2)
     lo2.setContentsMargins(0, 0, 0, 0)
     lo2.setMargin(5)
     self.wokbtn = QPushButton("OK", self)
     self.wokbtn.setMinimumWidth(128)
     QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
     self.wokbtn.setEnabled(False)
     cancelbtn = QPushButton("Cancel", self)
     cancelbtn.setMinimumWidth(128)
     QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
     lo2.addWidget(self.wokbtn)
     lo2.addStretch(1)
     lo2.addWidget(cancelbtn)
     self.setMinimumWidth(384)
Ejemplo n.º 13
0
    def __init__(self):
        QGroupBox.__init__(self)

        self.treemodel = treemodel = TestNe1Model()

        self.view = view = ModelTreeGui(TestMainWindow(), "Model tree", treemodel, self)
        view.mt_update()
        self.chunkNum = 2
        self.gbox = QGroupBox()
        vl = QVBoxLayout(self)
        vl.setSpacing(0)
        vl.setMargin(0)
        vl.addWidget(self.view)
        self.buttonLayout = hl = QHBoxLayout()
        hl.setSpacing(0)
        hl.setMargin(0)
        vl.addLayout(hl)
        self.buttonNum = 1
        for func in (self.addmol, self.addjig, self.selected):
            self.addButton(func)
Ejemplo n.º 14
0
    def __init__(self):
        QGroupBox.__init__(self)

        self.treemodel = treemodel = TestNe1Model()

        self.view = view = ModelTreeGui(TestMainWindow(), "Model tree",
                                        treemodel, self)
        view.mt_update()
        self.chunkNum = 2
        self.gbox = QGroupBox()
        vl = QVBoxLayout(self)
        vl.setSpacing(0)
        vl.setMargin(0)
        vl.addWidget(self.view)
        self.buttonLayout = hl = QHBoxLayout()
        hl.setSpacing(0)
        hl.setMargin(0)
        vl.addLayout(hl)
        self.buttonNum = 1
        for func in (self.addmol, self.addjig, self.selected):
            self.addButton(func)
Ejemplo n.º 15
0
 def __init__(self, html, width=600, timeout=None):
     QDialog.__init__(self)
     self.setMinimumWidth(width)
     self.setObjectName("About NanoEngineer-1")
     TextEditLayout = QVBoxLayout(self)
     TextEditLayout.setSpacing(0)
     TextEditLayout.setMargin(0)
     self.text_edit = QTextEdit(self)
     self.text_edit.setHtml(html)
     self.text_edit.setReadOnly(True)
     self.text_edit.setWordWrapMode(QTextOption.WordWrap)
     TextEditLayout.addWidget(self.text_edit)
     self.quit_button = QPushButton("OK")
     TextEditLayout.addWidget(self.quit_button)
     self.connect(self.quit_button, SIGNAL("clicked()"), self.close)
     if timeout is not None:
         self.qt = QTimer()
         self.qt.setInterval(1000 * timeout)
         self.qt.start()
         self.connect(self.qt, SIGNAL("timeout()"), self.close)
     self.show()
     self.exec_()
Ejemplo n.º 16
0
 def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
     QDialog.__init__(self, parent, flags)
     self.setModal(modal)
     self.setWindowTitle("Add Tag")
     lo = QVBoxLayout(self)
     lo.setMargin(10)
     lo.setSpacing(5)
     # tag selector
     lo1 = QHBoxLayout()
     lo.addLayout(lo1)
     lo1.setSpacing(5)
     self.wtagsel = QComboBox(self)
     self.wtagsel.setEditable(True)
     wtagsel_lbl = QLabel("&Tag:", self)
     wtagsel_lbl.setBuddy(self.wtagsel)
     lo1.addWidget(wtagsel_lbl, 0)
     lo1.addWidget(self.wtagsel, 1)
     QObject.connect(self.wtagsel, SIGNAL("activated(int)"), self._check_tag)
     QObject.connect(self.wtagsel, SIGNAL("editTextChanged(const QString &)"), self._check_tag_text)
     # value editor
     self.valedit = ValueTypeEditor(self)
     lo.addWidget(self.valedit)
     # buttons
     lo.addSpacing(10)
     lo2 = QHBoxLayout()
     lo.addLayout(lo2)
     lo2.setContentsMargins(0, 0, 0, 0)
     lo2.setMargin(5)
     self.wokbtn = QPushButton("OK", self)
     self.wokbtn.setMinimumWidth(128)
     QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
     self.wokbtn.setEnabled(False)
     cancelbtn = QPushButton("Cancel", self)
     cancelbtn.setMinimumWidth(128)
     QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
     lo2.addWidget(self.wokbtn)
     lo2.addStretch(1)
     lo2.addWidget(cancelbtn)
     self.setMinimumWidth(384)
Ejemplo n.º 17
0
 def __init__(self, html, width=600, timeout=None):
     QDialog.__init__(self)
     self.setMinimumWidth(width)
     self.setObjectName("About NanoEngineer-1")
     TextEditLayout = QVBoxLayout(self)
     TextEditLayout.setSpacing(0)
     TextEditLayout.setMargin(0)
     self.text_edit = QTextEdit(self)
     self.text_edit.setHtml(html)
     self.text_edit.setReadOnly(True)
     self.text_edit.setWordWrapMode(QTextOption.WordWrap)
     TextEditLayout.addWidget(self.text_edit)
     self.quit_button = QPushButton("OK")
     TextEditLayout.addWidget(self.quit_button)
     self.connect(self.quit_button, SIGNAL("clicked()"), self.close)
     if timeout is not None:
         self.qt = QTimer()
         self.qt.setInterval(1000*timeout)
         self.qt.start()
         self.connect(self.qt, SIGNAL("timeout()"), self.close)
     self.show()
     self.exec_()
Ejemplo n.º 18
0
 def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
     QDialog.__init__(self, parent, flags)
     self.setModal(modal)
     self.setWindowTitle("Export Karma annotations")
     lo = QVBoxLayout(self)
     lo.setMargin(10)
     lo.setSpacing(5)
     # file selector
     self.wfile = FileSelector(self,
                               label="Filename:",
                               dialog_label="Karma annotations filename",
                               default_suffix="ann",
                               file_types="Karma annotations (*.ann)")
     lo.addWidget(self.wfile)
     # selected sources checkbox
     self.wsel = QCheckBox("selected sources only", self)
     lo.addWidget(self.wsel)
     # OK/cancel buttons
     lo.addSpacing(10)
     lo2 = QHBoxLayout()
     lo.addLayout(lo2)
     lo2.setContentsMargins(0, 0, 0, 0)
     lo2.setMargin(5)
     self.wokbtn = QPushButton("OK", self)
     self.wokbtn.setMinimumWidth(128)
     QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
     self.wokbtn.setEnabled(False)
     cancelbtn = QPushButton("Cancel", self)
     cancelbtn.setMinimumWidth(128)
     QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
     lo2.addWidget(self.wokbtn)
     lo2.addStretch(1)
     lo2.addWidget(cancelbtn)
     self.setMinimumWidth(384)
     # signals
     QObject.connect(self.wfile, SIGNAL("valid"), self.wokbtn.setEnabled)
     # internal state
     self.qerrmsg = QErrorMessage(self)
     self._model_filename = None
Ejemplo n.º 19
0
 def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
     QDialog.__init__(self, parent, flags)
     self.setModal(modal)
     self.setWindowTitle("Export Karma annotations")
     lo = QVBoxLayout(self)
     lo.setMargin(10)
     lo.setSpacing(5)
     # file selector
     self.wfile = FileSelector(self, label="Filename:", dialog_label="Karma annotations filename",
                               default_suffix="ann", file_types="Karma annotations (*.ann)")
     lo.addWidget(self.wfile)
     # selected sources checkbox
     self.wsel = QCheckBox("selected sources only", self)
     lo.addWidget(self.wsel)
     # OK/cancel buttons
     lo.addSpacing(10)
     lo2 = QHBoxLayout()
     lo.addLayout(lo2)
     lo2.setContentsMargins(0, 0, 0, 0)
     lo2.setMargin(5)
     self.wokbtn = QPushButton("OK", self)
     self.wokbtn.setMinimumWidth(128)
     QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
     self.wokbtn.setEnabled(False)
     cancelbtn = QPushButton("Cancel", self)
     cancelbtn.setMinimumWidth(128)
     QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
     lo2.addWidget(self.wokbtn)
     lo2.addStretch(1)
     lo2.addWidget(cancelbtn)
     self.setMinimumWidth(384)
     # signals
     QObject.connect(self.wfile, SIGNAL("valid"), self.wokbtn.setEnabled)
     # internal state
     self.qerrmsg = QErrorMessage(self)
     self._model_filename = None
Ejemplo n.º 20
0
    def __init__(self, assy, parent):
        """
        Constructor for the part window.

        @param assy: The assembly (part)
        @type  assy: Assembly

        @param parent: The parent widget.
        @type  parent: U{B{QMainWindow}
                       <http://doc.trolltech.com/4/qmainwindow.html>}
        """
        QWidget.__init__(self, parent)
        self.parent = parent
        self.assy = assy
        # note: to support MDI, self.assy would probably need to be a
        # different assembly for each PartWindow.
        # [bruce 080216 comment]
        self.setWindowIcon(geticon("ui/border/Part.png"))
        self.updateWindowTitle()

        # The main layout for the part window is a VBoxLayout <pwVBoxLayout>.
        self.pwVBoxLayout = QVBoxLayout(self)
        pwVBoxLayout = self.pwVBoxLayout
        pwVBoxLayout.setMargin(0)
        pwVBoxLayout.setSpacing(0)

        # ################################################################
        # <pwSplitter> is the horizontal splitter b/w the
        # pwLeftArea (mt and pm) and the glpane.
        self.pwSplitter = QSplitter(Qt.Horizontal)
        pwSplitter = self.pwSplitter
        pwSplitter.setObjectName("pwSplitter")
        pwSplitter.setHandleWidth(3)  # 3 pixels wide.
        pwVBoxLayout.addWidget(pwSplitter)

        # ##################################################################
        # <pwLeftArea> is the container holding the pwProjectTabWidget.
        # Note: Making pwLeftArea (and pwRightArea and pwBottomArea) QFrame
        # widgets has the benefit of making it easy to draw a border around
        # each area. One purpose of this would be to help developers understand
        # (visually) how the part window is laid out. I intend to add a debug
        # pref to draw part window area borders and add "What's This" text to
        # them. Mark 2008-01-05.
        self.pwLeftArea = LeftFrame(self)
        pwLeftArea = self.pwLeftArea
        pwLeftArea.setObjectName("pwLeftArea")
        pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)

        # Setting the frame style like this is nice since it clearly
        # defines the splitter at the top-left corner.
        pwLeftArea.setFrameStyle(QFrame.Panel | QFrame.Sunken)

        # This layout will contain splitter (above) and the pwBottomArea.
        leftChannelVBoxLayout = QVBoxLayout(pwLeftArea)
        leftChannelVBoxLayout.setMargin(0)
        leftChannelVBoxLayout.setSpacing(0)

        pwSplitter.addWidget(pwLeftArea)

        # Makes it so pwLeftArea is not collapsible.
        pwSplitter.setCollapsible(0, False)

        # ##################################################################
        # <pwProjectTabWidget> is a QTabWidget that contains the MT and PM
        # widgets. It lives in the "left area" of the part window.
        self.pwProjectTabWidget = _pwProjectTabWidget()
        # _pwProjectTabWidget subclasses QTabWidget
        # Note [bruce 070829]: to fix bug 2522 I need to intercept
        # self.pwProjectTabWidget.removeTab, so I made it a subclass of
        # QTabWidget. It needs to know the GLPane, but that's not created
        # yet, so we set it later using KLUGE_setGLPane (below).
        # Note: No parent supplied. Could this be the source of the
        # minor vsplitter resizing problem I was trying to resolve a few
        # months ago?  Try supplying a parent later. Mark 2008-01-01
        self.pwProjectTabWidget.setObjectName("pwProjectTabWidget")
        self.pwProjectTabWidget.setCurrentIndex(0)
        self.pwProjectTabWidget.setAutoFillBackground(True)

        # Create the model tree "tab" widget. It will contain the MT GUI widget.
        # Set the tab icon, too.
        self.modelTreeTab = QWidget()
        self.modelTreeTab.setObjectName("modelTreeTab")
        self.pwProjectTabWidget.addTab(self.modelTreeTab,
                                       geticon("ui/modeltree/Model_Tree.png"),
                                       "")

        modelTreeTabLayout = QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        # Create the model tree (GUI) and add it to the tab layout.
        self.modelTree = ModelTree(self.modelTreeTab, parent)
        self.modelTree.modelTreeGui.setObjectName("modelTreeGui")
        modelTreeTabLayout.addWidget(self.modelTree.modelTreeGui)

        # Create the property manager "tab" widget. It will contain the PropMgr
        # scroll area, which will contain the property manager and all its
        # widgets.
        self.propertyManagerTab = QWidget()
        self.propertyManagerTab.setObjectName("propertyManagerTab")

        self.propertyManagerScrollArea = QScrollArea(self.pwProjectTabWidget)
        self.propertyManagerScrollArea.setObjectName(
            "propertyManagerScrollArea")
        self.propertyManagerScrollArea.setWidget(self.propertyManagerTab)
        self.propertyManagerScrollArea.setWidgetResizable(True)
        # Eureka!
        # setWidgetResizable(True) will resize the Property Manager (and its
        # contents) correctly when the scrollbar appears/disappears.
        # It even accounts correctly for collapsed/expanded groupboxes!
        # Mark 2007-05-29

        # Add the property manager scroll area as a "tabbed" widget.
        # Set the tab icon, too.
        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"), "")

        # Finally, add the "pwProjectTabWidget" to the left channel layout.

        leftChannelVBoxLayout.addWidget(self.pwProjectTabWidget)

        # Create the glpane and make it a child of the part splitter.
        self.glpane = GLPane(assy, self, 'glpane name', parent)
        # note: our owner (MWsemantics) assumes
        # there is just this one GLPane for assy, and stores it
        # into assy as assy.o and assy.glpane. [bruce 080216 comment]

        # Add what's this text to self.glpane.
        # [bruce 080912 moved this here from part of a method in class GLPane.
        #  In this code's old location, Mark wrote [2007-06-01]: "Problem -
        #  I don't believe this text is processed by fix_whatsthis_text_and_links()
        #  in whatsthis_utilities.py." Now that this code is here, I don't know
        #  whether that's still true. ]
        from ne1_ui.WhatsThisText_for_MainWindow import whats_this_text_for_glpane
        self.glpane.setWhatsThis(whats_this_text_for_glpane())

        # update [re the above comment], bruce 081209:
        # I added the following explicit call of fix_whatsthis_text_and_links,
        # but it doesn't work to replace Ctrl with Cmd on Mac;
        # see today's comment in fix_whatsthis_text_and_links for likely reason.
        # So I will leave this here, but also leave in place the kluges
        # in whats_this_text_for_glpane to do that replacement itself.
        # The wiki help link in this whatsthis text doesn't work,
        # but I guess that is an independent issue, related to lack
        # of use of class QToolBar_WikiHelp or similar code, for GLPane
        # or this class or the main window class.
        from foundation.whatsthis_utilities import fix_whatsthis_text_and_links
        fix_whatsthis_text_and_links(self.glpane)  # doesn't yet work

        self.pwProjectTabWidget.KLUGE_setGLPane(self.glpane)
        # help fix bug 2522 [bruce 070829]
        qt4warnDestruction(self.glpane, 'GLPane of PartWindow')
        pwSplitter.addWidget(self.glpane)

        # ##################################################################
        # <pwBottomArea> is a container at the bottom of the part window
        # spanning its entire width. It is intended to be used as an extra
        # area for use by Property Managers (or anything else) that needs
        # a landscape oriented layout.
        # An example is the Sequence Editor, which is part of the
        # Strand Properties PM.
        self.pwBottomArea = QFrame()
        # IMHO, self is not a good parent. Mark 2008-01-04.
        pwBottomArea = self.pwBottomArea
        pwBottomArea.setObjectName("pwBottomArea")
        pwBottomArea.setMaximumHeight(50)

        # Add a frame border to see what it looks like.
        pwBottomArea.setFrameStyle(QFrame.Panel | QFrame.Sunken)

        self.pwVBoxLayout.addWidget(pwBottomArea)

        # Hide the bottom frame for now. Later this might be used for the
        # sequence editor.
        pwBottomArea.hide()

        #This widget implementation is subject to heavy revision. The purpose
        #is to implement a NFR that Mark urgently needs : The NFR is: Need a
        #way to quickly find a node in the MT by entering its name.
        #-- Ninad 2008-11-06
        self.pwSpecialDockWidgetInLeftChannel = SelectNodeByNameDockWidget(
            self.glpane.win)
        leftChannelVBoxLayout.addWidget(self.pwSpecialDockWidgetInLeftChannel)

        # See the resizeEvent() docstring for more information about
        # resizeTimer.
        self.resizeTimer = QTimer(self)
        self.resizeTimer.setSingleShot(True)
        return
Ejemplo n.º 21
0
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("My Main Window")
        self.setMinimumWidth(MAIN_WINDOW_SIZE[0])
        self.setMinimumHeight(MAIN_WINDOW_SIZE[1])

        self.statusbar = QtGui.QStatusBar(self)
        self.statusbar.showMessage("Status message")
        self.setStatusBar(self.statusbar)

        ################################################

        self.menubar = self.menuBar()
        # Any menu action makes the status bar message disappear

        fileMenu = QtGui.QMenu(self.menubar)
        fileMenu.setTitle("File")
        self.menubar.addAction(fileMenu.menuAction())

        newAction = QtGui.QAction("New", self)
        newAction.setIcon(QtGui.QtIcon(icons + '/GroupPropDialog_image0.png'))
        fileMenu.addAction(newAction)
        openAction = QtGui.QAction("Open", self)
        openAction.setIcon(QtGui.QtIcon(icons + "/MainWindowUI_image1"))
        fileMenu.addAction(openAction)
        saveAction = QtGui.QAction("Save", self)
        saveAction.setIcon(QtGui.QtIcon(icons + "/MainWindowUI_image2"))
        fileMenu.addAction(saveAction)

        self.connect(newAction, SIGNAL("activated()"), self.fileNew)
        self.connect(openAction, SIGNAL("activated()"), self.fileOpen)
        self.connect(saveAction, SIGNAL("activated()"), self.fileSave)

        for otherMenuName in ('Edit', 'View', 'Display', 'Select', 'Modify',
                              'NanoHive-1'):
            otherMenu = QtGui.QMenu(self.menubar)
            otherMenu.setTitle(otherMenuName)
            self.menubar.addAction(otherMenu.menuAction())

        helpMenu = QtGui.QMenu(self.menubar)
        helpMenu.setTitle("Help")
        self.menubar.addAction(helpMenu.menuAction())

        aboutAction = QtGui.QAction("About", self)
        aboutAction.setIcon(QtGui.QtIcon(icons + '/MainWindowUI_image0.png'))
        helpMenu.addAction(aboutAction)

        self.connect(aboutAction, SIGNAL("activated()"), self.helpAbout)

        ##############################################

        self.setMenuBar(self.menubar)

        centralwidget = QWidget()
        self.setCentralWidget(centralwidget)
        layout = QVBoxLayout(centralwidget)
        layout.setMargin(0)
        layout.setSpacing(0)
        middlewidget = QWidget()

        self.bigButtons = QWidget()
        bblo = QHBoxLayout(self.bigButtons)
        bblo.setMargin(0)
        bblo.setSpacing(0)
        self.bigButtons.setMinimumHeight(50)
        self.bigButtons.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        for name in ('Features', 'Sketch', 'Build', 'Dimension', 'Simulator'):
            btn = QPushButton(self.bigButtons)
            btn.setMaximumWidth(80)
            btn.setMinimumHeight(50)
            btn.setText(name)
            self.bigButtons.layout().addWidget(btn)
        self.bigButtons.hide()
        layout.addWidget(self.bigButtons)

        self.littleIcons = QWidget()
        self.littleIcons.setMinimumHeight(30)
        self.littleIcons.setMaximumHeight(30)
        lilo = QHBoxLayout(self.littleIcons)
        lilo.setMargin(0)
        lilo.setSpacing(0)
        pb = QPushButton(self.littleIcons)
        pb.setIcon(QIcon(icons + '/GroupPropDialog_image0.png'))
        self.connect(pb, SIGNAL("clicked()"), self.fileNew)
        lilo.addWidget(pb)
        for x in "1 2 4 5 6 7 8 18 42 10 43 150 93 94 97 137".split():
            pb = QPushButton(self.littleIcons)
            pb.setIcon(QIcon(icons + '/MainWindowUI_image' + x + '.png'))
            lilo.addWidget(pb)
        layout.addWidget(self.littleIcons)

        layout.addWidget(middlewidget)

        self.layout = QGridLayout(middlewidget)
        self.layout.setMargin(0)
        self.layout.setSpacing(2)
        self.gridPosition = GridPosition()
        self.numParts = 0

        self.show()
        explainWindow = AboutWindow(
            "Select <b>Help-&gt;About</b>"
            " for instructions...", 200, 3)
Ejemplo n.º 22
0
class PM_DockWidget(QDockWidget):
    """
    PM_DockWidget class provides a dockable widget that can either be docked
    inside a PropertyManager OR can be docked in the MainWindow depending
    on the <parentWidget> . see DnaSequenceEditor.py for an example.
    The dockWidget has its own layout and containerwidget which makes it easy
    to add various children widgets  similar to how its done in PM_GroupBox
    """


    def __init__(self, parentWidget, title = "", showWidget = True):
        """
        Constructor for PM_dockWidget

        @param showWidget: If true, this class will show the widget
        immediately in the __init__ method itself. This is the default behavior
        and subclasses may pass appropriate value to this flag
        @type showWidget: bool
        """
        self.labelWidget  = None
        self._title         = ""
        self._widgetList    = []
        self._rowCount      = 0

        QDockWidget.__init__(self, parentWidget)

        self.parentWidget = parentWidget

        self._title = title

        self.label = ''
        self.labelColumn = 0
        self.spanWidth = True
        self.labelWidget = None

        if self.label: # Create this widget's QLabel.
            self.labelWidget = QLabel()
            self.labelWidget.setText(self.label)

        self.setEnabled(True)
        self.setFloating(False)
        self.setVisible(showWidget)
        self.setWindowTitle(self._title)
        self.setAutoFillBackground(True)
        self.setPalette(getPalette( None,
                                    QPalette.Window,
                                    pmGrpBoxColor))

        self.parentWidget.addDockWidget(Qt.BottomDockWidgetArea, self)

        #Define layout
        self._containerWidget = QWidget()
        self.setWidget(self._containerWidget)

        # Create vertical box layout
        self.vBoxLayout = QVBoxLayout(self._containerWidget)
        self.vBoxLayout.setMargin(1)
        self.vBoxLayout.setSpacing(0)

        # Create grid layout
        self.gridLayout = QGridLayout()
        self.gridLayout.setMargin(1)
        self.gridLayout.setSpacing(1)

        # Insert grid layout in its own vBoxLayout
        self.vBoxLayout.addLayout(self.gridLayout)

        #self.parentWidget.addPmWidget(self)
        self._loadWidgets()

        try:
            self._addWhatsThisText()
        except:
            print_compact_traceback("Error loading whatsthis text for this " \
                                    "property manager dock widget.")

        try:
            self._addToolTipText()
        except:
            print_compact_traceback("Error loading tool tip text for this " \
                                    "property manager dock widget.")

    def _loadWidgets(self):
        """
        Subclasses should override this method. Default implementation does
        nothing.
        @see: DnaSequenceEditor._loadWidgets
        """
        pass

    def _addWhatsThisText(self):
        """
        Add 'What's This' help text for self and child widgets.
        Subclasses should override this method.
        """
        pass

    def _addToolTipText(self):
        """
        Add 'Tool tip' help text for self and child widgets.
        Subclasses should override this method.
        """
        pass

    def getPmWidgetPlacementParameters(self, pmWidget):
        """
        NOTE: This method is duplicated from PM_GroupBox

        Returns all the layout parameters needed to place
        a PM_Widget in the group box grid layout.

        @param pmWidget: The PM widget.
        @type  pmWidget: PM_Widget
        """
        row = self._rowCount

        #PM_CheckBox doesn't have a label. So do the following to decide the
        #placement of the checkbox. (can be placed either in column 0 or 1 ,
        #This also needs to be implemented for PM_RadioButton, but at present
        #the following code doesn't support PM_RadioButton.
        if isinstance(pmWidget, PM_CheckBox):
            # Set the widget's row and column parameters.
            widgetRow      = row
            widgetColumn   = pmWidget.widgetColumn
            widgetSpanCols = 1
            widgetAlignment = PM_LABEL_LEFT_ALIGNMENT
            rowIncrement   = 1
            #set a virtual label
            labelRow       = row
            labelSpanCols  = 1
            labelAlignment = PM_LABEL_RIGHT_ALIGNMENT

            if widgetColumn == 0:
                labelColumn   = 1
            elif widgetColumn == 1:
                labelColumn   = 0

            return (widgetRow,
                    widgetColumn,
                    widgetSpanCols,
                    widgetAlignment,
                    rowIncrement,
                    labelRow,
                    labelColumn,
                    labelSpanCols,
                    labelAlignment)


        label       = pmWidget.label
        labelColumn = pmWidget.labelColumn
        spanWidth   = pmWidget.spanWidth

        if not spanWidth:
            # This widget and its label are on the same row
            labelRow       = row
            labelSpanCols  = 1
            labelAlignment = PM_LABEL_RIGHT_ALIGNMENT
            # Set the widget's row and column parameters.
            widgetRow      = row
            widgetColumn   = 1
            widgetSpanCols = 1
            widgetAlignment = PM_LABEL_LEFT_ALIGNMENT
            rowIncrement   = 1

            if labelColumn == 1:
                widgetColumn   = 0
                labelAlignment = PM_LABEL_LEFT_ALIGNMENT
                widgetAlignment = PM_LABEL_RIGHT_ALIGNMENT

        else:

            # This widget spans the full width of the groupbox
            if label:
                # The label and widget are on separate rows.
                # Set the label's row, column and alignment.
                labelRow       = row
                labelColumn    = 0
                labelSpanCols  = 2

                # Set this widget's row and column parameters.
                widgetRow      = row + 1 # Widget is below the label.
                widgetColumn   = 0
                widgetSpanCols = 2

                rowIncrement   = 2
            else:  # No label. Just the widget.
                labelRow       = 0
                labelColumn    = 0
                labelSpanCols  = 0

                # Set the widget's row and column parameters.
                widgetRow      = row
                widgetColumn   = 0
                widgetSpanCols = 2
                rowIncrement   = 1

            labelAlignment = PM_LABEL_LEFT_ALIGNMENT
            widgetAlignment = PM_LABEL_LEFT_ALIGNMENT

        return (widgetRow,
                widgetColumn,
                widgetSpanCols,
                widgetAlignment,
                rowIncrement,
                labelRow,
                labelColumn,
                labelSpanCols,
                labelAlignment)

    def addPmWidget(self, pmWidget):
        """
        Add a PM widget and its label to this group box.

        @param pmWidget: The PM widget to add.
        @type  pmWidget: PM_Widget
        """

        # Get all the widget and label layout parameters.
        widgetRow, \
        widgetColumn, \
        widgetSpanCols, \
        widgetAlignment, \
        rowIncrement, \
        labelRow, \
        labelColumn, \
        labelSpanCols, \
        labelAlignment = self.getPmWidgetPlacementParameters(pmWidget)


        if pmWidget.labelWidget:
            #Create Label as a pixmap (instead of text) if a valid icon path
            #is provided
            labelPath = str(pmWidget.label)
            if labelPath and labelPath.startswith("ui/"): #bruce 080325 revised
                labelPixmap = getpixmap(labelPath)
                if not labelPixmap.isNull():
                    pmWidget.labelWidget.setPixmap(labelPixmap)
                    pmWidget.labelWidget.setText('')

            self.gridLayout.addWidget( pmWidget.labelWidget,
                                       labelRow,
                                       labelColumn,
                                       1,
                                       labelSpanCols,
                                       labelAlignment )


        # The following is a workaround for a Qt bug. If addWidth()'s
        # <alignment> argument is not supplied, the widget spans the full
        # column width of the grid cell containing it. If <alignment>
        # is supplied, this desired behavior is lost and there is no
        # value that can be supplied to maintain the behavior (0 doesn't
        # work). The workaround is to call addWidget() without the <alignment>
        # argument. Mark 2007-07-27.
        if widgetAlignment == PM_LABEL_LEFT_ALIGNMENT:
            self.gridLayout.addWidget( pmWidget,
                                       widgetRow,
                                       widgetColumn,
                                       1,
                                       widgetSpanCols)
                                       # aligment = 0 doesn't work.
        else:
            self.gridLayout.addWidget( pmWidget,
                                       widgetRow,
                                       widgetColumn,
                                       1,
                                       widgetSpanCols,
                                       widgetAlignment)
        self._rowCount += rowIncrement
Ejemplo n.º 23
0
 def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
     QDialog.__init__(self, parent, flags)
     self.setModal(modal)
     self.setWindowTitle("Restore model into image")
     lo = QVBoxLayout(self)
     lo.setMargin(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.setMargin(5)
     self.wokbtn = QPushButton("OK", self)
     self.wokbtn.setMinimumWidth(128)
     QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
     self.wokbtn.setEnabled(False)
     cancelbtn = QPushButton("Cancel", self)
     cancelbtn.setMinimumWidth(128)
     QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
     lo2.addWidget(self.wokbtn)
     lo2.addStretch(1)
     lo2.addWidget(cancelbtn)
     self.setMinimumWidth(384)
     # signals
     QObject.connect(self.wfile_in, SIGNAL("filenameSelected"), self._fileSelected)
     QObject.connect(self.wfile_in, SIGNAL("filenameSelected"), self._inputFileSelected)
     QObject.connect(self.wfile_out, SIGNAL("filenameSelected"), self._fileSelected)
     QObject.connect(self.wfile_psf, SIGNAL("filenameSelected"), self._psfFileSelected)
     # internal state
     self.qerrmsg = QErrorMessage(self)
Ejemplo n.º 24
0
class ImageManager(QWidget):
    """An ImageManager manages a stack of images (and associated ImageControllers)"""

    def __init__(self, *args):
        QWidget.__init__(self, *args)
        # init layout
        self._lo = QVBoxLayout(self)
        self._lo.setContentsMargins(0, 0, 0, 0)
        self._lo.setSpacing(0)
        # init internal state
        self._currier = PersistentCurrier()
        self._z0 = 0;  # z-depth of first image, the rest count down from it
        self._updating_imap = False
        self._locked_display_range = False
        self._imagecons = []
        self._imagecon_loadorder = []
        self._center_image = None
        self._plot = None
        self._border_pen = None
        self._drawing_key = None
        self._load_image_dialog = None
        self._model_imagecons = set()
        # init menu and standard actions
        self._menu = QMenu("&Image", self)
        qag = QActionGroup(self)
        # exclusive controls for plotting topmost or all images
        self._qa_plot_top = qag.addAction("Display topmost image only")
        self._qa_plot_all = qag.addAction("Display all images")
        self._qa_plot_top.setCheckable(True)
        self._qa_plot_all.setCheckable(True)
        self._qa_plot_top.setChecked(True)
        QObject.connect(self._qa_plot_all, SIGNAL("toggled(bool)"), self._displayAllImages)
        self._closing = False

        self._qa_load_clipboard = None
        self._clipboard_mode = QClipboard.Clipboard
        QObject.connect(QApplication.clipboard(), SIGNAL("changed(QClipboard::Mode)"), self._checkClipboardPath)
        # populate the menu
        self._repopulateMenu()

    def close(self):
        dprint(1, "closing Manager")
        self._closing = True
        for ic in self._imagecons:
            ic.close()

    def loadImage(self, filename=None, duplicate=True, to_top=True, model=None):
        """Loads image. Returns ImageControlBar object.
        If image is already loaded: returns old ICB if duplicate=False (raises to top if to_top=True),
        or else makes a new control bar.
        If model is set to a source name, marks the image as associated with a model source. These can be unloaded en masse by calling
        unloadModelImages().
        """
        if filename is None:
            if not self._load_image_dialog:
                dialog = self._load_image_dialog = QFileDialog(self, "Load FITS image", ".",
                                                               "FITS images (%s);;All files (*)" % (" ".join(
                                                                   ["*" + ext for ext in FITS_ExtensionList])))
                dialog.setFileMode(QFileDialog.ExistingFile)
                dialog.setModal(True)
                QObject.connect(dialog, SIGNAL("filesSelected(const QStringList &)"), self.loadImage)
            self._load_image_dialog.exec_()
            return None
        if isinstance(filename, QStringList):
            filename = filename[0]
        filename = str(filename)
        # report error if image does not exist
        if not os.path.exists(filename):
            self.showErrorMessage("""FITS image %s does not exist.""" % filename)
            return None
        # see if image is already loaded
        if not duplicate:
            for ic in self._imagecons:
                if ic.getFilename() and os.path.samefile(filename, ic.getFilename()):
                    if to_top:
                        self.raiseImage(ic)
                    if model:
                        self._model_imagecons.add(id(ic))
                    return ic
        # load the FITS image
        busy = BusyIndicator()
        dprint(2, "reading FITS image", filename)
        self.showMessage("""Reading FITS image %s""" % filename, 3000)
        QApplication.flush()
        try:
            image = SkyImage.FITSImagePlotItem(str(filename))
        except KeyboardInterrupt:
            raise
        except:
            busy = None
            traceback.print_exc()
            self.showErrorMessage("""<P>Error loading FITS image %s: %s. This may be due to a bug in Tigger; if the FITS file loads fine in another viewer,
          please send the FITS file, along with a copy of any error messages from the text console, to [email protected].</P>""" % (
            filename, str(sys.exc_info()[1])))
            return None
        # create control bar, add to widget stack
        ic = self._createImageController(image, "model source '%s'" % model if model else filename, model or image.name,
                                         model=model)
        self.showMessage("""Loaded FITS image %s""" % filename, 3000)
        dprint(2, "image loaded")
        return ic

    def showMessage(self, message, time=None):
        self.emit(SIGNAL("showMessage"), message, time)

    def showErrorMessage(self, message, time=None):
        self.emit(SIGNAL("showErrorMessage"), message, time)

    def setZ0(self, z0):
        self._z0 = z0
        if self._imagecons:
            self.raiseImage(self._imagecons[0])

    def enableImageBorders(self, border_pen, label_color, label_bg_brush):
        self._border_pen, self._label_color, self._label_bg_brush = \
            border_pen, label_color, label_bg_brush

    def lockAllDisplayRanges(self, rc0):
        """Locks all display ranges, and sets the intensity from rc0"""
        if not self._updating_imap:
            self._updating_imap = True
            rc0.lockDisplayRange()
            try:
                for ic in self._imagecons:
                    rc1 = ic.renderControl()
                    if rc1 is not rc0:
                        rc1.setDisplayRange(*rc0.displayRange())
                        rc1.lockDisplayRange()
            finally:
                self._updating_imap = False

    def unlockAllDisplayRanges(self):
        """Unlocks all display range."""
        for ic in self._imagecons:
            ic.renderControl().lockDisplayRange(False)

    def _lockDisplayRange(self, rc0, lock):
        """Locks or unlocks the display range of a specific controller."""
        if lock and not self._updating_imap:
            self._updating_imap = True
            try:
                # if something is already locked, copy display range from it
                for ic in self._imagecons:
                    rc1 = ic.renderControl()
                    if rc1 is not rc0 and rc1.isDisplayRangeLocked():
                        rc0.setDisplayRange(*rc1.displayRange())
            finally:
                self._updating_imap = False

    def _updateDisplayRange(self, rc, dmin, dmax):
        """This is called whenever one of the images (or rather, its associated RenderControl object) changes its display range."""
        if not rc.isDisplayRangeLocked():
            return
        # If the display range is locked, propagate it to all images.
        # but don't do it if we're already propagating (otherwise we may get called in an infinte loop)
        if not self._updating_imap:
            self._updating_imap = True
            try:
                for ic in self._imagecons:
                    rc1 = ic.renderControl()
                    if rc1 is not rc and rc1.isDisplayRangeLocked():
                        rc1.setDisplayRange(dmin, dmax)
            finally:
                self._updating_imap = False

    def getImages(self):
        return [ic.image for ic in self._imagecons]

    def getTopImage(self):
        return (self._imagecons or None) and self._imagecons[0].image

    def cycleImages(self):
        index = self._imagecon_loadorder.index(self._imagecons[0])
        index = (index + 1) % len(self._imagecon_loadorder)
        self.raiseImage(self._imagecon_loadorder[index])

    def blinkImages(self):
        if len(self._imagecons) > 1:
            self.raiseImage(self._imagecons[1])

    def incrementSlice(self, extra_axis, incr):
        if self._imagecons:
            rc = self._imagecons[0].renderControl()
            sliced_axes = rc.slicedAxes()
            if extra_axis < len(sliced_axes):
                rc.incrementSlice(sliced_axes[extra_axis][0], incr)

    def setLMRectSubset(self, rect):
        if self._imagecons:
            self._imagecons[0].setLMRectSubset(rect)

    def getLMRectStats(self, rect):
        if self._imagecons:
            return self._imagecons[0].renderControl().getLMRectStats(rect)

    def unloadModelImages(self):
        """Unloads images associated with model (i.e. loaded with the model=True flag)"""
        for ic in [ic for ic in self._imagecons if id(ic) in self._model_imagecons]:
            self.unloadImage(ic)

    def unloadImage(self, imagecon):
        """Unloads the given imagecon object."""
        if imagecon not in self._imagecons:
            return
        # recenter if needed
        self._imagecons.remove(imagecon)
        self._imagecon_loadorder.remove(imagecon)
        self._model_imagecons.discard(id(imagecon))
        # reparent widget and release it
        imagecon.setParent(None)
        imagecon.close()
        # recenter image, if unloaded the center image
        if self._center_image is imagecon.image:
            self.centerImage(self._imagecons[0] if self._imagecons else None, emit=False)
        # emit signal
        self._repopulateMenu()
        self.emit(SIGNAL("imagesChanged"))
        if self._imagecons:
            self.raiseImage(self._imagecons[0])

    def getCenterImage(self):
        return self._center_image

    def centerImage(self, imagecon, emit=True):
        self._center_image = imagecon and imagecon.image
        for ic in self._imagecons:
            ic.setPlotProjection(self._center_image.projection)
        if emit:
            self.emit(SIGNAL("imagesChanged"))

    def raiseImage(self, imagecon):
        # reshuffle image stack, if more than one image image
        if len(self._imagecons) > 1:
            busy = BusyIndicator()
            # reshuffle image stack
            self._imagecons.remove(imagecon)
            self._imagecons.insert(0, imagecon)
            # notify imagecons
            for i, ic in enumerate(self._imagecons):
                label = "%d" % (i + 1) if i else "<B>1</B>"
                ic.setZ(self._z0 - i * 10, top=not i, depthlabel=label, can_raise=True)
            # adjust visibility
            for j, ic in enumerate(self._imagecons):
                ic.setImageVisible(not j or bool(self._qa_plot_all.isChecked()))
            # issue replot signal
            self.emit(SIGNAL("imageRaised"))
            self.fastReplot()
        # else simply update labels
        else:
            self._imagecons[0].setZ(self._z0, top=True, depthlabel=None, can_raise=False)
            self._imagecons[0].setImageVisible(True)
        # update slice menus
        img = imagecon.image
        axes = imagecon.renderControl().slicedAxes()
        for i, (next, prev) in enumerate(self._qa_slices):
            next.setVisible(False)
            prev.setVisible(False)
            if i < len(axes):
                iaxis, name, labels = axes[i]
                next.setVisible(True)
                prev.setVisible(True)
                next.setText("Show next slice along %s axis" % name)
                prev.setText("Show previous slice along %s axis" % name)
        # emit signasl
        self.emit(SIGNAL("imageRaised"), img)

    def resetDrawKey(self):
        """Makes and sets the current plot's drawing key"""
        if self._plot:
            key = []
            for ic in self._imagecons:
                key.append(id(ic))
                key += ic.currentSlice()
                self._plot.setDrawingKey(tuple(key))

    def fastReplot(self, *dum):
        """Fast replot -- called when flipping images or slices. Uses the plot cache, if possible."""
        if self._plot:
            self.resetDrawKey()
            dprint(2, "calling replot", time.time() % 60)
            self._plot.replot()
            dprint(2, "replot done", time.time() % 60)

    def replot(self, *dum):
        """Proper replot -- called when an image needs to be properly redrawn. Cleares the plot's drawing cache."""
        if self._plot:
            self._plot.clearDrawCache()
            self.resetDrawKey()
            self._plot.replot()

    def attachImagesToPlot(self, plot):
        self._plot = plot
        self.resetDrawKey()
        for ic in self._imagecons:
            ic.attachToPlot(plot)

    def getMenu(self):
        return self._menu

    def _displayAllImages(self, enabled):
        busy = BusyIndicator()
        if enabled:
            for ic in self._imagecons:
                ic.setImageVisible(True)
        else:
            self._imagecons[0].setImageVisible(True)
            for ic in self._imagecons[1:]:
                ic.setImageVisible(False)
        self.replot()

    def _checkClipboardPath(self, mode=QClipboard.Clipboard):
        if self._qa_load_clipboard:
            self._clipboard_mode = mode
            try:
                path = str(QApplication.clipboard().text(mode))
            except:
                path = None
            self._qa_load_clipboard.setEnabled(bool(path and os.path.isfile(path)))

    def _loadClipboardPath(self):
        try:
            path = QApplication.clipboard().text(self._clipboard_mode)
        except:
            return
        self.loadImage(path)

    def _repopulateMenu(self):
        self._menu.clear()
        self._menu.addAction("&Load image...", self.loadImage, Qt.CTRL + Qt.Key_L)
        self._menu.addAction("&Compute image...", self.computeImage, Qt.CTRL + Qt.Key_M)
        self._qa_load_clipboard = self._menu.addAction("Load from clipboard &path", self._loadClipboardPath,
                                                       Qt.CTRL + Qt.Key_P)
        self._checkClipboardPath()
        if self._imagecons:
            self._menu.addSeparator()
            # add controls to cycle images and planes
            for i, imgcon in enumerate(self._imagecons[::-1]):
                self._menu.addMenu(imgcon.getMenu())
            self._menu.addSeparator()
            if len(self._imagecons) > 1:
                self._menu.addAction("Cycle images", self.cycleImages, Qt.Key_F5)
                self._menu.addAction("Blink images", self.blinkImages, Qt.Key_F6)
            self._qa_slices = (
            (self._menu.addAction("Next slice along axis 1", self._currier.curry(self.incrementSlice, 0, 1), Qt.Key_F7),
             self._menu.addAction("Previous slice along axis 1", self._currier.curry(self.incrementSlice, 0, -1),
                                  Qt.SHIFT + Qt.Key_F7)),
            (self._menu.addAction("Next slice along axis 2", self._currier.curry(self.incrementSlice, 1, 1), Qt.Key_F8),
             self._menu.addAction("Previous slice along axis 2", self._currier.curry(self.incrementSlice, 1, -1),
                                  Qt.SHIFT + Qt.Key_F8)))
            self._menu.addSeparator()
            self._menu.addAction(self._qa_plot_top)
            self._menu.addAction(self._qa_plot_all)

    def computeImage(self, expression=None):
        """Computes image from expression (if expression is None, pops up dialog)"""
        if expression is None:
            (expression, ok) = QInputDialog.getText(self, "Compute image",
                                                    """Enter an image expression to compute.
                                              Any valid numpy expression is supported, and
                                              all functions from the numpy module are available (including sub-modules such as fft).
                                              Use 'a', 'b', 'c' to refer to images.
                                              Examples:  "(a+b)/2", "cos(a)+sin(b)", "a-a.mean()", "fft.fft2(a)", etc.""")
            #      (expression,ok) = QInputDialog.getText(self,"Compute image","""<P>Enter an expression to compute.
            #        Use 'a', 'b', etc. to refer to loaded images. Any valid numpy expression is supported, and all the
            #       functions from the numpy module are available. Examples of valid expressions include "(a+b)/2",
            #       "cos(a)+sin(b)", "a-a.mean()", etc.
            #        </P>
            #      """)
            expression = str(expression)
            if not ok or not expression:
                return
        # try to parse expression
        arglist = [(chr(ord('a') + ic.getNumber()), ic.image) for ic in self._imagecons]
        try:
            exprfunc = eval("lambda " + (",".join([x[0] for x in arglist])) + ":" + expression,
                            numpy.__dict__, {})
        except Exception as exc:
            self.showErrorMessage("""Error parsing expression "%s": %s.""" % (expression, str(exc)))
            return None
        # try to evaluate expression
        self.showMessage("Computing expression \"%s\"" % expression, 10000)
        busy = BusyIndicator()
        QApplication.flush()

        # trim trivial trailing dimensions. This avoids the problem of when an NxMx1 and an NxMx1x1 arrays are added,
        # the result is promoted to NxMxMx1 following the numpy rules.
        def trimshape(shape):
            out = shape
            while out and out[-1] == 1:
                out = out[:-1]
            return out

        def trimarray(array):
            return array.reshape(trimshape(array.shape))

        try:
            result = exprfunc(*[trimarray(x[1].data()) for x in arglist])
        except Exception as exc:
            busy = None
            traceback.print_exc()
            self.showErrorMessage("""Error evaluating "%s": %s.""" % (expression, str(exc)))
            return None
        busy = None
        if type(result) != numpy.ma.masked_array and type(result) != numpy.ndarray:
            self.showErrorMessage(
                """Result of "%s" is of invalid type "%s" (array expected).""" % (expression, type(result).__name__))
            return None
        # convert coomplex results to real
        if numpy.iscomplexobj(result):
            self.showErrorMessage("""Result of "%s" is complex. Complex images are currently
      not fully supported, so we'll implicitly use the absolute value instead.""" % (expression))
            expression = "abs(%s)" % expression
            result = abs(result)
        # determine which image this expression can be associated with
        res_shape = trimshape(result.shape)
        arglist = [x for x in arglist if hasattr(x[1], 'fits_header') and trimshape(x[1].data().shape) == res_shape]
        if not arglist:
            self.showErrorMessage("""Result of "%s" has shape %s, which does not match any loaded FITS image.""" % (
            expression, "x".join(map(str, result.shape))))
            return None
        # look for an image in the arglist with the same projection, and with a valid dirname
        # (for the where-to-save hint)
        template = arglist[0][1]
        # if all images in arglist have the same projection, then it doesn't matter what we use
        # else ask
        if len([x for x in arglist[1:] if x[1].projection == template.projection]) != len(arglist) - 1:
            options = [x[0] for x in arglist]
            (which, ok) = QInputDialog.getItem(self, "Compute image",
                                               "Coordinate system to use for the result of \"%s\":" % expression,
                                               options, 0, False)
            if not ok:
                return None
            try:
                template = arglist[options.index(which)][1]
            except:
                pass
        # create a FITS image
        busy = BusyIndicator()
        dprint(2, "creating FITS image", expression)
        self.showMessage("""Creating image for %s""" % expression, 3000)
        QApplication.flush()
        try:
            hdu = pyfits.PrimaryHDU(result.transpose(), template.fits_header)
            skyimage = SkyImage.FITSImagePlotItem(name=expression, filename=None, hdu=hdu)
        except:
            busy = None
            traceback.print_exc()
            self.showErrorMessage("""Error creating FITS image %s: %s""" % (expression, str(sys.exc_info()[1])))
            return None
        # get directory name for save-to hint
        dirname = getattr(template, 'filename', None)
        if not dirname:
            dirnames = [getattr(img, 'filename') for x, img in arglist if hasattr(img, 'filename')]
            dirname = dirnames[0] if dirnames else None
        # create control bar, add to widget stack
        self._createImageController(skyimage, expression, expression,
                                    save=((dirname and os.path.dirname(dirname)) or "."))
        self.showMessage("Created new image for %s" % expression, 3000)
        dprint(2, "image created")

    def _createImageController(self, image, name, basename, model=False, save=False):
        dprint(2, "creating ImageController for", name)
        ic = ImageController(image, self, self, name, save=save)
        ic.setNumber(len(self._imagecons))
        self._imagecons.insert(0, ic)
        self._imagecon_loadorder.append(ic)
        if model:
            self._model_imagecons.add(id(ic))
        self._lo.addWidget(ic)
        if self._border_pen:
            ic.addPlotBorder(self._border_pen, basename, self._label_color, self._label_bg_brush)
        # attach appropriate signals
        image.connect(SIGNAL("slice"), self.fastReplot)
        image.connect(SIGNAL("repaint"), self.replot)
        image.connect(SIGNAL("raise"), self._currier.curry(self.raiseImage, ic))
        image.connect(SIGNAL("unload"), self._currier.curry(self.unloadImage, ic))
        image.connect(SIGNAL("center"), self._currier.curry(self.centerImage, ic))
        QObject.connect(ic.renderControl(), SIGNAL("displayRangeChanged"),
                        self._currier.curry(self._updateDisplayRange, ic.renderControl()))
        QObject.connect(ic.renderControl(), SIGNAL("displayRangeLocked"),
                        self._currier.curry(self._lockDisplayRange, ic.renderControl()))
        self._plot = None
        # add to menus
        dprint(2, "repopulating menus")
        self._repopulateMenu()
        # center and raise to top of stack
        self.raiseImage(ic)
        if not self._center_image:
            self.centerImage(ic, emit=False)
        else:
            ic.setPlotProjection(self._center_image.projection)
        # signal
        self.emit(SIGNAL("imagesChanged"))
        return ic
Ejemplo n.º 25
0
class PM_DockWidget(QDockWidget):
    """
    PM_DockWidget class provides a dockable widget that can either be docked 
    inside a PropertyManager OR can be docked in the MainWindow depending 
    on the <parentWidget> . see DnaSequenceEditor.py for an example.
    The dockWidget has its own layout and containerwidget which makes it easy 
    to add various children widgets  similar to how its done in PM_GroupBox    
    """   
        
    
    def __init__(self, parentWidget, title = "", showWidget = True):
        """
        Constructor for PM_dockWidget
        
        @param showWidget: If true, this class will show the widget 
        immediately in the __init__ method itself. This is the default behavior 
        and subclasses may pass appropriate value to this flag
        @type showWidget: bool
        """
        self.labelWidget  = None    
        self._title         = ""
        self._widgetList    = []
        self._rowCount      = 0
    
        QDockWidget.__init__(self, parentWidget)
        
        self.parentWidget = parentWidget        
        
        self._title = title
        
        self.label = ''
        self.labelColumn = 0
        self.spanWidth = True
        self.labelWidget = None
        
        if self.label: # Create this widget's QLabel.
            self.labelWidget = QLabel()
            self.labelWidget.setText(self.label)
            
        self.setEnabled(True) 
        self.setFloating(False)
        self.setVisible(showWidget)
        self.setWindowTitle(self._title)
        self.setAutoFillBackground(True)
        self.setPalette(getPalette( None,
                                    QPalette.Window,
                                    pmGrpBoxColor))

        self.parentWidget.addDockWidget(Qt.BottomDockWidgetArea, self)
        
        #Define layout
        self._containerWidget = QWidget()
        self.setWidget(self._containerWidget)        
        
        # Create vertical box layout
        self.vBoxLayout = QVBoxLayout(self._containerWidget)
        self.vBoxLayout.setMargin(1)
        self.vBoxLayout.setSpacing(0)
        
        # Create grid layout
        self.gridLayout = QGridLayout()
        self.gridLayout.setMargin(1)
        self.gridLayout.setSpacing(1)
        
        # Insert grid layout in its own vBoxLayout
        self.vBoxLayout.addLayout(self.gridLayout)
                         
        #self.parentWidget.addPmWidget(self)
        self._loadWidgets()
        
        try:
            self._addWhatsThisText()
        except:
            print_compact_traceback("Error loading whatsthis text for this " \
                                    "property manager dock widget.")
        
        try:
            self._addToolTipText()
        except:
            print_compact_traceback("Error loading tool tip text for this " \
                                    "property manager dock widget.")
    
    def _loadWidgets(self):
        """
        Subclasses should override this method. Default implementation does 
        nothing. 
        @see: DnaSequenceEditor._loadWidgets 
        """
        pass
    
    def _addWhatsThisText(self):
        """
        Add 'What's This' help text for self and child widgets. 
        Subclasses should override this method. 
        """
        pass
    
    def _addToolTipText(self):
        """
        Add 'Tool tip' help text for self and child widgets. 
        Subclasses should override this method. 
        """
        pass
    
    def getPmWidgetPlacementParameters(self, pmWidget):
        """
        NOTE: This method is duplicated from PM_GroupBox
        
        Returns all the layout parameters needed to place 
        a PM_Widget in the group box grid layout.
        
        @param pmWidget: The PM widget.
        @type  pmWidget: PM_Widget
        """        
        row = self._rowCount
        
        #PM_CheckBox doesn't have a label. So do the following to decide the 
        #placement of the checkbox. (can be placed either in column 0 or 1 , 
        #This also needs to be implemented for PM_RadioButton, but at present 
        #the following code doesn't support PM_RadioButton. 
        if isinstance(pmWidget, PM_CheckBox):
            # Set the widget's row and column parameters.
            widgetRow      = row
            widgetColumn   = pmWidget.widgetColumn
            widgetSpanCols = 1
            widgetAlignment = PM_LABEL_LEFT_ALIGNMENT
            rowIncrement   = 1
            #set a virtual label
            labelRow       = row
            labelSpanCols  = 1
            labelAlignment = PM_LABEL_RIGHT_ALIGNMENT
                        
            if widgetColumn == 0:
                labelColumn   = 1                              
            elif widgetColumn == 1:
                labelColumn   = 0
            
            return (widgetRow, 
                    widgetColumn, 
                    widgetSpanCols, 
                    widgetAlignment,
                    rowIncrement, 
                    labelRow, 
                    labelColumn, 
                    labelSpanCols, 
                    labelAlignment)
        
       
        label       = pmWidget.label            
        labelColumn = pmWidget.labelColumn
        spanWidth   = pmWidget.spanWidth
        
        if not spanWidth: 
            # This widget and its label are on the same row
            labelRow       = row
            labelSpanCols  = 1
            labelAlignment = PM_LABEL_RIGHT_ALIGNMENT
            # Set the widget's row and column parameters.
            widgetRow      = row
            widgetColumn   = 1
            widgetSpanCols = 1
            widgetAlignment = PM_LABEL_LEFT_ALIGNMENT
            rowIncrement   = 1
            
            if labelColumn == 1:
                widgetColumn   = 0
                labelAlignment = PM_LABEL_LEFT_ALIGNMENT
                widgetAlignment = PM_LABEL_RIGHT_ALIGNMENT
                        
        else: 
                      
            # This widget spans the full width of the groupbox           
            if label: 
                # The label and widget are on separate rows.
                # Set the label's row, column and alignment.
                labelRow       = row
                labelColumn    = 0
                labelSpanCols  = 2
                    
                # Set this widget's row and column parameters.
                widgetRow      = row + 1 # Widget is below the label.
                widgetColumn   = 0
                widgetSpanCols = 2
                
                rowIncrement   = 2
            else:  # No label. Just the widget.
                labelRow       = 0
                labelColumn    = 0
                labelSpanCols  = 0

                # Set the widget's row and column parameters.
                widgetRow      = row
                widgetColumn   = 0
                widgetSpanCols = 2
                rowIncrement   = 1
                
            labelAlignment = PM_LABEL_LEFT_ALIGNMENT
            widgetAlignment = PM_LABEL_LEFT_ALIGNMENT
                
        return (widgetRow, 
                widgetColumn, 
                widgetSpanCols, 
                widgetAlignment,
                rowIncrement, 
                labelRow, 
                labelColumn, 
                labelSpanCols, 
                labelAlignment)
        
    def addPmWidget(self, pmWidget):
        """
        Add a PM widget and its label to this group box.
        
        @param pmWidget: The PM widget to add.
        @type  pmWidget: PM_Widget
        """
        
        # Get all the widget and label layout parameters.
        widgetRow, \
        widgetColumn, \
        widgetSpanCols, \
        widgetAlignment, \
        rowIncrement, \
        labelRow, \
        labelColumn, \
        labelSpanCols, \
        labelAlignment = self.getPmWidgetPlacementParameters(pmWidget)        
        
        
        if pmWidget.labelWidget: 
            #Create Label as a pixmap (instead of text) if a valid icon path 
            #is provided
            labelPath = str(pmWidget.label)
            if labelPath and labelPath.startswith("ui/"): #bruce 080325 revised
                labelPixmap = getpixmap(labelPath)
                if not labelPixmap.isNull():
                    pmWidget.labelWidget.setPixmap(labelPixmap)
                    pmWidget.labelWidget.setText('')
               
            self.gridLayout.addWidget( pmWidget.labelWidget,
                                       labelRow, 
                                       labelColumn,
                                       1, 
                                       labelSpanCols,
                                       labelAlignment )
            
        
        # The following is a workaround for a Qt bug. If addWidth()'s 
        # <alignment> argument is not supplied, the widget spans the full 
        # column width of the grid cell containing it. If <alignment> 
        # is supplied, this desired behavior is lost and there is no 
        # value that can be supplied to maintain the behavior (0 doesn't 
        # work). The workaround is to call addWidget() without the <alignment>
        # argument. Mark 2007-07-27.
        if widgetAlignment == PM_LABEL_LEFT_ALIGNMENT:
            self.gridLayout.addWidget( pmWidget,
                                       widgetRow, 
                                       widgetColumn,
                                       1, 
                                       widgetSpanCols) 
                                       # aligment = 0 doesn't work.
        else:
            self.gridLayout.addWidget( pmWidget,
                                       widgetRow, 
                                       widgetColumn,
                                       1, 
                                       widgetSpanCols,
                                       widgetAlignment)        
        self._rowCount += rowIncrement
Ejemplo n.º 26
0
 def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
     QDialog.__init__(self, parent, flags)
     self.setModal(modal)
     self.setWindowTitle("Convert sources to FITS brick")
     lo = QVBoxLayout(self)
     lo.setMargin(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())
     QObject.connect(self.wadd, SIGNAL("toggled(bool)"), self.wpad.setEnabled)
     QObject.connect(self.wadd, SIGNAL("toggled(bool)"), 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(0, 0, 0, 0)
     lo2.setMargin(5)
     self.wokbtn = QPushButton("OK", self)
     self.wokbtn.setMinimumWidth(128)
     QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
     self.wokbtn.setEnabled(False)
     cancelbtn = QPushButton("Cancel", self)
     cancelbtn.setMinimumWidth(128)
     QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
     lo2.addWidget(self.wokbtn)
     lo2.addStretch(1)
     lo2.addWidget(cancelbtn)
     self.setMinimumWidth(384)
     # signals
     QObject.connect(self.wfile, SIGNAL("filenameSelected"), self._fileSelected)
     # internal state
     self.qerrmsg = QErrorMessage(self)
Ejemplo n.º 27
0
    def __init__(self, parent, hide_on_close=False):
        QMainWindow.__init__(self, parent)
        self._hide_on_close = hide_on_close
        # replace the BusyIndicator class with a GUI-aware one
        Purr.BusyIndicator = BusyIndicator
        self._pounce = False
        # we keep a small stack of previously active purrers. This makes directory changes
        # faster (when going back and forth between dirs)
        # current purrer
        self.purrer = None
        self.purrer_stack = []
        # Purr pipes for receiving remote commands
        self.purrpipes = {}
        # init GUI
        self.setWindowTitle("PURR")
        self.setWindowIcon(pixmaps.purr_logo.icon())
        cw = QWidget(self)
        self.setCentralWidget(cw)
        cwlo = QVBoxLayout(cw)
        cwlo.setContentsMargins(0, 0, 0, 0)
        cwlo.setMargin(5)
        cwlo.setSpacing(0)
        toplo = QHBoxLayout()
        cwlo.addLayout(toplo)

        # About dialog
        self._about_dialog = QMessageBox(self)
        self._about_dialog.setWindowTitle("About PURR")
        self._about_dialog.setText(self.about_message + """
        <P>PURR is not watching any directories right now. You may need to restart it, and give it
  some directory names on the command line.</P>""")
        self._about_dialog.setIconPixmap(pixmaps.purr_logo.pm())
        # Log viewer dialog
        self.viewer_dialog = HTMLViewerDialog(
            self,
            config_name="log-viewer",
            buttons=
            [(pixmaps.blue_round_reload, "Regenerate",
              """<P>Regenerates your log's HTML code from scratch. This can be useful if
                                                        your PURR version has changed, or if there was an error of some kind
                                                        the last time the files were generated.</P>
                                                        """)])
        self._viewer_timestamp = None
        self.connect(self.viewer_dialog, SIGNAL("Regenerate"),
                     self._regenerateLog)
        self.connect(self.viewer_dialog, SIGNAL("viewPath"), self._viewPath)

        # Log title toolbar
        title_tb = QToolBar(cw)
        title_tb.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        title_tb.setIconSize(QSize(16, 16))
        cwlo.addWidget(title_tb)
        title_label = QLabel("Purrlog title:", title_tb)
        title_tb.addWidget(title_label)
        self.title_editor = QLineEdit(title_tb)
        title_tb.addWidget(self.title_editor)
        self.connect(self.title_editor, SIGNAL("editingFinished()"),
                     self._titleChanged)
        tip = """<P>This is your current log title. To rename the log, enter new name here and press Enter.</P>"""
        title_label.setToolTip(tip)
        self.title_editor.setToolTip(tip)
        self.wviewlog = title_tb.addAction(pixmaps.openbook.icon(), "View",
                                           self._showViewerDialog)
        self.wviewlog.setToolTip(
            "Click to see an HTML rendering of your current log.")
        qa = title_tb.addAction(pixmaps.purr_logo.icon(), "About...",
                                self._about_dialog.exec_)
        qa.setToolTip(
            "<P>Click to see the About... dialog, which will tell you something about PURR.</P>"
        )

        self.wdirframe = QFrame(cw)
        cwlo.addWidget(self.wdirframe)
        self.dirs_lo = QVBoxLayout(self.wdirframe)
        self.dirs_lo.setMargin(5)
        self.dirs_lo.setContentsMargins(5, 0, 5, 5)
        self.dirs_lo.setSpacing(0)
        self.wdirframe.setFrameStyle(QFrame.Box | QFrame.Raised)
        self.wdirframe.setLineWidth(1)

        ## Directories toolbar
        dirs_tb = QToolBar(self.wdirframe)
        dirs_tb.setToolButtonStyle(Qt.ToolButtonIconOnly)
        dirs_tb.setIconSize(QSize(16, 16))
        self.dirs_lo.addWidget(dirs_tb)
        label = QLabel("Monitoring directories:", dirs_tb)
        self._dirs_tip = """<P>PURR can monitor your working directories for new or updated files. If there's a checkmark
      next to the directory name in this list, PURR is monitoring it.</P>

      <P>If the checkmark is grey, PURR is monitoring things unobtrusively. When a new or updated file is detected in he monitored directory,
      it is quietly added to the list of files in the "New entry" window, even if this window is not currently visible.</P>

      <P>If the checkmark is black, PURR will be more obtrusive. Whenever a new or updated file is detected, the "New entry" window will
      pop up automatically. This is called "pouncing", and some people find it annoying.</P>
      """
        label.setToolTip(self._dirs_tip)
        label.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
        dirs_tb.addWidget(label)

        # add directory list widget
        self.wdirlist = DirectoryListWidget(self.wdirframe)
        self.wdirlist.setToolTip(self._dirs_tip)
        QObject.connect(self.wdirlist, SIGNAL("directoryStateChanged"),
                        self._changeWatchedDirState)
        self.dirs_lo.addWidget(self.wdirlist)
        # self.wdirlist.setMaximumSize(1000000,64)

        # add directory button
        add = dirs_tb.addAction(pixmaps.list_add.icon(), "Add",
                                self._showAddDirectoryDialog)
        add.setToolTip(
            "<P>Click to add another directory to be monitored.</P>")

        # remove directory button
        delbtn = dirs_tb.addAction(pixmaps.list_remove.icon(), "Remove",
                                   self.wdirlist.removeCurrent)
        delbtn.setEnabled(False)
        delbtn.setToolTip(
            "<P>Click to removed the currently selected directory from the list.</P>"
        )
        QObject.connect(self.wdirlist, SIGNAL("hasSelection"),
                        delbtn.setEnabled)

        #    # qa = dirs_tb.addAction(pixmaps.blue_round_reload.icon(),"Rescan",self._forceRescan)
        #    # qa.setToolTip("Click to rescan the directories for any new or updated files.")
        #    self.wshownew = QCheckBox("show new files",dirs_tb)
        #    dirs_tb.addWidget(self.wshownew)
        #    self.wshownew.setCheckState(Qt.Checked)
        #    self.wshownew.setToolTip("""<P>If this is checked, the "New entry" window will pop up automatically whenever
        #  new or updated files are detected. If this is unchecked, the files will be added to the window quietly
        #        and unobtrusively; you can show the window manually by clicking on the "New entry..." button below.</P>""")
        #    self._dir_entries = {}

        cwlo.addSpacing(5)

        wlogframe = QFrame(cw)
        cwlo.addWidget(wlogframe)
        log_lo = QVBoxLayout(wlogframe)
        log_lo.setMargin(5)
        log_lo.setContentsMargins(5, 5, 5, 5)
        log_lo.setSpacing(0)
        wlogframe.setFrameStyle(QFrame.Box | QFrame.Raised)
        wlogframe.setLineWidth(1)

        # listview of log entries
        self.etw = LogEntryTree(cw)
        log_lo.addWidget(self.etw, 1)
        self.etw.header().setDefaultSectionSize(128)
        self.etw.header().setMovable(False)
        self.etw.setHeaderLabels(["date", "entry title", "comment"])
        if hasattr(QHeaderView, 'ResizeToContents'):
            self.etw.header().setResizeMode(0, QHeaderView.ResizeToContents)
        else:
            self.etw.header().setResizeMode(0, QHeaderView.Custom)
            self.etw.header().resizeSection(0, 120)
        self.etw.header().setResizeMode(1, QHeaderView.Interactive)
        self.etw.header().setResizeMode(2, QHeaderView.Stretch)
        self.etw.header().show()
        try:
            self.etw.setAllColumnsShowFocus(True)
        except AttributeError:
            pass
            # Qt 4.2+
        # self.etw.setShowToolTips(True)
        self.etw.setSortingEnabled(False)
        # self.etw.setColumnAlignment(2,Qt.AlignLeft|Qt.AlignTop)
        self.etw.setSelectionMode(QTreeWidget.ExtendedSelection)
        self.etw.setRootIsDecorated(True)
        self.connect(self.etw, SIGNAL("itemSelectionChanged()"),
                     self._entrySelectionChanged)
        self.connect(self.etw, SIGNAL("itemActivated(QTreeWidgetItem*,int)"),
                     self._viewEntryItem)
        self.connect(self.etw, SIGNAL("itemContextMenuRequested"),
                     self._showItemContextMenu)
        # create popup menu for data products
        self._archived_dp_menu = menu = QMenu(self)
        self._archived_dp_menu_title = QLabel()
        self._archived_dp_menu_title.setMargin(5)
        self._archived_dp_menu_title_wa = wa = QWidgetAction(self)
        wa.setDefaultWidget(self._archived_dp_menu_title)
        menu.addAction(wa)
        menu.addSeparator()
        menu.addAction(pixmaps.editcopy.icon(),
                       "Restore file(s) from archived copy",
                       self._restoreItemFromArchive)
        menu.addAction(pixmaps.editpaste.icon(),
                       "Copy pathname of archived copy to clipboard",
                       self._copyItemToClipboard)
        self._current_item = None
        # create popup menu for entries
        self._entry_menu = menu = QMenu(self)
        self._entry_menu_title = QLabel()
        self._entry_menu_title.setMargin(5)
        self._entry_menu_title_wa = wa = QWidgetAction(self)
        wa.setDefaultWidget(self._entry_menu_title)
        menu.addAction(wa)
        menu.addSeparator()
        menu.addAction(pixmaps.filefind.icon(), "View this log entry",
                       self._viewEntryItem)
        menu.addAction(pixmaps.editdelete.icon(), "Delete this log entry",
                       self._deleteSelectedEntries)
        # buttons at bottom
        log_lo.addSpacing(5)
        btnlo = QHBoxLayout()
        log_lo.addLayout(btnlo)
        self.wnewbtn = QPushButton(pixmaps.filenew.icon(), "New entry...", cw)
        self.wnewbtn.setToolTip("Click to add a new log entry.")
        # self.wnewbtn.setFlat(True)
        self.wnewbtn.setEnabled(False)
        btnlo.addWidget(self.wnewbtn)
        btnlo.addSpacing(5)
        self.weditbtn = QPushButton(pixmaps.filefind.icon(), "View entry...",
                                    cw)
        self.weditbtn.setToolTip(
            "Click to view or edit the selected log entry/")
        # self.weditbtn.setFlat(True)
        self.weditbtn.setEnabled(False)
        self.connect(self.weditbtn, SIGNAL("clicked()"), self._viewEntryItem)
        btnlo.addWidget(self.weditbtn)
        btnlo.addSpacing(5)
        self.wdelbtn = QPushButton(pixmaps.editdelete.icon(), "Delete", cw)
        self.wdelbtn.setToolTip(
            "Click to delete the selected log entry or entries.")
        # self.wdelbtn.setFlat(True)
        self.wdelbtn.setEnabled(False)
        self.connect(self.wdelbtn, SIGNAL("clicked()"),
                     self._deleteSelectedEntries)
        btnlo.addWidget(self.wdelbtn)
        # enable status line
        self.statusBar().show()
        Purr.progressMessage = self.message
        self._prev_msg = None
        # editor dialog for new entry
        self.new_entry_dialog = Purr.Editors.NewLogEntryDialog(self)
        self.connect(self.new_entry_dialog, SIGNAL("newLogEntry"),
                     self._newLogEntry)
        self.connect(self.new_entry_dialog, SIGNAL("filesSelected"),
                     self._addDPFiles)
        self.connect(self.wnewbtn, SIGNAL("clicked()"),
                     self.new_entry_dialog.show)
        self.connect(self.new_entry_dialog, SIGNAL("shown"),
                     self._checkPounceStatus)
        # entry viewer dialog
        self.view_entry_dialog = Purr.Editors.ExistingLogEntryDialog(self)
        self.connect(self.view_entry_dialog, SIGNAL("previous()"),
                     self._viewPrevEntry)
        self.connect(self.view_entry_dialog, SIGNAL("next()"),
                     self._viewNextEntry)
        self.connect(self.view_entry_dialog, SIGNAL("viewPath"),
                     self._viewPath)
        self.connect(self.view_entry_dialog, SIGNAL("filesSelected"),
                     self._addDPFilesToOldEntry)
        self.connect(self.view_entry_dialog, SIGNAL("entryChanged"),
                     self._entryChanged)
        # saving a data product to an older entry will automatically drop it from the
        # new entry dialog
        self.connect(self.view_entry_dialog, SIGNAL("creatingDataProduct"),
                     self.new_entry_dialog.dropDataProducts)
        # resize selves
        width = Config.getint('main-window-width', 512)
        height = Config.getint('main-window-height', 512)
        self.resize(QSize(width, height))
        # create timer for pouncing
        self._timer = QTimer(self)
        self.connect(self._timer, SIGNAL("timeout()"), self._rescan)
        # create dict mapping index.html paths to entry numbers
        self._index_paths = {}
Ejemplo n.º 28
0
    def __init__(self, win):
        qt4todo('what to do with all those options?')
        ## ElementColorsDialog.__init__(self, win, None, 0,
        ##   Qt.WStyle_Customize | Qt.WStyle_NormalBorder | 
        ##   Qt.WStyle_Title | Qt.WStyle_SysMenu)
        QDialog.__init__(self, win)
        self.setupUi(self)
        self.connect(self.okButton, SIGNAL("clicked()"), self.ok)
        self.connect(self.loadColorsPB, SIGNAL("clicked()"), self.read_element_rgb_table)
        self.connect(self.saveColorsPB, SIGNAL("clicked()"), self.write_element_rgb_table)
        self.connect(self.cancelButton, SIGNAL("clicked()"), self.reject)
        self.connect(self.defaultButton, SIGNAL("clicked()"), self.loadDefaultProp)
        self.connect(self.alterButton, SIGNAL("clicked()"), self.loadAlterProp)
        self.connect(self.elementButtonGroup, SIGNAL("clicked(int)"), self.setElementInfo)
        self.connect(self.previewPB, SIGNAL("clicked()"), self.preview_color_change)
        self.connect(self.restorePB, SIGNAL("clicked()"), self.restore_current_color)
        self.w = win
        self.fileName = None
        self.isElementModified = False
        self.isFileSaved = False
        self.oldTable = PeriodicTable.deepCopy()
        self.elemTable = PeriodicTable
        self.displayMode = self._displayList[0]
        # The next line fixes a bug. Thumbview expects self.gridLayout on
        # line 117 of Thumbview.py. Mark 2007-10-19.
        self.gridLayout = self.gridlayout 
        self.elemGLPane = ElementView(self, "element glPane", self.w.glpane)
        # Put the GL widget inside the frame
        flayout = QVBoxLayout(self.elementFrame)
        flayout.setMargin(1)
        flayout.setSpacing(1)
        flayout.addWidget(self.elemGLPane, 1)
        
        def elementId(symbol):
            return PeriodicTable.getElement(symbol).eltnum
        self.toolButton6.setChecked(True)
        self.elementButtonGroup.setId(self.toolButton6, elementId("C"))
        self.elementButtonGroup.setId(self.toolButton8, elementId("O"))
        self.elementButtonGroup.setId(self.toolButton10, elementId("Ne"))
        self.elementButtonGroup.setId(self.toolButton9, elementId("F"))
        self.elementButtonGroup.setId(self.toolButton13, elementId("Al"))
        self.elementButtonGroup.setId(self.toolButton17, elementId("Cl"))
        self.elementButtonGroup.setId(self.toolButton5, elementId("B"))
        self.elementButtonGroup.setId(self.toolButton10_2, elementId("Ar"))
        self.elementButtonGroup.setId(self.toolButton15, elementId("P"))
        self.elementButtonGroup.setId(self.toolButton16, elementId("S"))
        self.elementButtonGroup.setId(self.toolButton14, elementId("Si"))
        self.elementButtonGroup.setId(self.toolButton33, elementId("As"))
        self.elementButtonGroup.setId(self.toolButton34, elementId("Se"))
        self.elementButtonGroup.setId(self.toolButton35, elementId("Br"))
        self.elementButtonGroup.setId(self.toolButton36, elementId("Kr"))
        self.elementButtonGroup.setId(self.toolButton32, elementId("Ge"))
        self.elementButtonGroup.setId(self.toolButton7, elementId("N"))
        self.elementButtonGroup.setId(self.toolButton2, elementId("He"))
        self.elementButtonGroup.setId(self.toolButton1, elementId("H"))
        self.elementButtonGroup.setId(self.toolButton0, elementId("X"))

        self.connect(self.toolButton6, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton8, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton10, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton9, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton13, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton17, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton5, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton10_2, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton15, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton16, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton14, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton33, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton34, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton35, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton36, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton32, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton7, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton2, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton1, SIGNAL("clicked()"), self.updateElemColorDisplay)
        self.connect(self.toolButton0, SIGNAL("clicked()"), self.updateElemColorDisplay)
        
        self.connectChangingControls()
        self.saveColorsPB.setWhatsThis(
            """Save the current color settings for elements in a text file.""")
        self.defaultButton.setWhatsThis(
            """Restore current element colors to the default colors.""")
        self.loadColorsPB.setWhatsThis(
            """Load element colors from an external text file.""")
        self.alterButton.setWhatsThis(
            """Set element colors to the alternate color set.""")
Ejemplo n.º 29
0
Archivo: moldy.py Proyecto: shrx/moldy
class MainWidget(QWidget):
    def __init__(self):
        QWidget.__init__(self)

        # define periodic table widget for element selection
        self.periodicTableWidget = widgets.PeriodicTableDialog()

        # initial molecule Zmatrix (can be empty)
        # self.inp = []
        self.inp = [['H'],
        ['O', 1, 0.9],
        ['O', 2, 1.4, 1, 105.],
        ['H', 3, 0.9, 2, 105., 1, 120.]]

        self.atomList = []
        self.highList = []
        self.labelList = []
        self.fast = False

        # define & initialize ZMatModel that will contain Zmatrix data
        self.ZMatModel = QStandardItemModel(len(self.inp), 7, self)
        self.ZMatTable = QTableView(self)
        self.ZMatTable.setModel(self.ZMatModel)
        self.ZMatTable.setFixedWidth(325)
        #self.ZMatTable.installEventFilter(self)
        #self.ZMatModel.installEventFilter(self)
        self.ZMatModel.setHorizontalHeaderLabels(['atom','','bond','','angle','','dihedral'])
        for j, width in enumerate([40, 22, 65, 22, 65, 22, 65]):
            self.ZMatTable.setColumnWidth(j, width)
        # populate the ZMatModel
        self.populateZMatModel()

        #define Menu bar menus and their actions
        self.menuBar = QMenuBar(self)
        fileMenu = self.menuBar.addMenu('&File')
        editMenu = self.menuBar.addMenu('&Edit')
        viewMenu = self.menuBar.addMenu('&View')
        measureMenu = self.menuBar.addMenu('&Measure')
        helpMenu = self.menuBar.addMenu('&Help')

        readZmatAction = QAction('&Read &ZMat', self)
        readZmatAction.setShortcut('Ctrl+O')
        readZmatAction.setStatusTip('Read Zmat from file')
        readZmatAction.triggered.connect(self.readZmat)
        fileMenu.addAction(readZmatAction)

        readXYZAction = QAction('&Read &XYZ', self)
        readXYZAction.setShortcut('Ctrl+Shift+O')
        readXYZAction.setStatusTip('Read XYZ from file')
        readXYZAction.triggered.connect(self.readXYZ)
        fileMenu.addAction(readXYZAction)

        readGaussianAction = QAction('&Read &Gaussian log', self)
        readGaussianAction.setShortcut('Ctrl+G')
        readGaussianAction.setStatusTip('Read Gaussian log file')
        readGaussianAction.triggered.connect(self.readGaussian)
        fileMenu.addAction(readGaussianAction)

        writeZmatAction = QAction('&Write &ZMat', self)
        writeZmatAction.setShortcut('Ctrl+S')
        writeZmatAction.setStatusTip('Write Zmat to file')
        writeZmatAction.triggered.connect(self.writeZmat)
        fileMenu.addAction(writeZmatAction)

        writeXYZAction = QAction('&Write &XYZ', self)
        writeXYZAction.setShortcut('Ctrl+Shift+S')
        writeXYZAction.setStatusTip('Write XYZ from file')
        writeXYZAction.triggered.connect(self.writeXYZ)
        fileMenu.addAction(writeXYZAction)

        exitAction = QAction('&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(qApp.quit)
        fileMenu.addAction(exitAction)

        addRowAction = QAction('&Add &row', self)
        addRowAction.setShortcut('Ctrl+R')
        addRowAction.setStatusTip('Add row to ZMatrix')
        addRowAction.triggered.connect(self.addRow)
        editMenu.addAction(addRowAction)

        deleteRowAction = QAction('&Delete &row', self)
        deleteRowAction.setShortcut('Ctrl+Shift+R')
        deleteRowAction.setStatusTip('Delete row from ZMatrix')
        deleteRowAction.triggered.connect(self.deleteRow)
        editMenu.addAction(deleteRowAction)

        addAtomAction = QAction('&Add &atom', self)
        addAtomAction.setShortcut('Ctrl+A')
        addAtomAction.setStatusTip('Add atom to ZMatrix')
        addAtomAction.triggered.connect(self.buildB)
        editMenu.addAction(addAtomAction)

        drawModeMenu = QMenu('Draw mode', self)
        viewMenu.addMenu(drawModeMenu)
        fastDrawAction = QAction('&Fast draw', self)
        fastDrawAction.triggered.connect(self.fastDraw)
        normalDrawAction = QAction('&Normal draw', self)
        normalDrawAction.triggered.connect(self.normalDraw)
        drawModeMenu.addAction(normalDrawAction)
        drawModeMenu.addAction(fastDrawAction)

        clearHighlightsAction = QAction('&Clear selection', self)
        clearHighlightsAction.setShortcut('Ctrl+C')
        clearHighlightsAction.setStatusTip('Clear highlighted atoms')
        clearHighlightsAction.triggered.connect(self.clearHighlights)
        viewMenu.addAction(clearHighlightsAction)

        clearLabelsAction = QAction('&Clear labels', self)
        clearLabelsAction.setShortcut('Ctrl+Alt+C')
        clearLabelsAction.setStatusTip('Clear labels')
        clearLabelsAction.triggered.connect(self.clearLabels)
        viewMenu.addAction(clearLabelsAction)

        clearUpdateViewAction = QAction('&Clear selection and labels', self)
        clearUpdateViewAction.setShortcut('Ctrl+Shift+C')
        clearUpdateViewAction.setStatusTip('Clear highlighted atoms and labels')
        clearUpdateViewAction.triggered.connect(self.clearUpdateView)
        viewMenu.addAction(clearUpdateViewAction)

        self.showGaussAction = QAction('Show &Gaussian geometry optimization', self)
        self.showGaussAction.setShortcut('Ctrl+G')
        self.showGaussAction.setStatusTip('Show Gaussian geometry optimization plots for energy, force and displacement.')
        self.showGaussAction.setEnabled(False)
        self.showGaussAction.triggered.connect(self.showGauss)
        viewMenu.addAction(self.showGaussAction)
        self.showFreqAction = QAction('Show &IR frequency plot', self)
        self.showFreqAction.setShortcut('Ctrl+I')
        self.showFreqAction.setStatusTip('Show Gaussian calculated IR frequency plot.')
        self.showFreqAction.setEnabled(False)
        self.showFreqAction.triggered.connect(self.showFreq)
        viewMenu.addAction(self.showFreqAction)

        measureDistanceAction = QAction('&Measure &distance', self)
        measureDistanceAction.setShortcut('Ctrl+D')
        measureDistanceAction.setStatusTip('Measure distance between two atoms')
        measureDistanceAction.triggered.connect(self.measureDistanceB)
        measureMenu.addAction(measureDistanceAction)

        measureAngleAction = QAction('&Measure &angle', self)
        measureAngleAction.setShortcut('Ctrl+Shift+D')
        measureAngleAction.setStatusTip('Measure angle between three atoms')
        measureAngleAction.triggered.connect(self.measureAngleB)
        measureMenu.addAction(measureAngleAction)

        aboutAction = QAction('&About', self)
        aboutAction.setStatusTip('About this program...')
        aboutAction.triggered.connect(self.about)
        helpMenu.addAction(aboutAction)

        aboutQtAction = QAction('&About Qt', self)
        aboutQtAction.setStatusTip('About Qt...')
        aboutQtAction.triggered.connect(self.aboutQt)
        helpMenu.addAction(aboutQtAction)

        # define GL widget that displays the 3D molecule model
        self.window = widgets.MyGLView()
        self.window.installEventFilter(self)
        self.window.setMinimumSize(500, 500)
        #self.window.setBackgroundColor((50, 0, 10))
        self.updateView()

        self.gaussianPlot = GraphicsLayoutWidget()
        self.gaussianPlot.resize(750, 250)
        self.gaussianPlot.setWindowTitle('Gaussian geometry optimization')
        #self.gaussianPlot.setAspectLocked(True)
        #self.gaussianPlot.addLayout(rowspan=3, colspan=1)

        self.FreqModel = QStandardItemModel(1, 3, self)
        self.freqTable = QTableView(self)
        self.freqTable.setModel(self.FreqModel)
        self.freqTable.setMinimumWidth(240)
        self.freqTable.installEventFilter(self)
        self.FreqModel.installEventFilter(self)
        self.FreqModel.setHorizontalHeaderLabels(['Frequency','IR Intensity','Raman Intensity'])
        for j, width in enumerate([80, 80, 80]):
            self.freqTable.setColumnWidth(j, width)

        self.freqWidget = QWidget()
        self.freqWidget.setWindowTitle('IR frequency plot & table')
        self.freqWidget.resize(800, 400)
        self.freqWidget.layout = QHBoxLayout(self.freqWidget)
        self.freqWidget.layout.setSpacing(1)
        self.freqWidget.layout.setContentsMargins(1, 1, 1, 1)
        self.freqPlot = GraphicsLayoutWidget()
        self.freqWidget.layout.addWidget(self.freqPlot)
        self.freqWidget.layout.addWidget(self.freqTable)
        self.freqTable.clicked.connect(self.freqCellClicked)

        # define other application parts
        self.statusBar = QStatusBar(self)
        self.fileDialog = QFileDialog(self)

        # define application layout
        self.layout = QVBoxLayout(self)
        self.layout.setSpacing(1)
        self.layout.setContentsMargins(1, 1, 1, 1)
        self.layout1 = QHBoxLayout()
        self.layout1.setSpacing(1)
        self.layout1.addWidget(self.ZMatTable)
        self.layout1.addWidget(self.window)
        self.layout.addWidget(self.menuBar)
        self.layout.addLayout(self.layout1)
        self.layout.addWidget(self.statusBar)

        self.adjustSize()
        self.setWindowTitle('Moldy')
        iconPath = 'icon.png'
        icon = QIcon(iconPath)
        icon.addFile(iconPath, QSize(16, 16))
        icon.addFile(iconPath, QSize(24, 24))
        icon.addFile(iconPath, QSize(32, 32))
        icon.addFile(iconPath, QSize(48, 48))
        icon.addFile(iconPath, QSize(256, 256))
        self.setWindowIcon(icon)

        # start monitoring changes in the ZMatModel
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)

    # run and show the application
    def run(self):
        self.show()
        self.ZMatTable.clicked.connect(self.ZMatCellClicked)
        qt_app.instance().aboutToQuit.connect(self.deleteGLwidget)
        qt_app.exec_()

    # fill the ZMatModel with initial data from 'self.inp'
    def populateZMatModel(self):
        self.ZMatModel.removeRows(0, self.ZMatModel.rowCount())
        for i, row in enumerate(self.inp):
            for j, cell in enumerate(row):
                item = QStandardItem(str(cell))
                self.ZMatModel.setItem(i, j, item)
        # some cells should not be editable, they are disabled
        for i in range(min(len(self.inp), 3)):
            for j in range(2*i+1, 7):
                self.ZMatModel.setItem(i, j, QStandardItem())
                self.ZMatModel.item(i, j).setBackground(QColor(150,150,150))
                self.ZMatModel.item(i, j).setFlags(Qt.ItemIsEnabled)
    
    def populateFreqModel(self):
        self.FreqModel.removeRows(0, self.FreqModel.rowCount())
        for i, row in enumerate(zip(self.vibfreqs, self.vibirs, self.vibramans)):
            for j, cell in enumerate(row):
                item = QStandardItem(str(cell))
                self.FreqModel.setItem(i, j, item)

    # add a row to the bottom of the ZMatModel
    def addRow(self):
        # temporarily stop updating the GL window
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        row = self.ZMatModel.rowCount()
        self.ZMatModel.insertRow(row)
        # some cells should not be editable
        if row < 3:
            for j in range(2*row+1, 7):
                self.ZMatModel.setItem(row, j, QStandardItem())
                self.ZMatModel.item(row, j).setBackground(QColor(150,150,150))
                self.ZMatModel.item(row, j).setFlags(Qt.ItemIsEnabled)
        # restart GL window updating
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Added 1 row.', 3000)

    # delete the last row of the ZMatModel
    def deleteRow(self):
        xyz = [list(vi) for vi in list(v)]
        atoms = [str(elements[e]) for e in elems]
        oldLen = self.ZMatModel.rowCount()
        idxs = sorted(set(idx.row() for idx in self.ZMatTable.selectedIndexes()), reverse=True)
        newLen = oldLen - len(idxs)
        if newLen == oldLen:
            self.ZMatModel.removeRow(self.ZMatModel.rowCount()-1)
        else:
            self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
            for idx in idxs:
                self.ZMatModel.removeRow(idx)
                if idx < 3:
                    for i in range(idx, min(3, newLen)):
                        for j in range(2*i+1, 7):
                            self.ZMatModel.setItem(i, j, QStandardItem())
                            self.ZMatModel.item(i, j).setBackground(QColor(150,150,150))
                            self.ZMatModel.item(i, j).setFlags(Qt.ItemIsEnabled)
                if len(xyz) > idx:
                    xyz.pop(idx)
                    atoms.pop(idx)
            self.inp = xyz2zmat(xyz, atoms)
            self.populateZMatModel()
            for i in reversed(self.highList):
                self.window.removeItem(i[1])
            self.highList = []
            self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.updateView()
        self.statusBar.clearMessage()
        if idxs:
            self.statusBar.showMessage('Deleted row(s): '+str([i+1 for i in idxs]), 3000)
        else:
            self.statusBar.showMessage('Deleted last row.', 3000)

    # show the periodic table widget
    def periodicTable(self):
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Select element from periodic table.')
        self.periodicTableWidget.exec_()
        selection = self.periodicTableWidget.selection()
        return selection

    # import molecule with zmatrix coordinates
    def readZmat(self):
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        filename = self.fileDialog.getOpenFileName(self, 'Open file', expanduser('~'), '*.zmat;;*.*')
        self.inp = []
        self.populateZMatModel()
        if filename:
            with open(filename, 'r') as f:
                next(f)
                next(f)
                for row in f:
                    self.inp.append(row.split())
                f.close()
            self.populateZMatModel()
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.updateView()
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Read molecule from '+filename+'.', 5000)
        self.showGaussAction.setEnabled(False)
        self.showFreqAction.setEnabled(False)

    # import molecule with xyz coordinates
    def readXYZ(self):
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        filename = self.fileDialog.getOpenFileName(self, 'Open file', expanduser('~'), '*.xyz;;*.*')
        xyz = []
        elems = []
        self.inp = []
        self.populateZMatModel()
        if filename:
            with open(filename, 'r') as f:
                next(f)
                next(f)
                for row in f:
                    rs = row.split()
                    if len(rs) == 4:
                        elems.append(rs[0])
                        xyz.append([float(f) for f in rs[1:]])
                f.close()
            self.inp = xyz2zmat(xyz, elems)
            self.populateZMatModel()
            #print(elems)
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.updateView()
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Read molecule from '+filename+'.', 5000)
        self.showGaussAction.setEnabled(False)
        self.showFreqAction.setEnabled(False)

    # import Gaussian log file
    def readGaussian(self):
        global vsShifted
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        filename = self.fileDialog.getOpenFileName(self, 'Open file', expanduser('~'), '*.log;;*.*')
        if filename:
            self.gaussianPlot.clear()
            self.inp = []
            self.populateZMatModel()
            file = ccopen(filename)
            data = file.parse().getattributes()
            self.natom = data['natom']
            self.atomnos = data['atomnos'].tolist()
            self.atomsymbols = [ str(elements[e]) for e in self.atomnos ]
            self.atomcoords = data['atomcoords'].tolist()
            self.scfenergies = data['scfenergies'].tolist()
            self.geovalues = data['geovalues'].T.tolist()
            self.geotargets = data['geotargets'].tolist()
            if 'vibfreqs' in data.keys():
                self.vibfreqs = data['vibfreqs']
                #print(self.vibfreqs)
                self.vibirs = data['vibirs']
                #print(self.vibirs)
                #print(data.keys())
                if 'vibramans' in data.keys():
                    self.vibramans = data['vibramans']
                else:
                    self.vibramans = [''] * len(self.vibirs)
                self.vibdisps = data['vibdisps']
                #print(self.vibdisps)
            self.inp = xyz2zmat(self.atomcoords[0], self.atomsymbols)
            self.populateZMatModel()

            titles = ['SCF Energies', 'RMS & Max Forces', 'RMS & Max Displacements']
            for i in range(3):
                self.gaussianPlot.addPlot(row=1, col=i+1)
                plot = self.gaussianPlot.getItem(1, i+1)
                plot.setTitle(title=titles[i])
                if i == 0:
                    c = ['c']
                    x = [0]
                    y = [self.scfenergies]
                else:
                    c = ['r', 'y']
                    x = [0, 0]
                    y = [self.geovalues[2*i-2], self.geovalues[2*i-1]]
                    targety = [self.geotargets[2*i-2], self.geotargets[2*i-1]]
                plot.clear()
                plot.maxData = plot.plot(y[0], symbol='o', symbolPen=c[0], symbolBrush=c[0], pen=c[0], symbolSize=5, pxMode=True, antialias=True, autoDownsample=False)
                plot.highlight=plot.plot(x, [ yy[0] for yy in y ], symbol='o', symbolPen='w', symbolBrush=None, pen=None, symbolSize=15, pxMode=True, antialias=True, autoDownsample=False)
                plot.maxData.sigPointsClicked.connect(self.gausclicked)
                if i > 0:
                    for j in range(2):
                        plot.addLine(y=np.log10(targety[j]), pen=mkPen((255, 255*j, 0, int(255/2)), width=1))
                    plot.RMSData=plot.plot(y[1], symbol='o', symbolPen=c[1], symbolBrush=c[1], pen=c[1], symbolSize=5, pxMode=True, antialias=True, autoDownsample=False)
                    plot.RMSData.sigPointsClicked.connect(self.gausclicked)
                    plot.setLogMode(y=True)
            self.showGauss()
            self.updateView()
            self.statusBar.clearMessage()
            self.statusBar.showMessage('Read molecule from '+filename+'.', 5000)
            self.ZMatModel.dataChanged.connect(self.clearUpdateView)
            if self.natom:
                self.showGaussAction.setEnabled(True)
            if 'vibfreqs' in data.keys():
                self.showFreqAction.setEnabled(True)

                # populate the FreqModel
                self.populateFreqModel()

                self.freqPlot.clear()
                irPlot = self.freqPlot.addPlot(row=1, col=1)
                irPlot.clear()
                minFreq = np.min(self.vibfreqs)
                maxFreq = np.max(self.vibfreqs)
                maxInt = np.max(self.vibirs)
                x = np.sort(np.concatenate([np.linspace(minFreq-100, maxFreq+100, num=1000), self.vibfreqs]))
                y = x*0
                for f,i in zip(self.vibfreqs, self.vibirs):
                    y += lorentzv(x, f, 2*np.pi, i)
                #xy = np.array([np.concatenate([x, np.array(self.vibfreqs)]), np.concatenate([y, np.array(self.vibirs)])]).T
                #xysort = xy[xy[:,0].argsort()]
                irPlot.maxData = irPlot.plot(x, y, antialias=True)
                markers = ErrorBarItem(x=self.vibfreqs, y=self.vibirs, top=maxInt/30, bottom=None, pen='r')
                irPlot.addItem(markers)
                self.showFreq()
                #self.vibdisps = np.append(self.vibdisps, [np.mean(self.vibdisps, axis=0)], axis=0)
                maxt = 100
                vsShifted = np.array([ [ vs + self.vibdisps[i]*np.sin(t*2*np.pi/maxt)/3 for t in range(maxt) ] for i in range(len(self.vibfreqs)) ])
            else:
                self.showFreqAction.setEnabled(False)
                self.freqWidget.hide()

    def showGauss(self):
        self.gaussianPlot.show()

    def showFreq(self):
        self.freqWidget.show()

    # export Zmatrix to csv
    def writeZmat(self):
        zm = model2list(self.ZMatModel)
        filename = self.fileDialog.getSaveFileName(self, 'Save file', expanduser('~')+'/'+getFormula(list(list(zip(*zm))[0]))+'.zmat', '*.zmat;;*.*')
        try:
            filename
        except NameError:
            pass
        else:
            if filename:
                writeOutput(zm, filename)
                self.statusBar.clearMessage()
                self.statusBar.showMessage('Wrote molecule to '+filename+'.', 5000)

    # export XYZ coordinates to csv
    def writeXYZ(self):
        xyz = []
        zm = model2list(self.ZMatModel)
        for i in range(len(v)):
            xyz.append(np.round(v[i], 7).tolist())
            xyz[i][:0] = zm[i][0]
        if len(v) > 0:
            formula = getFormula(list(list(zip(*xyz))[0]))
        else:
            formula = 'moldy_output'
        filename = self.fileDialog.getSaveFileName(self, 'Save file', expanduser('~')+'/'+formula+'.xyz', '*.xyz;;*.*')
        try:
            filename
        except NameError:
            pass
        else:
            if filename:
                writeOutput(xyz, filename)
                self.statusBar.clearMessage()
                self.statusBar.showMessage('Wrote molecule to '+filename+'.', 5000)

    # redraw the 3D molecule in GL widget
    def updateView(self):
        global r
        global c
        global v
        global vs
        global elems
        global nelems
        data = model2list(self.ZMatModel)
        try:
            # create a list with element coordinates
            v = zmat2xyz(data)
        except (AssertionError, IndexError, ZMError):
            pass
        else:
            # clear the screen before redraw
            for item in reversed(self.window.items):
                self.window.removeItem(item)
            # create a second coordinate list 'vs' that is centered in the GL view
            self.atomList = []
            if len(v) > 0:
                shift = np.mean(v, axis=0)
                vs = np.add(v, -shift)
                elems = [ 1 + next((i for i, sublist in enumerate(colors) if row[0] in sublist), -1) for row in data ]
                nelems = len(elems)
                # define molecule radii and colors
                r = []
                c = []
                for i in elems:
                    r.append(elements[i].covalent_radius)
                    c.append(colors[i-1][-1])
                # draw atoms
                for i in range(nelems):
                    addAtom(self.window, i, r, vs, c, fast=self.fast)
                    self.atomList.append([i, self.window.items[-1]])
                #print(self.atomList)
                # draw bonds where appropriate
                combs = list(itertools.combinations(range(nelems), 2))
                bonds = []
                for i in combs:
                    bonds.append(addBond(self.window, i[0], i[1], r, vs, c, fast=self.fast))
                if self.fast:
                    bondedAtoms = set(filter((None).__ne__, flatten(bonds)))
                    for i in set(range(nelems)) - bondedAtoms:
                        addUnbonded(self.window, i, vs, c)
                        self.atomList[i][1]=self.window.items[-1]
                    #print(self.atomList)

                for i in self.highList:
                    self.window.addItem(i[1])
                for i in self.labelList:
                    self.window.addItem(i)
        if len(v) > 1:
            maxDim = float('-inf')
            for dim in v.T:
                span = max(dim)-min(dim)
                if span > maxDim:
                    maxDim = span
        else: maxDim = 2
        self.window.setCameraPosition(distance=maxDim*1.5+1)

    global index
    index = 0
    def updateFreq(self):
        global vsShifted, index, r, c
        index += 1
        index = index % len(vsShifted[0])
        #print(index)
        #print(vsShifted[index])
        for item in reversed(self.window.items):
            self.window.removeItem(item)
        for i in range(nelems):
            addAtom(self.window, i, r, vsShifted[self.freqIndex, index], c, fast=self.fast)
            self.atomList.append([i, self.window.items[-1]])
        combs = itertools.combinations(range(nelems), 2)
        bonds = []
        for i in combs:
            bonds.append(addBond(self.window, i[0], i[1], r, vsShifted[self.freqIndex, index], c, fast=self.fast))
        if self.fast:
            bondedAtoms = set(filter((None).__ne__, flatten(bonds)))
            for i in set(range(nelems)) - bondedAtoms:
                addUnbonded(self.window, i, vsShifted[self.freqIndex, index], c)
                self.atomList[i][1]=self.window.items[-1]

    # detect mouse clicks in GL window and process them
    def eventFilter(self, obj, event):
        if obj == self.window:
            if event.type() == event.MouseButtonPress:
                itms = obj.itemsAt((event.pos().x()-2, event.pos().y()-2, 4, 4))
                if len(itms):
                    self.highlight(obj, [itms[0]])
                elif len(self.atomList) == 0:
                    self.build()
        # also do the default click action
        return super(MainWidget, self).eventFilter(obj, event)

    def ZMatCellClicked(self):
        idxs = sorted(set(idx.row() for idx in self.ZMatTable.selectedIndexes()), reverse=True)
        itms = []
        if self.highList:
            highIdx = list(np.array(self.highList).T[0])
        for idx in idxs:
            if self.highList and idx in highIdx:
                itms.append(self.highList[highIdx.index(idx)][1])
            elif len(self.atomList) > idx:
                itms.append(self.atomList[idx][1])
        self.highlight(self.window, itms)

    def freqCellClicked(self):
        global vsShifted
        self.timer = QTimer()
        self.timer.setInterval(30)
        self.timer.timeout.connect(self.updateFreq)
        idxs = [ idx.row() for idx in self.freqTable.selectedIndexes() ]
        if len(idxs) == 1:
            self.freqIndex = idxs[0]
            self.timer.stop()
            self.timer.timeout.connect(self.updateFreq)
            try:
                self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
            except TypeError:
                pass
            self.timer.start()
        if len(idxs) != 1:
            self.timer.stop()
            self.freqTable.clearSelection()
            self.timer.timeout.disconnect(self.updateFreq)
            self.ZMatModel.dataChanged.connect(self.clearUpdateView)
            self.clearUpdateView()

    def gausclicked(self, item, point):
        itemdata = item.scatter.data
        points = [ row[7] for row in itemdata ]
        idx = points.index(point[0])
        for i in range(3):
            if i == 0:
                x = [idx]
                y = [self.scfenergies[idx]]
            else:
                x = [idx, idx]
                y = [self.geovalues[2*i-2][idx], self.geovalues[2*i-1][idx]]
            plot = self.gaussianPlot.getItem(1, i+1)
            plot.removeItem(plot.highlight)
            plot.highlight=plot.plot(x, y, symbol='o', symbolPen='w', symbolBrush=None, pen=None, symbolSize=15, pxMode=True, antialias=True, autoDownsample=False)
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        self.inp = []
        self.populateZMatModel()
        self.inp = xyz2zmat(self.atomcoords[min(idx, len(self.atomcoords)-1)], self.atomsymbols)
        self.populateZMatModel()
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.updateView()

    def highlight(self, obj, itms):
        for itm in itms:
            idx = next((i for i, sublist in enumerate(self.atomList) if itm in sublist), -1)
            #print(idx)
            if idx != -1:
                addAtom(obj, idx, r, vs, c, opt='highlight', fast=self.fast)
                self.highList.append([idx, obj.items[-1]])
                self.ZMatTable.selectRow(idx)
            idx = next((i for i, sublist in enumerate(self.highList) if itm in sublist), -1)
            if idx != -1:
                obj.removeItem(self.highList[idx][1])
                self.highList.pop(idx)
                self.ZMatTable.clearSelection()
        self.statusBar.clearMessage()
        if len(self.highList) > 0:
            idxs = np.asarray(self.highList).T[0]
            selected = []
            for i in idxs:
                selected.append(str(i+1)+str(elements[elems[i]]))
            self.statusBar.showMessage('Selected atoms: '+str(selected), 5000)

    def buildB(self):
        try:
            nelems
        except NameError:
            self.build()
        else:
            if len(self.highList) <= min(nelems, 3):
                diff = min(nelems, 3) - len(self.highList)
                if diff != 0:
                    self.statusBar.clearMessage()
                    self.statusBar.showMessage('Please select '+str(diff)+' more atom(s).')
                else:
                    self.build()
            else:
                self.statusBar.clearMessage()
                self.statusBar.showMessage('Too many atoms selected.')

    def build(self):
        selection = self.periodicTable()
        row = self.ZMatModel.rowCount()
        self.addRow()
        self.ZMatModel.dataChanged.disconnect(self.clearUpdateView)
        newSymbol = selection[1]
        newData = [newSymbol]
        if len(self.highList) >= 1:
            newBond = round(2.1*gmean([ elements[e].covalent_radius for e in [selection[0], elems[self.highList[0][0]]] ]), 4)
            newData.append(self.highList[0][0]+1)
            newData.append(newBond)
            if len(self.highList) >= 2:
                newAngle = 109.4712
                newData.append(self.highList[1][0]+1)
                newData.append(newAngle)
                if len(self.highList) == 3:
                    newDihedral = 120.
                    newData.append(self.highList[2][0]+1)
                    newData.append(newDihedral)
        for j, cell in enumerate(newData):
            item = QStandardItem(str(cell))
            self.ZMatModel.setItem(row, j, item)
        self.highList = []
        self.ZMatModel.dataChanged.connect(self.clearUpdateView)
        self.updateView()

    def measureDistanceB(self):
        sel = len(self.highList)
        if sel <= 2:
            if sel < 2:
                self.statusBar.clearMessage()
                self.statusBar.showMessage('Please select '+str(2-sel)+' more atom(s).')
            else:
                self.measureDistance()
        else:
            self.statusBar.clearMessage()
            self.statusBar.showMessage('Too many atoms selected.')

    def measureDistance(self):
        pts = []
        for pt in self.highList:
            pts.append(vs[pt[0]])
        pts = np.array(pts)
        self.clearHighlights()
        line = gl.GLLinePlotItem(pos=pts, color=(0., 1., 0., 1.), width=3)
        self.window.addItem(line)
        self.labelList.append(line)
        q = pts[1]-pts[0]
        dist = round(np.sqrt(np.dot(q, q)), 4)
        self.window.labelPos.append(np.mean(pts[0:2], axis=0))
        self.window.labelText.append(str(dist))
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Measured distance: '+str(dist)+' A.', 3000)

    def measureAngleB(self):
        sel = len(self.highList)
        if sel <= 3:
            if sel < 3:
                self.statusBar.clearMessage()
                self.statusBar.showMessage('Please select '+str(3-sel)+' more atom(s).')
            else:
                self.measureAngle()
        else:
            self.statusBar.clearMessage()
            self.statusBar.showMessage('Too many atoms selected.')

    def measureAngle(self):
        pts = []
        for pt in self.highList:
            pts.append(vs[pt[0]])
        pts = np.array(pts)
        q = pts[1]-pts[0]
        r = pts[2]-pts[0]
        q_u = q / np.sqrt(np.dot(q, q))
        r_u = r / np.sqrt(np.dot(r, r))
        angle = round(degrees(acos(np.dot(q_u, r_u))), 1)
        srange = np.array([slerp(q, r, t) for t in np.arange(0.0, 13/12, 1/12)])
        self.clearHighlights()
        for i in range(12):
            mesh = gl.MeshData(np.array([[0,0,0],srange[i],srange[i+1]]))
            tri = gl.GLMeshItem(meshdata=mesh, smooth=False, computeNormals=False, color=(0.3, 1., 0.3, 0.5), glOptions=('translucent'))
            tri.translate(pts[0][0], pts[0][1], pts[0][2])
            self.window.addItem(tri)
            self.labelList.append(tri)
        self.window.labelPos.append(slerp(q, r, 0.5)+pts[0])
        self.window.labelText.append(str(angle))
        self.statusBar.clearMessage()
        self.statusBar.showMessage('Measured angle: '+str(angle)+'°', 3000)

    def clearLabels(self):
        self.window.labelPos = []
        self.window.labelText = []
        self.labelList = []
        self.updateView()

    def clearHighlights(self):
        for item in reversed(self.highList):
                self.window.removeItem(item[1])
        self.highList = []
        self.updateView()

    def clearUpdateView(self):
        self.window.labelPos = []
        self.window.labelText = []
        self.labelList = []
        for item in reversed(self.highList):
                self.window.removeItem(item[1])
        self.highList = []
        self.updateView()
        #print(self.highList)

    def fastDraw(self):
        if not self.fast:
            self.fast = True
            self.updateView()

    def normalDraw(self):
        if self.fast:
            self.fast = False
            self.updateView()

    def about(self):
        QMessageBox.about(self, 'About moldy', 'moldy beta 15. 9. 2015')

    def aboutQt(self):
        QMessageBox.aboutQt(self, 'About Qt')

    def deleteGLwidget(self):
        self.window.setParent(None)
        del self.window
Ejemplo n.º 30
0
class pmMessageGroupBox(QGroupBox, PropertyManager_common):
    """Creates a Message group box.
    This class is used by the following Property Managers
    (which are still not using the PropMgrBaseClass):
    - Build Atoms
    - Build Crystal
    - Extrude
    - Move
    - Movie Player
    - Fuse Chunks

    Note: This class is temporary. It will be removed after the
    PropMgrs in this list are converted to the PropMgrBaseClass.
    Mark 2007-06-21
    """

    expanded = True  # Set to False when groupbox is collapsed.
    defaultText = ""
    # The default text that is displayed whenever the Property Manager is displayed.
    setAsDefault = True

    # Checked to determine if <defaultText> should be restored whenever the
    # Property Manager is displayed.

    def __init__(self, parent, title=''):

        QGroupBox.__init__(self)
        if parent:
            self.setParent(parent)  #Fixed bug 2465 -- ninad 20070622

        self.setAutoFillBackground(True)
        self.setPalette(self.getPropMgrGroupBoxPalette())
        self.setStyleSheet(self.getStyleSheet())

        # Create vertical box layout
        self.VBoxLayout = QVBoxLayout(self)
        self.VBoxLayout.setMargin(pmMsgGrpBoxMargin)
        self.VBoxLayout.setSpacing(pmMsgGrpBoxSpacing)

        # Add title button to GroupBox
        self.titleButton = self.getTitleButton(title, self)
        self.VBoxLayout.addWidget(self.titleButton)
        self.connect(self.titleButton, SIGNAL("clicked()"),
                     self.toggleExpandCollapse)

        # Yellow MessageTextEdit
        self.MessageTextEdit = QtGui.QTextEdit(self)
        self.MessageTextEdit.setMaximumHeight(80)  # 80 pixels height
        self.MessageTextEdit.setHorizontalScrollBarPolicy(
            Qt.ScrollBarAlwaysOff)
        self.VBoxLayout.addWidget(self.MessageTextEdit)

        msg_palette = self.getMessageTextEditPalette()

        self.MessageTextEdit.setPalette(msg_palette)
        self.MessageTextEdit.setReadOnly(True)

        # wrapWrapMode seems to be set to QTextOption.WrapAnywhere on MacOS,
        # so let's force it here. Mark 2007-05-22.
        self.MessageTextEdit.setWordWrapMode(QTextOption.WordWrap)

        # These two policies very important. Mark 2007-05-22
        self.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.MessageTextEdit.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.setWhatsThis("""<b>Messages</b>
            <p>This prompts the user for a requisite operation and/or displays
            helpful messages to the user.</p>""")

        parent.MessageTextEdit = self.MessageTextEdit

        self.hide()

    def getTitleButton(self,
                       title,
                       parent=None,
                       showExpanded=True):  #Ninad 070206
        """ Return the groupbox title pushbutton. The pushbutton is customized
        such that  it appears as a title bar to the user. If the user clicks on
        this 'titlebar' it sends appropriate signals to open or close the
        groupboxes   'name = string -- title of the groupbox
        'showExpanded' = boolean .. NE1 uses a different background
        image in the button's  Style Sheet depending on the bool.
        (i.e. if showExpanded = True it uses a opened group image  '^')
        See also: getGroupBoxTitleCheckBox , getGroupBoxButtonStyleSheet  methods
        """

        button = QPushButton(title, parent)
        button.setFlat(False)
        button.setAutoFillBackground(True)

        button.setStyleSheet(self.getTitleButtonStyleSheet(showExpanded))
        button.setPalette(self.getTitleButtonPalette())

        #ninad 070221 set a non existant 'Ghost Icon' for this button
        #By setting such an icon, the button text left aligns!
        #(which what we want :-) )
        #So this might be a bug in Qt4.2.  If we don't use the following kludge,
        #there is no way to left align the push button text but to subclass it.
        #(could mean a lot of work for such a minor thing)  So OK for now

        button.setIcon(geticon("ui/actions/Properties Manager/GHOST_ICON"))

        return button

    def getTitleButtonStyleSheet(self, showExpanded=True):
        """Returns the style sheet for a groupbox title button (or checkbox).
        If <showExpanded> is True, the style sheet includes an expanded icon.
        If <showExpanded> is False, the style sheet includes a collapsed icon.
        """

        # Need to move border color and text color to top (make global constants).
        if showExpanded:
            styleSheet = "QPushButton {border-style:outset;\
            border-width: 2px;\
            border-color: " + pmGrpBoxButtonBorderColor + ";\
            border-radius:2px;\
            font:bold 12px 'Arial'; \
            color: " + pmGrpBoxButtonTextColor + ";\
            min-width:10em;\
            background-image: url(" + pmGrpBoxExpandedImage + ");\
            background-position: right;\
            background-repeat: no-repeat;\
            }"

        else:

            styleSheet = "QPushButton {border-style:outset;\
            border-width: 2px;\
            border-color: " + pmGrpBoxButtonBorderColor + ";\
            border-radius:2px;\
            font: bold 12px 'Arial'; \
            color: " + pmGrpBoxButtonTextColor + ";\
            min-width:10em;\
            background-image: url(" + pmGrpBoxCollapsedImage + ");\
            background-position: right;\
            background-repeat: no-repeat;\
            }"

        return styleSheet

    def toggleExpandCollapse(self):
        """Slot method for the title button to expand/collapse the groupbox.
        """
        if self.expanded:  # Collapse groupbox by hiding ahe yellow TextEdit.
            # The styleSheet contains the expand/collapse icon.
            styleSheet = self.getTitleButtonStyleSheet(showExpanded=False)
            self.titleButton.setStyleSheet(styleSheet)
            # Why do we have to keep resetting the palette?
            # Does assigning a new styleSheet reset the button's palette?
            # If yes, we should add the button's color to the styleSheet.
            # Mark 2007-05-20
            self.titleButton.setPalette(self.getTitleButtonPalette())
            self.titleButton.setIcon(
                geticon("ui/actions/Properties Manager/GHOST_ICON"))
            self.MessageTextEdit.hide()
            self.expanded = False
        else:  # Expand groupbox by showing the yellow TextEdit.
            # The styleSheet contains the expand/collapse icon.
            styleSheet = self.getTitleButtonStyleSheet(showExpanded=True)
            self.titleButton.setStyleSheet(styleSheet)
            # Why do we have to keep resetting the palette?
            # Does assigning a new styleSheet reset the button's palette?
            # If yes, we should add the button's color to the styleSheet.
            # Mark 2007-05-20
            self.titleButton.setPalette(self.getTitleButtonPalette())
            self.titleButton.setIcon(
                geticon("ui/actions/Properties Manager/GHOST_ICON"))
            self.MessageTextEdit.show()
            self.expanded = True

    def getStyleSheet(self):
        """Return the style sheet for a groupbox. This sets the following
        properties only:
         - border style
         - border width
         - border color
         - border radius (on corners)
        The background color for a groupbox is set using getPalette()."""

        styleSheet = "QGroupBox {border-style:solid;\
        border-width: 1px;\
        border-color: " + pmGrpBoxBorderColor + ";\
        border-radius: 0px;\
        min-width: 10em; }"

        ## For Groupboxs' Pushbutton :
        ##Other options not used : font:bold 10px;

        return styleSheet

    def insertHtmlMessage(self,
                          text,
                          setAsDefault=True,
                          minLines=4,
                          maxLines=10,
                          replace=True):
        """Insert <text> (HTML) into the message groupbox.
        <minLines> - The minimum number of lines (of text) to display in the TextEdit.
        if <minLines>=0 the TextEdit will fit its own height to fit <text>. The
        default height is 4 (lines of text).
        <maxLines> - The maximum number of lines to display in the TextEdit widget.
        <replace> should be set to False if you do not wish
        to replace the current text. It will append <text> instead.

        Shows the message groupbox if it is hidden.
        """
        if setAsDefault:
            self.defaultText = text
            self.setAsDefault = True

        if replace:
            self.MessageTextEdit.clear()

        if text:
            self._setHeight(minLines, maxLines)
            QTextEdit.insertHtml(self.MessageTextEdit, text)
            self.show()
        else:
            # Hide the message groupbox if it contains no text.
            self.hide()

    def _setHeight(self, minLines=4, maxLines=8):
        """Set the height just high enough to display
        the current text without a vertical scrollbar.
        <minLines> is the minimum number of lines to
        display, even if the text takes up fewer lines.
        <maxLines> is the maximum number of lines to
        diplay before adding a vertical scrollbar.
        """

        if minLines == 0:
            fitToHeight = True
        else:
            fitToHeight = False

        # Current width of PropMgrTextEdit widget.
        current_width = self.MessageTextEdit.sizeHint().width()

        # Probably including Html tags.
        text = self.MessageTextEdit.toPlainText()
        text_width = self.MessageTextEdit.fontMetrics().width(text)

        num_lines = text_width / current_width + 1
        # + 1 may create an extra (empty) line on rare occasions.

        if fitToHeight:
            num_lines = min(num_lines, maxLines)

        else:
            num_lines = max(num_lines, minLines)

        #margin = self.fontMetrics().leading() * 2 # leading() returned 0. Mark 2007-05-28
        margin = 10  # Based on trial and error. Maybe it is pm?Spacing=5 (*2)? Mark 2007-05-28
        new_height = num_lines * self.MessageTextEdit.fontMetrics(
        ).lineSpacing() + margin

        if 0:  # Debugging code for me. Mark 2007-05-24
            print "--------------------------------"
            print "Widget name =", self.objectName()
            print "minLines =", minLines
            print "maxLines =", maxLines
            print "num_lines=", num_lines
            print "New height=", new_height
            print "text =", text
            print "Text width=", text_width
            print "current_width (of PropMgrTextEdit)=", current_width

        # Reset height of PropMgrTextEdit.
        self.MessageTextEdit.setMinimumSize(QSize(pmMinWidth * 0.5,
                                                  new_height))
        self.MessageTextEdit.setMaximumHeight(new_height)
Ejemplo n.º 31
0
 def __init__(self, parent, flags=Qt.WindowFlags()):
     QDialog.__init__(self, parent, flags)
     self.setModal(False)
     self.setWindowTitle("Select sources by...")
     lo = QVBoxLayout(self)
     lo.setMargin(10)
     lo.setSpacing(5)
     # select by
     lo1 = QHBoxLayout()
     lo.addLayout(lo1)
     lo1.setContentsMargins(0, 0, 0, 0)
     #    lab = QLabel("Select:")
     #   lo1.addWidget(lab)
     self.wselby = QComboBox(self)
     lo1.addWidget(self.wselby, 0)
     QObject.connect(self.wselby, SIGNAL("activated(const QString &)"),
                     self._setup_selection_by)
     # under/over
     self.wgele = QComboBox(self)
     lo1.addWidget(self.wgele, 0)
     self.wgele.addItems([">", ">=", "<=", "<", "sum<=", "sum>"])
     QObject.connect(self.wgele, SIGNAL("activated(const QString &)"),
                     self._select_threshold)
     # threshold value
     self.wthreshold = QLineEdit(self)
     QObject.connect(self.wthreshold, SIGNAL("editingFinished()"),
                     self._select_threshold)
     lo1.addWidget(self.wthreshold, 1)
     # min and max label
     self.wminmax = QLabel(self)
     lo.addWidget(self.wminmax)
     # selection slider
     lo1 = QHBoxLayout()
     lo.addLayout(lo1)
     self.wpercent = QSlider(self)
     self.wpercent.setTracking(False)
     QObject.connect(self.wpercent, SIGNAL("valueChanged(int)"),
                     self._select_percentile)
     QObject.connect(self.wpercent, SIGNAL("sliderMoved(int)"),
                     self._select_percentile_threshold)
     self.wpercent.setRange(0, 100)
     self.wpercent.setOrientation(Qt.Horizontal)
     lo1.addWidget(self.wpercent)
     self.wpercent_lbl = QLabel("0%", self)
     self.wpercent_lbl.setMinimumWidth(64)
     lo1.addWidget(self.wpercent_lbl)
     #    # hide button
     #    lo.addSpacing(10)
     #    lo2 = QHBoxLayout()
     #    lo.addLayout(lo2)
     #    lo2.setContentsMargins(0,0,0,0)
     #    hidebtn = QPushButton("Close",self)
     #    hidebtn.setMinimumWidth(128)
     #    QObject.connect(hidebtn,SIGNAL("clicked()"),self.hide)
     #    lo2.addStretch(1)
     #    lo2.addWidget(hidebtn)
     #    lo2.addStretch(1)
     #    self.setMinimumWidth(384)
     self._in_select_threshold = False
     self._sort_index = None
     self.qerrmsg = QErrorMessage(self)
Ejemplo n.º 32
0
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle("My Main Window")
        self.setMinimumWidth(MAIN_WINDOW_SIZE[0])
        self.setMinimumHeight(MAIN_WINDOW_SIZE[1])

        self.statusbar = QtGui.QStatusBar(self)
        self.statusbar.showMessage("Status message")
        self.setStatusBar(self.statusbar)

        ################################################

        self.menubar = self.menuBar()
        # Any menu action makes the status bar message disappear

        fileMenu = QtGui.QMenu(self.menubar)
        fileMenu.setTitle("File")
        self.menubar.addAction(fileMenu.menuAction())

        newAction = QtGui.QAction("New", self)
        newAction.setIcon(QtGui.QtIcon(icons + '/GroupPropDialog_image0.png'))
        fileMenu.addAction(newAction)
        openAction = QtGui.QAction("Open", self)
        openAction.setIcon(QtGui.QtIcon(icons + "/MainWindowUI_image1"))
        fileMenu.addAction(openAction)
        saveAction = QtGui.QAction("Save", self)
        saveAction.setIcon(QtGui.QtIcon(icons + "/MainWindowUI_image2"))
        fileMenu.addAction(saveAction)

        self.connect(newAction,SIGNAL("activated()"),self.fileNew)
        self.connect(openAction,SIGNAL("activated()"),self.fileOpen)
        self.connect(saveAction,SIGNAL("activated()"),self.fileSave)

        for otherMenuName in ('Edit', 'View', 'Display', 'Select', 'Modify', 'NanoHive-1'):
            otherMenu = QtGui.QMenu(self.menubar)
            otherMenu.setTitle(otherMenuName)
            self.menubar.addAction(otherMenu.menuAction())

        helpMenu = QtGui.QMenu(self.menubar)
        helpMenu.setTitle("Help")
        self.menubar.addAction(helpMenu.menuAction())

        aboutAction = QtGui.QAction("About", self)
        aboutAction.setIcon(QtGui.QtIcon(icons + '/MainWindowUI_image0.png'))
        helpMenu.addAction(aboutAction)

        self.connect(aboutAction,SIGNAL("activated()"),self.helpAbout)

        ##############################################

        self.setMenuBar(self.menubar)

        centralwidget = QWidget()
        self.setCentralWidget(centralwidget)
        layout = QVBoxLayout(centralwidget)
        layout.setMargin(0)
        layout.setSpacing(0)
        middlewidget = QWidget()

        self.bigButtons = QWidget()
        bblo = QHBoxLayout(self.bigButtons)
        bblo.setMargin(0)
        bblo.setSpacing(0)
        self.bigButtons.setMinimumHeight(50)
        self.bigButtons.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        for name in ('Features', 'Sketch', 'Build', 'Dimension', 'Simulator'):
            btn = QPushButton(self.bigButtons)
            btn.setMaximumWidth(80)
            btn.setMinimumHeight(50)
            btn.setText(name)
            self.bigButtons.layout().addWidget(btn)
        self.bigButtons.hide()
        layout.addWidget(self.bigButtons)

        self.littleIcons = QWidget()
        self.littleIcons.setMinimumHeight(30)
        self.littleIcons.setMaximumHeight(30)
        lilo = QHBoxLayout(self.littleIcons)
        lilo.setMargin(0)
        lilo.setSpacing(0)
        pb = QPushButton(self.littleIcons)
        pb.setIcon(QIcon(icons + '/GroupPropDialog_image0.png'))
        self.connect(pb,SIGNAL("clicked()"),self.fileNew)
        lilo.addWidget(pb)
        for x in "1 2 4 5 6 7 8 18 42 10 43 150 93 94 97 137".split():
            pb = QPushButton(self.littleIcons)
            pb.setIcon(QIcon(icons + '/MainWindowUI_image' + x + '.png'))
            lilo.addWidget(pb)
        layout.addWidget(self.littleIcons)

        layout.addWidget(middlewidget)

        self.layout = QGridLayout(middlewidget)
        self.layout.setMargin(0)
        self.layout.setSpacing(2)
        self.gridPosition = GridPosition()
        self.numParts = 0

        self.show()
        explainWindow = AboutWindow("Select <b>Help-&gt;About</b>"
                                    " for instructions...",
                                    200, 3)
Ejemplo n.º 33
0
class Ui_MainWindow(object):
    """
    The Ui_MainWindow class creates the main window and all its widgets.
    """
    def setupUi(self):
        """
        Sets up the main window UI in the following order:
        - Create all main window widgets used by menus and/or toolbars.
        - Create main menu bar and its menus
        - Create main toolbars
        - Create the statusbar
        """
        self.setObjectName("MainWindow")
        self.setEnabled(True)
        self.setWindowIcon(geticon("ui/border/MainWindow.png"))
        self.setWindowTitle("NanoEngineer-1")

        # Set minimum width and height of the main window.
        # To do: Check that the current screen size is at least 800 x 600.
        MINIMUM_MAIN_WINDOW_SIZE = (800, 600) # Mark 2008-02-08
        self.setMinimumWidth(MINIMUM_MAIN_WINDOW_SIZE[0])
        self.setMinimumHeight(MINIMUM_MAIN_WINDOW_SIZE[1])

        # Create all widgets for all main window menus and toolbars.
        Ui_MainWindowWidgets.setupUi(self)

        # Set up all main window widget connections to their slots.
        Ui_MainWindowWidgetConnections.setupUi(self)

        # Toolbars should come before menus. The only reason this is necessary
        # is that the "View > Toolbars" submenu needs the main window toolbars
        # to be created before it is created (in
        self._setupToolbars()
        self._setupMenus()

        # Now set all UI text for main window widgets.
        # Note: I intend to compile all retranslateUi() functions into a single
        # function/method soon. mark 2007-12-31.
        self._retranslateUi()

        # The central widget of the NE1 main window contains a VBoxLayout
        # containing the Command Toolbar at top. Below that will be either:
        # 1. a Part Window widget (the default), or
        # 2. a QWorkspace widget (for MDI support) which will contain only
        #    a single Part Window (experimental)
        # The QWorkspace can be enabled by setting the following debug pref
        # to True: "Enable QWorkspace for MDI support? (next session)"
        # Mark 2007-12-31
        _centralWidget = QWidget()
        self.setCentralWidget(_centralWidget)
        self.centralAreaVBoxLayout = QVBoxLayout(_centralWidget)
        self.centralAreaVBoxLayout.setMargin(0)
        self.centralAreaVBoxLayout.setSpacing(0)

        # Add the Command Toolbar to the top of the central widget.
        # Note: Main window widgets must have their text set
        # via retranslateUi() before instantiating CommandToolbar.
        from commandToolbar.CommandToolbar import CommandToolbar
        self.commandToolbar = CommandToolbar(self)
        self.centralAreaVBoxLayout.addWidget(self.commandToolbar)

        # Create the statusbar for the main window.
        self.setStatusBar(StatusBar(self))

        # =

        # Create the "What's This?" text for all main window widgets.
        from ne1_ui.WhatsThisText_for_MainWindow import createWhatsThisTextForMainWindowWidgets
        createWhatsThisTextForMainWindowWidgets(self)

        # IMPORTANT: All main window widgets and their "What's This" text should
        # be created before this line. [If this is not possible, we'll need to
        # split out some functions within this one which can be called
        # later on individual QActions and/or QWidgets. bruce 060319]
        from foundation.whatsthis_utilities import fix_whatsthis_text_and_links
        fix_whatsthis_text_and_links(self)
            # (main call) Fixes bug 1136.  Mark 051126.
        fix_whatsthis_text_and_links(self.toolsMoveRotateActionGroup)
            # This is needed to add links to the "Translate" and "Rotate"
            # QAction widgets on the standard toolbar, since those two
            # widgets are not direct children of the main window.
            # Fixes one of the many bugs listed in bug 2412. Mark 2007-12-19

    def _setupToolbars(self):
        """
        Populates all Main Window toolbars.
        Also restores the state of toolbars from the NE1 last session.
        """

        # Add toolbars to the Top area of the main window (first row)
        toolbarArea = Qt.TopToolBarArea
        Ui_StandardToolBar.setupUi(self, toolbarArea)
        Ui_BuildToolsToolBar.setupUi(self, toolbarArea)
        self.addToolBarBreak (toolbarArea) # Starts second row.
        Ui_ViewToolBar.setupUi(self, toolbarArea)
        Ui_StandardViewsToolBar.setupUi(self, toolbarArea)
        Ui_SimulationToolBar.setupUi(self, toolbarArea)

        # Add toolbars to the Right area of the main window.
        toolbarArea = Qt.RightToolBarArea
        Ui_SelectToolBar.setupUi(self, toolbarArea)
        Ui_DisplayStylesToolBar.setupUi(self, toolbarArea)
        Ui_RenderingToolBar.setupUi(self, toolbarArea)

        # This is hidden the first time NE1 starts (below).
        # BTW, I don't think the "Build Structures" toolbar is necessary since
        # all its options are available prominentaly on the Command Toolbar.
        # I intend to completely remove it soon. Mark 2008-02-29.
        Ui_BuildStructuresToolBar.setupUi(self, toolbarArea)

        from utilities.prefs_constants import toolbar_state_prefs_key
        # This fixes bug 2482.
        if not env.prefs[toolbar_state_prefs_key] == 'defaultToolbarState':
            # Restore the state of the toolbars from the last session.
            toolBarState = QtCore.QByteArray(env.prefs[toolbar_state_prefs_key])
            self.restoreState(toolBarState)
        else:
            # No previous session. Hide only these toolbars by default.
            self.buildStructuresToolBar.hide()

        return

    def _setupMenus(self):
        """
        Populates all main window menus and adds them to the main menu bar.
        """

        # Populate the menus that appear in the main window menu bar.
        Ui_FileMenu.setupUi(self)
        Ui_EditMenu.setupUi(self)
        Ui_ViewMenu.setupUi(self)
        Ui_InsertMenu.setupUi(self)
        Ui_ToolsMenu.setupUi(self)
        Ui_SimulationMenu.setupUi(self)
        Ui_RenderingMenu.setupUi(self)
        Ui_HelpMenu.setupUi(self)

        # Add menus to the main window menu bar.
        self.MenuBar.addAction(self.fileMenu.menuAction())
        self.MenuBar.addAction(self.editMenu.menuAction())
        self.MenuBar.addAction(self.viewMenu.menuAction())
        self.MenuBar.addAction(self.insertMenu.menuAction())
        self.MenuBar.addAction(self.toolsMenu.menuAction())
        self.MenuBar.addAction(self.simulationMenu.menuAction())
        self.MenuBar.addAction(self.renderingMenu.menuAction())
        self.MenuBar.addAction(self.helpMenu.menuAction())

        # Add the MenuBar to the main window.
        self.setMenuBar(self.MenuBar)

        return

    def _retranslateUi(self):
        """
        This method centralizes all calls that set UI text for the purpose of
        making it easier for the programmer to translate the UI into other
        languages.

        @see: U{B{The Qt Linquist Manual}<http://doc.trolltech.com/4/linguist-manual.html>}
        """
        self.setWindowTitle(QtGui.QApplication.translate(
            "MainWindow", "NanoEngineer-1",
            None, QtGui.QApplication.UnicodeUTF8))

        # QActions
        Ui_MainWindowWidgets.retranslateUi(self)

        # Toolbars
        Ui_StandardToolBar.retranslateUi(self)
        Ui_ViewToolBar.retranslateUi(self)
        Ui_StandardViewsToolBar.retranslateUi(self)
        Ui_DisplayStylesToolBar.retranslateUi(self)
        Ui_BuildStructuresToolBar.retranslateUi(self)
        Ui_BuildToolsToolBar.retranslateUi(self)
        Ui_SelectToolBar.retranslateUi(self)
        Ui_SimulationToolBar.retranslateUi(self)
        Ui_RenderingToolBar.retranslateUi(self)

        # Menus and submenus
        Ui_FileMenu.retranslateUi(self)
        Ui_EditMenu.retranslateUi(self)
        Ui_ViewMenu.retranslateUi(self)
        Ui_InsertMenu.retranslateUi(self)
        Ui_ToolsMenu.retranslateUi(self)
        Ui_SimulationMenu.retranslateUi(self)
        Ui_RenderingMenu.retranslateUi(self)
        Ui_HelpMenu.retranslateUi(self)
Ejemplo n.º 34
0
class PM_Dialog( QDialog, SponsorableMixin ):
    """
    The PM_Dialog class is the base class for Property Manager dialogs.
    
    [To make a PM class from this superclass, subclass it to customize
    the widget set and add behavior.
    You must also provide certain methods that used to be provided by
    GeneratorBaseClass, including ok_btn_clicked and several others,
    including at least some defined by SponsorableMixin (open_sponsor_homepage,
    setSponsor).
    This set of requirements may be cleaned up.]
    """
    
    headerTitleText  = ""  # The header title text.
    
    _widgetList = [] # A list of all group boxes in this PM dialog, 
                     # including the message group box
                     # (but not header, sponsor button, etc.)
                     
    _groupBoxCount = 0 # Number of PM_GroupBoxes in this PM dialog
    
    _lastGroupBox = None # The last PM_GroupBox in this PM dialog
                         # (i.e. the most recent PM_GroupBox added)
    
    def __init__(self, 
                 name,
                 iconPath = "",
                 title    = ""
                 ):
        """
        Property Manager constructor.
        
        @param name: the name to assign the property manager dialog object.
        @type  name: str
        
        @param iconPath: the relative path for the icon (PNG image) that 
                         appears in the header.
        @type  iconPath: str
        
        @param title: the title that appears in the header.
        @type  title: str
        """
        
        QDialog.__init__(self)
        
        self.setObjectName(name)
        self._widgetList = [] 
        
        # Main pallete for PropMgr.
        
        self.setPalette(QPalette(pmColor))
        
        # Main vertical layout for PropMgr.
        self.vBoxLayout = QVBoxLayout(self)
        self.vBoxLayout.setMargin(PM_MAINVBOXLAYOUT_MARGIN)
        self.vBoxLayout.setSpacing(PM_MAINVBOXLAYOUT_SPACING)

        # Add PropMgr's header, sponsor button, top row buttons and (hidden) 
        # message group box.
        self._createHeader(iconPath, title)
        self._createSponsorButton()
        self._createTopRowBtns() # Create top buttons row
        
        self.MessageGroupBox = PM_MessageGroupBox(self)
        
        # Keep the line below around; it might be useful.
        # I may want to use it now that I understand it.
        # Mark 2007-05-17.
        #QMetaObject.connectSlotsByName(self)
        
        self._addGroupBoxes()
        
                
        try:
            self._addWhatsThisText()
        except:
            print_compact_traceback("Error loading whatsthis text for this "
                                    "property manager: ")
        
        try:
            self._addToolTipText()
        except:
            print_compact_traceback("Error loading tool tip text for this "
                                    "property manager: ")            
            
        #The following attr is used for comparison in method
        #'_update_UI_wanted_as_something_changed'
        self._previous_all_change_indicators = None
        

    def update_UI(self):
        """
        Update whatever is shown in this PM based on current state
        of the rest of the system, especially the state of self.command
        and of the model it shows.

        This is part of the PM API required by baseCommand,
        since it's called by baseCommand.command_update_UI.
        
        @note: Overridden in Command_PropertyManager, but should not be
               overridden in its subclasses. See that method's docstring
               for details.
        """
        #bruce 081125 defined this here, to fix bugs in example commands
        return
    
    def keyPressEvent(self, event):
        """
        Handles keyPress event. 
        
        @note: Subclasses should carefully override this. 
        Note that the default implementation doesn't permit ESC key 
        as a way to close the PM_dialog. (This is typically desirable
        for Property Managers.) If any subclass needs to implement the key press, 
        they should first call this method (i.e. superclass.keyPressEvent) and then 
        implement specific code that closes the dialog when ESC key is pressed.
        """
        key = event.key()
        # Don't use ESC key to close the PM dialog. Fixes bug 2596
        if key == Qt.Key_Escape:
            pass
        else:
            QDialog.keyPressEvent(self, event) 
        return
                                    
    
    def _addGroupBoxes(self):
        """
        Add various group boxes to this PM. Subclasses should override this
        method.
        """
        pass

    def _addWhatsThisText(self):
        """
        Add what's this text. 
        Subclasses should override this method. 
        """
        pass
    
    def _addToolTipText(self):
        """
        Add Tool tip text. 
        Subclasses should override this method. 
        """
        pass
    
    
    def show(self):
        """
        Shows the Property Manager.
        """
        self.setSponsor()
        
        # Show or hide the sponsor logo based on whether the user gave 
        # permission to download sponsor logos.
        if env.prefs[sponsor_download_permission_prefs_key]:
            self.sponsorButtonContainer.show()
        else:
            self.sponsorButtonContainer.hide()
        
        if not self.pw or self:            
            self.pw = self.win.activePartWindow()
            
        self.pw.updatePropertyManagerTab(self)
        self.pw.pwProjectTabWidget.setCurrentIndex(
            self.pw.pwProjectTabWidget.indexOf(self))
        
        # Show the default message whenever we open the Property Manager.
        self.MessageGroupBox.MessageTextEdit.restoreDefault()
        
    def open(self, pm):
        """
        Closes the current property manager (if any) and opens the 
        property manager I{pm}.
        
        @param pm: The property manager to open.
        @type  pm: L{PM_Dialog} or QDialog (of legacy PMs)
        
        @attention: This method is a temporary workaround for "Insert > Plane".
                    The current command should always be responsible for 
                    (re)opening its own PM via self.show().
                    
        @see: L{show()}
        """
        if 1:
            commandSequencer = self.win.commandSequencer #bruce 071008
            commandName = commandSequencer.currentCommand.commandName
                # that's an internal name, but this is just for a debug print
            print "PM_Dialog.open(): Reopening the PM for command:", commandName
        
        # The following line of code is buggy when you, for instance, exit a PM
        # and reopen the previous one. It sends the disconnect signal twice to 
        # the PM that is just closed.So disabling this line -- Ninad 2007-12-04
        
        ##self.close() # Just in case there is another PM open.

        self.pw = self.win.activePartWindow()         
        self.pw.updatePropertyManagerTab(pm)
        try:
            pm.setSponsor()
        except:
            print """PM_Dialog.open(): pm has no attribute 'setSponsor()'  
                     ignoring."""
        self.pw.pwProjectTabWidget.setCurrentIndex(
            self.pw.pwProjectTabWidget.indexOf(pm))
        
    def close(self):
        """
        Closes the Property Manager.
        """
        if not self.pw:
            self.pw = self.win.activePartWindow() 
        self.pw.pwProjectTabWidget.setCurrentIndex(0)
        
        ## try: [bruce 071018 moved this lower, since errmsg only covers attr]
        pmWidget = self.pw.propertyManagerScrollArea.widget()
        if debug_flags.atom_debug: #bruce 071018
            "atom_debug fyi: %r is closing %r (can they differ?)" % \
                        (self, pmWidget)
        try:
            pmWidget.update_props_if_needed_before_closing
        except AttributeError:
            if 1 or debug_flags.atom_debug:
                msg1 = "Last PropMgr %r doesn't have method" % pmWidget
                msg2 = " update_props_if_needed_before_closing. That's"
                msg3 = " OK (for now, only implemented for Plane PM). "
                msg4 = "Ignoring Exception: "
                print_compact_traceback(msg1 + msg2 + msg3 + msg4)
                #bruce 071018: I'll define that method in PM_Dialog
                # so this message should become rare or nonexistent,
                # so I'll make it happen whether or not atom_debug.
        else:
            pmWidget.update_props_if_needed_before_closing()
                    
        self.pw.pwProjectTabWidget.removeTab(
            self.pw.pwProjectTabWidget.indexOf(
                self.pw.propertyManagerScrollArea))
        
        if self.pw.propertyManagerTab:
            self.pw.propertyManagerTab = None

    def update_props_if_needed_before_closing(self):
        # bruce 071018 default implem
        """
        Subclasses can override this to update some cosmetic properties
        of their associated model objects before closing self
        (the Property Manager).
        """
        pass
    
    def updateMessage(self, msg = ''):
        """
        Updates the message box with an informative message
        @param msg: Message to be displayed in the Message groupbox of 
                        the property manager
        @type  msg: string
        """
        self.MessageGroupBox.insertHtmlMessage(msg, 
                                               setAsDefault = False,
                                               minLines     = 5) 
        
    def _createHeader(self, iconPath, title):
        """
        Creates the Property Manager header, which contains an icon
        (a QLabel with a pixmap) and white text (a QLabel with text).
        
        @param iconPath: The relative path for the icon (PNG image) that 
                         appears in the header.
        @type  iconPath: str
        
        @param title: The title that appears in the header.
        @type  title: str
        """
        
        # Heading frame (dark gray), which contains 
        # a pixmap and (white) heading text.
        self.headerFrame = QFrame(self)
        self.headerFrame.setFrameShape(QFrame.NoFrame)
        self.headerFrame.setFrameShadow(QFrame.Plain)
        
        self.headerFrame.setPalette(QPalette(pmHeaderFrameColor))
        self.headerFrame.setAutoFillBackground(True)

        # HBox layout for heading frame, containing the pixmap
        # and label (title).
        HeaderFrameHLayout = QHBoxLayout(self.headerFrame)
        # 2 pixels around edges --
        HeaderFrameHLayout.setMargin(PM_HEADER_FRAME_MARGIN) 
        # 5 pixel between pixmap and label. --
        HeaderFrameHLayout.setSpacing(PM_HEADER_FRAME_SPACING) 

        # PropMgr icon. Set image by calling setHeaderIcon().
        self.headerIcon = QLabel(self.headerFrame)
        self.headerIcon.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Fixed),
                              QSizePolicy.Policy(QSizePolicy.Fixed)))
            
        self.headerIcon.setScaledContents(True)
        
        HeaderFrameHLayout.addWidget(self.headerIcon)
        
        # PropMgr header title text (a QLabel).
        self.headerTitle = QLabel(self.headerFrame)
        headerTitlePalette = self._getHeaderTitlePalette()
        self.headerTitle.setPalette(headerTitlePalette)
        self.headerTitle.setAlignment(PM_LABEL_LEFT_ALIGNMENT)

        # Assign header title font.
        self.headerTitle.setFont(self._getHeaderFont())
        HeaderFrameHLayout.addWidget(self.headerTitle)
        
        self.vBoxLayout.addWidget(self.headerFrame)
        
        # Set header icon and title text.
        self.setHeaderIcon(iconPath)
        self.setHeaderTitle(title)
        
    def _getHeaderFont(self):
        """
        Returns the font used for the header.
        
        @return: the header font
        @rtype:  QFont
        """
        font = QFont()
        font.setFamily(PM_HEADER_FONT)
        font.setPointSize(PM_HEADER_FONT_POINT_SIZE)
        font.setBold(PM_HEADER_FONT_BOLD)
        return font
        
    def setHeaderTitle(self, title):
        """
        Set the Property Manager header title to string <title>.
        
        @param title: the title to insert in the header.
        @type  title: str
        """
        self.headerTitleText = title
        self.headerTitle.setText(title)
    
    def setHeaderIcon(self, iconPath):
        """
        Set the Property Manager header icon.
        
        @param iconPath: the relative path to the PNG file containing the 
                         icon image.
        @type  iconPath: str
        """
        
        if not iconPath:
            return
        
        self.headerIcon.setPixmap(getpixmap(iconPath))
        
    def _createSponsorButton(self):
        """
        Creates the Property Manager sponsor button, which contains
        a QPushButton inside of a QGridLayout inside of a QFrame.
        The sponsor logo image is not loaded here.
        """
        
        # Sponsor button (inside a frame)
        self.sponsorButtonContainer = QWidget(self)

        SponsorFrameGrid = QGridLayout(self.sponsorButtonContainer)
        SponsorFrameGrid.setMargin(PM_SPONSOR_FRAME_MARGIN)
        SponsorFrameGrid.setSpacing(PM_SPONSOR_FRAME_SPACING) # Has no effect.

        self.sponsor_btn = QToolButton(self.sponsorButtonContainer)
        self.sponsor_btn.setAutoRaise(True)
        self.connect(self.sponsor_btn,
                     SIGNAL("clicked()"),
                     self.open_sponsor_homepage)
        
        SponsorFrameGrid.addWidget(self.sponsor_btn, 0, 0, 1, 1)
        
        self.vBoxLayout.addWidget(self.sponsorButtonContainer)

        button_whatsthis_widget = self.sponsor_btn
        #bruce 070615 bugfix -- put tooltip & whatsthis on self.sponsor_btn, 
        # not self.
        # [self.sponsorButtonContainer might be another possible place to put them.]
        
        button_whatsthis_widget.setWhatsThis("""<b>Sponsor Button</b>
            <p>When clicked, this sponsor logo will display a short 
            description about a NanoEngineer-1 sponsor. This can 
            be an official sponsor or credit given to a contributor 
            that has helped code part or all of this command. 
            A link is provided in the description to learn more 
            about this sponsor.</p>""")
        
        button_whatsthis_widget.setToolTip("NanoEngineer-1 Sponsor Button")
        
        return

    def _createTopRowBtns(self):
        """
        Creates the Done, Cancel, Preview, Restore Defaults and What's This 
        buttons row at the top of the Property Manager.
        """        
        topBtnSize = QSize(22, 22) # button images should be 16 x 16, though.
        
        # Main "button group" widget (but it is not a QButtonGroup).
        self.pmTopRowBtns = QHBoxLayout()
        # This QHBoxLayout is (probably) not necessary. Try using just the frame
        # for the foundation. I think it should work. Mark 2007-05-30
        
        # Horizontal spacer
        horizontalSpacer = QSpacerItem(1, 1, 
                                QSizePolicy.Expanding, 
                                QSizePolicy.Minimum)
        
        # Widget containing all the buttons.
        self.topRowBtnsContainer = QWidget()
        
        # Create Hbox layout for main frame.
        topRowBtnsHLayout = QHBoxLayout(self.topRowBtnsContainer)
        topRowBtnsHLayout.setMargin(PM_TOPROWBUTTONS_MARGIN)
        topRowBtnsHLayout.setSpacing(PM_TOPROWBUTTONS_SPACING)
        
        # Set to True to center align the buttons in the PM
        if False: # Left aligns the buttons.
            topRowBtnsHLayout.addItem(horizontalSpacer)
        
        # Done (OK) button.
        self.done_btn = QToolButton(self.topRowBtnsContainer)
        self.done_btn.setIcon(
            geticon("ui/actions/Properties Manager/Done_16x16.png"))
        self.done_btn.setIconSize(topBtnSize)  
        self.done_btn.setAutoRaise(True) 
        self.connect(self.done_btn,
                     SIGNAL("clicked()"),
                     self.doneButtonClicked)
        self.done_btn.setToolTip("Done")
        
        topRowBtnsHLayout.addWidget(self.done_btn)
        
        # Cancel (Abort) button.
        self.cancel_btn = QToolButton(self.topRowBtnsContainer)
        self.cancel_btn.setIcon(
            geticon("ui/actions/Properties Manager/Abort_16x16.png"))
        self.cancel_btn.setIconSize(topBtnSize) 
        self.cancel_btn.setAutoRaise(True) 
        self.connect(self.cancel_btn,
                     SIGNAL("clicked()"),
                     self.cancelButtonClicked)
        self.cancel_btn.setToolTip("Cancel")
        
        topRowBtnsHLayout.addWidget(self.cancel_btn)
        
        #@ abort_btn deprecated. We still need it because modes use it. 
        self.abort_btn = self.cancel_btn
        
        # Restore Defaults button.
        self.restore_defaults_btn = QToolButton(self.topRowBtnsContainer)
        self.restore_defaults_btn.setIcon(
            geticon("ui/actions/Properties Manager/Restore_16x16.png"))
        self.restore_defaults_btn.setIconSize(topBtnSize) 
        self.restore_defaults_btn.setAutoRaise(True) 
        self.connect(self.restore_defaults_btn,
                     SIGNAL("clicked()"),
                     self.restoreDefaultsButtonClicked)
        self.restore_defaults_btn.setToolTip("Restore Defaults")
        topRowBtnsHLayout.addWidget(self.restore_defaults_btn)
        
        # Preview (glasses) button.
        self.preview_btn = QToolButton(self.topRowBtnsContainer)
        self.preview_btn.setIcon(
            geticon("ui/actions/Properties Manager/Preview_16x16.png"))
        self.preview_btn.setIconSize(topBtnSize) 
        self.preview_btn.setAutoRaise(True) 
        self.connect(self.preview_btn,
                     SIGNAL("clicked()"),
                     self.previewButtonClicked)
        self.preview_btn.setToolTip("Preview")
        
        topRowBtnsHLayout.addWidget(self.preview_btn)        
        
        # What's This (?) button.
        self.whatsthis_btn = QToolButton(self.topRowBtnsContainer)
        self.whatsthis_btn.setIcon(
            geticon("ui/actions/Properties Manager/WhatsThis_16x16.png"))
        self.whatsthis_btn.setIconSize(topBtnSize) 
        self.whatsthis_btn.setAutoRaise(True) 
        self.connect(self.whatsthis_btn,
                     SIGNAL("clicked()"),
                     self.whatsThisButtonClicked)
        self.whatsthis_btn.setToolTip("Enter \"What's This\" help mode")
        
        topRowBtnsHLayout.addWidget(self.whatsthis_btn)
        
        topRowBtnsHLayout.addItem(horizontalSpacer)
        
        # Create Button Row
        self.pmTopRowBtns.addWidget(self.topRowBtnsContainer)
        
        self.vBoxLayout.addLayout(self.pmTopRowBtns)
        
        # Add What's This for buttons.
        
        self.done_btn.setWhatsThis("""<b>Done</b>
            <p>
            <img source=\"ui/actions/Properties Manager/Done_16x16.png\"><br>
            Completes and/or exits the current command.</p>""")
        
        self.cancel_btn.setWhatsThis("""<b>Cancel</b>
            <p>
            <img source=\"ui/actions/Properties Manager/Abort_16x16.png\"><br>
            Cancels the current command.</p>""")
        
        self.restore_defaults_btn.setWhatsThis("""<b>Restore Defaults</b>
            <p><img source=\"ui/actions/Properties Manager/Restore_16x16.png\"><br>
            Restores the defaut values of the Property Manager.</p>""")
        
        self.preview_btn.setWhatsThis("""<b>Preview</b>
            <p>
            <img source=\"ui/actions/Properties Manager/Preview_16x16.png\"><br>
            Preview the structure based on current Property Manager settings.
            </p>""")

        self.whatsthis_btn.setWhatsThis("""<b>What's This</b> 
            <p>
            <img source=\"ui/actions/Properties Manager/WhatsThis_16x16.png\"><br>
            This invokes \"What's This?\" help mode which is part of 
            NanoEngineer-1's online help system, and provides users with 
            information about the functionality and usage of a particular 
            command button or widget.
            </p>""")
        
        return

    def hideTopRowButtons(self, pmButtonFlags = None):
        """
        Hides one or more top row buttons using <pmButtonFlags>.
        Button flags not set will cause the button to be shown
        if currently hidden.
        
        @param pmButtonFlags: This enumerator describes the which buttons to 
                              hide, where:
        
            - PM_DONE_BUTTON            =  1
            - PM_CANCEL_BUTTON          =  2
            - PM_RESTORE_DEFAULTS_BUTTON =  4
            - PM_PREVIEW_BUTTON         =  8
            - PM_WHATS_THIS_BUTTON       = 16
            - PM_ALL_BUTTONS            = 31
            
        @type  pmButtonFlags: int
        """
        
        if pmButtonFlags & PM_DONE_BUTTON: 
            self.done_btn.hide()
        else: 
            self.done_btn.show()
            
        if pmButtonFlags & PM_CANCEL_BUTTON: 
            self.cancel_btn.hide()
        else: 
            self.cancel_btn.show()
            
        if pmButtonFlags & PM_RESTORE_DEFAULTS_BUTTON: 
            self.restore_defaults_btn.hide()
        else: 
            self.restore_defaults_btn.show()
            
        if pmButtonFlags & PM_PREVIEW_BUTTON: 
            self.preview_btn.hide()
        else: 
            self.preview_btn.show()
            
        if pmButtonFlags & PM_WHATS_THIS_BUTTON: 
            self.whatsthis_btn.hide()
        else: 
            self.whatsthis_btn.show()
        
    def showTopRowButtons(self, pmButtonFlags = PM_ALL_BUTTONS):
        """
        Shows one or more top row buttons using <pmButtonFlags>.
        Button flags not set will cause the button to be hidden
        if currently displayed.
        
        @param pmButtonFlags: this enumerator describes which buttons to 
        display, where:
        
            - PM_DONE_BUTTON            =  1
            - PM_CANCEL_BUTTON          =  2
            - PM_RESTORE_DEFAULTS_BUTTON =  4
            - PM_PREVIEW_BUTTON         =  8
            - PM_WHATS_THIS_BUTTON       = 16
            - PM_ALL_BUTTONS            = 31
            
        @type  pmButtonFlags: int
        """
        
        self.hideTopRowButtons(pmButtonFlags ^ PM_ALL_BUTTONS)
        
    def _getHeaderTitlePalette(self):
        """
        Return a palette for header title (text) label. 
        """
        palette = QPalette()
        palette.setColor(QPalette.WindowText, pmHeaderTitleColor)
        return palette
        
    def doneButtonClicked(self): # note: never overridden, as of 080815
        """
        Slot for the Done button.
        """
        self.ok_btn_clicked()
    
    def cancelButtonClicked(self): # note: never overridden, as of 080815
        """
        Slot for the Cancel button.
        """
        self.cancel_btn_clicked()
    
    def restoreDefaultsButtonClicked(self):
        """
        Slot for "Restore Defaults" button in the Property Manager.
        It is called each time the button is clicked.
        """
        for widget in self._widgetList:
            if isinstance(widget, PM_GroupBox):
                widget.restoreDefault()
                         
    def previewButtonClicked(self):
        """
        Slot for the Preview button.
        """
        self.preview_btn_clicked()
        
    def whatsThisButtonClicked(self):
        """
        Slot for the What's This button.
        """
        QWhatsThis.enterWhatsThisMode()

    # default implementations for subclasses
    # [bruce 080815 pulled these in from subclasses]

    def ok_btn_clicked(self):
        """
        Implements Done button. Called by its slot method in PM_Dialog.
        
        [subclasses can override as needed]
        """      
        self.win.toolsDone()

    def cancel_btn_clicked(self):
        """
        Implements Cancel button. Called by its slot method in PM_Dialog.

        [subclasses can override as needed]
        """  
        # Note: many subclasses override this to call self.w.toolsDone
        # (rather than toolsCancel). This should be cleaned up
        # so those overrides are not needed. (Maybe they are already
        # not needed.) [bruce 080815 comment]
        
        self.win.toolsCancel()

    pass
Ejemplo n.º 35
0
class MainWindow(QMainWindow):
    about_message = """
    <P>PURR ("<B>P</B>URR is <B>U</B>seful for <B>R</B>emembering <B>R</B>eductions", for those working with
    a stable version, or "<B>P</B>URR <B>U</B>sually <B>R</B>emembers <B>R</B>eductions", for those
    working with a development version, or "<B>P</B>URR <B>U</B>sed to <B>R</B>emember <B>R</B>eductions",
    for those working with a broken version) is a tool for
    automatically keeping a log of your data reduction operations. PURR will monitor your working directories
    for new or updated files (called "data products"), and upon seeing any, it can "pounce" -- that is, offer
    you the option of saving the files to a log, along with descriptive comments. It will then
    generate an HTML page with a pretty rendering of your log and data products.</P>
  """

    def __init__(self, parent, hide_on_close=False):
        QMainWindow.__init__(self, parent)
        self._hide_on_close = hide_on_close
        # replace the BusyIndicator class with a GUI-aware one
        Purr.BusyIndicator = BusyIndicator
        self._pounce = False
        # we keep a small stack of previously active purrers. This makes directory changes
        # faster (when going back and forth between dirs)
        # current purrer
        self.purrer = None
        self.purrer_stack = []
        # Purr pipes for receiving remote commands
        self.purrpipes = {}
        # init GUI
        self.setWindowTitle("PURR")
        self.setWindowIcon(pixmaps.purr_logo.icon())
        cw = QWidget(self)
        self.setCentralWidget(cw)
        cwlo = QVBoxLayout(cw)
        cwlo.setContentsMargins(0, 0, 0, 0)
        cwlo.setMargin(5)
        cwlo.setSpacing(0)
        toplo = QHBoxLayout();
        cwlo.addLayout(toplo)

        # About dialog
        self._about_dialog = QMessageBox(self)
        self._about_dialog.setWindowTitle("About PURR")
        self._about_dialog.setText(self.about_message + """
        <P>PURR is not watching any directories right now. You may need to restart it, and give it
  some directory names on the command line.</P>""")
        self._about_dialog.setIconPixmap(pixmaps.purr_logo.pm())
        # Log viewer dialog
        self.viewer_dialog = HTMLViewerDialog(self, config_name="log-viewer",
                                              buttons=[(pixmaps.blue_round_reload, "Regenerate",
                                                        """<P>Regenerates your log's HTML code from scratch. This can be useful if
                                                        your PURR version has changed, or if there was an error of some kind
                                                        the last time the files were generated.</P>
                                                        """)])
        self._viewer_timestamp = None
        self.connect(self.viewer_dialog, SIGNAL("Regenerate"), self._regenerateLog)
        self.connect(self.viewer_dialog, SIGNAL("viewPath"), self._viewPath)

        # Log title toolbar
        title_tb = QToolBar(cw)
        title_tb.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        title_tb.setIconSize(QSize(16, 16))
        cwlo.addWidget(title_tb)
        title_label = QLabel("Purrlog title:", title_tb)
        title_tb.addWidget(title_label)
        self.title_editor = QLineEdit(title_tb)
        title_tb.addWidget(self.title_editor)
        self.connect(self.title_editor, SIGNAL("editingFinished()"), self._titleChanged)
        tip = """<P>This is your current log title. To rename the log, enter new name here and press Enter.</P>"""
        title_label.setToolTip(tip)
        self.title_editor.setToolTip(tip)
        self.wviewlog = title_tb.addAction(pixmaps.openbook.icon(), "View", self._showViewerDialog)
        self.wviewlog.setToolTip("Click to see an HTML rendering of your current log.")
        qa = title_tb.addAction(pixmaps.purr_logo.icon(), "About...", self._about_dialog.exec_)
        qa.setToolTip("<P>Click to see the About... dialog, which will tell you something about PURR.</P>")

        self.wdirframe = QFrame(cw)
        cwlo.addWidget(self.wdirframe)
        self.dirs_lo = QVBoxLayout(self.wdirframe)
        self.dirs_lo.setMargin(5)
        self.dirs_lo.setContentsMargins(5, 0, 5, 5)
        self.dirs_lo.setSpacing(0)
        self.wdirframe.setFrameStyle(QFrame.Box | QFrame.Raised)
        self.wdirframe.setLineWidth(1)

        ## Directories toolbar
        dirs_tb = QToolBar(self.wdirframe)
        dirs_tb.setToolButtonStyle(Qt.ToolButtonIconOnly)
        dirs_tb.setIconSize(QSize(16, 16))
        self.dirs_lo.addWidget(dirs_tb)
        label = QLabel("Monitoring directories:", dirs_tb)
        self._dirs_tip = """<P>PURR can monitor your working directories for new or updated files. If there's a checkmark
      next to the directory name in this list, PURR is monitoring it.</P>

      <P>If the checkmark is grey, PURR is monitoring things unobtrusively. When a new or updated file is detected in he monitored directory,
      it is quietly added to the list of files in the "New entry" window, even if this window is not currently visible.</P>

      <P>If the checkmark is black, PURR will be more obtrusive. Whenever a new or updated file is detected, the "New entry" window will
      pop up automatically. This is called "pouncing", and some people find it annoying.</P>
      """
        label.setToolTip(self._dirs_tip)
        label.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
        dirs_tb.addWidget(label)

        # add directory list widget
        self.wdirlist = DirectoryListWidget(self.wdirframe)
        self.wdirlist.setToolTip(self._dirs_tip)
        QObject.connect(self.wdirlist, SIGNAL("directoryStateChanged"), self._changeWatchedDirState)
        self.dirs_lo.addWidget(self.wdirlist)
        # self.wdirlist.setMaximumSize(1000000,64)

        # add directory button
        add = dirs_tb.addAction(pixmaps.list_add.icon(), "Add", self._showAddDirectoryDialog)
        add.setToolTip("<P>Click to add another directory to be monitored.</P>")

        # remove directory button
        delbtn = dirs_tb.addAction(pixmaps.list_remove.icon(), "Remove", self.wdirlist.removeCurrent)
        delbtn.setEnabled(False)
        delbtn.setToolTip("<P>Click to removed the currently selected directory from the list.</P>")
        QObject.connect(self.wdirlist, SIGNAL("hasSelection"), delbtn.setEnabled)

        #    # qa = dirs_tb.addAction(pixmaps.blue_round_reload.icon(),"Rescan",self._forceRescan)
        #    # qa.setToolTip("Click to rescan the directories for any new or updated files.")
        #    self.wshownew = QCheckBox("show new files",dirs_tb)
        #    dirs_tb.addWidget(self.wshownew)
        #    self.wshownew.setCheckState(Qt.Checked)
        #    self.wshownew.setToolTip("""<P>If this is checked, the "New entry" window will pop up automatically whenever
        #  new or updated files are detected. If this is unchecked, the files will be added to the window quietly
        #        and unobtrusively; you can show the window manually by clicking on the "New entry..." button below.</P>""")
        #    self._dir_entries = {}

        cwlo.addSpacing(5)

        wlogframe = QFrame(cw)
        cwlo.addWidget(wlogframe)
        log_lo = QVBoxLayout(wlogframe)
        log_lo.setMargin(5)
        log_lo.setContentsMargins(5, 5, 5, 5)
        log_lo.setSpacing(0)
        wlogframe.setFrameStyle(QFrame.Box | QFrame.Raised)
        wlogframe.setLineWidth(1)

        # listview of log entries
        self.etw = LogEntryTree(cw)
        log_lo.addWidget(self.etw, 1)
        self.etw.header().setDefaultSectionSize(128)
        self.etw.header().setMovable(False)
        self.etw.setHeaderLabels(["date", "entry title", "comment"])
        if hasattr(QHeaderView, 'ResizeToContents'):
            self.etw.header().setResizeMode(0, QHeaderView.ResizeToContents)
        else:
            self.etw.header().setResizeMode(0, QHeaderView.Custom)
            self.etw.header().resizeSection(0, 120)
        self.etw.header().setResizeMode(1, QHeaderView.Interactive)
        self.etw.header().setResizeMode(2, QHeaderView.Stretch)
        self.etw.header().show()
        try:
            self.etw.setAllColumnsShowFocus(True)
        except AttributeError:
            pass;  # Qt 4.2+
        # self.etw.setShowToolTips(True)
        self.etw.setSortingEnabled(False)
        # self.etw.setColumnAlignment(2,Qt.AlignLeft|Qt.AlignTop)
        self.etw.setSelectionMode(QTreeWidget.ExtendedSelection)
        self.etw.setRootIsDecorated(True)
        self.connect(self.etw, SIGNAL("itemSelectionChanged()"), self._entrySelectionChanged)
        self.connect(self.etw, SIGNAL("itemActivated(QTreeWidgetItem*,int)"), self._viewEntryItem)
        self.connect(self.etw, SIGNAL("itemContextMenuRequested"), self._showItemContextMenu)
        # create popup menu for data products
        self._archived_dp_menu = menu = QMenu(self)
        self._archived_dp_menu_title = QLabel()
        self._archived_dp_menu_title.setMargin(5)
        self._archived_dp_menu_title_wa = wa = QWidgetAction(self)
        wa.setDefaultWidget(self._archived_dp_menu_title)
        menu.addAction(wa)
        menu.addSeparator()
        menu.addAction(pixmaps.editcopy.icon(), "Restore file(s) from archived copy", self._restoreItemFromArchive)
        menu.addAction(pixmaps.editpaste.icon(), "Copy pathname of archived copy to clipboard",
                       self._copyItemToClipboard)
        self._current_item = None
        # create popup menu for entries
        self._entry_menu = menu = QMenu(self)
        self._entry_menu_title = QLabel()
        self._entry_menu_title.setMargin(5)
        self._entry_menu_title_wa = wa = QWidgetAction(self)
        wa.setDefaultWidget(self._entry_menu_title)
        menu.addAction(wa)
        menu.addSeparator()
        menu.addAction(pixmaps.filefind.icon(), "View this log entry", self._viewEntryItem)
        menu.addAction(pixmaps.editdelete.icon(), "Delete this log entry", self._deleteSelectedEntries)
        # buttons at bottom
        log_lo.addSpacing(5)
        btnlo = QHBoxLayout()
        log_lo.addLayout(btnlo)
        self.wnewbtn = QPushButton(pixmaps.filenew.icon(), "New entry...", cw)
        self.wnewbtn.setToolTip("Click to add a new log entry.")
        # self.wnewbtn.setFlat(True)
        self.wnewbtn.setEnabled(False)
        btnlo.addWidget(self.wnewbtn)
        btnlo.addSpacing(5)
        self.weditbtn = QPushButton(pixmaps.filefind.icon(), "View entry...", cw)
        self.weditbtn.setToolTip("Click to view or edit the selected log entry/")
        # self.weditbtn.setFlat(True)
        self.weditbtn.setEnabled(False)
        self.connect(self.weditbtn, SIGNAL("clicked()"), self._viewEntryItem)
        btnlo.addWidget(self.weditbtn)
        btnlo.addSpacing(5)
        self.wdelbtn = QPushButton(pixmaps.editdelete.icon(), "Delete", cw)
        self.wdelbtn.setToolTip("Click to delete the selected log entry or entries.")
        # self.wdelbtn.setFlat(True)
        self.wdelbtn.setEnabled(False)
        self.connect(self.wdelbtn, SIGNAL("clicked()"), self._deleteSelectedEntries)
        btnlo.addWidget(self.wdelbtn)
        # enable status line
        self.statusBar().show()
        Purr.progressMessage = self.message
        self._prev_msg = None
        # editor dialog for new entry
        self.new_entry_dialog = Purr.Editors.NewLogEntryDialog(self)
        self.connect(self.new_entry_dialog, SIGNAL("newLogEntry"), self._newLogEntry)
        self.connect(self.new_entry_dialog, SIGNAL("filesSelected"), self._addDPFiles)
        self.connect(self.wnewbtn, SIGNAL("clicked()"), self.new_entry_dialog.show)
        self.connect(self.new_entry_dialog, SIGNAL("shown"), self._checkPounceStatus)
        # entry viewer dialog
        self.view_entry_dialog = Purr.Editors.ExistingLogEntryDialog(self)
        self.connect(self.view_entry_dialog, SIGNAL("previous()"), self._viewPrevEntry)
        self.connect(self.view_entry_dialog, SIGNAL("next()"), self._viewNextEntry)
        self.connect(self.view_entry_dialog, SIGNAL("viewPath"), self._viewPath)
        self.connect(self.view_entry_dialog, SIGNAL("filesSelected"), self._addDPFilesToOldEntry)
        self.connect(self.view_entry_dialog, SIGNAL("entryChanged"), self._entryChanged)
        # saving a data product to an older entry will automatically drop it from the
        # new entry dialog
        self.connect(self.view_entry_dialog, SIGNAL("creatingDataProduct"),
                     self.new_entry_dialog.dropDataProducts)
        # resize selves
        width = Config.getint('main-window-width', 512)
        height = Config.getint('main-window-height', 512)
        self.resize(QSize(width, height))
        # create timer for pouncing
        self._timer = QTimer(self)
        self.connect(self._timer, SIGNAL("timeout()"), self._rescan)
        # create dict mapping index.html paths to entry numbers
        self._index_paths = {}

    def resizeEvent(self, ev):
        QMainWindow.resizeEvent(self, ev)
        sz = ev.size()
        Config.set('main-window-width', sz.width())
        Config.set('main-window-height', sz.height())

    def closeEvent(self, ev):
        if self._hide_on_close:
            ev.ignore()
            self.hide()
            self.new_entry_dialog.hide()
        else:
            if self.purrer:
                self.purrer.detach()
            return QMainWindow.closeEvent(self, ev)

    def message(self, msg, ms=2000, sub=False):
        if sub:
            if self._prev_msg:
                msg = ": ".join((self._prev_msg, msg))
        else:
            self._prev_msg = msg
        self.statusBar().showMessage(msg, ms)
        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def _changeWatchedDirState(self, pathname, watching):
        self.purrer.setWatchingState(pathname, watching)
        # update dialogs if dir list has changed
        if watching == Purr.REMOVED:
            self.purrpipes.pop(pathname)
            dirs = [path for path, state in self.purrer.watchedDirectories()]
            self.new_entry_dialog.setDefaultDirs(*dirs)
            self.view_entry_dialog.setDefaultDirs(*dirs)
        pass

    def _showAddDirectoryDialog(self):
        dd = str(QFileDialog.getExistingDirectory(self, "PURR: Add a directory to monitor")).strip()
        if dd:
            # adds a watched directory. Default initial setting of 'watching' is POUNCE if all
            # directories are in POUNCE state, or WATCHED otherwise.
            watching = max(Purr.WATCHED,
                           min([state for path, state in self.purrer.watchedDirectories()] or [Purr.WATCHED]))
            self.purrer.addWatchedDirectory(dd, watching)
            self.purrpipes[dd] = Purr.Pipe.open(dd)
            self.wdirlist.add(dd, watching)
            # update dialogs since dir list has changed
            dirs = [path for path, state in self.purrer.watchedDirectories()]
            self.new_entry_dialog.setDefaultDirs(*dirs)
            self.view_entry_dialog.setDefaultDirs(*dirs)

    def detachPurrlog(self):
        self.wdirlist.clear()
        self.purrer and self.purrer.detach()
        self.purrer = None

    def hasPurrlog(self):
        return bool(self.purrer)

    def attachPurrlog(self, purrlog, watchdirs=[]):
        """Attaches Purr to the given purrlog directory. Arguments are passed to Purrer object as is."""
        # check purrer stack for a Purrer already watching this directory
        dprint(1, "attaching to purrlog", purrlog)
        for i, purrer in enumerate(self.purrer_stack):
            if os.path.samefile(purrer.logdir, purrlog):
                dprint(1, "Purrer object found on stack (#%d),reusing\n", i)
                # found? move to front of stack
                self.purrer_stack.pop(i)
                self.purrer_stack.insert(0, purrer)
                # update purrer with watched directories, in case they have changed
                for dd in (watchdirs or []):
                    purrer.addWatchedDirectory(dd, watching=None)
                break
        # no purrer found, make a new one
        else:
            dprint(1, "creating new Purrer object")
            try:
                purrer = Purr.Purrer(purrlog, watchdirs)
            except Purr.Purrer.LockedError as err:
                # check that we could attach, display message if not
                QMessageBox.warning(self, "Catfight!", """<P><NOBR>It appears that another PURR process (%s)</NOBR>
          is already attached to <tt>%s</tt>, so we're not allowed to touch it. You should exit the other PURR
          process first.</P>""" % (err.args[0], os.path.abspath(purrlog)), QMessageBox.Ok, 0)
                return False
            except Purr.Purrer.LockFailError as err:
                QMessageBox.warning(self, "Failed to obtain lock", """<P><NOBR>PURR was unable to obtain a lock</NOBR>
          on directory <tt>%s</tt> (error was "%s"). The most likely cause is insufficient permissions.</P>""" % (
                os.path.abspath(purrlog), err.args[0]), QMessageBox.Ok, 0)
                return False
            self.purrer_stack.insert(0, purrer)
            # discard end of stack
            self.purrer_stack = self.purrer_stack[:3]
            # attach signals
            self.connect(purrer, SIGNAL("disappearedFile"),
                         self.new_entry_dialog.dropDataProducts)
            self.connect(purrer, SIGNAL("disappearedFile"),
                         self.view_entry_dialog.dropDataProducts)
        # have we changed the current purrer? Update our state then
        # reopen Purr pipes
        self.purrpipes = {}
        for dd, state in purrer.watchedDirectories():
            self.purrpipes[dd] = Purr.Pipe.open(dd)
        if purrer is not self.purrer:
            self.message("Attached to %s" % purrer.logdir, ms=10000)
            dprint(1, "current Purrer changed, updating state")
            # set window title
            path = Kittens.utils.collapseuser(os.path.join(purrer.logdir, ''))
            self.setWindowTitle("PURR - %s" % path)
            # other init
            self.purrer = purrer
            self.new_entry_dialog.hide()
            self.new_entry_dialog.reset()
            dirs = [path for path, state in purrer.watchedDirectories()]
            self.new_entry_dialog.setDefaultDirs(*dirs)
            self.view_entry_dialog.setDefaultDirs(*dirs)
            self.view_entry_dialog.hide()
            self.viewer_dialog.hide()
            self._viewing_ientry = None
            self._setEntries(self.purrer.getLogEntries())
            #      print self._index_paths
            self._viewer_timestamp = None
            self._updateViewer()
            self._updateNames()
            # update directory widgets
            self.wdirlist.clear()
            for pathname, state in purrer.watchedDirectories():
                self.wdirlist.add(pathname, state)
            # Reset _pounce to false -- this will cause checkPounceStatus() into a rescan
            self._pounce = False
            self._checkPounceStatus()
        return True

    def setLogTitle(self, title):
        if self.purrer:
            if title != self.purrer.logtitle:
                self.purrer.setLogTitle(title)
                self._updateViewer()
            self._updateNames()

    def _updateNames(self):
        self.wnewbtn.setEnabled(True)
        self.wviewlog.setEnabled(True)
        self._about_dialog.setText(self.about_message + """
      <P>Your current log resides in:<PRE>  <tt>%s</tt></PRE>To see your log in all its HTML-rendered
      glory, point your browser to <tt>index.html</tt> therein, or use the handy "View" button provided by PURR.</P>

      <P>Your current working directories are:</P>
      <P>%s</P>
      """ % (self.purrer.logdir,
             "".join(["<PRE>  <tt>%s</tt></PRE>" % name for name, state in self.purrer.watchedDirectories()])
             ))
        title = self.purrer.logtitle or "Unnamed log"
        self.title_editor.setText(title)
        self.viewer_dialog.setWindowTitle(title)

    def _showViewerDialog(self):
        self._updateViewer(True)
        self.viewer_dialog.show()

    @staticmethod
    def fileModTime(path):
        try:
            return os.path.getmtime(path)
        except:
            return None

    def _updateViewer(self, force=False):
        """Updates the viewer dialog.
        If dialog is not visible and force=False, does nothing.
        Otherwise, checks the mtime of the current purrer index.html file against self._viewer_timestamp.
        If it is newer, reloads it.
        """
        if not force and not self.viewer_dialog.isVisible():
            return
        # default text if nothing is found
        path = self.purrer.indexfile
        mtime = self.fileModTime(path)
        # return if file is older than our content
        if mtime and mtime <= (self._viewer_timestamp or 0):
            return
        busy = BusyIndicator()
        self.viewer_dialog.setDocument(path, empty=
        "<P>Nothing in the log yet. Try adding some log entries.</P>")
        self.viewer_dialog.reload()
        self.viewer_dialog.setLabel("""<P>Below is your full HTML-rendered log. Note that this is 
      only a bare-bones viewer, so only a limited set of links will work. 
      For a fully-functional view, use a proper HTML browser to look at the index file residing here:<BR>
      <tt>%s</tt></P>
      """ % self.purrer.indexfile)
        self._viewer_timestamp = mtime

    def _setEntries(self, entries):
        self.etw.clear()
        item = None
        self._index_paths = {}
        self._index_paths[os.path.abspath(self.purrer.indexfile)] = -1
        for i, entry in enumerate(entries):
            item = self._addEntryItem(entry, i, item)
            self._index_paths[os.path.abspath(entry.index_file)] = i
        self.etw.resizeColumnToContents(0)

    def _titleChanged(self):
        self.setLogTitle(str(self.title_editor.text()))

    def _checkPounceStatus(self):
        ## pounce = bool([ entry for entry in self._dir_entries.itervalues() if entry.watching ])
        pounce = bool([path for path, state in self.purrer.watchedDirectories() if state >= Purr.WATCHED])
        # rescan, if going from not-pounce to pounce
        if pounce and not self._pounce:
            self._rescan()
        self._pounce = pounce
        # start timer -- we need it running to check the purr pipe, anyway
        self._timer.start(2000)

    def _forceRescan(self):
        if not self.purrer:
            self.attachDirectory('.')
        self._rescan(force=True)

    def _rescan(self, force=False):
        if not self.purrer:
            return
        # if pounce is on, tell the Purrer to rescan directories
        if self._pounce or force:
            dps = self.purrer.rescan()
            if dps:
                filenames = [dp.filename for dp in dps]
                dprint(2, "new data products:", filenames)
                self.message("Pounced on " + ", ".join(filenames))
                if self.new_entry_dialog.addDataProducts(dps):
                    dprint(2, "showing dialog")
                    self.new_entry_dialog.show()
        # else read stuff from pipe
        for pipe in self.purrpipes.values():
            do_show = False
            for command, show, content in pipe.read():
                if command == "title":
                    self.new_entry_dialog.suggestTitle(content)
                elif command == "comment":
                    self.new_entry_dialog.addComment(content)
                elif command == "pounce":
                    self.new_entry_dialog.addDataProducts(self.purrer.makeDataProducts(
                        [(content, not show)], unbanish=True))
                else:
                    print("Unknown command received from Purr pipe: ", command)
                    continue
                do_show = do_show or show
            if do_show:
                self.new_entry_dialog.show()

    def _addDPFiles(self, *files):
        """callback to add DPs corresponding to files."""
        # quiet flag is always true
        self.new_entry_dialog.addDataProducts(self.purrer.makeDataProducts(
            [(file, True) for file in files], unbanish=True, unignore=True))

    def _addDPFilesToOldEntry(self, *files):
        """callback to add DPs corresponding to files."""
        # quiet flag is always true
        self.view_entry_dialog.addDataProducts(self.purrer.makeDataProducts(
            [(file, True) for file in files], unbanish=True, unignore=True))

    def _entrySelectionChanged(self):
        selected = [item for item in self.etw.iterator(self.etw.Iterator.Selected) if item._ientry is not None]
        self.weditbtn.setEnabled(len(selected) == 1)
        self.wdelbtn.setEnabled(bool(selected))

    def _viewEntryItem(self, item=None, *dum):
        """Pops up the viewer dialog for the entry associated with the given item.
        If 'item' is None, looks for a selected item in the listview.
        The dum arguments are for connecting this to QTreeWidget signals such as doubleClicked().
        """
        # if item not set, look for selected items in listview. Only 1 must be selected.
        select = True
        if item is None:
            selected = [item for item in self.etw.iterator(self.etw.Iterator.Selected) if item._ientry is not None]
            if len(selected) != 1:
                return
            item = selected[0]
            select = False;  # already selected
        else:
            # make sure item is open -- the click will cause it to close
            self.etw.expandItem(item)
        # show dialog
        ientry = getattr(item, '_ientry', None)
        if ientry is not None:
            self._viewEntryNumber(ientry, select=select)

    def _viewEntryNumber(self, ientry, select=True):
        """views entry #ientry. Also selects entry in listview if select=True"""
        # pass entry to viewer dialog
        self._viewing_ientry = ientry
        entry = self.purrer.entries[ientry]
        busy = BusyIndicator()
        self.view_entry_dialog.viewEntry(entry,
                                         prev=ientry > 0 and self.purrer.entries[ientry - 1],
                                         next=ientry < len(self.purrer.entries) - 1 and self.purrer.entries[ientry + 1])
        self.view_entry_dialog.show()
        # select entry in listview
        if select:
            self.etw.clearSelection()
            self.etw.setItemSelected(self.etw.topLevelItem(ientry), True)

    def _viewPrevEntry(self):
        if self._viewing_ientry is not None and self._viewing_ientry > 0:
            self._viewEntryNumber(self._viewing_ientry - 1)

    def _viewNextEntry(self):
        if self._viewing_ientry is not None and self._viewing_ientry < len(self.purrer.entries) - 1:
            self._viewEntryNumber(self._viewing_ientry + 1)

    def _viewPath(self, path):
        num = self._index_paths.get(os.path.abspath(path), None)
        if num is None:
            return
        elif num == -1:
            self.view_entry_dialog.hide()
            self._showViewerDialog()
        else:
            self._viewEntryNumber(num)

    def _showItemContextMenu(self, item, point, col):
        """Callback for contextMenuRequested() signal. Pops up item menu, if defined"""
        menu = getattr(item, '_menu', None)
        if menu:
            settitle = getattr(item, '_set_menu_title', None)
            if settitle:
                settitle()
            # self._current_item tells callbacks what item the menu was referring to
            point = self.etw.mapToGlobal(point)
            self._current_item = item
            self.etw.clearSelection()
            self.etw.setItemSelected(item, True)
            menu.exec_(point)
        else:
            self._current_item = None

    def _copyItemToClipboard(self):
        """Callback for item menu."""
        if self._current_item is None:
            return
        dp = getattr(self._current_item, '_dp', None)
        if dp and dp.archived:
            path = dp.fullpath.replace(" ", "\\ ")
            QApplication.clipboard().setText(path, QClipboard.Clipboard)
            QApplication.clipboard().setText(path, QClipboard.Selection)

    def _restoreItemFromArchive(self):
        """Callback for item menu."""
        if self._current_item is None:
            return
        dp = getattr(self._current_item, '_dp', None)
        if dp and dp.archived:
            dp.restore_from_archive(parent=self)

    def _deleteSelectedEntries(self):
        remaining_entries = []
        del_entries = list(self.etw.iterator(self.etw.Iterator.Selected))
        remaining_entries = list(self.etw.iterator(self.etw.Iterator.Unselected))
        if not del_entries:
            return
        hide_viewer = bool([item for item in del_entries if self._viewing_ientry == item._ientry])
        del_entries = [self.purrer.entries[self.etw.indexOfTopLevelItem(item)] for item in del_entries]
        remaining_entries = [self.purrer.entries[self.etw.indexOfTopLevelItem(item)] for item in remaining_entries]
        # ask for confirmation
        if len(del_entries) == 1:
            msg = """<P><NOBR>Permanently delete the log entry</NOBR> "%s"?</P>""" % del_entries[0].title
            if del_entries[0].dps:
                msg += """<P>%d data product(s) saved with this
                  entry will be deleted as well.</P>""" % len(del_entries[0].dps)
        else:
            msg = """<P>Permanently delete the %d selected log entries?</P>""" % len(del_entries)
            ndp = 0
            for entry in del_entries:
                ndp += len([dp for dp in entry.dps if not dp.ignored])
            if ndp:
                msg += """<P>%d data product(s) saved with these entries will be deleted as well.</P>""" % ndp
        if QMessageBox.warning(self, "Deleting log entries", msg,
                               QMessageBox.Yes, QMessageBox.No) != QMessageBox.Yes:
            return
        if hide_viewer:
            self.view_entry_dialog.hide()
        # reset entries in purrer and in our log window
        self._setEntries(remaining_entries)
        self.purrer.deleteLogEntries(del_entries)
        #    self.purrer.setLogEntries(remaining_entries)
        # log will have changed, so update the viewer
        self._updateViewer()
        # delete entry files
        for entry in del_entries:
            entry.remove_directory()

    def _addEntryItem(self, entry, number, after):
        item = entry.tw_item = QTreeWidgetItem(self.etw, after)
        timelabel = self._make_time_label(entry.timestamp)
        item.setText(0, timelabel)
        item.setText(1, " " + (entry.title or ""))
        item.setToolTip(1, entry.title)
        if entry.comment:
            item.setText(2, " " + entry.comment.split('\n')[0])
            item.setToolTip(2, "<P>" + entry.comment.replace("<", "&lt;").replace(">", "&gt;"). \
                            replace("\n\n", "</P><P>").replace("\n", "</P><P>") + "</P>")
        item._ientry = number
        item._dp = None
        item._menu = self._entry_menu
        item._set_menu_title = lambda: self._entry_menu_title.setText('"%s"' % entry.title)
        # now make subitems for DPs
        subitem = None
        for dp in entry.dps:
            if not dp.ignored:
                subitem = self._addDPSubItem(dp, item, subitem)
        self.etw.collapseItem(item)
        self.etw.header().headerDataChanged(Qt.Horizontal, 0, 2)
        return item

    def _addDPSubItem(self, dp, parent, after):
        item = QTreeWidgetItem(parent, after)
        item.setText(1, dp.filename)
        item.setToolTip(1, dp.filename)
        item.setText(2, dp.comment or "")
        item.setToolTip(2, dp.comment or "")
        item._ientry = None
        item._dp = dp
        item._menu = self._archived_dp_menu
        item._set_menu_title = lambda: self._archived_dp_menu_title.setText(os.path.basename(dp.filename))
        return item

    def _make_time_label(self, timestamp):
        return time.strftime("%b %d %H:%M", time.localtime(timestamp))

    def _newLogEntry(self, entry):
        """This is called when a new log entry is created"""
        # add entry to purrer
        self.purrer.addLogEntry(entry)
        # add entry to listview if it is not an ignored entry
        # (ignored entries only carry information about DPs to be ignored)
        if not entry.ignore:
            if self.etw.topLevelItemCount():
                lastitem = self.etw.topLevelItem(self.etw.topLevelItemCount() - 1)
            else:
                lastitem = None
            self._addEntryItem(entry, len(self.purrer.entries) - 1, lastitem)
            self._index_paths[os.path.abspath(entry.index_file)] = len(self.purrer.entries) - 1
        # log will have changed, so update the viewer
        if not entry.ignore:
            self._updateViewer()
            self.show()

    def _entryChanged(self, entry):
        """This is called when a log entry is changed"""
        # resave the log
        self.purrer.save()
        # redo entry item
        if entry.tw_item:
            number = entry.tw_item._ientry
            entry.tw_item = None
            self.etw.takeTopLevelItem(number)
            if number:
                after = self.etw.topLevelItem(number - 1)
            else:
                after = None
            self._addEntryItem(entry, number, after)
        # log will have changed, so update the viewer
        self._updateViewer()

    def _regenerateLog(self):
        if QMessageBox.question(self.viewer_dialog, "Regenerate log", """<P><NOBR>Do you really want to regenerate the
      entire</NOBR> log? This can be a time-consuming operation.</P>""",
                                QMessageBox.Yes, QMessageBox.No) != QMessageBox.Yes:
            return
        self.purrer.save(refresh=True)
        self._updateViewer()
Ejemplo n.º 36
0
class ImageManager(QWidget):
    """An ImageManager manages a stack of images (and associated ImageControllers)"""
    def __init__(self, *args):
        QWidget.__init__(self, *args)
        # init layout
        self._lo = QVBoxLayout(self)
        self._lo.setContentsMargins(0, 0, 0, 0)
        self._lo.setSpacing(0)
        # init internal state
        self._currier = PersistentCurrier()
        self._z0 = 0
        # z-depth of first image, the rest count down from it
        self._updating_imap = False
        self._locked_display_range = False
        self._imagecons = []
        self._imagecon_loadorder = []
        self._center_image = None
        self._plot = None
        self._border_pen = None
        self._drawing_key = None
        self._load_image_dialog = None
        self._model_imagecons = set()
        # init menu and standard actions
        self._menu = QMenu("&Image", self)
        qag = QActionGroup(self)
        # exclusive controls for plotting topmost or all images
        self._qa_plot_top = qag.addAction("Display topmost image only")
        self._qa_plot_all = qag.addAction("Display all images")
        self._qa_plot_top.setCheckable(True)
        self._qa_plot_all.setCheckable(True)
        self._qa_plot_top.setChecked(True)
        QObject.connect(self._qa_plot_all, SIGNAL("toggled(bool)"),
                        self._displayAllImages)
        self._closing = False

        self._qa_load_clipboard = None
        self._clipboard_mode = QClipboard.Clipboard
        QObject.connect(QApplication.clipboard(),
                        SIGNAL("changed(QClipboard::Mode)"),
                        self._checkClipboardPath)
        # populate the menu
        self._repopulateMenu()

    def close(self):
        dprint(1, "closing Manager")
        self._closing = True
        for ic in self._imagecons:
            ic.close()

    def loadImage(self,
                  filename=None,
                  duplicate=True,
                  to_top=True,
                  model=None):
        """Loads image. Returns ImageControlBar object.
        If image is already loaded: returns old ICB if duplicate=False (raises to top if to_top=True),
        or else makes a new control bar.
        If model is set to a source name, marks the image as associated with a model source. These can be unloaded en masse by calling
        unloadModelImages().
        """
        if filename is None:
            if not self._load_image_dialog:
                dialog = self._load_image_dialog = QFileDialog(
                    self, "Load FITS image", ".",
                    "FITS images (%s);;All files (*)" %
                    (" ".join(["*" + ext for ext in FITS_ExtensionList])))
                dialog.setFileMode(QFileDialog.ExistingFile)
                dialog.setModal(True)
                QObject.connect(dialog,
                                SIGNAL("filesSelected(const QStringList &)"),
                                self.loadImage)
            self._load_image_dialog.exec_()
            return None
        if isinstance(filename, QStringList):
            filename = filename[0]
        filename = str(filename)
        # report error if image does not exist
        if not os.path.exists(filename):
            self.showErrorMessage("""FITS image %s does not exist.""" %
                                  filename)
            return None
        # see if image is already loaded
        if not duplicate:
            for ic in self._imagecons:
                if ic.getFilename() and os.path.samefile(
                        filename, ic.getFilename()):
                    if to_top:
                        self.raiseImage(ic)
                    if model:
                        self._model_imagecons.add(id(ic))
                    return ic
        # load the FITS image
        busy = BusyIndicator()
        dprint(2, "reading FITS image", filename)
        self.showMessage("""Reading FITS image %s""" % filename, 3000)
        QApplication.flush()
        try:
            image = SkyImage.FITSImagePlotItem(str(filename))
        except KeyboardInterrupt:
            raise
        except:
            busy = None
            traceback.print_exc()
            self.showErrorMessage(
                """<P>Error loading FITS image %s: %s. This may be due to a bug in Tigger; if the FITS file loads fine in another viewer,
          please send the FITS file, along with a copy of any error messages from the text console, to [email protected].</P>"""
                % (filename, str(sys.exc_info()[1])))
            return None
        # create control bar, add to widget stack
        ic = self._createImageController(image,
                                         "model source '%s'" %
                                         model if model else filename,
                                         model or image.name,
                                         model=model)
        self.showMessage("""Loaded FITS image %s""" % filename, 3000)
        dprint(2, "image loaded")
        return ic

    def showMessage(self, message, time=None):
        self.emit(SIGNAL("showMessage"), message, time)

    def showErrorMessage(self, message, time=None):
        self.emit(SIGNAL("showErrorMessage"), message, time)

    def setZ0(self, z0):
        self._z0 = z0
        if self._imagecons:
            self.raiseImage(self._imagecons[0])

    def enableImageBorders(self, border_pen, label_color, label_bg_brush):
        self._border_pen, self._label_color, self._label_bg_brush = \
            border_pen, label_color, label_bg_brush

    def lockAllDisplayRanges(self, rc0):
        """Locks all display ranges, and sets the intensity from rc0"""
        if not self._updating_imap:
            self._updating_imap = True
            rc0.lockDisplayRange()
            try:
                for ic in self._imagecons:
                    rc1 = ic.renderControl()
                    if rc1 is not rc0:
                        rc1.setDisplayRange(*rc0.displayRange())
                        rc1.lockDisplayRange()
            finally:
                self._updating_imap = False

    def unlockAllDisplayRanges(self):
        """Unlocks all display range."""
        for ic in self._imagecons:
            ic.renderControl().lockDisplayRange(False)

    def _lockDisplayRange(self, rc0, lock):
        """Locks or unlocks the display range of a specific controller."""
        if lock and not self._updating_imap:
            self._updating_imap = True
            try:
                # if something is already locked, copy display range from it
                for ic in self._imagecons:
                    rc1 = ic.renderControl()
                    if rc1 is not rc0 and rc1.isDisplayRangeLocked():
                        rc0.setDisplayRange(*rc1.displayRange())
            finally:
                self._updating_imap = False

    def _updateDisplayRange(self, rc, dmin, dmax):
        """This is called whenever one of the images (or rather, its associated RenderControl object) changes its display range."""
        if not rc.isDisplayRangeLocked():
            return
        # If the display range is locked, propagate it to all images.
        # but don't do it if we're already propagating (otherwise we may get called in an infinte loop)
        if not self._updating_imap:
            self._updating_imap = True
            try:
                for ic in self._imagecons:
                    rc1 = ic.renderControl()
                    if rc1 is not rc and rc1.isDisplayRangeLocked():
                        rc1.setDisplayRange(dmin, dmax)
            finally:
                self._updating_imap = False

    def getImages(self):
        return [ic.image for ic in self._imagecons]

    def getTopImage(self):
        return (self._imagecons or None) and self._imagecons[0].image

    def cycleImages(self):
        index = self._imagecon_loadorder.index(self._imagecons[0])
        index = (index + 1) % len(self._imagecon_loadorder)
        self.raiseImage(self._imagecon_loadorder[index])

    def blinkImages(self):
        if len(self._imagecons) > 1:
            self.raiseImage(self._imagecons[1])

    def incrementSlice(self, extra_axis, incr):
        if self._imagecons:
            rc = self._imagecons[0].renderControl()
            sliced_axes = rc.slicedAxes()
            if extra_axis < len(sliced_axes):
                rc.incrementSlice(sliced_axes[extra_axis][0], incr)

    def setLMRectSubset(self, rect):
        if self._imagecons:
            self._imagecons[0].setLMRectSubset(rect)

    def getLMRectStats(self, rect):
        if self._imagecons:
            return self._imagecons[0].renderControl().getLMRectStats(rect)

    def unloadModelImages(self):
        """Unloads images associated with model (i.e. loaded with the model=True flag)"""
        for ic in [
                ic for ic in self._imagecons if id(ic) in self._model_imagecons
        ]:
            self.unloadImage(ic)

    def unloadImage(self, imagecon):
        """Unloads the given imagecon object."""
        if imagecon not in self._imagecons:
            return
        # recenter if needed
        self._imagecons.remove(imagecon)
        self._imagecon_loadorder.remove(imagecon)
        self._model_imagecons.discard(id(imagecon))
        # reparent widget and release it
        imagecon.setParent(None)
        imagecon.close()
        # recenter image, if unloaded the center image
        if self._center_image is imagecon.image:
            self.centerImage(self._imagecons[0] if self._imagecons else None,
                             emit=False)
        # emit signal
        self._repopulateMenu()
        self.emit(SIGNAL("imagesChanged"))
        if self._imagecons:
            self.raiseImage(self._imagecons[0])

    def getCenterImage(self):
        return self._center_image

    def centerImage(self, imagecon, emit=True):
        self._center_image = imagecon and imagecon.image
        for ic in self._imagecons:
            ic.setPlotProjection(self._center_image.projection)
        if emit:
            self.emit(SIGNAL("imagesChanged"))

    def raiseImage(self, imagecon):
        # reshuffle image stack, if more than one image image
        if len(self._imagecons) > 1:
            busy = BusyIndicator()
            # reshuffle image stack
            self._imagecons.remove(imagecon)
            self._imagecons.insert(0, imagecon)
            # notify imagecons
            for i, ic in enumerate(self._imagecons):
                label = "%d" % (i + 1) if i else "<B>1</B>"
                ic.setZ(self._z0 - i * 10,
                        top=not i,
                        depthlabel=label,
                        can_raise=True)
            # adjust visibility
            for j, ic in enumerate(self._imagecons):
                ic.setImageVisible(not j
                                   or bool(self._qa_plot_all.isChecked()))
            # issue replot signal
            self.emit(SIGNAL("imageRaised"))
            self.fastReplot()
        # else simply update labels
        else:
            self._imagecons[0].setZ(self._z0,
                                    top=True,
                                    depthlabel=None,
                                    can_raise=False)
            self._imagecons[0].setImageVisible(True)
        # update slice menus
        img = imagecon.image
        axes = imagecon.renderControl().slicedAxes()
        for i, (next, prev) in enumerate(self._qa_slices):
            next.setVisible(False)
            prev.setVisible(False)
            if i < len(axes):
                iaxis, name, labels = axes[i]
                next.setVisible(True)
                prev.setVisible(True)
                next.setText("Show next slice along %s axis" % name)
                prev.setText("Show previous slice along %s axis" % name)
        # emit signasl
        self.emit(SIGNAL("imageRaised"), img)

    def resetDrawKey(self):
        """Makes and sets the current plot's drawing key"""
        if self._plot:
            key = []
            for ic in self._imagecons:
                key.append(id(ic))
                key += ic.currentSlice()
                self._plot.setDrawingKey(tuple(key))

    def fastReplot(self, *dum):
        """Fast replot -- called when flipping images or slices. Uses the plot cache, if possible."""
        if self._plot:
            self.resetDrawKey()
            dprint(2, "calling replot", time.time() % 60)
            self._plot.replot()
            dprint(2, "replot done", time.time() % 60)

    def replot(self, *dum):
        """Proper replot -- called when an image needs to be properly redrawn. Cleares the plot's drawing cache."""
        if self._plot:
            self._plot.clearDrawCache()
            self.resetDrawKey()
            self._plot.replot()

    def attachImagesToPlot(self, plot):
        self._plot = plot
        self.resetDrawKey()
        for ic in self._imagecons:
            ic.attachToPlot(plot)

    def getMenu(self):
        return self._menu

    def _displayAllImages(self, enabled):
        busy = BusyIndicator()
        if enabled:
            for ic in self._imagecons:
                ic.setImageVisible(True)
        else:
            self._imagecons[0].setImageVisible(True)
            for ic in self._imagecons[1:]:
                ic.setImageVisible(False)
        self.replot()

    def _checkClipboardPath(self, mode=QClipboard.Clipboard):
        if self._qa_load_clipboard:
            self._clipboard_mode = mode
            try:
                path = str(QApplication.clipboard().text(mode))
            except:
                path = None
            self._qa_load_clipboard.setEnabled(
                bool(path and os.path.isfile(path)))

    def _loadClipboardPath(self):
        try:
            path = QApplication.clipboard().text(self._clipboard_mode)
        except:
            return
        self.loadImage(path)

    def _repopulateMenu(self):
        self._menu.clear()
        self._menu.addAction("&Load image...", self.loadImage,
                             Qt.CTRL + Qt.Key_L)
        self._menu.addAction("&Compute image...", self.computeImage,
                             Qt.CTRL + Qt.Key_M)
        self._qa_load_clipboard = self._menu.addAction(
            "Load from clipboard &path", self._loadClipboardPath,
            Qt.CTRL + Qt.Key_P)
        self._checkClipboardPath()
        if self._imagecons:
            self._menu.addSeparator()
            # add controls to cycle images and planes
            for i, imgcon in enumerate(self._imagecons[::-1]):
                self._menu.addMenu(imgcon.getMenu())
            self._menu.addSeparator()
            if len(self._imagecons) > 1:
                self._menu.addAction("Cycle images", self.cycleImages,
                                     Qt.Key_F5)
                self._menu.addAction("Blink images", self.blinkImages,
                                     Qt.Key_F6)
            self._qa_slices = ((self._menu.addAction(
                "Next slice along axis 1",
                self._currier.curry(self.incrementSlice, 0, 1), Qt.Key_F7),
                                self._menu.addAction(
                                    "Previous slice along axis 1",
                                    self._currier.curry(
                                        self.incrementSlice, 0, -1),
                                    Qt.SHIFT + Qt.Key_F7)),
                               (self._menu.addAction(
                                   "Next slice along axis 2",
                                   self._currier.curry(self.incrementSlice, 1,
                                                       1), Qt.Key_F8),
                                self._menu.addAction(
                                    "Previous slice along axis 2",
                                    self._currier.curry(
                                        self.incrementSlice, 1, -1),
                                    Qt.SHIFT + Qt.Key_F8)))
            self._menu.addSeparator()
            self._menu.addAction(self._qa_plot_top)
            self._menu.addAction(self._qa_plot_all)

    def computeImage(self, expression=None):
        """Computes image from expression (if expression is None, pops up dialog)"""
        if expression is None:
            (expression, ok) = QInputDialog.getText(
                self, "Compute image", """Enter an image expression to compute.
                                              Any valid numpy expression is supported, and
                                              all functions from the numpy module are available (including sub-modules such as fft).
                                              Use 'a', 'b', 'c' to refer to images.
                                              Examples:  "(a+b)/2", "cos(a)+sin(b)", "a-a.mean()", "fft.fft2(a)", etc."""
            )
            #      (expression,ok) = QInputDialog.getText(self,"Compute image","""<P>Enter an expression to compute.
            #        Use 'a', 'b', etc. to refer to loaded images. Any valid numpy expression is supported, and all the
            #       functions from the numpy module are available. Examples of valid expressions include "(a+b)/2",
            #       "cos(a)+sin(b)", "a-a.mean()", etc.
            #        </P>
            #      """)
            expression = str(expression)
            if not ok or not expression:
                return
        # try to parse expression
        arglist = [(chr(ord('a') + ic.getNumber()), ic.image)
                   for ic in self._imagecons]
        try:
            exprfunc = eval(
                "lambda " + (",".join([x[0]
                                       for x in arglist])) + ":" + expression,
                numpy.__dict__, {})
        except Exception as exc:
            self.showErrorMessage("""Error parsing expression "%s": %s.""" %
                                  (expression, str(exc)))
            return None
        # try to evaluate expression
        self.showMessage("Computing expression \"%s\"" % expression, 10000)
        busy = BusyIndicator()
        QApplication.flush()

        # trim trivial trailing dimensions. This avoids the problem of when an NxMx1 and an NxMx1x1 arrays are added,
        # the result is promoted to NxMxMx1 following the numpy rules.
        def trimshape(shape):
            out = shape
            while out and out[-1] == 1:
                out = out[:-1]
            return out

        def trimarray(array):
            return array.reshape(trimshape(array.shape))

        try:
            result = exprfunc(*[trimarray(x[1].data()) for x in arglist])
        except Exception as exc:
            busy = None
            traceback.print_exc()
            self.showErrorMessage("""Error evaluating "%s": %s.""" %
                                  (expression, str(exc)))
            return None
        busy = None
        if type(result) != numpy.ma.masked_array and type(
                result) != numpy.ndarray:
            self.showErrorMessage(
                """Result of "%s" is of invalid type "%s" (array expected)."""
                % (expression, type(result).__name__))
            return None
        # convert coomplex results to real
        if numpy.iscomplexobj(result):
            self.showErrorMessage(
                """Result of "%s" is complex. Complex images are currently
      not fully supported, so we'll implicitly use the absolute value instead."""
                % (expression))
            expression = "abs(%s)" % expression
            result = abs(result)
        # determine which image this expression can be associated with
        res_shape = trimshape(result.shape)
        arglist = [
            x for x in arglist if hasattr(x[1], 'fits_header')
            and trimshape(x[1].data().shape) == res_shape
        ]
        if not arglist:
            self.showErrorMessage(
                """Result of "%s" has shape %s, which does not match any loaded FITS image."""
                % (expression, "x".join(map(str, result.shape))))
            return None
        # look for an image in the arglist with the same projection, and with a valid dirname
        # (for the where-to-save hint)
        template = arglist[0][1]
        # if all images in arglist have the same projection, then it doesn't matter what we use
        # else ask
        if len(
            [x for x in arglist[1:] if x[1].projection == template.projection
             ]) != len(arglist) - 1:
            options = [x[0] for x in arglist]
            (which, ok) = QInputDialog.getItem(
                self, "Compute image",
                "Coordinate system to use for the result of \"%s\":" %
                expression, options, 0, False)
            if not ok:
                return None
            try:
                template = arglist[options.index(which)][1]
            except:
                pass
        # create a FITS image
        busy = BusyIndicator()
        dprint(2, "creating FITS image", expression)
        self.showMessage("""Creating image for %s""" % expression, 3000)
        QApplication.flush()
        try:
            hdu = pyfits.PrimaryHDU(result.transpose(), template.fits_header)
            skyimage = SkyImage.FITSImagePlotItem(name=expression,
                                                  filename=None,
                                                  hdu=hdu)
        except:
            busy = None
            traceback.print_exc()
            self.showErrorMessage("""Error creating FITS image %s: %s""" %
                                  (expression, str(sys.exc_info()[1])))
            return None
        # get directory name for save-to hint
        dirname = getattr(template, 'filename', None)
        if not dirname:
            dirnames = [
                getattr(img, 'filename') for x, img in arglist
                if hasattr(img, 'filename')
            ]
            dirname = dirnames[0] if dirnames else None
        # create control bar, add to widget stack
        self._createImageController(
            skyimage,
            expression,
            expression,
            save=((dirname and os.path.dirname(dirname)) or "."))
        self.showMessage("Created new image for %s" % expression, 3000)
        dprint(2, "image created")

    def _createImageController(self,
                               image,
                               name,
                               basename,
                               model=False,
                               save=False):
        dprint(2, "creating ImageController for", name)
        ic = ImageController(image, self, self, name, save=save)
        ic.setNumber(len(self._imagecons))
        self._imagecons.insert(0, ic)
        self._imagecon_loadorder.append(ic)
        if model:
            self._model_imagecons.add(id(ic))
        self._lo.addWidget(ic)
        if self._border_pen:
            ic.addPlotBorder(self._border_pen, basename, self._label_color,
                             self._label_bg_brush)
        # attach appropriate signals
        image.connect(SIGNAL("slice"), self.fastReplot)
        image.connect(SIGNAL("repaint"), self.replot)
        image.connect(SIGNAL("raise"),
                      self._currier.curry(self.raiseImage, ic))
        image.connect(SIGNAL("unload"),
                      self._currier.curry(self.unloadImage, ic))
        image.connect(SIGNAL("center"),
                      self._currier.curry(self.centerImage, ic))
        QObject.connect(
            ic.renderControl(), SIGNAL("displayRangeChanged"),
            self._currier.curry(self._updateDisplayRange, ic.renderControl()))
        QObject.connect(
            ic.renderControl(), SIGNAL("displayRangeLocked"),
            self._currier.curry(self._lockDisplayRange, ic.renderControl()))
        self._plot = None
        # add to menus
        dprint(2, "repopulating menus")
        self._repopulateMenu()
        # center and raise to top of stack
        self.raiseImage(ic)
        if not self._center_image:
            self.centerImage(ic, emit=False)
        else:
            ic.setPlotProjection(self._center_image.projection)
        # signal
        self.emit(SIGNAL("imagesChanged"))
        return ic
Ejemplo n.º 37
0
    def __init__(self, parent, hide_on_close=False):
        QMainWindow.__init__(self, parent)
        self._hide_on_close = hide_on_close
        # replace the BusyIndicator class with a GUI-aware one
        Purr.BusyIndicator = BusyIndicator
        self._pounce = False
        # we keep a small stack of previously active purrers. This makes directory changes
        # faster (when going back and forth between dirs)
        # current purrer
        self.purrer = None
        self.purrer_stack = []
        # Purr pipes for receiving remote commands
        self.purrpipes = {}
        # init GUI
        self.setWindowTitle("PURR")
        self.setWindowIcon(pixmaps.purr_logo.icon())
        cw = QWidget(self)
        self.setCentralWidget(cw)
        cwlo = QVBoxLayout(cw)
        cwlo.setContentsMargins(0, 0, 0, 0)
        cwlo.setMargin(5)
        cwlo.setSpacing(0)
        toplo = QHBoxLayout();
        cwlo.addLayout(toplo)

        # About dialog
        self._about_dialog = QMessageBox(self)
        self._about_dialog.setWindowTitle("About PURR")
        self._about_dialog.setText(self.about_message + """
        <P>PURR is not watching any directories right now. You may need to restart it, and give it
  some directory names on the command line.</P>""")
        self._about_dialog.setIconPixmap(pixmaps.purr_logo.pm())
        # Log viewer dialog
        self.viewer_dialog = HTMLViewerDialog(self, config_name="log-viewer",
                                              buttons=[(pixmaps.blue_round_reload, "Regenerate",
                                                        """<P>Regenerates your log's HTML code from scratch. This can be useful if
                                                        your PURR version has changed, or if there was an error of some kind
                                                        the last time the files were generated.</P>
                                                        """)])
        self._viewer_timestamp = None
        self.connect(self.viewer_dialog, SIGNAL("Regenerate"), self._regenerateLog)
        self.connect(self.viewer_dialog, SIGNAL("viewPath"), self._viewPath)

        # Log title toolbar
        title_tb = QToolBar(cw)
        title_tb.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        title_tb.setIconSize(QSize(16, 16))
        cwlo.addWidget(title_tb)
        title_label = QLabel("Purrlog title:", title_tb)
        title_tb.addWidget(title_label)
        self.title_editor = QLineEdit(title_tb)
        title_tb.addWidget(self.title_editor)
        self.connect(self.title_editor, SIGNAL("editingFinished()"), self._titleChanged)
        tip = """<P>This is your current log title. To rename the log, enter new name here and press Enter.</P>"""
        title_label.setToolTip(tip)
        self.title_editor.setToolTip(tip)
        self.wviewlog = title_tb.addAction(pixmaps.openbook.icon(), "View", self._showViewerDialog)
        self.wviewlog.setToolTip("Click to see an HTML rendering of your current log.")
        qa = title_tb.addAction(pixmaps.purr_logo.icon(), "About...", self._about_dialog.exec_)
        qa.setToolTip("<P>Click to see the About... dialog, which will tell you something about PURR.</P>")

        self.wdirframe = QFrame(cw)
        cwlo.addWidget(self.wdirframe)
        self.dirs_lo = QVBoxLayout(self.wdirframe)
        self.dirs_lo.setMargin(5)
        self.dirs_lo.setContentsMargins(5, 0, 5, 5)
        self.dirs_lo.setSpacing(0)
        self.wdirframe.setFrameStyle(QFrame.Box | QFrame.Raised)
        self.wdirframe.setLineWidth(1)

        ## Directories toolbar
        dirs_tb = QToolBar(self.wdirframe)
        dirs_tb.setToolButtonStyle(Qt.ToolButtonIconOnly)
        dirs_tb.setIconSize(QSize(16, 16))
        self.dirs_lo.addWidget(dirs_tb)
        label = QLabel("Monitoring directories:", dirs_tb)
        self._dirs_tip = """<P>PURR can monitor your working directories for new or updated files. If there's a checkmark
      next to the directory name in this list, PURR is monitoring it.</P>

      <P>If the checkmark is grey, PURR is monitoring things unobtrusively. When a new or updated file is detected in he monitored directory,
      it is quietly added to the list of files in the "New entry" window, even if this window is not currently visible.</P>

      <P>If the checkmark is black, PURR will be more obtrusive. Whenever a new or updated file is detected, the "New entry" window will
      pop up automatically. This is called "pouncing", and some people find it annoying.</P>
      """
        label.setToolTip(self._dirs_tip)
        label.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
        dirs_tb.addWidget(label)

        # add directory list widget
        self.wdirlist = DirectoryListWidget(self.wdirframe)
        self.wdirlist.setToolTip(self._dirs_tip)
        QObject.connect(self.wdirlist, SIGNAL("directoryStateChanged"), self._changeWatchedDirState)
        self.dirs_lo.addWidget(self.wdirlist)
        # self.wdirlist.setMaximumSize(1000000,64)

        # add directory button
        add = dirs_tb.addAction(pixmaps.list_add.icon(), "Add", self._showAddDirectoryDialog)
        add.setToolTip("<P>Click to add another directory to be monitored.</P>")

        # remove directory button
        delbtn = dirs_tb.addAction(pixmaps.list_remove.icon(), "Remove", self.wdirlist.removeCurrent)
        delbtn.setEnabled(False)
        delbtn.setToolTip("<P>Click to removed the currently selected directory from the list.</P>")
        QObject.connect(self.wdirlist, SIGNAL("hasSelection"), delbtn.setEnabled)

        #    # qa = dirs_tb.addAction(pixmaps.blue_round_reload.icon(),"Rescan",self._forceRescan)
        #    # qa.setToolTip("Click to rescan the directories for any new or updated files.")
        #    self.wshownew = QCheckBox("show new files",dirs_tb)
        #    dirs_tb.addWidget(self.wshownew)
        #    self.wshownew.setCheckState(Qt.Checked)
        #    self.wshownew.setToolTip("""<P>If this is checked, the "New entry" window will pop up automatically whenever
        #  new or updated files are detected. If this is unchecked, the files will be added to the window quietly
        #        and unobtrusively; you can show the window manually by clicking on the "New entry..." button below.</P>""")
        #    self._dir_entries = {}

        cwlo.addSpacing(5)

        wlogframe = QFrame(cw)
        cwlo.addWidget(wlogframe)
        log_lo = QVBoxLayout(wlogframe)
        log_lo.setMargin(5)
        log_lo.setContentsMargins(5, 5, 5, 5)
        log_lo.setSpacing(0)
        wlogframe.setFrameStyle(QFrame.Box | QFrame.Raised)
        wlogframe.setLineWidth(1)

        # listview of log entries
        self.etw = LogEntryTree(cw)
        log_lo.addWidget(self.etw, 1)
        self.etw.header().setDefaultSectionSize(128)
        self.etw.header().setMovable(False)
        self.etw.setHeaderLabels(["date", "entry title", "comment"])
        if hasattr(QHeaderView, 'ResizeToContents'):
            self.etw.header().setResizeMode(0, QHeaderView.ResizeToContents)
        else:
            self.etw.header().setResizeMode(0, QHeaderView.Custom)
            self.etw.header().resizeSection(0, 120)
        self.etw.header().setResizeMode(1, QHeaderView.Interactive)
        self.etw.header().setResizeMode(2, QHeaderView.Stretch)
        self.etw.header().show()
        try:
            self.etw.setAllColumnsShowFocus(True)
        except AttributeError:
            pass;  # Qt 4.2+
        # self.etw.setShowToolTips(True)
        self.etw.setSortingEnabled(False)
        # self.etw.setColumnAlignment(2,Qt.AlignLeft|Qt.AlignTop)
        self.etw.setSelectionMode(QTreeWidget.ExtendedSelection)
        self.etw.setRootIsDecorated(True)
        self.connect(self.etw, SIGNAL("itemSelectionChanged()"), self._entrySelectionChanged)
        self.connect(self.etw, SIGNAL("itemActivated(QTreeWidgetItem*,int)"), self._viewEntryItem)
        self.connect(self.etw, SIGNAL("itemContextMenuRequested"), self._showItemContextMenu)
        # create popup menu for data products
        self._archived_dp_menu = menu = QMenu(self)
        self._archived_dp_menu_title = QLabel()
        self._archived_dp_menu_title.setMargin(5)
        self._archived_dp_menu_title_wa = wa = QWidgetAction(self)
        wa.setDefaultWidget(self._archived_dp_menu_title)
        menu.addAction(wa)
        menu.addSeparator()
        menu.addAction(pixmaps.editcopy.icon(), "Restore file(s) from archived copy", self._restoreItemFromArchive)
        menu.addAction(pixmaps.editpaste.icon(), "Copy pathname of archived copy to clipboard",
                       self._copyItemToClipboard)
        self._current_item = None
        # create popup menu for entries
        self._entry_menu = menu = QMenu(self)
        self._entry_menu_title = QLabel()
        self._entry_menu_title.setMargin(5)
        self._entry_menu_title_wa = wa = QWidgetAction(self)
        wa.setDefaultWidget(self._entry_menu_title)
        menu.addAction(wa)
        menu.addSeparator()
        menu.addAction(pixmaps.filefind.icon(), "View this log entry", self._viewEntryItem)
        menu.addAction(pixmaps.editdelete.icon(), "Delete this log entry", self._deleteSelectedEntries)
        # buttons at bottom
        log_lo.addSpacing(5)
        btnlo = QHBoxLayout()
        log_lo.addLayout(btnlo)
        self.wnewbtn = QPushButton(pixmaps.filenew.icon(), "New entry...", cw)
        self.wnewbtn.setToolTip("Click to add a new log entry.")
        # self.wnewbtn.setFlat(True)
        self.wnewbtn.setEnabled(False)
        btnlo.addWidget(self.wnewbtn)
        btnlo.addSpacing(5)
        self.weditbtn = QPushButton(pixmaps.filefind.icon(), "View entry...", cw)
        self.weditbtn.setToolTip("Click to view or edit the selected log entry/")
        # self.weditbtn.setFlat(True)
        self.weditbtn.setEnabled(False)
        self.connect(self.weditbtn, SIGNAL("clicked()"), self._viewEntryItem)
        btnlo.addWidget(self.weditbtn)
        btnlo.addSpacing(5)
        self.wdelbtn = QPushButton(pixmaps.editdelete.icon(), "Delete", cw)
        self.wdelbtn.setToolTip("Click to delete the selected log entry or entries.")
        # self.wdelbtn.setFlat(True)
        self.wdelbtn.setEnabled(False)
        self.connect(self.wdelbtn, SIGNAL("clicked()"), self._deleteSelectedEntries)
        btnlo.addWidget(self.wdelbtn)
        # enable status line
        self.statusBar().show()
        Purr.progressMessage = self.message
        self._prev_msg = None
        # editor dialog for new entry
        self.new_entry_dialog = Purr.Editors.NewLogEntryDialog(self)
        self.connect(self.new_entry_dialog, SIGNAL("newLogEntry"), self._newLogEntry)
        self.connect(self.new_entry_dialog, SIGNAL("filesSelected"), self._addDPFiles)
        self.connect(self.wnewbtn, SIGNAL("clicked()"), self.new_entry_dialog.show)
        self.connect(self.new_entry_dialog, SIGNAL("shown"), self._checkPounceStatus)
        # entry viewer dialog
        self.view_entry_dialog = Purr.Editors.ExistingLogEntryDialog(self)
        self.connect(self.view_entry_dialog, SIGNAL("previous()"), self._viewPrevEntry)
        self.connect(self.view_entry_dialog, SIGNAL("next()"), self._viewNextEntry)
        self.connect(self.view_entry_dialog, SIGNAL("viewPath"), self._viewPath)
        self.connect(self.view_entry_dialog, SIGNAL("filesSelected"), self._addDPFilesToOldEntry)
        self.connect(self.view_entry_dialog, SIGNAL("entryChanged"), self._entryChanged)
        # saving a data product to an older entry will automatically drop it from the
        # new entry dialog
        self.connect(self.view_entry_dialog, SIGNAL("creatingDataProduct"),
                     self.new_entry_dialog.dropDataProducts)
        # resize selves
        width = Config.getint('main-window-width', 512)
        height = Config.getint('main-window-height', 512)
        self.resize(QSize(width, height))
        # create timer for pouncing
        self._timer = QTimer(self)
        self.connect(self._timer, SIGNAL("timeout()"), self._rescan)
        # create dict mapping index.html paths to entry numbers
        self._index_paths = {}
Ejemplo n.º 38
0
class PM_GroupBox( QGroupBox ):
    """
    The PM_GroupBox widget provides a group box container with a 
    collapse/expand button and a title button.
    
    PM group boxes can be nested by supplying an existing PM_GroupBox as the 
    parentWidget of a new PM_GroupBox (as an argument to its constructor).
    If the parentWidget is a PM_GroupBox, no title button will be created
    for the new group box.
    
    @cvar setAsDefault: Determines whether to reset the value of all
                        widgets in the group box when the user clicks
                        the "Restore Defaults" button. If set to False,
                        no widgets will be reset regardless thier own 
                        I{setAsDefault} value.
    @type setAsDefault: bool
       
    @cvar labelWidget: The Qt label widget of this group box.
    @type labelWidget: U{B{QLabel}<http://doc.trolltech.com/4/qlabel.html>}
    
    @cvar expanded: Expanded flag.
    @type expanded: bool
    
    @cvar _title: The group box title.
    @type _title: str
    
    @cvar _widgetList: List of widgets in the group box 
                      (except the title button).
    @type _widgetList: list
    
    @cvar _rowCount: Number of rows in the group box.
    @type _rowCount: int
    
    @cvar _groupBoxCount: Number of group boxes in this group box.
    @type _groupBoxCount: int
    
    @cvar _lastGroupBox: The last group box in this group box (i.e. the
                         most recent PM group box added).
    @type _lastGroupBox: PM_GroupBox
    """
    
    setAsDefault = True
    labelWidget  = None
    expanded     = True
    
    _title         = ""
    _widgetList    = []
    _rowCount      = 0
    _groupBoxCount = 0
    _lastGroupBox  = None
    titleButtonRequested = True
    
    def __init__(self, 
                 parentWidget, 
                 title          = '', 
                 connectTitleButton = True,
                 setAsDefault   = True
                 ):
        """
        Appends a PM_GroupBox widget to I{parentWidget}, a L{PM_Dialog} or a 
        L{PM_GroupBox}.
        
        If I{parentWidget} is a L{PM_Dialog}, the group box will have a title 
        button at the top for collapsing and expanding the group box. If 
        I{parentWidget} is a PM_GroupBox, the title will simply be a text 
        label at the top of the group box.
        
        @param parentWidget: The parent dialog or group box containing this
                             widget.
        @type  parentWidget: L{PM_Dialog} or L{PM_GroupBox}
        
        @param title: The title (button) text. If empty, no title is added.
        @type  title: str
        
        @param connectTitleButton: If True, this class will automatically 
                      connect the title button of the groupbox to send signal
                      to expand or collapse the groupbox. Otherwise, the caller
                      has to connect this signal by itself. See:
                      B{Ui_MovePropertyManager.addGroupBoxes} and 
                      B{MovePropertyManager.connect_or_disconnect_signals} for
                      examples where the client connects this slot. 
        @type  connectTitleButton: bool
               
        
        @param setAsDefault: If False, no widgets in this group box will have 
                             thier default values restored when the B{Restore 
                             Defaults} button is clicked, regardless thier own 
                             I{setAsDefault} value.
        @type  setAsDefault: bool
        
        @see: U{B{QGroupBox}<http://doc.trolltech.com/4/qgroupbox.html>}
        """
      
        QGroupBox.__init__(self)
        
        self.parentWidget = parentWidget
        
        # Calling addWidget() here is important. If done at the end,
        # the title button does not get assigned its palette for some 
        # unknown reason. Mark 2007-05-20.
        # Add self to PropMgr's vBoxLayout
        if parentWidget:
            parentWidget._groupBoxCount += 1
            parentWidget.vBoxLayout.addWidget(self)
            parentWidget._widgetList.append(self)
            
        _groupBoxCount = 0
        self._widgetList = []
        self._title = title
        self.setAsDefault = setAsDefault
        
        self.setAutoFillBackground(True)
        self.setStyleSheet(self._getStyleSheet())
        
        # Create vertical box layout which will contain two widgets:
        # - the group box title button (or title) on row 0.
        # - the container widget for all PM widgets on row 1.
        self._vBoxLayout = QVBoxLayout(self)
        self._vBoxLayout.setMargin(0)
        self._vBoxLayout.setSpacing(0)
        
        # _containerWidget contains all PM widgets in this group box.
        # Its sole purpose is to easily support the collapsing and
        # expanding of a group box by calling this widget's hide()
        # and show() methods.
        self._containerWidget = QWidget()
        
        self._vBoxLayout.insertWidget(0, self._containerWidget)
        
        # Create vertical box layout
        self.vBoxLayout = QVBoxLayout(self._containerWidget)
        self.vBoxLayout.setMargin(PM_GROUPBOX_VBOXLAYOUT_MARGIN)
        self.vBoxLayout.setSpacing(PM_GROUPBOX_VBOXLAYOUT_SPACING)
        
        # Create grid layout
        self.gridLayout = QGridLayout()
        self.gridLayout.setMargin(PM_GRIDLAYOUT_MARGIN)
        self.gridLayout.setSpacing(PM_GRIDLAYOUT_SPACING)
        
        # Insert grid layout in its own vBoxLayout
        self.vBoxLayout.addLayout(self.gridLayout)
        
        # Add title button (or just a title if the parent is not a PM_Dialog).
        if not parentWidget or isinstance(parentWidget, PM_GroupBox):
            self.setTitle(title)
        else: # Parent is a PM_Dialog, so add a title button.
            if not self.titleButtonRequested:
                self.setTitle(title)
            else:
                self.titleButton = self._getTitleButton(self, title)
                self._vBoxLayout.insertWidget(0, self.titleButton)
                if connectTitleButton:
                    self.connect( self.titleButton, 
                                  SIGNAL("clicked()"),
                                  self.toggleExpandCollapse)
                self._insertMacSpacer()
            
        # Fixes the height of the group box. Very important. Mark 2007-05-29
        self.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))
        
        self._addBottomSpacer()
        return
    
    def _insertMacSpacer(self, spacerHeight = 6):
        """
        This addresses a Qt 4.3.5 layout bug on Mac OS X in which the 
        title button will overlap with and cover the first widget in 
        the group box. This workaround inserts a I{spacerHeight} tall
        spacer between the group box's title button and container widget.
        """
        if sys.platform != "darwin":
            return
        
        self._macVerticalSpacer = QSpacerItem(10, spacerHeight, 
                                        QSizePolicy.MinimumExpanding,
                                        QSizePolicy.Fixed)
        self._vBoxLayout.insertItem(1, self._macVerticalSpacer)
        return
        
    def _addBottomSpacer(self):
        """
        Add a vertical spacer below this group box.
        
        Method: Assume this is going to be the last group box in the PM, so set
        its spacer's vertical sizePolicy to MinimumExpanding. We then set the 
        vertical sizePolicy of the last group box's spacer to Fixed and set its
        height to PM_GROUPBOX_SPACING.
        """
        # Spacers are only added to groupboxes in the PropMgr, not
        # nested groupboxes.
        
##        from PM.PM_Dialog import PM_Dialog
##        if not isinstance(self.parentWidget, PM_Dialog):
        
        if not self.parentWidget or isinstance(self.parentWidget, PM_GroupBox):
            #bruce 071103 revised test to remove import cycle; I assume that
            # self.parentWidget is either a PM_GroupBox or a PM_Dialog, since
            # comments about similar code in __init__ imply that.
            #
            # A cleaner fix would involve asking self.parentWidget whether to
            # do this, with instances of PM_GroupBox and PM_Dialog giving
            # different answers, and making them both inherit an API class
            # which documents the method or attr with which we ask them that
            # question; the API class would be inherited by any object to
            # which PM_GroupBox's such as self can be added.
            #
            # Or, an even cleaner fix would just call a method in
            # self.parentWidget to do what this code does now (implemented
            # differently in PM_GroupBox and PM_Dialog).
            self.verticalSpacer = None
            return
        
        if self.parentWidget._lastGroupBox:
            # _lastGroupBox is no longer the last one. <self> will be the
            # _lastGroupBox, so we must change the verticalSpacer height 
            # and sizePolicy of _lastGroupBox to be a fixed
            # spacer between it and <self>.
            defaultHeight = PM_GROUPBOX_SPACING
            self.parentWidget._lastGroupBox.verticalSpacer.changeSize(
                10, defaultHeight, 
                QSizePolicy.Fixed,
                QSizePolicy.Fixed)
            self.parentWidget._lastGroupBox.verticalSpacer.defaultHeight = \
                defaultHeight
            
        # Add a 1 pixel high, MinimumExpanding VSpacer below this group box.
        # This keeps all group boxes in the PM layout squeezed together as 
        # group boxes  are expanded, collapsed, hidden and shown again.
        defaultHeight = 1
        self.verticalSpacer = QSpacerItem(10, defaultHeight, 
                                        QSizePolicy.Fixed,
                                        QSizePolicy.MinimumExpanding)
        
        self.verticalSpacer.defaultHeight = defaultHeight
        
        self.parentWidget.vBoxLayout.addItem(self.verticalSpacer)
        
        # This groupbox is now the last one in the PropMgr.
        self.parentWidget._lastGroupBox = self
        return
        
    def restoreDefault (self):
        """
        Restores the default values for all widgets in this group box.
        """
        for widget in self._widgetList:
            if debug_flags.atom_debug:
                print "PM_GroupBox.restoreDefault(): widget =", \
                      widget.objectName()
            widget.restoreDefault()
            
        return
    
    def getTitle(self):
        """
        Returns the group box title.
        
        @return: The group box title.
        @rtype:  str
        """
        return self._title
    
    def setTitle(self, title):
        """
        Sets the groupbox title to <title>.
        
        @param title: The group box title.
        @type  title: str
        
        @attention: This overrides QGroupBox's setTitle() method.
        """
        
        if not title:
            return
        
        # Create QLabel widget.
        if not self.labelWidget:
            self.labelWidget = QLabel()
            self.vBoxLayout.insertWidget(0, self.labelWidget)
            labelAlignment = PM_LABEL_LEFT_ALIGNMENT
            self.labelWidget.setAlignment(labelAlignment)
        
        self._title = title
        self.labelWidget.setText(title)
        return
        
    def getPmWidgetPlacementParameters(self, pmWidget):
        """
        Returns all the layout parameters needed to place 
        a PM_Widget in the group box grid layout.
        
        @param pmWidget: The PM widget.
        @type  pmWidget: PM_Widget
        """
        
        row = self._rowCount
        
        #PM_CheckBox doesn't have a label. So do the following to decide the 
        #placement of the checkbox. (can be placed either in column 0 or 1 , 
        #This also needs to be implemented for PM_RadioButton, but at present 
        #the following code doesn't support PM_RadioButton. 
        if isinstance(pmWidget, PM_CheckBox):
            spanWidth = pmWidget.spanWidth
            
            if not spanWidth:
                # Set the widget's row and column parameters.
                widgetRow      = row
                widgetColumn   = pmWidget.widgetColumn
                widgetSpanCols = 1
                widgetAlignment = PM_LABEL_LEFT_ALIGNMENT
                rowIncrement   = 1
                #set a virtual label
                labelRow       = row
                labelSpanCols  = 1
                labelAlignment = PM_LABEL_RIGHT_ALIGNMENT
                            
                if widgetColumn == 0:
                    labelColumn   = 1                              
                elif widgetColumn == 1:
                    labelColumn   = 0
            else:                
                # Set the widget's row and column parameters.
                widgetRow      = row
                widgetColumn   = pmWidget.widgetColumn
                widgetSpanCols = 2
                widgetAlignment = PM_LABEL_LEFT_ALIGNMENT
                rowIncrement   = 1
                #no label 
                labelRow       = 0
                labelColumn    = 0
                labelSpanCols  = 0
                labelAlignment = PM_LABEL_RIGHT_ALIGNMENT
                
            
            return widgetRow, \
               widgetColumn, \
               widgetSpanCols, \
               widgetAlignment, \
               rowIncrement, \
               labelRow, \
               labelColumn, \
               labelSpanCols, \
               labelAlignment
        
       
        label       = pmWidget.label            
        labelColumn = pmWidget.labelColumn
        spanWidth   = pmWidget.spanWidth
        
        if not spanWidth: 
            # This widget and its label are on the same row
            labelRow       = row
            labelSpanCols  = 1
            labelAlignment = PM_LABEL_RIGHT_ALIGNMENT
            # Set the widget's row and column parameters.
            widgetRow      = row
            widgetColumn   = 1
            widgetSpanCols = 1
            widgetAlignment = PM_LABEL_LEFT_ALIGNMENT
            rowIncrement   = 1
            
            if labelColumn == 1:
                widgetColumn   = 0
                labelAlignment = PM_LABEL_LEFT_ALIGNMENT
                widgetAlignment = PM_LABEL_RIGHT_ALIGNMENT
                        
        else: 
                      
            # This widget spans the full width of the groupbox           
            if label: 
                # The label and widget are on separate rows.
                # Set the label's row, column and alignment.
                labelRow       = row
                labelColumn    = 0
                labelSpanCols  = 2
                    
                # Set this widget's row and column parameters.
                widgetRow      = row + 1 # Widget is below the label.
                widgetColumn   = 0
                widgetSpanCols = 2
                
                rowIncrement   = 2
            else:  # No label. Just the widget.
                labelRow       = 0
                labelColumn    = 0
                labelSpanCols  = 0

                # Set the widget's row and column parameters.
                widgetRow      = row
                widgetColumn   = 0
                widgetSpanCols = 2
                rowIncrement   = 1
                
            labelAlignment = PM_LABEL_LEFT_ALIGNMENT
            widgetAlignment = PM_LABEL_LEFT_ALIGNMENT
                
        return widgetRow, \
               widgetColumn, \
               widgetSpanCols, \
               widgetAlignment, \
               rowIncrement, \
               labelRow, \
               labelColumn, \
               labelSpanCols, \
               labelAlignment
    
    def addPmWidget(self, pmWidget):
        """
        Add a PM widget and its label to this group box.
        
        @param pmWidget: The PM widget to add.
        @type  pmWidget: PM_Widget
        """
        
        # Get all the widget and label layout parameters.
        widgetRow, \
        widgetColumn, \
        widgetSpanCols, \
        widgetAlignment, \
        rowIncrement, \
        labelRow, \
        labelColumn, \
        labelSpanCols, \
        labelAlignment = \
            self.getPmWidgetPlacementParameters(pmWidget)
        
        if pmWidget.labelWidget: 
            #Create Label as a pixmap (instead of text) if a valid icon path 
            #is provided
            labelPath = str(pmWidget.label)
            if labelPath and labelPath.startswith("ui/"): #bruce 080325 revised
                labelPixmap = getpixmap(labelPath)
                if not labelPixmap.isNull():
                    pmWidget.labelWidget.setPixmap(labelPixmap)
                    pmWidget.labelWidget.setText('')
               
            self.gridLayout.addWidget( pmWidget.labelWidget,
                                       labelRow, 
                                       labelColumn,
                                       1, 
                                       labelSpanCols,
                                       labelAlignment )
            
        
        # The following is a workaround for a Qt bug. If addWidth()'s 
        # <alignment> argument is not supplied, the widget spans the full 
        # column width of the grid cell containing it. If <alignment> 
        # is supplied, this desired behavior is lost and there is no 
        # value that can be supplied to maintain the behavior (0 doesn't 
        # work). The workaround is to call addWidget() without the <alignment>
        # argument. Mark 2007-07-27.

        if widgetAlignment == PM_LABEL_LEFT_ALIGNMENT:
            self.gridLayout.addWidget( pmWidget,
                                       widgetRow, 
                                       widgetColumn,
                                       1, 
                                       widgetSpanCols) 
                                       # aligment = 0 doesn't work.
        else:
            self.gridLayout.addWidget( pmWidget,
                                       widgetRow, 
                                       widgetColumn,
                                       1, 
                                       widgetSpanCols,
                                       widgetAlignment
                                     )

        self._widgetList.append(pmWidget)
        
        self._rowCount += rowIncrement
        return
    
    def getRowCount(self):
        """
        Return the row count of gridlayout of L{PM_Groupbox}
        
        @return: The row count of L{self.gridlayout}
        @rtype: int
        """
        return self._rowCount
    
    def incrementRowCount(self, increment = 1):
        """
        Increment the row count of the gridlayout of L{PM_groupBox}
        @param increment: The incremental value
        @type  increment: int
        """
        self._rowCount += increment
        return
        
    def addQtWidget(self, qtWidget, column = 0, spanWidth = False):
        """
        Add a Qt widget to this group box.
        
        @param qtWidget: The Qt widget to add.
        @type  qtWidget: QWidget
        
        @warning: this method has not been tested yet.
        """
        # Set the widget's row and column parameters.
        widgetRow      = self._rowCount
        widgetColumn   = column
        if spanWidth:
            widgetSpanCols = 2
        else:
            widgetSpanCols = 1
        
        self.gridLayout.addWidget( qtWidget,
                                   widgetRow, 
                                   widgetColumn,
                                   1, 
                                   widgetSpanCols )
        
        self._rowCount += 1
        return
    
    def collapse(self):
        """
        Collapse this group box i.e. hide all its contents and change the look 
        and feel of the groupbox button. 
        """
        self.vBoxLayout.setMargin(0)
        self.vBoxLayout.setSpacing(0)
        self.gridLayout.setMargin(0)
        self.gridLayout.setSpacing(0)
        # The styleSheet contains the expand/collapse.
        styleSheet = self._getTitleButtonStyleSheet(showExpanded = False)
        self.titleButton.setStyleSheet(styleSheet)
        self._containerWidget.hide()
        self.expanded = False 
        return
    
    def expand(self):
        """
        Expand this group box i.e. show all its contents and change the look 
        and feel of the groupbox button. 
        """       
        self.vBoxLayout.setMargin(PM_GROUPBOX_VBOXLAYOUT_MARGIN)
        self.vBoxLayout.setSpacing(PM_GROUPBOX_VBOXLAYOUT_SPACING)
        self.gridLayout.setMargin(PM_GROUPBOX_GRIDLAYOUT_MARGIN)
        self.gridLayout.setSpacing(PM_GROUPBOX_GRIDLAYOUT_SPACING)
            
        # The styleSheet contains the expand/collapse.
        styleSheet = self._getTitleButtonStyleSheet(showExpanded = True)
        self.titleButton.setStyleSheet(styleSheet)
        self._containerWidget.show()
        self.expanded = True
        return

    def hide(self):
        """
        Hides the group box .
        
        @see: L{show}
        """
        QWidget.hide(self)
        if self.labelWidget:
            self.labelWidget.hide() 
        
        # Change the spacer height to zero to "hide" it unless
        # self is the last GroupBox in the Property Manager.
        if self.parentWidget._lastGroupBox is self:
            return
        
        if self.verticalSpacer:
            self.verticalSpacer.changeSize(10, 0)
        return
            
    def show(self):
        """
        Unhides the group box.
        
        @see: L{hide}
        """
        QWidget.show(self)
        if self.labelWidget:
            self.labelWidget.show() 
        
        if self.parentWidget._lastGroupBox is self:
            return
        
        if self.verticalSpacer:
            self.verticalSpacer.changeSize(10, 
                                           self.verticalSpacer.defaultHeight)
        return

    # Title Button Methods #####################################
    
    def _getTitleButton(self, 
                        parentWidget = None,
                        title        = '', 
                        showExpanded = True ):
        """
        Return the group box title push button. The push button is customized 
        such that it appears as a title bar at the top of the group box. 
        If the user clicks on this 'title bar' it sends a signal to open or 
        close the group box.
        
        @param parentWidget: The parent dialog or group box containing this 
                             widget.
        @type  parentWidget: PM_Dialog or PM_GroupBox
        
        @param title: The button title.
        @type  title: str 
        
        @param showExpanded: dDetermines whether the expand or collapse image is 
                             displayed on the title button.
        @type  showExpanded: bool
                             
        @see: L{_getTitleButtonStyleSheet()}
        
        @Note: Including a title button should only be legal if the parentWidget
               is a PM_Dialog.
        """
        
        button  = QPushButton(title, parentWidget)
        button.setFlat(False)
        button.setAutoFillBackground(True)
        
        button.setStyleSheet(self._getTitleButtonStyleSheet(showExpanded))     
        
        return button
    
    def _getTitleButtonStyleSheet(self, showExpanded = True):
        """
        Returns the style sheet for a group box title button (or checkbox).
        
        @param showExpanded: Determines whether to include an expand or
                             collapse icon.
        @type  showExpanded: bool
        
        @return: The title button style sheet.
        @rtype:  str
        """
        
        # Need to move border color and text color to top 
        # (make global constants).
        if showExpanded:        
            styleSheet = \
                       "QPushButton {"\
                       "border-style: outset; "\
                       "border-width: 2px; "\
                       "border-color: #%s; "\
                       "border-radius: 2px; "\
                       "background-color: #%s; "\
                       "font: bold 12px 'Arial'; "\
                       "color: #%s; "\
                       "min-width: 10em; "\
                       "background-image: url(%s); "\
                       "background-position: right; "\
                       "background-repeat: no-repeat; "\
                       "text-align: left; "\
                       "}" % (QColor_to_Hex(pmGrpBoxButtonBorderColor),
                              QColor_to_Hex(pmGrpBoxButtonColor),
                              QColor_to_Hex(pmGrpBoxButtonTextColor),
                              pmGrpBoxExpandedIconPath
                              )
                              
        else:
            # Collapsed.
            styleSheet = \
                       "QPushButton {"\
                       "border-style: outset; "\
                       "border-width: 2px; "\
                       "border-color: #%s; "\
                       "border-radius: 2px; "\
                       "background-color: #%s; "\
                       "font: bold 12px 'Arial'; "\
                       "color: #%s; "\
                       "min-width: 10em; "\
                       "background-image: url(%s); "\
                       "background-position: right; "\
                       "background-repeat: no-repeat; "\
                       "text-align: left; "\
                       "}" % (QColor_to_Hex(pmGrpBoxButtonBorderColor),
                              QColor_to_Hex(pmGrpBoxButtonColor),
                              QColor_to_Hex(pmGrpBoxButtonTextColor),
                              pmGrpBoxCollapsedIconPath
                              )
        return styleSheet
            
    def toggleExpandCollapse(self):
        """
        Slot method for the title button to expand/collapse the group box.
        """
        if self._widgetList:
            if self.expanded:
                self.collapse()
            else: # Expand groupbox by showing all widgets in groupbox.
                self.expand()         
        else:
            print "Clicking on the group box button has no effect "\
                   "since it has no widgets."
        return
    
    # GroupBox stylesheet methods. ##############################
    
    def _getStyleSheet(self):
        """
        Return the style sheet for the groupbox. This sets the following 
        properties only:
         - border style
         - border width
         - border color
         - border radius (on corners)
         - background color
        
        @return: The group box style sheet.
        @rtype:  str
        """
        
        styleSheet = \
                   "QGroupBox {"\
                   "border-style: solid; "\
                   "border-width: 1px; "\
                   "border-color: #%s; "\
                   "border-radius: 0px; "\
                   "background-color: #%s; "\
                   "min-width: 10em; "\
                   "}" % ( QColor_to_Hex(pmGrpBoxBorderColor), 
                           QColor_to_Hex(pmGrpBoxColor)
                           )
        return styleSheet
Ejemplo n.º 39
0
class pmMessageGroupBox(QGroupBox, PropertyManager_common):
    """Creates a Message group box.
    This class is used by the following Property Managers
    (which are still not using the PropMgrBaseClass):
    - Build Atoms
    - Build Crystal
    - Extrude
    - Move
    - Movie Player
    - Fuse Chunks

    Note: This class is temporary. It will be removed after the
    PropMgrs in this list are converted to the PropMgrBaseClass.
    Mark 2007-06-21
    """

    expanded = True # Set to False when groupbox is collapsed.
    defaultText = ""
    # The default text that is displayed whenever the Property Manager is displayed.
    setAsDefault = True
    # Checked to determine if <defaultText> should be restored whenever the
    # Property Manager is displayed.

    def __init__(self,
                 parent,
                 title = ''):

        QGroupBox.__init__(self)
        if parent:
            self.setParent(parent) #Fixed bug 2465 -- ninad 20070622

        self.setAutoFillBackground(True)
        self.setPalette(self.getPropMgrGroupBoxPalette())
        self.setStyleSheet(self.getStyleSheet())

        # Create vertical box layout
        self.VBoxLayout = QVBoxLayout(self)
        self.VBoxLayout.setMargin(pmMsgGrpBoxMargin)
        self.VBoxLayout.setSpacing(pmMsgGrpBoxSpacing)

        # Add title button to GroupBox
        self.titleButton = self.getTitleButton(title, self)
        self.VBoxLayout.addWidget(self.titleButton)
        self.connect(self.titleButton,SIGNAL("clicked()"),
                     self.toggleExpandCollapse)

        # Yellow MessageTextEdit
        self.MessageTextEdit = QtGui.QTextEdit(self)
        self.MessageTextEdit.setMaximumHeight(80) # 80 pixels height
        self.MessageTextEdit.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.VBoxLayout.addWidget(self.MessageTextEdit)

        msg_palette = self.getMessageTextEditPalette()

        self.MessageTextEdit.setPalette(msg_palette)
        self.MessageTextEdit.setReadOnly(True)

        # wrapWrapMode seems to be set to QTextOption.WrapAnywhere on MacOS,
        # so let's force it here. Mark 2007-05-22.
        self.MessageTextEdit.setWordWrapMode(QTextOption.WordWrap)

        # These two policies very important. Mark 2007-05-22
        self.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.MessageTextEdit.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Preferred),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.setWhatsThis("""<b>Messages</b>
            <p>This prompts the user for a requisite operation and/or displays
            helpful messages to the user.</p>""")

        parent.MessageTextEdit = self.MessageTextEdit

        self.hide()

    def getTitleButton(self, title, parent=None, showExpanded=True): #Ninad 070206
        """ Return the groupbox title pushbutton. The pushbutton is customized
        such that  it appears as a title bar to the user. If the user clicks on
        this 'titlebar' it sends appropriate signals to open or close the
        groupboxes   'name = string -- title of the groupbox
        'showExpanded' = boolean .. NE1 uses a different background
        image in the button's  Style Sheet depending on the bool.
        (i.e. if showExpanded = True it uses a opened group image  '^')
        See also: getGroupBoxTitleCheckBox , getGroupBoxButtonStyleSheet  methods
        """

        button  = QPushButton(title, parent)
        button.setFlat(False)
        button.setAutoFillBackground(True)

        button.setStyleSheet(self.getTitleButtonStyleSheet(showExpanded))
        button.setPalette(self.getTitleButtonPalette())

        #ninad 070221 set a non existant 'Ghost Icon' for this button
        #By setting such an icon, the button text left aligns!
        #(which what we want :-) )
        #So this might be a bug in Qt4.2.  If we don't use the following kludge,
        #there is no way to left align the push button text but to subclass it.
        #(could mean a lot of work for such a minor thing)  So OK for now

        button.setIcon(geticon("ui/actions/Properties Manager/GHOST_ICON"))

        return button

    def getTitleButtonStyleSheet(self, showExpanded=True):
        """Returns the style sheet for a groupbox title button (or checkbox).
        If <showExpanded> is True, the style sheet includes an expanded icon.
        If <showExpanded> is False, the style sheet includes a collapsed icon.
        """

        # Need to move border color and text color to top (make global constants).
        if showExpanded:
            styleSheet = "QPushButton {border-style:outset;\
            border-width: 2px;\
            border-color: " + pmGrpBoxButtonBorderColor + ";\
            border-radius:2px;\
            font:bold 12px 'Arial'; \
            color: " + pmGrpBoxButtonTextColor + ";\
            min-width:10em;\
            background-image: url(" + pmGrpBoxExpandedImage + ");\
            background-position: right;\
            background-repeat: no-repeat;\
            }"
        else:

            styleSheet = "QPushButton {border-style:outset;\
            border-width: 2px;\
            border-color: " + pmGrpBoxButtonBorderColor + ";\
            border-radius:2px;\
            font: bold 12px 'Arial'; \
            color: " + pmGrpBoxButtonTextColor + ";\
            min-width:10em;\
            background-image: url(" + pmGrpBoxCollapsedImage + ");\
            background-position: right;\
            background-repeat: no-repeat;\
            }"

        return styleSheet

    def toggleExpandCollapse(self):
        """Slot method for the title button to expand/collapse the groupbox.
        """
        if self.expanded: # Collapse groupbox by hiding ahe yellow TextEdit.
            # The styleSheet contains the expand/collapse icon.
            styleSheet = self.getTitleButtonStyleSheet(showExpanded = False)
            self.titleButton.setStyleSheet(styleSheet)
            # Why do we have to keep resetting the palette?
            # Does assigning a new styleSheet reset the button's palette?
            # If yes, we should add the button's color to the styleSheet.
            # Mark 2007-05-20
            self.titleButton.setPalette(self.getTitleButtonPalette())
            self.titleButton.setIcon(
                    geticon("ui/actions/Properties Manager/GHOST_ICON"))
            self.MessageTextEdit.hide()
            self.expanded = False
        else: # Expand groupbox by showing the yellow TextEdit.
            # The styleSheet contains the expand/collapse icon.
            styleSheet = self.getTitleButtonStyleSheet(showExpanded = True)
            self.titleButton.setStyleSheet(styleSheet)
            # Why do we have to keep resetting the palette?
            # Does assigning a new styleSheet reset the button's palette?
            # If yes, we should add the button's color to the styleSheet.
            # Mark 2007-05-20
            self.titleButton.setPalette(self.getTitleButtonPalette())
            self.titleButton.setIcon(
                    geticon("ui/actions/Properties Manager/GHOST_ICON"))
            self.MessageTextEdit.show()
            self.expanded = True

    def getStyleSheet(self):
        """Return the style sheet for a groupbox. This sets the following
        properties only:
         - border style
         - border width
         - border color
         - border radius (on corners)
        The background color for a groupbox is set using getPalette()."""

        styleSheet = "QGroupBox {border-style:solid;\
        border-width: 1px;\
        border-color: " + pmGrpBoxBorderColor + ";\
        border-radius: 0px;\
        min-width: 10em; }"

        ## For Groupboxs' Pushbutton :
        ##Other options not used : font:bold 10px;

        return styleSheet

    def insertHtmlMessage(self, text, setAsDefault=True,
                          minLines=4, maxLines=10,
                          replace=True):
        """Insert <text> (HTML) into the message groupbox.
        <minLines> - The minimum number of lines (of text) to display in the TextEdit.
        if <minLines>=0 the TextEdit will fit its own height to fit <text>. The
        default height is 4 (lines of text).
        <maxLines> - The maximum number of lines to display in the TextEdit widget.
        <replace> should be set to False if you do not wish
        to replace the current text. It will append <text> instead.

        Shows the message groupbox if it is hidden.
        """
        if setAsDefault:
            self.defaultText = text
            self.setAsDefault = True

        if replace:
            self.MessageTextEdit.clear()

        if text:
            self._setHeight(minLines, maxLines)
            QTextEdit.insertHtml(self.MessageTextEdit, text)
            self.show()
        else:
            # Hide the message groupbox if it contains no text.
            self.hide()

    def _setHeight(self, minLines=4, maxLines=8):
        """Set the height just high enough to display
        the current text without a vertical scrollbar.
        <minLines> is the minimum number of lines to
        display, even if the text takes up fewer lines.
        <maxLines> is the maximum number of lines to
        diplay before adding a vertical scrollbar.
        """

        if minLines == 0:
            fitToHeight=True
        else:
            fitToHeight=False

        # Current width of PropMgrTextEdit widget.
        current_width = self.MessageTextEdit.sizeHint().width()

        # Probably including Html tags.
        text = self.MessageTextEdit.toPlainText()
        text_width = self.MessageTextEdit.fontMetrics().width(text)

        num_lines = text_width/current_width + 1
            # + 1 may create an extra (empty) line on rare occasions.

        if fitToHeight:
            num_lines = min(num_lines, maxLines)

        else:
            num_lines = max(num_lines, minLines)

        #margin = self.fontMetrics().leading() * 2 # leading() returned 0. Mark 2007-05-28
        margin = 10 # Based on trial and error. Maybe it is pm?Spacing=5 (*2)? Mark 2007-05-28
        new_height = num_lines * self.MessageTextEdit.fontMetrics().lineSpacing() + margin

        if 0: # Debugging code for me. Mark 2007-05-24
            print "--------------------------------"
            print "Widget name =", self.objectName()
            print "minLines =", minLines
            print "maxLines =", maxLines
            print "num_lines=", num_lines
            print "New height=", new_height
            print "text =", text
            print "Text width=", text_width
            print "current_width (of PropMgrTextEdit)=", current_width

        # Reset height of PropMgrTextEdit.
        self.MessageTextEdit.setMinimumSize(QSize(pmMinWidth * 0.5, new_height))
        self.MessageTextEdit.setMaximumHeight(new_height)
Ejemplo n.º 40
0
class PM_Dialog(QDialog, SponsorableMixin):
    """
    The PM_Dialog class is the base class for Property Manager dialogs.
    
    [To make a PM class from this mixin-superclass, subclass it to customize
    the widget set and add behavior.
    You must also provide certain methods provided by GeneratorBaseClass
    (either by inheriting it -- not sure if superclass order matters for that --
    or by defining them yourself), including ok_btn_clicked and several others,
    including at least some defined by SponsorableMixin (open_sponsor_homepage,
    setSponsor).
    This set of requirements may be cleaned up.]
    [Note: Technically, this is not a "base class" but a "mixin class".]    
    """

    headerTitleText = ""  # The header title text.

    _widgetList = []  # A list of all group boxes in this PM dialog,
    # including the message group box.
    # (but not header, sponsor button, etc.)
    _groupBoxCount = 0  # Number of PM_GroupBoxes in this PM dialog.
    _lastGroupBox = None  # The last PM_GroupBox in this PM dialog.

    # (i.e. the most recent PM_GroupBox added).

    def __init__(self, name, iconPath="", title=""):
        """
        Property Manager constructor.
        
        @param name: the name to assign the property manager dialog object.
        @type  name: str
        
        @param iconPath: the relative path for the icon (PNG image) that 
                         appears in the header.
        @type  iconPath: str
        
        @param title: the title that appears in the header.
        @type  title: str
        """

        QDialog.__init__(self)

        self.setObjectName(name)
        self._widgetList = []

        # Main pallete for PropMgr.

        self.setPalette(QPalette(pmColor))

        # Main vertical layout for PropMgr.
        self.vBoxLayout = QVBoxLayout(self)
        self.vBoxLayout.setMargin(PM_MAINVBOXLAYOUT_MARGIN)
        self.vBoxLayout.setSpacing(PM_MAINVBOXLAYOUT_SPACING)

        # Add PropMgr's header, sponsor button, top row buttons and (hidden)
        # message group box.
        self._createHeader(iconPath, title)
        self._createSponsorButton()
        self._createTopRowBtns()  # Create top buttons row

        self.MessageGroupBox = PM_MessageGroupBox(self)

        # Keep the line below around; it might be useful.
        # I may want to use it now that I understand it.
        # Mark 2007-05-17.
        #QMetaObject.connectSlotsByName(self)

        self._addGroupBoxes()

        try:
            self._addWhatsThisText()
        except:
            print_compact_traceback("Error loading whatsthis text for this " \
                                    "property manager.")

        try:
            self._addToolTipText()
        except:
            print_compact_traceback("Error loading tool tip text for this " \
                                    "property manager.")

    def keyPressEvent(self, event):
        """
        Handles keyPress event. 
        
        NOTE: 
        Subclasses should carefully override this. 
        Note that the default implementation  doesn't permit ESC key 
        as a way to close the PM_dialog. (this is typically dsesirable
        for Property Managers) If any subclass need to implement the key press, 
        they should first call this method(i.e. superclass.keyPressEvent) and then 
        implement specific code that closed the dialog when ESC key is pressed.
        """
        key = event.key()
        # Don't use ESC key to close the PM dialog. Fixes bug 2596
        if key == Qt.Key_Escape:
            pass
        else:
            QDialog.keyPressEvent(self, event)
        return

    def _addGroupBoxes(self):
        """
        Add various group boxes to this PM. Subclasses should override this 
        method. 
         
        """
        pass

    def _addWhatsThisText(self):
        """
        Add what's this text. 
        Subclasses should override this  method. 
        """
        pass

    def _addToolTipText(self):
        """
        Add Tool tip text. 
        Subclasses should override this  method. 
        """
        pass

    def show(self):
        """
        Shows the Property Manager.
        """
        self.setSponsor()

        if not self.pw or self:
            self.pw = self.win.activePartWindow()

        self.pw.updatePropertyManagerTab(self)
        self.pw.pwProjectTabWidget.setCurrentIndex(
            self.pw.pwProjectTabWidget.indexOf(self))

        # Show the default message whenever we open the Property Manager.
        self.MessageGroupBox.MessageTextEdit.restoreDefault()

    def open(self, pm):
        """
        Closes the current property manager (if any) and opens the 
        property manager I{pm}.
        
        @param pm: The property manager to open.
        @type  pm: L{PM_Dialog} or QDialog (of legacy PMs)
        
        @attention: This method is a temporary workaround for "Insert > Plane".
                    The current command should always be responsible for 
                    (re)opening its own PM via self.show().
                    
        @see: L{show()}
        """
        if 1:
            commandSequencer = self.win.commandSequencer  #bruce 071008
            commandName = commandSequencer.currentCommand.commandName
            # that's an internal name, but this is just for a debug print
            print "PM_Dialog.open(): Reopening the PM for command:", commandName

        # The following line of code is buggy when you, for instance, exit a PM
        # and reopen the previous one. It sends the disconnect signal twice to
        # the PM that is just closed.So disabling this line -- Ninad 2007-12-04

        ##self.close() # Just in case there is another PM open.

        self.pw = self.win.activePartWindow()
        self.pw.updatePropertyManagerTab(pm)
        try:
            pm.setSponsor()
        except:
            print """PM_Dialog.open(): pm has no attribute 'setSponsor()'  
                     ignoring."""
        self.pw.pwProjectTabWidget.setCurrentIndex(
            self.pw.pwProjectTabWidget.indexOf(pm))

    def close(self):
        """
        Closes the Property Manager.
        """
        if not self.pw:
            self.pw = self.win.activePartWindow()
        self.pw.pwProjectTabWidget.setCurrentIndex(0)

        ## try: [bruce 071018 moved this lower, since errmsg only covers attr]
        pmWidget = self.pw.propertyManagerScrollArea.widget()
        if debug_flags.atom_debug:  #bruce 071018
            "atom_debug fyi: %r is closing %r (can they differ?)" % \
                        (self, pmWidget)
        try:
            pmWidget.update_props_if_needed_before_closing
        except AttributeError:
            if 1 or debug_flags.atom_debug:
                msg1 = "Last PropMgr %r doesn't have method" % pmWidget
                msg2 = " update_props_if_needed_before_closing. That's"
                msg3 = " OK (for now, only implemented for Plane PM). "
                msg4 = "Ignoring Exception: "
                print_compact_traceback(msg1 + msg2 + msg3 + msg4)
                #bruce 071018: I'll define that method in PM_Dialog
                # so this message should become rare or nonexistent,
                # so I'll make it happen whether or not atom_debug.
        else:
            pmWidget.update_props_if_needed_before_closing()

        self.pw.pwProjectTabWidget.removeTab(
            self.pw.pwProjectTabWidget.indexOf(
                self.pw.propertyManagerScrollArea))

        if self.pw.propertyManagerTab:
            self.pw.propertyManagerTab = None

    def update_props_if_needed_before_closing(self):
        # bruce 071018 default implem
        """
        Subclasses can override this to update some cosmetic properties
        of their associated model objects before closing self
        (the Property Manager).
        """
        pass

    def updateMessage(self, msg=''):
        """
        Updates the message box with an informative message
        @param msg: Message to be displayed in the Message groupbox of 
                        the property manager
        @type  msg: string
        """
        self.MessageGroupBox.insertHtmlMessage(msg,
                                               setAsDefault=False,
                                               minLines=5)

    def _createHeader(self, iconPath, title):
        """
        Creates the Property Manager header, which contains an icon
        (a QLabel with a pixmap) and white text (a QLabel with text).
        
        @param iconPath: The relative path for the icon (PNG image) that 
                         appears in the header.
        @type  iconPath: str
        
        @param title: The title that appears in the header.
        @type  title: str
        """

        # Heading frame (dark gray), which contains
        # a pixmap and (white) heading text.
        self.headerFrame = QFrame(self)
        self.headerFrame.setFrameShape(QFrame.NoFrame)
        self.headerFrame.setFrameShadow(QFrame.Plain)

        self.headerFrame.setPalette(QPalette(pmHeaderFrameColor))
        self.headerFrame.setAutoFillBackground(True)

        # HBox layout for heading frame, containing the pixmap
        # and label (title).
        HeaderFrameHLayout = QHBoxLayout(self.headerFrame)
        # 2 pixels around edges --
        HeaderFrameHLayout.setMargin(PM_HEADER_FRAME_MARGIN)
        # 5 pixel between pixmap and label. --
        HeaderFrameHLayout.setSpacing(PM_HEADER_FRAME_SPACING)

        # PropMgr icon. Set image by calling setHeaderIcon().
        self.headerIcon = QLabel(self.headerFrame)
        self.headerIcon.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Fixed),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        self.headerIcon.setScaledContents(True)

        HeaderFrameHLayout.addWidget(self.headerIcon)

        # PropMgr header title text (a QLabel).
        self.headerTitle = QLabel(self.headerFrame)
        headerTitlePalette = self._getHeaderTitlePalette()
        self.headerTitle.setPalette(headerTitlePalette)
        self.headerTitle.setAlignment(PM_LABEL_LEFT_ALIGNMENT)

        # Assign header title font.
        self.headerTitle.setFont(self._getHeaderFont())
        HeaderFrameHLayout.addWidget(self.headerTitle)

        self.vBoxLayout.addWidget(self.headerFrame)

        # Set header icon and title text.
        self.setHeaderIcon(iconPath)
        self.setHeaderTitle(title)

    def _getHeaderFont(self):
        """
        Returns the font used for the header.
        
        @return: the header font
        @rtype:  QFont
        """
        font = QFont()
        font.setFamily(PM_HEADER_FONT)
        font.setPointSize(PM_HEADER_FONT_POINT_SIZE)
        font.setBold(PM_HEADER_FONT_BOLD)
        return font

    def setHeaderTitle(self, title):
        """
        Set the Property Manager header title to string <title>.
        
        @param title: the title to insert in the header.
        @type  title: str
        """
        self.headerTitleText = title
        self.headerTitle.setText(title)

    def setHeaderIcon(self, iconPath):
        """
        Set the Property Manager header icon.
        
        @param iconPath: the relative path to the PNG file containing the 
                         icon image.
        @type  iconPath: str
        """

        if not iconPath:
            return

        self.headerIcon.setPixmap(getpixmap(iconPath))

    def _createSponsorButton(self):
        """
        Creates the Property Manager sponsor button, which contains
        a QPushButton inside of a QGridLayout inside of a QFrame.
        The sponsor logo image is not loaded here.
        """

        # Sponsor button (inside a frame)
        self.sponsor_frame = QFrame(self)
        self.sponsor_frame.setFrameShape(QFrame.NoFrame)
        self.sponsor_frame.setFrameShadow(QFrame.Plain)

        SponsorFrameGrid = QGridLayout(self.sponsor_frame)
        SponsorFrameGrid.setMargin(PM_SPONSOR_FRAME_MARGIN)
        SponsorFrameGrid.setSpacing(PM_SPONSOR_FRAME_SPACING)  # Has no effect.

        self.sponsor_btn = QPushButton(self.sponsor_frame)
        self.sponsor_btn.setAutoDefault(False)
        self.sponsor_btn.setFlat(True)
        self.connect(self.sponsor_btn, SIGNAL("clicked()"),
                     self.open_sponsor_homepage)

        SponsorFrameGrid.addWidget(self.sponsor_btn, 0, 0, 1, 1)

        self.vBoxLayout.addWidget(self.sponsor_frame)

        button_whatsthis_widget = self.sponsor_btn
        #bruce 070615 bugfix -- put tooltip & whatsthis on self.sponsor_btn,
        # not self.
        # [self.sponsor_frame might be another possible place to put them.]

        button_whatsthis_widget.setWhatsThis("""<b>Sponsor Button</b>
            <p>When clicked, this sponsor logo will display a short 
            description about a NanoEngineer-1 sponsor. This can 
            be an official sponsor or credit given to a contributor 
            that has helped code part or all of this command. 
            A link is provided in the description to learn more 
            about this sponsor.</p>""")

        button_whatsthis_widget.setToolTip("NanoEngineer-1 Sponsor Button")

        return

    def _createTopRowBtns(self):
        """
        Creates the Done, Cancel, Preview, Restore Defaults and What's This 
        buttons row at the top of the Property Manager.
        """

        # Main "button group" widget (but it is not a QButtonGroup).
        self.pmTopRowBtns = QHBoxLayout()
        # This QHBoxLayout is (probably) not necessary. Try using just the frame
        # for the foundation. I think it should work. Mark 2007-05-30

        # Horizontal spacer
        horizontalSpacer = QSpacerItem(1, 1, QSizePolicy.Expanding,
                                       QSizePolicy.Minimum)

        # Frame containing all the buttons.
        self.topRowBtnsFrame = QFrame()

        self.topRowBtnsFrame.setFrameShape(QFrame.NoFrame)
        self.topRowBtnsFrame.setFrameShadow(QFrame.Plain)

        # Create Hbox layout for main frame.
        topRowBtnsHLayout = QHBoxLayout(self.topRowBtnsFrame)
        topRowBtnsHLayout.setMargin(PM_TOPROWBUTTONS_MARGIN)
        topRowBtnsHLayout.setSpacing(PM_TOPROWBUTTONS_SPACING)

        topRowBtnsHLayout.addItem(horizontalSpacer)

        # Set button type.
        if 1:  # Mark 2007-05-30
            # Needs to be QToolButton for MacOS. Fine for Windows, too.
            buttonType = QToolButton
            # May want to use QToolButton.setAutoRaise(1) below. Mark 2007-05-29
        else:
            buttonType = QPushButton  # Do not use.

        # Done (OK) button.
        self.done_btn = buttonType(self.topRowBtnsFrame)
        self.done_btn.setIcon(
            geticon("ui/actions/Properties Manager/Done.png"))
        self.done_btn.setIconSize(QSize(22, 22))
        self.connect(self.done_btn, SIGNAL("clicked()"),
                     self.doneButtonClicked)
        self.done_btn.setToolTip("Done")

        topRowBtnsHLayout.addWidget(self.done_btn)

        # Cancel (Abort) button.
        self.cancel_btn = buttonType(self.topRowBtnsFrame)
        self.cancel_btn.setIcon(
            geticon("ui/actions/Properties Manager/Abort.png"))
        self.cancel_btn.setIconSize(QSize(22, 22))
        self.connect(self.cancel_btn, SIGNAL("clicked()"),
                     self.cancelButtonClicked)
        self.cancel_btn.setToolTip("Cancel")

        topRowBtnsHLayout.addWidget(self.cancel_btn)

        #@ abort_btn deprecated. We still need it because modes use it.
        self.abort_btn = self.cancel_btn

        # Restore Defaults button.
        self.restore_defaults_btn = buttonType(self.topRowBtnsFrame)
        self.restore_defaults_btn.setIcon(
            geticon("ui/actions/Properties Manager/Restore.png"))
        self.restore_defaults_btn.setIconSize(QSize(22, 22))
        self.connect(self.restore_defaults_btn, SIGNAL("clicked()"),
                     self.restoreDefaultsButtonClicked)
        self.restore_defaults_btn.setToolTip("Restore Defaults")
        topRowBtnsHLayout.addWidget(self.restore_defaults_btn)

        # Preview (glasses) button.
        self.preview_btn = buttonType(self.topRowBtnsFrame)
        self.preview_btn.setIcon(
            geticon("ui/actions/Properties Manager/Preview.png"))
        self.preview_btn.setIconSize(QSize(22, 22))
        self.connect(self.preview_btn, SIGNAL("clicked()"),
                     self.previewButtonClicked)
        self.preview_btn.setToolTip("Preview")

        topRowBtnsHLayout.addWidget(self.preview_btn)

        # What's This (?) button.
        self.whatsthis_btn = buttonType(self.topRowBtnsFrame)
        self.whatsthis_btn.setIcon(
            geticon("ui/actions/Properties Manager/WhatsThis.png"))
        self.whatsthis_btn.setIconSize(QSize(22, 22))
        self.connect(self.whatsthis_btn, SIGNAL("clicked()"),
                     self.whatsThisButtonClicked)
        self.whatsthis_btn.setToolTip("Enter \"What's This\" help mode")

        topRowBtnsHLayout.addWidget(self.whatsthis_btn)

        topRowBtnsHLayout.addItem(horizontalSpacer)

        # Create Button Row
        self.pmTopRowBtns.addWidget(self.topRowBtnsFrame)

        self.vBoxLayout.addLayout(self.pmTopRowBtns)

        # Add What's This for buttons.

        self.done_btn.setWhatsThis("""<b>Done</b>
            <p>
            <img source=\"ui/actions/Properties Manager/Done.png\"><br>
            Completes and/or exits the current command.</p>""")

        self.cancel_btn.setWhatsThis("""<b>Cancel</b>
            <p>
            <img source=\"ui/actions/Properties Manager/Abort.png\"><br>
            Cancels the current command.</p>""")

        self.restore_defaults_btn.setWhatsThis("""<b>Restore Defaults</b>
            <p><img source=\"ui/actions/Properties Manager/Restore.png\"><br>
            Restores the defaut values of the Property Manager.</p>""")

        self.preview_btn.setWhatsThis("""<b>Preview</b>
            <p>
            <img source=\"ui/actions/Properties Manager/Preview.png\"><br>
            Preview the structure based on current Property Manager settings.
            </p>""")

        self.whatsthis_btn.setWhatsThis("""<b>What's This</b> 
            <p>
            <img source=\"ui/actions/Properties Manager/WhatsThis.png\"><br>
            This invokes \"What's This?\" help mode which is part of 
            NanoEngineer-1's online help system, and provides users with 
            information about the functionality and usage of a particular 
            command button or widget.
            </p>""")

        return

    def hideTopRowButtons(self, pmButtonFlags=None):
        """
        Hides one or more top row buttons using <pmButtonFlags>.
        Button flags not set will cause the button to be shown
        if currently hidden.
        
        @param pmButtonFlags: This enumerator describes the which buttons to 
                              hide, where:
        
            - PM_DONE_BUTTON            =  1
            - PM_CANCEL_BUTTON          =  2
            - PM_RESTORE_DEFAULTS_BUTTON =  4
            - PM_PREVIEW_BUTTON         =  8
            - PM_WHATS_THIS_BUTTON       = 16
            - PM_ALL_BUTTONS            = 31
            
        @type  pmButtonFlags: int
        """

        if pmButtonFlags & PM_DONE_BUTTON:
            self.done_btn.hide()
        else:
            self.done_btn.show()

        if pmButtonFlags & PM_CANCEL_BUTTON:
            self.cancel_btn.hide()
        else:
            self.cancel_btn.show()

        if pmButtonFlags & PM_RESTORE_DEFAULTS_BUTTON:
            self.restore_defaults_btn.hide()
        else:
            self.restore_defaults_btn.show()

        if pmButtonFlags & PM_PREVIEW_BUTTON:
            self.preview_btn.hide()
        else:
            self.preview_btn.show()

        if pmButtonFlags & PM_WHATS_THIS_BUTTON:
            self.whatsthis_btn.hide()
        else:
            self.whatsthis_btn.show()

    def showTopRowButtons(self, pmButtonFlags=PM_ALL_BUTTONS):
        """
        Shows one or more top row buttons using <pmButtonFlags>.
        Button flags not set will cause the button to be hidden
        if currently displayed.
        
        @param pmButtonFlags: this enumerator describes which buttons to 
        display, where:
        
            - PM_DONE_BUTTON            =  1
            - PM_CANCEL_BUTTON          =  2
            - PM_RESTORE_DEFAULTS_BUTTON =  4
            - PM_PREVIEW_BUTTON         =  8
            - PM_WHATS_THIS_BUTTON       = 16
            - PM_ALL_BUTTONS            = 31
            
        @type  pmButtonFlags: int
        """

        self.hideTopRowButtons(pmButtonFlags ^ PM_ALL_BUTTONS)

    def _getHeaderTitlePalette(self):
        """
        Return a palette for header title (text) label. 
        """
        palette = QPalette()
        palette.setColor(QPalette.WindowText, pmHeaderTitleColor)
        return palette

    def doneButtonClicked(self):
        """
        Slot for the What's This button.
        """
        self.ok_btn_clicked()

    def cancelButtonClicked(self):
        """
        Slot for the What's This button.
        """
        self.cancel_btn_clicked()

    def restoreDefaultsButtonClicked(self):
        """
        Slot for "Restore Defaults" button in the Property Manager.
        It is called each time the button is clicked.
        """
        for widget in self._widgetList:
            if isinstance(widget, PM_GroupBox):
                widget.restoreDefault()

    def previewButtonClicked(self):
        """
        Slot for the What's This button.
        """
        self.preview_btn_clicked()

    def whatsThisButtonClicked(self):
        """
        Slot for the What's This button.
        """
        QWhatsThis.enterWhatsThisMode()
Ejemplo n.º 41
0
    def __init__(self, win):
        qt4todo('what to do with all those options?')
        ## ElementColorsDialog.__init__(self, win, None, 0,
        ##   Qt.WStyle_Customize | Qt.WStyle_NormalBorder |
        ##   Qt.WStyle_Title | Qt.WStyle_SysMenu)
        QDialog.__init__(self, win)
        self.setupUi(self)
        self.connect(self.okButton, SIGNAL("clicked()"), self.ok)
        self.connect(self.loadColorsPB, SIGNAL("clicked()"),
                     self.read_element_rgb_table)
        self.connect(self.saveColorsPB, SIGNAL("clicked()"),
                     self.write_element_rgb_table)
        self.connect(self.cancelButton, SIGNAL("clicked()"), self.reject)
        self.connect(self.defaultButton, SIGNAL("clicked()"),
                     self.loadDefaultProp)
        self.connect(self.alterButton, SIGNAL("clicked()"), self.loadAlterProp)
        self.connect(self.elementButtonGroup, SIGNAL("clicked(int)"),
                     self.setElementInfo)
        self.connect(self.previewPB, SIGNAL("clicked()"),
                     self.preview_color_change)
        self.connect(self.restorePB, SIGNAL("clicked()"),
                     self.restore_current_color)
        self.w = win
        self.fileName = None
        self.isElementModified = False
        self.isFileSaved = False
        self.oldTable = PeriodicTable.deepCopy()
        self.elemTable = PeriodicTable
        self.displayMode = self._displayList[0]
        # The next line fixes a bug. Thumbview expects self.gridLayout on
        # line 117 of Thumbview.py. Mark 2007-10-19.
        self.gridLayout = self.gridlayout
        self.elemGLPane = ElementView(self, "element glPane", self.w.glpane)
        # Put the GL widget inside the frame
        flayout = QVBoxLayout(self.elementFrame)
        flayout.setMargin(1)
        flayout.setSpacing(1)
        flayout.addWidget(self.elemGLPane, 1)

        def elementId(symbol):
            return PeriodicTable.getElement(symbol).eltnum

        self.toolButton6.setChecked(True)
        self.elementButtonGroup.setId(self.toolButton6, elementId("C"))
        self.elementButtonGroup.setId(self.toolButton8, elementId("O"))
        self.elementButtonGroup.setId(self.toolButton10, elementId("Ne"))
        self.elementButtonGroup.setId(self.toolButton9, elementId("F"))
        self.elementButtonGroup.setId(self.toolButton13, elementId("Al"))
        self.elementButtonGroup.setId(self.toolButton17, elementId("Cl"))
        self.elementButtonGroup.setId(self.toolButton5, elementId("B"))
        self.elementButtonGroup.setId(self.toolButton10_2, elementId("Ar"))
        self.elementButtonGroup.setId(self.toolButton15, elementId("P"))
        self.elementButtonGroup.setId(self.toolButton16, elementId("S"))
        self.elementButtonGroup.setId(self.toolButton14, elementId("Si"))
        self.elementButtonGroup.setId(self.toolButton33, elementId("As"))
        self.elementButtonGroup.setId(self.toolButton34, elementId("Se"))
        self.elementButtonGroup.setId(self.toolButton35, elementId("Br"))
        self.elementButtonGroup.setId(self.toolButton36, elementId("Kr"))
        self.elementButtonGroup.setId(self.toolButton32, elementId("Ge"))
        self.elementButtonGroup.setId(self.toolButton7, elementId("N"))
        self.elementButtonGroup.setId(self.toolButton2, elementId("He"))
        self.elementButtonGroup.setId(self.toolButton1, elementId("H"))
        self.elementButtonGroup.setId(self.toolButton0, elementId("X"))

        self.connect(self.toolButton6, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton8, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton10, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton9, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton13, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton17, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton5, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton10_2, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton15, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton16, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton14, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton33, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton34, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton35, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton36, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton32, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton7, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton2, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton1, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)
        self.connect(self.toolButton0, SIGNAL("clicked()"),
                     self.updateElemColorDisplay)

        self.connectChangingControls()
        self.saveColorsPB.setWhatsThis(
            """Save the current color settings for elements in a text file.""")
        self.defaultButton.setWhatsThis(
            """Restore current element colors to the default colors.""")
        self.loadColorsPB.setWhatsThis(
            """Load element colors from an external text file.""")
        self.alterButton.setWhatsThis(
            """Set element colors to the alternate color set.""")
Ejemplo n.º 42
0
    def __init__(self, assy, parent):
        """
        Constructor for the part window.

        @param assy: The assembly (part)
        @type  assy: Assembly

        @param parent: The parent widget.
        @type  parent: U{B{QMainWindow}
                       <http://doc.trolltech.com/4/qmainwindow.html>}
        """
        QWidget.__init__(self, parent)
        self.parent = parent
        self.assy = assy
            # note: to support MDI, self.assy would probably need to be a
            # different assembly for each PartWindow.
            # [bruce 080216 comment]
        self.setWindowIcon(geticon("ui/border/Part.png"))
        self.updateWindowTitle()
        
        # The main layout for the part window is a VBoxLayout <pwVBoxLayout>.
        self.pwVBoxLayout = QVBoxLayout(self)
        pwVBoxLayout = self.pwVBoxLayout
        pwVBoxLayout.setMargin(0)
        pwVBoxLayout.setSpacing(0)

        # ################################################################
        # <pwSplitter> is the horizontal splitter b/w the
        # pwLeftArea (mt and pm) and the glpane.
        self.pwSplitter = QSplitter(Qt.Horizontal)
        pwSplitter = self.pwSplitter
        pwSplitter.setObjectName("pwSplitter")
        pwSplitter.setHandleWidth(3) # 3 pixels wide.
        pwVBoxLayout.addWidget(pwSplitter)

        # ##################################################################
        # <pwLeftArea> is the container holding the pwProjectTabWidget.
        # Note: Making pwLeftArea (and pwRightArea and pwBottomArea) QFrame
        # widgets has the benefit of making it easy to draw a border around
        # each area. One purpose of this would be to help developers understand
        # (visually) how the part window is laid out. I intend to add a debug
        # pref to draw part window area borders and add "What's This" text to
        # them. Mark 2008-01-05.
        self.pwLeftArea = LeftFrame(self)
        pwLeftArea = self.pwLeftArea
        pwLeftArea.setObjectName("pwLeftArea")
        pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)

        # Setting the frame style like this is nice since it clearly
        # defines the splitter at the top-left corner.
        pwLeftArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        # This layout will contain splitter (above) and the pwBottomArea.
        leftChannelVBoxLayout = QVBoxLayout(pwLeftArea)
        leftChannelVBoxLayout.setMargin(0)
        leftChannelVBoxLayout.setSpacing(0)

        pwSplitter.addWidget(pwLeftArea)

        # Makes it so pwLeftArea is not collapsible.
        pwSplitter.setCollapsible (0, False)

        # ##################################################################
        # <pwProjectTabWidget> is a QTabWidget that contains the MT and PM
        # widgets. It lives in the "left area" of the part window.
        self.pwProjectTabWidget = _pwProjectTabWidget()
           # _pwProjectTabWidget subclasses QTabWidget
           # Note [bruce 070829]: to fix bug 2522 I need to intercept
           # self.pwProjectTabWidget.removeTab, so I made it a subclass of
           # QTabWidget. It needs to know the GLPane, but that's not created
           # yet, so we set it later using KLUGE_setGLPane (below).
           # Note: No parent supplied. Could this be the source of the
           # minor vsplitter resizing problem I was trying to resolve a few
           # months ago?  Try supplying a parent later. Mark 2008-01-01
        self.pwProjectTabWidget.setObjectName("pwProjectTabWidget")
        self.pwProjectTabWidget.setCurrentIndex(0)
        self.pwProjectTabWidget.setAutoFillBackground(True)

        # Create the model tree "tab" widget. It will contain the MT GUI widget.
        # Set the tab icon, too.
        self.modelTreeTab = QWidget()
        self.modelTreeTab.setObjectName("modelTreeTab")
        self.pwProjectTabWidget.addTab(
            self.modelTreeTab,
            geticon("ui/modeltree/Model_Tree.png"),
            "")

        modelTreeTabLayout = QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        # Create the model tree (GUI) and add it to the tab layout.
        self.modelTree = ModelTree(self.modelTreeTab, parent)
        self.modelTree.modelTreeGui.setObjectName("modelTreeGui")
        modelTreeTabLayout.addWidget(self.modelTree.modelTreeGui)

        # Create the property manager "tab" widget. It will contain the PropMgr
        # scroll area, which will contain the property manager and all its
        # widgets.
        self.propertyManagerTab = QWidget()
        self.propertyManagerTab.setObjectName("propertyManagerTab")

        self.propertyManagerScrollArea = QScrollArea(self.pwProjectTabWidget)
        self.propertyManagerScrollArea.setObjectName("propertyManagerScrollArea")
        self.propertyManagerScrollArea.setWidget(self.propertyManagerTab)
        self.propertyManagerScrollArea.setWidgetResizable(True)
        # Eureka!
        # setWidgetResizable(True) will resize the Property Manager (and its
        # contents) correctly when the scrollbar appears/disappears.
        # It even accounts correctly for collapsed/expanded groupboxes!
        # Mark 2007-05-29

        # Add the property manager scroll area as a "tabbed" widget.
        # Set the tab icon, too.
        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"),
            "")

        # Finally, add the "pwProjectTabWidget" to the left channel layout.
        
        leftChannelVBoxLayout.addWidget(self.pwProjectTabWidget)
       

        # Create the glpane and make it a child of the part splitter.
        self.glpane = GLPane(assy, self, 'glpane name', parent)
            # note: our owner (MWsemantics) assumes
            # there is just this one GLPane for assy, and stores it
            # into assy as assy.o and assy.glpane. [bruce 080216 comment]
        
        # Add what's this text to self.glpane.
        # [bruce 080912 moved this here from part of a method in class GLPane.
        #  In this code's old location, Mark wrote [2007-06-01]: "Problem -
        #  I don't believe this text is processed by fix_whatsthis_text_and_links()
        #  in whatsthis_utilities.py." Now that this code is here, I don't know
        #  whether that's still true. ]
        from ne1_ui.WhatsThisText_for_MainWindow import whats_this_text_for_glpane
        self.glpane.setWhatsThis( whats_this_text_for_glpane() )
        
        # update [re the above comment], bruce 081209:
        # I added the following explicit call of fix_whatsthis_text_and_links,
        # but it doesn't work to replace Ctrl with Cmd on Mac;
        # see today's comment in fix_whatsthis_text_and_links for likely reason.
        # So I will leave this here, but also leave in place the kluges
        # in whats_this_text_for_glpane to do that replacement itself.
        # The wiki help link in this whatsthis text doesn't work,
        # but I guess that is an independent issue, related to lack
        # of use of class QToolBar_WikiHelp or similar code, for GLPane
        # or this class or the main window class.
        from foundation.whatsthis_utilities import fix_whatsthis_text_and_links
        fix_whatsthis_text_and_links(self.glpane) # doesn't yet work
        
        self.pwProjectTabWidget.KLUGE_setGLPane(self.glpane)
            # help fix bug 2522 [bruce 070829]
        qt4warnDestruction(self.glpane, 'GLPane of PartWindow')
        pwSplitter.addWidget(self.glpane)

        # ##################################################################
        # <pwBottomArea> is a container at the bottom of the part window
        # spanning its entire width. It is intended to be used as an extra
        # area for use by Property Managers (or anything else) that needs
        # a landscape oriented layout.
        # An example is the Sequence Editor, which is part of the
        # Strand Properties PM.
        self.pwBottomArea = QFrame()
            # IMHO, self is not a good parent. Mark 2008-01-04.
        pwBottomArea = self.pwBottomArea
        pwBottomArea.setObjectName("pwBottomArea")
        pwBottomArea.setMaximumHeight(50)

        # Add a frame border to see what it looks like.
        pwBottomArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        self.pwVBoxLayout.addWidget(pwBottomArea)

        # Hide the bottom frame for now. Later this might be used for the
        # sequence editor.
        pwBottomArea.hide()
        
        #This widget implementation is subject to heavy revision. The purpose
        #is to implement a NFR that Mark urgently needs : The NFR is: Need a 
        #way to quickly find a node in the MT by entering its name.
        #-- Ninad 2008-11-06
        self.pwSpecialDockWidgetInLeftChannel = SelectNodeByNameDockWidget(self.glpane.win)
        leftChannelVBoxLayout.addWidget(self.pwSpecialDockWidgetInLeftChannel)
                
        # See the resizeEvent() docstring for more information about
        # resizeTimer.
        self.resizeTimer = QTimer(self)
        self.resizeTimer.setSingleShot(True)
        return
Ejemplo n.º 43
0
class Ui_MainWindow(object):
    """
    The Ui_MainWindow class creates the main window and all its widgets.
    """
    def setupUi(self):
        """
        Sets up the main window UI in the following order:
        - Create all main window widgets used by menus and/or toolbars.
        - Create main menu bar and its menus
        - Create main toolbars
        - Create the statusbar
        """
        self.setObjectName("MainWindow")
        self.setEnabled(True)
        self.setWindowIcon(geticon("ui/border/MainWindow.png"))
        self.setWindowTitle("NanoEngineer-1")
        
        # Set minimum width and height of the main window.
        # To do: Check that the current screen size is at least 800 x 600.
        MINIMUM_MAIN_WINDOW_SIZE = (800, 600) # Mark 2008-02-08
        self.setMinimumWidth(MINIMUM_MAIN_WINDOW_SIZE[0])
        self.setMinimumHeight(MINIMUM_MAIN_WINDOW_SIZE[1])

        # Create all widgets for all main window menus and toolbars.
        Ui_MainWindowWidgets.setupUi(self)
        
        # Set up all main window widget connections to their slots.
        Ui_MainWindowWidgetConnections.setupUi(self)
        
        # Toolbars should come before menus. The only reason this is necessary
        # is that the "View > Toolbars" submenu needs the main window toolbars
        # to be created before it is created (in 
        self._setupToolbars() 
        self._setupMenus()
        
        # Now set all UI text for main window widgets.
        # Note: I intend to compile all retranslateUi() functions into a single
        # function/method soon. mark 2007-12-31.
        self._retranslateUi()
        
        # The central widget of the NE1 main window contains a VBoxLayout
        # containing the Command Toolbar at top. Below that will be either:
        # 1. a Part Window widget (the default), or 
        # 2. a QWorkspace widget (for MDI support) which will contain only
        #    a single Part Window (experimental)
        # The QWorkspace can be enabled by setting the following debug pref
        # to True: "Enable QWorkspace for MDI support? (next session)"
        # Mark 2007-12-31
        _centralWidget = QWidget()
        self.setCentralWidget(_centralWidget)
        self.centralAreaVBoxLayout = QVBoxLayout(_centralWidget)
        self.centralAreaVBoxLayout.setMargin(0)
        self.centralAreaVBoxLayout.setSpacing(0)
        
        # Add the Command Toolbar to the top of the central widget.
        # Note: Main window widgets must have their text set 
        # via retranslateUi() before instantiating CommandToolbar.
        from commandToolbar.CommandToolbar import CommandToolbar
        self.commandToolbar = CommandToolbar(self)
        self.centralAreaVBoxLayout.addWidget(self.commandToolbar)
        
        # Create the statusbar for the main window.
        self.setStatusBar(StatusBar(self))
        
        # =
    
        # Create the "What's This?" text for all main window widgets.
        from ne1_ui.WhatsThisText_for_MainWindow import createWhatsThisTextForMainWindowWidgets
        createWhatsThisTextForMainWindowWidgets(self)
        
        # IMPORTANT: All main window widgets and their "What's This" text should 
        # be created before this line. [If this is not possible, we'll need to 
        # split out some functions within this one which can be called
        # later on individual QActions and/or QWidgets. bruce 060319]
        from foundation.whatsthis_utilities import fix_whatsthis_text_and_links
        fix_whatsthis_text_and_links(self)
            # (main call) Fixes bug 1136.  Mark 051126.
        fix_whatsthis_text_and_links(self.toolsMoveRotateActionGroup)
            # This is needed to add links to the "Translate" and "Rotate"
            # QAction widgets on the standard toolbar, since those two
            # widgets are not direct children of the main window. 
            # Fixes one of the many bugs listed in bug 2412. Mark 2007-12-19
    
    def _setupToolbars(self):
        """
        Populates all Main Window toolbars. 
        Also restores the state of toolbars from the NE1 last session.
        """
        
        # Add toolbars to the Top area of the main window (first row)
        toolbarArea = Qt.TopToolBarArea
        Ui_StandardToolBar.setupUi(self, toolbarArea)
        Ui_BuildToolsToolBar.setupUi(self, toolbarArea)
        self.addToolBarBreak (toolbarArea) # Starts second row.
        Ui_ViewToolBar.setupUi(self, toolbarArea)
        Ui_StandardViewsToolBar.setupUi(self, toolbarArea)
        Ui_SimulationToolBar.setupUi(self, toolbarArea)
        
        # Add toolbars to the Right area of the main window.
        toolbarArea = Qt.RightToolBarArea
        Ui_SelectToolBar.setupUi(self, toolbarArea)
        Ui_DisplayStylesToolBar.setupUi(self, toolbarArea)
        Ui_RenderingToolBar.setupUi(self, toolbarArea)
        
        # This is hidden the first time NE1 starts (below).
        # BTW, I don't think the "Build Structures" toolbar is necessary since
        # all its options are available prominentaly on the Command Toolbar.
        # I intend to completely remove it soon. Mark 2008-02-29.
        Ui_BuildStructuresToolBar.setupUi(self, toolbarArea)
        
        from utilities.prefs_constants import toolbar_state_prefs_key
        # This fixes bug 2482.
        if not env.prefs[toolbar_state_prefs_key] == 'defaultToolbarState':
            # Restore the state of the toolbars from the last session.
            toolBarState = QtCore.QByteArray(env.prefs[toolbar_state_prefs_key])
            self.restoreState(toolBarState)
        else:
            # No previous session. Hide only these toolbars by default.
            self.buildStructuresToolBar.hide()
        
        return
    
    def _setupMenus(self):
        """
        Populates all main window menus and adds them to the main menu bar.
        """

        # Populate the menus that appear in the main window menu bar.
        Ui_FileMenu.setupUi(self)
        Ui_EditMenu.setupUi(self)
        Ui_ViewMenu.setupUi(self)
        Ui_InsertMenu.setupUi(self)
        Ui_ToolsMenu.setupUi(self)
        Ui_SimulationMenu.setupUi(self)
        Ui_RenderingMenu.setupUi(self)
        Ui_HelpMenu.setupUi(self)

        # Add menus to the main window menu bar.
        self.MenuBar.addAction(self.fileMenu.menuAction())
        self.MenuBar.addAction(self.editMenu.menuAction())
        self.MenuBar.addAction(self.viewMenu.menuAction())
        self.MenuBar.addAction(self.insertMenu.menuAction())
        self.MenuBar.addAction(self.toolsMenu.menuAction())
        self.MenuBar.addAction(self.simulationMenu.menuAction())
        self.MenuBar.addAction(self.renderingMenu.menuAction())
        self.MenuBar.addAction(self.helpMenu.menuAction())

        # Add the MenuBar to the main window.
        self.setMenuBar(self.MenuBar)
        
        return

    def _retranslateUi(self):
        """
        This method centralizes all calls that set UI text for the purpose of 
        making it easier for the programmer to translate the UI into other 
        languages.

        @see: U{B{The Qt Linquist Manual}<http://doc.trolltech.com/4/linguist-manual.html>}
        """
        self.setWindowTitle(QtGui.QApplication.translate(
            "MainWindow", "NanoEngineer-1", 
            None, QtGui.QApplication.UnicodeUTF8))

        # QActions
        Ui_MainWindowWidgets.retranslateUi(self)
        
        # Toolbars
        Ui_StandardToolBar.retranslateUi(self)
        Ui_ViewToolBar.retranslateUi(self)
        Ui_StandardViewsToolBar.retranslateUi(self)
        Ui_DisplayStylesToolBar.retranslateUi(self)
        Ui_BuildStructuresToolBar.retranslateUi(self)
        Ui_BuildToolsToolBar.retranslateUi(self)
        Ui_SelectToolBar.retranslateUi(self)
        Ui_SimulationToolBar.retranslateUi(self)
        Ui_RenderingToolBar.retranslateUi(self)

        # Menus and submenus
        Ui_FileMenu.retranslateUi(self)
        Ui_EditMenu.retranslateUi(self)
        Ui_ViewMenu.retranslateUi(self)     
        Ui_InsertMenu.retranslateUi(self)
        Ui_ToolsMenu.retranslateUi(self)
        Ui_SimulationMenu.retranslateUi(self)
        Ui_RenderingMenu.retranslateUi(self)
        Ui_HelpMenu.retranslateUi(self)
Ejemplo n.º 44
0
 def __init__(self, parent, modal=True, flags=Qt.WindowFlags()):
     QDialog.__init__(self, parent, flags)
     self.setModal(modal)
     self.setWindowTitle("Convert sources to FITS brick")
     lo = QVBoxLayout(self)
     lo.setMargin(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())
     QObject.connect(self.wadd, SIGNAL("toggled(bool)"),
                     self.wpad.setEnabled)
     QObject.connect(self.wadd, SIGNAL("toggled(bool)"), 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(0, 0, 0, 0)
     lo2.setMargin(5)
     self.wokbtn = QPushButton("OK", self)
     self.wokbtn.setMinimumWidth(128)
     QObject.connect(self.wokbtn, SIGNAL("clicked()"), self.accept)
     self.wokbtn.setEnabled(False)
     cancelbtn = QPushButton("Cancel", self)
     cancelbtn.setMinimumWidth(128)
     QObject.connect(cancelbtn, SIGNAL("clicked()"), self.reject)
     lo2.addWidget(self.wokbtn)
     lo2.addStretch(1)
     lo2.addWidget(cancelbtn)
     self.setMinimumWidth(384)
     # signals
     QObject.connect(self.wfile, SIGNAL("filenameSelected"),
                     self._fileSelected)
     # internal state
     self.qerrmsg = QErrorMessage(self)
Ejemplo n.º 45
0
class MMKit(QDialog,
            Ui_MMKitDialog,
            PropertyManagerMixin,
            SponsorableMixin):
    """
    THIS CLASS HAS BEEN DEPRECATED.
    SEE NEW IMPLEMETATION IN--
    Ui_BuildAtomsPropertyManager.py,
    BuildAtomsPropertyManager.py,
    Ui_PartLibPropertyManager.py,
    PartLibPropertyManager.py,
    Ui_PastePropertyManager.py
    PastePropertyManager.py

    Provide the MMKit PM for Build Atoms mode.
    """
    # <title> - the title that appears in the property manager header.
    title = "Build Atoms"
    # <iconPath> - full path to PNG file that appears in the header.
    iconPath = "ui/actions/Tools/Build Structures/Build Atoms.png"

    bond_id2name =['sp3', 'sp2', 'sp', 'sp2(graphitic)']
    sponsor_keyword = 'Build'

    def __init__(self, parentMode, win):
        QDialog.__init__(self, win, Qt.Dialog)# Qt.WStyle_Customize | Qt.WStyle_Tool | Qt.WStyle_Title | Qt.WStyle_NoBorder)

        self.w = win
        self.o = self.w.glpane

        #@NOTE: As of 20070717, MMKit supports only depositMode as its parent
        #(and perhaps subclasses of depositMode ..but such a class that also
        #uses MMKit is NIY so it is unconfirmed)  -- ninad
        self.parentMode = parentMode

        self.setupUi(self)

        # setupUi() did not add the icon or title. We do that here.
        pmSetPropMgrIcon( self, self.iconPath )
        pmSetPropMgrTitle( self, self.title )

        #self.connect(self.hybrid_btngrp,SIGNAL("buttonClicked(int)"),self.set_hybrid_type)

        self.pw = None # pw = partwindow

        self.connect(self.mmkit_tab,
                     SIGNAL("currentChanged(int)"),
                     self.tabCurrentChanged)

        self.connect(self.chunkListBox,
                     SIGNAL("currentItemChanged(QListWidgetItem*,QListWidgetItem*)"),
                     self.chunkChanged)
        self.connect(self.browseButton,
                     SIGNAL("clicked(bool)"),
                     self.browseDirectories)
        self.connect(self.defaultPartLibButton,
                     SIGNAL("clicked(bool)"),
                     self.useDefaultPartLibDirectory)

        #self.connect(self.elementButtonGroup,SIGNAL("buttonClicked(int)"),self.setElementInfo)

        self.connect(self.thumbView_groupBoxButton, SIGNAL("clicked()"),
                     self.toggle_thumbView_groupBox)
        self.connect(self.bondTool_groupBoxButton , SIGNAL("clicked()"),
                     self.toggle_bondTool_groupBox)
        self.connect(self.MMKitGrpBox_TitleButton, SIGNAL("clicked()"),
                     self.toggle_MMKit_groupBox)
        self.connect(self.filterCB, SIGNAL("stateChanged(int)"),
                     self.toggle_selectionFilter_groupBox)
        self.connect(self.advancedOptions_groupBoxButton, SIGNAL("clicked()"),
                     self.toggle_advancedOptions_groupBox)

        # Make the elements act like a big exclusive radio button.
        self.theElements = QtGui.QButtonGroup()
        self.theElements.setExclusive(True)
        self.theElements.addButton(self.toolButton1, 1)
        self.theElements.addButton(self.toolButton2, 2)
        self.theElements.addButton(self.toolButton6, 6)
        self.theElements.addButton(self.toolButton7, 7)
        self.theElements.addButton(self.toolButton8, 8)
        self.theElements.addButton(self.toolButton10, 10)
        self.theElements.addButton(self.toolButton9, 9)
        self.theElements.addButton(self.toolButton13,13)
        self.theElements.addButton(self.toolButton17,17)
        self.theElements.addButton(self.toolButton5, 5)
        self.theElements.addButton(self.toolButton10_2, 18)
        self.theElements.addButton(self.toolButton15, 15)
        self.theElements.addButton(self.toolButton16, 16)
        self.theElements.addButton(self.toolButton14, 14)
        self.theElements.addButton(self.toolButton33, 33)
        self.theElements.addButton(self.toolButton34, 34)
        self.theElements.addButton(self.toolButton35, 35)
        self.theElements.addButton(self.toolButton32, 32)
        self.theElements.addButton(self.toolButton36, 36)
        self.connect(self.theElements, SIGNAL("buttonPressed(int)"), self.update_dialog)

        self.theHybridizations = QtGui.QButtonGroup()
        self.theHybridizations.setExclusive(True)
        self.theHybridizations.addButton(self.sp3_btn, 0)
        self.theHybridizations.addButton(self.sp2_btn, 1)
        self.theHybridizations.addButton(self.sp_btn, 2)
        self.theHybridizations.addButton(self.graphitic_btn, 3)
        self.connect(self.theHybridizations, SIGNAL("buttonClicked(int)"), self.update_hybrid_btngrp)

        self.connect(self.filterCB,
                        SIGNAL("toggled(bool)"),self.set_selection_filter)


        self.elemTable = PeriodicTable
        self.displayMode = diTUBES
        self.elm = None

        self.newModel = None  ## used to save the selected lib part

        self.flayout = None

        # It looks like we now have correct fixes for bugs 1659 and bug 1824. If so, it would be safe to simply
        # hardware self.icon_tabs to True and simplify code accordingly. But we're not 100% certain, so by leaving
        # it as a debug pref, we can help any users who see those bugs come up again.
        # wware 060420
        #
        # Update, bruce 070328: the False value of this debug_pref is known to fail in the Qt4 version (on Mac anyway),
        # due to AttributeError exceptions for setMargin and setTabLabel,
        # so I'm changing the prefs key for it in order to let Qt3 and Qt4 have independent debug_pref settings,
        # adding a warning in the menu text, and adding a try/except to help in debugging this if anyone ever wants to.
        # (If the bugs Will mentioned go away entirely, we can abandon support for the False value instead of fixing it,
        #  as Will suggested.)
        from utilities.debug_prefs import debug_pref, Choice_boolean_True
        self.icon_tabs = debug_pref("use icons in MMKit tabs? (only True works in Qt4)", Choice_boolean_True,
                                    prefs_key = "A7/mmkit tab icons/Qt4")
            #e Changes to this only take effect in the next session.
            # Ideally we'd add a history message about that, when this is changed.
            # (It's not yet easy to do that in a supported way in debug_pref.) [bruce 060313]

        if not self.icon_tabs:
            # This code is known to fail in Qt4 Mac version, as explained above. [bruce 061222 and 070328]
            try:
                self.mmkit_tab.setMargin ( 0 )
            except:
                print_compact_traceback("ignoring this Qt4-specific exception: ") #bruce 061222
                pass
            self.mmkit_tab.setTabLabel (self.atomsPage, 'Atoms')
            self.mmkit_tab.setTabLabel (self.clipboardPage, 'Clipbd')
            self.mmkit_tab.setTabLabel (self.libraryPage, 'Lib')
        else:
            # Add icons to MMKit's tabs. mark 060223.
            atoms_ic = imagename_to_icon("actions/Properties Manager/MMKit.png")
            self.mmkit_tab.setTabIcon(self.mmkit_tab.indexOf(self.atomsPage), QIcon(atoms_ic))

            self.update_clipboard_page_icon() # Loads proper icon for clibpoard tab. Mark 2007-06-01

            library_ic = imagename_to_icon("actions/Properties Manager/library.png")
            self.mmkit_tab.setTabIcon(self.mmkit_tab.indexOf(self.libraryPage), QIcon(library_ic))

        # Tab tooltips. mark 060326
        self.mmkit_tab.setTabToolTip(self.mmkit_tab.indexOf(self.atomsPage), 'Atoms')
        self.mmkit_tab.setTabToolTip(self.mmkit_tab.indexOf(self.clipboardPage), 'Clipboard')
        self.mmkit_tab.setTabToolTip(self.mmkit_tab.indexOf(self.libraryPage), 'Part Library')

        self._setNewView('MMKitView')

        # Set current element in element button group.


        self.theElements.button(self.w.Element).setChecked(True)

        #self.connect(self., SIGNAL("), )

        self.connect(self.w.hybridComboBox, SIGNAL("activated(int)"), self.hybridChangedOutside)

        self.connect(self.w.hybridComboBox, SIGNAL("activated(const QString&)"), self.change2AtomsPage)
        self.connect(self.w.elemChangeComboBox, SIGNAL("activated(const QString&)"), self.change2AtomsPage)
        self.connect(self.w.pasteComboBox, SIGNAL("activated(const QString&)"), self.change2ClipboardPage)

        #self.connect(self.w.depositAtomDashboard.pasteBtn, SIGNAL("pressed()"), self.change2ClipboardPage)
        self.connect(self.w.depositAtomDashboard.pasteBtn, SIGNAL("stateChanged(int)"), self.pasteBtnStateChanged)
        self.connect(self.w.depositAtomDashboard.depositBtn, SIGNAL("stateChanged(int)"), self.depositBtnStateChanged)

        self.connect(self.dirView, SIGNAL("selectionChanged(QItemSelection *, QItemSelection *)"), self.partChanged)

        self.add_whats_this_text()

        return # from __init__

    # ==

    #bruce 060412 added everything related to __needs_update_xxx, to fix bugs 1726, 1629 and mitigate bug 1677;
    # for more info see the comments where update_clipboard_items is called (in depositMode.py).

    __needs_update_clipboard_items = False
        # (there could be other flags like this for other kinds of updates we might need)


    def show_propMgr(self):
        """
        Show the Build Property Manager.
        """
        #@NOTE: The build property manager files are still refered as MMKit and
        #MMKitDialog. This will change in the near future. -- ninad 20070717

        self.update_dialog(self.parentMode.w.Element)
        self.parentMode.set_selection_filter(False) # disable selection filter
        self.openPropertyManager(self)

        #Following is an old comment, was originally in depositMode.init_gui:
        #Do these before connecting signals or we'll get history msgs.
        #Part of fix for bug 1620. mark 060322
        self.highlightingCB.setChecked(self.parentMode.hover_highlighting_enabled)
        self.waterCB.setChecked(self.parentMode.water_enabled)


    def update_dialog(self, elemNum):
        """Called when the current element has been changed.
           Update non user interactive controls display for current selected
           element: element label info and element graphics info """

        elm = self.elemTable.getElement(elemNum)

        currentIndex = self.mmkit_tab.currentIndex()
        atomPageIndex = self.mmkit_tab.indexOf(self.atomsPage)

        ##if elm == self.elm and self.currentPageOpen(AtomsPage): return
        if elm == self.elm and (currentIndex == atomPageIndex) : return

        ## The following statements are redundant in some situations.
        self.theElements.button(elemNum).setChecked(True)
        self.w.Element = elemNum

        self.color = self.elemTable.getElemColor(elemNum)
        self.elm = self.elemTable.getElement(elemNum)

        self.update_hybrid_btngrp()

        self.elemGLPane.resetView()
        self.elemGLPane.refreshDisplay(self.elm, self.displayMode)

        #update the selection filter
        self.update_selection_filter_list()

        # Fix for bug 353, to allow the dialog to be updated with the correct page.  For example,
        # when the user selects Paste from the Edit toolbar/menu, the MMKit should show
        # the Clipboard page and not the Atoms page. Mark 050808
        if self.w.depositState == 'Clipboard':
            self.change2ClipboardPage()
        else:
            self.change2AtomsPage()
        self.tabCurrentChanged()

        self.updateBuildAtomsMessage() # Mark 2007-06-01

    def updateBuildAtomsMessage(self):
        """Updates the message box with an informative message based on the current page
        and current selected atom type.
        """
        msg = ""

        if self.MMKit_groupBox.isVisible():
            pageIndex = self.mmkit_tab.currentIndex()
            page = None

            if pageIndex is 0: # atomsPage
                msg = "Double click in empty space to insert a single " + self.elm.name + " atom. "
                if not self.elm.symbol in noblegases:
                    msg += "Click on an atom's <i>red bondpoint</i> to attach a " + self.elm.name + " atom to it."

            elif pageIndex is 1: # clipboardPage
                pastableItems = self.w.assy.shelf.get_pastable_chunks()
                if pastableItems:
                    msg = "Double click in empty space to insert a copy of the selected clipboard item. \
                    Click on a <i>red bondpoint</i> to attach a copy of the selected clipboard item."
                else:
                    msg = "There are no items on the clipboard."

            elif pageIndex is 2: # libraryPage
                msg = "Double click in empty space to insert a copy of the selected part in the library."

        else: # Bonds Tool is selected (MMKit groupbox is hidden).
            if self.parentMode.cutBondsAction.isChecked():
                msg = "<b> Cut Bonds </b> tool is active. \
                Click on bonds in order to delete them."
                self.MessageGroupBox.insertHtmlMessage(msg)
                return

            if not hasattr(self, 'bondclick_v6'): # Mark 2007-06-01
                return
            if self.bondclick_v6:
                name = btype_from_v6(self.bondclick_v6)
                msg = "Click bonds or bondpoints to make them %s bonds." % name # name is 'single' etc

        # Post message.
        self.MessageGroupBox.insertHtmlMessage(msg)

    #== Atom Selection Filter helper methods

    def set_selection_filter(self, enabled):
        """Slot for Atom Selection Filter checkbox. Prints history message when selection filter is
        enabled/disabled and updates the cursor.
        """

        if enabled != self.w.selection_filter_enabled:
            if enabled:
                env.history.message("Atom Selection Filter enabled.")
            else:
                env.history.message("Atom Selection Filter disabled.")

        self.w.selection_filter_enabled = enabled

        #print "update_selection_filter_list(): self.w.filtered_elements=", self.w.filtered_elements

        ##self.update_selection_filter_list_widget()
        self.update_selection_filter_list()
        self.filterlistLE.setEnabled(enabled)
        self.filterCB.setChecked(enabled)
        self.o.graphicsMode.update_cursor()

    def update_selection_filter_list(self):
        """Adds/removes the element selected in the MMKit to/from Atom Selection Filter
        based on what modifier key is pressed (if any).
        """
        eltnum = self.w.Element

        if self.o.modkeys is None:
            self.w.filtered_elements = []
            self.w.filtered_elements.append(PeriodicTable.getElement(eltnum))
        if self.o.modkeys == 'Shift':
            if not PeriodicTable.getElement(eltnum) in self.w.filtered_elements[:]:
                self.w.filtered_elements.append(PeriodicTable.getElement(eltnum))
        elif self.o.modkeys == 'Control':
            if PeriodicTable.getElement(eltnum) in self.w.filtered_elements[:]:
                self.w.filtered_elements.remove(PeriodicTable.getElement(eltnum))

        self.update_selection_filter_list_widget()

    def update_selection_filter_list_widget(self):
        """Updates the list of elements displayed in the Atom Selection Filter List.
        """
        filtered_syms=''
        for e in self.w.filtered_elements[:]:
            if filtered_syms: filtered_syms += ", "
            filtered_syms += e.symbol
        #self.w.depositAtomDashboard.filterlistLE.setText(filtered_syms)
        self.filterlistLE.setText(filtered_syms)

    def toggle_bondTool_groupBox(self):
        self.toggle_groupbox(self.bondTool_groupBoxButton, self.bondToolWidget)

    def toggle_thumbView_groupBox(self):
        self.toggle_groupbox(self.thumbView_groupBoxButton, self.elementFrame)

    def toggle_MMKit_groupBox(self):
        self.toggle_groupbox(self.MMKitGrpBox_TitleButton, self.mmkit_tab, self.transmuteBtn, self.transmuteCB)

    def toggle_selectionFilter_groupBox(self, state):
        """ Toggles the groupbox item display depending on checked state of the selection filter checkbox """

        #Current state is 'off' or(Qt.Unchecked)

        if state is 0:
            styleSheet = self.getGroupBoxCheckBoxStyleSheet(bool_expand = False)
            self.filterCB.setStyleSheet(styleSheet)

            palette = self.getGroupBoxCheckBoxPalette()
            self.filterCB.setPalette(palette)

            #hide the following widgets when checkbox is unchecked --
            self.selectionFilter_label.hide()
            self.filterlistLE.hide()
        else:
            styleSheet = self.getGroupBoxCheckBoxStyleSheet(bool_expand = True)
            self.filterCB.setStyleSheet(styleSheet)

            palette = self.getGroupBoxCheckBoxPalette()
            self.filterCB.setPalette(palette)

            #hide the following widgets when checkbox is unchecked --
            self.selectionFilter_label.show()
            self.filterlistLE.show()


    def toggle_advancedOptions_groupBox(self):
        self.toggle_groupbox(self.advancedOptions_groupBoxButton, self.autobondCB, self.waterCB, self.highlightingCB)

    #bruce 070615 removed 'def toggle_groupbox', since our mixin superclass PropertyManagerMixin
    # provides an identical-enough version.

    def tabCurrentChanged(self):
        pageIndex = self.mmkit_tab.currentIndex()
        page = None
        if pageIndex is 0: page = self.atomsPage
        elif pageIndex is 1: page = self.clipboardPage
        elif pageIndex is 2: page = self.libraryPage
        self.setup_current_page(page)

    def update_clipboard_items(self):
        self.__needs_update_clipboard_items = True
        self.update() # this makes sure self.event will get called; it might be better to test the flag only in self.repaint, not sure
        return

    def event(self, event): #bruce 060412 debug code, but also checks all self.__needs_update_xxx flags (an essential bugfix)
        if debug_mmkit_events:
            print "debug: MMKit.event got %r, type %r" % (event, event.type())
                # Qt doc for QEvent lists 'enum type' codes; the subclass is also printed by %r

        if self.__needs_update_clipboard_items:
            self.__really_update_clipboard_items()
            self.__needs_update_clipboard_items = False

        res = QDialog.event(self, event)
        if debug_mmkit_events:
            if res is not None:
                print "debug: MMKit.event returns %r" % (res,) # usually True, sometimes False
        # if we return None we get TypeError: invalid result type from MMKit.event()
        return res

    # ==

    def pasteBtnStateChanged(self, state):
        """Slot method. Called when the state of the Paste button of deposit dashboard has been changed. """
        if state == QButton.On:
            self.change2ClipboardPage()


    def depositBtnStateChanged(self, state):
        """Slot method. Called when the state of the Deposit button of deposit dashboard has been changed. """
        if state == QButton.On:
           self.change2AtomsPage()


    def hybridChangedOutside(self, newId):
        """Slot method. Called when user changes element hybridization from the dashboard.
         This method achieves the same effect as user clicked one of the hybridization buttons."""
        self.theElements.button(newId).setChecked(True)
        self.set_hybrid_type(newId)
        self.w.Element = newId

        ## fix bug 868
        self.w.depositAtomDashboard.depositBtn.setChecked(True)


    def change2AtomsPage(self):
        """Slot method called when user changes element/hybrid combobox or
        presses Deposit button from Build mode dashboard.
        """
        if self.mmkit_tab.currentIndex() != AtomsPage:
            self.mmkit_tab.setCurrentIndex(AtomsPage) # Generates signal

    def change2ClipboardPage(self):
        """Slot method called when user changes pastable item combobox or
        presses the Paste button from the Build mode dashboard. """
        if self.mmkit_tab.currentIndex() != ClipboardPage:
            self.mmkit_tab.setCurrentIndex(ClipboardPage) # Generates signal??


    def setElementInfo(self,value):
        """Slot method called when an element button is pressed in the element ButtonGroup.
        """
        self.w.setElement(value)


    def update_hybrid_btngrp(self, buttonIndex = 0):
        """Update the buttons of the current element\'s hybridization types into hybrid_btngrp;
        select the specified one if provided"""
        elem = PeriodicTable.getElement(self.w.Element) # self.w.Element is atomic number

        atypes = elem.atomtypes

        if elem.name == 'Carbon':
            self.setup_C_hybrid_buttons()
        elif elem.name == 'Nitrogen':
            self.setup_N_hybrid_buttons()
        elif elem.name == 'Oxygen':
            self.setup_O_hybrid_buttons()
        elif elem.name == 'Sulfur':
            self.setup_S_hybrid_buttons()
        else:
            self.hide_hybrid_btngrp()
            self.elemGLPane.changeHybridType(None)
            return

        #if len(atypes) > 1:
        # Prequisite: w.hybridComboBox has been updated at this moment.
        b_name = self.bond_id2name[buttonIndex]
        self.elemGLPane.changeHybridType(b_name)
        self.elemGLPane.refreshDisplay(self.elm, self.displayMode)
        self.theHybridizations.button(buttonIndex).setChecked(True)
        self.set_hybrid_type(buttonIndex)
        # Added Atomic Hybrids label. Mark 2007-05-30
        self.atomic_hybrids_label.setText("Atomic Hybrids for " + elem.name + " :")
        self.show_hybrid_btngrp()

    def show_hybrid_btngrp(self): # Mark 2007-06-20
        """Show the hybrid button group and label above it.
        This is a companion method to hide_hybrid_btngrp().
        It includes workarounds for Qt layout issues that crop up
        when hiding/showing the hybrid button groupbox using Qt's
        hide() and show() methods. See bug 2407 for more information.
        """
        if 1:
            self.hybrid_btngrp.show()
        else:
            self.hybrid_btngrp.show()
            self.atomic_hybrids_label.show()

    def hide_hybrid_btngrp(self): # Mark 2007-06-20
        """Hide the hybrid button group and label above it.
        This is a companion method to show_hybrid_btngrp().
        It includes workarounds for Qt layout issues that crop up
        when hiding/showing the hybrid button groupbox using Qt's
        hide() and show() methods. See bug 2407 for more information.
        """
        if 1:
            # This way of hiding confuses the layout manager, so I had
            # do create special spacers and set sizepolicies just to make
            # this work.
            self.hybrid_btngrp.hide()
            # I had to do this instead of use hide() since hide()
            # confuses the layout manager in special situations, like
            # that described in bug 2407. Mark 2007-06-20
            self.atomic_hybrids_label.setText(" ")
        else:
            # Alternate way of hiding. Hide all contents and the QButtonGroupbox
            # border, but there is no way to hide the border.
            self.sp3_btn.hide()
            self.sp2_btn.hide()
            self.sp_btn.hide()
            self.graphitic_btn.hide()
            self.atomic_hybrids_label.hide()

    def setup_C_hybrid_buttons(self):
        """Displays the Carbon hybrid buttons.
        """
        self.theElements.button(self.w.Element).setChecked(True)
        self.sp3_btn.setIcon(imagename_to_icon('modeltree/C_sp3.png'))
        self.sp3_btn.show()
        self.sp2_btn.setIcon(imagename_to_icon('modeltree/C_sp2.png'))
        self.sp2_btn.show()
        self.sp_btn.setIcon(imagename_to_icon('modeltree/C_sp.png'))
        self.sp_btn.show()
        self.graphitic_btn.hide()

    def setup_N_hybrid_buttons(self):
        """Displays the Nitrogen hybrid buttons.
        """
        self.sp3_btn.setIcon(imagename_to_icon('modeltree/N_sp3.png'))
        self.sp3_btn.show()
        self.sp2_btn.setIcon(imagename_to_icon('modeltree/N_sp2.png'))
        self.sp2_btn.show()
        self.sp_btn.setIcon(imagename_to_icon('modeltree/N_sp.png'))
        self.sp_btn.show()
        self.graphitic_btn.setIcon(imagename_to_icon('modeltree/N_graphitic.png'))
        self.graphitic_btn.show()


    def setup_O_hybrid_buttons(self):
        """Displays the Oxygen hybrid buttons.
        """
        self.sp3_btn.setIcon(imagename_to_icon('modeltree/O_sp3.png'))
        self.sp3_btn.show()
        self.sp2_btn.setIcon(imagename_to_icon('modeltree/O_sp2.png'))
        self.sp2_btn.show()
        self.sp_btn.hide()
        self.graphitic_btn.hide()


    def setup_S_hybrid_buttons(self):
        """Displays the Sulfur hybrid buttons.
        """
        self.sp3_btn.setIcon(imagename_to_icon('modeltree/O_sp3.png')) # S and O are the same.
        self.sp3_btn.show()
        self.sp2_btn.setIcon(imagename_to_icon('modeltree/O_sp2.png'))
        self.sp2_btn.show()
        self.sp_btn.hide()
        self.graphitic_btn.hide()


    def set_hybrid_type(self, type_id):
        """Slot method. Called when any of the hybrid type buttons was clicked. """
        self.w.hybridComboBox.setCurrentIndex( type_id )

        b_name = self.bond_id2name[type_id]

        #This condition fixs bug 866, also saves time since no need to draw without MMKIt shown
        if self.isVisible():
            self.elemGLPane.changeHybridType(b_name)
            self.elemGLPane.refreshDisplay(self.elm, self.displayMode)


    def setup_current_page(self, page):
        """Slot method that is called whenever a user clicks on the
        'Atoms', 'Clipboard' or 'Library' tab to change to that page.
        """

        #print "setup_current_page: page=", page

        if page == self.atomsPage:  # Atoms page
            self.w.depositState = 'Atoms'
            self.w.update_depositState_buttons()
            self.elemGLPane.resetView()
            self.elemGLPane.refreshDisplay(self.elm, self.displayMode)
            self.browseButton.hide()
            self.defaultPartLibButton.hide()
            self.atomsPageSpacer.changeSize(0,5,
                QtGui.QSizePolicy.Expanding,
                QtGui.QSizePolicy.Minimum)

        elif page == self.clipboardPage: # Clipboard page
            self.w.depositState = 'Clipboard'
            self.w.update_depositState_buttons()
            self.elemGLPane.setDisplay(self.displayMode)
            self._clipboardPageView()
            self.browseButton.hide()
            self.defaultPartLibButton.hide()
            self.atomsPageSpacer.changeSize(0,5,
                QtGui.QSizePolicy.Expanding,
                QtGui.QSizePolicy.Minimum)

        elif page == self.libraryPage: # Library page
            if self.rootDir:
                self.elemGLPane.setDisplay(self.displayMode)
                self._libPageView()
            self.browseButton.show()
            self.defaultPartLibButton.show()
            self.atomsPageSpacer.changeSize(0,5,
                QtGui.QSizePolicy.Minimum,
                QtGui.QSizePolicy.Minimum)

            #Turn off both paste and deposit buttons, so when in library page and user choose 'set hotspot and copy'
            #it will change to paste page, also, when no chunk selected, a history message shows instead of depositing an atom.
            self.w.depositState = 'Library'
            self.w.update_depositState_buttons()
        else:
            print 'Error: MMKit page unknown: ', page

        self.elemGLPane.setFocus()
        self.updateBuildAtomsMessage()

    def chunkChanged(self, item, previous):
        """Slot method. Called when user changed the selected chunk. """

        itemId = self.chunkListBox.row(item)

        if itemId != -1:

            newChunk = self.pastableItems[itemId]

            #self.w.pasteComboBox.setCurrentIndex(itemId)
            #buildModeObj = self.w.commandSequencer._commandTable['DEPOSIT']
            #assert buildModeObj
            #buildModeObj.setPaste()

            ##Compared to the above way, I think this way is better. Modules are more uncoupled.
            self.w.pasteComboBox.setCurrentIndex(itemId) # Fixes bug 1754. mark 060325

            self.elemGLPane.updateModel(newChunk)


    def __really_update_clipboard_items(self): #bruce 060412 renamed this from update_clipboard_items to __really_update_clipboard_items
        """Updates the items in the clipboard\'s listview, if the clipboard is currently shown. """
        if self.currentPageOpen(ClipboardPage): #bruce 060313 added this condition to fix bugs 1631, 1669, and MMKit part of 1627
            self._clipboardPageView() # includes self.update_clipboard_page_icon()
        else:
            self.update_clipboard_page_icon() # do this part of it, even if page not shown
        return

    def partChanged(self, item):
        """Slot method, called when user changed the partlib brower tree"""
        if isinstance(item, self.FileItem):
           self._libPageView(True)
        else:
           self.newModel = None
           self.elemGLPane.updateModel(self.newModel)


    def getPastablePart(self):
        """Public method. Retrieve pastable part and hotspot if current tab page is in libary, otherwise, return None. """
        if self.currentPageOpen(LibraryPage):
            return self.newModel, self.elemGLPane.hotspotAtom
        return None, None

    def currentPageOpen(self, page_id):
        """Returns True if <page_id> is the current page open in the tab widget, where:
            0 = Atoms page
            1 = Clipboard page
            2 = Library page
        """
        pageIndex = self.mmkit_tab.currentIndex()

        if page_id == pageIndex:
            return True
        else:
            return False

    def _libPageView(self, isFile = False):
        item = self.dirView.selectedItem()
        if not isFile and not isinstance(item, self.FileItem):
            self.newModel = None
            self.elemGLPane.updateModel(self.newModel)
            return

        mmpfile = str(item.getFileObj())
        if os.path.isfile(mmpfile):
            #self.newModel = Assembly(self.w, "Assembly 1")
            self.newModel = Assembly(self.w, os.path.normpath(mmpfile)) #ninad060924 to fix bug 1164
            self.newModel.o = self.elemGLPane ## Make it looks "Assembly" used by glpane.
            readmmp(self.newModel, mmpfile)

# What we did in Qt 3:
##         #self.newModel = Assembly(self.w, "Assembly 1")
##         self.newModel = Assembly(self.w, os.path.normpath(mmpfile)) #ninad060924 to fix bug 1164

##         self.newModel.o = self.elemGLPane ## Make it looks "Assembly" used by glpane.
##         readmmp(self.newModel, mmpfile)

##         # The following is absolute nonsense, and is part of what's breaking the fix of bug 2028,
##         # so it needs to be revised, to give this assy a standard structure.
##         # We'll have to find some other way to draw the hotspot singlet
##         # (e.g. a reasonable, straightforward way). So we did -- MMKitView.always_draw_hotspot is True.
##         # [bruce 060627]

## ##        # Move all stuff under assembly.tree into assy.shelf. This is needed to draw hotspot singlet
## ##        def addChild(child):
## ##            self.newModel.shelf.addchild(child)
## ##
## ##        # Remove existing clipboard items from the libary part before adopting childern from 'tree'.
## ##        self.newModel.shelf.members = []
## ##        self.newModel.tree.apply2all(addChild)
## ##
## ##        self.newModel.shelf.prior_part = None
## ##        self.newModel.part = Part(self.newModel, self.newModel.shelf)

##         if 1: #bruce 060627

            self.newModel.update_parts() #k not sure if needed after readmmp)
            self.newModel.checkparts()
            if self.newModel.shelf.members:
                if debug_flags.atom_debug:
                    print "debug warning: library part %r contains clipboard items" % mmpfile
                    # we'll see if this is common
                    # happens for e.g. nanokids/nanoKid-C39H42O2.mmp
                for m in self.newModel.shelf.members[:]:
                    m.kill() #k guess about a correct way to handle them
                self.newModel.update_parts() #k probably not needed
                self.newModel.checkparts() #k probably not needed
        else:
            self.newModel = None

        self.elemGLPane.updateModel(self.newModel)


    def _clipboardPageView(self):
        """Updates the clipboard page. """
        if not self.currentPageOpen(ClipboardPage):
            # (old code permitted this to be false below in 'if len(list):',
            #  but failed to check for it in the 'else' clause,
            #  thus causing bug 1627 [I think], now fixed in our caller)
            print "bug: _clipboardPageView called when not self.currentPageOpen(ClipboardPage)" #bruce 060313
            return

        self.pastableItems = self.w.assy.shelf.get_pastable_chunks()
        self.chunkListBox.clear()
        for item in self.pastableItems:
            self.chunkListBox.addItem(item.name)

        newModel = None
        if len(self.pastableItems):
            i = self.w.pasteComboBox.currentIndex()
            if i < 0:
                i = self.w.pasteComboBox.count() - 1
            # Make sure the clipboard page is open before calling selSelected(), because
            # setSelected() causes the clipboard page to be displayed when we don't want it to
            # be displayed (i.e. pressing Control+C to copy something to the clipboard).
            self.chunkListBox.setCurrentItem(self.chunkListBox.item(i))
            # bruce 060313 question: why don't we now have to pass the selected chunk to
            # self.elemGLPane.updateModel? ###@@@
            newModel = self.pastableItems[i]
        self.elemGLPane.updateModel(newModel)
        self.update_clipboard_page_icon()


    def update_clipboard_page_icon(self):
        """Updates the Clipboard page (tab) icon with a full or empty clipboard icon
        based on whether there is anything on the clipboard (pasteables).
        """
        if not self.icon_tabs:
            # Work around for bug 1659. mark 060310 [revised by bruce 060313]
            return

        if self.w.assy.shelf.get_pastable_chunks():
            clipboard_ic = imagename_to_icon("actions/Properties Manager/clipboard-full.png")
        else:
            clipboard_ic = imagename_to_icon("actions/Properties Manager/clipboard-empty.png")

        self.mmkit_tab.setTabIcon(self.mmkit_tab.indexOf(self.clipboardPage), QIcon(clipboard_ic))

    class DirView(QTreeView):
        def __init__(self, mmkit, parent):
            QTreeView.__init__(self, parent)

            self.setEnabled(True)
            self.model = QtGui.QDirModel(['*.mmp', '*.MMP'], # name filters
                                         QDir.AllEntries|QDir.AllDirs|QDir.NoDotAndDotDot, # filters
                                         QDir.Name # sort order
                                         )
                # explanation of filters (from Qt 4.2 doc for QDirModel):
                # - QDir.AllEntries = list files, dirs, drives, symlinks.
                # - QDir.AllDirs = include dirs regardless of other filters [guess: needed to ignore the name filters for dirs]
                # - QDir.NoDotAndDotDot = don't include '.' and '..' dirs
                #
                # about dirnames of "CVS":
                # The docs don't mention any way to filter the dirnames using a callback,
                # or any choices besides "filter them same as filenames" or "don't filter them".
                # So there is no documented way to filter out the "CVS" subdirectories like we did in Qt3
                # (short of subclassing this and overriding some methods,
                #  but the docs don't make it obvious how to do that correctly).
                # Fortunately, autoBuild.py removes them from the partlib copy in built releases.
                #
                # Other QDirModel methods we might want to use:
                # QDirModel.refresh, to update its state from the filesystem (but see the docs --
                #  they imply we might have to pass the model's root pathname to that method,
                #  even if it hasn't changed, but they're not entirely clear on that).
                #
                # [bruce 070502 comments]

            self.path = None
            self.mmkit = mmkit
            self.setModel(self.model)
            self.setWindowTitle(self.tr("Dir View"))

            self.setItemsExpandable(True)
            self.setAlternatingRowColors(True)
            self.setColumnWidth(0, 200)
            for i in range(2,4):
                self.setColumnWidth(i, 4)

            self.show()

        #Ninad 070326 reimplementing mouseReleaseEvent and resizeEvent
        #for DirView Class (which is a subclass of QTreeView)
        #The old code reimplemented 'event' class which handles *all* events.
        #There was a bad bug which didn't send an event when the widget is resized
        # and then the seletcion is changed. In NE1Qt3 this wasn't a problem because
        #it only had one column. Now that we have multiple columns
        #(which are needed to show the horizontal scrollbar.
        # While using Library page only resize event or mouse release events
        #by the user should update the thumbview.
        #The Qt documentation also suggests reimplementing subevents instead of the main
        #event handler method (event())
        def mouseReleaseEvent(self, evt):
            """ Reimplementation of mouseReleaseEvent method of QTreeView"""
            if self.selectedItem() is not None:
                    self.mmkit._libPageView()
            return QTreeView.mouseReleaseEvent(self, evt)

        def resizeEvent(self, evt):
            """ Reimplementation of resizeEvent method of QTreeView"""
            if self.selectedItem() is not None:
                    self.mmkit._libPageView()
            return QTreeView.resizeEvent(self, evt)


        #Following method (event() ) is not reimplemented anymore. Instead, the subevent handlers are
        #reimplemented (see above)  -- ninad 070326
        """
        def event(self, evt):
            if evt.type() == evt.Timer:
                # This is the event we get when the library selection changes, so if there has
                # been a library selection, update the library page's GLPane. But this can also
                # happen without a selection; in that case don't erase the atom page's display.
                if self.selectedItem() is not None:
                    self.mmkit._libPageView()
            return QTreeView.event(self, evt)"""

        def setRootPath(self, path):
            self.path = path
            self.setRootIndex(self.model.index(path))

        def selectedItem(self):
            indexes = self.selectedIndexes()
            if not indexes:
                return None
            index = indexes[0]
            if not index.isValid():
                return None

            return self.FileItem(str(self.model.filePath(index)))

    class FileItem:
        def __init__(self, path):
            self.path = path
            dummy, self.filename = os.path.split(path)
        def name(self):
            return self.filename
        def getFileObj(self):
            return self.path
    DirView.FileItem = FileItem

    def _setNewView(self, viewClassName):
        # Put the GL widget inside the frame
        if not self.flayout:
            self.flayout = QVBoxLayout(self.elementFrame)
            self.flayout.setMargin(1)
            self.flayout.setSpacing(1)
        else:
            if self.elemGLPane:
                self.flayout.removeChild(self.elemGLPane)
                self.elemGLPane = None

        if viewClassName == 'ChunkView':
            # We never come here! How odd.
            self.elemGLPane = ChunkView(self.elementFrame, "chunk glPane", self.w.glpane)
        elif viewClassName == 'MMKitView':
            self.elemGLPane = MMKitView(self.elementFrame, "MMKitView glPane", self.w.glpane)

        self.flayout.addWidget(self.elemGLPane,1)


        #ninad 070326. Note that self.DirView inherits QTreeView.
        #It has got nothing to do with the experimental class DirView in file Dirview.py
        self.dirView = self.DirView(self, self.libraryPage)
        self.dirView.setSortingEnabled(False) #bruce 070521 changed True to False -- fixes "reverse order" bug on my Mac
        ##self.dirView.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

        libraryPageLayout = QVBoxLayout(self.libraryPage)
        libraryPageLayout.setMargin(pmMMKitPageMargin) # Was 4. Mark 2007-05-30
        libraryPageLayout.setSpacing(2)
        libraryPageLayout.addWidget(self.dirView)


        filePath = os.path.dirname(os.path.abspath(sys.argv[0]))
        libDir = os.path.normpath(filePath + '/../partlib')

        self.libPathKey = '/nanorex/nE-1/libraryPath'
        libDir = env.prefs.get(self.libPathKey, libDir)

        if os.path.isdir(libDir):
            self.rootDir = libDir
            self.dirView.setRootPath(libDir)
        else:
            self.rootDir = None
            from history.HistoryWidget import redmsg
            env.history.message(redmsg("The part library directory: %s doesn't exist." %libDir))

    def browseDirectories(self):
       """Slot method for the browse button of library page. """
       # Determine what directory to open.
       if self.w.assy.filename:
           odir = os.path.dirname(self.w.assy.filename)
       else:
           from utilities.prefs_constants import workingDirectory_prefs_key
           odir = env.prefs[workingDirectory_prefs_key]

       fdir = QFileDialog.getExistingDirectory(self, "Choose library directory", odir)
       libDir = str(fdir)
       if libDir and os.path.isdir(libDir):
           env.prefs[self.libPathKey] = libDir
           self.dirView.setRootPath(libDir)
           #Refresh GL-thumbView display
           self.newModel = None
           self.elemGLPane.updateModel(self.newModel)

    def useDefaultPartLibDirectory(self):
        """ Slot method that to reset the part lib directory path to default"""
        from history.HistoryWidget import redmsg

        #ninad070503 : A future enhancement would be a preference to have
        # a user defined 'default dir path' for the
        #part lib location.

        # set libDir to the standard partlib path
        filePath = os.path.dirname(os.path.abspath(sys.argv[0]))
        libDir = os.path.normpath(filePath + '/../partlib')

        if libDir and os.path.isdir(libDir):
            if self.dirView.path == libDir:
                msg1 = "Current directory path for the partlib is the default path."
                msg2 = " Current path not changed"
                env.history.message(redmsg(msg1 + msg2))
                return
            env.prefs[self.libPathKey] = libDir
            self.dirView.setRootPath(libDir)
            #Refresh GL-thumbView display
            self.newModel = None
            self.elemGLPane.updateModel(self.newModel)
        else:
            msg1 = "The default patlib directory %s doesn't exist."%libDir
            msg2 = "Current path not changed"
            env.history.message(redmsg(msg1 + msg2))


    def closeEvent(self, e):
        """This event handler receives all MMKit close events.  In other words,
        when this dialog's close() slot gets called, this gets called with event 'e'.
        """
        self.hide()

        # If the 'Library' page is open, change it to 'Atoms'.  Mark 051212.
        if self.currentPageOpen(LibraryPage):
            self.setup_current_page(self.atomsPage)


#bruce 070615 commented out the following since I think it's obsolete:
# (evidence: no other mentions of 'polish' or 'move_to_best_location' in our code,
#  and MMKit is no longer a movable dialog)
##    num_polish = 0
##
##    def polish(self):
##        """This slot is called after a widget has been fully created and before it is shown the very first time.
##        Polishing is useful for final initialization which depends on having an instantiated widget.
##        This is something a constructor cannot guarantee since the initialization of the subclasses might not be finished.
##        After this function, the widget has a proper font and palette and QApplication.polish() has been called.
##        Remember to call QDialog's implementation when reimplementing this function.
##        """
##        QDialog.polish(self) # call QDialog's polish() implementation
##        self.num_polish += 1
##        #&print "num_polish =", self.num_polish
##        if self.num_polish < 2:
##            # polish() is called twice; not sure why.
##            # Call move_to_best_location() only after the second polish signal since
##            # get_location() can only get self.frameGeometry() after that.
##            return
##        self.move_to_best_location(True)


    def show(self):
        """MMKit\'s show slot.
        """
        #QDialog.show(self)
        #&print "MMKit.move: setting mainwindow to active window"
        #Show it in Property Manager Tab ninad061207
        if not self.pw or self:
            self.pw = self.w.activePartWindow()       #@@@ ninad061206
            self.pw.updatePropertyManagerTab(self)
            self.setSponsor()

        else:
            if not self.pw:
                self.pw = self.w.activePartWindow()
            self.pw.updatePropertyManagerTab(self)


        self.w.activateWindow() # Fixes bug 1503.  mark 060216.
            # Required to give the keyboard input focus back to self (MainWindow).

    def add_whats_this_text(self):
        """What's This text for some of the widgets in the Build > Atoms Property Manager.
        Many are still missing.
        """

        self.elementFrame.setWhatsThis("""<b>Preview Window</b>
        <p>This displays the active object. It can be inserted by double
        clicking in the 3D graphics area.</p>
        <p>Left click on a red bondpoint to make it a green <i>hotspot</i>
        (clipboard and library parts only). The hotspot indicates which
        bondpoint should be used to bond the current object when selecting
        a bondpoint in the model.</p>
        <p><u>Mouse Commands Supported</u><br>
        Left - Select hotspot (clipboard and library parts only)<br>
        Middle - Rotate view<br> \
        Wheel - Zoom in/out \
        </p>""")

        self.MMKit_groupBox.setWhatsThis("""<b>Molecular Modeling Kit</b>
        <p>A tabular widget for selecting atom types or other structures to insert
        into the model.</p>
        <p><b>Atoms Tab</b> - A select group of atom types options from the periodic
        table of elements.</p>
        <p><b>Clipboard Tab</b> - the list of objects copied on the clipboard (using
        the Copy command).</p>
        <p><b>Library Tab</b> - Accesses the NE1 Part Library. There are hundreds of
        structures to choose from.</p>""")

        self.transmuteBtn.setWhatsThis("""<b>Transmute Atoms</b>
        <p>When clicked, transmutes selected atoms to the current type displayed
        in the Preview window.</p>""")

        self.transmuteCB.setWhatsThis("""<b>Force to keep bonds</b>
        <p>When checked, all bonds remain in place when atoms are transmuted,
        even if this will result in unrealistic (chemical) bonds.</p>""")

        self.selectionFilter_groupBox.setWhatsThis("""<b>Atoms Selection Filter</b>
        <p>When activated, only atoms listed can be selected.</p>""")

        self.advancedOptions_groupBox.setWhatsThis("""<b>Advanced Options</b>
        <p><b>Autobond</b> - when checked, the atom being inserted or attached to another
        atom will autobond to other bondpoints of other candidate atoms automatically.</p>
        <p><b>Highlighting</b> - Turns on/off hover highlighting.</p>
        <p><b>Water</b>- Adds a tranparent plane normal to the screen through 0,0,0. Anything
        behind the water plane can not be selected.</p>""")

        self.transmuteAtomsAction.setWhatsThis("""<b>Transmute Atoms</b>
        <p>When clicked, transmutes selected atoms to the current type displayed
        in the Preview window.</p>""")

    pass # end of class MMKit
Ejemplo n.º 46
0
    def __init__(self, assy, parent):
        """
        Constructor for the part window.

        @param assy: The assembly (part)
        @type  assy: Assembly

        @param parent: The parent widget.
        @type  parent: U{B{QMainWindow}
                       <http://doc.trolltech.com/4/qmainwindow.html>}
        """
        QWidget.__init__(self, parent)
        self.parent = parent
        self.assy = assy
            # note: to support MDI, self.assy would probably need to be a
            # different assembly for each PartWindow.
            # [bruce 080216 comment]
        self.setWindowIcon(geticon("ui/border/Part.png"))
        self.updateWindowTitle()

        # Used in expanding or collapsing the Model Tree/ PM area
        self._previous_pwLeftAreaWidth = PM_DEFAULT_WIDTH

        # The main layout for the part window is a VBoxLayout <pwVBoxLayout>.
        self.pwVBoxLayout = QVBoxLayout(self)
        pwVBoxLayout = self.pwVBoxLayout
        pwVBoxLayout.setMargin(0)
        pwVBoxLayout.setSpacing(0)

        # ################################################################
        # <pwSplitter> is the horizontal splitter b/w the
        # pwProjectTabWidget and the glpane.
        self.pwSplitter = QSplitter(Qt.Horizontal)
        pwSplitter = self.pwSplitter
        pwSplitter.setObjectName("pwSplitter")
        pwSplitter.setHandleWidth(3) # 3 pixels wide.
        pwVBoxLayout.addWidget(pwSplitter)

        # ##################################################################
        # <pwLeftArea> is the container holding the pwProjectTabWidget.
        # Note: Making pwLeftArea (and pwRightArea and pwBottomArea) QFrame
        # widgets has the benefit of making it easy to draw a border around
        # each area. One purpose of this would be to help developers understand
        # (visually) how the part window is laid out. I intend to add a debug
        # pref to draw part window area borders and add "What's This" text to
        # them. Mark 2008-01-05.
        self.pwLeftArea = QFrame()
        pwLeftArea = self.pwLeftArea
        pwLeftArea.setObjectName("pwLeftArea")
        pwLeftArea.setMinimumWidth(PM_MINIMUM_WIDTH)
        pwLeftArea.setMaximumWidth(PM_MAXIMUM_WIDTH)
        pwLeftArea.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Fixed),
                        QSizePolicy.Policy(QSizePolicy.Expanding)))

        # Setting the frame style like this is nice since it clearly
        # defines the splitter at the top-left corner.
        pwLeftArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        # This layout will contain splitter (above) and the pwBottomArea.
        leftChannelVBoxLayout = QVBoxLayout(pwLeftArea)
        leftChannelVBoxLayout.setMargin(0)
        leftChannelVBoxLayout.setSpacing(0)

        pwSplitter.addWidget(pwLeftArea)

        # Makes it so pwLeftArea is not collapsible.
        pwSplitter.setCollapsible (0, False)

        # ##################################################################
        # <pwProjectTabWidget> is a QTabWidget that contains the MT and PM
        # widgets. It lives in the "left area" of the part window.
        self.pwProjectTabWidget = _pwProjectTabWidget()
           # _pwProjectTabWidget subclasses QTabWidget
           # Note [bruce 070829]: to fix bug 2522 I need to intercept
           # self.pwProjectTabWidget.removeTab, so I made it a subclass of
           # QTabWidget. It needs to know the GLPane, but that's not created
           # yet, so we set it later using KLUGE_setGLPane (below).
           # Note: No parent supplied. Could this be the source of the
           # minor vsplitter resizing problem I was trying to resolve a few
           # months ago?  Try supplying a parent later. Mark 2008-01-01
        self.pwProjectTabWidget.setObjectName("pwProjectTabWidget")
        self.pwProjectTabWidget.setCurrentIndex(0)
        self.pwProjectTabWidget.setAutoFillBackground(True)

        # Create the model tree "tab" widget. It will contain the MT GUI widget.
        # Set the tab icon, too.
        self.modelTreeTab = QWidget()
        self.modelTreeTab.setObjectName("modelTreeTab")
        self.pwProjectTabWidget.addTab(
            self.modelTreeTab,
            geticon("ui/modeltree/Model_Tree.png"),
            "")

        modelTreeTabLayout = QVBoxLayout(self.modelTreeTab)
        modelTreeTabLayout.setMargin(0)
        modelTreeTabLayout.setSpacing(0)

        # Create the model tree (GUI) and add it to the tab layout.
        self.modelTree = modelTree(self.modelTreeTab, parent)
        self.modelTree.modelTreeGui.setObjectName("modelTreeGui")
        modelTreeTabLayout.addWidget(self.modelTree.modelTreeGui)

        # Create the property manager "tab" widget. It will contain the PropMgr
        # scroll area, which will contain the property manager and all its
        # widgets.
        self.propertyManagerTab = QWidget()
        self.propertyManagerTab.setObjectName("propertyManagerTab")

        self.propertyManagerScrollArea = QScrollArea(self.pwProjectTabWidget)
        self.propertyManagerScrollArea.setObjectName("propertyManagerScrollArea")
        self.propertyManagerScrollArea.setWidget(self.propertyManagerTab)
        self.propertyManagerScrollArea.setWidgetResizable(True)
        # Eureka!
        # setWidgetResizable(True) will resize the Property Manager (and its
        # contents) correctly when the scrollbar appears/disappears.
        # It even accounts correctly for collapsed/expanded groupboxes!
        # Mark 2007-05-29

        # Add the property manager scroll area as a "tabbed" widget.
        # Set the tab icon, too.
        self.pwProjectTabWidget.addTab(
            self.propertyManagerScrollArea,
            geticon("ui/modeltree/Property_Manager.png"),
            "")

        # Finally, add the "pwProjectTabWidget" to the left channel layout.
        leftChannelVBoxLayout.addWidget(self.pwProjectTabWidget)

        # Create the glpane and make it a child of the part splitter.
        self.glpane = GLPane(assy, self, 'glpane name', parent)
            # note: our owner (MWsemantics) assumes
            # there is just this one GLPane for assy, and stores it
            # into assy as assy.o and assy.glpane. [bruce 080216 comment]
        self.pwProjectTabWidget.KLUGE_setGLPane(self.glpane)
            # help fix bug 2522 [bruce 070829]
        qt4warnDestruction(self.glpane, 'GLPane of PartWindow')
        pwSplitter.addWidget(self.glpane)

        # ##################################################################
        # <pwBottomArea> is a container at the bottom of the part window
        # spanning its entire width. It is intended to be used as an extra
        # area for use by Property Managers (or anything else) that needs
        # a landscape oriented layout.
        # An example is the Sequence Editor, which is part of the
        # Strand Properties PM.
        self.pwBottomArea = QFrame()
            # IMHO, self is not a good parent. Mark 2008-01-04.
        pwBottomArea = self.pwBottomArea
        pwBottomArea.setObjectName("pwBottomArea")
        pwBottomArea.setMaximumHeight(50)
        pwBottomArea.setSizePolicy(
            QSizePolicy(QSizePolicy.Policy(QSizePolicy.Expanding),
                        QSizePolicy.Policy(QSizePolicy.Fixed)))

        # Add a frame border to see what it looks like.
        pwBottomArea.setFrameStyle( QFrame.Panel | QFrame.Sunken )

        self.pwVBoxLayout.addWidget(pwBottomArea)

        # Hide the bottom frame for now. Later this might be used for the
        # sequence editor.
        pwBottomArea.hide()
Ejemplo n.º 47
0
class MainWindow(QMainWindow):
    about_message = """
    <P>PURR ("<B>P</B>URR is <B>U</B>seful for <B>R</B>emembering <B>R</B>eductions", for those working with
    a stable version, or "<B>P</B>URR <B>U</B>sually <B>R</B>emembers <B>R</B>eductions", for those
    working with a development version, or "<B>P</B>URR <B>U</B>sed to <B>R</B>emember <B>R</B>eductions",
    for those working with a broken version) is a tool for
    automatically keeping a log of your data reduction operations. PURR will monitor your working directories
    for new or updated files (called "data products"), and upon seeing any, it can "pounce" -- that is, offer
    you the option of saving the files to a log, along with descriptive comments. It will then
    generate an HTML page with a pretty rendering of your log and data products.</P>
  """

    def __init__(self, parent, hide_on_close=False):
        QMainWindow.__init__(self, parent)
        self._hide_on_close = hide_on_close
        # replace the BusyIndicator class with a GUI-aware one
        Purr.BusyIndicator = BusyIndicator
        self._pounce = False
        # we keep a small stack of previously active purrers. This makes directory changes
        # faster (when going back and forth between dirs)
        # current purrer
        self.purrer = None
        self.purrer_stack = []
        # Purr pipes for receiving remote commands
        self.purrpipes = {}
        # init GUI
        self.setWindowTitle("PURR")
        self.setWindowIcon(pixmaps.purr_logo.icon())
        cw = QWidget(self)
        self.setCentralWidget(cw)
        cwlo = QVBoxLayout(cw)
        cwlo.setContentsMargins(0, 0, 0, 0)
        cwlo.setMargin(5)
        cwlo.setSpacing(0)
        toplo = QHBoxLayout()
        cwlo.addLayout(toplo)

        # About dialog
        self._about_dialog = QMessageBox(self)
        self._about_dialog.setWindowTitle("About PURR")
        self._about_dialog.setText(self.about_message + """
        <P>PURR is not watching any directories right now. You may need to restart it, and give it
  some directory names on the command line.</P>""")
        self._about_dialog.setIconPixmap(pixmaps.purr_logo.pm())
        # Log viewer dialog
        self.viewer_dialog = HTMLViewerDialog(
            self,
            config_name="log-viewer",
            buttons=
            [(pixmaps.blue_round_reload, "Regenerate",
              """<P>Regenerates your log's HTML code from scratch. This can be useful if
                                                        your PURR version has changed, or if there was an error of some kind
                                                        the last time the files were generated.</P>
                                                        """)])
        self._viewer_timestamp = None
        self.connect(self.viewer_dialog, SIGNAL("Regenerate"),
                     self._regenerateLog)
        self.connect(self.viewer_dialog, SIGNAL("viewPath"), self._viewPath)

        # Log title toolbar
        title_tb = QToolBar(cw)
        title_tb.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        title_tb.setIconSize(QSize(16, 16))
        cwlo.addWidget(title_tb)
        title_label = QLabel("Purrlog title:", title_tb)
        title_tb.addWidget(title_label)
        self.title_editor = QLineEdit(title_tb)
        title_tb.addWidget(self.title_editor)
        self.connect(self.title_editor, SIGNAL("editingFinished()"),
                     self._titleChanged)
        tip = """<P>This is your current log title. To rename the log, enter new name here and press Enter.</P>"""
        title_label.setToolTip(tip)
        self.title_editor.setToolTip(tip)
        self.wviewlog = title_tb.addAction(pixmaps.openbook.icon(), "View",
                                           self._showViewerDialog)
        self.wviewlog.setToolTip(
            "Click to see an HTML rendering of your current log.")
        qa = title_tb.addAction(pixmaps.purr_logo.icon(), "About...",
                                self._about_dialog.exec_)
        qa.setToolTip(
            "<P>Click to see the About... dialog, which will tell you something about PURR.</P>"
        )

        self.wdirframe = QFrame(cw)
        cwlo.addWidget(self.wdirframe)
        self.dirs_lo = QVBoxLayout(self.wdirframe)
        self.dirs_lo.setMargin(5)
        self.dirs_lo.setContentsMargins(5, 0, 5, 5)
        self.dirs_lo.setSpacing(0)
        self.wdirframe.setFrameStyle(QFrame.Box | QFrame.Raised)
        self.wdirframe.setLineWidth(1)

        ## Directories toolbar
        dirs_tb = QToolBar(self.wdirframe)
        dirs_tb.setToolButtonStyle(Qt.ToolButtonIconOnly)
        dirs_tb.setIconSize(QSize(16, 16))
        self.dirs_lo.addWidget(dirs_tb)
        label = QLabel("Monitoring directories:", dirs_tb)
        self._dirs_tip = """<P>PURR can monitor your working directories for new or updated files. If there's a checkmark
      next to the directory name in this list, PURR is monitoring it.</P>

      <P>If the checkmark is grey, PURR is monitoring things unobtrusively. When a new or updated file is detected in he monitored directory,
      it is quietly added to the list of files in the "New entry" window, even if this window is not currently visible.</P>

      <P>If the checkmark is black, PURR will be more obtrusive. Whenever a new or updated file is detected, the "New entry" window will
      pop up automatically. This is called "pouncing", and some people find it annoying.</P>
      """
        label.setToolTip(self._dirs_tip)
        label.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
        dirs_tb.addWidget(label)

        # add directory list widget
        self.wdirlist = DirectoryListWidget(self.wdirframe)
        self.wdirlist.setToolTip(self._dirs_tip)
        QObject.connect(self.wdirlist, SIGNAL("directoryStateChanged"),
                        self._changeWatchedDirState)
        self.dirs_lo.addWidget(self.wdirlist)
        # self.wdirlist.setMaximumSize(1000000,64)

        # add directory button
        add = dirs_tb.addAction(pixmaps.list_add.icon(), "Add",
                                self._showAddDirectoryDialog)
        add.setToolTip(
            "<P>Click to add another directory to be monitored.</P>")

        # remove directory button
        delbtn = dirs_tb.addAction(pixmaps.list_remove.icon(), "Remove",
                                   self.wdirlist.removeCurrent)
        delbtn.setEnabled(False)
        delbtn.setToolTip(
            "<P>Click to removed the currently selected directory from the list.</P>"
        )
        QObject.connect(self.wdirlist, SIGNAL("hasSelection"),
                        delbtn.setEnabled)

        #    # qa = dirs_tb.addAction(pixmaps.blue_round_reload.icon(),"Rescan",self._forceRescan)
        #    # qa.setToolTip("Click to rescan the directories for any new or updated files.")
        #    self.wshownew = QCheckBox("show new files",dirs_tb)
        #    dirs_tb.addWidget(self.wshownew)
        #    self.wshownew.setCheckState(Qt.Checked)
        #    self.wshownew.setToolTip("""<P>If this is checked, the "New entry" window will pop up automatically whenever
        #  new or updated files are detected. If this is unchecked, the files will be added to the window quietly
        #        and unobtrusively; you can show the window manually by clicking on the "New entry..." button below.</P>""")
        #    self._dir_entries = {}

        cwlo.addSpacing(5)

        wlogframe = QFrame(cw)
        cwlo.addWidget(wlogframe)
        log_lo = QVBoxLayout(wlogframe)
        log_lo.setMargin(5)
        log_lo.setContentsMargins(5, 5, 5, 5)
        log_lo.setSpacing(0)
        wlogframe.setFrameStyle(QFrame.Box | QFrame.Raised)
        wlogframe.setLineWidth(1)

        # listview of log entries
        self.etw = LogEntryTree(cw)
        log_lo.addWidget(self.etw, 1)
        self.etw.header().setDefaultSectionSize(128)
        self.etw.header().setMovable(False)
        self.etw.setHeaderLabels(["date", "entry title", "comment"])
        if hasattr(QHeaderView, 'ResizeToContents'):
            self.etw.header().setResizeMode(0, QHeaderView.ResizeToContents)
        else:
            self.etw.header().setResizeMode(0, QHeaderView.Custom)
            self.etw.header().resizeSection(0, 120)
        self.etw.header().setResizeMode(1, QHeaderView.Interactive)
        self.etw.header().setResizeMode(2, QHeaderView.Stretch)
        self.etw.header().show()
        try:
            self.etw.setAllColumnsShowFocus(True)
        except AttributeError:
            pass
            # Qt 4.2+
        # self.etw.setShowToolTips(True)
        self.etw.setSortingEnabled(False)
        # self.etw.setColumnAlignment(2,Qt.AlignLeft|Qt.AlignTop)
        self.etw.setSelectionMode(QTreeWidget.ExtendedSelection)
        self.etw.setRootIsDecorated(True)
        self.connect(self.etw, SIGNAL("itemSelectionChanged()"),
                     self._entrySelectionChanged)
        self.connect(self.etw, SIGNAL("itemActivated(QTreeWidgetItem*,int)"),
                     self._viewEntryItem)
        self.connect(self.etw, SIGNAL("itemContextMenuRequested"),
                     self._showItemContextMenu)
        # create popup menu for data products
        self._archived_dp_menu = menu = QMenu(self)
        self._archived_dp_menu_title = QLabel()
        self._archived_dp_menu_title.setMargin(5)
        self._archived_dp_menu_title_wa = wa = QWidgetAction(self)
        wa.setDefaultWidget(self._archived_dp_menu_title)
        menu.addAction(wa)
        menu.addSeparator()
        menu.addAction(pixmaps.editcopy.icon(),
                       "Restore file(s) from archived copy",
                       self._restoreItemFromArchive)
        menu.addAction(pixmaps.editpaste.icon(),
                       "Copy pathname of archived copy to clipboard",
                       self._copyItemToClipboard)
        self._current_item = None
        # create popup menu for entries
        self._entry_menu = menu = QMenu(self)
        self._entry_menu_title = QLabel()
        self._entry_menu_title.setMargin(5)
        self._entry_menu_title_wa = wa = QWidgetAction(self)
        wa.setDefaultWidget(self._entry_menu_title)
        menu.addAction(wa)
        menu.addSeparator()
        menu.addAction(pixmaps.filefind.icon(), "View this log entry",
                       self._viewEntryItem)
        menu.addAction(pixmaps.editdelete.icon(), "Delete this log entry",
                       self._deleteSelectedEntries)
        # buttons at bottom
        log_lo.addSpacing(5)
        btnlo = QHBoxLayout()
        log_lo.addLayout(btnlo)
        self.wnewbtn = QPushButton(pixmaps.filenew.icon(), "New entry...", cw)
        self.wnewbtn.setToolTip("Click to add a new log entry.")
        # self.wnewbtn.setFlat(True)
        self.wnewbtn.setEnabled(False)
        btnlo.addWidget(self.wnewbtn)
        btnlo.addSpacing(5)
        self.weditbtn = QPushButton(pixmaps.filefind.icon(), "View entry...",
                                    cw)
        self.weditbtn.setToolTip(
            "Click to view or edit the selected log entry/")
        # self.weditbtn.setFlat(True)
        self.weditbtn.setEnabled(False)
        self.connect(self.weditbtn, SIGNAL("clicked()"), self._viewEntryItem)
        btnlo.addWidget(self.weditbtn)
        btnlo.addSpacing(5)
        self.wdelbtn = QPushButton(pixmaps.editdelete.icon(), "Delete", cw)
        self.wdelbtn.setToolTip(
            "Click to delete the selected log entry or entries.")
        # self.wdelbtn.setFlat(True)
        self.wdelbtn.setEnabled(False)
        self.connect(self.wdelbtn, SIGNAL("clicked()"),
                     self._deleteSelectedEntries)
        btnlo.addWidget(self.wdelbtn)
        # enable status line
        self.statusBar().show()
        Purr.progressMessage = self.message
        self._prev_msg = None
        # editor dialog for new entry
        self.new_entry_dialog = Purr.Editors.NewLogEntryDialog(self)
        self.connect(self.new_entry_dialog, SIGNAL("newLogEntry"),
                     self._newLogEntry)
        self.connect(self.new_entry_dialog, SIGNAL("filesSelected"),
                     self._addDPFiles)
        self.connect(self.wnewbtn, SIGNAL("clicked()"),
                     self.new_entry_dialog.show)
        self.connect(self.new_entry_dialog, SIGNAL("shown"),
                     self._checkPounceStatus)
        # entry viewer dialog
        self.view_entry_dialog = Purr.Editors.ExistingLogEntryDialog(self)
        self.connect(self.view_entry_dialog, SIGNAL("previous()"),
                     self._viewPrevEntry)
        self.connect(self.view_entry_dialog, SIGNAL("next()"),
                     self._viewNextEntry)
        self.connect(self.view_entry_dialog, SIGNAL("viewPath"),
                     self._viewPath)
        self.connect(self.view_entry_dialog, SIGNAL("filesSelected"),
                     self._addDPFilesToOldEntry)
        self.connect(self.view_entry_dialog, SIGNAL("entryChanged"),
                     self._entryChanged)
        # saving a data product to an older entry will automatically drop it from the
        # new entry dialog
        self.connect(self.view_entry_dialog, SIGNAL("creatingDataProduct"),
                     self.new_entry_dialog.dropDataProducts)
        # resize selves
        width = Config.getint('main-window-width', 512)
        height = Config.getint('main-window-height', 512)
        self.resize(QSize(width, height))
        # create timer for pouncing
        self._timer = QTimer(self)
        self.connect(self._timer, SIGNAL("timeout()"), self._rescan)
        # create dict mapping index.html paths to entry numbers
        self._index_paths = {}

    def resizeEvent(self, ev):
        QMainWindow.resizeEvent(self, ev)
        sz = ev.size()
        Config.set('main-window-width', sz.width())
        Config.set('main-window-height', sz.height())

    def closeEvent(self, ev):
        if self._hide_on_close:
            ev.ignore()
            self.hide()
            self.new_entry_dialog.hide()
        else:
            if self.purrer:
                self.purrer.detach()
            return QMainWindow.closeEvent(self, ev)

    def message(self, msg, ms=2000, sub=False):
        if sub:
            if self._prev_msg:
                msg = ": ".join((self._prev_msg, msg))
        else:
            self._prev_msg = msg
        self.statusBar().showMessage(msg, ms)
        QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

    def _changeWatchedDirState(self, pathname, watching):
        self.purrer.setWatchingState(pathname, watching)
        # update dialogs if dir list has changed
        if watching == Purr.REMOVED:
            self.purrpipes.pop(pathname)
            dirs = [path for path, state in self.purrer.watchedDirectories()]
            self.new_entry_dialog.setDefaultDirs(*dirs)
            self.view_entry_dialog.setDefaultDirs(*dirs)
        pass

    def _showAddDirectoryDialog(self):
        dd = str(
            QFileDialog.getExistingDirectory(
                self, "PURR: Add a directory to monitor")).strip()
        if dd:
            # adds a watched directory. Default initial setting of 'watching' is POUNCE if all
            # directories are in POUNCE state, or WATCHED otherwise.
            watching = max(
                Purr.WATCHED,
                min([
                    state for path, state in self.purrer.watchedDirectories()
                ] or [Purr.WATCHED]))
            self.purrer.addWatchedDirectory(dd, watching)
            self.purrpipes[dd] = Purr.Pipe.open(dd)
            self.wdirlist.add(dd, watching)
            # update dialogs since dir list has changed
            dirs = [path for path, state in self.purrer.watchedDirectories()]
            self.new_entry_dialog.setDefaultDirs(*dirs)
            self.view_entry_dialog.setDefaultDirs(*dirs)

    def detachPurrlog(self):
        self.wdirlist.clear()
        self.purrer and self.purrer.detach()
        self.purrer = None

    def hasPurrlog(self):
        return bool(self.purrer)

    def attachPurrlog(self, purrlog, watchdirs=[]):
        """Attaches Purr to the given purrlog directory. Arguments are passed to Purrer object as is."""
        # check purrer stack for a Purrer already watching this directory
        dprint(1, "attaching to purrlog", purrlog)
        for i, purrer in enumerate(self.purrer_stack):
            if os.path.samefile(purrer.logdir, purrlog):
                dprint(1, "Purrer object found on stack (#%d),reusing\n", i)
                # found? move to front of stack
                self.purrer_stack.pop(i)
                self.purrer_stack.insert(0, purrer)
                # update purrer with watched directories, in case they have changed
                for dd in (watchdirs or []):
                    purrer.addWatchedDirectory(dd, watching=None)
                break
        # no purrer found, make a new one
        else:
            dprint(1, "creating new Purrer object")
            try:
                purrer = Purr.Purrer(purrlog, watchdirs)
            except Purr.Purrer.LockedError as err:
                # check that we could attach, display message if not
                QMessageBox.warning(
                    self, "Catfight!",
                    """<P><NOBR>It appears that another PURR process (%s)</NOBR>
          is already attached to <tt>%s</tt>, so we're not allowed to touch it. You should exit the other PURR
          process first.</P>""" % (err.args[0], os.path.abspath(purrlog)),
                    QMessageBox.Ok, 0)
                return False
            except Purr.Purrer.LockFailError as err:
                QMessageBox.warning(
                    self, "Failed to obtain lock",
                    """<P><NOBR>PURR was unable to obtain a lock</NOBR>
          on directory <tt>%s</tt> (error was "%s"). The most likely cause is insufficient permissions.</P>"""
                    % (os.path.abspath(purrlog), err.args[0]), QMessageBox.Ok,
                    0)
                return False
            self.purrer_stack.insert(0, purrer)
            # discard end of stack
            self.purrer_stack = self.purrer_stack[:3]
            # attach signals
            self.connect(purrer, SIGNAL("disappearedFile"),
                         self.new_entry_dialog.dropDataProducts)
            self.connect(purrer, SIGNAL("disappearedFile"),
                         self.view_entry_dialog.dropDataProducts)
        # have we changed the current purrer? Update our state then
        # reopen Purr pipes
        self.purrpipes = {}
        for dd, state in purrer.watchedDirectories():
            self.purrpipes[dd] = Purr.Pipe.open(dd)
        if purrer is not self.purrer:
            self.message("Attached to %s" % purrer.logdir, ms=10000)
            dprint(1, "current Purrer changed, updating state")
            # set window title
            path = Kittens.utils.collapseuser(os.path.join(purrer.logdir, ''))
            self.setWindowTitle("PURR - %s" % path)
            # other init
            self.purrer = purrer
            self.new_entry_dialog.hide()
            self.new_entry_dialog.reset()
            dirs = [path for path, state in purrer.watchedDirectories()]
            self.new_entry_dialog.setDefaultDirs(*dirs)
            self.view_entry_dialog.setDefaultDirs(*dirs)
            self.view_entry_dialog.hide()
            self.viewer_dialog.hide()
            self._viewing_ientry = None
            self._setEntries(self.purrer.getLogEntries())
            #      print self._index_paths
            self._viewer_timestamp = None
            self._updateViewer()
            self._updateNames()
            # update directory widgets
            self.wdirlist.clear()
            for pathname, state in purrer.watchedDirectories():
                self.wdirlist.add(pathname, state)
            # Reset _pounce to false -- this will cause checkPounceStatus() into a rescan
            self._pounce = False
            self._checkPounceStatus()
        return True

    def setLogTitle(self, title):
        if self.purrer:
            if title != self.purrer.logtitle:
                self.purrer.setLogTitle(title)
                self._updateViewer()
            self._updateNames()

    def _updateNames(self):
        self.wnewbtn.setEnabled(True)
        self.wviewlog.setEnabled(True)
        self._about_dialog.setText(self.about_message + """
      <P>Your current log resides in:<PRE>  <tt>%s</tt></PRE>To see your log in all its HTML-rendered
      glory, point your browser to <tt>index.html</tt> therein, or use the handy "View" button provided by PURR.</P>

      <P>Your current working directories are:</P>
      <P>%s</P>
      """ % (self.purrer.logdir, "".join([
            "<PRE>  <tt>%s</tt></PRE>" % name
            for name, state in self.purrer.watchedDirectories()
        ])))
        title = self.purrer.logtitle or "Unnamed log"
        self.title_editor.setText(title)
        self.viewer_dialog.setWindowTitle(title)

    def _showViewerDialog(self):
        self._updateViewer(True)
        self.viewer_dialog.show()

    @staticmethod
    def fileModTime(path):
        try:
            return os.path.getmtime(path)
        except:
            return None

    def _updateViewer(self, force=False):
        """Updates the viewer dialog.
        If dialog is not visible and force=False, does nothing.
        Otherwise, checks the mtime of the current purrer index.html file against self._viewer_timestamp.
        If it is newer, reloads it.
        """
        if not force and not self.viewer_dialog.isVisible():
            return
        # default text if nothing is found
        path = self.purrer.indexfile
        mtime = self.fileModTime(path)
        # return if file is older than our content
        if mtime and mtime <= (self._viewer_timestamp or 0):
            return
        busy = BusyIndicator()
        self.viewer_dialog.setDocument(
            path,
            empty="<P>Nothing in the log yet. Try adding some log entries.</P>"
        )
        self.viewer_dialog.reload()
        self.viewer_dialog.setLabel(
            """<P>Below is your full HTML-rendered log. Note that this is 
      only a bare-bones viewer, so only a limited set of links will work. 
      For a fully-functional view, use a proper HTML browser to look at the index file residing here:<BR>
      <tt>%s</tt></P>
      """ % self.purrer.indexfile)
        self._viewer_timestamp = mtime

    def _setEntries(self, entries):
        self.etw.clear()
        item = None
        self._index_paths = {}
        self._index_paths[os.path.abspath(self.purrer.indexfile)] = -1
        for i, entry in enumerate(entries):
            item = self._addEntryItem(entry, i, item)
            self._index_paths[os.path.abspath(entry.index_file)] = i
        self.etw.resizeColumnToContents(0)

    def _titleChanged(self):
        self.setLogTitle(str(self.title_editor.text()))

    def _checkPounceStatus(self):
        ## pounce = bool([ entry for entry in self._dir_entries.itervalues() if entry.watching ])
        pounce = bool([
            path for path, state in self.purrer.watchedDirectories()
            if state >= Purr.WATCHED
        ])
        # rescan, if going from not-pounce to pounce
        if pounce and not self._pounce:
            self._rescan()
        self._pounce = pounce
        # start timer -- we need it running to check the purr pipe, anyway
        self._timer.start(2000)

    def _forceRescan(self):
        if not self.purrer:
            self.attachDirectory('.')
        self._rescan(force=True)

    def _rescan(self, force=False):
        if not self.purrer:
            return
        # if pounce is on, tell the Purrer to rescan directories
        if self._pounce or force:
            dps = self.purrer.rescan()
            if dps:
                filenames = [dp.filename for dp in dps]
                dprint(2, "new data products:", filenames)
                self.message("Pounced on " + ", ".join(filenames))
                if self.new_entry_dialog.addDataProducts(dps):
                    dprint(2, "showing dialog")
                    self.new_entry_dialog.show()
        # else read stuff from pipe
        for pipe in self.purrpipes.values():
            do_show = False
            for command, show, content in pipe.read():
                if command == "title":
                    self.new_entry_dialog.suggestTitle(content)
                elif command == "comment":
                    self.new_entry_dialog.addComment(content)
                elif command == "pounce":
                    self.new_entry_dialog.addDataProducts(
                        self.purrer.makeDataProducts([(content, not show)],
                                                     unbanish=True))
                else:
                    print("Unknown command received from Purr pipe: ", command)
                    continue
                do_show = do_show or show
            if do_show:
                self.new_entry_dialog.show()

    def _addDPFiles(self, *files):
        """callback to add DPs corresponding to files."""
        # quiet flag is always true
        self.new_entry_dialog.addDataProducts(
            self.purrer.makeDataProducts([(file, True) for file in files],
                                         unbanish=True,
                                         unignore=True))

    def _addDPFilesToOldEntry(self, *files):
        """callback to add DPs corresponding to files."""
        # quiet flag is always true
        self.view_entry_dialog.addDataProducts(
            self.purrer.makeDataProducts([(file, True) for file in files],
                                         unbanish=True,
                                         unignore=True))

    def _entrySelectionChanged(self):
        selected = [
            item for item in self.etw.iterator(self.etw.Iterator.Selected)
            if item._ientry is not None
        ]
        self.weditbtn.setEnabled(len(selected) == 1)
        self.wdelbtn.setEnabled(bool(selected))

    def _viewEntryItem(self, item=None, *dum):
        """Pops up the viewer dialog for the entry associated with the given item.
        If 'item' is None, looks for a selected item in the listview.
        The dum arguments are for connecting this to QTreeWidget signals such as doubleClicked().
        """
        # if item not set, look for selected items in listview. Only 1 must be selected.
        select = True
        if item is None:
            selected = [
                item for item in self.etw.iterator(self.etw.Iterator.Selected)
                if item._ientry is not None
            ]
            if len(selected) != 1:
                return
            item = selected[0]
            select = False
            # already selected
        else:
            # make sure item is open -- the click will cause it to close
            self.etw.expandItem(item)
        # show dialog
        ientry = getattr(item, '_ientry', None)
        if ientry is not None:
            self._viewEntryNumber(ientry, select=select)

    def _viewEntryNumber(self, ientry, select=True):
        """views entry #ientry. Also selects entry in listview if select=True"""
        # pass entry to viewer dialog
        self._viewing_ientry = ientry
        entry = self.purrer.entries[ientry]
        busy = BusyIndicator()
        self.view_entry_dialog.viewEntry(
            entry,
            prev=ientry > 0 and self.purrer.entries[ientry - 1],
            next=ientry < len(self.purrer.entries) - 1
            and self.purrer.entries[ientry + 1])
        self.view_entry_dialog.show()
        # select entry in listview
        if select:
            self.etw.clearSelection()
            self.etw.setItemSelected(self.etw.topLevelItem(ientry), True)

    def _viewPrevEntry(self):
        if self._viewing_ientry is not None and self._viewing_ientry > 0:
            self._viewEntryNumber(self._viewing_ientry - 1)

    def _viewNextEntry(self):
        if self._viewing_ientry is not None and self._viewing_ientry < len(
                self.purrer.entries) - 1:
            self._viewEntryNumber(self._viewing_ientry + 1)

    def _viewPath(self, path):
        num = self._index_paths.get(os.path.abspath(path), None)
        if num is None:
            return
        elif num == -1:
            self.view_entry_dialog.hide()
            self._showViewerDialog()
        else:
            self._viewEntryNumber(num)

    def _showItemContextMenu(self, item, point, col):
        """Callback for contextMenuRequested() signal. Pops up item menu, if defined"""
        menu = getattr(item, '_menu', None)
        if menu:
            settitle = getattr(item, '_set_menu_title', None)
            if settitle:
                settitle()
            # self._current_item tells callbacks what item the menu was referring to
            point = self.etw.mapToGlobal(point)
            self._current_item = item
            self.etw.clearSelection()
            self.etw.setItemSelected(item, True)
            menu.exec_(point)
        else:
            self._current_item = None

    def _copyItemToClipboard(self):
        """Callback for item menu."""
        if self._current_item is None:
            return
        dp = getattr(self._current_item, '_dp', None)
        if dp and dp.archived:
            path = dp.fullpath.replace(" ", "\\ ")
            QApplication.clipboard().setText(path, QClipboard.Clipboard)
            QApplication.clipboard().setText(path, QClipboard.Selection)

    def _restoreItemFromArchive(self):
        """Callback for item menu."""
        if self._current_item is None:
            return
        dp = getattr(self._current_item, '_dp', None)
        if dp and dp.archived:
            dp.restore_from_archive(parent=self)

    def _deleteSelectedEntries(self):
        remaining_entries = []
        del_entries = list(self.etw.iterator(self.etw.Iterator.Selected))
        remaining_entries = list(
            self.etw.iterator(self.etw.Iterator.Unselected))
        if not del_entries:
            return
        hide_viewer = bool([
            item for item in del_entries
            if self._viewing_ientry == item._ientry
        ])
        del_entries = [
            self.purrer.entries[self.etw.indexOfTopLevelItem(item)]
            for item in del_entries
        ]
        remaining_entries = [
            self.purrer.entries[self.etw.indexOfTopLevelItem(item)]
            for item in remaining_entries
        ]
        # ask for confirmation
        if len(del_entries) == 1:
            msg = """<P><NOBR>Permanently delete the log entry</NOBR> "%s"?</P>""" % del_entries[
                0].title
            if del_entries[0].dps:
                msg += """<P>%d data product(s) saved with this
                  entry will be deleted as well.</P>""" % len(
                    del_entries[0].dps)
        else:
            msg = """<P>Permanently delete the %d selected log entries?</P>""" % len(
                del_entries)
            ndp = 0
            for entry in del_entries:
                ndp += len([dp for dp in entry.dps if not dp.ignored])
            if ndp:
                msg += """<P>%d data product(s) saved with these entries will be deleted as well.</P>""" % ndp
        if QMessageBox.warning(self, "Deleting log entries", msg,
                               QMessageBox.Yes,
                               QMessageBox.No) != QMessageBox.Yes:
            return
        if hide_viewer:
            self.view_entry_dialog.hide()
        # reset entries in purrer and in our log window
        self._setEntries(remaining_entries)
        self.purrer.deleteLogEntries(del_entries)
        #    self.purrer.setLogEntries(remaining_entries)
        # log will have changed, so update the viewer
        self._updateViewer()
        # delete entry files
        for entry in del_entries:
            entry.remove_directory()

    def _addEntryItem(self, entry, number, after):
        item = entry.tw_item = QTreeWidgetItem(self.etw, after)
        timelabel = self._make_time_label(entry.timestamp)
        item.setText(0, timelabel)
        item.setText(1, " " + (entry.title or ""))
        item.setToolTip(1, entry.title)
        if entry.comment:
            item.setText(2, " " + entry.comment.split('\n')[0])
            item.setToolTip(2, "<P>" + entry.comment.replace("<", "&lt;").replace(">", "&gt;"). \
                            replace("\n\n", "</P><P>").replace("\n", "</P><P>") + "</P>")
        item._ientry = number
        item._dp = None
        item._menu = self._entry_menu
        item._set_menu_title = lambda: self._entry_menu_title.setText(
            '"%s"' % entry.title)
        # now make subitems for DPs
        subitem = None
        for dp in entry.dps:
            if not dp.ignored:
                subitem = self._addDPSubItem(dp, item, subitem)
        self.etw.collapseItem(item)
        self.etw.header().headerDataChanged(Qt.Horizontal, 0, 2)
        return item

    def _addDPSubItem(self, dp, parent, after):
        item = QTreeWidgetItem(parent, after)
        item.setText(1, dp.filename)
        item.setToolTip(1, dp.filename)
        item.setText(2, dp.comment or "")
        item.setToolTip(2, dp.comment or "")
        item._ientry = None
        item._dp = dp
        item._menu = self._archived_dp_menu
        item._set_menu_title = lambda: self._archived_dp_menu_title.setText(
            os.path.basename(dp.filename))
        return item

    def _make_time_label(self, timestamp):
        return time.strftime("%b %d %H:%M", time.localtime(timestamp))

    def _newLogEntry(self, entry):
        """This is called when a new log entry is created"""
        # add entry to purrer
        self.purrer.addLogEntry(entry)
        # add entry to listview if it is not an ignored entry
        # (ignored entries only carry information about DPs to be ignored)
        if not entry.ignore:
            if self.etw.topLevelItemCount():
                lastitem = self.etw.topLevelItem(self.etw.topLevelItemCount() -
                                                 1)
            else:
                lastitem = None
            self._addEntryItem(entry, len(self.purrer.entries) - 1, lastitem)
            self._index_paths[os.path.abspath(
                entry.index_file)] = len(self.purrer.entries) - 1
        # log will have changed, so update the viewer
        if not entry.ignore:
            self._updateViewer()
            self.show()

    def _entryChanged(self, entry):
        """This is called when a log entry is changed"""
        # resave the log
        self.purrer.save()
        # redo entry item
        if entry.tw_item:
            number = entry.tw_item._ientry
            entry.tw_item = None
            self.etw.takeTopLevelItem(number)
            if number:
                after = self.etw.topLevelItem(number - 1)
            else:
                after = None
            self._addEntryItem(entry, number, after)
        # log will have changed, so update the viewer
        self._updateViewer()

    def _regenerateLog(self):
        if QMessageBox.question(
                self.viewer_dialog, "Regenerate log",
                """<P><NOBR>Do you really want to regenerate the
      entire</NOBR> log? This can be a time-consuming operation.</P>""",
                QMessageBox.Yes, QMessageBox.No) != QMessageBox.Yes:
            return
        self.purrer.save(refresh=True)
        self._updateViewer()