#!/usr/bin/env python #**************************************************************************** # treedialogs.py, provides many dialog interfaces # # TreeLine, an information storage program # Copyright (C) 2005, Douglas W. Bell # # This is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License, Version 2. This program is # distributed in the hope that it will be useful, but WITTHOUT ANY WARRANTY. #***************************************************************************** import sys, os.path, copy, re from treedoc import TreeDoc from nodeformat import NodeFormat from fieldformat import TextFormat, NumberFormat, ChoiceFormat, \ CombinationFormat, AutoChoiceFormat, DateFormat, \ TimeFormat, BooleanFormat, URLFormat, PathFormat, \ EmailFormat, InternalLinkFormat, ExecuteLinkFormat, \ PictureFormat from treeformats import TreeFormats from helpview import HelpView from icondict import IconDict from optiondefaults import OptionDefaults from conditional import Conditional import globalref from qt import Qt, PYSIGNAL, SIGNAL, SLOT, qVersion, QApplication, QCheckBox, \ QComboBox, QDialog, QFrame, QGridLayout, QGroupBox, \ QHBox, QHBoxLayout, QHGroupBox, QIconView, QIconViewItem, \ QInputDialog, QLabel, QLineEdit, QListBox, QListView, \ QListViewItem, QMessageBox, QPoint, QPopupMenu, \ QPushButton, QRadioButton, QSpinBox, QStringList, QVBox, \ QVBoxLayout, QVButtonGroup, QVGroupBox if qVersion()[0] >= '3': from textedit3 import FormatEdit, SpellContextEdit else: from textedit2 import FormatEdit, SpellContextEdit stdFlags = Qt.WStyle_Customize | Qt.WStyle_NormalBorder | Qt.WStyle_Title | \ Qt.WStyle_SysMenu class ConfigDlg(QDialog): """Dialog for configuring data types""" noChildTypeName = _('[None]', 'no default child type') def __init__(self, initType, parent=None, name=None, modal=False, \ flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.treeFormats = copy.deepcopy(globalref.docRef.treeFormats) self.treeFormats.updateDerivedTypes() self.fileInfoFormat = self.treeFormats.findFormat(globalref.docRef.\ fileInfoItem.\ nodeFormat.name) if not self.fileInfoFormat: self.fileInfoFormat = copy.deepcopy(globalref.docRef.fileInfoItem.\ nodeFormat) self.currentType = None self.fieldRenameDict = {} self.typeRenameDict = {} self.parentLevel = 0 self.otherType = None self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Configure Data Types') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QGridLayout(self, 6, 3, 7) topLayout.addColSpacing(0, 250) topLayout.addColSpacing(2, 250) selectBox = QHGroupBox(_('&Data Type'), self) topLayout.addWidget(selectBox, 0, 0) self.selectCombo = QComboBox(False, selectBox) modSelect = QPushButton(_('&Modify List'), selectBox) modSelect.setMaximumWidth(modSelect.sizeHint().width()) self.connect(modSelect, SIGNAL('clicked()'), self.modTypeList) self.connect(self.selectCombo, SIGNAL('activated(const QString&)'), \ self.changeType) iconBox = QHGroupBox(_('Data Type Icon'), self) topLayout.addWidget(iconBox, 0, 2) self.iconLabel = QLabel(iconBox) self.iconLabel.setAlignment(Qt.AlignCenter) iconButton = QPushButton(_('Change &Icon'), iconBox) self.connect(iconButton, SIGNAL('clicked()'), self.modifyIcon) fieldBox = QGroupBox(_('Fields'), self) topLayout.addMultiCellWidget(fieldBox, 1, 2, 0, 0) fieldLayout = QGridLayout(fieldBox, 8, 2, 10, 2) fieldLayout.addRowSpacing(0, self.fontMetrics().lineSpacing()) fieldLayout.setRowStretch(0, 0) self.fieldListView = QListView(fieldBox) self.fieldListView.addColumn(_('Name')) self.fieldListView.addColumn(_('Type')) self.fieldListView.setSorting(-1) fieldLayout.addMultiCellWidget(self.fieldListView, 1, 7, 1, 1) fieldLayout.setRowStretch(1, 1) fieldLayout.setColStretch(1, 0) self.upFieldButton = QPushButton(_('Move &Up'), fieldBox) fieldLayout.addWidget(self.upFieldButton, 1, 0) self.connect(self.upFieldButton, SIGNAL('clicked()'), self.fieldUp) self.downFieldButton = QPushButton(_('Move Do&wn'), fieldBox) fieldLayout.addWidget(self.downFieldButton, 2, 0) self.connect(self.downFieldButton, SIGNAL('clicked()'), self.fieldDown) self.newFieldButton = QPushButton(_('&New Field...'), fieldBox) fieldLayout.addWidget(self.newFieldButton, 3, 0) self.connect(self.newFieldButton, SIGNAL('clicked()'), self.newField) self.fieldTypeButton = QPushButton(_('Fie&ld Type...'), fieldBox) fieldLayout.addWidget(self.fieldTypeButton, 4, 0) self.connect(self.fieldTypeButton, SIGNAL('clicked()'), self.fieldType) self.renameFieldButton = QPushButton(_('&Rename Field...'), fieldBox) fieldLayout.addWidget(self.renameFieldButton, 5, 0) self.connect(self.renameFieldButton, SIGNAL('clicked()'), \ self.renameField) self.delFieldButton = QPushButton(_('D&elete Field'), fieldBox) fieldLayout.addWidget(self.delFieldButton, 6, 0) self.connect(self.delFieldButton, SIGNAL('clicked()'), self.delField) otherfieldButton = QPushButton(_('&Other Fields...'), fieldBox) fieldLayout.addWidget(otherfieldButton, 7, 0) self.connect(otherfieldButton, SIGNAL('clicked()'), self.otherfields) xferTitleLayout = QVBoxLayout(5) topLayout.addLayout(xferTitleLayout, 1, 1) self.toTitleButton = QPushButton('>>', self) self.toTitleButton.setMaximumWidth(self.toTitleButton.height()) xferTitleLayout.addWidget(self.toTitleButton) self.connect(self.toTitleButton, SIGNAL('clicked()'), \ self.fieldToTitle) self.delTitleButton = QPushButton('<<', self) self.delTitleButton.setMaximumWidth(self.delTitleButton.height()) xferTitleLayout.addWidget(self.delTitleButton) self.connect(self.delTitleButton, SIGNAL('clicked()'), \ self.delTitleField) titleBox = QGroupBox(_('&Title Format'), self) topLayout.addWidget(titleBox, 1, 2) titleLayout = QVBoxLayout(titleBox, 10, 5) titleLayout.addSpacing(self.fontMetrics().lineSpacing()) self.titleEdit = TitleEdit(titleBox) titleLayout.addWidget(self.titleEdit) self.connect(self.titleEdit, SIGNAL('textChanged(const QString&)'), \ self.changeTitle) self.connect(self.titleEdit, PYSIGNAL('cursorMove'), \ self.setButtonAvail) xferOutputLayout = QVBoxLayout(5) topLayout.addLayout(xferOutputLayout, 2, 1) self.toOutputButton = QPushButton('>>', self) self.toOutputButton.setMaximumWidth(self.toOutputButton.height()) xferOutputLayout.addWidget(self.toOutputButton) self.connect(self.toOutputButton, SIGNAL('clicked()'), \ self.fieldToOutput) self.delOutputButton = QPushButton('<<', self) self.delOutputButton.setMaximumWidth(self.delOutputButton.height()) xferOutputLayout.addWidget(self.delOutputButton) self.connect(self.delOutputButton, SIGNAL('clicked()'), \ self.delOutputField) outputBox = QGroupBox(_('Output &Format'), self) topLayout.addWidget(outputBox, 2, 2) outputLayout = QVBoxLayout(outputBox, 10, 5) outputLayout.addSpacing(self.fontMetrics().lineSpacing()) self.outputEdit = FormatEdit(outputBox) outputLayout.addWidget(self.outputEdit) self.connect(self.outputEdit, SIGNAL('textChanged()'), \ self.changeOutput) self.connect(self.outputEdit, PYSIGNAL('cursorMove'), \ self.setButtonAvail) childBox = QHGroupBox(_('Default C&hild Type'), self) topLayout.addMultiCellWidget(childBox, 3, 4, 0, 0) self.childCombo = QComboBox(False, childBox) self.loadTypeNames(initType) self.connect(self.childCombo, SIGNAL('activated(const QString&)'), \ self.changeChildType) self.loadFields() self.connect(self.fieldListView, SIGNAL('selectionChanged()'), \ self.setButtonAvail) self.loadText() self.setButtonAvail() ctrlLayout = QHBoxLayout(5) topLayout.addMultiCellLayout(ctrlLayout, 4, 4, 1, 2) ctrlLayout.insertStretch(0) advButton = QPushButton(_('&Advanced...'), self) ctrlLayout.addWidget(advButton) self.connect(advButton, SIGNAL('clicked()'), self.showAdvDlg) okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) self.selectCombo.setFocus() def loadTypeNames(self, initName=''): """Load combo box with data type names""" names = self.treeFormats.nameList(True) names.sort() self.selectCombo.clear() self.childCombo.clear() self.childCombo.insertItem(ConfigDlg.noChildTypeName) for name in names: self.selectCombo.insertItem(name) self.childCombo.insertItem(name) if initName: self.selectCombo.setCurrentItem(names.index(initName)) self.currentType = self.treeFormats.\ findFormat(unicode(self.selectCombo.currentText())) self.updateChildType() self.updateIconDisplay() def updateChildType(self): """Set current type name in child type combo""" typeNames = self.treeFormats.nameList(True) typeNames.sort() try: childItem = typeNames.index(self.currentType.childType) + 1 except ValueError: childItem = 0 self.childCombo.setCurrentItem(childItem) def updateIconDisplay(self): """Update display of current icon""" icon = globalref.treeIcons.getIcon(self.currentType.iconName) if icon: self.iconLabel.setPixmap(icon) else: self.iconLabel.setText(_('None', 'no icon set')) def changeType(self, text): """Change type name based on combo box signal""" self.currentType = self.treeFormats.findFormat(unicode(text)) self.treeFormats.updateDerivedTypes() self.loadFields() self.loadText() self.updateChildType() self.updateIconDisplay() self.setButtonAvail() def changeChildType(self, text): """Change default child type based on combo box signal""" typeName = unicode(text) self.currentType.childType = typeName != ConfigDlg.noChildTypeName \ and typeName or '' def setButtonAvail(self): """Update button availability""" fieldList = self.currentType.fieldList num = self.selectedFieldNum() titlePos = self.titleFieldPos() outputPos = self.outputFieldPos() allowFieldChg = not self.otherType and not self.currentType.genericType self.upFieldButton.setEnabled(allowFieldChg and num > 0) self.downFieldButton.setEnabled(allowFieldChg and \ 0 <= num < len(fieldList) - 1) self.newFieldButton.setEnabled(allowFieldChg) self.fieldTypeButton.setEnabled(allowFieldChg or \ self.parentLevel == 0) self.renameFieldButton.setEnabled(allowFieldChg) self.delFieldButton.setEnabled(allowFieldChg and \ num >= 0 and len(fieldList) > 1) self.toTitleButton.setEnabled(num >= 0 and not titlePos) self.delTitleButton.setEnabled(titlePos and True or False) self.toOutputButton.setEnabled(num >= 0 and not outputPos) self.delOutputButton.setEnabled(outputPos and True or False) def loadFields(self, selNum=0): """Load list with field names""" self.fieldListView.clear() items = [] if self.otherType: fields = [field for field in self.otherType.fieldList if \ field.showInDialog] else: fields = self.currentType.fieldList[:] fields.reverse() for field in fields: items.insert(0, QListViewItem(self.fieldListView, field.name, \ _(field.typeName))) self.fieldListView.setSelected(items[selNum], True) def selectedFieldName(self): """Return currently selected field name""" item = self.fieldListView.selectedItem() if not item: # select last item for unselecting click item = self.fieldListView.lastItem() self.fieldListView.setSelected(item, True) return unicode(item.text(0)) def selectedFieldNum(self): """Return currently selected field number""" if self.otherType: fields = [field.name for field in self.otherType.fieldList if \ field.showInDialog] else: fields = self.currentType.fieldNames() return fields.index(self.selectedFieldName()) def fieldUp(self): """Move field upward in list""" fieldList = self.currentType.fieldList num = self.selectedFieldNum() if num > 0: fieldList[num-1], fieldList[num] = fieldList[num], fieldList[num-1] self.loadFields(num-1) def fieldDown(self): """Move field downward in list""" fieldList = self.currentType.fieldList num = self.selectedFieldNum() if 0 <= num < len(fieldList) - 1: fieldList[num], fieldList[num+1] = fieldList[num+1], fieldList[num] self.loadFields(num+1) def newField(self): """Add a new field to the list""" fieldNames = self.currentType.fieldNames() dlg = FieldEntry(_('Add Field'), _('Enter new field name:'), '', \ fieldNames, self, None, True) if dlg.exec_loop() == QDialog.Accepted: htmlAttrs = globalref.options.boolData('HtmlNewFields') and \ {'html': 'y'} or {} self.currentType.addNewField(dlg.text, htmlAttrs) self.loadFields(len(fieldNames)) def fieldType(self): """Display the dialog for changing the field type settings""" fieldNum = self.selectedFieldNum() if not self.otherType: # normal field field = self.currentType.fieldList[fieldNum] else: # file info field fields = [field for field in self.otherType.fieldList if \ field.showInDialog] field = fields[fieldNum] dlg = FieldFormatDialog(field, self.currentType, not self.otherType, \ self, None, True) if dlg.exec_loop() == QDialog.Accepted: # field's class is changed to avoid having to change the ref's field.changeType(dlg.field.typeName) field.duplicateSettings(dlg.field) field.initFormat() if self.otherType: # file info field if not self.treeFormats.findFormat(globalref.docRef.\ fileInfoItem.nodeFormat.name): self.treeFormats.append(self.fileInfoFormat) self.loadFields(fieldNum) def renameField(self): """Change the field name and save for item changes""" fieldNames = self.currentType.fieldNames() num = self.selectedFieldNum() if num >= 0: dlg = FieldEntry(_('Rename Field'), _('Rename from "%s" to:') \ % fieldNames[num], fieldNames[num], fieldNames, \ self, None, True) if dlg.exec_loop() == QDialog.Accepted: self.currentType.fieldList[num].name = dlg.text derivedTypes = self.treeFormats.derivedDict.\ get(self.currentType.name, []) for derived in derivedTypes: field = derived.findField(fieldNames[num]) if field: field.name = dlg.text if self.currentType.name in self.fieldRenameDict: self.fieldRenameDict[self.currentType.name].\ append((fieldNames[num], dlg.text)) else: self.fieldRenameDict[self.currentType.name] = \ [(fieldNames[num], dlg.text)] self.loadText() self.loadFields(num) def delField(self): """Remove a field from the list""" fieldList = self.currentType.fieldList num = self.selectedFieldNum() if num >= 0: if self.currentType.removeField(fieldList[num]): self.loadText() for format in self.treeFormats.derivedDict.\ get(self.currentType.name, []): field = format.findField(fieldList[num].name) if field: format.removeField(field) if self.currentType.refField == fieldList[num]: del fieldList[num] self.currentType.refField = self.currentType.fieldList[0] else: del fieldList[num] if num: num -= 1 self.loadFields(num) def otherfields(self): """Display dialog to allow setting of ancestor & file data field choices""" dlg = OtherFieldDlg(self.parentLevel, self.otherType, \ self.treeFormats, self.fileInfoFormat, self, \ None, True) if dlg.exec_loop() == QDialog.Accepted: self.parentLevel = dlg.parentLevel self.otherType = dlg.otherType else: self.parentLevel = 0 self.otherType = None self.loadFields() self.setButtonAvail() def loadText(self): """Load text into title and output format editors and sibling prefix/postfix""" lines = self.currentType.getLines() self.titleEdit.blockSignals(True) self.titleEdit.setText(lines[0]) self.titleEdit.blockSignals(False) self.outputEdit.blockSignals(True) self.outputEdit.setText(u'\n'.join(lines[1:])) if lines[1:]: self.outputEdit.setCursorPosition(len(lines) - 2, len(lines[-1])) self.outputEdit.blockSignals(False) def changeTitle(self): """Update title format lines based on editor change""" self.currentType.changeTitleLine(unicode(self.titleEdit.text())) self.setButtonAvail() def changeOutput(self): """Update output format lines based on editor change""" self.currentType.lineList = self.currentType.lineList[:1] if not self.currentType.lineList: self.currentType.lineList = [''] for lineNum in range(self.outputEdit.numLines()): self.currentType.addLine(unicode(self.outputEdit.textLine(lineNum))) self.setButtonAvail() def fieldSepName(self): """Return current field name with proper separators""" name = self.selectedFieldName() if self.parentLevel < 0: return u'{*&%s*}' % name if self.parentLevel == 1000: return u'{*?%s*}' % name if not self.parentLevel and self.otherType: return u'{*!%s*}' % name return u'{*%s%s*}' % (self.parentLevel * '*', name) def fieldToTitle(self): """Add selected field to cursor pos in editor""" self.titleEdit.deselect() self.titleEdit.insert(self.fieldSepName()) self.titleEdit.setFocus() def delTitleField(self): """Remove field from cursor pos in editor""" start, end = self.titleFieldPos() line = self.currentType.getLines()[0] line = line[:start] + line[end:] self.titleEdit.setText(line) self.titleEdit.setCursorPosition(start) def fieldToOutput(self): """Add selected field to cursor pos in editor""" name = self.selectedFieldName() self.outputEdit.deselect() self.outputEdit.insert(self.fieldSepName()) self.changeOutput() self.outputEdit.setFocus() def delOutputField(self): """Remove field from cursor pos in editor""" num, start, end = self.outputFieldPos() lines = self.currentType.getLines()[1:] lines[num] = lines[num][:start] + lines[num][end:] self.outputEdit.setText(u'\n'.join(lines)) self.outputEdit.setCursorPosition(num, start) def titleFieldPos(self): """Return tuple of start, end for title field at cursor or None""" line = self.currentType.getLines()[0] cursorPos = self.titleEdit.cursorPosition() return self.currentFieldPos(cursorPos, line) def outputFieldPos(self): """Return tuple of line num start, end for output field at cursor or None""" lines = self.currentType.getLines()[1:] cursorPos = self.outputEdit.getCursorPosition() if len(lines) > cursorPos[0]: pos = self.currentFieldPos(cursorPos[1], lines[cursorPos[0]]) if pos: return (cursorPos[0], pos[0], pos[1]) return None def currentFieldPos(self, cursorPos, textLine): """Return tuple of start, end for field at cursorPos or None""" pattern = re.compile('{\*(.*?)\*}') match = pattern.search(textLine) while match: if match.start() < cursorPos < match.end(): return (match.start(), match.end()) match = pattern.search(textLine, match.end()) return None def modTypeList(self): """Show dialog to add/remove from data type list""" dlg = ModTypeListDlg(self.treeFormats, self.typeRenameDict, self, None, True) if dlg.exec_loop() == QDialog.Accepted: self.treeFormats = dlg.treeFormats self.typeRenameDict = dlg.typeRenameDict for format in self.treeFormats: format.childType = self.typeRenameDict.get(format.childType, \ format.childType) if not self.treeFormats.findFormat(format.childType): format.childType = '' format.genericType = self.typeRenameDict.\ get(format.genericType, \ format.genericType) if not self.treeFormats.findFormat(format.genericType): format.genericType = '' self.loadTypeNames(unicode(dlg.listBox.currentText())) self.updateChildType() self.loadFields() self.loadText() self.setButtonAvail() def modifyIcon(self): """Show dialog to select icon for the data type""" dlg = IconSelectDlg(self.currentType, self, None, True) if dlg.exec_loop() == QDialog.Accepted: self.currentType.iconName = dlg.currentName self.updateIconDisplay() def showAdvDlg(self): """Show the dialog for advanced settings""" dlg = AdvancedConfigDlg(self.currentType, self.treeFormats, self, \ None, True) dlg.exec_loop() self.loadFields() self.loadText() class AdvancedConfigDlg(QDialog): """Dialog for advanced configuration of data types""" def __init__(self, currentType, treeFormats, parent=None, name=None, \ modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.currentType = currentType self.treeFormats = treeFormats self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Advanced Configuration') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QVBoxLayout(self, 5) typeLabel = QLabel(_('For Data Type: %s') % self.currentType.name, \ self) topLayout.addWidget(typeLabel) refFieldBox = QHGroupBox(_('&Link Reference Field'), self) topLayout.addWidget(refFieldBox) self.refFieldCombo = QComboBox(False, refFieldBox) for field in self.currentType.fieldList: self.refFieldCombo.insertItem(field.name) self.refFieldCombo.setCurrentItem(self.currentType.fieldList.\ index(self.currentType.refField)) siblingBox = QVGroupBox(_('Sibling Text'), self) topLayout.addWidget(siblingBox) prefixLabel = QLabel(_('&Prefix Tags'), siblingBox) self.prefixEdit = QLineEdit(self.currentType.sibPrefix, siblingBox) prefixLabel.setBuddy(self.prefixEdit) suffixLabel = QLabel(_('&Suffix Tags'), siblingBox) self.suffixEdit = QLineEdit(self.currentType.sibSuffix, siblingBox) suffixLabel.setBuddy(self.suffixEdit) genericBox = QHGroupBox(_('&Derived from Generic Type'), self) topLayout.addWidget(genericBox) self.genericCombo = QComboBox(False, genericBox) availGenerics = [format for format in self.treeFormats if not \ format.genericType] try: availGenerics.remove(globalref.docRef.fileInfoItem.nodeFormat) except ValueError: pass try: availGenerics.remove(self.currentType) except ValueError: pass names = [format.name for format in availGenerics] names.sort() self.genericCombo.insertItem(ConfigDlg.noChildTypeName) for name in names: self.genericCombo.insertItem(name) if self.currentType.genericType: self.genericCombo.setCurrentItem(names.index(self.currentType.\ genericType) + 1) else: self.genericCombo.setCurrentItem(0) genericBox.setEnabled(self.currentType.name not in \ self.treeFormats.derivedDict) self.connect(self.genericCombo, SIGNAL('activated(const QString&)'), \ self.setConditionAvail) self.conditionBox = QHGroupBox(_('Automatic Types'), self) topLayout.addWidget(self.conditionBox) self.conditionButton = QPushButton('', self.conditionBox) self.connect(self.conditionButton, SIGNAL('clicked()'), \ self.setCondition) ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) ctrlLayout.insertSpacing(0, 60) okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) self.setConditionAvail() self.refFieldCombo.setFocus() def setConditionAvail(self): """Set conditional type available if geenric or derived type""" if self.genericCombo.currentItem() > 0 or \ self.currentType.name in self.treeFormats.derivedDict: self.conditionBox.setEnabled(True) if self.currentType.conditional: self.conditionButton.setText(_('Modify Conditional &Types')) return else: self.conditionBox.setEnabled(False) self.conditionButton.setText(_('Create Conditional &Types')) def setCondition(self): """Show dialog for conditional types""" dlg = ConditionDlg(_('Set Types Conditionally'), self.currentType, \ self, None, True) dlg.setConditions(self.currentType.conditional) if dlg.exec_loop() == QDialog.Accepted: self.currentType.conditional = dlg.conditional() self.currentType.conditional.setupFields(self.currentType) self.setConditionAvail() def accept(self): """Change type as req'd before closing""" self.currentType.refField = self.currentType.\ fieldList[self.refFieldCombo.currentItem()] self.currentType.sibPrefix = unicode(self.prefixEdit.text()) self.currentType.sibSuffix = unicode(self.suffixEdit.text()) generic = unicode(self.genericCombo.currentText()) if generic != ConfigDlg.noChildTypeName: self.currentType.genericType = generic self.currentType.updateFromGeneric(self.treeFormats) else: self.currentType.genericType = '' return QDialog.accept(self) class TitleEdit(QLineEdit): """Editor signals cursor movement and focus-in events""" def __init__(self, parent=None, name=None): QLineEdit.__init__(self, parent, name) def event(self, event): """Signal cursor movement if text didn't also change""" pos = self.cursorPosition() self.setEdited(False) result = QLineEdit.event(self, event) if not self.edited() and pos != self.cursorPosition(): self.emit(PYSIGNAL('cursorMove'), ()) return result def focusInEvent(self, event): """Signal focus in events""" self.emit(PYSIGNAL('focusIn'), (self,)) QLineEdit.focusInEvent(self, event) class FieldEntry(QDialog): """Dialog for alpha-numeric and underscore text entry""" def __init__(self, caption, label, dfltText='', badStr=[], parent=None, \ name=None, modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.text = '' self.badStr = badStr if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) self.setIcon(globalref.treeIcons.getIcon('treeline')) self.topLayout = QVBoxLayout(self, 5) label = QLabel(label, self) self.topLayout.addWidget(label) self.entry = QLineEdit(dfltText, self) self.topLayout.addWidget(self.entry) self.entry.setFocus() self.connect(self.entry, SIGNAL('returnPressed()'), \ self, SLOT('accept()')) ctrlLayout = QHBoxLayout(self.topLayout) ctrlLayout.insertStretch(0) okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) def accept(self): """Check for acceptable string before closing""" self.text = unicode(self.entry.text()).strip() illegalRe = re.compile(r'[^\w_\-.]', re.U) error = '' if not self.text: error = _('Empty name is not acceptable') elif not self.text[0].isalpha(): error = _('Name must start with a letter') elif self.text[:3].lower() == 'xml': error = _('Name cannot start with "xml"') elif illegalRe.search(self.text): error = _('The following characters are not allowed: "%s"') % \ illegalRe.search(self.text).group() elif self.text in self.badStr: error = _('Entered name was already used') if error: QMessageBox.warning(self, 'TreeLine', error) return return QDialog.accept(self) class FieldCopyEntry(FieldEntry): """Dialog for alpha-numeric and underscore text entry with derive option""" def __init__(self, dfltText='', badStr=[], allowDerive=True, parent=None, \ name=None, modal=False, flags=stdFlags): FieldEntry.__init__(self, _('Copy Type'), _('Enter new type name:'), \ dfltText, badStr, parent, name, modal, flags) self.derived = False self.deriveCheck = QCheckBox(_('Derive from original'), self) self.topLayout.insertWidget(2, self.deriveCheck) self.deriveCheck.setEnabled(allowDerive) def accept(self): """Check for derived and acceptable string before closing""" self.derived = self.deriveCheck.isChecked() FieldEntry.accept(self) class IconSelectDlg(QDialog): """Dialog for selecting icons for a format type""" iconList = [N_('default', 'icon name'), N_('treeline', 'icon name'), \ N_('anchor', 'icon name'), N_('arrow_1', 'icon name'), \ N_('arrow_2', 'icon name'), N_('arrow_3', 'icon name'), \ N_('arrow_4', 'icon name'), N_('arrow_5', 'icon name'), \ N_('bell', 'icon name'), N_('book_1', 'icon name'), \ N_('book_2', 'icon name'), N_('book_3', 'icon name'), \ N_('bookmark', 'icon name'), N_('bulb', 'icon name'), \ N_('bullet_1', 'icon name'), N_('bullet_2', 'icon name'), \ N_('bullet_3', 'icon name'), N_('check_1', 'icon name'), \ N_('check_2', 'icon name'), N_('check_3', 'icon name'), \ N_('clock', 'icon name'), N_('colors', 'icon name'), \ N_('date_1', 'icon name'), N_('date_2', 'icon name'), \ N_('disk', 'icon name'), N_('doc', 'icon name'), \ N_('euro', 'icon name'), N_('folder_1', 'icon name'), \ N_('folder_2', 'icon name'), N_('folder_3', 'icon name'), \ N_('gear', 'icon name'), N_('gnu', 'icon name'), \ N_('hand', 'icon name'), N_('heart', 'icon name'), \ N_('home', 'icon name'), N_('lock_1', 'icon name'), \ N_('lock_2', 'icon name'), N_('mag', 'icon name'), \ N_('mail', 'icon name'), N_('minus', 'icon name'), \ N_('misc', 'icon name'), N_('move', 'icon name'), \ N_('music', 'icon name'), N_('note', 'icon name'), \ N_('pencil', 'icon name'), N_('person', 'icon name'), \ N_('plus', 'icon name'), N_('printer', 'icon name'), \ N_('question', 'icon name'), N_('rocket', 'icon name'), \ N_('smiley_1', 'icon name'), N_('smiley_2', 'icon name'), \ N_('smiley_3', 'icon name'), N_('smiley_4', 'icon name'), \ N_('smiley_5', 'icon name'), N_('sphere', 'icon name'), \ N_('star', 'icon name'), N_('sum', 'icon name'), \ N_('table', 'icon name'), N_('task_1', 'icon name'), \ N_('task_2', 'icon name'), N_('term', 'icon name'), \ N_('text', 'icon name'), N_('trash', 'icon name'), \ N_('tux_1', 'icon name'), N_('tux_2', 'icon name'), \ N_('warning', 'icon name'), N_('wrench', 'icon name'), \ N_('write', 'icon name'), N_('x_1', 'icon name'), \ N_('x_2', 'icon name'), N_('x_3', 'icon name')] iconTransDict = dict([(_(name), name) for name in iconList]) def __init__(self, nodeFormat, parent=None, name=None, modal=False, \ flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.currentName = nodeFormat.iconName if not self.currentName or \ self.currentName not in globalref.treeIcons.keys(): self.currentName = globalref.treeIcons.defaultName self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Set Data Type Icon') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QVBoxLayout(self, 5) self.iconView = QIconView(self) self.iconView.setItemsMovable(False) self.iconView.setResizeMode(QIconView.Adjust) self.iconView.setItemTextPos(QIconView.Right) self.iconView.setArrangement(QIconView.TopToBottom) self.iconView.setGridX(112) self.iconView.setGridY(32) self.iconView.setMinimumHeight(160) topLayout.addWidget(self.iconView) ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) clearButton = QPushButton(_('Clear &Select'), self) ctrlLayout.addWidget(clearButton) self.connect(clearButton, SIGNAL('clicked()'), \ self.iconView.clearSelection) okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) if qVersion()[0] >= '3': # double-click freezes in Qt2 self.connect(self.iconView, \ SIGNAL('doubleClicked(QIconViewItem*)'), \ SLOT('accept()')) self.loadIcons() def loadIcons(self): """Load icons from the icon source""" if not hasattr(globalref.treeIcons, 'dialogLoaded'): globalref.treeIcons.loadIcons(IconSelectDlg.iconList) globalref.treeIcons.dialogLoaded = True for name, icon in globalref.treeIcons.items(): if icon: item = QIconViewItem(self.iconView, _(name.encode('utf-8')), \ icon) if name == self.currentName: item.setSelected(True) self.iconView.sort() # making it visible doesn't work??? # curItem = self.iconView.currentItem() # if curItem: # self.iconView.ensureItemVisible(curItem) def accept(self): item = self.iconView.currentItem() if item and item.isSelected(): name = unicode(item.text()) self.currentName = IconSelectDlg.iconTransDict.get(name, name) if self.currentName == globalref.treeIcons.defaultName: self.currentName = '' else: self.currentName = globalref.treeIcons.noneName QDialog.accept(self) class ModTypeListDlg(QDialog): """Dialog for adding and deleting data types""" def __init__(self, treeFormats, renameDict, parent=None, name=None, \ modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.treeFormats = copy.deepcopy(treeFormats) self.typeRenameDict = copy.deepcopy(renameDict) self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Add/Remove Data Types') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QVBoxLayout(self, 5) groupBox = QGroupBox(_('Data Types'), self) topLayout.addWidget(groupBox) innerLayout = QGridLayout(groupBox, 6, 2, 10, 2) innerLayout.addRowSpacing(0, self.fontMetrics().lineSpacing()) innerLayout.setRowStretch(1, 1) self.listBox = QListBox(groupBox) self.loadList() innerLayout.addMultiCellWidget(self.listBox, 1, 5, 0, 0) newButton = QPushButton(_('&New Type'), groupBox) innerLayout.addWidget(newButton, 2, 1) self.connect(newButton, SIGNAL('clicked()'), self.newType) copyButton = QPushButton(_('Copy &Type'), groupBox) innerLayout.addWidget(copyButton, 3, 1) self.connect(copyButton, SIGNAL('clicked()'), self.copyType) renameButton = QPushButton(_('&Rename Type'), groupBox) innerLayout.addWidget(renameButton, 4, 1) self.connect(renameButton, SIGNAL('clicked()'), self.renameType) delButton = QPushButton(_('&Delete Type'), groupBox) innerLayout.addWidget(delButton, 5, 1) self.connect(delButton, SIGNAL('clicked()'), self.delType) ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) self.listBox.setFocus() def loadList(self, selName=''): """Load type names into list""" names = self.treeFormats.nameList(True) names.sort() self.listBox.clear() for name in names: self.listBox.insertItem(name) selNum = 0 if selName: selNum = names.index(selName) self.listBox.setSelected(selNum, True) def newType(self): """Add new type to the list""" dlg = FieldEntry(_('Add Type'), _('Enter new type name:'), '', \ self.treeFormats.nameList(), self, None, True) if dlg.exec_loop() == QDialog.Accepted: self.treeFormats.append(NodeFormat(dlg.text, {}, \ TreeFormats.fieldDefault)) self.loadList(dlg.text) def copyType(self): """Copy selected type to a new type""" current = unicode(self.listBox.currentText()) format = self.treeFormats.findFormat(current) allowDerive = not format.genericType dlg = FieldCopyEntry(current, self.treeFormats.nameList(), \ allowDerive, self, None, True) if dlg.exec_loop() == QDialog.Accepted: newFormat = copy.deepcopy(self.treeFormats.findFormat(current)) newFormat.name = dlg.text self.treeFormats.append(newFormat) if dlg.derived: newFormat.genericType = current newFormat.updateFromGeneric(self.treeFormats) self.loadList(dlg.text) def renameType(self): """Rename selected type""" current = unicode(self.listBox.currentText()) dlg = FieldEntry(_('Rename Type'), _('Rename from "%s" to:') \ % current, current, self.treeFormats.nameList(), \ self, None, True) if dlg.exec_loop() == QDialog.Accepted: self.treeFormats.findFormat(current).name = dlg.text self.typeRenameDict[current] = dlg.text self.loadList(dlg.text) def delType(self): """Remove selected type from the list""" current = unicode(self.listBox.currentText()) format = self.treeFormats.findFormat(current) if globalref.docRef.root.usesType(format): QMessageBox.warning(self, 'TreeLine', \ _('Cannot delete data type being used by nodes')) return self.treeFormats.removeQuiet(format) self.loadList() class FieldFormatDialog(QDialog): """Dialog for changing the field type settings""" types = [N_('Text', 'field type'), N_('Number', 'field type'), \ N_('Choice', 'field type'), N_('Combination', 'field type'), \ N_('AutoChoice', 'field type'), N_('Date', 'field type'), \ N_('Time', 'field type'), N_('Boolean', 'field type'), \ N_('URL', 'field type'), N_('Path', 'field type'), \ N_('InternalLink', 'field type'), \ N_('ExecuteLink', 'field type'), N_('Email', 'field type'), \ N_('Picture', 'field type')] typeTransDict = dict([(_(name), name) for name in types]) def __init__(self, field, typeRef, allowAdvChg=True, parent=None, name=None, modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.field = copy.deepcopy(field) self.typeRef = typeRef self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Field Format: %s') % self.field.name if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) self.formatHelpView = None topLayout = QGridLayout(self, 4, 2, 10, 5) topLayout.addColSpacing(0, 200) topLayout.addColSpacing(1, 200) typeBox = QVGroupBox(_('Field &Type'), self) topLayout.addWidget(typeBox, 0, 0) self.typeCombo = QComboBox(False, typeBox) for type in FieldFormatDialog.types: self.typeCombo.insertItem(_(type)) self.typeCombo.setCurrentItem(FieldFormatDialog.types.\ index(self.field.typeName)) self.connect(self.typeCombo, SIGNAL('activated(const QString&)'), \ self.changeType) typeBox.setEnabled(allowAdvChg and not typeRef.genericType) self.formatBox = QHGroupBox(_('Output &Format'), self) topLayout.addWidget(self.formatBox, 0, 1) self.formatEdit = QLineEdit(self.formatBox) self.formatEdit.setText(self.field.format) self.formatBox.setEnabled(field.defaultFormat != '') self.helpButton = QPushButton(_('Format He&lp'), self.formatBox) self.connect(self.helpButton, SIGNAL('clicked()'), self.formatHelp) extraBox = QVGroupBox(_('Extra Text'), self) topLayout.addMultiCellWidget(extraBox, 1, 2, 0, 0) prefixBox = QVBox(extraBox) prefixLabel = QLabel(_('Prefi&x'), prefixBox) self.prefixEdit = QLineEdit(prefixBox) self.prefixEdit.setText(self.field.prefix) prefixLabel.setBuddy(self.prefixEdit) suffixBox = QVBox(extraBox) suffixLabel = QLabel(_('&Suffix'), suffixBox) self.suffixEdit = QLineEdit(suffixBox) self.suffixEdit.setText(self.field.suffix) suffixLabel.setBuddy(self.suffixEdit) self.handleBox = QVButtonGroup(_('Content Text Handling'), self) topLayout.addWidget(self.handleBox, 1, 1) self.htmlButton = QRadioButton(_('Allow &HTML rich text'), \ self.handleBox) plainButton = QRadioButton(_('&Plain text with line breaks'), \ self.handleBox) self.htmlButton.setChecked(self.field.html) plainButton.setChecked(not self.field.html) self.handleBox.setEnabled(field.htmlOption) self.heightBox = QHGroupBox(_('Editor Height'), self) topLayout.addWidget(self.heightBox, 2, 1) heightLabel = QLabel(_('&Number of text lines '), self.heightBox) self.heightCtrl = QSpinBox(1, OptionDefaults.maxNumLines, 1, \ self.heightBox) self.heightCtrl.setValue(self.field.numLines) heightLabel.setBuddy(self.heightCtrl) self.heightBox.setEnabled(allowAdvChg and \ not self.field.hasEditChoices) ctrlLayout = QHBoxLayout(5) topLayout.addMultiCellLayout(ctrlLayout, 3, 3, 0, 1) ctrlLayout.insertStretch(0) advButton = QPushButton(_('&Advanced...'), self) ctrlLayout.addWidget(advButton) self.connect(advButton, SIGNAL('clicked()'), self.showAdvDlg) advButton.setEnabled(allowAdvChg) okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) self.typeCombo.setFocus() def changeType(self, text): """Change type name based on combo box signal""" self.field.changeType(FieldFormatDialog.typeTransDict[unicode(text)]) self.formatEdit.setText(self.field.format) self.formatBox.setEnabled(self.field.format != '') self.handleBox.setEnabled(self.field.htmlOption) self.heightBox.setEnabled(not self.field.hasEditChoices) if self.field.hasEditChoices: self.heightCtrl.setValue(1) def formatHelp(self): """Show help window for formats""" menu = self.formatMenu() menu.popup(self.helpButton.\ mapToGlobal(QPoint(0, self.helpButton.height()))) self.connect(menu, SIGNAL('activated(int)'), self.insertFormat) def formatMenu(self): """Popup menu for format help""" menu = QPopupMenu(self) self.formatDict = {} index = 0 for item in self.field.formatMenuList: if item: descr, key = item self.formatDict[index] = key menu.insertItem(descr, index) index += 1 else: menu.insertSeparator() return menu def insertFormat(self, id): """Insert format text from id into edit box""" self.formatEdit.insert(self.formatDict[id]) def showAdvDlg(self): """Show the dialog for advanced settings""" self.field.format = unicode(self.formatEdit.text()) self.field.initFormat() dlg = AdvancedFieldFormatDlg(self.field, self.typeRef, self, None, True) dlg.exec_loop() def accept(self): """Retrieve data settings before closing""" self.field.format = unicode(self.formatEdit.text()) self.field.prefix = unicode(self.prefixEdit.text()) self.field.suffix = unicode(self.suffixEdit.text()) self.field.html = self.htmlButton.isChecked() self.field.numLines = self.heightCtrl.value() return QDialog.accept(self) class AdvancedFieldFormatDlg(QDialog): """Dialog for advanced field settings""" noAltLinkText = _('No Alternate', 'no alt link field text') def __init__(self, field, typeRef, parent=None, name=None, modal=False, \ flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.field = field self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Advanced Field Format') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QVBoxLayout(self, 5) typeLabel = QLabel(_('For Field Type: %s') % self.field.name, self) topLayout.addWidget(typeLabel) initBox = QHGroupBox(_('&Default Value for New Nodes'), self) topLayout.addWidget(initBox) self.initCombo = QComboBox(True, initBox) self.initCombo.insertItem(self.field.getEditInitDefault(), 0) for text in self.field.initDefaultChoices(): self.initCombo.insertItem(text, -1) self.initCombo.setCurrentItem(0) linkBox = QHGroupBox(_('&Field with alternate text for links'), self) topLayout.addWidget(linkBox) self.linkCombo = QComboBox(False, linkBox) self.linkCombo.insertItem(AdvancedFieldFormatDlg.noAltLinkText) if self.field.allowAltLinkText: for field in typeRef.fieldList: if field.name != self.field.name: self.linkCombo.insertItem(field.name) if field.name == self.field.linkAltField: self.linkCombo.setCurrentItem(self.linkCombo.count() \ - 1) else: linkBox.setEnabled(False) refBox = QVGroupBox(_('Optional Parameters'), self) topLayout.addWidget(refBox) self.isReqdButton = QCheckBox(_('&Required to be filled'), refBox) self.isReqdButton.setChecked(self.field.isRequired) self.hiddenButton = QCheckBox(_('&Hidden on editor view'), refBox) self.hiddenButton.setChecked(self.field.hidden) ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) self.initCombo.setFocus() def accept(self): """Change type as req'd before closing""" self.field.setInitDefault(unicode(self.initCombo.currentText())) if self.field.allowAltLinkText: text = unicode(self.linkCombo.currentText()) if text == AdvancedFieldFormatDlg.noAltLinkText: self.field.linkAltField = '' else: self.field.linkAltField = text self.field.isRequired = self.isReqdButton.isChecked() self.field.hidden = self.hiddenButton.isChecked() return QDialog.accept(self) class OtherFieldDlg(QDialog): """Dialog for changing to ancestor and file data fields in the ConfigDlg""" levelList = [_('No Other Reference'), _('File Info Reference'), \ _('Any Ancestor Reference'), _('Parent Reference'), \ _('Grandparent Reference'), _('Great Grandparent Reference'), _('Child Reference')] levelNums = [0, 0, 1000, 1, 2, 3, -1] # correspond to levelList def __init__(self, parentLevel, otherType, treeFormats, fileInfoFormat, \ parent=None, name=None, modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.parentLevel = parentLevel self.otherType = otherType self.treeFormats = treeFormats self.fileInfoFormat = fileInfoFormat self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Choose Other Field References') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QVBoxLayout(self, 5) levelBox = QHGroupBox(_('Reference Level'), self) topLayout.addWidget(levelBox) levelCombo = QComboBox(False, levelBox) for name in OtherFieldDlg.levelList: levelCombo.insertItem(name) levelCombo.setCurrentItem(OtherFieldDlg.levelNums.\ index(self.parentLevel)) if not self.parentLevel and otherType: levelCombo.setCurrentItem(1) self.connect(levelCombo, SIGNAL('activated(int)'), self.changeLevel) self.typeBox = QHGroupBox(_('Reference Type'), self) topLayout.addWidget(self.typeBox) self.typeCombo = QComboBox(False, self.typeBox) names = self.treeFormats.nameList(True) names.sort() for name in names: self.typeCombo.insertItem(name) if otherType and self.parentLevel: self.typeCombo.setCurrentItem(names.index(otherType.name)) self.typeBox.setEnabled(self.parentLevel) self.connect(self.typeCombo, SIGNAL('activated(const QString&)') , \ self.changeType) ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) def changeLevel(self, index): """Change level in response to combo box change""" self.parentLevel = OtherFieldDlg.levelNums[index] if index and not self.parentLevel: self.otherType = self.fileInfoFormat elif self.parentLevel: self.changeType(self.typeCombo.currentText()) else: self.otherType = None self.typeBox.setEnabled(self.parentLevel) def changeType(self, name): """Change type in response to combo box change""" self.otherType = self.treeFormats.findFormat(unicode(name)) class TypeSetDlg(QDialog): """Dialog for setting items to a type""" def __init__(self, parent=None, name=None, modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Set Data Types') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QVBoxLayout(self, 5) self.groupBox = QGroupBox('', self) topLayout.addWidget(self.groupBox) innerLayout = QGridLayout(self.groupBox, 6, 2, 10, 5) innerLayout.addRowSpacing(0, self.fontMetrics().lineSpacing()) innerLayout.setRowStretch(1, 1) self.listBox = QListBox(self.groupBox) innerLayout.addMultiCellWidget(self.listBox, 1, 5, 0, 0) self.itemButton = QPushButton(_('Set &Selection'), self.groupBox) innerLayout.addWidget(self.itemButton, 2, 1) self.connect(self.itemButton, SIGNAL('clicked()'), self.setItem) self.childButton = QPushButton(_('Set S&election\'s Children'), \ self.groupBox) innerLayout.addWidget(self.childButton, 3, 1) self.connect(self.childButton, SIGNAL('clicked()'), self.setChild) self.descendButton = QPushButton(_('Set All &Descendants'), \ self.groupBox) innerLayout.addWidget(self.descendButton, 4, 1) self.connect(self.descendButton, SIGNAL('clicked()'), self.setDescend) self.conditionButton = QPushButton(_('Set Descendants '\ 'C&ondtionally...'), \ self.groupBox) innerLayout.addWidget(self.conditionButton, 5, 1) self.connect(self.conditionButton, SIGNAL('clicked()'), \ self.setCondition) ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) closeButton = QPushButton(_('&Close'), self) ctrlLayout.addWidget(closeButton) self.connect(closeButton, SIGNAL('clicked()'), self, SLOT('close()')) self.loadList() self.connect(self.listBox, SIGNAL('selectionChanged()'), \ self.updateDlg) self.listBox.setFocus() def loadList(self): """Load types into list box""" names = globalref.docRef.treeFormats.nameList(True) names.sort() self.listBox.clear() for name in names: self.listBox.insertItem(name) self.listBox.setSelected(0, True) self.updateDlg() def updateDlg(self): """Update label text & button availability""" selList = globalref.docRef.selection selTypes = [item.nodeFormat.name for item in selList] childTypes = [] descendTypes = [] for item in selList: childTypes.extend([i.nodeFormat.name for i in item.childList]) descendTypes.extend([format.name for format in \ item.descendTypes()]) if len(selList) == 1: self.groupBox.setTitle(_('Selection = "%s"') % selList[0].title()) elif len(selList) > 1: self.groupBox.setTitle(_('Multiple Selection')) else: self.groupBox.setTitle(_('No Selection')) currentType = unicode(self.listBox.currentText()) self.itemButton.setEnabled(len(selTypes) and \ (min(selTypes) != max(selTypes) or \ selTypes[0] != currentType)) self.childButton.setEnabled(len(childTypes) and \ (min(childTypes) != max(childTypes) or \ childTypes[0] != currentType)) descendEnable = len(descendTypes) and \ (min(descendTypes) != max(descendTypes) or \ descendTypes[0] != currentType) self.descendButton.setEnabled(descendEnable) self.conditionButton.setEnabled(descendEnable) def updateViews(self): """Update main views due to type setting changes""" globalref.docRef.modified = True globalref.updateViewAll() def setCurrentSel(self): """Set type list selection to current item on initial open""" self.blockSignals(True) selTypes = [item.nodeFormat.name for item \ in globalref.docRef.selection] names = globalref.docRef.treeFormats.nameList(True) names.sort() if selTypes and min(selTypes) == max(selTypes): self.listBox.setSelected(names.index(selTypes[0]), True) self.blockSignals(False) def setItem(self): """Set types for selected item""" newFormat = globalref.docRef.treeFormats.\ findFormat(unicode(self.listBox.currentText())) globalref.docRef.undoStore.addTypeUndo(globalref.docRef.selection) for item in globalref.docRef.selection: item.changeType(newFormat) self.updateDlg() self.updateViews() def setChild(self): """Set types for selected item's children""" newFormat = globalref.docRef.treeFormats.\ findFormat(unicode(self.listBox.currentText())) childList = [] for parent in globalref.docRef.selection: for child in parent.childList: childList.append(child) globalref.docRef.undoStore.addTypeUndo(childList) for item in childList: item.changeType(newFormat) self.updateDlg() self.updateViews() def setDescend(self): """Set types for selected item's descendants""" newFormat = globalref.docRef.treeFormats.\ findFormat(unicode(self.listBox.currentText())) itemList = [] for parent in globalref.docRef.selection: for item in parent.descendantGenNoRoot(): itemList.append(item) globalref.docRef.undoStore.addTypeUndo(itemList) for item in itemList: item.changeType(newFormat) self.updateDlg() self.updateViews() def setCondition(self): """Set types for selected item's descendants conditionally""" typeList = [] for item in globalref.docRef.selection: for type in item.descendTypes(): if type.name not in typeList: typeList.append(type.name) if len(typeList) > 1: typeList = QStringList.split(' ', ' '.join(typeList)) caption = _('Select Type') if sys.platform == 'win32': caption += ' (PyQt)' type, ok = QInputDialog.getItem(caption, \ _('Change from data type'), \ typeList, 0, 0, self) if not ok: return type = unicode(type) else: type = typeList[0] format = globalref.docRef.treeFormats.findFormat(type) dlg = ConditionDlg(_('Set Descendants Conditionally'), format, \ self, None, True) if dlg.exec_loop() != QDialog.Accepted: return cond = dlg.conditional() cond.setupFields(format) newFormat = globalref.docRef.treeFormats.\ findFormat(unicode(self.listBox.currentText())) itemList = [] for parent in globalref.docRef.selection: for item in parent.descendantGenNoRoot(): if item.nodeFormat == format and cond.evaluate(item.data): itemList.append(item) globalref.docRef.undoStore.addTypeUndo(itemList) for item in itemList: item.changeType(newFormat) self.updateDlg() self.updateViews() def keyPressEvent(self, event): """Close on escape key""" if event.key() == Qt.Key_Escape: self.close() QDialog.keyPressEvent(self, event) def closeEvent(self, event): """Signal that view is closing""" self.emit(PYSIGNAL('viewClosed'), (0,)) event.accept() class FieldSelectList(QListView): """List view that shows direction of sort, changes with right-click or left/right arrows""" def __init__(self, parent=None, name=None): QListView.__init__(self, parent, name) def contentsMousePressEvent(self, event): """Signal right-click""" if event.button() == Qt.RightButton: item = self.itemAt(self.contentsToViewport(event.pos())) if item: self.emit(PYSIGNAL('directionToggle'), (item,)) else: QListView.contentsMousePressEvent(self, event) def keyPressEvent(self, event): if event.key() in (Qt.Key_Left, Qt.Key_Right): item = self.currentItem() if item: self.emit(PYSIGNAL('directionToggle'), (item,)) else: QListView.keyPressEvent(self, event) class FieldSelectDlg(QDialog): """Dialog for selecting from a field list in order""" directionText = [_('descend', 'sort direction'), \ _('ascend', 'sort direction')] def __init__(self, fieldList, caption, label, directional=False, \ parent=None, name=None, modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.availFields = fieldList[:] self.availFields.reverse() self.selFields = [] self.directional = directional self.setIcon(globalref.treeIcons.getIcon('treeline')) if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QGridLayout(self, 2, 3, 10, 5) groupBox = QVGroupBox(label, self) topLayout.addMultiCellWidget(groupBox, 0, 0, 0, 2) self.listView = directional and FieldSelectList(groupBox) or \ QListView(groupBox) self.listView.addColumn('#') self.listView.addColumn(_('Fields')) if directional: self.listView.addColumn(_('Direction')) self.connect(self.listView, PYSIGNAL('directionToggle'), \ self.toggleDirection) self.listView.setSorting(-1) self.listView.setAllColumnsShowFocus(True) self.listView.setSelectionMode(QListView.Multi) self.listView.setFocus() self.connect(self.listView, SIGNAL('returnPressed(QListViewItem*)'), \ self.selectCurrent) self.connect(self.listView, SIGNAL('selectionChanged()'), \ self.updateSelFields) QLabel(_('(Use right mouse click or\nleft/right keys to change\n'\ 'direction)'), groupBox) self.okButton = QPushButton(_('&OK'), self) topLayout.addWidget(self.okButton, 1, 1) self.okButton.setEnabled(False) self.connect(self.okButton, SIGNAL('clicked()'), \ self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) topLayout.addWidget(cancelButton, 1, 2) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) self.loadFields() def loadFields(self): """Load fields into list view""" for field in self.availFields: QListViewItem(self.listView, '', field) def getSelList(self): """Return sort list, a list of tuples if directional""" result = self.selFields[:] if self.directional: item = self.listView.firstChild() while item: num = str(item.text(0)) if num: num = int(num) - 1 result[num] = (result[num], FieldSelectDlg.directionText.\ index(str(item.text(2)))) item = item.nextSibling() return result def selectCurrent(self, viewItem): """Select current item on return pressed signal""" self.listView.setSelected(viewItem, not viewItem.isSelected()) def toggleDirection(self, item): """Toggle sort direction for viewItem""" origText = str(item.text(2)) if origText: item.setText(2, FieldSelectDlg.directionText[not FieldSelectDlg.\ directionText.\ index(origText)]) def updateSelFields(self): """Update selected fields based on current selections""" item = self.listView.firstChild() while item: if item.isSelected(): if unicode(item.text(1)) not in self.selFields: self.selFields.append(unicode(item.text(1))) if self.directional: item.setText(2, FieldSelectDlg.directionText[1]) elif unicode(item.text(1)) in self.selFields: self.selFields.remove(unicode(item.text(1))) if self.directional: item.setText(2, '') item = item.nextSibling() item = self.listView.firstChild() while item: if item.isSelected(): item.setText(0, `self.selFields.index(unicode(item.text(1))) \ + 1`) else: item.setText(0, '') item = item.nextSibling() self.okButton.setEnabled(len(self.selFields)) class EditFieldsDlg(QDialog): """Dialog for editing all instances of the selected nodes field""" def __init__(self, fieldList, parent=None, name=None, modal=False, \ flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.resultDict = {} self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Change Selection') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QGridLayout(self, 3, 3, 10, 5) groupBox = QVGroupBox(_('&Field'), self) topLayout.addMultiCellWidget(groupBox, 0, 0, 0, 2) self.fieldBox = QComboBox(False, groupBox) for field in fieldList: self.fieldBox.insertItem(field) self.connect(self.fieldBox, SIGNAL('activated(const QString &)'), \ self.changeField) groupBox = QVGroupBox(_('&New Value'), self) topLayout.addMultiCellWidget(groupBox, 1, 1, 0, 2) self.editBox = QLineEdit(groupBox) self.connect(self.editBox, SIGNAL('textChanged(const QString &)'), \ self.updateText) self.okButton = QPushButton(_('&OK'), self) topLayout.addWidget(self.okButton, 2, 1) self.okButton.setEnabled(False) self.connect(self.okButton, SIGNAL('clicked()'), \ self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) topLayout.addWidget(cancelButton, 2, 2) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) self.fieldBox.setFocus() def changeField(self, newField): """Update the value editor based on new field name""" self.editBox.blockSignals(True) self.editBox.setText(self.resultDict.get(unicode(newField), '')) self.editBox.blockSignals(False) def updateText(self, newText): """Update the stored values based on editor change""" self.resultDict[unicode(self.fieldBox.currentText())] \ = unicode(newText) self.okButton.setEnabled(True) class ExportDlg(QDialog): """Dialog for selecting type of file export""" htmlType = 0 dirType = 1 xsltType = 2 trlType = 3 textType = 4 tableType = 5 xbelType = 6 mozType = 7 xmlType = 8 def __init__(self, itemText, parent=None, name=None, modal=False, \ flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.exportType = ExportDlg.htmlType self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Export File') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QVBoxLayout(self, 5) label = QLabel(self) label.setFrameStyle(QFrame.Panel | QFrame.Sunken) label.setTextFormat(Qt.PlainText) label.setText(_('Exporting from "%s"') % itemText) topLayout.addWidget(label) groupBox = QVButtonGroup(_('Export Type'), self) topLayout.addWidget(groupBox) QRadioButton(_('&HTML single file output'), groupBox) QRadioButton(_('HTML &directories output'), groupBox) QRadioButton(_('&XSLT output'), groupBox) QRadioButton(_('TreeLine &subtree'), groupBox) QRadioButton(_('&Tabbed title text'), groupBox) QRadioButton(_('Table &of child data'), groupBox) QRadioButton(_('XBEL &bookmarks'), groupBox) QRadioButton(_('&Mozilla HTML bookmarks'), groupBox) QRadioButton(_('&Generic XML'), groupBox) groupBox.setButton(0) self.connect(groupBox, SIGNAL('clicked(int)'), self.updateAvail) optionBox = QVGroupBox(_('Export Options'), self) topLayout.addWidget(optionBox) self.rootButton = QCheckBox(_('&Include root node'), optionBox) self.openOnlyButton = QCheckBox(_('Only open &node children'), \ optionBox) self.headerButton = QCheckBox(_('Include print header && &footer'), \ optionBox) columnBox = QHBox(optionBox) columnBox.setSpacing(5) self.numColSpin = QSpinBox(1, OptionDefaults.maxNumCol, 1, columnBox) self.numColSpin.setMaximumWidth(40) self.colLabel = QLabel(_('Co&lumns'), columnBox) self.colLabel.setBuddy(self.numColSpin) ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) groupBox.setFocus() def updateAvail(self, numClicked): """Update options available based on export type change""" self.exportType = numClicked if numClicked in (ExportDlg.htmlType, ExportDlg.xsltType, \ ExportDlg.textType): self.rootButton.setEnabled(True) elif numClicked == ExportDlg.tableType: self.rootButton.setChecked(False) self.rootButton.setEnabled(False) else: self.rootButton.setChecked(True) self.rootButton.setEnabled(False) if numClicked in (ExportDlg.htmlType, ExportDlg.textType): self.openOnlyButton.setEnabled(True) else: self.openOnlyButton.setChecked(False) self.openOnlyButton.setEnabled(False) if numClicked in (ExportDlg.htmlType, ExportDlg.dirType): self.headerButton.setEnabled(True) else: self.headerButton.setChecked(False) self.headerButton.setEnabled(False) if numClicked == ExportDlg.htmlType: self.colLabel.setEnabled(True) else: self.numColSpin.setValue(1) self.numColSpin.setEnabled(False) self.colLabel.setEnabled(False) def isRootIncluded(self): """Return true if root include box is checked""" return self.rootButton.isChecked() def openOnly(self): """Return true if open only box is checked""" return self.openOnlyButton.isChecked() def headerIncluded(self): """Return true if header/footer include box is checked""" return self.headerButton.isChecked() def numColumns(self): """Return number of columns in spin box""" return self.numColSpin.value() class ConditionDlg(QDialog): """Dialog for selecting conditional filter rules""" boolOp = [N_('and', 'filter bool'), N_('or', 'filter bool')] boolOpTransDict = dict([(_(name), name) for name in boolOp]) def __init__(self, caption, nodeFormat, parent=None, name=None, \ modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.nodeFormat = nodeFormat self.fieldList = nodeFormat.fieldNames() self.setIcon(globalref.treeIcons.getIcon('treeline')) if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) self.topLayout = QVBoxLayout(self, 5) self.ruleList = [ConditionRule(1, self.fieldList, self)] self.topLayout.addWidget(self.ruleList[-1]) self.boolList = [] self.ctrlLayout = QHBoxLayout(self.topLayout) self.ctrlLayout.insertStretch(0) addButton = QPushButton(_('&Add New Rule'), self) self.ctrlLayout.addWidget(addButton) self.connect(addButton, SIGNAL('clicked()'), self.addRule) self.remButton = QPushButton(_('&Remove Rule'), self) self.ctrlLayout.addWidget(self.remButton) self.connect(self.remButton, SIGNAL('clicked()'), self.removeRule) self.okButton = QPushButton(_('&OK'), self) self.ctrlLayout.addWidget(self.okButton) self.connect(self.okButton, SIGNAL('clicked()'), \ self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) self.ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) if len(self.ruleList) < 1: self.remButton.setEnabled(False) def addRule(self): """Add new rule to dialog""" if self.ruleList: self.boolList.append(QComboBox(False, self)) for op in ConditionDlg.boolOp: self.boolList[-1].insertItem(_(op)) self.topLayout.insertWidget(len(self.ruleList) * 2 - 1, \ self.boolList[-1], 0, Qt.AlignHCenter) self.ruleList.append(ConditionRule(len(self.ruleList) + 1, \ self.fieldList, self)) self.topLayout.insertWidget(len(self.ruleList) * 2 - 2, \ self.ruleList[-1]) if self.boolList: self.boolList[-1].show() self.ruleList[-1].show() self.remButton.setEnabled(True) def removeRule(self): """Remove the last rule""" if len(self.ruleList) > 0: if self.boolList: self.boolList[-1].hide() del self.boolList[-1] self.ruleList[-1].hide() del self.ruleList[-1] self.topLayout.invalidate() if len(self.ruleList) < 1: self.remButton.setEnabled(False) def setConditions(self, condition): """Set dialog to match Condition instance""" while len(self.ruleList) > 1: self.removeRule() if condition: self.ruleList[0].setCondition(condition.conditionList[0], \ self.nodeFormat, self.fieldList) for condLine in condition.conditionList[1:]: self.addRule() self.boolList[-1].setCurrentItem(ConditionDlg.boolOp.\ index(condLine.boolOper)) self.ruleList[-1].setCondition(condLine, self.nodeFormat, \ self.fieldList) def conditional(self): """Return a Conditional instance for this rule set""" return Conditional(self.ruleText()) def ruleText(self): """Return full text of this rule set""" textList = [rule.ruleText(self.nodeFormat) for rule in self.ruleList] boolList = [ConditionDlg.boolOpTransDict[unicode(box.currentText())] \ for box in self.boolList] result = '' if textList: result = textList.pop(0) for text in textList: result = ' '.join((result, boolList.pop(0), text)) return result class ConditionRule(QHGroupBox): """Saves rules for filtering items""" oper = ['==', '<', '<=', '>', '>=', '!=', \ N_('starts with', 'filter rule'), N_('ends with', 'filter rule'), \ N_('contains', 'filter rule'), N_('True', 'filter rule'), \ N_('False', 'filter rule')] operTransDict = dict([(_(name), name) for name in oper]) def __init__(self, num, fieldList, parent=None, name=None): QHGroupBox.__init__(self, parent, name) self.setTitle(_('Rule %d') % num) self.fieldBox = QComboBox(False, self) for field in fieldList: self.fieldBox.insertItem(field) self.opBox = QComboBox(False, self) for op in ConditionRule.oper: self.opBox.insertItem(_(op)) self.connect(self.opBox, SIGNAL('activated(const QString &)'), \ self.changeOp) self.edit = QLineEdit(self) self.edit.setMinimumWidth(80) self.fieldBox.setFocus() def changeOp(self, newOp): """Update the dialog based on type selection change""" newOp = ConditionRule.operTransDict[unicode(newOp)] hasFields = newOp not in ('True', 'False') self.fieldBox.setEnabled(hasFields) self.edit.setEnabled(hasFields) def setCondition(self, condLine, nodeFormat, fieldList): """Set values to match ConditionLine instance""" try: fieldNum = fieldList.index(condLine.field.name) except ValueError: fieldNum = 0 self.fieldBox.setCurrentItem(fieldNum) self.opBox.setCurrentItem(ConditionRule.oper.index(condLine.oper)) value = condLine.field.formatEditText(condLine.value)[0] self.edit.setText(value) def ruleText(self, nodeFormat): """Return full text of this rule""" op = ConditionRule.operTransDict[unicode(self.opBox.currentText())] field = unicode(self.fieldBox.currentText()) value = unicode(self.edit.text()) value = nodeFormat.findField(field).storedText(value)[0] value = value.replace('\\', '\\\\').replace('"', '\\"') return '%s %s "%s"' % (field, op, value) class NumberingDlg(QDialog): """Dialog for adding numbering to nodes""" outlineType = 0 sectionType = 1 singleType = 2 outlineFormat = ['I.', 'A.', '1.', 'a)', '(1)', '(a)', '(i)'] sectionFormat = ['1', '.1', '.1'] singleFormat = ['1.'] def __init__(self, fieldList, maxLevels, parent=None, name=None, \ modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.maxLevels = maxLevels self.currentStyle = NumberingDlg.outlineType self.currentFormat = [] self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Data Numbering') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QVBoxLayout(self, 5) groupBox = QVGroupBox(_('&Number Field'), self) topLayout.addWidget(groupBox) self.fieldBox = QComboBox(True, groupBox) for field in fieldList: self.fieldBox.insertItem(field) self.fieldBox.clearEdit() self.fieldBox.setFocus() self.connect(self.fieldBox, SIGNAL('textChanged(const QString&)'), \ self.updateField) groupBox = QVGroupBox(_('Root Node'), self) topLayout.addWidget(groupBox) self.inclRootButton = QCheckBox(_('&Include root node'), groupBox) self.inclRootButton.setChecked(True) self.connect(self.inclRootButton, SIGNAL('toggled(bool)'), \ self.updateRoot) self.styleBox = QVButtonGroup(_('Number Style'), self) topLayout.addWidget(self.styleBox) QRadioButton(_('Outline (&discrete numbers)'), self.styleBox) QRadioButton(_('&Section (append to parent number)'), self.styleBox) QRadioButton(_('Single &level (children only)'), self.styleBox) self.styleBox.setButton(self.currentStyle) self.connect(self.styleBox, SIGNAL('clicked(int)'), self.updateStyle) groupBox = QHGroupBox(_('Number &Format'), self) topLayout.addWidget(groupBox) self.formatEdit = QLineEdit(groupBox) self.connect(self.formatEdit, SIGNAL('textChanged(const QString&)'), \ self.updateFormat) QLabel(_('for Level'), groupBox) self.levelBox = QSpinBox(groupBox) self.connect(self.levelBox, SIGNAL('valueChanged(int)'), \ self.updateLevel) groupBox = QHGroupBox(_('Initial N&umber'), self) topLayout.addWidget(groupBox) QLabel(_('Start first level at number'), groupBox) self.startBox = QSpinBox(1, 1000000, 1, groupBox) self.loadFormat() ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) self.okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(self.okButton) self.okButton.setEnabled(False) self.connect(self.okButton, SIGNAL('clicked()'), \ self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) def updateField(self, text): """Update OK button based on combo changes""" self.okButton.setEnabled(len(unicode(text).strip())) def updateRoot(self, on): """Update styles based on include root change""" if self.maxLevels <= 1: if not on: self.currentStyle = NumberingDlg.singleType self.styleBox.setButton(self.currentStyle) self.styleBox.find(NumberingDlg.outlineType).setEnabled(False) self.styleBox.find(NumberingDlg.sectionType).setEnabled(False) else: self.styleBox.find(NumberingDlg.outlineType).setEnabled(True) self.styleBox.find(NumberingDlg.sectionType).setEnabled(True) if self.currentStyle == NumberingDlg.singleType and on: self.currentStyle = NumberingDlg.outlineType self.styleBox.find(NumberingDlg.outlineType).setChecked(True) self.loadFormat() def updateStyle(self, style): """Update dialog based on style selection change""" self.currentStyle = style if style == NumberingDlg.singleType: self.inclRootButton.setChecked(False) self.loadFormat() def loadFormat(self): """Load a default format and level numbers into dialog""" numLevels = self.maxLevels startLevel = 1 if self.inclRootButton.isOn(): numLevels += 1 startLevel = 0 if self.currentStyle == NumberingDlg.singleType: self.currentFormat = copy.deepcopy(NumberingDlg.singleFormat) numLevels = 1 elif self.currentStyle == NumberingDlg.outlineType: self.currentFormat = copy.deepcopy(NumberingDlg.outlineFormat) else: self.currentFormat = copy.deepcopy(NumberingDlg.sectionFormat) while len(self.currentFormat) < numLevels: self.currentFormat.extend(self.currentFormat[-2:]) self.currentFormat = self.currentFormat[:numLevels] self.levelBox.setMinValue(startLevel) self.levelBox.setMaxValue(startLevel + numLevels - 1) self.levelBox.setValue(startLevel) self.updateLevel(startLevel) def updateLevel(self, levelNum): """Update dialog based on a level change""" self.formatEdit.blockSignals(True) self.formatEdit.setText(self.currentFormat[levelNum \ - self.levelBox.minValue()]) self.formatEdit.blockSignals(False) def updateFormat(self, text): """Update dialog based on a format string change""" self.currentFormat[self.levelBox.value() \ - self.levelBox.minValue()] = unicode(text).strip() def getField(self): """Return adjusted field name""" return unicode(self.fieldBox.currentText()).strip() def accept(self): """Check for acceptable field string before closing""" try: text = unicode(self.fieldBox.currentText()).strip() except UnicodeError: text = '' if not text.replace('_', '').isalnum(): QMessageBox.warning(self, 'TreeLine', \ _('Illegal characters in field '\ '(only alpa-numerics & underscores allowed)')) return return QDialog.accept(self) def includeRoot(self): """Return True if root include box is checked""" return self.inclRootButton.isOn() def startNumber(self): """Return value from start number box""" return self.startBox.value() class FindTextEntry(QDialog): """Dialog for find string text entry""" def __init__(self, parent=None, name=None, modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) caption = _('Find') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) self.setIcon(globalref.treeIcons.getIcon('treeline')) topLayout = QVBoxLayout(self, 5) label = QLabel(_('Enter key words'), self) topLayout.addWidget(label) self.entry = QLineEdit(self) topLayout.addWidget(self.entry) self.entry.setFocus() ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) prevButton = QPushButton(_('Find &Previous'), self) ctrlLayout.addWidget(prevButton) self.connect(prevButton, SIGNAL('clicked()'), self.findPrev) nextButton = QPushButton(_('Find &Next'), self) ctrlLayout.addWidget(nextButton) nextButton.setDefault(True) self.connect(nextButton, SIGNAL('clicked()'), self.findNext) closeButton = QPushButton(_('&Close'), self) ctrlLayout.addWidget(closeButton) self.connect(closeButton, SIGNAL('clicked()'), self, SLOT('close()')) def find(self, forward=True): """Find match in direction""" text = unicode(self.entry.text()).strip() if text: if not globalref.docRef.selection.findText(text, forward): globalref.setStatusBar(_('Text string not found'), 4000) def findNext(self): """Find next match""" self.find(True) def findPrev(self): """Find previous match""" self.find(False) def keyPressEvent(self, event): """Close on escape key""" if event.key() == Qt.Key_Escape: self.close() QDialog.keyPressEvent(self, event) def closeEvent(self, event): """Signal that view is closing""" self.emit(PYSIGNAL('viewClosed'), (0,)) event.accept() class SpellCheckDlg(QDialog): """Dialog for the spell check interface""" def __init__(self, spCheck, parent=None, name=None, modal=False, \ flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Spell Check') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) self.spCheck = spCheck self.replaceAllDict = {} self.word = '' self.textLine = '' self.postion = 0 self.ignoreWord = '' self.newLine = '' topLayout = QHBoxLayout(self, 5, 5) leftLayout = QVBoxLayout(topLayout) wordBox = QVGroupBox(_('Not in Dictionary'), self) leftLayout.addWidget(wordBox) QLabel(_('Word:'), wordBox) self.wordEdit = QLineEdit('', wordBox) self.connect(self.wordEdit, SIGNAL('textChanged(const QString&)'), \ self.updateFromWord) wordBox.addSpace(5) QLabel(_('Context:'), wordBox) self.contextEdit = SpellContextEdit(wordBox) self.connect(self.contextEdit, SIGNAL('textChanged()'), \ self.updateFromContext) suggestBox = QVGroupBox(_('Suggestions'), self) leftLayout.addWidget(suggestBox) self.suggestList = QListBox(suggestBox) self.connect(self.suggestList, SIGNAL('doubleClicked(QListBoxItem*)'), \ self.replace) rightLayout = QVBoxLayout(topLayout) ignoreButton = QPushButton(_('Ignor&e'), self) rightLayout.addWidget(ignoreButton) self.connect(ignoreButton, SIGNAL('clicked()'), self.ignore) ignoreAllButton = QPushButton(_('&Ignore All'), self) rightLayout.addWidget(ignoreAllButton) self.connect(ignoreAllButton, SIGNAL('clicked()'), self.ignoreAll) rightLayout.addStretch() addButton = QPushButton(_('&Add'), self) rightLayout.addWidget(addButton) self.connect(addButton, SIGNAL('clicked()'), self.add) addLowerButton = QPushButton(_('Add &Lowercase'), self) rightLayout.addWidget(addLowerButton) self.connect(addLowerButton, SIGNAL('clicked()'), self.addLower) rightLayout.addStretch() replaceButton = QPushButton(_('&Replace'), self) rightLayout.addWidget(replaceButton) self.connect(replaceButton, SIGNAL('clicked()'), self.replace) self.replaceAllButton = QPushButton(_('Re&place All'), self) rightLayout.addWidget(self.replaceAllButton) self.connect(self.replaceAllButton, SIGNAL('clicked()'), \ self.replaceAll) rightLayout.addStretch() cancelButton = QPushButton(_('&Cancel'), self) rightLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) self.widgetDisableList = [ignoreButton, ignoreAllButton, addButton, \ addLowerButton, self.suggestList] self.fullDisableList = self.widgetDisableList + \ [self.replaceAllButton, self.wordEdit] def setWord(self, textLine, results): """Set dialog contents from the checked line and the spell check results""" self.textLine = textLine self.word, self.position, suggestions = results[0] self.wordEdit.blockSignals(True) self.wordEdit.setText(self.word) self.wordEdit.blockSignals(False) self.contextEdit.blockSignals(True) self.contextEdit.setText(self.textLine) self.contextEdit.setSelection(self.position, \ self.position + len(self.word)) self.contextEdit.blockSignals(False) self.suggestList.clear() for element in suggestions: self.suggestList.insertItem(element) self.suggestList.setCurrentItem(0) for widget in self.fullDisableList: widget.setEnabled(True) self.ignoreWord = '' self.newLine = '' newWord = self.replaceAllDict.get(self.word, '') if newWord: self.newLine = self.replaceWord(newWord) def replaceWord(self, newWord): """Return textLine with word replaced to newWord""" return self.textLine[:self.position] + newWord + \ self.textLine[self.position+len(self.word):] def ignore(self): """Set word to ignored and close dialog""" self.ignoreWord = self.word self.accept() def ignoreAll(self): """Add to dictionary's ignore list and close dialog""" self.spCheck.acceptWord(self.word) self.accept() def add(self): """Add to dictionary and close dialog""" self.spCheck.addToDict(self.word, False) self.accept() def addLower(self): """Add to dictionary as lowercase and close dialog""" self.spCheck.addToDict(self.word, True) self.accept() def replace(self): """Replace with current suggestion or contents from word or context edit boxes and close dialog""" if self.widgetDisableList[0].isEnabled(): newWord = unicode(self.suggestList.currentText()) self.newLine = self.replaceWord(newWord) else: self.newLine = unicode(self.contextEdit.text()) self.accept() def replaceAll(self): """Replace with current suggestion (in future too) and close dialog""" if self.widgetDisableList[0].isEnabled(): newWord = unicode(self.suggestList.currentText()) else: newWord = unicode(self.wordEdit.text()) self.newLine = self.replaceWord(newWord) self.replaceAllDict[self.word] = newWord self.accept() def updateFromWord(self): """Update dialog after word editor change""" for widget in self.widgetDisableList: widget.setEnabled(False) newWord = unicode(self.wordEdit.text()) self.suggestList.clearSelection() self.contextEdit.blockSignals(True) self.contextEdit.setText(self.replaceWord(newWord)) self.contextEdit.setSelection(self.position, \ self.position + len(newWord)) self.contextEdit.blockSignals(False) def updateFromContext(self): """Update dialog after context editor change""" for widget in self.fullDisableList: widget.setEnabled(False) self.suggestList.clearSelection() class RadioChoiceDlg(QDialog): """Dialog for choosing between a list of text items (radio buttons) choiceList contains tuples of item text and return values""" def __init__(self, caption, heading, choiceList, parent=None, \ name=None, modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.setIcon(globalref.treeIcons.getIcon('treeline')) if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) topLayout = QVBoxLayout(self, 5) self.groupBox = QVButtonGroup(heading, self) topLayout.addWidget(self.groupBox) for text, value in choiceList: button = QRadioButton(text, self.groupBox) button.returnValue = value self.groupBox.setButton(0) ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) self.groupBox.setFocus() def getResult(self): """Return value of selected button""" return self.groupBox.selected().returnValue class PrintHeaderDlg(QDialog): """Dialog for configuring print header and footer""" def __init__(self, parent=None, name=None, modal=False, flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.setIcon(globalref.treeIcons.getIcon('treeline')) caption = _('Set Print Header & Footer') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) self.fileInfoFormat = copy.deepcopy(globalref.docRef.fileInfoItem.\ nodeFormat) topLayout = QVBoxLayout(self, 7) mainLayout = QGridLayout(topLayout, 3, 3) fieldBox = QVGroupBox(_('Fiel&ds'), self) mainLayout.addMultiCellWidget(fieldBox, 0, 2, 0, 0) self.fieldListView = QListView(fieldBox) self.fieldListView.addColumn(_('Name')) self.fieldListView.addColumn(_('Type')) self.fieldListView.setSorting(-1) fieldFormatButton = QPushButton(_('Field Forma&t'), fieldBox) self.connect(fieldFormatButton, SIGNAL('clicked()'), self.fieldFormat) xferLayout = QVBoxLayout(5) mainLayout.addLayout(xferLayout, 0, 1) self.addFieldButton = QPushButton('>>', self) self.addFieldButton.setMaximumWidth(self.addFieldButton.height()) xferLayout.addWidget(self.addFieldButton) self.connect(self.addFieldButton, SIGNAL('clicked()'), self.addField) self.delFieldButton = QPushButton('<<', self) self.delFieldButton.setMaximumWidth(self.delFieldButton.height()) xferLayout.addWidget(self.delFieldButton) self.connect(self.delFieldButton, SIGNAL('clicked()'), self.delField) headerFooterBox = QGroupBox(_('Header and Footer'), self) mainLayout.addWidget(headerFooterBox, 0, 2) headerFooterLayout = QGridLayout(headerFooterBox, 6, 3, 10, 5) headerFooterLayout.addRowSpacing(0, self.fontMetrics().lineSpacing()) headerFooterLayout.setRowStretch(0, 0) headerFooterLayout.addRowSpacing(5, self.fontMetrics().lineSpacing()) headerFooterLayout.setRowStretch(5, 1) self.textEdits = [] names = [_('&Header Left'), _('Header C&enter'), _('Header &Right'), \ _('&Footer Left'), _('Footer Ce&nter'), _('Footer R&ight')] for num, name in enumerate(names): if num < 3: row = 2 col = num else: row = 4 col = num - 3 lineEdit = TitleEdit(headerFooterBox) headerFooterLayout.addWidget(QLabel(lineEdit, name, \ headerFooterBox), \ row - 1, col, \ Qt.AlignLeft | Qt.AlignBottom) headerFooterLayout.setRowStretch(row - 1, 1) self.textEdits.append(lineEdit) headerFooterLayout.addWidget(lineEdit, row, col) self.connect(lineEdit, PYSIGNAL('cursorMove'), self.setButtonAvail) self.connect(lineEdit, SIGNAL('textChanged(const QString&)'), \ self.setButtonAvail) self.connect(lineEdit, PYSIGNAL('focusIn'), self.setCurrentEditor) ctrlLayout = QHBoxLayout(5) mainLayout.addMultiCellLayout(ctrlLayout, 2, 2, 1, 2) mainLayout.addRowSpacing(1, 30) mainLayout.setRowStretch(1, 2) ctrlLayout.insertStretch(0) okButton = QPushButton(_('&OK'), self) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) self.loadFields() self.loadText() self.focusedEditor = self.textEdits[0] self.setButtonAvail() def setButtonAvail(self): """Update button availability""" self.delFieldButton.setEnabled(self.currentFieldPos() and True or False) def setCurrentEditor(self, sender): """Set focusedEditor based on editor focus change signal""" self.focusedEditor = sender self.setButtonAvail() def loadFields(self, selNum=0): """Load list with field names""" self.fieldListView.clear() items = [] fields = self.fileInfoFormat.fieldList[:] fields.reverse() for field in fields: items.insert(0, QListViewItem(self.fieldListView, field.name, \ _(field.typeName))) self.fieldListView.setSelected(items[selNum], True) def loadText(self): """Load text into editors""" lines = self.fileInfoFormat.getLines() lines.extend([''] * (6 - len(lines))) for editor, line in zip(self.textEdits, lines): editor.blockSignals(True) editor.setText(line) editor.blockSignals(False) def getTextLines(self): """Return a list of six text lines of the header & footer""" return [unicode(editor.text()) for editor in self.textEdits] def addField(self): """Add selected field to cursor pos in active editor""" fieldName = unicode(self.fieldListView.selectedItem().text(0)) text = u'{*!%s*}' % fieldName self.focusedEditor.deselect() self.focusedEditor.insert(text) self.focusedEditor.setFocus() def delField(self): """Remove field from cursor pos in editor""" start, end = self.currentFieldPos() text = unicode(self.focusedEditor.text()) self.focusedEditor.setText(text[:start] + text[end:]) self.focusedEditor.setCursorPosition(start) def currentFieldPos(self): """Return tuple of start, end for field at cursorPos in focusedEditor or None""" pattern = re.compile('{\*(.*?)\*}') text = unicode(self.focusedEditor.text()) cursorPos = self.focusedEditor.cursorPosition() match = pattern.search(text) while match: if match.start() < cursorPos < match.end(): return (match.start(), match.end()) match = pattern.search(text, match.end()) return None def fieldFormat(self): """Display the dialog for changing the field type settings""" fieldName = unicode(self.fieldListView.selectedItem().text(0)) field = self.fileInfoFormat.findField(fieldName) dlg = FieldFormatDialog(field, self.fileInfoFormat, False, self, \ None, True) if dlg.exec_loop() == QDialog.Accepted: field.duplicateSettings(dlg.field) def accept(self): """Set field lines after OK button is hit""" self.fileInfoFormat.lineList = [] lines = self.getTextLines() for pos, text in enumerate(lines): if text: self.fileInfoFormat.insertLine(text, pos) QDialog.accept(self) class PasswordEntry(QDialog): """Dialog for password entry and optional verification""" def __init__(self, retype=True, parent=None, name=None, modal=False, \ flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) self.password = '' self.saveIt = True caption = _('Encrypted File Password') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) self.setIcon(globalref.treeIcons.getIcon('treeline')) topLayout = QVBoxLayout(self, 5) label = QLabel(_('Type Password:'), self) topLayout.addWidget(label) self.editors = [QLineEdit(self)] self.editors[0].setEchoMode(QLineEdit.Password) topLayout.addWidget(self.editors[0]) if retype: label = QLabel(_('Re-Type Password:'), self) topLayout.addWidget(label) self.editors.append(QLineEdit(self)) self.editors[1].setEchoMode(QLineEdit.Password) topLayout.addWidget(self.editors[1]) self.connect(self.editors[0], SIGNAL('returnPressed()'), \ self.editors[1], SLOT('setFocus()')) self.editors[0].setFocus() self.connect(self.editors[-1], SIGNAL('returnPressed()'), \ self, SLOT('accept()')) self.saveCheck = QCheckBox(_('Remember password during this session'), \ self) self.saveCheck.setChecked(True) topLayout.addWidget(self.saveCheck) ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) okButton = QPushButton(_('&OK'), self) okButton.setAutoDefault(False) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()')) cancelButton = QPushButton(_('&Cancel'), self) cancelButton.setAutoDefault(False) ctrlLayout.addWidget(cancelButton) self.connect(cancelButton, SIGNAL('clicked()'), self, SLOT('reject()')) def accept(self): """Store result and check for matching re-type before closing""" self.password = unicode(self.editors[0].text()) self.saveIt = self.saveCheck.isChecked() if not self.password: QMessageBox.warning(self, 'TreeLine', \ _('Zero-length passwords are not permitted')) self.editors[0].setFocus() return if len(self.editors) > 1 and \ unicode(self.editors[1].text()) != self.password: QMessageBox.warning(self, 'TreeLine', \ _('Re-typed password did not match')) self.editors[0].clear() self.editors[1].clear() self.editors[0].setFocus() return QDialog.accept(self) class PluginListDlg(QDialog): """Dialog for password entry and optional verification""" def __init__(self, plugins, parent=None, name=None, modal=False, \ flags=stdFlags): QDialog.__init__(self, parent, name, modal, flags) caption = _('TreeLine Plugins') if sys.platform == 'win32': caption += ' (PyQt)' self.setCaption(caption) self.setIcon(globalref.treeIcons.getIcon('treeline')) topLayout = QVBoxLayout(self, 5) label = QLabel(_('Plugin Modules Loaded'), self) topLayout.addWidget(label) listBox = QListBox(self) listBox.setSelectionMode(QListBox.NoSelection) listBox.setMinimumSize(250, 65) for plugin in plugins: listBox.insertItem(plugin) topLayout.addWidget(listBox) ctrlLayout = QHBoxLayout(topLayout) ctrlLayout.insertStretch(0) okButton = QPushButton(_('&OK'), self) okButton.setAutoDefault(False) ctrlLayout.addWidget(okButton) self.connect(okButton, SIGNAL('clicked()'), self, SLOT('accept()'))