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)
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, 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 = []
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, 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)
class Dialog(QDialog): def __init__(self, title, widget=None, closeButton=True, keySequence=None, isDialog=False, icon=None): QDialog.__init__(self, ctx.mainScreen) self.setObjectName("dialog") self.isDialog = isDialog self.layout = QVBoxLayout() self.setLayout(self.layout) self.wlayout= QHBoxLayout() if icon: self.setStyleSheet("""QDialog QLabel{ margin-left:16px;margin-right:10px} QDialog#dialog {background-image:url(':/images/%s.png'); background-repeat:no-repeat; background-position: top left; padding-left:500px;} """ % icon) self.windowTitle = windowTitle(self, closeButton) self.setTitle(title) self.layout.setMargin(0) self.layout.addWidget(self.windowTitle) if widget: self.addWidget(widget) QObject.connect(widget, SIGNAL("finished(int)"), self.reject) QObject.connect(widget, SIGNAL("resizeDialog(int,int)"), self.resize) if closeButton: QObject.connect(self.windowTitle.pushButton, SIGNAL("clicked()"), self.reject) if keySequence: shortCut = QShortcut(keySequence, self) QObject.connect(shortCut, SIGNAL("activated()"), self.reject) QMetaObject.connectSlotsByName(self) self.resize(10,10) def setTitle(self, title): self.windowTitle.label.setText(title) def addWidget(self, widget): self.content = widget self.wlayout.addWidget(self.content) if self.isDialog: widget.setStyleSheet("QMessageBox { background:none }") self.layout.addItem(QSpacerItem(10, 10, QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)) self.layout.setContentsMargins(0, 0, 0, 8) self.layout.addLayout(self.wlayout) def setCentered(self): self.move(ctx.mainScreen.width()/2 - self.width()/2, ctx.mainScreen.height()/2 - self.height()/2) def exec_(self): QTimer.singleShot(0, self.setCentered) return QDialog.exec_(self)
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)
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 __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)
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)
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)
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)
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_()
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_()
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
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("<", "<").replace(">", ">"). \ 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()
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)
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
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)
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.""")
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->About</b>" " for instructions...", 200, 3)
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 __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)
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
def __init__(self, parent, hide_on_close=False): QMainWindow.__init__(self, parent) self.setWindowIcon(pixmaps.tigger_starface.icon()) self._currier = PersistentCurrier() self.hide() # init column constants for icol, col in enumerate(self.ViewModelColumns): setattr(self, "Column%s" % col.capitalize(), icol) # init GUI self.setWindowTitle("Tigger") # self.setIcon(pixmaps.purr_logo.pm()) cw = QWidget(self) self.setCentralWidget(cw) cwlo = QVBoxLayout(cw) cwlo.setMargin(5) # make splitter spl1 = self._splitter1 = QSplitter(Qt.Vertical, cw) spl1.setOpaqueResize(False) cwlo.addWidget(spl1) # Create listview of LSM entries self.tw = SkyModelTreeWidget(spl1) self.tw.hide() # split bottom pane spl2 = self._splitter2 = QSplitter(Qt.Horizontal, spl1) spl2.setOpaqueResize(False) self._skyplot_stack = QWidget(spl2) self._skyplot_stack_lo = QVBoxLayout(self._skyplot_stack) self._skyplot_stack_lo.setContentsMargins(0, 0, 0, 0) # add plot self.skyplot = SkyModelPlotter(self._skyplot_stack, self) self.skyplot.resize(128, 128) self.skyplot.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self._skyplot_stack_lo.addWidget(self.skyplot, 1000) self.skyplot.hide() QObject.connect(self.skyplot, SIGNAL("imagesChanged"), self._imagesChanged) QObject.connect(self.skyplot, SIGNAL("showMessage"), self.showMessage) QObject.connect(self.skyplot, SIGNAL("showErrorMessage"), self.showErrorMessage) self._grouptab_stack = QWidget(spl2) self._grouptab_stack_lo = lo = QVBoxLayout(self._grouptab_stack) self._grouptab_stack_lo.setContentsMargins(0, 0, 0, 0) # add groupings table self.grouptab = ModelGroupsTable(self._grouptab_stack) self.grouptab.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) QObject.connect(self, SIGNAL("hasSkyModel"), self.grouptab.setEnabled) lo.addWidget(self.grouptab, 1000) lo.addStretch(1) self.grouptab.hide() # add image controls -- parentless for now (setLayout will reparent them anyway) self.imgman = ImageManager() self.skyplot.setImageManager(self.imgman) QObject.connect(self.imgman, SIGNAL("imagesChanged"), self._imagesChanged) QObject.connect(self.imgman, SIGNAL("showMessage"), self.showMessage) QObject.connect(self.imgman, SIGNAL("showErrorMessage"), self.showErrorMessage) # enable status line self.statusBar().show() # Create and populate main menu menubar = self.menuBar() # File menu file_menu = menubar.addMenu("&File") qa_open = file_menu.addAction("&Open model...", self._openFileCallback, Qt.CTRL + Qt.Key_O) qa_merge = file_menu.addAction("&Merge in model...", self._mergeFileCallback, Qt.CTRL + Qt.SHIFT + Qt.Key_O) QObject.connect(self, SIGNAL("hasSkyModel"), qa_merge.setEnabled) file_menu.addSeparator() qa_save = file_menu.addAction("&Save model", self.saveFile, Qt.CTRL + Qt.Key_S) QObject.connect(self, SIGNAL("isUpdated"), qa_save.setEnabled) qa_save_as = file_menu.addAction("Save model &as...", self.saveFileAs) QObject.connect(self, SIGNAL("hasSkyModel"), qa_save_as.setEnabled) qa_save_selection_as = file_menu.addAction("Save selection as...", self.saveSelectionAs) QObject.connect(self, SIGNAL("hasSelection"), qa_save_selection_as.setEnabled) file_menu.addSeparator() qa_close = file_menu.addAction("&Close model", self.closeFile, Qt.CTRL + Qt.Key_W) QObject.connect(self, SIGNAL("hasSkyModel"), qa_close.setEnabled) qa_quit = file_menu.addAction("Quit", self.close, Qt.CTRL + Qt.Key_Q) # Image menu menubar.addMenu(self.imgman.getMenu()) # Plot menu menubar.addMenu(self.skyplot.getMenu()) # LSM Menu em = QMenu("&LSM", self) self._qa_em = menubar.addMenu(em) self._qa_em.setVisible(False) QObject.connect(self, SIGNAL("hasSkyModel"), self._qa_em.setVisible) self._column_view_menu = QMenu("&Show columns", self) self._qa_cv_menu = em.addMenu(self._column_view_menu) em.addSeparator() em.addAction("Select &all", self._selectAll, Qt.CTRL + Qt.Key_A) em.addAction("&Invert selection", self._selectInvert, Qt.CTRL + Qt.Key_I) em.addAction("Select b&y attribute...", self._showSourceSelector, Qt.CTRL + Qt.Key_Y) em.addSeparator() qa_add_tag = em.addAction("&Tag selection...", self.addTagToSelection, Qt.CTRL + Qt.Key_T) QObject.connect(self, SIGNAL("hasSelection"), qa_add_tag.setEnabled) qa_del_tag = em.addAction("&Untag selection...", self.removeTagsFromSelection, Qt.CTRL + Qt.Key_U) QObject.connect(self, SIGNAL("hasSelection"), qa_del_tag.setEnabled) qa_del_sel = em.addAction("&Delete selection", self._deleteSelection) QObject.connect(self, SIGNAL("hasSelection"), qa_del_sel.setEnabled) # Tools menu tm = self._tools_menu = QMenu("&Tools", self) self._qa_tm = menubar.addMenu(tm) self._qa_tm.setVisible(False) QObject.connect(self, SIGNAL("hasSkyModel"), self._qa_tm.setVisible) # Help menu menubar.addSeparator() hm = self._help_menu = menubar.addMenu("&Help") hm.addAction("&About...", self._showAboutDialog) self._about_dialog = None # message handlers self.qerrmsg = QErrorMessage(self) # set initial state self.setAcceptDrops(True) self.model = None self.filename = None self._display_filename = None self._open_file_dialog = self._merge_file_dialog = self._save_as_dialog = self._save_sel_as_dialog = self._open_image_dialog = None self.emit(SIGNAL("isUpdated"), False) self.emit(SIGNAL("hasSkyModel"), False) self.emit(SIGNAL("hasSelection"), False) self._exiting = False # set initial layout self._current_layout = None self.setLayout(self.LayoutEmpty) dprint(1, "init complete")
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 = {}
class Dialog(QDialog): def __init__(self, title, widget=None, closeButton=True, keySequence=None, isDialog=False, icon=None): QDialog.__init__(self, ctx.mainScreen) self.setObjectName("dialog") self.isDialog = isDialog self.layout = QVBoxLayout() self.setLayout(self.layout) self.wlayout = QHBoxLayout() if icon: self.setStyleSheet( """QDialog QLabel{ margin-left:16px;margin-right:10px} QDialog#dialog {background-image:url(':/images/%s.png'); background-repeat:no-repeat; background-position: top left; padding-left:500px;} """ % icon) self.windowTitle = windowTitle(self, closeButton) self.setTitle(title) self.layout.setMargin(0) self.layout.addWidget(self.windowTitle) if widget: self.addWidget(widget) QObject.connect(widget, SIGNAL("finished(int)"), self.reject) QObject.connect(widget, SIGNAL("resizeDialog(int,int)"), self.resize) if closeButton: QObject.connect(self.windowTitle.pushButton, SIGNAL("clicked()"), self.reject) if keySequence: shortCut = QShortcut(keySequence, self) QObject.connect(shortCut, SIGNAL("activated()"), self.reject) QMetaObject.connectSlotsByName(self) self.resize(10, 10) def setTitle(self, title): self.windowTitle.label.setText(title) def addWidget(self, widget): self.content = widget self.wlayout.addWidget(self.content) if self.isDialog: widget.setStyleSheet("QMessageBox { background:none }") self.layout.addItem( QSpacerItem(10, 10, QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)) self.layout.setContentsMargins(0, 0, 0, 8) self.layout.addLayout(self.wlayout) def setCentered(self): self.move(ctx.mainScreen.width() / 2 - self.width() / 2, ctx.mainScreen.height() / 2 - self.height() / 2) def exec_(self): QTimer.singleShot(0, self.setCentered) return QDialog.exec_(self)
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)
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
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()
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
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)
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
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
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->About</b>" " for instructions...", 200, 3)
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)
class GAppLayerW(QgsMapCanvas): def __init__(self, layer, rawLayer, linkedCanvas, dock): # Canvas params QgsMapCanvas.__init__(self, dock) self.setCanvasColor(Qt.white) self.setWheelAction(QgsMapCanvas.WheelZoom, factor=0) self._dock = dock # Add buttons buttonWidth = self.width() / 2.5 self._buttonUp = QToolButton(self) self._buttonUp.setArrowType(Qt.UpArrow) self._buttonUp.setMaximumWidth(buttonWidth) self._buttonUp.setVisible(False) self._buttonUp.connect(self._buttonUp, SIGNAL("clicked()"), self.moveUp) self._buttonHide = QPushButton("Hide") self._buttonHide.setMaximumWidth(buttonWidth) self._buttonHide.setVisible(False) self._buttonHide.setFocusPolicy(Qt.NoFocus) self._buttonHide.connect(self._buttonHide, SIGNAL("clicked()"), self.toggleLayer) self._buttonDown = QToolButton(self) self._buttonDown.setArrowType(Qt.DownArrow) self._buttonDown.setMaximumWidth(buttonWidth) self._buttonDown.setVisible(False) self._buttonDown.connect(self._buttonDown, SIGNAL("clicked()"), self.moveDown) self._layout = QVBoxLayout(self) self._layout.setMargin(1) self._layout.addWidget(self._buttonUp) self._layout.addWidget(self._buttonHide) self._layout.addWidget(self._buttonDown) self._rawLayer = rawLayer self._layer = layer self.setLayerSet([layer]) self._linkedCanvas = linkedCanvas self._extent = self._linkedCanvas.extent() self.setExtent(self._extent) self.setFocusPolicy(Qt.StrongFocus) self._hidden = False def toggleLayer(self): if not self._hidden: self._linkedCanvas.hideLayer(self._layer) self._buttonHide.setText("Show") self._hidden = True else: self._linkedCanvas.showLayer(self._layer) self._buttonHide.setText("Hide") self._hidden = False def moveUp(self): if not self._hidden: self._linkedCanvas.moveLayerUp(self._layer) self._dock.moveWidgetUp(self) def moveDown(self): if not self._hidden: self._linkedCanvas.moveLayerDown(self._layer) self._dock.moveWidgetDown(self) def refreshExtent(self): self._extent = self._linkedCanvas.extent() self.zoomToFeatureExtent(self._extent) def isCurrentLayer(self): return self._linkedCanvas.currentLayer == self._rawLayer def setAsCurrentLayer(self): self._linkedCanvas.setCurrentLayer(self._rawLayer) def focusInEvent(self, focusEvent): self._buttonUp.setVisible(True) self._buttonHide.setVisible(True) self._buttonDown.setVisible(True) # if not self.isCurrentLayer(): # self.setAsCurrentLayer() QgsMapCanvas.focusInEvent(self, focusEvent) def focusOutEvent(self, focusEvent): if not self._buttonHide.hasFocus(): self._buttonUp.setVisible(False) self._buttonHide.setVisible(False) self._buttonDown.setVisible(False) QgsMapCanvas.focusOutEvent(self, focusEvent) def keyPressEvent(self, keyEvent): if keyEvent.key() == Qt.Key_Down: self.moveDown() elif keyEvent.key() == Qt.Key_Up: self.moveUp() elif keyEvent.key() == Qt.Key_H: self.toggleLayer() def keyPressed(self, keyEvent): pass def keyReleased(self, QKeyEvent): pass def keyReleaseEvent(self, QKeyEvent): pass def _getCanvas(self): return self._canvas canvas = property(_getCanvas)
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()
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("<", "<").replace(">", ">"). \ 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()