class MemUsageDialog(QDialog): def __init__(self, parent=None, update=True): QDialog.__init__(self, parent=parent) layout = QVBoxLayout() self.tree = QTreeWidget() layout.addWidget(self.tree) self.setLayout(layout) self._mgr = CacheMemoryManager() self._tracked_caches = {} # tree setup code self.tree.setHeaderLabels( ["cache", "memory", "roi", "dtype", "type", "info", "id"]) self._idIndex = self.tree.columnCount() - 1 self.tree.setColumnHidden(self._idIndex, True) self.tree.setSortingEnabled(True) self.tree.clear() self._root = TreeNode() # refresh every x seconds (see showEvent()) self.timer = QTimer(self) if update: self.timer.timeout.connect(self._updateReport) def _updateReport(self): # we keep track of dirty reports so we just have to update the tree # instead of reconstructing it reports = [] for c in self._mgr.getFirstClassCaches(): r = MemInfoNode() c.generateReport(r) reports.append(r) self._root.handleChildrenReports( reports, root=self.tree.invisibleRootItem()) def hideEvent(self, event): self.timer.stop() def showEvent(self, show): # update once so we don't have to wait for initial report self._updateReport() # update every 5 sec. self.timer.start(5*1000)
class ErrorsTree(QWidget): def __init__(self, parent=None): super().__init__(parent) IDE.register_service("errors_tree", self) box = QVBoxLayout(self) box.setContentsMargins(0, 0, 0, 0) self._tree = QTreeWidget() self._tree.header().setHidden(True) self._tree.setAnimated(True) box.addWidget(self._tree) def refresh(self, errors, path): self._tree.clear() parent = QTreeWidgetItem(self._tree, [path]) for lineno, msg in errors.items(): item = QTreeWidgetItem(parent, [msg]) self._tree.addTopLevelItem(item) def display_name(self): return 'Errors'
class Results(QWidget): """Show results of occurrences in files inside the tools dock.""" def __init__(self, parent): super(Results, self).__init__(parent) self._parent = parent vbox = QVBoxLayout(self) self._tree = QTreeWidget() self._tree.setHeaderLabels((translations.TR_CONTENT, translations.TR_FILE, translations.TR_LINE)) self._tree.header().setHorizontalScrollMode( QAbstractItemView.ScrollPerPixel) self._tree.header().setSectionResizeMode(0, QHeaderView.ResizeToContents) self._tree.header().setSectionResizeMode(1, QHeaderView.ResizeToContents) self._tree.header().setSectionResizeMode(2, QHeaderView.ResizeToContents) self._tree.header().setStretchLastSection(True) self._tree.sortByColumn(1, Qt.AscendingOrder) vbox.addWidget(self._tree) #Signals self._tree.itemActivated['QTreeWidgetItem*', int].connect(self._open_result) self._tree.itemClicked['QTreeWidgetItem*', int].connect(self._open_result) def _open_result(self, item, col): """Get the data of the selected item and open the file.""" filename = item.toolTip(1) line = int(item.text(2)) - 1 main_container = IDE.get_service('main_container') if main_container: main_container.open_file(filename=filename, line=line) self._parent.hide() def update_result(self, items): """Update the result tree with the new items.""" self._tree.clear() for i in items: item = QTreeWidgetItem(self._tree, (i[3], i[0], str(i[2] + 1))) item.setToolTip(1, i[1])
class InspectorWindow(QWidget): def __init__(self, parent=None): super().__init__(parent, Qt.Tool) self.setWindowTitle(self.tr("Inspector")) self._font = None self._glyph = None glyphGroup = AccordionBox(self.tr("Glyph"), self) glyphLayout = QGridLayout(self) columnOneWidth = self.fontMetrics().width('0') * 7 nameLabel = RLabel(self.tr("Name:"), self) self.nameEdit = QLineEdit(self) self.nameEdit.editingFinished.connect(self.writeGlyphName) unicodesLabel = RLabel(self.tr("Unicode:"), self) self.unicodesEdit = QLineEdit(self) self.unicodesEdit.editingFinished.connect(self.writeUnicodes) unicodesRegExp = QRegularExpression( "(|([a-fA-F0-9]{4,6})( ([a-fA-F0-9]{4,6}))*)") unicodesValidator = QRegularExpressionValidator(unicodesRegExp, self) self.unicodesEdit.setValidator(unicodesValidator) widthLabel = RLabel(self.tr("Width:"), self) self.widthEdit = QLineEdit(self) self.widthEdit.editingFinished.connect(self.writeWidth) self.widthEdit.setMaximumWidth(columnOneWidth) self.widthEdit.setValidator(QIntValidator(self)) leftSideBearingLabel = RLabel(self.tr("Left:"), self) self.leftSideBearingEdit = QLineEdit(self) self.leftSideBearingEdit.editingFinished.connect( self.writeLeftSideBearing) self.leftSideBearingEdit.setMaximumWidth(columnOneWidth) self.leftSideBearingEdit.setValidator(QIntValidator(self)) rightSideBearingLabel = RLabel(self.tr("Right:"), self) self.rightSideBearingEdit = QLineEdit(self) self.rightSideBearingEdit.editingFinished.connect( self.writeRightSideBearing) self.rightSideBearingEdit.setMaximumWidth(columnOneWidth) self.rightSideBearingEdit.setValidator(QIntValidator(self)) markColorLabel = RLabel(self.tr("Flag:"), self) self.markColorWidget = ColorVignette(self) self.markColorWidget.colorChanged.connect( self.writeMarkColor) self.markColorWidget.setMaximumWidth(columnOneWidth) l = 0 glyphLayout.addWidget(nameLabel, l, 0) glyphLayout.addWidget(self.nameEdit, l, 1, 1, 5) l += 1 glyphLayout.addWidget(unicodesLabel, l, 0) glyphLayout.addWidget(self.unicodesEdit, l, 1, 1, 5) l += 1 glyphLayout.addWidget(widthLabel, l, 0) glyphLayout.addWidget(self.widthEdit, l, 1) l += 1 glyphLayout.addWidget(leftSideBearingLabel, l, 0) glyphLayout.addWidget(self.leftSideBearingEdit, l, 1) glyphLayout.addWidget(rightSideBearingLabel, l, 2) glyphLayout.addWidget(self.rightSideBearingEdit, l, 3) l += 1 glyphLayout.addWidget(markColorLabel, l, 0) glyphLayout.addWidget(self.markColorWidget, l, 1) glyphGroup.setLayout(glyphLayout) transformGroup = AccordionBox(self.tr("Transform"), self) transformLayout = QGridLayout(self) self.alignmentWidget = GlyphAlignmentWidget(self) hMirrorButton = QToolButton() hMirrorButton.clicked.connect(self.hMirror) hMirrorButton.setIcon(QIcon(":swap.svg")) vMirrorButton = QToolButton() vMirrorButton.clicked.connect(self.vMirror) vMirrorButton.setIcon(QIcon(":swap-vertical.svg")) # TODO: implement alignVTopButton = QToolButton() alignVTopButton.setEnabled(False) alignVTopButton.setIcon(QIcon(":format-vertical-align-top.svg")) alignVCenterButton = QToolButton() alignVCenterButton.setEnabled(False) alignVCenterButton.setIcon(QIcon(":format-vertical-align-center.svg")) alignVBottomButton = QToolButton() alignVBottomButton.setEnabled(False) alignVBottomButton.setIcon(QIcon(":format-vertical-align-bottom.svg")) alignHLeftButton = QToolButton() alignHLeftButton.setEnabled(False) alignHLeftButton.setIcon(QIcon(":format-horizontal-align-left.svg")) alignHCenterButton = QToolButton() alignHCenterButton.setEnabled(False) alignHCenterButton.setIcon( QIcon(":format-horizontal-align-center.svg")) alignHRightButton = QToolButton() alignHRightButton.setEnabled(False) alignHRightButton.setIcon(QIcon(":format-horizontal-align-right.svg")) buttonsLayout = QGridLayout() l = 0 buttonsLayout.addWidget(hMirrorButton, l, 0) buttonsLayout.addWidget(vMirrorButton, l, 1) l += 1 buttonsLayout.addWidget(alignVTopButton, l, 0) buttonsLayout.addWidget(alignVCenterButton, l, 1) buttonsLayout.addWidget(alignVBottomButton, l, 2) buttonsLayout.addWidget(alignHLeftButton, l, 3) buttonsLayout.addWidget(alignHCenterButton, l, 4) buttonsLayout.addWidget(alignHRightButton, l, 5) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) buttonsLayout.addWidget(spacer, l, 7) # TODO: should this be implemented for partial selection? moveButton = QPushButton(self.tr("Move"), self) moveButton.clicked.connect(self.moveGlyph) moveXLabel = RLabel("x:", self) self.moveXEdit = QLineEdit("0", self) self.moveXEdit.setValidator(QIntValidator(self)) moveYLabel = RLabel("y:", self) self.moveYEdit = QLineEdit("0", self) self.moveYEdit.setValidator(QIntValidator(self)) moveXYBox = QCheckBox("x=y", self) moveXYBox.clicked.connect(self.lockMove) scaleButton = QPushButton(self.tr("Scale"), self) scaleButton.clicked.connect(self.scaleGlyph) scaleXLabel = RLabel("x:", self) self.scaleXEdit = QLineEdit("100", self) self.scaleXEdit.setValidator(QIntValidator(self)) scaleYLabel = RLabel("y:", self) self.scaleYEdit = QLineEdit("100", self) self.scaleYEdit.setValidator(QIntValidator(self)) scaleXYBox = QCheckBox("x=y", self) scaleXYBox.clicked.connect(self.lockScale) rotateButton = QPushButton(self.tr("Rotate"), self) rotateButton.clicked.connect(self.rotateGlyph) rotateLabel = RLabel("α:", self) self.rotateEdit = QLineEdit("0", self) self.rotateEdit.setValidator(QIntValidator(self)) skewButton = QPushButton(self.tr("Skew"), self) skewButton.clicked.connect(self.skewGlyph) skewXLabel = RLabel("α:", self) self.skewXEdit = QLineEdit("0", self) self.skewXEdit.setValidator(QIntValidator(self)) skewYLabel = RLabel("β:", self) self.skewYEdit = QLineEdit("0", self) self.skewYEdit.setValidator(QIntValidator(self)) skewXYBox = QCheckBox("α=β", self) skewXYBox.clicked.connect(self.lockSkew) snapButton = QPushButton(self.tr("Snap"), self) snapButton.clicked.connect(self.snapGlyph) self.snapEdit = QLineEdit("1", self) self.snapEdit.setValidator(QIntValidator(self)) l = 0 transformLayout.addWidget(self.alignmentWidget, l, 0) transformLayout.addLayout(buttonsLayout, l, 1, 1, 5) l += 1 transformLayout.addWidget(moveButton, l, 0) transformLayout.addWidget(moveXLabel, l, 1) transformLayout.addWidget(self.moveXEdit, l, 2) transformLayout.addWidget(moveYLabel, l, 3) transformLayout.addWidget(self.moveYEdit, l, 4) transformLayout.addWidget(moveXYBox, l, 5) l += 1 transformLayout.addWidget(scaleButton, l, 0) transformLayout.addWidget(scaleXLabel, l, 1) transformLayout.addWidget(self.scaleXEdit, l, 2) transformLayout.addWidget(scaleYLabel, l, 3) transformLayout.addWidget(self.scaleYEdit, l, 4) transformLayout.addWidget(scaleXYBox, l, 5) transformGroup.setLayout(transformLayout) l += 1 transformLayout.addWidget(rotateButton, l, 0) transformLayout.addWidget(rotateLabel, l, 1) transformLayout.addWidget(self.rotateEdit, l, 2) transformGroup.setLayout(transformLayout) l += 1 transformLayout.addWidget(skewButton, l, 0) transformLayout.addWidget(skewXLabel, l, 1) transformLayout.addWidget(self.skewXEdit, l, 2) transformLayout.addWidget(skewYLabel, l, 3) transformLayout.addWidget(self.skewYEdit, l, 4) transformLayout.addWidget(skewXYBox, l, 5) transformGroup.setLayout(transformLayout) l += 1 transformLayout.addWidget(snapButton, l, 0) transformLayout.addWidget(self.snapEdit, l, 2) transformGroup.setLayout(transformLayout) layerSetGroup = AccordionBox(self.tr("Layers"), self) layerSetLayout = QVBoxLayout(self) self.layerSetWidget = QTreeWidget(self) self.layerSetWidget.setHeaderLabels( (self.tr("Layer Name"), self.tr("Color"))) self.layerSetWidget.setRootIsDecorated(False) self.layerSetWidget.setSelectionBehavior(QAbstractItemView.SelectRows) # TODO: make this work correctly, top-level items only # self.layerSetWidget.setDragDropMode(QAbstractItemView.InternalMove) layerSetLayout.addWidget(self.layerSetWidget) layerSetGroup.setLayout(layerSetLayout) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) mainLayout = QVBoxLayout() mainLayout.addWidget(glyphGroup) mainLayout.addWidget(transformGroup) mainLayout.addWidget(layerSetGroup) mainLayout.addWidget(spacer) self.setLayout(mainLayout) self.resize(200, self.height()) # ------------- # Notifications # ------------- def _unsubscribeFromGlyph(self): glyph = self._glyph if glyph is not None: glyph.removeObserver(self, "Glyph.Changed") def _subscribeToGlyph(self, glyph): if glyph is not None: glyph.addObserver( self, "_updateGlyphAttributes", "Glyph.Changed") def _unsubscribeFromFont(self): font = self._font if font is not None: layerSet = font.layers if layerSet is not None: layerSet.removeObserver(self, "LayerSet.Changed") def _subscribeToFont(self, font): if font is not None: layerSet = font.layers if layerSet is not None: layerSet.addObserver( self, "_updateLayerAttributes", "LayerSet.Changed") def _updateGlyph(self, notification=None): self._unsubscribeFromGlyph() app = QApplication.instance() self._glyph = app.currentGlyph() self._subscribeToGlyph(self._glyph) self.alignmentWidget.setGlyph(self._glyph) self._updateGlyphAttributes() def _updateFont(self, notification=None): self._unsubscribeFromFont() app = QApplication.instance() self._font = app.currentFont() self._subscribeToFont(self._font) self._updateLayerAttributes() def _updateGlyphAttributes(self, notification=None): name = None unicodes = None width = None leftSideBearing = None rightSideBearing = None markColor = None if self._glyph is not None: name = self._glyph.name unicodes = " ".join("%06X" % u if u > 0xFFFF else "%04X" % u for u in self._glyph.unicodes) width = str(int(self._glyph.width)) if self._glyph.leftMargin is not None: leftSideBearing = str(int(self._glyph.leftMargin)) if self._glyph.rightMargin is not None: rightSideBearing = str(int(self._glyph.rightMargin)) if self._glyph.markColor is not None: markColor = QColor.fromRgbF( *tuple(self._glyph.markColor)) self.nameEdit.setText(name) self.unicodesEdit.setText(unicodes) self.widthEdit.setText(width) self.leftSideBearingEdit.setText(leftSideBearing) self.rightSideBearingEdit.setText(rightSideBearing) self.markColorWidget.setColor(markColor) def _updateLayerAttributes(self, notification=None): self.layerSetWidget.clear() if self._font is None: return layerSet = self._font.layers if layerSet is None: return for layer in layerSet: item = QTreeWidgetItem(self.layerSetWidget) item.setFlags(item.flags() | Qt.ItemIsEditable) item.setText(0, layer.name) widget = ColorVignette(self) color = layer.color if color is not None: color = QColor.fromRgbF(*tuple(color)) widget.setColor(color) widget.setMargins(2, 2, 2, 2) widget.setMayClearColor(False) widget.colorChanged.connect(self.writeLayerColor) widget.setProperty("layer", layer) self.layerSetWidget.setItemWidget(item, 1, widget) self.layerSetWidget.setColumnWidth(1, 100) # --------- # Callbacks # --------- # glyph attributes def writeGlyphName(self): if self._glyph is None: return self._glyph.name = self.nameEdit.text() def writeUnicodes(self): if self._glyph is None: return unicodes = self.unicodesEdit.text().split(" ") if len(unicodes) == 1 and unicodes[0] == "": self._glyph.unicodes = [] else: self._glyph.unicodes = [int(uni, 16) for uni in unicodes] def writeWidth(self): if self._glyph is None: return self._glyph.width = int(self.widthEdit.text()) def writeLeftSideBearing(self): if self._glyph is None: return self._glyph.leftMargin = int(self.leftSideBearingEdit.text()) def writeRightSideBearing(self): if self._glyph is None: return self._glyph.rightMargin = int(self.rightSideBearingEdit.text()) def writeMarkColor(self): color = self.markColorWidget.color() if color is not None: color = color.getRgbF() self._glyph.markColor = color def writeLayerColor(self): widget = self.sender() color = widget.color() layer = widget.property("layer") if color is not None: color = color.getRgbF() layer.color = color # transforms def hMirror(self): glyph = self._glyph if None in (glyph, glyph.controlPointBounds): return xMin, _, xMax, _ = glyph.controlPointBounds for contour in glyph: for point in contour: point.x = xMin + xMax - point.x glyph.dirty = True def vMirror(self): glyph = self._glyph if None in (glyph, glyph.controlPointBounds): return _, yMin, _, yMax = glyph.controlPointBounds for contour in glyph: for point in contour: point.y = yMin + yMax - point.y glyph.dirty = True def lockMove(self, checked): self.moveYEdit.setEnabled(not checked) def moveGlyph(self): x = self.moveXEdit.text() if not self.moveYEdit.isEnabled(): y = x else: y = self.moveYEdit.text() x, y = int(x) if x != "" else 0, int(y) if y != "" else 0 self._glyph.move((x, y)) def lockScale(self, checked): self.scaleYEdit.setEnabled(not checked) def scaleGlyph(self): glyph = self._glyph # TODO: consider disabling the buttons in that case? if glyph is None: return sX = self.scaleXEdit.text() if not self.scaleYEdit.isEnabled(): sY = sX else: sY = self.scaleYEdit.text() sX, sY = int(sX) if sX != "" else 100, int(sY) if sY != "" else 100 sX /= 100 sY /= 100 center = self.alignmentWidget.origin() glyph.scale((sX, sY), center=center) def rotateGlyph(self): glyph = self._glyph if glyph is None: return r = self.rotateEdit.text() r = int(r) if r != "" else 0 origin = self.alignmentWidget.origin() glyph.rotate(r, offset=origin) def lockSkew(self, checked): self.skewYEdit.setEnabled(not checked) def skewGlyph(self): glyph = self._glyph if glyph is None: return sX = self.skewXEdit.text() if not self.skewYEdit.isEnabled(): sY = sX else: sY = self.skewYEdit.text() sX, sY = int(sX) if sX != "" else 0, int(sY) if sY != "" else 0 glyph.skew((sX, sY)) def snapGlyph(self): glyph = self._glyph if glyph is None: return base = self.snapEdit.text() base = int(base) if base != "" else 0 glyph.snap(base) # ---------- # Qt methods # ---------- def showEvent(self, event): super().showEvent(event) # positioning screenRect = QApplication.desktop().availableGeometry(self) widgetRect = self.frameGeometry() x = screenRect.width() - (widgetRect.width() + 20) y = screenRect.center().y() - widgetRect.height() / 2 self.move(x, y) # notifications app = QApplication.instance() self._updateGlyph() app.dispatcher.addObserver(self, "_updateGlyph", "currentGlyphChanged") self._updateFont() app.dispatcher.addObserver(self, "_updateFont", "currentFontChanged") def closeEvent(self, event): super().closeEvent(event) if event.isAccepted(): app = QApplication.instance() app.dispatcher.removeObserver(self, "currentGlyphChanged") app.dispatcher.removeObserver(self, "currentFontChanged")
class QrcPackageEditor(QGridLayout): """ A resource file system package editor. Note that this is a QLayout and not a QWidget. """ # Emitted when the package has changed. package_changed = pyqtSignal() def __init__(self, show_root=False, scan="Scan", scan_whats_this='', whats_this=''): """ Initialise the editor. """ super().__init__() self.package = None self.project = None self._show_root = show_root self._package_edit = QTreeWidget(whatsThis=whats_this) self._package_edit.header().hide() self._package_edit.itemChanged.connect(self._package_changed) self.addWidget(self._package_edit, 0, 0, 3, 1) self._scan_button = QPushButton(scan, whatsThis=scan_whats_this, clicked=self._scan, enabled=False) self.addWidget(self._scan_button, 0, 1) self._remove_button = QPushButton("Remove all", whatsThis="Remove all of the scanned directories and files.", clicked=self._remove_all, enabled=False) self.addWidget(self._remove_button, 0, 2) self._include_button = QPushButton("Include all", whatsThis="Select all of the scanned directories and files.", clicked=self._include_all, enabled=False) self.addWidget(self._include_button, 1, 1) self._exclude_button = QPushButton("Exclude all", whatsThis="Deselect all of the scanned directories and files.", clicked=self._exclude_all, enabled=False) self.addWidget(self._exclude_button, 1, 2) self._exclusions_edit = QTreeWidget( whatsThis="Any directory or file that matches any of the " "these patterns will be automatically ignored when " "scanning. Double-click on a pattern to edit or remove " "it. Double-click below the last pattern in order to " "add a new one.") self._exclusions_edit.setHeaderLabel("Exclusions") self._exclusions_edit.setEditTriggers( QTreeWidget.DoubleClicked|QTreeWidget.SelectedClicked| QTreeWidget.EditKeyPressed) self._exclusions_edit.setRootIsDecorated(False) self._exclusions_edit.itemChanged.connect(self._exclusion_changed) self.addWidget(self._exclusions_edit, 2, 1, 1, 2) def configure(self, package, project): """ Configure the editor with the contents of the given package and project. """ # Save the configuration. self.package = package self.project = project # Set the package itself. self._visualise() # Set the exclusions. self._exclusions_edit.clear() for exclude in package.exclusions: self._add_exclusion_item(exclude) # Add one to be edited to create a new entry. self._add_exclusion_item() self._scan_button.setEnabled(package is not None) def get_root_dir(self): """ Return the root directory to scan, or '' if there was an error or the user cancelled. """ raise NotImplementedError def filter(self, name): """ See if a scanned name should be discarded. """ # Include everything by default. return False def required(self, name): """ See if a scanned name is required. """ # Nothing is required by default. return False def _add_exclusion_item(self, exclude=''): """ Add a QTreeWidgetItem that holds an exclusion. """ itm = QTreeWidgetItem([exclude]) itm.setFlags( Qt.ItemIsSelectable|Qt.ItemIsEditable|Qt.ItemIsEnabled| Qt.ItemNeverHasChildren) self._exclusions_edit.addTopLevelItem(itm) def _exclusion_changed(self, itm, column): """ Invoked when an exclusion has changed. """ exc_edit = self._exclusions_edit new_exc = itm.data(0, Qt.DisplayRole).strip() itm_index = exc_edit.indexOfTopLevelItem(itm) if new_exc != '': # See if we have added a new one. if itm_index == exc_edit.topLevelItemCount() - 1: self._add_exclusion_item() else: # It is empty so remove it. exc_edit.takeTopLevelItem(itm_index) # Save the new exclusions. self.package.exclusions = [ exc_edit.topLevelItem(i).data(0, Qt.DisplayRole).strip() for i in range(exc_edit.topLevelItemCount() - 1)] self.package_changed.emit() def _get_items(self): """ Return an iterator over the tree widget items. """ it = QTreeWidgetItemIterator(self._package_edit) if self._show_root: it += 1 itm = it.value() while itm is not None: yield itm it += 1 itm = it.value() def _include_all(self, _): """ Invoked when the user clicks on the include all button. """ for itm in self._get_items(): itm.setCheckState(0, Qt.Checked) def _exclude_all(self, _): """ Invoked when the user clicks on the exclude all button. """ for itm in self._get_items(): if not itm.isDisabled(): itm.setCheckState(0, Qt.Unchecked) itm.setExpanded(False) def _remove_all(self, _): """ Invoked when the use clicks on the remove all button. """ blocked = self._package_edit.blockSignals(True) self._package_edit.clear() self._package_edit.blockSignals(blocked) self._enable_buttons() # This is a bit of a hack but is currently the only way to completely # remove the application package. if self._show_root: self.package.name = '' del self.package.contents[:] self.package_changed.emit() def _enable_buttons(self): """ Set the enabled state of those buttons that require content. """ enable = (len(list(self._get_items())) != 0) self._remove_button.setEnabled(enable) self._include_button.setEnabled(enable) self._exclude_button.setEnabled(enable) def _scan(self, _): """ Invoked when the user clicks on the scan button. """ project = self.project package = self.package # Get the root directory to scan. root = self.get_root_dir() if root == '': return # Save the included state of any existing contents so that they can be # restored after the scan. old_state = {} for itm in self._get_items(): rel_path = [itm.data(0, Qt.DisplayRole)] parent = itm.parent() while parent is not None: rel_path.append(parent.data(0, Qt.DisplayRole)) parent = parent.parent() rel_path.reverse() if self._show_root: rel_path = rel_path[1:] old_state['/'.join(rel_path)] = (itm.checkState(0) == Qt.Checked) # Walk the package. root_dir = QDir(root) if not root_dir.exists(): QMessageBox.warning(self.parentWidget(), "Scan Directory", "{0} is not a valid directory.".format( QDir.toNativeSeparators(root))) return self._add_to_container(package, root_dir, [], old_state) self._visualise() self.package_changed.emit() def _add_to_container(self, container, content_dir, dir_stack, old_state): """ Add the files and directories of a package or sub-package to a container. """ dir_contents = content_dir.entryInfoList( QDir.Files|QDir.Dirs|QDir.NoDotAndDotDot) # Make sure any filter is applied in a predictable order. dir_contents.sort(key=lambda fi: fi.fileName().lower()[1:] if fi.fileName().startswith('_') else fi.fileName().lower()) dir_stack.append(content_dir.dirName()) contents = [] for content in dir_contents: name = content.fileName() # Apply any exclusions. for exc in self.package.exclusions: if fnmatch.fnmatch(name, exc): name = None break if name is None: continue # Apply any filter. if len(dir_stack) > 1: module_path = dir_stack[1:] module_path.append(name) path_name = '/'.join(module_path) else: path_name = name if self.filter(path_name): continue # See if we already know the included state. included = old_state.get(path_name, False) # Add the content. if content.isDir(): qrc = QrcDirectory(name, included) self._add_to_container(qrc, QDir(content.canonicalFilePath()), dir_stack, old_state) elif content.isFile(): qrc = QrcFile(name, included) else: continue contents.append(qrc) container.contents = contents dir_stack.pop() def _visualise(self): """ Update the GUI with the package content. """ blocked = self._package_edit.blockSignals(True) self._package_edit.clear() if self.package.name is not None: if self._show_root: parent = QTreeWidgetItem([':/' + self.package.name]) self._package_edit.addTopLevelItem(parent) parent.setExpanded(True) else: parent = self._package_edit self._visualise_contents(self.package.contents, parent) self._package_edit.blockSignals(blocked) self._enable_buttons() def _visualise_contents(self, contents, parent): """ Visualise the contents for a parent. """ p = parent while p is not None and isinstance(p, QTreeWidgetItem): p = p.parent() for content in contents: itm = QTreeWidgetItem(parent, [content.name]) itm.setCheckState(0, Qt.Checked if content.included else Qt.Unchecked) itm.setData(0, Qt.UserRole, content) if isinstance(content, QrcDirectory): self._visualise_contents(content.contents, itm) def _package_changed(self, itm, column): """ Invoked when part of the package changes. """ if itm.checkState(0) == Qt.Checked: itm.data(0, Qt.UserRole).included = True itm.setExpanded(True) else: self._exclude(itm) itm.setExpanded(False) self.package_changed.emit() def _exclude(self, itm): """ Exclude an item and any children it may have. """ for idx in range(itm.childCount()): self._exclude(itm.child(idx)) itm.data(0, Qt.UserRole).included = False itm.setCheckState(0, Qt.Unchecked)
class Waterfall(QWidget, waterfall.Ui_Waterfall): plot_settings_signal = QtCore.pyqtSignal(list) #send list of plotting params updated_rectangles_signal = QtCore.pyqtSignal(list) #send list of updated artists for redrawing def __init__(self, parent): super(Waterfall,self).__init__(parent) self.setupUi(self) self.get_settings() #self.send_settings() #Button functions self.btn_apply_general_settings.clicked.connect(self.send_settings) self.btn_apply_keys_and_colors_settings.clicked.connect(self.send_settings) self.patient_tree = self.create_patient_tree() self.data_viewer_container.addWidget(self.patient_tree) self.btn_color_test.clicked.connect(self.get_color) def get_color(self): self.color = QColorDialog.getColor() #returns a color object print(color) def get_settings(self): try: with shelve.open('WaterfallSettings') as shelfFile: self.keys_and_colors = shelfFile['keys_and_colors'] shelfFile.close() except: #set and use default settings self.keys_and_colors = { 'CR':'#03945D', 'PR':'#B1EE97', 'PD':'#FF6F69', 'SD':'#707070'} with shelve.open('WaterfallSettings') as shelfFile: shelfFile['keys_and_colors'] = self.keys_and_colors shelfFile.close() def on_waterfall_data_signal(self,signal): self.waterfall_data = signal['waterfall_data'] #pandas dataframe def on_generated_rectangles_signal(self,signal): self.rectangles_received = signal[0] self.add_items() #display in table self.btn_apply_general_settings.setEnabled(True) self.btn_finalize_plot.setEnabled(True) self.btn_apply_keys_and_colors_settings.setEnabled(True) def send_settings(self): ''' Emit both general plot settings, and color labeling settings. These are the settings to be used when the plot is created. ''' self.general_settings = [ self.plot_title.text(), self.x_label.text(), self.y_label.text(), [self.twenty_percent_line.isChecked(), self.thirty_percent_line.isChecked(), self.zero_percent_line.isChecked()], [self.display_responses_as_text.isChecked(), self.display_responses_as_color.isChecked(), self.display_no_responses.isChecked()], self.include_table.isChecked(), self.show_cancer_type.isChecked(), self.get_updated_color_coding() ] self.plot_settings_signal.emit(self.general_settings) def create_patient_tree(self): ''' Create QTreeWidget populated with a patient's data for the DataEntry dialog. Assumes that self.temp_patient is the patient of interest and that the variable belongs to the dialog. ''' self.tree = QTreeWidget() self.root = self.tree.invisibleRootItem() self.headers = [ 'Patient #', 'Best response %', 'Response', 'Cancer', 'Color key', ] self.headers_item = QTreeWidgetItem(self.headers) self.tree.setColumnCount(len(self.headers)) self.tree.setHeaderItem(self.headers_item) self.root.setExpanded(True) self.tree.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.tree.header().setStretchLastSection(False) return self.tree def add_items(self): ''' Populate viewing tree ''' self.tree.clear() #clear prior to entering items, prevent aggregation i=0 for rect in self.rectangles_received: #populate editable tree with rect data self.rect_item = QTreeWidgetItem(self.root) self.rect_params = [ self.waterfall_data['Patient number'][i], rect.get_height(), self.waterfall_data['Overall response'][i], self.waterfall_data['Cancer'][i] ] for col in range(0,4): self.rect_item.setText(col,str(self.rect_params[col])) self.rect_item.setTextAlignment(col,4) self.tree.setItemWidget(self.rect_item, 4, CustomCombo(self,self.keys_and_colors,self.waterfall_data['Overall response'][i])) self.rect_item.setFlags(self.rect_item.flags() | QtCore.Qt.ItemIsEditable) i+=1 def get_updated_color_coding(self): tmp_updated_color_coding = [] self.root = self.tree.invisibleRootItem() child_count = self.root.childCount() #return list of keys (children are iterated in order they were entered, which agrees with order of patient data in waterfall_data lists) return [self.tree.itemWidget(self.root.child(i),4).currentText() for i in range(child_count)] def on_updated_tree_item(self): #update the rectangle which was edited pass
class NodeSelector(QDialog): """ Popup window for creating new nodes """ def __init__(self, moduleManager, modfilter={}): super().__init__() self.modMan = moduleManager self.modfilter = modfilter self.gridLayout = QGridLayout(self) self.lineEdit = QLineEdit() self.lineEdit.textChanged.connect(self.textChanged) self.gridLayout.addWidget(self.lineEdit, 0, 0, 1, 1) self.treeWidget = QTreeWidget() self.treeWidget.itemActivated.connect(self.itemActivated) self.gridLayout.addWidget(self.treeWidget, 1, 0, 1, 1) self.data = {"node": None, "pin": None} self.rebuildTree() def rebuildTree(self): # Fill Tree with items ## Find all available Categories self.treeWidget.clear() self.categories = [] for key in self.modMan.availableNodes: if self.modMan.availableNodes[key].placable and self.filterModule(self.modMan.availableNodes[key]): self.categories.append(self.modMan.availableNodes[key].category) self.categories = list(set(self.categories)) # Make list unique self.categoryTreeitems = {} for category in self.categories: newTreeitem = QTreeWidgetItem() newTreeitem.setText(0, category) self.treeWidget.addTopLevelItem(newTreeitem) self.categoryTreeitems[category] = newTreeitem self.moduleDict = {} for key in self.modMan.availableNodes: if self.modMan.availableNodes[key].placable and self.filterModule(self.modMan.availableNodes[key]): # Todo: Could reuse previous results if performance becomes critical newTreeitem = QTreeWidgetItem() newTreeitem.setText(0, self.modMan.availableNodes[key].name) newTreeitem.setToolTip(0, self.modMan.availableNodes[key].desc) self.categoryTreeitems[self.modMan.availableNodes[key].category].addChild(newTreeitem) self.moduleDict[key] = newTreeitem for key in self.categoryTreeitems: self.categoryTreeitems[key].setExpanded(True) def filterModule(self, module): ratio = 0 compatibleType = False if "type" in self.modfilter: if self.modfilter["type"]["dir"] == "input": for input in module.inputDefs: if input.pintype == self.modfilter["type"]["type"]: compatibleType = True break elif self.modfilter["type"]["dir"] == "output": for output in module.outputDefs: if output.pintype == self.modfilter["type"]["type"]: compatibleType = True break if not compatibleType: return False if "text" in self.modfilter: # Filter by text input if self.modfilter["text"] in module.name: return True if not self.modfilter["text"]: # Text entry is empty return True ratio = fuzz.ratio(self.modfilter["text"], module.name) ratio = max(ratio, fuzz.partial_ratio(self.modfilter["text"], module.desc)) else: return True # Don't filter by text? Return all remaining if ratio > 40: return True else: return False def textChanged(self, newText): self.modfilter["text"] = newText self.rebuildTree() def itemActivated(self, item, column): for key in self.moduleDict: if self.moduleDict[key] == item: if "type" in self.modfilter: if self.modfilter["type"]["dir"] == "input": for input in self.modMan.availableNodes[key].inputDefs: if input.pintype == self.modfilter["type"]["type"]: self.data["pin"] = self.modMan.availableNodes[key].inputDefs.index(input) break elif self.modfilter["type"]["dir"] == "output": for output in self.modMan.availableNodes[key].outputDefs: if output.pintype == self.modfilter["type"]["type"]: self.data["pin"] = self.modMan.availableNodes[key].outputDefs.index(output) break self.data["node"] = self.modMan.availableNodes[key] self.done(1)
class CueListDialog(QDialog): def __init__(self, cues=None, properties=('index', 'name'), **kwargs): super().__init__(**kwargs) self.setMinimumSize(600, 400) self._properties = list(properties) self._cues = {} self.list = QTreeWidget(self) self.list.setSelectionMode(QTreeWidget.SingleSelection) self.list.setSelectionBehavior(QTreeWidget.SelectRows) self.list.setAlternatingRowColors(True) self.list.setIndentation(0) self.list.setHeaderLabels([prop.title() for prop in properties]) self.list.header().setSectionResizeMode(QHeaderView.Fixed) self.list.header().setSectionResizeMode(1, QHeaderView.Stretch) self.list.header().setStretchLastSection(False) self.list.sortByColumn(0, Qt.AscendingOrder) self.list.setSortingEnabled(True) if cues is not None: self.add_cues(cues) self.setLayout(QVBoxLayout()) self.layout().addWidget(self.list) self.buttons = QDialogButtonBox(self) self.buttons.addButton(QDialogButtonBox.Cancel) self.buttons.addButton(QDialogButtonBox.Ok) self.layout().addWidget(self.buttons) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) def add_cue(self, cue): item = QTreeWidgetItem() item.setTextAlignment(0, Qt.AlignCenter) for n, prop in enumerate(self._properties): try: item.setData(n, Qt.DisplayRole, getattr(cue, prop, 'Undefined')) except Exception as e: logging.exception('Cannot display {0} property'.format(prop), e, dialog=False) self._cues[cue] = item item.setData(0, Qt.UserRole, cue) self.list.addTopLevelItem(item) def add_cues(self, cues): self.list.setSortingEnabled(False) for cue in cues: self.add_cue(cue) self.list.setSortingEnabled(True) def remove_cue(self, cue): index = self.list.indexOfTopLevelItem(self._cues.pop(cue)) self.list.takeTopLevelItem(index) def reset(self): self.list.clear() self._cues.clear() def selected_cues(self): cues = [] for item in self.list.selectedItems(): cues.append(item.data(0, Qt.UserRole)) return cues
class GuiWritingStats(QDialog): C_TIME = 0 C_LENGTH = 1 C_IDLE = 2 C_COUNT = 3 C_BAR = 4 FMT_JSON = 0 FMT_CSV = 1 def __init__(self, mainGui): QDialog.__init__(self, mainGui) logger.debug("Initialising GuiWritingStats ...") self.setObjectName("GuiWritingStats") self.mainConf = novelwriter.CONFIG self.mainGui = mainGui self.mainTheme = mainGui.mainTheme self.theProject = mainGui.theProject self.logData = [] self.filterData = [] self.timeFilter = 0.0 self.wordOffset = 0 pOptions = self.theProject.options self.setWindowTitle(self.tr("Writing Statistics")) self.setMinimumWidth(self.mainConf.pxInt(420)) self.setMinimumHeight(self.mainConf.pxInt(400)) self.resize( self.mainConf.pxInt(pOptions.getInt("GuiWritingStats", "winWidth", 550)), self.mainConf.pxInt(pOptions.getInt("GuiWritingStats", "winHeight", 500)) ) # List Box wCol0 = self.mainConf.pxInt( pOptions.getInt("GuiWritingStats", "widthCol0", 180) ) wCol1 = self.mainConf.pxInt( pOptions.getInt("GuiWritingStats", "widthCol1", 80) ) wCol2 = self.mainConf.pxInt( pOptions.getInt("GuiWritingStats", "widthCol2", 80) ) wCol3 = self.mainConf.pxInt( pOptions.getInt("GuiWritingStats", "widthCol3", 80) ) self.listBox = QTreeWidget() self.listBox.setHeaderLabels([ self.tr("Session Start"), self.tr("Length"), self.tr("Idle"), self.tr("Words"), self.tr("Histogram"), ]) self.listBox.setIndentation(0) self.listBox.setColumnWidth(self.C_TIME, wCol0) self.listBox.setColumnWidth(self.C_LENGTH, wCol1) self.listBox.setColumnWidth(self.C_IDLE, wCol2) self.listBox.setColumnWidth(self.C_COUNT, wCol3) hHeader = self.listBox.headerItem() hHeader.setTextAlignment(self.C_LENGTH, Qt.AlignRight) hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight) hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight) sortCol = checkIntRange(pOptions.getInt("GuiWritingStats", "sortCol", 0), 0, 2, 0) sortOrder = checkIntTuple( pOptions.getInt("GuiWritingStats", "sortOrder", Qt.DescendingOrder), (Qt.AscendingOrder, Qt.DescendingOrder), Qt.DescendingOrder ) self.listBox.sortByColumn(sortCol, sortOrder) self.listBox.setSortingEnabled(True) # Word Bar self.barHeight = int(round(0.5*self.mainTheme.fontPixelSize)) self.barWidth = self.mainConf.pxInt(200) self.barImage = QPixmap(self.barHeight, self.barHeight) self.barImage.fill(self.palette().highlight().color()) # Session Info self.infoBox = QGroupBox(self.tr("Sum Totals"), self) self.infoForm = QGridLayout(self) self.infoBox.setLayout(self.infoForm) self.labelTotal = QLabel(formatTime(0)) self.labelTotal.setFont(self.mainTheme.guiFontFixed) self.labelTotal.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.labelIdleT = QLabel(formatTime(0)) self.labelIdleT.setFont(self.mainTheme.guiFontFixed) self.labelIdleT.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.labelFilter = QLabel(formatTime(0)) self.labelFilter.setFont(self.mainTheme.guiFontFixed) self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.novelWords = QLabel("0") self.novelWords.setFont(self.mainTheme.guiFontFixed) self.novelWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.notesWords = QLabel("0") self.notesWords.setFont(self.mainTheme.guiFontFixed) self.notesWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.totalWords = QLabel("0") self.totalWords.setFont(self.mainTheme.guiFontFixed) self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight) lblTTime = QLabel(self.tr("Total Time:")) lblITime = QLabel(self.tr("Idle Time:")) lblFTime = QLabel(self.tr("Filtered Time:")) lblNvCount = QLabel(self.tr("Novel Word Count:")) lblNtCount = QLabel(self.tr("Notes Word Count:")) lblTtCount = QLabel(self.tr("Total Word Count:")) self.infoForm.addWidget(lblTTime, 0, 0) self.infoForm.addWidget(lblITime, 1, 0) self.infoForm.addWidget(lblFTime, 2, 0) self.infoForm.addWidget(lblNvCount, 3, 0) self.infoForm.addWidget(lblNtCount, 4, 0) self.infoForm.addWidget(lblTtCount, 5, 0) self.infoForm.addWidget(self.labelTotal, 0, 1) self.infoForm.addWidget(self.labelIdleT, 1, 1) self.infoForm.addWidget(self.labelFilter, 2, 1) self.infoForm.addWidget(self.novelWords, 3, 1) self.infoForm.addWidget(self.notesWords, 4, 1) self.infoForm.addWidget(self.totalWords, 5, 1) self.infoForm.setRowStretch(6, 1) # Filter Options sPx = self.mainTheme.baseIconSize self.filterBox = QGroupBox(self.tr("Filters"), self) self.filterForm = QGridLayout(self) self.filterBox.setLayout(self.filterForm) self.incNovel = QSwitch(width=2*sPx, height=sPx) self.incNovel.setChecked( pOptions.getBool("GuiWritingStats", "incNovel", True) ) self.incNovel.clicked.connect(self._updateListBox) self.incNotes = QSwitch(width=2*sPx, height=sPx) self.incNotes.setChecked( pOptions.getBool("GuiWritingStats", "incNotes", True) ) self.incNotes.clicked.connect(self._updateListBox) self.hideZeros = QSwitch(width=2*sPx, height=sPx) self.hideZeros.setChecked( pOptions.getBool("GuiWritingStats", "hideZeros", True) ) self.hideZeros.clicked.connect(self._updateListBox) self.hideNegative = QSwitch(width=2*sPx, height=sPx) self.hideNegative.setChecked( pOptions.getBool("GuiWritingStats", "hideNegative", False) ) self.hideNegative.clicked.connect(self._updateListBox) self.groupByDay = QSwitch(width=2*sPx, height=sPx) self.groupByDay.setChecked( pOptions.getBool("GuiWritingStats", "groupByDay", False) ) self.groupByDay.clicked.connect(self._updateListBox) self.showIdleTime = QSwitch(width=2*sPx, height=sPx) self.showIdleTime.setChecked( pOptions.getBool("GuiWritingStats", "showIdleTime", False) ) self.showIdleTime.clicked.connect(self._updateListBox) self.filterForm.addWidget(QLabel(self.tr("Count novel files")), 0, 0) self.filterForm.addWidget(QLabel(self.tr("Count note files")), 1, 0) self.filterForm.addWidget(QLabel(self.tr("Hide zero word count")), 2, 0) self.filterForm.addWidget(QLabel(self.tr("Hide negative word count")), 3, 0) self.filterForm.addWidget(QLabel(self.tr("Group entries by day")), 4, 0) self.filterForm.addWidget(QLabel(self.tr("Show idle time")), 5, 0) self.filterForm.addWidget(self.incNovel, 0, 1) self.filterForm.addWidget(self.incNotes, 1, 1) self.filterForm.addWidget(self.hideZeros, 2, 1) self.filterForm.addWidget(self.hideNegative, 3, 1) self.filterForm.addWidget(self.groupByDay, 4, 1) self.filterForm.addWidget(self.showIdleTime, 5, 1) self.filterForm.setRowStretch(6, 1) # Settings self.histMax = QSpinBox(self) self.histMax.setMinimum(100) self.histMax.setMaximum(100000) self.histMax.setSingleStep(100) self.histMax.setValue( pOptions.getInt("GuiWritingStats", "histMax", 2000) ) self.histMax.valueChanged.connect(self._updateListBox) self.optsBox = QHBoxLayout() self.optsBox.addStretch(1) self.optsBox.addWidget(QLabel(self.tr("Word count cap for the histogram")), 0) self.optsBox.addWidget(self.histMax, 0) # Buttons self.buttonBox = QDialogButtonBox() self.buttonBox.rejected.connect(self._doClose) self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close) self.btnClose.setAutoDefault(False) self.btnSave = self.buttonBox.addButton(self.tr("Save As"), QDialogButtonBox.ActionRole) self.btnSave.setAutoDefault(False) self.saveMenu = QMenu(self) self.btnSave.setMenu(self.saveMenu) self.saveJSON = QAction(self.tr("JSON Data File (.json)"), self) self.saveJSON.triggered.connect(lambda: self._saveData(self.FMT_JSON)) self.saveMenu.addAction(self.saveJSON) self.saveCSV = QAction(self.tr("CSV Data File (.csv)"), self) self.saveCSV.triggered.connect(lambda: self._saveData(self.FMT_CSV)) self.saveMenu.addAction(self.saveCSV) # Assemble self.outerBox = QGridLayout() self.outerBox.addWidget(self.listBox, 0, 0, 1, 2) self.outerBox.addLayout(self.optsBox, 1, 0, 1, 2) self.outerBox.addWidget(self.infoBox, 2, 0) self.outerBox.addWidget(self.filterBox, 2, 1) self.outerBox.addWidget(self.buttonBox, 3, 0, 1, 2) self.outerBox.setRowStretch(0, 1) self.setLayout(self.outerBox) logger.debug("GuiWritingStats initialisation complete") return def populateGUI(self): """Populate list box with data from the log file. """ qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) self._loadLogFile() self._updateListBox() qApp.restoreOverrideCursor() return ## # Slots ## def _doClose(self): """Save the state of the window, clear cache, end close. """ self.logData = [] winWidth = self.mainConf.rpxInt(self.width()) winHeight = self.mainConf.rpxInt(self.height()) widthCol0 = self.mainConf.rpxInt(self.listBox.columnWidth(0)) widthCol1 = self.mainConf.rpxInt(self.listBox.columnWidth(1)) widthCol2 = self.mainConf.rpxInt(self.listBox.columnWidth(2)) widthCol3 = self.mainConf.rpxInt(self.listBox.columnWidth(3)) sortCol = self.listBox.sortColumn() sortOrder = self.listBox.header().sortIndicatorOrder() incNovel = self.incNovel.isChecked() incNotes = self.incNotes.isChecked() hideZeros = self.hideZeros.isChecked() hideNegative = self.hideNegative.isChecked() groupByDay = self.groupByDay.isChecked() showIdleTime = self.showIdleTime.isChecked() histMax = self.histMax.value() pOptions = self.theProject.options pOptions.setValue("GuiWritingStats", "winWidth", winWidth) pOptions.setValue("GuiWritingStats", "winHeight", winHeight) pOptions.setValue("GuiWritingStats", "widthCol0", widthCol0) pOptions.setValue("GuiWritingStats", "widthCol1", widthCol1) pOptions.setValue("GuiWritingStats", "widthCol2", widthCol2) pOptions.setValue("GuiWritingStats", "widthCol3", widthCol3) pOptions.setValue("GuiWritingStats", "sortCol", sortCol) pOptions.setValue("GuiWritingStats", "sortOrder", sortOrder) pOptions.setValue("GuiWritingStats", "incNovel", incNovel) pOptions.setValue("GuiWritingStats", "incNotes", incNotes) pOptions.setValue("GuiWritingStats", "hideZeros", hideZeros) pOptions.setValue("GuiWritingStats", "hideNegative", hideNegative) pOptions.setValue("GuiWritingStats", "groupByDay", groupByDay) pOptions.setValue("GuiWritingStats", "showIdleTime", showIdleTime) pOptions.setValue("GuiWritingStats", "histMax", histMax) pOptions.saveSettings() self.close() return def _saveData(self, dataFmt): """Save the content of the list box to a file. """ fileExt = "" textFmt = "" if dataFmt == self.FMT_JSON: fileExt = "json" textFmt = self.tr("JSON Data File") elif dataFmt == self.FMT_CSV: fileExt = "csv" textFmt = self.tr("CSV Data File") else: return False # Generate the file name saveDir = self.mainConf.lastPath if not os.path.isdir(saveDir): saveDir = os.path.expanduser("~") fileName = "sessionStats.%s" % fileExt savePath = os.path.join(saveDir, fileName) savePath, _ = QFileDialog.getSaveFileName( self, self.tr("Save Data As"), savePath, "%s (*.%s)" % (textFmt, fileExt) ) if not savePath: return False self.mainConf.setLastPath(savePath) # Do the actual writing wSuccess = False errMsg = "" try: with open(savePath, mode="w", encoding="utf-8") as outFile: if dataFmt == self.FMT_JSON: jsonData = [] for _, sD, tT, wD, wA, wB, tI in self.filterData: jsonData.append({ "date": sD, "length": tT, "newWords": wD, "novelWords": wA, "noteWords": wB, "idleTime": tI, }) json.dump(jsonData, outFile, indent=2) wSuccess = True if dataFmt == self.FMT_CSV: outFile.write( '"Date","Length (sec)","Words Changed",' '"Novel Words","Note Words","Idle Time (sec)"\n' ) for _, sD, tT, wD, wA, wB, tI in self.filterData: outFile.write(f'"{sD}",{tT:.0f},{wD},{wA},{wB},{tI}\n') wSuccess = True except Exception as exc: errMsg = formatException(exc) wSuccess = False # Report to user if wSuccess: self.mainGui.makeAlert([ self.tr("{0} file successfully written to:").format(textFmt), savePath ], nwAlert.INFO) else: self.mainGui.makeAlert([ self.tr("Failed to write {0} file.").format(textFmt), errMsg ], nwAlert.ERROR) return wSuccess ## # Internal Functions ## def _loadLogFile(self): """Load the content of the log file into a buffer. """ logger.debug("Loading session log file") self.logData = [] self.wordOffset = 0 ttNovel = 0 ttNotes = 0 ttTime = 0 ttIdle = 0 logFile = os.path.join(self.theProject.projMeta, nwFiles.SESS_STATS) if not os.path.isfile(logFile): logger.info("This project has no writing stats logfile") return False try: with open(logFile, mode="r", encoding="utf-8") as inFile: for inLine in inFile: if inLine.startswith("#"): if inLine.startswith("# Offset"): self.wordOffset = checkInt(inLine[9:].strip(), 0) logger.verbose( "Initial word count when log was started is %d" % self.wordOffset ) continue inData = inLine.split() if len(inData) < 6: continue dStart = datetime.fromisoformat(" ".join(inData[0:2])) dEnd = datetime.fromisoformat(" ".join(inData[2:4])) sIdle = 0 if len(inData) > 6: sIdle = checkInt(inData[6], 0) tDiff = dEnd - dStart sDiff = tDiff.total_seconds() ttTime += sDiff ttIdle += sIdle wcNovel = int(inData[4]) wcNotes = int(inData[5]) ttNovel = wcNovel ttNotes = wcNotes self.logData.append((dStart, sDiff, wcNovel, wcNotes, sIdle)) except Exception as exc: self.mainGui.makeAlert(self.tr( "Failed to read session log file." ), nwAlert.ERROR, exception=exc) return False ttWords = ttNovel + ttNotes self.labelTotal.setText(formatTime(round(ttTime))) self.labelIdleT.setText(formatTime(round(ttIdle))) self.novelWords.setText(f"{ttNovel:n}") self.notesWords.setText(f"{ttNotes:n}") self.totalWords.setText(f"{ttWords:n}") return True ## # Slots ## def _updateListBox(self): """Load/reload the content of the list box. """ self.listBox.clear() self.timeFilter = 0.0 incNovel = self.incNovel.isChecked() incNotes = self.incNotes.isChecked() hideZeros = self.hideZeros.isChecked() hideNegative = self.hideNegative.isChecked() groupByDay = self.groupByDay.isChecked() histMax = self.histMax.value() # Group the data if groupByDay: tempData = [] sessDate = None sessTime = 0 sessIdle = 0 lstNovel = 0 lstNotes = 0 for n, (dStart, sDiff, wcNovel, wcNotes, sIdle) in enumerate(self.logData): if n == 0: sessDate = dStart.date() if sessDate != dStart.date(): tempData.append((sessDate, sessTime, lstNovel, lstNotes, sessIdle)) sessDate = dStart.date() sessTime = sDiff sessIdle = sIdle lstNovel = wcNovel lstNotes = wcNotes else: sessTime += sDiff sessIdle += sIdle lstNovel = wcNovel lstNotes = wcNotes if sessDate is not None: tempData.append((sessDate, sessTime, lstNovel, lstNotes, sessIdle)) else: tempData = self.logData # Calculate Word Diff self.filterData = [] pcTotal = 0 listMax = 0 isFirst = True for dStart, sDiff, wcNovel, wcNotes, sIdle in tempData: wcTotal = 0 if incNovel: wcTotal += wcNovel if incNotes: wcTotal += wcNotes dwTotal = wcTotal - pcTotal if hideZeros and dwTotal == 0: continue if hideNegative and dwTotal < 0: pcTotal = wcTotal continue if isFirst: # Subtract the offset from the first list entry dwTotal -= self.wordOffset dwTotal = max(dwTotal, 1) # Don't go zero or negative isFirst = False if groupByDay: sStart = dStart.strftime(nwConst.FMT_DSTAMP) else: sStart = dStart.strftime(nwConst.FMT_TSTAMP) self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes, sIdle)) listMax = min(max(listMax, dwTotal), histMax) pcTotal = wcTotal # Populate the list showIdleTime = self.showIdleTime.isChecked() for _, sStart, sDiff, nWords, _, _, sIdle in self.filterData: if showIdleTime: idleEntry = formatTime(sIdle) else: sRatio = sIdle/sDiff if sDiff > 0.0 else 0.0 idleEntry = "%d %%" % round(100.0 * sRatio) newItem = QTreeWidgetItem() newItem.setText(self.C_TIME, sStart) newItem.setText(self.C_LENGTH, formatTime(round(sDiff))) newItem.setText(self.C_IDLE, idleEntry) newItem.setText(self.C_COUNT, f"{nWords:n}") if nWords > 0 and listMax > 0: theBar = self.barImage.scaled( int(200*min(nWords, histMax)/listMax), self.barHeight, Qt.IgnoreAspectRatio, Qt.FastTransformation ) newItem.setData(self.C_BAR, Qt.DecorationRole, theBar) newItem.setTextAlignment(self.C_LENGTH, Qt.AlignRight) newItem.setTextAlignment(self.C_IDLE, Qt.AlignRight) newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight) newItem.setTextAlignment(self.C_BAR, Qt.AlignLeft | Qt.AlignVCenter) newItem.setFont(self.C_TIME, self.mainTheme.guiFontFixed) newItem.setFont(self.C_LENGTH, self.mainTheme.guiFontFixed) newItem.setFont(self.C_COUNT, self.mainTheme.guiFontFixed) if showIdleTime: newItem.setFont(self.C_IDLE, self.mainTheme.guiFontFixed) else: newItem.setFont(self.C_IDLE, self.mainTheme.guiFont) self.listBox.addTopLevelItem(newItem) self.timeFilter += sDiff self.labelFilter.setText(formatTime(round(self.timeFilter))) return True
class BookmarksWindow(QDialog): """ A simple UI for showing bookmarks and navigating to them. FIXME: For now, this window is tied to a particular lane. If your project has more than one lane, then each one will have it's own bookmark window, which is kinda dumb. """ def __init__(self, parent, topLevelOperatorView): super(BookmarksWindow, self).__init__(parent) self.setWindowTitle("Bookmarks") self.topLevelOperatorView = topLevelOperatorView self.bookmark_tree = QTreeWidget(self) self.bookmark_tree.setHeaderLabels(["Location", "Notes"]) self.bookmark_tree.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) self.bookmark_tree.setColumnWidth(0, 200) self.bookmark_tree.setColumnWidth(1, 300) self.note_edit = QLineEdit(self) self.add_bookmark_button = QPushButton("Add Bookmark", self, clicked=self.add_bookmark) geometry = self.geometry() geometry.setSize(QSize(520, 520)) self.setGeometry(geometry) layout = QVBoxLayout() layout.addWidget(self.bookmark_tree) layout.addWidget(self.note_edit) layout.addWidget(self.add_bookmark_button) self.setLayout(layout) self._load_bookmarks() self.bookmark_tree.setContextMenuPolicy(Qt.CustomContextMenu) self.bookmark_tree.customContextMenuRequested.connect( self.showContextMenu) self.bookmark_tree.itemDoubleClicked.connect(self._handle_doubleclick) def _handle_doubleclick(self, item, col): """ Navigate to the bookmark """ data = item.data(0, Qt.UserRole).toPyObject() if data is None: return (coord, notes) = data axes = self.topLevelOperatorView.InputImages.meta.getAxisKeys() axes = axes[:-1] # drop channel axes = sorted(axes) assert len(axes) == len(coord) tagged_coord = dict(list(zip(axes, coord))) tagged_location = OrderedDict(list(zip("txyzc", (0, 0, 0, 0, 0)))) tagged_location.update(tagged_coord) t = list(tagged_location.values())[0] coord3d = list(tagged_location.values())[1:4] self.parent().editor.posModel.time = t self.parent().editor.navCtrl.panSlicingViews(coord3d, [0, 1, 2]) self.parent().editor.posModel.slicingPos = coord3d def showContextMenu(self, pos): item = self.bookmark_tree.itemAt(pos) data = item.data(0, Qt.UserRole).toPyObject() if data is None: return def delete_bookmark(): (coord, notes) = data bookmarks = list(self.topLevelOperatorView.Bookmarks.value) i = bookmarks.index((coord, notes)) bookmarks.pop(i) self.topLevelOperatorView.Bookmarks.setValue(bookmarks) self._load_bookmarks() menu = QMenu(parent=self) menu.addAction(QAction("Delete", menu, triggered=delete_bookmark)) globalPos = self.bookmark_tree.viewport().mapToGlobal(pos) menu.exec_(globalPos) # selection = menu.exec_( globalPos ) # if selection is removeLanesAction: # self.removeLanesRequested.emit( self._selectedLanes ) def add_bookmark(self): coord_txyzc = self.parent().editor.posModel.slicingPos5D tagged_coord_txyzc = dict(list(zip("txyzc", coord_txyzc))) axes = self.topLevelOperatorView.InputImages.meta.getAxisKeys() axes = axes[:-1] # drop channel axes = sorted(axes) coord = tuple(tagged_coord_txyzc[c] for c in axes) notes = str(self.note_edit.text()) bookmarks = list(self.topLevelOperatorView.Bookmarks.value) bookmarks.append((coord, notes)) self.topLevelOperatorView.Bookmarks.setValue(bookmarks) self._load_bookmarks() def _load_bookmarks(self): self.bookmark_tree.clear() lane_index = self.topLevelOperatorView.current_view_index() lane_nickname = self.topLevelOperatorView.InputImages.meta.nickname or "Lane {}".format( lane_index) bookmarks = self.topLevelOperatorView.Bookmarks.value group_item = QTreeWidgetItem(self.bookmark_tree, [lane_nickname]) for coord, notes in bookmarks: item = QTreeWidgetItem(group_item, []) item.setText(0, str(coord)) item.setData(0, Qt.UserRole, (coord, notes)) item.setText(1, notes) self.bookmark_tree.expandAll()
class OtherExtensionModulesPage(QWidget): """ The GUI for the other extension modules page of a project. """ # The page's label. label = "Other Extension Modules" @property def project(self): """ The project property getter. """ return self._project @project.setter def project(self, value): """ The project property setter. """ if self._project != value: self._project = value self._extension_modules_delegate.set_project(value) self._update_page() def __init__(self): """ Initialise the page. """ super().__init__() self._project = None # Create the page's GUI. layout = QVBoxLayout() self._extension_modules_edit = QTreeWidget( whatsThis="This shows a list of additional compiled " "extension modules to be linked with the application. " "<b>Name</b> should be the full (dot separated) " "package name of the extension module. <b>LIBS</b> " "should be the value of the corresponding " "<tt>qmake</tt> variable needed to link the extension " "module. Double-click on an entry to edit or remove " "it. Double-click below the last entry in order to " "add a new one. Values may be prefixed by a platform " "specific <tt>qmake</tt> scope.") self._extension_modules_edit.setHeaderLabels(["Name", "LIBS"]) self._extension_modules_edit.setEditTriggers( QTreeWidget.DoubleClicked|QTreeWidget.SelectedClicked| QTreeWidget.EditKeyPressed) self._extension_modules_edit.setRootIsDecorated(False) self._extension_modules_edit.itemChanged.connect( self._extension_module_changed) self._extension_modules_delegate = FilenameEditorDelegate( "Extension Module Directory", directory=True) self._extension_modules_edit.setItemDelegateForColumn(1, self._extension_modules_delegate) layout.addWidget(self._extension_modules_edit) self.setLayout(layout) def _update_page(self): """ Update the page using the current project. """ project = self.project # Set the extension modules. self._extension_modules_edit.clear() for extension_module in project.other_extension_modules: self._add_extension_module_item(extension_module) # Add one to be edited to create a new entry. self._add_extension_module_item() def _add_extension_module_item(self, extension_module=None): """ Add a QTreeWidgetItem that holds an exclusion. """ if extension_module is not None: name = extension_module.name libs = extension_module.libs else: name = libs = '' itm = QTreeWidgetItem([name, libs]) itm.setFlags( Qt.ItemIsSelectable|Qt.ItemIsEditable|Qt.ItemIsEnabled| Qt.ItemNeverHasChildren) self._extension_modules_edit.addTopLevelItem(itm) def _extension_module_changed(self, itm, value): """ Invoked when an extension module has changed. """ project = self.project em_edit = self._extension_modules_edit new_name = itm.data(0, Qt.DisplayRole).strip() new_libs = itm.data(1, Qt.DisplayRole).strip() itm_index = em_edit.indexOfTopLevelItem(itm) if new_name != '' or new_libs != '': # See if we have added a new one. if itm_index == em_edit.topLevelItemCount() - 1: self._add_extension_module_item() else: # It is empty so remove it. em_edit.takeTopLevelItem(itm_index) # Save the new extension modules. project.other_extension_modules = [ ExtensionModule( em_edit.topLevelItem(i).data(0, Qt.DisplayRole).strip(), em_edit.topLevelItem(i).data(1, Qt.DisplayRole).strip()) for i in range(em_edit.topLevelItemCount() - 1)] project.modified = True
class Tab(QWidget): """Tab in the QTableWidget where user executes query and sees the result.""" # ---------------------------------------------------------------------- def __init__(self): """Initialize Tab with layout and behavior.""" super(Tab, self).__init__() # regex pattern for SQL query block comments self.block_comment_re = re.compile( r'(^)?[^\S\n]*/(?:\*(.*?)\*/[^\S\n]*|/[^\n]*)($)?', re.DOTALL | re.MULTILINE) main_layout = QVBoxLayout(self) # define gdb props self.gdb = None self.gdb_items = None self.gdb_columns_names = None self.gdb_schemas = None # connected geodatabase path toolbar self.connected_gdb_path_label = QLabel('') self.connected_gdb_path_label.setTextInteractionFlags( Qt.TextSelectableByMouse) self.connected_gdb_path_label.setToolTip( 'Connected geodatabase that queries will be run against') self.connected_gdb_path_label.setText(not_connected_to_gdb_message) self.browse_to_gdb = QPushButton('Browse') self.browse_to_gdb.setShortcut(QKeySequence('Ctrl+B')) self.browse_to_gdb.clicked.connect( lambda evt, arg=True: self.connect_to_geodatabase( evt, triggered_with_browse=True)) self.gdb_sql_dialect_combobox = QComboBox() for dialect in sql_dialects_names: self.gdb_sql_dialect_combobox.addItem(dialect) self.gdb_browse_toolbar = QToolBar() self.gdb_browse_toolbar.setMaximumHeight(50) self.gdb_browse_toolbar.addWidget(self.browse_to_gdb) self.gdb_browse_toolbar.addWidget(self.connected_gdb_path_label) self.gdb_browse_toolbar.addSeparator() self.gdb_browse_toolbar.addWidget(self.gdb_sql_dialect_combobox) # table with results self.table = ResultTable() # execute SQL query self.execute = QAction('Execute', self) self.execute.setShortcuts( [QKeySequence('F5'), QKeySequence('Ctrl+Return')]) self.execute.triggered.connect(self.run_query) self.addAction(self.execute) # enter a SQL query self.query = TextEditor() self.query.setPlainText('') font = self.query.font() font.setFamily('Consolas') font.setStyleHint(QFont.Monospace) # TODO: add line numbers to the text editor font.setPointSize(14) self.query.setFont(font) self.query.setTabStopWidth(20) self.highlighter = Highlighter(self.query.document()) # TODO select block of text - Ctrl+/ and they become comments self.completer = Completer() self.query.set_completer(self.completer.completer) # errors panel to show if query fails to execute properly self.errors_panel = QPlainTextEdit() font = self.query.font() font.setPointSize(12) self.errors_panel.setStyleSheet('color:red') self.errors_panel.setFont(font) self.errors_panel.hide() # splitter between the toolbar, query window, and the result set table splitter = QSplitter(Qt.Vertical) splitter.addWidget(self.gdb_browse_toolbar) splitter.addWidget(self.query) splitter.addWidget(self.table) splitter.addWidget(self.errors_panel) # add the settings after the widget have been added splitter.setCollapsible(0, True) splitter.setCollapsible(1, False) splitter.setCollapsible(2, False) splitter.setCollapsible(3, False) splitter.setStretchFactor(0, 3) splitter.setStretchFactor(1, 7) splitter.setSizes((100, 200, 300)) self.table.hide() # TOC self.toc = QTreeWidget() self.toc.setHeaderHidden(True) # second splitter between the TOC to the left and the query/table to the # right toc_splitter = QSplitter(Qt.Horizontal) toc_splitter.addWidget(self.toc) toc_splitter.addWidget(splitter) toc_splitter.setCollapsible(0, True) toc_splitter.setSizes((200, 800)) # set the TOC vs data panel main_layout.addWidget(toc_splitter) margins = QMargins() margins.setBottom(10) margins.setLeft(10) margins.setRight(10) margins.setTop(10) main_layout.setContentsMargins(margins) self.setLayout(main_layout) QApplication.setStyle(QStyleFactory.create('Cleanlooks')) self.show() return # ---------------------------------------------------------------------- def connect_to_geodatabase(self, evt, triggered_with_browse=True): """Connect to geodatabase by letting user browse to a gdb folder.""" if triggered_with_browse: gdb_connect_dialog = QFileDialog(self) gdb_connect_dialog.setFileMode(QFileDialog.Directory) gdb_path = gdb_connect_dialog.getExistingDirectory() # TODO: add a filter to show only .gdb folders? # https://stackoverflow.com/questions/4893122/filtering-in-qfiledialog if gdb_path and gdb_path.endswith('.gdb'): self.gdb = Geodatabase(gdb_path) if self.gdb.is_valid(): self.connected_gdb_path_label.setText(self.gdb.path) self._set_gdb_items_highlight() self._set_gdb_items_complete() self._fill_toc() else: msg = QMessageBox() msg.setText('This is not a valid file geodatabase') msg.setWindowTitle('Validation error') msg.setStandardButtons(QMessageBox.Ok) msg.exec_() else: if self.gdb.is_valid(): self._set_gdb_items_highlight() self._set_gdb_items_complete() return # ---------------------------------------------------------------------- def wheelEvent(self, event): # noqa: N802 """Override built-in method to handle mouse wheel scrolling. Necessary to do when the tab is focused. """ modifiers = QApplication.keyboardModifiers() if modifiers == Qt.ControlModifier: if event.angleDelta().y() > 0: # scroll forward self.query.zoomIn(1) else: self.query.zoomOut(1) return # ---------------------------------------------------------------------- def run_query(self): """Run SQL query and draw the record set and call table drawing.""" if not self.gdb: self.print_sql_execute_errors(not_connected_to_gdb_message) return try: if not self.gdb.is_valid(): return # use the text of what user selected, if none -> need to run the # whole query part_sql_query = self.query.textCursor().selection().toPlainText() if part_sql_query: sql_query = part_sql_query else: sql_query = self.query.toPlainText() if sql_query: # removing block comments and single line comments sql_query = self.block_comment_re.sub( self._strip_block_comments, sql_query) sql_query = self._strip_single_comments(sql_query) else: return # TODO: add threading to allow user to cancel a long running query QApplication.setOverrideCursor(Qt.WaitCursor) start_time = time.time() self.gdb.open_connection() res, errors = self.gdb.execute_sql( sql_query, self.gdb_sql_dialect_combobox.currentText()) end_time = time.time() if errors: self.print_sql_execute_errors(errors) if res: self.table.show() self.errors_panel.hide() self.draw_result_table(res) msg = 'Executed in {exec_time:.1f} secs | {rows} rows'.format( exec_time=end_time - start_time, rows=self.table.table_data.number_layer_rows) self.update_app_status_bar(msg) except Exception as err: print(err) finally: QApplication.restoreOverrideCursor() return # ---------------------------------------------------------------------- def result_should_include_geometry(self): """Get the setting defining whether to include the geometry column.""" try: return self.parentWidget().parentWidget().parentWidget( ).do_include_geometry.isChecked() except BaseException: return True # ---------------------------------------------------------------------- def update_app_status_bar(self, message): """Update app status bar with the execution result details.""" try: self.parentWidget().parentWidget().parentWidget().statusBar( ).showMessage(message) except BaseException: pass return # ---------------------------------------------------------------------- def draw_result_table(self, res): """Draw table with the record set received from the geodatabase.""" geom_col_name = res.GetGeometryColumn( ) # shape col was in the sql query self.geometry_isin_query = bool(geom_col_name) self.table.draw_result(res, show_shapes=bool( self.result_should_include_geometry())) self.table.view.resizeColumnsToContents() return # ---------------------------------------------------------------------- def print_sql_execute_errors(self, err): """Print to a special panel errors that occurred during execution.""" self.table.hide() self.errors_panel.show() self.errors_panel.setPlainText(err) return # ---------------------------------------------------------------------- def _set_gdb_items_highlight(self): """Set completer and highlight properties for geodatabase items.""" self.gdb_items = self.gdb.get_items() self.highlighter.set_highlight_rules_gdb_items(self.gdb_items, 'Table') self.gdb_schemas = self.gdb.get_schemas() self.gdb_columns_names = sorted(list( set( itertools.chain.from_iterable( [i.keys() for i in self.gdb_schemas.values()]))), key=lambda x: x.lower()) # ---------------------------------------------------------------------- def _set_gdb_items_complete(self): """Update completer rules to include geodatabase items.""" self.completer.update_completer_string_list(self.gdb_items + self.gdb_columns_names) self.highlighter.set_highlight_rules_gdb_items(self.gdb_columns_names, 'Column') return # ---------------------------------------------------------------------- def _fill_toc(self): """Fill TOC with geodatabase datasets and columns.""" self.toc.clear() if not self.gdb_items: return for tbl_name in sorted(self.gdb_items, key=lambda i: i.lower()): if tbl_name.islower() or tbl_name.isupper(): item = QTreeWidgetItem([tbl_name.title()]) else: item = QTreeWidgetItem([tbl_name]) font = QFont() font.setBold(True) item.setFont(0, font) for col_name, col_type in sorted( self.gdb_schemas[tbl_name].items()): if col_name.islower() or col_name.isupper(): col_name = col_name.title() item_child = QTreeWidgetItem( ['{0} ({1})'.format(col_name, col_type)]) item.addChild(item_child) self.toc.addTopLevelItem(item) return # ---------------------------------------------------------------------- def _do_toc_hide_show(self): """Hide TOC with tables and columns.""" if self.toc.isVisible(): self.toc.setVisible(False) else: self.toc.setVisible(True) return # ---------------------------------------------------------------------- def _strip_block_comments(self, sql_query): """Strip the block comments in SQL query.""" start, mid, end = sql_query.group(1, 2, 3) if mid is None: # this is a single-line comment return '' elif start is not None or end is not None: # this is a multi-line comment at start/end of a line return '' elif '\n' in mid: # this is a multi-line comment with line break return '\n' else: # this is a multi-line comment without line break return ' ' # ---------------------------------------------------------------------- def _strip_single_comments(self, sql_query): """Strip the single line comments in SQL query.""" clean_query = [] for line in sql_query.rstrip().split('\n'): clean_line = line.split('--')[0] if clean_line: clean_query.append(clean_line) return ' '.join([line for line in clean_query])
class client(QMainWindow): def __init__(self): super().__init__() # self.localPath = os.environ['HOME'] self.localPath = '/' self.serverPath = '/' self.linkData = [] self.serverFileInfo = [] self.waitingTaskQueue = [] self.finishedTaskQueue = [] self.createServerDirQueue = [] self.downloadingTask = [] self.connectionNow = { 'hostname': '', 'username': '', 'passwd': '', 'port': 21 } self.FTP = None self.clearFlag = 0 self.t1 = None self.timer = QTimer(self) self.lock = threading.Lock() self.Mutex = threading.Semaphore(1) self.initUI() def initUI(self): self.setGeometry(100, 100, 1100, 600) self.setMinimumWidth(650) self.setWindowTitle('ftpClient') self.initMenuBar() self.initCenterWidget() self.show() def initMenuBar(self): linkManage = QAction('管理连接(&L)', self) linkManage.setShortcut('Ctrl+M') linkManage.setStatusTip('管理所有连接') linkManage.triggered.connect(self.startLinkManageDialog) exitAct = QAction('退出(&E)', self) exitAct.setShortcut('Ctrl+Q') exitAct.setStatusTip('退出程序') exitAct.triggered.connect(qApp.quit) menuBar = self.menuBar() fileMenu = menuBar.addMenu('文件(&F)') fileMenu.addAction(linkManage) fileMenu.addAction(exitAct) def initCenterWidget(self): centerWidget = QWidget() self.centerBox = QVBoxLayout() self.centerBox.setAlignment(Qt.AlignTop) self.initQuickLink() localWidget = QWidget() serverWidget = QWidget() localWidgetLayout = QVBoxLayout() serverWidgetLayout = QVBoxLayout() localLabelLayout = QHBoxLayout() serverLabelLayout = QHBoxLayout() self.localPathLineEdit = QLineEdit() self.serverPathLineEdit = QLineEdit() self.localPathLineEdit.setFocusPolicy(Qt.NoFocus) self.serverPathLineEdit.setFocusPolicy(Qt.NoFocus) self.initLocalFileBox() self.initServerFileBox() localLabelLayout.addWidget(QLabel('本地文件:')) localLabelLayout.addWidget(self.localPathLineEdit) serverLabelLayout.addWidget(QLabel('服务器文件:')) serverLabelLayout.addWidget(self.serverPathLineEdit) localWidgetLayout.addLayout(localLabelLayout) localWidgetLayout.addWidget(self.localFileTreeView) localWidgetLayout.setContentsMargins(0, 0, 0, 0) serverWidgetLayout.addLayout(serverLabelLayout) serverWidgetLayout.addWidget(self.serverFileTree) serverWidgetLayout.setContentsMargins(0, 0, 0, 0) localWidget.setLayout(localWidgetLayout) localWidget.setContentsMargins(0, 0, 0, 0) serverWidget.setLayout(serverWidgetLayout) serverWidget.setContentsMargins(0, 0, 0, 0) self.taskQueueTreeWidget = QTreeWidget() self.taskQueueTreeWidget.setColumnCount(6) self.taskQueueTreeWidget.setHeaderLabels( ['文件名', '本地文件夹', '传输方向', ' 远程文件夹', '文件大小', '当前状态']) self.taskQueueTreeWidget.setItemDelegate(MyDelegate()) taskQueueWidget = QWidget() taskQueueLayout = QVBoxLayout() taskQueueLayout.addWidget(QLabel('任务队列:')) taskQueueLayout.addWidget(self.taskQueueTreeWidget) taskQueueLayout.setContentsMargins(0, 0, 0, 0) taskQueueWidget.setLayout(taskQueueLayout) taskQueueWidget.setContentsMargins(0, 0, 0, 0) splitter1 = QSplitter(Qt.Horizontal) splitter2 = QSplitter(Qt.Vertical) splitter3 = QSplitter(Qt.Vertical) splitter4 = QSplitter(Qt.Vertical) splitter2.addWidget(localWidget) splitter2.addWidget(self.localFileTreeWidget) splitter3.addWidget(serverWidget) splitter3.addWidget(self.serverFileTable) splitter1.addWidget(splitter2) splitter1.addWidget(splitter3) splitter4.addWidget(splitter1) splitter4.addWidget(taskQueueWidget) self.centerBox.addWidget(splitter4) centerWidget.setLayout(self.centerBox) self.setCentralWidget(centerWidget) self.refreshTableButton.clicked.connect(self.tableRefresh) def initQuickLink(self): self.hostInput = QLineEdit(self) self.userNameInput = QLineEdit(self) self.passwdInput = QLineEdit(self) self.portInput = QLineEdit('21', self) self.quickLoginButton = QPushButton('快速连接', self) self.anonymousLoginCheckBox = QCheckBox('匿名连接') self.refreshTableButton = QPushButton('刷新文件列表') self.passwdInput.setEchoMode(QLineEdit.Password) self.portInput.setValidator(QIntValidator(0, 65535)) self.portInput.setMaximumWidth(40) quickLinkBox = QHBoxLayout() quickLinkBox.addWidget(QLabel(' 主机:', self)) quickLinkBox.addWidget(self.hostInput) quickLinkBox.addWidget(QLabel(' 用户名:', self)) quickLinkBox.addWidget(self.userNameInput) quickLinkBox.addWidget(QLabel(' 密码:', self)) quickLinkBox.addWidget(self.passwdInput) quickLinkBox.addWidget(QLabel(' 端口:', self)) quickLinkBox.addWidget(self.portInput) quickLinkBox.addWidget(self.quickLoginButton) quickLinkBox.addWidget(self.anonymousLoginCheckBox) quickLinkBox.addWidget(self.refreshTableButton) quickLinkBox.addStretch(1) self.centerBox.addLayout(quickLinkBox) self.quickLoginButton.clicked.connect(self.connectFromQuickLink) self.anonymousLoginCheckBox.stateChanged.connect( self.quickLinkCheckBoxChanged) def initLocalFileBox(self): self.localFileTreeView = QTreeView() self.localFileTreeWidget = QTreeWidget() self.localDirModel = ChangedQDirModel() self.localFileTreeView.setModel(self.localDirModel) self.localFileTreeView.setColumnWidth(0, 240) self.localFileTreeView.setColumnWidth(2, 60) self.localFileTreeView.clicked.connect(self.localTreeClicked) self.localFileTreeWidget.setColumnCount(4) self.localFileTreeWidget.setHeaderLabels( ['文件名', '文件大小', '文件类型', '修改时间']) self.localFileTreeWidget.setColumnWidth(0, 240) self.localFileTreeWidget.setColumnWidth(2, 60) self.localFileTreeWidget.setItemDelegate(MyDelegate()) self.localFileTable = [] self.localFileRefesh() self.localFileTreeWidget.doubleClicked.connect( self.localTableDoubleClicked) self.localFileTreeWidget.itemPressed.connect( self.localTableRightClicked) def initServerFileBox(self): self.serverFileTree = QTreeWidget() self.serverFileTable = QTreeWidget() self.serverFileTree.setHeaderLabels(['目录结构']) self.serverFileTree.setItemDelegate(MyDelegate()) self.serverFileTable.setColumnCount(5) self.serverFileTable.setHeaderLabels( ['文件名', '文件类型', '文件大小', '权限', '修改时间']) self.serverFileTable.setColumnWidth(0, 240) self.serverFileTable.setColumnWidth(1, 60) self.serverFileTable.setColumnWidth(2, 60) self.serverFileTable.setColumnWidth(3, 70) self.serverFileTable.setItemDelegate(MyDelegate()) self.serverFileTree.itemExpanded.connect(self.serverFileTreeRefresh) self.serverFileTree.itemClicked.connect(self.serverFileTreeClicked) self.serverFileTable.doubleClicked.connect( self.serverTableDoubleClicked) self.serverFileTable.itemPressed.connect(self.serverTableRightClicked) def connectFromQuickLink(self): hostName = self.hostInput.text() userName = self.userNameInput.text() passwd = self.passwdInput.text() port = int(self.portInput.text()) with open('linkdata.json', 'r') as f: self.linkData = json.load(f) data = { 'hostname': hostName, 'username': userName, 'passwd': passwd, 'port': port, 'remark': '来自快速连接' } self.linkData[userName + '@' + hostName + ':' + str(port) + ' ' + '来自快速连接'] = data with open('linkdata.json', 'w') as f: json.dump(self.linkData, f) try: self.aNewConnection(hostName, userName, passwd, port) except socket.gaierror: QMessageBox.information(self, '主机名错误', '主机名错误,请输入正确的主机名', QMessageBox.Ok, QMessageBox.Ok) return except ConnectionRefusedError: QMessageBox.information(self, '连接出错', '连接失败,请检查是否输入了正确的主机名或端口', QMessageBox.Ok, QMessageBox.Ok) return except ftplib.error_perm: QMessageBox.information(self, '登陆出错', '登陆失败,请检查是否输入了正确的用户名或密码', QMessageBox.Ok, QMessageBox.Ok) return def localFileRefesh(self): self.localFileTable.clear() self.localFileTreeWidget.clear() if self.localPath != '/': node = QTreeWidgetItem(self.localFileTreeWidget) node.setText(0, '..') self.localFileTable.append(node) for i in os.listdir(self.localPath): node = QTreeWidgetItem(self.localFileTreeWidget) node.setText(0, i) tempPath = os.path.join(self.localPath, i) if os.path.isfile(tempPath): node.setText(1, str(os.path.getsize(tempPath))) node.setText(2, 'File') elif os.path.isdir(tempPath): node.setText(1, '') node.setText(2, 'Folder') elif os.path.islink(tempPath): node.setText(1, '') node.setText(2, 'Shortcut') elif os.path.ismount(tempPath): node.setText(1, '') node.setText(2, 'Mount') try: node.setText(3, TimeStampToTime(os.path.getmtime(tempPath))) except FileNotFoundError: pass except PermissionError: pass self.localFileTable.append(node) self.localPathLineEdit.setText(self.localPath) for i in self.localFileTable: self.localFileTreeWidget.addTopLevelItem(i) def serverFileTableRefresh(self): self.Mutex.acquire() try: fileinfo = self.FTP.getdirinfo(self.serverPath) except ftplib.error_temp: self.reconnect() fileinfo = self.FTP.getdirinfo(self.serverPath) self.Mutex.release() for i in fileinfo: node = QTreeWidgetItem(self.serverFileTable) node.setText(0, i[0]) node.setText(1, i[1]) node.setText(2, i[2]) node.setText(3, i[3]) node.setText(4, i[4]) self.serverFileInfo.append(node) self.serverPathLineEdit.setText(self.serverPath) for i in self.serverFileInfo: self.serverFileTable.addTopLevelItem(i) def serverFileTreeRefresh(self, item): if self.clearFlag == 0: self.clearFlag = 1 return path = item.text(0) fatherNode = item while fatherNode != self.serverFileTreeRoot: fatherNode = fatherNode.parent() path = fatherNode.text(0) + '/' + path path = path[1:] + '/' childrenItemList = item.takeChildren() for i in childrenItemList: item.removeChild(i) self.Mutex.acquire() fileinfo = self.FTP.getdirinfo(path) for i in fileinfo: if i[1] == 'Folder': node = QTreeWidgetItem(item) node.setText(0, i[0]) tempinfo = self.FTP.getdirinfo(path + i[0]) for j in tempinfo: if j[1] == 'Folder': tempnode = QTreeWidgetItem(node) tempnode.setText(0, j[0]) node.addChild(tempnode) item.addChild(node) self.Mutex.release() def serverFileTreeClicked(self, item, int_p): path = item.text(0) fatherNode = item while fatherNode != self.serverFileTreeRoot: fatherNode = fatherNode.parent() path = fatherNode.text(0) + '/' + path if path != '/': self.serverPath = path[1:] else: self.serverPath = path self.serverFileTable.clear() self.serverFileInfo.clear() if self.serverPath != '/': node = QTreeWidgetItem(self.serverFileTable) node.setText(0, '..') self.serverFileInfo.append(node) self.serverFileTableRefresh() def serverTableDoubleClicked(self, index): if qApp.mouseButtons() == Qt.RightButton: return if self.serverFileInfo[index.row()].text(1) == 'File': return if index.row() == 0: if self.serverPath == '/': self.localPath = self.serverPath + self.serverFileInfo[0].text( 0) else: tempPath = self.serverPath.split('/')[:-1] self.serverPath = '' for i in tempPath: self.serverPath = self.serverPath + '/' + i if self.serverPath != '/': self.serverPath = self.serverPath[1:] else: self.serverPath = os.path.join( self.serverPath, self.serverFileInfo[index.row()].text(0)) self.serverFileTable.clear() self.serverFileInfo.clear() if self.serverPath != '/': node = QTreeWidgetItem(self.serverFileTable) node.setText(0, '..') self.serverFileInfo.append(node) self.serverFileTableRefresh() def serverTableRightClicked(self, item, int_p): if item.text(0) == '..': return if qApp.mouseButtons() == Qt.RightButton: serverMenu = QMenu() downLoadFile = QAction('download') downLoadFolder = QAction('downloadfolder') downLoadFile.triggered.connect(self.downloadFile) downLoadFolder.triggered.connect(self.downloadFolder) if item.text(1) == 'Folder': serverMenu.addAction(downLoadFolder) else: serverMenu.addAction(downLoadFile) serverMenu.exec_(QCursor.pos()) def downloadFile(self): filename = self.serverFileTable.selectedItems()[0].text(0) self.lock.acquire() try: self.waitingTaskQueue.append({ 'filename': filename, 'localpath': self.localPath, 'direction': '<--', 'serverpath': self.serverPath, 'filesize': self.serverFileTable.selectedItems()[0].text(2) }) finally: self.lock.release() self.taskQueueRefresh() if len(self.downloadingTask) == 0: if self.t1 == None: self.t1 = threading.Thread(target=self.taskQueueOpertion) self.t1.start() else: if self.t1.is_alive(): pass else: self.t1 = threading.Thread(target=self.taskQueueOpertion) self.t1.start() def downloadFolder(self): folderpath = os.path.join( self.serverPath, self.serverFileTable.selectedItems()[0].text(0)) if self.localPath == '/': os.mkdir('/' + self.serverFileTable.selectedItems()[0].text(0)) else: os.mkdir(self.localPath + '/' + self.serverFileTable.selectedItems()[0].text(0)) self.Mutex.acquire() self.lock.acquire() try: self.traversalServerDir(folderpath, self.localPath, self.serverPath) except ftplib.error_temp: self.reconnect() self.traversalServerDir(folderpath, self.localPath, self.serverPath) finally: self.lock.release() self.Mutex.release() self.taskQueueRefresh() if len(self.downloadingTask) == 0: if self.t1 == None: self.t1 = threading.Thread(target=self.taskQueueOpertion) self.t1.start() else: if self.t1.is_alive(): pass else: self.t1 = threading.Thread(target=self.taskQueueOpertion) self.t1.start() def localTreeClicked(self, index): if self.localDirModel.fileInfo(index).isDir(): self.localPath = self.localDirModel.filePath(index) else: self.localPath = self.localDirModel.filePath(index) tempPath = self.localPath.split('/')[:-1] self.localPath = '' for i in tempPath: self.localPath = self.localPath + '/' + i if self.localPath != '/': self.localPath = self.localPath[1:] self.localFileRefesh() def localTableDoubleClicked(self, index): if qApp.mouseButtons() == Qt.RightButton: return if os.path.isdir( os.path.join( self.localPath, self.localFileTable[index.row()].text(0))) == False: return if index.row() == 0: if self.localPath == '/': self.localPath = self.localPath + self.localFileTable[0].text( 0) else: tempPath = self.localPath.split('/')[:-1] self.localPath = '' for i in tempPath: self.localPath = self.localPath + '/' + i if self.localPath != '/': self.localPath = self.localPath[1:] else: self.localPath = os.path.join( self.localPath, self.localFileTable[index.row()].text(0)) self.localFileRefesh() def localTableRightClicked(self, item, int_p): if item.text(0) == '..': return if qApp.mouseButtons() == Qt.RightButton: localMenu = QMenu() upLoadFile = QAction('upload') upLoadFolder = QAction('uploadfolder') upLoadFile.triggered.connect(self.uploadFile) upLoadFolder.triggered.connect(self.uploadFolder) if item.text(2) == 'Folder': localMenu.addAction(upLoadFolder) else: localMenu.addAction(upLoadFile) localMenu.exec_(QCursor.pos()) def uploadFile(self): filename = self.localFileTreeWidget.selectedItems()[0].text(0) self.lock.acquire() try: self.waitingTaskQueue.append({ 'filename': filename, 'localpath': self.localPath, 'direction': '-->', 'serverpath': self.serverPath, 'filesize': self.localFileTreeWidget.selectedItems()[0].text(1) }) finally: self.lock.release() self.taskQueueRefresh() if len(self.downloadingTask) == 0: if self.t1 == None: self.t1 = threading.Thread(target=self.taskQueueOpertion) self.t1.start() else: if self.t1.is_alive(): pass else: self.t1 = threading.Thread(target=self.taskQueueOpertion) self.t1.start() def uploadFolder(self): folderpath = os.path.join( self.localPath, self.localFileTreeWidget.selectedItems()[0].text(0)) self.lock.acquire() try: self.traversalLocalDir(folderpath, self.localPath, self.serverPath) except ftplib.error_temp: self.reconnect() self.traversalLocalDir(folderpath, self.localPath, self.serverPath) finally: self.lock.release() self.taskQueueRefresh() if len(self.downloadingTask) == 0: if self.t1 == None: self.t1 = threading.Thread(target=self.taskQueueOpertion) self.t1.start() else: if self.t1.is_alive(): pass else: self.t1 = threading.Thread(target=self.taskQueueOpertion) self.t1.start() def taskQueueOpertion(self): while len(self.createServerDirQueue) != 0: self.FTP.mkd(self.createServerDirQueue[0]) self.createServerDirQueue.pop(0) while len(self.waitingTaskQueue) != 0: self.lock.acquire() try: self.downloadingTask.append(self.waitingTaskQueue[0]) print('taskadded' + str(self.waitingTaskQueue[0])) self.waitingTaskQueue.pop(0) print('self.waitingTaskQueue.pop(0)') finally: self.lock.release() print('before self.taskQueueRefresh()') self.Mutex.acquire() if self.downloadingTask[0]['direction'] == '<--': if ' ' in self.downloadingTask[0]['filename']: localName = self.downloadingTask[0]['filename'].replace( ' ', '_') else: localName = self.downloadingTask[0]['filename'] with open( self.downloadingTask[0]['localpath'] + '/' + localName, 'wb') as fp: if self.downloadingTask[0]['serverpath'] == '/': tempserverpath = '/' + self.downloadingTask[0][ 'filename'] else: tempserverpath = self.downloadingTask[0][ 'serverpath'] + '/' + self.downloadingTask[0][ 'filename'] try: self.FTP.retrbinary('RETR ' + tempserverpath, fp.write, 10240) except ftplib.error_temp: self.reconnect() self.FTP.retrbinary('RETR ' + tempserverpath, fp.write, 10240) self.FTP.set_debuglevel(0) elif self.downloadingTask[0]['direction'] == '-->': if self.downloadingTask[0]['serverpath'] == '/': tempserverpath = '/' + self.downloadingTask[0]['filename'] else: tempserverpath = self.downloadingTask[0][ 'serverpath'] + '/' + self.downloadingTask[0][ 'filename'] with open( self.downloadingTask[0]['localpath'] + '/' + self.downloadingTask[0]['filename'], 'rb') as fp: try: self.FTP.storbinary('STOR ' + tempserverpath, fp, 10240) except ftplib.error_temp: self.reconnect() self.FTP.storbinary('STOR ' + tempserverpath, fp, 10240) self.FTP.set_debuglevel(0) self.Mutex.release() self.lock.acquire() try: self.finishedTaskQueue.insert(0, self.downloadingTask[0]) print('finish' + str(self.downloadingTask[0])) self.downloadingTask.clear() print('downloadingTask.clear()') finally: self.lock.release() print('after self.taskQueueRefresh()') def startLinkManageDialog(self): with open('linkdata.json', 'r') as f: self.linkData = json.load(f) self.linkManageDialog = QDialog() self.linkManageDialog.setModal(True) linkManageLayout = QVBoxLayout() self.linkManageDialog.setLayout(linkManageLayout) self.linkManageDialog.setWindowTitle('连接管理') linkDisplayLayout = QHBoxLayout() bottomButtomGroupLayout = QHBoxLayout() connectButtom = QPushButton('连接') confirmButtom = QPushButton('确定') cancleButtom = QPushButton('取消') bottomButtomGroupLayout.addStretch(1) bottomButtomGroupLayout.addWidget(connectButtom) bottomButtomGroupLayout.addWidget(confirmButtom) bottomButtomGroupLayout.addWidget(cancleButtom) linkManageLayout.addLayout(linkDisplayLayout) linkManageLayout.addLayout(bottomButtomGroupLayout) linkListLayout = QVBoxLayout() linkEditLayout = QVBoxLayout() linkDisplayLayout.addLayout(linkListLayout) linkDisplayLayout.addLayout(linkEditLayout) self.linkList = QListWidget() addLinkButton = QPushButton('新建') removeLinkButton = QPushButton('删除') linkManageButtonGroupLayout = QHBoxLayout() linkManageButtonGroupLayout.addWidget(addLinkButton) linkManageButtonGroupLayout.addWidget(removeLinkButton) linkListLayout.addWidget(QLabel('连接列表:'), 0, Qt.AlignTop) linkListLayout.addWidget(self.linkList) linkListLayout.addLayout(linkManageButtonGroupLayout) hBox1 = QHBoxLayout() hBox2 = QHBoxLayout() hBox3 = QHBoxLayout() hBox4 = QHBoxLayout() hBox5 = QHBoxLayout() hBox6 = QHBoxLayout() self.host = QLineEdit() self.userName = QLineEdit() self.passwd = QLineEdit() self.port = QLineEdit() self.remark = QLineEdit() self.passwd.setEchoMode(QLineEdit.Password) self.port.setValidator(QIntValidator(0, 65535)) self.anonymousLogin = QCheckBox('匿名登录') confirmEdit = QPushButton('确定修改') confirmEdit.setFixedWidth(80) self.anonymousLogin.stateChanged.connect( self.linkManageCheckBoxChanged) hBox1.addWidget(QLabel('主机: ')) hBox1.addWidget(self.host) hBox2.addWidget(QLabel('用户名:')) hBox2.addWidget(self.userName) hBox3.addWidget(QLabel('密码: ')) hBox3.addWidget(self.passwd) hBox4.addWidget(QLabel('端口: ')) hBox4.addWidget(self.port) hBox6.addWidget(QLabel('备注: ')) hBox6.addWidget(self.remark) hBox5.addWidget(self.anonymousLogin) hBox5.addWidget(confirmEdit, Qt.AlignRight) linkEditLayout.addLayout(hBox1) linkEditLayout.addLayout(hBox2) linkEditLayout.addLayout(hBox3) linkEditLayout.addLayout(hBox4) linkEditLayout.addLayout(hBox6) linkEditLayout.addLayout(hBox5) for key in self.linkData: item = QListWidgetItem(self.linkList) item.setText(key) self.linkList.setCurrentRow(0) if len(self.linkData) != 0: tempdata = self.linkData[self.linkList.currentItem().text()] self.host.setText(tempdata['hostname']) self.port.setText(str(tempdata['port'])) self.remark.setText(tempdata['remark']) if tempdata['username'] == 'anonymous': self.anonymousLogin.setCheckState(Qt.Checked) else: self.userName.setText(tempdata['username']) self.passwd.setText(tempdata['passwd']) cancleButtom.clicked.connect(self.linkManageDialog.close) self.linkList.itemClicked.connect(self.listItemClicked) addLinkButton.clicked.connect(self.addNewLink) confirmEdit.clicked.connect(self.confirmEditLink) confirmButtom.clicked.connect(self.saveData) removeLinkButton.clicked.connect(self.removeLink) connectButtom.clicked.connect(self.connectFromDialog) self.linkManageDialog.show() def connectFromDialog(self): hostName = self.host.text() userName = self.userName.text() passwd = self.passwd.text() port = int(self.port.text()) try: self.aNewConnection(hostName, userName, passwd, port) except socket.gaierror: QMessageBox.information(self, '主机名错误', '主机名错误,请输入正确的主机名', QMessageBox.Ok, QMessageBox.Ok) return except ConnectionRefusedError: QMessageBox.information(self, '连接出错', '连接失败,请检查是否输入了正确的主机名或端口', QMessageBox.Ok, QMessageBox.Ok) return except ftplib.error_perm: QMessageBox.information(self, '登陆出错', '登陆失败,请检查是否输入了正确的用户名或密码', QMessageBox.Ok, QMessageBox.Ok) return self.saveData() def removeLink(self): if len(self.linkData) == 0: return rowNow = self.linkList.currentRow() itemNow = self.linkList.currentItem() self.linkData.pop(itemNow.text()) self.linkList.removeItemWidget(itemNow) self.linkList.clear() for key in self.linkData: item = QListWidgetItem(self.linkList) item.setText(key) if len(self.linkData) == 0: self.host.setText('') self.port.setText('') self.anonymousLogin.setCheckState(Qt.Unchecked) self.userName.setText('') self.passwd.setText('') self.remark.setText('') return elif len(self.linkData) < rowNow + 1: rowNow = len(self.linkData) - 1 self.linkList.setCurrentRow(len(self.linkData) - 1) else: self.linkList.setCurrentRow(rowNow) self.listItemClicked(self.linkList.currentItem()) def saveData(self): self.confirmEditLink() with open('linkdata.json', 'w') as f: json.dump(self.linkData, f) self.linkManageDialog.close() def confirmEditLink(self): hostName = self.host.text() userName = self.userName.text() passwd = self.passwd.text() port = int(self.port.text()) remark = self.remark.text() data = { 'hostname': hostName, 'username': userName, 'passwd': passwd, 'port': port, "remark": remark } self.linkData.pop(self.linkList.currentItem().text()) self.linkData[userName + '@' + hostName + ':' + str(port) + ' ' + remark] = data self.linkList.clear() for key in self.linkData: item = QListWidgetItem(self.linkList) item.setText(key) self.linkList.setCurrentRow(len(self.linkList) - 1) def addNewLink(self): if '新连接' in self.linkData: return self.linkData['新连接'] = { 'hostname': '', 'username': '', 'passwd': '', 'port': '', 'remark': '' } item = QListWidgetItem(self.linkList) item.setText('新连接') self.linkList.setCurrentRow(len(self.linkList) - 1) self.host.setText('') self.port.setText('21') self.anonymousLogin.setCheckState(Qt.Unchecked) self.userName.setText('') self.passwd.setText('') self.remark.setText('') def listItemClicked(self, item): tempdata = self.linkData[item.text()] self.host.setText(tempdata['hostname']) self.port.setText(str(tempdata['port'])) self.remark.setText(tempdata['remark']) if tempdata['username'] == 'anonymous': self.anonymousLogin.setCheckState(Qt.Checked) else: self.anonymousLogin.setCheckState(Qt.Unchecked) self.userName.setText(tempdata['username']) self.passwd.setText(tempdata['passwd']) def quickLinkCheckBoxChanged(self): if self.anonymousLoginCheckBox.checkState() == Qt.Checked: self.userNameInput.setText('anonymous') self.passwdInput.setText('') self.userNameInput.setEnabled(False) self.passwdInput.setEnabled(False) elif self.anonymousLoginCheckBox.checkState() == Qt.Unchecked: self.userNameInput.setText('') self.passwdInput.setText('') self.userNameInput.setEnabled(True) self.passwdInput.setEnabled(True) def linkManageCheckBoxChanged(self): if self.anonymousLogin.checkState() == Qt.Checked: self.userName.setText('anonymous') self.passwd.setText('') self.userName.setEnabled(False) self.passwd.setEnabled(False) elif self.anonymousLogin.checkState() == Qt.Unchecked: self.userName.setText('') self.passwd.setText('') self.userName.setEnabled(True) self.passwd.setEnabled(True) def aNewConnection(self, host, username, passwd, port): if self.FTP != None: try: self.FTP.quit() except AttributeError: pass except EOFError: pass self.FTP = myFtp() self.FTP.set_pasv(True) self.connectionNow['hostname'] = host self.connectionNow['username'] = username self.connectionNow['passwd'] = passwd self.connectionNow['port'] = port self.FTP.connect(host, port) self.FTP.login(username, passwd) self.serverFileInfo = [] self.serverPath = '/' self.serverFileTable.clear() self.serverFileTree.clear() self.serverFileTreeRoot = QTreeWidgetItem(self.serverFileTree) self.serverFileTreeRoot.setText(0, '/') self.serverFileTree.addTopLevelItem(self.serverFileTreeRoot) self.Mutex.acquire() fileinfo = self.FTP.getdirinfo(self.serverPath) for i in fileinfo: if i[1] == 'Folder': node = QTreeWidgetItem(self.serverFileTreeRoot) node.setText(0, i[0]) tempinfo = self.FTP.getdirinfo(self.serverPath + i[0]) for j in tempinfo: if j[1] == 'Folder': tempnode = QTreeWidgetItem(node) tempnode.setText(0, j[0]) node.addChild(tempnode) self.serverFileTreeRoot.addChild(node) self.Mutex.release() self.serverFileTreeRoot.setExpanded(True) self.serverFileTableRefresh() if self.timer.isActive(): self.timer.disconnect() self.timer.timeout.connect(self.taskQueueRefresh) self.timer.start(500) def reconnect(self): self.FTP.connect(self.connectionNow['hostname'], self.connectionNow['port']) self.FTP.login(self.connectionNow['username'], self.connectionNow['passwd']) def taskQueueRefresh(self): self.lock.acquire() try: self.taskQueueTreeWidget.clear() if len(self.downloadingTask) != 0: node = QTreeWidgetItem(self.taskQueueTreeWidget) node.setText(0, self.downloadingTask[0]['filename']) node.setText(1, self.downloadingTask[0]['localpath']) node.setText(2, self.downloadingTask[0]['direction']) node.setText(3, self.downloadingTask[0]['serverpath']) node.setText(4, self.downloadingTask[0]['filesize']) node.setText(5, '正在传输') self.taskQueueTreeWidget.addTopLevelItem(node) for i in self.waitingTaskQueue: node = QTreeWidgetItem(self.taskQueueTreeWidget) node.setText(0, i['filename']) node.setText(1, i['localpath']) node.setText(2, i['direction']) node.setText(3, i['serverpath']) node.setText(4, i['filesize']) node.setText(5, '等待传输') self.taskQueueTreeWidget.addTopLevelItem(node) for i in self.finishedTaskQueue: node = QTreeWidgetItem(self.taskQueueTreeWidget) node.setText(0, i['filename']) node.setText(1, i['localpath']) node.setText(2, i['direction']) node.setText(3, i['serverpath']) node.setText(4, i['filesize']) node.setText(5, '传输完成') self.taskQueueTreeWidget.addTopLevelItem(node) finally: self.lock.release() def tableRefresh(self): if self.connectionNow['hostname'] != '': self.serverFileTable.clear() self.serverFileInfo.clear() if self.serverPath != '/': node = QTreeWidgetItem(self.serverFileTable) node.setText(0, '..') self.serverFileInfo.append(node) self.serverFileTableRefresh() self.localFileRefesh() def traversalLocalDir(self, dir, localpath, serverpath): fs = os.listdir(dir) for i in fs: temppath = os.path.join(dir, i) if os.path.isdir(temppath) == False: if serverpath == '/' and localpath == '/': print(i + ' ' + dir + ' ' + dir + ' ' + str(os.path.getsize(temppath))) self.waitingTaskQueue.append({ 'filename': i, 'localpath': dir, 'direction': '-->', 'serverpath': dir, 'filesize': str(os.path.getsize(temppath)) }) elif serverpath == '/': self.waitingTaskQueue.append({ 'filename': i, 'localpath': dir, 'direction': '-->', 'serverpath': dir[len(localpath):], 'filesize': str(os.path.getsize(temppath)) }) print(i + ' ' + dir + ' ' + dir[len(localpath):] + ' ' + str(os.path.getsize(temppath))) elif localpath == '/': self.waitingTaskQueue.append({ 'filename': i, 'localpath': dir, 'direction': '-->', 'serverpath': serverpath + dir, 'filesize': str(os.path.getsize(temppath)) }) print(i + ' ' + dir + ' ' + serverpath + dir + ' ' + str(os.path.getsize(temppath))) else: self.waitingTaskQueue.append({ 'filename': i, 'localpath': dir, 'direction': '-->', 'serverpath': serverpath + dir[len(localpath):], 'filesize': str(os.path.getsize(temppath)) }) print(i + ' ' + dir + ' ' + serverpath + dir[len(localpath):] + ' ' + str(os.path.getsize(temppath))) else: if serverpath == '/' and localpath == '/': print(i + ' ' + temppath + ' ' + temppath) self.createServerDirQueue.append(temppath) elif serverpath == '/': print(i + ' ' + temppath + ' ' + temppath[len(localpath):]) self.createServerDirQueue.append(temppath[len(localpath):]) elif localpath == '/': print(i + ' ' + temppath + ' ' + serverpath + temppath) self.createServerDirQueue.append(serverpath + temppath) else: print(i + ' ' + temppath + ' ' + serverpath + temppath[len(localpath):]) self.createServerDirQueue.append(serverpath + temppath[len(localpath):]) self.traversalLocalDir(temppath, localpath, serverpath) def traversalServerDir(self, dir, localpath, serverpath): fs = self.FTP.getdirinfo(dir) for i in fs: temppath = os.path.join(dir, i[0]) if i[1] == 'File': if serverpath == '/' and localpath == '/': print(i[0] + ' ' + dir + ' ' + dir + ' ' + i[2]) self.waitingTaskQueue.append({ 'filename': i[0], 'localpath': dir, 'direction': '<--', 'serverpath': dir, 'filesize': i[2] }) elif serverpath == '/': self.waitingTaskQueue.append({ 'filename': i[0], 'localpath': localpath + dir, 'direction': '<--', 'serverpath': dir, 'filesize': i[2] }) print(i[0] + ' ' + localpath + dir + ' ' + dir + ' ' + i[2]) elif localpath == '/': self.waitingTaskQueue.append({ 'filename': i[0], 'localpath': dir[len(serverpath):], 'direction': '<--', 'serverpath': dir, 'filesize': i[2] }) print(i[0] + ' ' + dir[len(serverpath):] + ' ' + dir + ' ' + i[2]) else: self.waitingTaskQueue.append({ 'filename': i[0], 'localpath': localpath + dir[len(serverpath):], 'direction': '<--', 'serverpath': dir, 'filesize': i[2] }) print(i[0] + ' ' + localpath + dir[len(serverpath):] + ' ' + dir + ' ' + i[2]) else: if serverpath == '/' and localpath == '/': print(i[0] + ' ' + temppath + ' ' + temppath) os.makedirs(temppath) elif serverpath == '/': print(i[0] + ' ' + localpath + temppath + ' ' + temppath) os.makedirs(localpath + temppath) elif localpath == '/': print(i[0] + ' ' + temppath[len(serverpath):] + ' ' + temppath) os.makedirs(temppath[len(serverpath):]) else: print(i[0] + ' ' + localpath + temppath[len(serverpath):] + ' ' + temppath) os.makedirs(localpath + temppath[len(serverpath):]) self.traversalServerDir(temppath, localpath, serverpath)
class FileReaderPanel(ToolInstance): SESSION_ENDURING = False SESSION_SAVE = False help = "https://github.com/QChASM/SEQCROW/wiki/Model-Manager-Tool" NAME_COL = 0 ID_COL = 1 COORDSETS_COL = 2 NRG_COL = 3 FREQ_COL = 4 def __init__(self, session, name): super().__init__(session, name) self.display_name = "SEQCROW Models" self.tool_window = MainToolWindow(self) self._build_ui() self.fill_tree() self._fr_change = self.session.filereader_manager.triggers.add_handler( FILEREADER_CHANGE, lambda *args: self.fill_tree(*args)) self._add_models = self.session.triggers.add_handler( ADD_MODELS, lambda *args: self.fill_tree(*args)) self._molid_change = self.session.triggers.add_handler( MODEL_ID_CHANGED, lambda *args: self.fill_tree(*args)) self._molname_change = self.session.triggers.add_handler( MODEL_NAME_CHANGED, lambda *args: self.fill_tree(*args)) def _build_ui(self): layout = QGridLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) #TODO: make buttons disabled/enabled if items are selected that don't have the info self.tree = QTreeWidget() self.tree.setSelectionMode(QTreeWidget.ExtendedSelection) self.tree.setHeaderLabels( ["Name", "ID", "movie", "energy", "frequencies"]) self.tree.setUniformRowHeights(True) self.tree.setColumnWidth(self.NAME_COL, 200) layout.addWidget(self.tree, 0, 0, 3, 1) restore_button = QPushButton("restore") restore_button.clicked.connect(self.restore_selected) layout.addWidget(restore_button, 0, 1) nrg_plot_button = QPushButton("energy plot") nrg_plot_button.clicked.connect(self.open_nrg_plot) layout.addWidget(nrg_plot_button, 1, 1) coordset_slider_button = QPushButton("movie slider") coordset_slider_button.clicked.connect(self.open_movie_slider) layout.addWidget(coordset_slider_button, 2, 1) self.tool_window.ui_area.setLayout(layout) self.tool_window.manage(placement="side") def fill_tree(self, *args): item_stack = [self.tree.invisibleRootItem()] self.tree.clear() self._items = [] fr_dict = self.session.filereader_manager.filereader_dict for model in fr_dict.keys(): id = model.id if id is None: continue name = model.name parent = item_stack[0] item = QTreeWidgetItem(parent) item._model = model item_stack.append(item) self._items.append(item) item.setData(self.NAME_COL, Qt.DisplayRole, model) item.setText(self.NAME_COL, name) item.setText(self.ID_COL, ".".join([str(x) for x in id])) if any(x.all_geom is not None and len(x.all_geom) > 1 for x in fr_dict[model]): item.setText(self.COORDSETS_COL, "yes") else: item.setText(self.COORDSETS_COL, "no") if any("energy" in x.other for x in fr_dict[model]): item.setText(self.NRG_COL, "yes") else: item.setText(self.NRG_COL, "no") if any("frequency" in x.other for x in fr_dict[model]): item.setText(self.FREQ_COL, "yes") else: item.setText(self.FREQ_COL, "no") for fr in fr_dict[model]: child = QTreeWidgetItem(item) child.setData(self.NAME_COL, Qt.DisplayRole, fr) child.setText(self.NAME_COL, fr.name) if fr.all_geom is not None and len(fr.all_geom) > 1: child.setText(self.COORDSETS_COL, "yes") else: child.setText(self.COORDSETS_COL, "no") if "energy" in fr.other: child.setText(self.NRG_COL, "%.6f" % fr.other["energy"]) else: child.setText(self.NRG_COL, "") if "frequency" in fr.other: child.setText(self.FREQ_COL, "yes") else: child.setText(self.FREQ_COL, "no") #self.tree.expandItem(item) for i in [ self.ID_COL, self.COORDSETS_COL, self.NRG_COL, self.FREQ_COL ]: self.tree.resizeColumnToContents(i) def restore_selected(self): items = [item for item in self.tree.selectedItems()] model_dict = self.session.filereader_manager.filereader_dict models = list(model_dict.keys()) for item in items: parent = item.parent() mdl = models[self.tree.indexOfTopLevelItem(parent)] if parent is None: fr = model_dict[mdl][-1] else: fr = model_dict[mdl][parent.indexOfChild(item)] fr_rescol = ResidueCollection(fr) fr_rescol.update_chix(mdl) if fr.all_geom is not None and len(fr.all_geom) > 1: coordsets = fr_rescol.all_geom_coordsets(fr) mdl.remove_coordsets() mdl.add_coordsets(coordsets) for i, coordset in enumerate(coordsets): mdl.active_coordset_id = i + 1 for atom, coord in zip(mdl.atoms, coordset): atom.coord = coord mdl.active_coordset_id = 1 def open_nrg_plot(self): items = [item for item in self.tree.selectedItems()] model_dict = self.session.filereader_manager.filereader_dict models = list(model_dict.keys()) for item in items: parent = item.parent() mdl = models[self.tree.indexOfTopLevelItem(parent)] if parent is None: fr = model_dict[mdl][-1] else: fr = model_dict[mdl][parent.indexOfChild(item)] EnergyPlot(self.session, mdl, fr) def open_movie_slider(self): items = [item for item in self.tree.selectedItems()] model_dict = self.session.filereader_manager.filereader_dict models = list(model_dict.keys()) for item in items: parent = item.parent() mdl = models[self.tree.indexOfTopLevelItem(parent)] #coordset doesn't start out with the current coordset id #it looks like it should, but it doesn't #it starts at 1 instead slider = CoordinateSetSlider(self.session, mdl) slider.set_slider(mdl.active_coordset_id) #run(self.session, "coordset slider %s" % mdl.atomspec) def display_help(self): """Show the help for this tool in the help viewer.""" from chimerax.core.commands import run run(self.session, 'open %s' % self.help if self.help is not None else "") def delete(self): """overload delete""" self.session.filereader_manager.triggers.remove_handler( self._fr_change) self.session.triggers.remove_handler(self._add_models) self.session.triggers.remove_handler(self._molid_change) self.session.triggers.remove_handler(self._molname_change) super().delete() def close(self): """overload close""" self.session.filereader_manager.triggers.remove_handler( self._fr_change) self.session.triggers.remove_handler(self._add_models) self.session.triggers.remove_handler(self._molid_change) self.session.triggers.remove_handler(self._molname_change) super().close()
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super().__init__() self.pageInfo = PageInfo("", "") self.setWindowTitle("河北腾云信息科技有限公司") self.icon = QtGui.QIcon() self.icon.addPixmap(QtGui.QPixmap("images/logo.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.setWindowIcon(self.icon) self.treeWidget = QTreeWidget() self.centralWidget = QWidget() self.setCentralWidget(self.centralWidget) self.root = QTreeWidgetItem(self.treeWidget) self.treeWidget.addTopLevelItem(self.root) # self.setStyleSheet(helper.CommonHelper.read_css('./css/style.css')) # 设置列数 self.treeWidget.setColumnCount(1) # 设置树形控件头部的标题 self.treeWidget.setHeaderLabels(['财税大数据平台']) self.treeWidget.setFixedWidth(500) self.rightWidget = QWidget() self.rightLayout = QVBoxLayout() self.dataBox = QGroupBox("数据库连接") self.dataBox.setStyleSheet("QGroupBox{border: 1px solid black;}") self.connectBtn = QPushButton("生成查找树") self.yearLab = QLabel("年份") self.yearEdit = QLineEdit() self.dataLayout = QGridLayout() self.dataLayout.setContentsMargins(10, 20, 10, 15) self.hostLab = QLabel("主机名或ip地址:") self.portLab = QLabel("端口:") self.userLab = QLabel("用户名:") self.passLab = QLabel("密码") self.dbLab = QLabel("数据库名:") self.charsetLab = QLabel("字符集:") self.hostEdit = QLineEdit() self.userEdit = QLineEdit() self.portEdit = QLineEdit() self.passEdit = QLineEdit() self.dbEdit = QLineEdit() self.charsetEdit = QLineEdit() self.hostEdit.setText("192.168.110.201") self.portEdit.setText("3306") self.userEdit.setText("root") self.passEdit.setEchoMode(QLineEdit.Password) self.passEdit.setText("tengyun2020") self.dbEdit.setText("fiscal_tax") self.charsetEdit.setText("utf8") self.yearEdit.setText("2019") self.pageTextInfo = [] self.dataLayout.addWidget(self.hostLab, 0, 0) self.dataLayout.addWidget(self.hostEdit, 0, 1) self.dataLayout.addWidget(self.portLab, 0, 2) self.dataLayout.addWidget(self.portEdit, 0, 3) self.dataLayout.addWidget(self.userLab, 0, 4) self.dataLayout.addWidget(self.userEdit, 0, 5) self.dataLayout.addWidget(self.yearLab, 0, 6) self.dataLayout.addWidget(self.yearEdit, 0, 7) self.dataLayout.addWidget(self.passLab, 1, 0) self.dataLayout.addWidget(self.passEdit, 1, 1) self.dataLayout.addWidget(self.dbLab, 1, 2) self.dataLayout.addWidget(self.dbEdit, 1, 3) self.dataLayout.addWidget(self.charsetLab, 1, 4) self.dataLayout.addWidget(self.charsetEdit, 1, 5) self.dataLayout.addWidget(self.connectBtn, 1, 7, 1, 1) self.dataBox.setLayout(self.dataLayout) self.budgetBox = QGroupBox("预决算信息提取") self.budgetBox.setStyleSheet("QGroupBox{border: 1px solid black;}") self.comboBox = QComboBox() self.comboBox.addItem("一般公共预算收支科目") self.comboBox.addItem("政府性基金预算收支科目") self.comboBox.addItem("国有资本经营预算收支科目") self.comboBox.addItem("社会保险基金预算收支科目") self.comboBox.addItem("支出经济分类科目") # self.comboBox.addItem("一般公共预算收入科目") # self.comboBox.addItem("一般公共预算支出科目") # self.comboBox.addItem("政府性基金预算收入科目") # self.comboBox.addItem("政府性基金预算支出科目") # self.comboBox.addItem("国有资本经营预算收入科目") # self.comboBox.addItem("国有资本经营预算支出科目") # self.comboBox.addItem("社会保险基金预算收入科目") # self.comboBox.addItem("社会保险基金预算支出科目") self.comboBox.setStyleSheet(""" QComboBox {border:none;background:#000000;color:#ffffff; padding-left:30px;font-size:16px "SimHei";} QComboBox QAbstractItemView {background:#000000;color:#ffffff;padding-left:30px;} QComboBox QAbstractItemView::item {min-height:30px;font-size:16px "SimHei";} """) self.comboBox.setView(QListView()) self.pathLab = QLabel("文件路径:") self.extractBtn = QPushButton("提取信息") self.genBtn = QPushButton("生成Excel") self.initBtn = QPushButton("清空文件") self.codeLab = QLabel("科目编码:") self.subLab = QLabel("科目名称:") self.budgetLab = QLabel("预算数:") self.actualLab = QLabel("决算数:") self.pageLab = QLabel("页码:") self.pageInfoBtn = QPushButton("页面信息") self.regxLab = QLabel("分割符:") self.posLab = QLabel("名称位置:") self.targetLab = QLabel("目标文件路径:") self.boundLab = QLabel("pdf无边框表格位置:") self.currentPageLab = QLabel("无边框表格页码:") self.pathEdit = LineEdit() self.codeEdit = QLineEdit() self.subEdit = QLineEdit() self.budgetEdit = QLineEdit() self.actualEdit = QLineEdit() self.pageEdit = QLineEdit() self.regxEdit = QLineEdit() self.posEdit = QLineEdit() self.targetEdit = LineEdit() self.boundEdit = QLineEdit() self.currentPageEdit = QLineEdit() current_page_validator = QtGui.QRegExpValidator( QtCore.QRegExp(r"^[1-9]\d*$"), self.currentPageEdit) self.currentPageEdit.setValidator(current_page_validator) page_validator = QtGui.QRegExpValidator( QtCore.QRegExp(r"(^[1-9]\d*)(-[1-9]\d*)||(^[1-9]\d*)(,[1-9]\d*)+"), self.pageEdit) self.pageEdit.setValidator(page_validator) sub_validator = QtGui.QRegExpValidator( QtCore.QRegExp(r"(^[1-9]\d*)(,[1-9]\d*)+"), self.subEdit) self.subEdit.setValidator(sub_validator) code_validator = QtGui.QRegExpValidator( QtCore.QRegExp(r"(^[1-9]\d*)(,[1-9]\d*)+"), self.codeEdit) self.codeEdit.setValidator(code_validator) budget_validator = QtGui.QRegExpValidator( QtCore.QRegExp(r"(^[1-9]\d*)(,[1-9]\d*)+"), self.budgetEdit) self.budgetEdit.setValidator(budget_validator) actual_validator = QtGui.QRegExpValidator( QtCore.QRegExp(r"(^[1-9]\d*)(,[1-9]\d*)+"), self.actualEdit) self.actualEdit.setValidator(actual_validator) bound_validator = QtGui.QRegExpValidator( QtCore.QRegExp(r"(^[0-9]\d*)(,[0-9]\d*)+"), self.boundEdit) self.boundEdit.setValidator(bound_validator) """ self.pathEdit.setText("C:/Users/localhost/Desktop/bb/新乐市2019年政府预算公开附表.xlsx") self.targetEdit.setText("C:/Users/localhost/Desktop/cc") self.subEdit.setText("1") self.codeEdit.setText("") self.budgetEdit.setText("2") self.actualEdit.setText("") self.pageEdit.setText("1") self.boundEdit.setText("0,700,550,0") """ self.checkBtn = QPushButton("查看") self.pathEdit.setObjectName("path") self.targetEdit.setObjectName("target") self.configEdit = QTextEdit() self.configEdit.setStyleSheet(""" QTextEdit{font-size:14px;background:#3D1140;color:#FFFFFF;} """) self.configEdit.setFixedHeight(300) self.json_str = """ { "一般公共预算收支科目":{ "parsed":{ "page":"", "code":"", "budget":"", "actual":"", "type":"" }, "unparsed":{ "page":"", "name":"", "budget":"", "actual":"", "regex":{ "sep":"", "pos":"" }, "type":"" } }, "政府性基金预算收支科目":{ "parsed":{ "page":"", "code":"", "budget":"", "actual":"", "type":"" }, "unparsed":{ "page":"", "name":"", "budget":"", "actual":"", "regex":{ "sep":"", "pos":"" }, "type":"" } }, "国有资本经营预算收支科目":{ "parsed":{ "page":"", "code":"", "budget":"", "actual":"", "type":"" }, "unparsed":{ "page":"", "name":"", "budget":"", "actual":"", "regex":{ "sep":"", "pos":"" }, "type":"" } }, "社会保险基金预算收支科目":{ "parsed":{ "page":"", "code":"", "budget":"", "actual":"", "type":"" }, "unparsed":{ "page":"", "name":"", "budget":"", "actual":"", "regex":{ "sep":"", "pos":"" }, "type":"" } }, "支出经济分类科目":{ "parsed":{ "page":"", "code":"", "budget":"", "actual":"", "type":"" }, "unparsed":{ "page":"", "name":"", "budget":"", "actual":"", "regex":{ "sep":"", "pos":"" }, "type":"" } } } """ self.configEdit.setText( json.dumps(json.loads(self.json_str), indent=4, sort_keys=False, ensure_ascii=False)) self.json = { "一般公共预算收支科目": {}, "政府性基金预算收支科目": {}, "国有资本经营预算收支科目": {}, "社会保险基金预算收支科目": {}, "支出经济分类科目": {} } self.page_settings = QPushButton("") self.budgetLayout = QGridLayout() self.budgetLayout.setContentsMargins(10, 20, 10, 15) self.budgetLayout.addWidget(self.pathLab, 0, 0) self.budgetLayout.addWidget(self.pathEdit, 0, 1, 1, 3) self.budgetLayout.addWidget(self.targetLab, 1, 0) self.budgetLayout.addWidget(self.targetEdit, 1, 1, 1, 3) self.spiderBox = QGroupBox("文件下载") self.urlLab = QLabel("下载页地址:") self.urlEdit = QLineEdit() self.downloadLab = QLabel("文件下载路径:") self.downloadEdit = LineEdit() self.downloadEdit.setObjectName("download") self.downloadBtn = QPushButton("下载当前页文件") self.spiderLayout = QGridLayout() self.spiderLayout.addWidget(self.urlLab, 0, 0) self.spiderLayout.addWidget(self.urlEdit, 0, 1) # self.spiderLayout.addSpacing(20) self.spiderLayout.addWidget(self.downloadLab, 1, 0) self.spiderLayout.addWidget(self.downloadEdit, 1, 1) self.spiderLayout.addWidget(self.downloadBtn, 1, 2) self.spiderBox.setLayout(self.spiderLayout) url_validator = QtGui.QRegExpValidator( QtCore.QRegExp( r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+" ), self.urlEdit) self.urlEdit.setValidator(url_validator) self.budgetLayout.addWidget(self.spiderBox, 3, 0, 4, 4) # self.budgetLayout.addWidget(self.configEdit, 2, 0, 6, 4) self.budgetLayout.addWidget(self.comboBox, 8, 0, 1, 4) self.budgetLayout.addWidget(self.subLab, 9, 0) self.budgetLayout.addWidget(self.subEdit, 9, 1) # self.budgetLayout.addWidget(self.regxLab, 3, 2) # self.budgetLayout.addWidget(self.regxEdit, 3, 3) # self.budgetLayout.addWidget(self.posLab, 4, 2) # self.budgetLayout.addWidget(self.posEdit, 4, 3) self.budgetLayout.addWidget(self.codeLab, 9, 2) self.budgetLayout.addWidget(self.codeEdit, 9, 3) self.budgetLayout.addWidget(self.budgetLab, 10, 0) self.budgetLayout.addWidget(self.budgetEdit, 10, 1) self.budgetLayout.addWidget(self.actualLab, 10, 2) self.budgetLayout.addWidget(self.actualEdit, 10, 3) self.budgetLayout.addWidget(self.pageLab, 11, 0) self.budgetLayout.addWidget(self.pageEdit, 11, 1) self.budgetLayout.addWidget(self.pageInfoBtn, 11, 4) self.budgetLayout.addWidget(self.boundLab, 12, 0) self.budgetLayout.addWidget(self.boundEdit, 12, 1) self.budgetLayout.addWidget(self.currentPageLab, 12, 2) self.budgetLayout.addWidget(self.currentPageEdit, 12, 3) self.budgetLayout.addWidget(self.checkBtn, 12, 4) self.btnWidget = QWidget() self.btnLayout = QHBoxLayout() self.btnLayout.addWidget(self.initBtn) self.btnLayout.addSpacing(20) self.btnLayout.addWidget(self.extractBtn) self.btnLayout.addSpacing(20) self.btnLayout.addWidget(self.genBtn) # self.testBtn = QPushButton("测试") # self.btnLayout.addSpacing(20) # self.btnLayout.addWidget(self.testBtn) self.btnWidget.setLayout(self.btnLayout) self.budgetLayout.addWidget(self.btnWidget, 13, 0, 1, 4) # self.budgetLayout.addWidget(self.initBtn, 10, 0) # self.budgetLayout.addWidget(self.extractBtn, 10, 1) # self.budgetLayout.addWidget(self.genBtn, 10, 3) for i in range(14): self.budgetLayout.setRowMinimumHeight(i, 25) self.budgetBox.setLayout(self.budgetLayout) self.rightLayout.addWidget(self.dataBox) self.rightLayout.addSpacing(20) self.rightLayout.addWidget(self.budgetBox) self.rightLayout.addStretch(1) self.rightWidget.setLayout(self.rightLayout) # 节点全部展开 self.treeWidget.expandAll() self.leftWidget = QWidget() self.leftLayout = QHBoxLayout() self.leftLayout.addWidget(self.treeWidget) self.leftWidget.setLayout(self.leftLayout) self.mainLayout = QHBoxLayout() self.mainLayout.setContentsMargins(0, 0, 0, 0) self.mainLayout.setSpacing(0) self.mainLayout.addStretch(1) self.mainLayout.addWidget(self.leftWidget) self.mainLayout.addStretch(1) self.mainLayout.addWidget(self.rightWidget) self.mainLayout.addStretch(1) self.centralWidget.setLayout(self.mainLayout) self.setMinimumSize(1200, 800) # 查找树 self.tree = MultiTree({ "id": 0, "pid": -1, "name": "政府科目", "code": "0" }) # 预声明子线程 self.initThread = RunThread(self.root, self.tree, "2020") self.downloadThread = DownloadThread("", "") # 子树对应字典 self.tree_dict = {} self.sheet_name_list = [ "一般公共预算收支科目", "政府性基金预算收支科目", "国有资本经营预算收支科目", "社会保险基金预算收支科目", "支出经济分类科目" ] self.connectBtn.clicked.connect(self.on_connect_clicked) self.pathEdit.clicked.connect(self.on_edit_double_clicked) self.checkBtn.clicked.connect(self.on_check_btn_clicked) self.targetEdit.clicked.connect(self.on_edit_double_clicked) self.downloadEdit.clicked.connect(self.on_edit_double_clicked) self.extractBtn.clicked.connect(self.on_extract_clicked) self.initBtn.clicked.connect(self.on_init_btn_clicked) self.genBtn.clicked.connect(self.on_gen_btn_clicked) self.downloadBtn.clicked.connect(self.on_download_btn_clicked) self.pageInfoBtn.clicked.connect(self.on_page_info_btn_clicked) # self.testBtn.clicked.connect(self.find_code_by_name) @pyqtSlot() def on_page_info_btn_clicked(self): if self.pageEdit.text().strip() == "": QMessageBox.information(self, "提示", ' 页码不能为空! ') return page = process_param(self.pageEdit.text().strip()) if len(page) > 0: suffix = self.pathEdit.text().split(".")[-1] if suffix.lower() == "doc" or suffix.lower( ) == "docx" or suffix.lower() == "pdf": pdf = camelot.read_pdf(self.pathEdit.text().strip().replace( "docx", "pdf").replace("doc", "pdf"), flavor='stream', pages=str(page[0])) print(len(pdf)) if len(pdf) > 0: info = "" for i, row in enumerate(pdf[0].df.values.tolist()): info += ",".join(row) + "\n" self.pageInfo = PageInfo(page[0], info) self.pageInfo.setWindowModality(QtCore.Qt.ApplicationModal) self.pageInfo.show() elif suffix.lower() == "xls" or suffix.lower() == "xlsx": wb = xlrd.open_workbook(self.pathEdit.text().strip()) sheet_names = wb.sheet_names() info = "\n" for i, sheet_name in enumerate(sheet_names): info += str(i + 1) + "\t" + sheet_name + "\n" self.pageInfo = PageInfo(page[0], info) self.pageInfo.setWindowModality(QtCore.Qt.ApplicationModal) self.pageInfo.show() else: pass @pyqtSlot() def on_download_btn_clicked(self): if self.urlEdit.text().strip() == "": QMessageBox.information(self, "提示", ' url地址不能为空! ') return if self.downloadEdit.text().strip() == "": QMessageBox.information(self, "提示", ' 文件夹地址不能为空! ') return self.downloadThread = DownloadThread( url=self.urlEdit.text().strip(), path=self.downloadEdit.text().strip()) self.downloadThread.resSignal.connect( lambda _msg: self.on_download_thread(_msg)) self.downloadThread.start() # downloader = DownLoader(timeout=30, url=self.urlEdit.text().strip(), path=self.downloadEdit.text().strip()) # downloader.download_file() # QMessageBox.information(self, "提示", ' 文件下载完成! ') # pass # @staticmethod # def check_excel_is_open(path): # file_name = path.split("\\")[-1] # print(file_name) # print(path) # print(path.replace(file_name, '~$' + file_name)) # if os.path.exists(path.replace(file_name, '~$' + file_name)): # print("True") # return True # print("False") # return False @pyqtSlot() def on_gen_btn_clicked(self): if len(self.json) > 0: # print(os.path.join(self.targetEdit.text().strip(), "预决算.xls")) # if self.check_excel_is_open(os.path.join(self.targetEdit.text().strip().replace("/", "\\"), "预决算.xls")): # QMessageBox.information(self, "提示", ' Excel文件已经打开,请先关闭! ') # return excel_writer = Excel( os.path.join(self.targetEdit.text().strip().replace("/", "\\"), "预决算.xls"), self.sheet_name_list, self.json) try: excel_writer.write_excel() except: QMessageBox.information(self, "提示", ' 写入失败查看是否文件已经打开! ') return QMessageBox.information(self, "提示", ' Json信息写入Excel成功! ') @pyqtSlot() def on_init_btn_clicked(self): try: if self.targetEdit.text().strip() == "" or not os.path.isdir( self.targetEdit.text().strip()): QMessageBox.information(self, "提示", ' 输入不能为空,或者路径有错误! ') else: self.json.clear() for file in os.listdir(self.targetEdit.text().strip().replace( "/", "\\")): # print(os.path.join(self.targetEdit.text().strip().replace("/", "\\"), file)) if os.path.isfile( os.path.join( self.targetEdit.text().strip().replace( "/", "\\"), file)): try: os.remove( os.path.join( self.targetEdit.text().strip().replace( "/", "\\"), file)) except: QMessageBox.information(self, "提示", ' 删除文件失败请查看是否打开! ') return QMessageBox.information(self, "提示", ' 清空文件成功! ') except Exception as e: QMessageBox.information(self, "提示", e) @pyqtSlot() def on_check_btn_clicked(self): # self.find_code_by_name() if self.currentPageEdit.text().strip() == "" or self.pathEdit.text( ).strip() == "": QMessageBox.information(self, "提示", ' 输入不能为空! ') return elif not self.pathEdit.text().strip().endswith(".pdf"): QMessageBox.information(self, "提示", ' 只有PDF文件需要次操作! ') return else: print(self.pathEdit.text().strip().replace('.docx', '.pdf').replace( ".doc", '.pdf')) pdf = camelot.read_pdf(self.pathEdit.text().strip().replace( '.docx', '.pdf').replace(".doc", '.pdf'), flavor='stream', pages=self.currentPageEdit.text().strip()) if pdf: plt = camelot.plot(pdf[0], kind='textedge') plt.show() axis('tight') fig = pylab.gcf() fig.canvas.set_window_title( "第" + self.currentPageEdit.text().strip() + "页表格解析示意图") @pyqtSlot() def on_extract_clicked(self): # print(self.tree_dict) # self.find_code("") # if len(self.tree_dict) == 0: QMessageBox.information(self, "提示", ' 未生成查找树! ') return if self.pathEdit.text().strip() == "" or self.targetEdit.text().strip( ) == "": QMessageBox.information(self, "提示", ' 文件路径和目标路径不能为空! ') return if self.pageEdit.text().strip() == "": QMessageBox.information(self, "提示", ' 页码不能为空! ') return if self.budgetEdit.text().strip() == "" and self.actualEdit.text( ).strip() == "": QMessageBox.information(self, "提示", ' 预算数、决算数不能同时为空! ') return if self.subEdit.text().strip() == "" and self.codeEdit.text().strip( ) == "": QMessageBox.information(self, "提示", ' 科目名称和科目编码不能同时为空! ') return if self.subEdit.text().strip() != "" and self.codeEdit.text().strip( ) != "": QMessageBox.information(self, "提示", ' 科目名称和科目编码不能同时非空! ') return try: process_file(self.pathEdit.text().strip()) suffix = self.pathEdit.text().split(".")[-1] # print(self.pathEdit.text()) page = process_param(self.pageEdit.text().strip()) if suffix.lower() == "doc" or suffix.lower( ) == "docx" or suffix.lower() == "pdf": bound_info = self.boundEdit.text().strip() print(bound_info) if bound_info != "": if bound_info.endswith(","): bound_info = bound_info.rstrip(",") # print(page) if len(page) > 0: # print((list(map(str, page)))) pdf = camelot.read_pdf(self.pathEdit.text().strip(), flavor='stream', pages=','.join( list(map(str, page))), table_areas=[bound_info]) print(len(pdf)) for i in range(len(pdf)): table_list = [] for row_data in pdf[i].df.values.tolist(): table_list.append(row_data) self.parse(table_list) QMessageBox.information(self, "提示", ' 提取信息结束! ') print(self.json) else: """ pdf = pdfplumber.open(self.pathEdit.text().strip()) # print(pdf.pages) # print(len(pdf.pages)) # print(page) for i, _page in enumerate(pdf.pages): if i + 1 in page: table_list = [] print(_page.extract_text()) for pdf_table in _page.extract_tables(): for row in pdf_table: table_list.append(row) print(row) print(table_list) self.parse(table_list) """ if len(page) > 0: # print((list(map(str, page)))) if suffix.lower() == "doc" or suffix.lower() == "docx": pdf = camelot.read_pdf( self.pathEdit.text().strip().replace( "." + suffix, ".pdf"), flavor='stream', pages=','.join(list(map(str, page)))) else: pdf = camelot.read_pdf( self.pathEdit.text().strip(), flavor='stream', pages=','.join(list(map(str, page)))) # print(len(pdf)) for i in range(len(pdf)): table_list = [] for row_data in pdf[i].df.values.tolist(): table_list.append(row_data) self.parse(table_list) QMessageBox.information(self, "提示", ' 提取信息结束! ') elif suffix.lower() == "xls" or suffix.lower() == "xlsx": wb = xlrd.open_workbook(self.pathEdit.text().strip()) sheet_names = wb.sheet_names() for i, sheet_name in enumerate(sheet_names): # print(sheet_name) if i + 1 in page: table = wb.sheet_by_index(i) table_list = [] print(table.nrows) for ii in range(table.nrows): # print(type(table.row_values(ii))) # print(table.row_values(ii)) table_list.append(table.row_values(ii)) print(table_list) self.parse(table_list) QMessageBox.information(self, "提示", ' 提取信息结束! ') except: QMessageBox.information(self, "提示", "提取信息失败请查看输入是否有误!") @pyqtSlot() def find_code_by_name(self): """ Test of new method for finding the code :param name_list: :return: """ pdf = camelot.read_pdf(r'C:\Users\localhost\Desktop\政府性基金预算支出表.pdf', flavor='stream', pages='1') for i in range(len(pdf)): name_list = [] for row_data in pdf[i].df.values.tolist(): name = re.sub(r'\s+', '', row_data[0]) name = name.split("、")[-1] name = name.split(":")[-1] if name.strip() != "": name_list.append(name.strip()) self.tree.prepare_search_name(self.tree_dict.get("政府性基金预算收支科目")) res = [] for name in name_list: res.append(self.tree.search_node_by_name(name, 0.8)) for item in res: print(item) pass def backtracking(self, index, data_list, res=[]): if index == len(data_list) - 1: return if len(data_list[index]) == 0: return for data in data_list: res_temp = res[:] if len(res_temp) == 0: pass for item in res_temp: if data.get("pid") == item.get("id"): pass pass def find_code(self, name_list): """ print(self.comboBox.currentText()) print(self.tree_dict.get(self.comboBox.currentText())) name_list = ["一般公共服务支出", "人大事务", "行政运行", "政协事务", "行政运行", "机关服务", "教育支出", "教育管理事务", "行政运行"] name_list = ["我拉个区", "一般公共服务支出", "援助其他地区支出", "垃圾", "一般公共服务", "国防支出", "公共安全支出", "教育支出"] name_list = ["一般公共服务支出", "援助其他地区支出", "一般公共服务", "国防支出", "公共安全支出", "教育支出"] """ try: self.tree.prepare_search_name( self.tree_dict.get(self.comboBox.currentText())) res = [] mark = "" for i, name in enumerate(name_list): search_res = self.tree.search_name(name) # print(search_res) # res.append(search_res) if len(search_res) == 1: mark = search_res[0] # print(i, search_res[0]) res.append((i, mark)) elif len(search_res) > 1: search_res.sort(key=lambda j: len(j)) for search_item in search_res: if search_item.startswith(mark): # print(i, search_item) mark = search_item res.append((i, mark)) break elif len(search_item) == len(mark): # print(i, search_item) mark = search_item res.append((i, mark)) break return res except Exception as e: QMessageBox.information(self, "提示", e) def parse(self, data_list): # print(data_list) try: if len(data_list) == 0: QMessageBox.information(self, "提示", ' 表格解析失败! ') return budget_num = process_param(self.budgetEdit.text().strip()) actual_num = process_param(self.actualEdit.text().strip()) if self.codeEdit.text().strip() != "": code_num = process_param(self.codeEdit.text().strip()) if (len(budget_num) > 0 and len(budget_num) != len(code_num) ) or (len(actual_num) > 0 and len(actual_num) != len(code_num)): QMessageBox.information(self, "提示", ' 长度不对应! ') return if (len(budget_num) > 0 and budget_num[-1] > len(data_list[0]) ) or (len(actual_num) > 0 and actual_num[-1] > len( data_list[0])) or code_num[-1] > len(data_list[0]): QMessageBox.information(self, "提示", ' 列数越界! ') return for data in data_list: for i in range(len(code_num)): key = re.sub(r'\s+', '', str(data[code_num[i] - 1])) # print(key) if isinstance(data[code_num[i] - 1], float): key = str(int(data[code_num[i] - 1])) if key and key.isdigit(): if len(budget_num) > 0: if self.json.get(self.comboBox.currentText(). strip()).get(key): self.json.get(self.comboBox.currentText( ).strip()).get(key).update( {"预算数": data[budget_num[i] - 1]}) else: self.json.get(self.comboBox.currentText( ).strip()).update({ key: { "预算数": data[budget_num[i] - 1] } }) if len(actual_num) > 0: if self.json.get(self.comboBox.currentText(). strip()).get(key): self.json.get(self.comboBox.currentText( ).strip()).get(key).update( {"决算数": data[actual_num[i] - 1]}) else: self.json.get(self.comboBox.currentText( ).strip()).update({ key: { "决算数": data[actual_num[i] - 1] } }) else: sub_num = process_param(self.subEdit.text().strip()) if (len(budget_num) > 0 and len(budget_num) != len(sub_num) ) or (len(actual_num) > 0 and len(actual_num) != len(sub_num)): QMessageBox.information(self, "提示", ' 长度不对应! ') return if (len(budget_num) > 0 and budget_num[-1] > len(data_list[0]) ) or (len(actual_num) > 0 and actual_num[-1] > len( data_list[0])) or sub_num[-1] > len(data_list[0]): QMessageBox.information(self, "提示", ' 列数越界! ') return name_list = [] for i in range(len(data_list)): row_name = [] for j in range(len(sub_num)): name = re.sub(r'\s+', '', data_list[i][sub_num[j] - 1]) name = name.split("、")[-1] name = name.split(":")[-1] row_name.append(name) name_list.append(row_name) name_array = np.array(name_list) for j in range(len(sub_num)): for index_code in self.find_code(name_array[:, j].tolist()): key = index_code[1] if key.isdigit(): if len(budget_num) > 0: if self.json.get(self.comboBox.currentText(). strip()).get(key): self.json.get(self.comboBox.currentText( ).strip()).get(key).update({ "预算数": data_list[index_code[0]][budget_num[j] - 1] }) else: self.json.get(self.comboBox.currentText( ).strip()).update({ key: { "预算数": data_list[index_code[0]][ budget_num[j] - 1] } }) if len(actual_num) > 0: print("------------>", actual_num[j] - 1, type(actual_num[j])) print("___________", data_list[index_code[0]]) if self.json.get(self.comboBox.currentText(). strip()).get(key): self.json.get(self.comboBox.currentText( ).strip()).get(key).update({ "决算数": data_list[index_code[0]][actual_num[j] - 1] }) else: self.json.get(self.comboBox.currentText( ).strip()).update({ key: { "决算数": data_list[index_code[0]][ actual_num[j] - 1] } }) except Exception as e: QMessageBox.information(self, "提示", e) # def check_input(self, num, budget, actual, data): # if (len(budget) > 0 and len(budget) != len(num)) or ( # len(actual) > 0 and len(actual) != len(num)): # QMessageBox.information(self, "提示", ' 长度不对应! ') # return # if budget[-1] >= len(data[0]) or actual[-1] >= len(data[0]) or num[-1] >= len( # data[0]): # QMessageBox.information(self, "提示", ' 列数越界! ') # return @pyqtSlot() def on_edit_double_clicked(self): if self.sender().objectName() == "path": filepath = QFileDialog.getOpenFileName(self, "请选择文件路径", "/") if filepath: self.pathEdit.setText(filepath[0].strip()) elif self.sender().objectName() == "target": directory = QFileDialog.getExistingDirectory(self, "请选择文件夹路径", "/") if directory: self.targetEdit.setText(directory.strip()) elif self.sender().objectName() == "download": directory = QFileDialog.getExistingDirectory(self, "请选择文件夹路径", "/") if directory: self.downloadEdit.setText(directory.strip()) else: pass @pyqtSlot() def on_connect_clicked(self): print("connect button is clicked!") if self.hostEdit.text().strip() == "" or self.portEdit.text().strip( ) == "" or self.userEdit.text().strip() == "" or self.yearEdit.text( ).strip() == "" or self.passEdit.text().strip( ) == "" or self.dbEdit.text().strip() == "" or self.charsetEdit.text( ).strip() == "": QMessageBox.information(self, "提示", ' 输入不能为空! ') else: year = self.yearEdit.text().strip() self.tree = MultiTree({ "id": 0, "pid": -1, "name": year + "年政府科目", "code": "0" }) self.treeWidget.clear() self.root = QTreeWidgetItem(self.treeWidget) self.root.setText(0, year + "年树状结构生成中...") self.treeWidget.addTopLevelItem(self.root) self.initThread = RunThread(self.root, self.tree, year) self.initThread.resSignal.connect( lambda _year, _tree: self.on_init_thread(_year, _tree)) self.initThread.start() @pyqtSlot() def on_download_thread(self, msg): if msg == "ok": QMessageBox.information(self, "提示", ' 文件下载成功! ') elif msg == "fail": QMessageBox.information(self, "提示", ' 文件下载失败! ') else: pass # QMessageBox.information(self, "提示", msg) @pyqtSlot() def on_init_thread(self, year, tree): self.tree = tree self.root.setText(0, year + "年树状结构") # 遍历树状结构 # self.tree.traverse(self.tree) self.tree_dict.clear() self.generate_tree_dict() self.treeWidget.expandAll() def generate_tree_dict(self): self.tree_dict.update({ "一般公共预算收入": self.tree.tree.children[0].children[0], "一般公共预算支出": self.tree.tree.children[0].children[1], "政府性基金预算收入": self.tree.tree.children[1].children[0], "政府性基金预算支出": self.tree.tree.children[1].children[1], "国有资本经营预算收入": self.tree.tree.children[2].children[0], "国有资本经营预算支出": self.tree.tree.children[2].children[1], "社会保险基金预算收入": self.tree.tree.children[3].children[0], "社会保险基金预算支出": self.tree.tree.children[3].children[1], "支出经济分类科目": self.tree.tree.children[4], "一般公共预算收支科目": self.tree.tree.children[0], "政府性基金预算收支科目": self.tree.tree.children[1], "国有资本经营预算收支科目": self.tree.tree.children[2], "社会保险基金预算收支科目": self.tree.tree.children[3] })
class GuiProjectDetailsContents(QWidget): C_TITLE = 0 C_WORDS = 1 C_PAGES = 2 C_PAGE = 3 C_PROG = 4 def __init__(self, theParent, theProject): QWidget.__init__(self, theParent) self.mainConf = nw.CONFIG self.theParent = theParent self.theProject = theProject self.theTheme = theParent.theTheme self.theIndex = theParent.theIndex self.optState = theProject.optState # Internal self._theToC = [] iPx = self.theTheme.baseIconSize hPx = self.mainConf.pxInt(12) vPx = self.mainConf.pxInt(4) # Contents Tree # ============= self.tocTree = QTreeWidget() self.tocTree.setIconSize(QSize(iPx, iPx)) self.tocTree.setIndentation(0) self.tocTree.setColumnCount(6) self.tocTree.setSelectionMode(QAbstractItemView.NoSelection) self.tocTree.setHeaderLabels( ["Title", "Words", "Pages", "Page", "Progress", ""]) treeHeadItem = self.tocTree.headerItem() treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) treeHeadItem.setTextAlignment(self.C_PAGES, Qt.AlignRight) treeHeadItem.setTextAlignment(self.C_PAGE, Qt.AlignRight) treeHeadItem.setTextAlignment(self.C_PROG, Qt.AlignRight) treeHeader = self.tocTree.header() treeHeader.setStretchLastSection(True) treeHeader.setMinimumSectionSize(hPx) wCol0 = self.mainConf.pxInt( self.optState.getInt("GuiProjectDetails", "widthCol0", 200)) wCol1 = self.mainConf.pxInt( self.optState.getInt("GuiProjectDetails", "widthCol1", 60)) wCol2 = self.mainConf.pxInt( self.optState.getInt("GuiProjectDetails", "widthCol2", 60)) wCol3 = self.mainConf.pxInt( self.optState.getInt("GuiProjectDetails", "widthCol3", 60)) wCol4 = self.mainConf.pxInt( self.optState.getInt("GuiProjectDetails", "widthCol4", 90)) self.tocTree.setColumnWidth(0, wCol0) self.tocTree.setColumnWidth(1, wCol1) self.tocTree.setColumnWidth(2, wCol2) self.tocTree.setColumnWidth(3, wCol3) self.tocTree.setColumnWidth(4, wCol4) self.tocTree.setColumnWidth(5, hPx) # Options # ======= wordsPerPage = self.optState.getInt("GuiProjectDetails", "wordsPerPage", 350) countFrom = self.optState.getInt("GuiProjectDetails", "countFrom", 1) clearDouble = self.optState.getInt("GuiProjectDetails", "clearDouble", True) wordsHelp = ( "Typical word count for a 5 by 8 inch book page with 11 pt font is 350." ) offsetHelp = ("Start counting page numbers from this page.") dblHelp = ( "Assume a new chapter or partition always start on an odd numbered page." ) self.wpLabel = QLabel("Words per page") self.wpLabel.setToolTip(wordsHelp) self.wpValue = QSpinBox() self.wpValue.setMinimum(10) self.wpValue.setMaximum(1000) self.wpValue.setSingleStep(10) self.wpValue.setValue(wordsPerPage) self.wpValue.setToolTip(wordsHelp) self.wpValue.valueChanged.connect(self._populateTree) self.poLabel = QLabel("Count pages from") self.poLabel.setToolTip(offsetHelp) self.poValue = QSpinBox() self.poValue.setMinimum(1) self.poValue.setMaximum(9999) self.poValue.setSingleStep(1) self.poValue.setValue(countFrom) self.poValue.setToolTip(offsetHelp) self.poValue.valueChanged.connect(self._populateTree) self.dblLabel = QLabel("Clear double pages") self.dblLabel.setToolTip(dblHelp) self.dblValue = QSwitch(self, 2 * iPx, iPx) self.dblValue.setChecked(clearDouble) self.dblValue.setToolTip(dblHelp) self.dblValue.clicked.connect(self._populateTree) self.optionsBox = QGridLayout() self.optionsBox.addWidget(self.wpLabel, 0, 0) self.optionsBox.addWidget(self.wpValue, 0, 1) self.optionsBox.addWidget(self.dblLabel, 0, 3) self.optionsBox.addWidget(self.dblValue, 0, 4) self.optionsBox.addWidget(self.poLabel, 1, 0) self.optionsBox.addWidget(self.poValue, 1, 1) self.optionsBox.setHorizontalSpacing(hPx) self.optionsBox.setVerticalSpacing(vPx) self.optionsBox.setColumnStretch(2, 1) # Assemble # ======== self.outerBox = QVBoxLayout() self.outerBox.addWidget(QLabel("<b>Table of Contents</b>")) self.outerBox.addWidget(self.tocTree) self.outerBox.addLayout(self.optionsBox) self.setLayout(self.outerBox) self._prepareData() self._populateTree() return def getColumnSizes(self): """Return the column widths for the tree columns. """ retVals = [ self.tocTree.columnWidth(0), self.tocTree.columnWidth(1), self.tocTree.columnWidth(2), self.tocTree.columnWidth(3), self.tocTree.columnWidth(4), ] return retVals ## # Internal Functions ## def _prepareData(self): """Extract the data for the tree. """ self._theToC = [] self._theToC = self.theIndex.getTableOfContents(2) self._theToC.append(("", 0, "END", 0)) return ## # Slots ## def _populateTree(self): """Set the content of the chapter/page tree. """ dblPages = self.dblValue.isChecked() wpPage = self.wpValue.value() fstPage = self.poValue.value() - 1 pTotal = 0 tPages = 1 theList = [] for _, tLevel, tTitle, wCount in self._theToC: pCount = math.ceil(wCount / wpPage) if dblPages: pCount += pCount % 2 pTotal += pCount theList.append((tLevel, tTitle, wCount, pCount)) pMax = pTotal - fstPage self.tocTree.clear() for tLevel, tTitle, wCount, pCount in theList: newItem = QTreeWidgetItem() if tPages <= fstPage: progPage = numberToRoman(tPages, True) progText = "" else: cPage = tPages - fstPage pgProg = 100.0 * (cPage - 1) / pMax if pMax > 0 else 0.0 progPage = f"{cPage:n}" progText = f"{pgProg:.1f}{nwUnicode.U_THSP}%" newItem.setIcon(self.C_TITLE, self.theTheme.getIcon("doc_h%d" % tLevel)) newItem.setText(self.C_TITLE, tTitle) newItem.setText(self.C_WORDS, f"{wCount:n}") newItem.setText(self.C_PAGES, f"{pCount:n}") newItem.setText(self.C_PAGE, progPage) newItem.setText(self.C_PROG, progText) newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight) newItem.setTextAlignment(self.C_PAGES, Qt.AlignRight) newItem.setTextAlignment(self.C_PAGE, Qt.AlignRight) newItem.setTextAlignment(self.C_PROG, Qt.AlignRight) # Make pages and titles/partitions stand out if tLevel < 2: bFont = newItem.font(self.C_TITLE) if tLevel == 0: bFont.setItalic(True) else: bFont.setBold(True) bFont.setUnderline(True) newItem.setFont(self.C_TITLE, bFont) tPages += pCount self.tocTree.addTopLevelItem(newItem) return
class ImperiumWidget(QWidget): def __init__(self, parent=None): super(ImperiumWidget, self).__init__(parent) # objects, sub-windows self._world = XNovaWorld_instance() self._layout = None self._layout_topbuttons = None self._tree = None self._btn_reload = None # initialization self.setup_ui() def setup_ui(self): self._layout = QVBoxLayout() self.setLayout(self._layout) # create layout for top line of buttons self._layout_topbuttons = QHBoxLayout() self._layout.addLayout(self._layout_topbuttons) # create reload button self._btn_reload = QPushButton(self.tr('Refresh imperium'), self) self._btn_reload.setIcon(QIcon(':i/reload.png')) self._btn_reload.clicked.connect(self.on_btn_refresh_imperium) self._layout_topbuttons.addWidget(self._btn_reload) # finalize top buttons layout self._layout_topbuttons.addStretch() # create tree self._tree = QTreeWidget(self) self._tree.setAnimated(False) self._tree.setExpandsOnDoubleClick(True) self._tree.setHeaderHidden(False) self._tree.setItemsExpandable(True) self._tree.setRootIsDecorated(True) self._tree.setSortingEnabled(False) self._tree.setColumnCount(1) self._tree.setHeaderLabels(['None']) self._layout.addWidget(self._tree) self._tree.show() # called once after full world load def update_planets(self): def additem_helper(item_texts, twi_parent=None, align_flag=0): # align_flag = Qt::AlignLeft / Qt::AlignRight / Qt::AlignHCenter if align_flag == 0: align_flag = Qt.AlignHCenter | Qt.AlignVCenter twi = QTreeWidgetItem(item_texts) for it_col in range(len(item_texts)): if it_col > 0: # void QTreeWidgetItem::setTextAlignment(int column, int alignment) twi.setTextAlignment(it_col, align_flag) if twi_parent is None: self._tree.addTopLevelItem(twi) else: twi_parent.addChild(twi) return True self._tree.clear() # clear the tree first planets = self._world.get_planets() # get planets from the world # # setup header and its labels header_labels = ['-'] for i in range(len(planets)): header_labels.append(planets[i].name) header_labels.append(self.tr('Total')) # last column - totals self._tree.setHeaderLabels(header_labels) # alignment of text in header labels self._tree.header().setDefaultAlignment(Qt.AlignHCenter | Qt.AlignVCenter) # default column widths for i in range(len(planets)): if i < 1: self._tree.setColumnWidth(i, 150) else: self._tree.setColumnWidth(i, 75) # # planets names item_strings = [self.tr('Name')] for pl in planets: item_strings.append(pl.name) additem_helper(item_strings) # # planets coords item_strings = [self.tr('Coords')] for pl in planets: item_strings.append('[{0}:{1}:{2}]'.format(pl.coords.galaxy, pl.coords.system, pl.coords.position)) additem_helper(item_strings) # # planets fields item_strings = [self.tr('Fields')] total_busy = 0 total_fields = 0 for pl in planets: total_busy += pl.fields_busy total_fields = pl.fields_total item_strings.append('{0} / {1}'.format(pl.fields_busy, pl.fields_total)) item_strings.append('{0} / {1}'.format(total_busy, total_fields)) additem_helper(item_strings) # # resources res_root = QTreeWidgetItem([self.tr('Resources')]) item_strings = [self.tr('Metal')] total_res = 0 for pl in planets: total_res += pl.res_current.met item_strings.append('{0}'.format(number_format(pl.res_current.met))) item_strings.append(number_format(total_res)) additem_helper(item_strings, res_root) # item_strings = [self.tr('Crystal')] total_res = 0 for pl in planets: total_res += pl.res_current.cry item_strings.append('{0}'.format(number_format(pl.res_current.cry))) item_strings.append(number_format(total_res)) additem_helper(item_strings, res_root) # item_strings = [self.tr('Deit')] total_res = 0 for pl in planets: total_res += pl.res_current.deit item_strings.append('{0}'.format(number_format(pl.res_current.deit))) item_strings.append(number_format(total_res)) additem_helper(item_strings, res_root) # item_strings = [self.tr('Energy')] total_busy = 0 total_fields = 0 for pl in planets: total_busy += pl.energy.energy_left total_fields += pl.energy.energy_total item_strings.append('{0} / {1}'.format( pl.energy.energy_left, pl.energy.energy_total)) item_strings.append('{0} / {1}'.format(total_busy, total_fields)) additem_helper(item_strings, res_root) # item_strings = [self.tr('Charge')] for pl in planets: item_strings.append('{0}%'.format(pl.energy.charge_percent)) additem_helper(item_strings, res_root) self._tree.addTopLevelItem(res_root) res_root.setExpanded(True) # # resources per hour rph_root = QTreeWidgetItem([self.tr('Production')]) item_strings = [self.tr('Met/h')] total_res = 0 for pl in planets: total_res += pl.res_per_hour.met item_strings.append('{0}'.format(number_format(pl.res_per_hour.met))) item_strings.append(number_format(total_res)) additem_helper(item_strings, rph_root) # item_strings = [self.tr('Cry/h')] total_res = 0 for pl in planets: total_res += pl.res_per_hour.cry item_strings.append('{0}'.format(number_format(pl.res_per_hour.cry))) item_strings.append(number_format(total_res)) additem_helper(item_strings, rph_root) # item_strings = [self.tr('Deit/h')] total_res = 0 for pl in planets: total_res += pl.res_per_hour.deit item_strings.append('{0}'.format(number_format(pl.res_per_hour.deit))) item_strings.append(number_format(total_res)) additem_helper(item_strings, rph_root) self._tree.addTopLevelItem(rph_root) rph_root.setExpanded(True) # # buildings buildings_root = QTreeWidgetItem([self.tr('Buildings')]) item_strings = [self.tr('Metal factory')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.met_factory)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Crystal factory')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.cry_factory)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Deit factory')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.deit_factory)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Solar station')] for pl in planets: # testing: detect solar station level up addstr = '{0}'.format(pl.buildings.solar_station) if pl.is_build_in_progress('Солнечная батарея'): addstr = '{0} -> {1}'.format(pl.buildings.solar_station, pl.buildings.solar_station+1) item_strings.append(addstr) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Nuclear station')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.nuclear_station)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Robotics factory')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.robotics_factory)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Nanites factory')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.nanites_factory)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Shipyard')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.shipyard)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Metal silo')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.met_silo)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Crystal silo')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.cry_silo)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Deit silo')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.deit_silo)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Lab')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.lab)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('TerraFormer')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.terraformer)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Alliance silo')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.alliance_silo)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Rocket silo')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.rocket_silo)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Lunar Base')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.lunar_base)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Lunar Phalanx')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.lunar_phalanx)) additem_helper(item_strings, buildings_root) item_strings = [self.tr('Gates')] for pl in planets: item_strings.append('{0}'.format(pl.buildings.gates)) additem_helper(item_strings, buildings_root) # add/expand self._tree.addTopLevelItem(buildings_root) buildings_root.setExpanded(True) # # defense defense_root = QTreeWidgetItem([self.tr('Defense')]) item_strings = [self.tr('Rocket Launcher')] total_res = 0 for pl in planets: total_res += pl.defense.ru item_strings.append(number_format(pl.defense.ru)) item_strings.append(number_format(total_res)) additem_helper(item_strings, defense_root) # item_strings = [self.tr('Light Laser')] total_res = 0 for pl in planets: total_res += pl.defense.ll item_strings.append(number_format(pl.defense.ll)) item_strings.append(number_format(total_res)) additem_helper(item_strings, defense_root) # item_strings = [self.tr('Heavy Laser')] total_res = 0 for pl in planets: total_res += pl.defense.tl item_strings.append(number_format(pl.defense.tl)) item_strings.append(number_format(total_res)) additem_helper(item_strings, defense_root) # item_strings = [self.tr('Gauss')] total_res = 0 for pl in planets: total_res += pl.defense.gauss item_strings.append(number_format(pl.defense.gauss)) item_strings.append(number_format(total_res)) additem_helper(item_strings, defense_root) # item_strings = [self.tr('Ion')] total_res = 0 for pl in planets: total_res += pl.defense.ion item_strings.append(number_format(pl.defense.ion)) item_strings.append(number_format(total_res)) additem_helper(item_strings, defense_root) # item_strings = [self.tr('Plasma')] total_res = 0 for pl in planets: total_res += pl.defense.plasma item_strings.append(number_format(pl.defense.plasma)) item_strings.append(number_format(total_res)) additem_helper(item_strings, defense_root) # item_strings = [self.tr('Small Dome')] total_res = 0 for pl in planets: total_res += pl.defense.small_dome item_strings.append(number_format(pl.defense.small_dome)) item_strings.append(number_format(total_res)) additem_helper(item_strings, defense_root) # item_strings = [self.tr('Big Dome')] total_res = 0 for pl in planets: total_res += pl.defense.big_dome item_strings.append(number_format(pl.defense.big_dome)) item_strings.append(number_format(total_res)) additem_helper(item_strings, defense_root) # item_strings = [self.tr('Defender Missile')] total_res = 0 for pl in planets: total_res += pl.defense.defender_rocket item_strings.append(number_format(pl.defense.defender_rocket)) item_strings.append(number_format(total_res)) additem_helper(item_strings, defense_root) # item_strings = [self.tr('Attack Missile')] total_res = 0 for pl in planets: total_res += pl.defense.attack_rocket item_strings.append(number_format(pl.defense.attack_rocket)) item_strings.append(number_format(total_res)) additem_helper(item_strings, defense_root) # add/expand self._tree.addTopLevelItem(defense_root) defense_root.setExpanded(True) # # fleet fleet_root = QTreeWidgetItem([self.tr('Fleet')]) item_strings = [self.tr('Small Transport')] total_res = 0 for pl in planets: total_res += pl.ships.mt item_strings.append(number_format(pl.ships.mt)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Big Transport')] total_res = 0 for pl in planets: total_res += pl.ships.bt item_strings.append(number_format(pl.ships.bt)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Light Fighter')] total_res = 0 for pl in planets: total_res += pl.ships.li item_strings.append(number_format(pl.ships.li)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Heavy Fighter')] total_res = 0 for pl in planets: total_res += pl.ships.ti item_strings.append(number_format(pl.ships.ti)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Cruiser')] total_res = 0 for pl in planets: total_res += pl.ships.crus item_strings.append(number_format(pl.ships.crus)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Battleship')] total_res = 0 for pl in planets: total_res += pl.ships.link item_strings.append(number_format(pl.ships.link)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Colonizer')] total_res = 0 for pl in planets: total_res += pl.ships.col item_strings.append(number_format(pl.ships.col)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Refiner')] total_res = 0 for pl in planets: total_res += pl.ships.rab item_strings.append(number_format(pl.ships.rab)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Spy')] total_res = 0 for pl in planets: total_res += pl.ships.spy item_strings.append(number_format(pl.ships.spy)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Bomber')] total_res = 0 for pl in planets: total_res += pl.ships.bomber item_strings.append(number_format(pl.ships.bomber)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Solar Satellite')] total_res = 0 for pl in planets: total_res += pl.ships.ss item_strings.append(number_format(pl.ships.ss)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Destroyer')] total_res = 0 for pl in planets: total_res += pl.ships.unik item_strings.append(number_format(pl.ships.unik)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Death Star')] total_res = 0 for pl in planets: total_res += pl.ships.zs item_strings.append(number_format(pl.ships.zs)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('BattleCruiser')] total_res = 0 for pl in planets: total_res += pl.ships.lk item_strings.append(number_format(pl.ships.lk)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('War Base')] total_res = 0 for pl in planets: total_res += pl.ships.warbase item_strings.append(number_format(pl.ships.warbase)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Corvett')] total_res = 0 for pl in planets: total_res += pl.ships.f_corvett item_strings.append(number_format(pl.ships.f_corvett)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Interceptor')] total_res = 0 for pl in planets: total_res += pl.ships.f_ceptor item_strings.append(number_format(pl.ships.f_ceptor)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Dreadnought')] total_res = 0 for pl in planets: total_res += pl.ships.f_dread item_strings.append(number_format(pl.ships.f_dread)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # item_strings = [self.tr('Corsair')] total_res = 0 for pl in planets: total_res += pl.ships.f_corsair item_strings.append(number_format(pl.ships.f_corsair)) item_strings.append(number_format(total_res)) additem_helper(item_strings, fleet_root) # add/expand self._tree.addTopLevelItem(fleet_root) fleet_root.setExpanded(True) def update_planet_resources(self): res_top_twi = self._tree.topLevelItem(3) # 4th top level item is "Resources" if res_top_twi is None: return res_met_twi = res_top_twi.child(0) # first child is metal res_cry_twi = res_top_twi.child(1) # 2nd is crystal res_deit_twi = res_top_twi.child(2) # 3rd is deit res_en_twi = res_top_twi.child(3) # 4th is energy if (res_met_twi is None) or (res_cry_twi is None) or (res_deit_twi is None) \ or (res_en_twi is None): return planets = self._world.get_planets() # get planets from the world ncolumn = 1 # column #0 is description, planets start at #1 totals = [0, 0, 0] # count total resources for planet in planets: res_met_twi.setText(ncolumn, number_format(int(planet.res_current.met))) res_cry_twi.setText(ncolumn, number_format(int(planet.res_current.cry))) res_deit_twi.setText(ncolumn, number_format(int(planet.res_current.deit))) res_en_twi.setText(ncolumn, '{0} / {1}'.format( planet.energy.energy_left, planet.energy.energy_total)) ncolumn += 1 totals[0] += int(planet.res_current.met) totals[1] += int(planet.res_current.cry) totals[2] += int(planet.res_current.deit) # set values for "totals" column res_met_twi.setText(ncolumn, number_format(totals[0])) res_cry_twi.setText(ncolumn, number_format(totals[1])) res_deit_twi.setText(ncolumn, number_format(totals[2])) @pyqtSlot() def on_btn_refresh_imperium(self): self._world.signal(self._world.SIGNAL_RELOAD_PAGE, page_name='imperium')
class sender(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): MainLayout = QHBoxLayout(self) self.ContentTree = QTreeWidget(self) RightLayout = QGridLayout() MainLayout.addWidget(self.ContentTree) MainLayout.addLayout(RightLayout) Templates_Label = QLabel('Templates') self.Templates_ComboBox = QComboBox() Templates = ['TCP', 'UDP', 'ICMP', 'DNS'] self.Templates_ComboBox.addItems(Templates) NumToSend_Label = QLabel('Num to send') self.NumToSend_SpinBox = QSpinBox() self.NumToSend_SpinBox.setValue(1) Interval_Label = QLabel('Interval') self.Interval_SpinBox = QDoubleSpinBox() self.Interval_SpinBox.setValue(0.0) Thread_Label = QLabel('Threads') self.Thread_SpinBox = QSpinBox() self.Thread_SpinBox.setValue(1) self.Follow_CheckBox = QCheckBox('Follow Stream') Send_Button = QPushButton('Send') Reset_Button = QPushButton('Reset') self.ContentTree.setColumnCount(2) self.ContentTree.setHeaderLabels(['Item', 'Detail']) RightLayout.addWidget(Templates_Label, 0, 0) RightLayout.addWidget(self.Templates_ComboBox, 0, 1) RightLayout.addWidget(NumToSend_Label, 1, 0) RightLayout.addWidget(self.NumToSend_SpinBox, 1, 1) RightLayout.addWidget(Interval_Label, 2, 0) RightLayout.addWidget(self.Interval_SpinBox, 2, 1) RightLayout.addWidget(Thread_Label, 3, 0) RightLayout.addWidget(self.Thread_SpinBox, 3, 1) RightLayout.addWidget(self.Follow_CheckBox, 4, 0) RightLayout.addWidget(Send_Button, 5, 0) RightLayout.addWidget(Reset_Button, 5, 1) RightLayout.setSpacing(15) RightLayout.setContentsMargins(10, 10, 10, 10) Send_Button.clicked.connect(self.SendPacket) Reset_Button.clicked.connect(self.reset_pkt) self.Templates_ComboBox.currentIndexChanged[int].connect( self.initTemplate) self.ContentTree.itemDoubleClicked[QTreeWidgetItem, int].connect( self.on_treeWidgetItem_doubleClicked) self.ContentTree.itemClicked[QTreeWidgetItem, int].connect( self.on_treeWidgetItem_itemClicked) self.initTemplate(0) def initPkt(self): self.ether_dic = { # 'dst': 'ff:ff:ff:ff:ff:ff', # 'src': '00:00:00:00:00:00', # 'type': 0x800 } self.ip_dic = { # 'version': 4, # 'ihl': None, # 'tos': 0x0, # 'len': 0, # 'id': 0, # 'flags': 0, # 'ttl': 64, # 'proto': 'tcp', # 'chksum': None, # 'src': '127.0.0.1', # 'dst': '127.0.0.1' } self.tcp_dic = { # 'sport': 20, # 'dport': 80, # 'seq': 0, # 'ack': 0, # 'dataofs': None, # 'reserved': 0, # 'flags': 2, # 'window': 8192, # 'chksum': None, # 'urgptr': 0, # 'options': [] } self.udp_dic = {} self.icmp_dic = {} self.dns_dic = {} self.data = b'payload' def SendPacket(self): if self.Templates_ComboBox.currentIndex() == 0: pkt = Ether(**self.ether_dic) / IP(**self.ip_dic) / TCP( **self.tcp_dic) / self.data elif self.Templates_ComboBox.currentIndex() == 1: pkt = Ether(**self.ether_dic) / IP(**self.ip_dic) / UDP( **self.udp_dic) / self.data elif self.Templates_ComboBox.currentIndex() == 2: pkt = Ether(**self.ether_dic) / IP(**self.ip_dic) / ICMP( **self.icmp_dic) / self.data elif self.Templates_ComboBox.currentIndex() == 3: pkt = Ether(**self.ether_dic) / IP(**self.ip_dic) / UDP( **self.udp_dic) / DNS(**self.dns_dic) baseNum = self.NumToSend_SpinBox.value() // self.Thread_SpinBox.value() remainderNum = self.NumToSend_SpinBox.value( ) % self.Thread_SpinBox.value() sendNum_list = [baseNum] * self.Thread_SpinBox.value() while remainderNum > 0: sendNum_list[remainderNum] += 1 remainderNum -= 1 tmp_thread = self.Thread_SpinBox.value() self.threadList = [] #avoid being garbage collected by Python. while tmp_thread > 0: thread = senderthread() self.threadList.append(thread) thread.set_send_num(sendNum_list[tmp_thread - 1]) thread.set_interval(self.Interval_SpinBox.value()) thread.set_pkt(pkt) thread.start() tmp_thread -= 1 self.initPkt() def reset_pkt(self): self.initTemplate(self.Templates_ComboBox.currentIndex()) def initTemplate(self, index): self.initPkt() # self.text = FakeOut() if index == 0: pkt = Ether() / IP() / TCP() / b'payload' elif index == 1: pkt = Ether() / IP() / UDP() / b'payload' elif index == 2: pkt = Ether() / IP() / ICMP() / b'payload' elif index == 3: pkt = Ether() / IP() / UDP() / DNS() # old = sys.stdout # sys.stdout = self.text # pkt.show2() # sys.stdout = old # tmp = self.text.str # self.formatString(tmp) self.packetDict = scapy2ordereddict.to_dict(pkt) self.buildTree() # def formatString(self, tmp): # self.final_dict = collections.OrderedDict() # title_pattern = re.compile(r'###[ [a-zA-Z]+ ]###') #abstract titles # tmp_titles = title_pattern.findall(tmp) # self.titles = [] # for title in tmp_titles: # refine_pattern = re.compile(r'###\[ | \]###') # self.titles.append(refine_pattern.sub('', title)) # #print(self.titles) # content_split_pattern = title_pattern #abstract contents # tmp_content = re.split(content_split_pattern, tmp) # self.contents = [i for i in tmp_content if i != ''] # #print(self.contents) # for (title, content) in zip(self.titles, self.contents): # tmp_dict = {} # tmp_lists = re.split(r'\n', content) # tmp_lists = [i.replace(' ', '') for i in tmp_lists if i != ''] # #print(tmp_lists) # for i in tmp_lists: # tmp_item = i.split('=') # #print(tmp_item) # if len(tmp_item) == 2: # tmp_dict[tmp_item[0]] = tmp_item[1] # self.final_dict[title] = tmp_dict # #print(self.final_dict) def buildTree(self): self.ContentTree.clear() self.doubleclicked = False self.lastColumn = 0 for title in self.packetDict.keys(): tree_item = QTreeWidgetItem(self.ContentTree) tree_item.setText(0, title) tree_item.setExpanded(True) detail_dic = self.packetDict[title] for i in detail_dic.keys(): #print(i,detail_dic[i]) leaf = QTreeWidgetItem(tree_item, [i, str(detail_dic[i])]) tree_item.addChild(leaf) leaf.setToolTip(1, str(detail_dic[i])) self.lastItem = tree_item self.ContentTree.addTopLevelItem(tree_item) def on_treeWidgetItem_doubleClicked(self, item, column): if column == 1: self.ContentTree.openPersistentEditor(item, column) self.pretext = item.text(1) self.doubleclicked = True self.lastColumn = column self.lastItem = item def on_treeWidgetItem_itemClicked(self, item, column): if self.lastColumn != column or self.lastItem != item: if self.doubleclicked: self.ContentTree.closePersistentEditor(self.lastItem, self.lastColumn) self.doubleclicked = False if self.pretext != self.lastItem.text(1): parent_title = self.lastItem.parent().text(0) if parent_title == 'Ethernet': self.ether_dic[self.lastItem.text( 0)] = self.lastItem.text(1) elif parent_title == 'IP': self.ip_dic[self.lastItem.text( 0)] = self.lastItem.text(1) elif parent_title == 'UDP': self.udp_dic[self.lastItem.text( 0)] = self.lastItem.text(1) elif parent_title == 'TCP': self.tcp_dic[self.lastItem.text( 0)] = self.lastItem.text(1) elif parent_title == 'ICMP': self.icmp_dic[self.lastItem.text( 0)] = self.lastItem.text(1) elif parent_title == 'DNS': self.dns_dic[self.lastItem.text( 0)] = self.lastItem.text(1) elif parent_title == 'Raw': self.data = self.lastItem.text(1) self.lastColumn = column self.lastItem = item
class TreeSymbolsWidget(QDialog): """ Class of Dialog for Tree Symbols """ dockWidget = pyqtSignal('PyQt_PyObject') undockWidget = pyqtSignal('PyQt_PyObject') def __init__(self, parent=None): super(TreeSymbolsWidget, self).__init__(parent) vbox = QVBoxLayout(self) vbox.setContentsMargins(0, 0, 0, 0) vbox.setSpacing(0) self.tree = QTreeWidget() self.tree.setFrameShape(0) vbox.addWidget(self.tree) self.tree.header().setHidden(True) self.tree.setSelectionMode(self.tree.SingleSelection) self.tree.setAnimated(True) self.tree.header().setHorizontalScrollMode( QAbstractItemView.ScrollPerPixel) self.tree.header().setSectionResizeMode(0, QHeaderView.ResizeToContents) self.tree.header().setStretchLastSection(False) self.actualSymbols = ('', {}) self.docstrings = {} self.collapsedItems = {} self.tree.itemClicked.connect(self._go_to_definition) self.tree.itemActivated.connect(self._go_to_definition) self.tree.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested['const QPoint &'].connect( self._menu_context_tree) # self.connect( # self, # SIGNAL("customContextMenuRequested(const QPoint &)"), # self._menu_context_tree) # self.connect(self, SIGNAL("itemCollapsed(QTreeWidgetItem *)"), # self._item_collapsed) # self.connect(self, SIGNAL("itemExpanded(QTreeWidgetItem *)"), # self._item_expanded) IDE.register_service('symbols_explorer', self) # ExplorerContainer.register_tab(translations.TR_TAB_SYMBOLS, self) def install_tab(self): """ Connect signals for goingdown """ ide = IDE.get_service('ide') ide.goingDown.connect(self.close) def _menu_context_tree(self, point): """Context menu""" index = self.tree.indexAt(point) if not index.isValid(): return menu = QMenu(self) f_all = menu.addAction(translations.TR_FOLD_ALL) u_all = menu.addAction(translations.TR_UNFOLD_ALL) menu.addSeparator() u_class = menu.addAction(translations.TR_UNFOLD_CLASSES) u_class_method = menu.addAction( translations.TR_UNFOLD_CLASSES_AND_METHODS) u_class_attr = menu.addAction( translations.TR_UNFOLD_CLASSES_AND_ATTRIBUTES) menu.addSeparator() # save_state = menu.addAction(self.tr("Save State")) # self.connect(f_all, SIGNAL("triggered()"), # lambda: self.tree.collapseAll()) # self.connect(u_all, SIGNAL("triggered()"), # lambda: self.tree.expandAll()) # self.connect(u_class, SIGNAL("triggered()"), self._unfold_class) # self.connect(u_class_method, SIGNAL("triggered()"), # self._unfold_class_method) # self.connect(u_class_attr, SIGNAL("triggered()"), # self._unfold_class_attribute) # self.connect(save_state, SIGNAL("triggered()"), # self._save_symbols_state) menu.exec_(QCursor.pos()) def _get_classes_root(self): """Return the root of classes""" class_root = None for i in range(self.tree.topLevelItemCount()): item = self.tree.topLevelItem(i) if item.isClass and not item.isClickable: class_root = item break return class_root def _unfold_class(self): """Method to Unfold Classes""" self.tree.collapseAll() classes_root = self._get_classes_root() if not classes_root: return classes_root.setExpanded(True) def _unfold_class_method(self): """Method to Unfold Methods""" self.tree.expandAll() classes_root = self._get_classes_root() if not classes_root: return # for each class! for i in range(classes_root.childCount()): class_item = classes_root.child(i) # for each attribute or functions for j in range(class_item.childCount()): item = class_item.child(j) # METHODS ROOT!! if not item.isMethod and not item.isClickable: item.setExpanded(False) break def _unfold_class_attribute(self): """Method to Unfold Attributes""" self.tree.expandAll() classes_root = self._get_classes_root() if not classes_root: return # for each class! for i in range(classes_root.childCount()): class_item = classes_root.child(i) # for each attribute or functions for j in range(class_item.childCount()): item = class_item.child(j) # ATTRIBUTES ROOT!! if not item.isAttribute and not item.isClickable: item.setExpanded(False) break def _save_symbols_state(self): """Method to Save a persistent Symbols state""" # filename = self.actualSymbols[0] # TODO: persist self.collapsedItems[filename] in QSettings pass def _get_expand(self, item): """ Returns True or False to be used as setExpanded() with the items It method is based on the click that the user made in the tree """ name = self._get_unique_name(item) filename = self.actualSymbols[0] collapsed_items = self.collapsedItems.get(filename, []) can_check = (not item.isClickable) or item.isClass or item.isMethod if can_check and name in collapsed_items: return False return True @staticmethod def _get_unique_name(item): """ Returns a string used as unique name """ # className_Attributes/className_Functions parent = item.parent() if parent: return "%s_%s" % (parent.text(0), item.text(0)) return "_%s" % item.text(0) def update_symbols_tree(self, symbols, filename='', parent=None): """Method to Update the symbols on the Tree""" TIP = "{} {}" if not parent: if filename == self.actualSymbols[0] and \ self.actualSymbols[1] and not symbols: return if symbols == self.actualSymbols[1]: # Nothing new then return return # we have new symbols refresh it self.tree.clear() self.actualSymbols = (filename, symbols) self.docstrings = symbols.get('docstrings', {}) parent = self.tree if 'attributes' in symbols: globalAttribute = ItemTree(parent, [translations.TR_ATTRIBUTES]) globalAttribute.isClickable = False globalAttribute.isAttribute = True globalAttribute.setExpanded(self._get_expand(globalAttribute)) globalAttribute.setToolTip( 0, TIP.format(len(symbols['attributes']), translations.TR_ATTRIBUTES)) for glob in sorted(symbols['attributes']): globItem = ItemTree(globalAttribute, [glob], lineno=symbols['attributes'][glob]) globItem.isAttribute = True # globItem.setIcon( # 0, ui_tools.colored_icon(":img/attr", "#5dade2")) globItem.setIcon(0, QIcon(":img/attr")) globItem.setExpanded(self._get_expand(globItem)) if 'functions' in symbols and symbols['functions']: functionsItem = ItemTree(parent, [translations.TR_FUNCTIONS]) functionsItem.isClickable = False functionsItem.isMethod = True functionsItem.setExpanded(self._get_expand(functionsItem)) functionsItem.setToolTip( 0, TIP.format(len(symbols['functions']), translations.TR_FUNCTIONS)) for func in sorted(symbols['functions']): item = ItemTree(functionsItem, [func], lineno=symbols['functions'][func]['lineno']) tooltip = self.create_tooltip( func, symbols['functions'][func]['lineno']) item.isMethod = True item.setIcon(0, QIcon(":img/function")) # item.setIcon( # 0, ui_tools.colored_icon(":img/function", "#9FA8DA")) item.setToolTip(0, tooltip) item.setExpanded(self._get_expand(item)) self.update_symbols_tree( symbols['functions'][func]['functions'], parent=item) if 'classes' in symbols and symbols['classes']: classItem = ItemTree(parent, [translations.TR_CLASSES]) classItem.isClickable = False classItem.isClass = True classItem.setExpanded(self._get_expand(classItem)) classItem.setToolTip( 0, TIP.format(len(symbols['classes']), translations.TR_CLASSES)) for claz in sorted(symbols['classes']): line_number = symbols['classes'][claz]['lineno'] item = ItemTree(classItem, [claz], lineno=line_number) item.isClass = True tooltip = self.create_tooltip(claz, line_number) item.setToolTip(0, tooltip) # item.setIcon(0, ui_tools.colored_icon(":img/class", "#FFCC80")) # item.setIcon(0, ui_tools.get_icon('class', '#FFCC80')) item.setIcon(0, ui_tools.get_icon('class')) item.setExpanded(self._get_expand(item)) self.update_symbols_tree(symbols['classes'][claz]['members'], parent=item) def _go_to_definition(self, item): """ Takes and item object and goes to definition on the editor """ main_container = IDE.get_service('main_container') if item.isClickable and main_container: main_container.editor_go_to_line(item.lineno - 1) def create_tooltip(self, name, lineno): """Takes a name and line number and returns a tooltip""" doc = self.docstrings.get(lineno, None) if doc is None: doc = '' else: doc = '\n' + doc tooltip = name + doc return tooltip def _item_collapsed(self, item): """When item collapsed""" super(TreeSymbolsWidget, self).collapseItem(item) can_check = (not item.isClickable) or item.isClass or item.isMethod if can_check: n = self._get_unique_name(item) filename = self.actualSymbols[0] self.collapsedItems.setdefault(filename, []) if n not in self.collapsedItems[filename]: self.collapsedItems[filename].append(n) def _item_expanded(self, item): """When item expanded""" super(TreeSymbolsWidget, self).expandItem(item) n = self._get_unique_name(item) filename = self.actualSymbols[0] if n in self.collapsedItems.get(filename, []): self.collapsedItems[filename].remove(n) if not len(self.collapsedItems[filename]): # no more items, free space del self.collapsedItems[filename] def clean(self): """ Reset the tree and reset attributes """ self.tree.clear() self.collapsedItems = {} def reject(self): if self.parent() is None: self.dockWidget.emit(self) def closeEvent(self, event): """On Close event handling""" self.dockWidget.emit(self) # self.emit(SIGNAL("dockWidget(PyQt_PyObject)"), self) event.ignore()
class BolumlemePencere(QWidget): def __init__(self, ebeveyn=None): super(BolumlemePencere, self).__init__(ebeveyn) self.ebeveyn = ebeveyn self.sistemDiski = ["", ""] self.takasDiski = ["", ""] self.seciliDisk = None self.diskler = parted.getAllDevices() disklerWidget = QWidget() disklerLayout = QHBoxLayout() self.disklerAcilirKutu = QComboBox() self.yenileButon = QPushButton(self.tr("Yenile")) self.yenileButon.pressed.connect(self.diskYenile) self.bolumListeKutu = QTreeWidget() self.bolumListeKutu.setColumnCount(4) self.bolumListeKutu.headerItem().setText(0, self.tr("bölüm")) self.bolumListeKutu.headerItem().setText(1, self.tr("kullanım")) self.bolumListeKutu.headerItem().setText(2, self.tr("boyut")) self.bolumListeKutu.headerItem().setText(3, self.tr("format")) # self.bolumListeKutu.headerItem().setText(4, self.tr("bayraklar")) self.bolumListeKutu.headerItem().setText(4, self.tr("bölüm numarası")) self.disklerAcilirKutu.currentIndexChanged.connect(self.diskDegisti) disklerLayout.addWidget(self.disklerAcilirKutu) disklerLayout.addWidget(self.yenileButon) disklerWidget.setLayout(disklerLayout) layout = QVBoxLayout() layout.addWidget(disklerWidget) layout.addWidget(self.bolumListeKutu) lejant = QLabel() lejant.setPixmap(QPixmap(":/gorseller/lejant.png")) lejant.setAlignment(Qt.AlignCenter) layout.addWidget(lejant) self.bolumListeKutu.itemClicked.connect(self.bolumSecildiFonk) self.bolumListeKutu.itemDoubleClicked.connect(self.bolumFormatSecFonk) opWidget = QWidget() opButonlar = QHBoxLayout() self.yeniBolumBtn = QPushButton(self.tr("Yeni Bölüm Ekle")) self.yeniBolumBtn.pressed.connect(self.bolumEkleFonk) self.bolumSilBtn = QPushButton(self.tr("Bölümü Sil")) self.bolumSilBtn.pressed.connect(self.bolumSilFonk) opButonlar.addWidget(self.yeniBolumBtn) opButonlar.addWidget(self.bolumSilBtn) opWidget.setLayout(opButonlar) layout.addWidget(opWidget) self.bolumSilBtn.setEnabled(False) self.setLayout(layout) self.diskYenile() def diskYenile(self): self.disklerAcilirKutu.clear() self.diskler = parted.getAllDevices() for disk in self.diskler: try: if parted.Disk(disk).type == "msdos": self.disklerAcilirKutu.addItem("{} {} GB ({})".format( disk.model, format(disk.getSize(unit="GB"), '.2f'), disk.path), userData=disk.path) except parted.DiskLabelException: disk = parted.freshDisk(disk, "msdos") # CDROM Aygıtları için try: disk.commit() except parted.IOException: pass else: disk = disk.device self.disklerAcilirKutu.addItem("{} {} GB ({})".format( disk.model, format(disk.getSize(unit="GB"), '.2f'), disk.path), userData=disk.path) def diskDegisti(self): if self.disklerAcilirKutu.currentData(): self.aygit = parted.getDevice(self.disklerAcilirKutu.currentData()) self.ebeveyn.disk = parted.Disk(self.aygit) self.bolumListeYenile() def bolumListeYenile(self): self.extended = None self.bolumListeKutu.clear() for bolum in self.ebeveyn.disk.partitions: _bolum = self.bolumBilgi(bolum, "GB") if self.sistemDiski and bolum.path == self.sistemDiski[0]: if self.sistemDiski[1] == "evet": item = self.treeWidgetItemOlustur(_bolum["yol"], self.tr("Sistem Diski"), _bolum["boyut"] + " GB", "ext4", _bolum["bayraklar"], _bolum["no"]) else: item = self.treeWidgetItemOlustur(_bolum["yol"], self.tr("Sistem Diski"), _bolum["boyut"], _bolum["dosyaSis"], _bolum["bayraklar"], _bolum["no"]) elif self.takasDiski and bolum.path == self.takasDiski[0]: item = self.treeWidgetItemOlustur(_bolum["yol"], self.tr("Takas Alanı"), _bolum["boyut"], self.tr("takas"), _bolum["bayraklar"], _bolum["no"]) else: item = self.treeWidgetItemOlustur(_bolum["yol"], "", _bolum["boyut"], _bolum["dosyaSis"], _bolum["bayraklar"], _bolum["no"]) if _bolum["tur"] == parted.PARTITION_NORMAL: item.setIcon(0, QIcon("gorseller/primary.xpm")) elif _bolum["tur"] == parted.PARTITION_EXTENDED: item.setIcon(0, QIcon("gorseller/extended.xpm")) self.extended = item elif _bolum["tur"] == parted.PARTITION_LOGICAL: item.setIcon(0, QIcon("gorseller/logical.xpm")) self.extended.addChild(item) self.extended.setExpanded(True) self.bolumListeKutu.addTopLevelItem(item) for bosBolum in self.ebeveyn.disk.getFreeSpacePartitions(): _toplam = 0 _bolum = self.bolumBilgi(bosBolum, "GB") if float(_bolum["boyut"]) > 1: if _bolum["tur"] == 5: uzatilmisKalan = self.treeWidgetItemOlustur( "", self.tr("Uzatılmış Bölüm Kalan"), _bolum["boyut"], "", "", "ayrilmamis") uzatilmisKalan.setIcon(0, QIcon(":/gorseller/blank.xpm")) self.extended.addChild(uzatilmisKalan) self.extended.setExpanded(True) if _bolum["tur"] == parted.PARTITION_FREESPACE: _toplam = _toplam + float(_bolum["boyut"]) ayrilmamis = self.treeWidgetItemOlustur( "", self.tr("Ayrılmamış Bölüm"), _toplam, "", "", "ayrilmamis") ayrilmamis.setIcon(0, QIcon(":/gorseller/blank.xpm")) self.bolumListeKutu.addTopLevelItem(ayrilmamis) self.bolumListeKutu.resizeColumnToContents(0) self.bolumListeKutu.resizeColumnToContents(1) self.bolumListeKutu.resizeColumnToContents(2) self.bolumListeKutu.resizeColumnToContents(3) def treeWidgetItemOlustur(self, bolum, kullanim, boyut, format, islev, bolumno): item = QTreeWidgetItem() item.setText(0, str(bolum)) item.setText(1, str(kullanim)) item.setText(2, str(boyut) + " GB ") item.setText(3, str(format)) # item.setText(4, str(islev)) item.setText(4, str(bolumno)) return item def bolumSecildiFonk(self, tiklanan): if tiklanan.text(4) != "ayrilmamis": self.bolumSilBtn.setEnabled(True) else: self.bolumSilBtn.setEnabled(False) def bolumFormatSecFonk(self, tiklanan): if tiklanan.text(4) != "ayrilmamis": self.seciliDisk = tiklanan diskOzellikPencere = diskOzellikleriSinif(self) diskOzellikPencere.exec_() if self.sistemDiski[0] != "": self.ebeveyn.kurparam["disk"]["bolum"] = self.sistemDiski[0] self.ebeveyn.kurparam["disk"]["format"] = self.sistemDiski[1] if self.takasDiski[0] != "": self.ebeveyn.kurparam["disk"]["takasbolum"] = self.takasDiski[ 0] else: self.ebeveyn.kurparam["disk"]["takasbolum"] = "" if self.sistemDiski[0] == "": pass elif self.sistemDiski[0] != "" and self.takasDiski[0] == "": QMessageBox.information( self, self.tr("Bilgi"), self. tr("Takas Alanı Belirtmediniz\nTakas alanı ram miktarınızın düşük olduğu durumlarda\nram yerine disk kullanarak işlemlerin devam etmesini sağlar." )) self.ebeveyn.ileriDugme.setDisabled(False) self.bolumListeYenile() elif self.sistemDiski[0] != "" and self.takasDiski[0] != "": if self.sistemDiski[0] == self.takasDiski[0]: QMessageBox.warning( self, self.tr("Hata"), self.takasDiski[0] + self. tr(" diskini hem sistem hem takas için seçtiniz\nAynı diski hem sistem hem takas olarak kullanmazsınız" )) self.ebeveyn.ileriDugme.setDisabled(True) else: self.ebeveyn.ileriDugme.setDisabled(False) self.bolumListeYenile() def bolumEkleFonk(self): if self._en_buyuk_bos_alan(): alan = self._en_buyuk_bos_alan() birincilSayi = len(self.ebeveyn.disk.getPrimaryPartitions()) uzatilmisSayi = ext_count = 1 if self.ebeveyn.disk.getExtendedPartition( ) else 0 parts_avail = self.ebeveyn.disk.maxPrimaryPartitionCount - ( birincilSayi + uzatilmisSayi) if not parts_avail and not ext_count: QMessageBox.warning( self, self.tr("Uyarı"), self. tr("""Eğer dörtten fazla disk bölümü oluşturmak istiyorsanız birincil bölümlerden birini silip uzatılmış bölüm oluşturun. Bu durumda oluşturduğunuz uzatılmış bölümleri işletim sistemi kurmak için kullanamayacağınızı aklınızda bulundurun.""" )) else: if parts_avail: if not uzatilmisSayi and parts_avail > 1: self.bolumOlustur(alan, parted.PARTITION_NORMAL) self.bolumListeYenile() elif parts_avail == 1: self.bolumOlustur(alan, parted.PARTITION_EXTENDED) self.bolumListeYenile() if uzatilmisSayi: ext_part = self.ebeveyn.disk.getExtendedPartition() try: alan = ext_part.geometry.intersect(alan) except ArithmeticError: QMessageBox.critical( self, self.tr("Hata"), self. tr("Yeni disk bölümü oluşturmak için yeterli alan yok ! Uzatılmış bölümün boyutunu arttırmayı deneyiniz." )) else: self.bolumOlustur(alan, parted.PARTITION_LOGICAL) self.bolumListeYenile() def bolumSilFonk(self): if self.bolumListeKutu.currentItem().text(4) != "ayrilmamis": bolumNo = int(self.bolumListeKutu.currentItem().text(4)) for bolum in self.ebeveyn.disk.partitions: if bolum.number == bolumNo: try: self.ebeveyn.disk.deletePartition(bolum) self.bolumListeYenile() except parted.PartitionException: QMessageBox.warning( self, self.tr("Uyarı"), self. tr("Lütfen uzatılmış bölümleri silmeden önce mantıksal bölümleri siliniz." )) self.bolumSilBtn.setDisabled(True) def bolumBilgi(self, bolum, birim): _bolum = {} _bolum["yol"] = bolum.path if birim == "GB": _bolum["boyut"] = format(bolum.getSize(unit=birim), '.2f') else: _bolum["boyut"] = bolum.getSize(unit=birim) _bolum["dosyaSis"] = "Bilinmeyen" if bolum.fileSystem: if bolum.fileSystem.type.startswith('linux-swap'): _bolum["dosyaSis"] = "takas" else: _bolum["dosyaSis"] = bolum.fileSystem.type try: _bolum["bayraklar"] = bolum.getFlagsAsString() except: pass _bolum["no"] = bolum.number _bolum["tur"] = bolum.type return _bolum def _en_buyuk_bos_alan(self): maks_boyut = -1 alan = None alignment = self.aygit.optimumAlignment for _alan in self.ebeveyn.disk.getFreeSpaceRegions(): if _alan.length > maks_boyut and _alan.length > alignment.grainSize: alan = _alan maks_boyut = _alan.length return alan def bolumOlustur(self, alan, bolumTur): if bolumTur == parted.PARTITION_NORMAL or bolumTur == parted.PARTITION_EXTENDED: for bosBolum in self.ebeveyn.disk.getFreeSpacePartitions(): _bolum = self.bolumBilgi(bosBolum, "GB") if _bolum["tur"] == parted.PARTITION_FREESPACE: maksBoyut = float(_bolum["boyut"]) elif bolumTur == bolumTur == parted.PARTITION_LOGICAL: for bosBolum in self.ebeveyn.disk.getFreeSpacePartitions(): _bolum = self.bolumBilgi(bosBolum, "GB") if _bolum["tur"] == 5: maksBoyut = float(_bolum["boyut"]) alignment = self.aygit.optimalAlignedConstraint constraint = self.aygit.getConstraint() data = { 'start': constraint.startAlign.alignUp(alan, alan.start), 'end': constraint.endAlign.alignDown(alan, alan.end), } boyut, ok = QInputDialog().getDouble(self, self.tr('Bölüm oluştur'), self.tr('GB cinsinden boyut:'), min=0.001, value=1, max=maksBoyut, decimals=3) if ok: data["end"] = int(data["start"]) + int( parted.sizeToSectors(float(boyut), "GiB", self.aygit.sectorSize)) try: geometry = parted.Geometry(device=self.aygit, start=int(data["start"]), end=int(data["end"])) partition = parted.Partition( disk=self.ebeveyn.disk, type=bolumTur, geometry=geometry, ) self.ebeveyn.disk.addPartition(partition=partition, constraint=constraint) except (parted.PartitionException, parted.GeometryException, parted.CreateException) as e: # GeometryException accounts for incorrect start/end values (e.g. start < end), # CreateException is raised e.g. when the partition doesn't fit on the disk. # PartedException is a generic error (e.g. start/end values out of range) raise RuntimeError(e.message)
class MainWindow(QMainWindow): def __init__(self, screen_height, screen_width, version, parent=None): super(MainWindow, self).__init__(parent) self.screen_width = screen_width self.screen_height = screen_height self.version = version # basic main window settings self.resize(MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT) self.setWindowTitle('Linguistica {}'.format(self.version)) # lexicon and lexicon tree self.lexicon = None self.lexicon_tree = None self.initialize_lexicon_tree() # set up major display, parameter window, then load main window self.majorDisplay = QWidget() self.parameterWindow = QWidget() self.load_main_window() # 'File' menu and actions select_corpus_action = self.create_action(text='&Select corpus...', slot=self.corpus_dir_dialog, tip='Select a corpus file', shortcut='Ctrl+N') select_wordlist_action = self.create_action(text='&Select wordlist...', slot=self.wordlist_dir_dialog, tip='Select a wordlist file', shortcut='Ctrl+W') run_file_action = self.create_action(text='&Run...', slot=self.run_file, tip='Run the input file', shortcut='Ctrl+D') parameters_action = self.create_action(text='&Parameters...', slot=self.parameters_dialog, tip='Change parameters', shortcut='Ctrl+P') file_menu = self.menuBar().addMenu('&File') file_menu.addAction(select_corpus_action) file_menu.addAction(select_wordlist_action) file_menu.addAction(run_file_action) file_menu.addAction(parameters_action) self.status = self.statusBar() self.status.setSizeGripEnabled(False) self.status.showMessage('No input file loaded. To select one: File --> ' 'Select corpus... or Select wordlist...') def initialize_lexicon_tree(self): self.lexicon_tree = QTreeWidget() self.lexicon_tree.setEnabled(True) self.lexicon_tree.setMinimumWidth(TREEWIDGET_WIDTH_MIN) self.lexicon_tree.setMaximumWidth(TREEWIDGET_WIDTH_MAX) self.lexicon_tree.setMinimumHeight(TREEWIDGET_HEIGHT_MIN) self.lexicon_tree.setHeaderLabel('') self.lexicon_tree.setItemsExpandable(True) # noinspection PyUnresolvedReferences self.lexicon_tree.itemClicked.connect(self.tree_item_clicked) def create_action(self, text=None, slot=None, tip=None, shortcut=None): """ This create actions for the File menu, things like Read Corpus, Rerun Corpus etc """ action = QAction(text, self) if shortcut: action.setShortcut(shortcut) if tip: action.setToolTip(tip) action.setStatusTip(tip) if slot: # noinspection PyUnresolvedReferences action.triggered.connect(slot) if shortcut: # noinspection PyUnresolvedReferences QShortcut(QKeySequence(shortcut), self).activated.connect(slot) return action def _get_filename_from_dialog(self, ftype='input'): self.determine_last_file() if self.last_file_path and self.last_file_type == ftype: open_dir = self.last_file_path else: open_dir = os.getcwd() # noinspection PyTypeChecker,PyCallByClass fname = QFileDialog.getOpenFileName(self, 'Select the {} file'.format(ftype), open_dir) process_all_gui_events() # HACK: fname is supposed to be a string (at least according to the # PyQt5 documentation), but for some reason fname is a tuple. # So we need this hack to make sure that fname is a string of a filename # -- Jackson Lee, 2015/06/22 # update: it's turned out that this behavior is due to compatibility # between PyQt and PySide. The "tuple" behavior is in line with the # newer API2 for PyQt. (PyQt on python 3 uses API2 by default.) # more here: http://srinikom.github.io/pyside-bz-archive/343.html # so perhaps we keep our current hack for now? # -- Jackson Lee, 2015/08/24 if fname and any(fname) and (type(fname) is tuple): return fname[0] else: # if this hack isn't needed somehow... return fname def corpus_dir_dialog(self): """ Pop up the "open a file" dialog and ask for which corpus text file to use """ self.corpus_filename = self._get_filename_from_dialog(ftype='corpus') process_all_gui_events() if type(self.corpus_filename) != str: return # note that self.corpus_filename is an absolute full path self.corpus_name = os.path.basename(self.corpus_filename) self.corpus_stem_name = Path(self.corpus_name).stem self.lexicon = read_corpus(self.corpus_filename) self.initialize_lexicon_tree() self.load_main_window(major_display=QWidget(), parameter_window=QWidget()) process_all_gui_events() self.status.clearMessage() self.status.showMessage( 'Corpus selected: {}'.format(self.corpus_filename)) def wordlist_dir_dialog(self): """ Pop up the "open a file" dialog and ask for which corpus text file to use """ self.corpus_filename = self._get_filename_from_dialog(ftype='wordlist') process_all_gui_events() if type(self.corpus_filename) != str: return # note that self.corpus_filename is an absolute full path self.corpus_name = os.path.basename(self.corpus_filename) self.corpus_stem_name = Path(self.corpus_name).stem self.lexicon = read_wordlist(self.corpus_filename) self.initialize_lexicon_tree() self.load_main_window(major_display=QWidget(), parameter_window=QWidget()) process_all_gui_events() self.status.clearMessage() self.status.showMessage( 'Wordlist selected: {}'.format(self.corpus_filename)) def parameters_dialog(self): if self.lexicon is None: warning = QMessageBox() warning.setIcon(QMessageBox.Warning) warning.setText('Parameters can only be accessed when an input ' 'file is specified.') warning.setWindowTitle('No input file selected') warning.setStandardButtons(QMessageBox.Ok) warning.exec_() return process_all_gui_events() parameters = self.lexicon.parameters() dialog = QDialog() layout = QVBoxLayout() layout.addWidget( QLabel('Filename: {}'.format(Path(self.corpus_filename).name))) file_type = 'Wordlist' if self.lexicon.file_is_wordlist else 'Corpus' layout.addWidget(QLabel('Type: {}'.format(file_type))) grid = QGridLayout() self.parameter_spinboxes = [QSpinBox() for _ in range(len(parameters))] for i, parameter_name in enumerate(sorted(parameters.keys())): self.parameter_spinboxes[i].setObjectName(parameter_name) self.parameter_spinboxes[i].setRange( *PARAMETERS_RANGES[parameter_name]) self.parameter_spinboxes[i].setValue(parameters[parameter_name]) self.parameter_spinboxes[i].setSingleStep(1) # noinspection PyUnresolvedReferences self.parameter_spinboxes[i].valueChanged.connect( self.update_parameter) grid.addWidget(QLabel(parameter_name), i, 0) grid.addWidget(self.parameter_spinboxes[i], i, 1) grid.addWidget(QLabel(PARAMETERS_HINTS[parameter_name]), i, 2) layout.addLayout(grid) reset_button = QPushButton() reset_button.setText('&Reset') # noinspection PyUnresolvedReferences reset_button.clicked.connect(self.reset_parameters) spacer = QWidget() # just for padding in tool_bar spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) tool_bar = QHBoxLayout() tool_bar.addWidget(spacer) # so that the buttons are right-aligned tool_bar.addWidget(reset_button) layout.addLayout(tool_bar) dialog.setLayout(layout) dialog.setWindowTitle('Parameters') dialog.exec_() def reset_parameters(self): self.lexicon.use_default_parameters() for i, (_, value) in \ enumerate(sorted(self.lexicon.parameters().items())): self.parameter_spinboxes[i].setValue(value) def update_parameter(self): for i in range(len(self.lexicon.parameters())): parameter_name, new_value = \ self.parameter_spinboxes[i].objectName(), \ self.parameter_spinboxes[i].value() self.lexicon.change_parameters(**{parameter_name: new_value}) def update_progress(self, progress_text, target_percentage): """ Update the progress dialog. This function is triggered by the "progress_signal" emitted from the linguistica component worker thread. """ self.progressDialog.setLabelText(progress_text) self.progressDialog.setValue(target_percentage) process_all_gui_events() # noinspection PyProtectedMember def run_file(self): if self.lexicon is None: warning = QMessageBox() warning.setIcon(QMessageBox.Warning) warning.setText('No input file is selected.') warning.setWindowTitle('Error') warning.setStandardButtons(QMessageBox.Ok) warning.exec_() return self.status.clearMessage() self.status.showMessage('Running the file {} now...' .format(self.corpus_name)) print('\nInput file in use:\n{}\n'.format(self.corpus_filename), flush=True) # set up the Linguistica components worker # The worker is a QThread. We spawn this thread, and the linguistica # components run on this new thread but not the main thread for the GUI. # This makes the GUI still responsive # while the long and heavy running process of # the Linguistica components is ongoing. self.lxa_worker = LinguisticaWorker(self.lexicon) self.lxa_worker.progress_signal.connect(self.update_progress) # set up progress dialog process_all_gui_events() self.progressDialog = QProgressDialog() self.progressDialog.setRange(0, 100) # it's like from 0% to 100% self.progressDialog.setLabelText('Initializing...') self.progressDialog.setValue(0) # initialize as 0 (= 0%) self.progressDialog.setWindowTitle( 'Processing {}'.format(self.corpus_name)) self.progressDialog.setCancelButton(None) self.progressDialog.resize(400, 100) process_all_gui_events() self.progressDialog.show() # We disable the "cancel" button # Setting up a "cancel" mechanism may not be a good idea, # since it would probably involve killing the linguistica component # worker at *any* point of its processing. # This may have undesirable effects (e.g., freezing the GUI) -- BAD! # make sure all GUI stuff up to this point has been processed before # doing the real work of running the Lxa components process_all_gui_events() # Now the real work begins here! self.lxa_worker.start() process_all_gui_events() self.lexicon = self.lxa_worker.get_lexicon() print('\nAll Linguistica components run for the file', flush=True) self.status.clearMessage() self.status.showMessage('{} processed'.format(self.corpus_name)) self.load_main_window(major_display=QWidget(), parameter_window=QWidget()) self.populate_lexicon_tree() self.update_last_file() process_all_gui_events() # display corpus name (in the tree header label) file_type = 'wordlist' if self.lexicon.file_is_wordlist else 'corpus' header_label = 'File: {}\nFile type: {}\n\n# word types: {:,}\n'.format( self.corpus_name, file_type, self.lexicon.number_of_word_types()) if file_type == 'corpus': header_label += '# word tokens: {:,}\n'.format( self.lexicon.number_of_word_tokens()) self.lexicon_tree.setHeaderLabel(header_label) @staticmethod def ensure_config_dir_exists(): if not os.path.isdir(CONFIG_DIR): os.mkdir(CONFIG_DIR) def determine_last_file(self): self.last_file_path = None self.last_file_type = None self.last_file_encoding = None if not os.path.isfile(CONFIG_LAST_FILE): return with open(CONFIG_LAST_FILE, encoding='utf8') as f: config_last_file = json.load(f) self.last_file_path = config_last_file['last_file_path'] self.last_file_type = config_last_file['last_file_type'] self.last_file_encoding = config_last_file['last_file_encoding'] def update_last_file(self): self.ensure_config_dir_exists() with open(CONFIG_LAST_FILE, 'w', encoding='utf8') as f: if self.lexicon.file_is_wordlist: file_type = 'wordlist' else: file_type = 'corpus' config = {'last_file_path': self.lexicon.file_abspath, 'last_file_type': file_type, 'last_file_encoding': self.lexicon.encoding, } json.dump(config, f) def populate_lexicon_tree(self): self.lexicon_tree.clear() process_all_gui_events() # wordlist ancestor = QTreeWidgetItem(self.lexicon_tree, [WORDLIST]) self.lexicon_tree.expandItem(ancestor) # word ngrams ancestor = QTreeWidgetItem(self.lexicon_tree, [WORD_NGRAMS]) self.lexicon_tree.expandItem(ancestor) for item_str in [BIGRAMS, TRIGRAMS]: item = QTreeWidgetItem(ancestor, [item_str]) self.lexicon_tree.expandItem(item) # signatures ancestor = QTreeWidgetItem(self.lexicon_tree, [SIGNATURES]) self.lexicon_tree.expandItem(ancestor) for item in [SIGS_TO_STEMS, WORDS_TO_SIGS]: self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item])) # tries ancestor = QTreeWidgetItem(self.lexicon_tree, [TRIES]) self.lexicon_tree.expandItem(ancestor) for item in [WORDS_AS_TRIES, SUCCESSORS, PREDECESSORS]: self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item])) # phonology ancestor = QTreeWidgetItem(self.lexicon_tree, [PHONOLOGY]) self.lexicon_tree.expandItem(ancestor) for item in [PHONES, BIPHONES, TRIPHONES]: self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item])) # manifolds ancestor = QTreeWidgetItem(self.lexicon_tree, [MANIFOLDS]) self.lexicon_tree.expandItem(ancestor) for item in [WORD_NEIGHBORS, VISUALIZED_GRAPH]: self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item])) self.status.clearMessage() self.status.showMessage('Navigation tree populated') print('Lexicon navigation tree populated', flush=True) def load_main_window(self, major_display=None, parameter_window=None): """ Refresh the main window for the updated display content (most probably after a click or some event is triggered) """ # get sizes of the three major PyQt objects major_display_size = self.majorDisplay.size() parameter_window_size = self.parameterWindow.size() lexicon_tree_size = self.lexicon_tree.size() if major_display: self.majorDisplay = major_display if parameter_window: self.parameterWindow = parameter_window # apply sizes to the major three objects self.majorDisplay.resize(major_display_size) self.parameterWindow.resize(parameter_window_size) self.lexicon_tree.resize(lexicon_tree_size) # set up: # 1) main splitter (b/w lexicon-tree+parameter window and major display) # 2) minor splitter (b/w lexicon-tree and parameter window) self.mainSplitter = QSplitter(Qt.Horizontal) self.mainSplitter.setHandleWidth(10) self.mainSplitter.setChildrenCollapsible(False) self.minorSplitter = QSplitter(Qt.Vertical) self.minorSplitter.setHandleWidth(10) self.minorSplitter.setChildrenCollapsible(False) self.minorSplitter.addWidget(self.lexicon_tree) self.minorSplitter.addWidget(self.parameterWindow) self.mainSplitter.addWidget(self.minorSplitter) self.mainSplitter.addWidget(self.majorDisplay) self.setCentralWidget(self.mainSplitter) def sig_to_stems_clicked(self, row): signature = self.sig_to_stems_major_table.item(row, 0).text() print(signature) signature = tuple(signature.split(SEP_SIG)) stems = sorted(self.lexicon.signatures_to_stems()[signature]) number_of_stems_per_column = 5 # create a master list of sublists, where each sublist contains k stems # k = number_of_stems_per_column stem_rows = list() stem_row = list() for i, stem in enumerate(stems, 1): stem_row.append(stem) if not i % number_of_stems_per_column: stem_rows.append(stem_row) stem_row = list() if stem_row: stem_rows.append(stem_row) # set up the minor table as table widget sig_to_stems_minor_table = QTableWidget() sig_to_stems_minor_table.horizontalHeader().hide() sig_to_stems_minor_table.verticalHeader().hide() sig_to_stems_minor_table.clear() sig_to_stems_minor_table.setRowCount(len(stem_rows)) sig_to_stems_minor_table.setColumnCount(number_of_stems_per_column) # fill in the minor table for row, stem_row in enumerate(stem_rows): for col, stem in enumerate(stem_row): item = QTableWidgetItem(stem) sig_to_stems_minor_table.setItem(row, col, item) sig_to_stems_minor_table.resizeColumnsToContents() minor_table_title = QLabel('{} (number of stems: {})' .format(SEP_SIG.join(signature), len(stems))) minor_table_widget_with_title = QWidget() layout = QVBoxLayout() layout.addWidget(minor_table_title) layout.addWidget(sig_to_stems_minor_table) minor_table_widget_with_title.setLayout(layout) new_display = QSplitter(Qt.Horizontal) new_display.setHandleWidth(10) new_display.setChildrenCollapsible(False) new_display.addWidget(self.sig_to_stems_major_table) new_display.addWidget(minor_table_widget_with_title) new_display_width = self.majorDisplay.width() / 2 new_display.setSizes( [new_display_width * 0.4, new_display_width * 0.6]) self.load_main_window(major_display=new_display) self.status.clearMessage() self.status.showMessage('{} selected'.format(signature)) def unavailable_for_wordlist(self): self.load_main_window(major_display=QWidget(), parameter_window=QWidget()) self.status.showMessage('') warning = QMessageBox() warning.setIcon(QMessageBox.Warning) warning.setText('Unavailable for a wordlist') warning.setWindowTitle('Error') warning.setStandardButtons(QMessageBox.Ok) warning.exec_() def tree_item_clicked(self, item): """ Trigger the appropriate action when something in the lexicon tree is clicked, and update the major display plus parameter window """ item_str = item.text(0) if item_str in {WORD_NGRAMS, SIGNATURES, TRIES, PHONOLOGY, MANIFOLDS}: return print('loading', item_str, flush=True) self.status.clearMessage() self.status.showMessage('Loading {}...'.format(item_str)) new_display = None new_parameter_window = None if item_str == WORDLIST: new_display = self.create_major_display_table( self.lexicon.word_phonology_dict().items(), key=lambda x: x[1].count, reverse=True, headers=['Word', 'Count', 'Frequency', 'Phones', 'Unigram plog', 'Avg unigram plog', 'Bigram plog', 'Avg bigram plog'], row_cell_functions=[ lambda x: x[0], lambda x: x[1].count, lambda x: x[1].frequency, lambda x: ' '.join(x[1].phones), lambda x: x[1].unigram_plog, lambda x: x[1].avg_unigram_plog, lambda x: x[1].bigram_plog, lambda x: x[1].avg_bigram_plog], cutoff=0) elif item_str == BIGRAMS: if self.lexicon.file_is_wordlist: self.unavailable_for_wordlist() return new_display = self.create_major_display_table( self.lexicon.word_bigram_counter().items(), key=lambda x: x[1], reverse=True, headers=['Bigram', 'Count'], row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]), lambda x: x[1]], cutoff=2000) elif item_str == TRIGRAMS: if self.lexicon.file_is_wordlist: self.unavailable_for_wordlist() return new_display = self.create_major_display_table( self.lexicon.word_trigram_counter().items(), key=lambda x: x[1], reverse=True, headers=['Trigram', 'Count'], row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]), lambda x: x[1]], cutoff=2000) elif item_str == SIGS_TO_STEMS: self.sig_to_stems_major_table = self.create_major_display_table( self.lexicon.signatures_to_stems().items(), key=lambda x: len(x[1]), reverse=True, headers=['Signature', 'Stem count', 'A few stems'], row_cell_functions=[lambda x: SEP_SIG.join(x[0]), lambda x: len(x[1]), lambda x: ', '.join(sorted(x[1])[:2]) + ', ...'], cutoff=0) # noinspection PyUnresolvedReferences self.sig_to_stems_major_table.cellClicked.connect( self.sig_to_stems_clicked) new_display = self.sig_to_stems_major_table elif item_str == WORDS_TO_SIGS: new_display = self.create_major_display_table( self.lexicon.words_to_signatures().items(), key=lambda x: len(x[1]), reverse=True, headers=['Word', 'Signature count', 'Signatures'], row_cell_functions=[lambda x: x[0], lambda x: len(x[1]), lambda x: ', '.join([SEP_SIG.join(sig) for sig in sorted(x[1])])], cutoff=2000) elif item_str == WORDS_AS_TRIES: words = self.lexicon.broken_words_left_to_right().keys() words_to_tries = dict() # key: word (str) # value: tuple of (str, str) # for left-to-right and right-to-left tries for word in words: l_r = ' '.join(self.lexicon.broken_words_left_to_right()[word]) r_l = ' '.join(self.lexicon.broken_words_right_to_left()[word]) words_to_tries[word] = (l_r, r_l) # left-right, right-left new_display = self.create_major_display_table( words_to_tries.items(), key=lambda x: x[0], reverse=False, headers=['Word', 'Reversed word', 'Left-to-right trie', 'Right-to-left trie'], row_cell_functions=[lambda x: x[0], lambda x: x[0][::-1], lambda x: x[1][0], lambda x: x[1][1]], cutoff=0, set_text_alignment=[(3, Qt.AlignRight)]) elif item_str == SUCCESSORS: new_display = self.create_major_display_table( self.lexicon.successors().items(), key=lambda x: len(x[1]), reverse=True, headers=['String', 'Successor count', 'Successors'], row_cell_functions=[lambda x: x[0], lambda x: len(x[1]), lambda x: ', '.join(sorted(x[1]))], cutoff=0) elif item_str == PREDECESSORS: new_display = self.create_major_display_table( self.lexicon.predecessors().items(), key=lambda x: len(x[1]), reverse=True, headers=['String', 'Predecessor count', 'Predecessors'], row_cell_functions=[lambda x: x[0], lambda x: len(x[1]), lambda x: ', '.join(sorted(x[1]))], cutoff=0) elif item_str == PHONES: new_display = self.create_major_display_table( self.lexicon.phone_dict().items(), key=lambda x: x[1].count, reverse=True, headers=['Phone', 'Count', 'Frequency', 'Plog'], row_cell_functions=[lambda x: x[0], lambda x: x[1].count, lambda x: x[1].frequency, lambda x: x[1].plog], cutoff=0) elif item_str == BIPHONES: new_display = self.create_major_display_table( self.lexicon.biphone_dict().items(), key=lambda x: x[1].count, reverse=True, headers=['Biphone', 'Count', 'Frequency', 'Mutual information (MI)', 'Weighted MI'], row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]), lambda x: x[1].count, lambda x: x[1].frequency, lambda x: x[1].MI, lambda x: x[1].weighted_MI], cutoff=0) elif item_str == TRIPHONES: new_display = self.create_major_display_table( self.lexicon.phone_trigram_counter().items(), key=lambda x: x[1], reverse=True, headers=['Triphone', 'Count'], row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]), lambda x: x[1]], cutoff=0) elif item_str == WORD_NEIGHBORS: if self.lexicon.file_is_wordlist: self.unavailable_for_wordlist() return word_to_freq = self.lexicon.word_unigram_counter() new_display = self.create_major_display_table( self.lexicon.words_to_neighbors().items(), key=lambda x: word_to_freq[x[0]], reverse=True, headers=['Word', 'Word count', 'Neighbors'], row_cell_functions=[lambda x: x[0], lambda x: word_to_freq[x[0]], lambda x: ' '.join(x[1])], cutoff=0) elif item_str == VISUALIZED_GRAPH: if self.lexicon.file_is_wordlist: self.unavailable_for_wordlist() return graph_width = self.screen_width - TREEWIDGET_WIDTH_MAX - 50 graph_height = self.screen_height - 70 html_name = 'show_manifold.html' manifold_name = '{}_manifold.json'.format(self.corpus_stem_name) manifold_filename = os.path.join(CONFIG_DIR, manifold_name) print('manifold_filename', manifold_filename) manifold_json_data = json_graph.node_link_data( self.lexicon.neighbor_graph()) json.dump(manifold_json_data, open(manifold_filename, 'w')) viz_html = os.path.join(CONFIG_DIR, html_name) print('viz_html', viz_html) # write the show_manifold html file with open(viz_html, 'w') as f: print(SHOW_MANIFOLD_HTML.format(os.path.dirname(__file__), graph_width, graph_height, manifold_filename), file=f) url = Path(viz_html).as_uri() print('url:', url) new_display = QWebView() new_display.setUrl(QUrl(url)) self.load_main_window(major_display=new_display, parameter_window=new_parameter_window) self.status.clearMessage() self.status.showMessage('{} selected'.format(item_str)) @staticmethod def create_major_display_table(input_iterable, key=lambda x: x, reverse=False, headers=None, row_cell_functions=None, cutoff=0, set_text_alignment=None): """ This is a general function for creating a tabular display for the major display. """ if not input_iterable: print('Warning: input is empty', flush=True) return if not hasattr(input_iterable, '__iter__'): print('Warning: input is not an iterable', flush=True) return number_of_headers = len(headers) number_of_columns = len(row_cell_functions) if number_of_headers != number_of_columns: print('headers and cell functions don\'t match', flush=True) return len_input = len(input_iterable) table_widget = QTableWidget() table_widget.clear() table_widget.setSortingEnabled(False) # set up row count if cutoff and cutoff < len_input: actual_cutoff = cutoff else: actual_cutoff = len_input table_widget.setRowCount(actual_cutoff) # set up column count and table headers table_widget.setColumnCount(number_of_headers) table_widget.setHorizontalHeaderLabels(headers) # fill in the table for row, x in enumerate(double_sorted(input_iterable, key=key, reverse=reverse)): for col, fn in enumerate(row_cell_functions): cell = fn(x) if isinstance(cell, (int, float)): # cell is numeric item = QTableWidgetItem() item.setData(Qt.EditRole, cell) else: # cell is not numeric item = QTableWidgetItem(cell) if set_text_alignment: for align_col, alignment in set_text_alignment: if col == align_col: item.setTextAlignment(alignment) table_widget.setItem(row, col, item) if not row < actual_cutoff: break table_widget.setSortingEnabled(True) table_widget.resizeColumnsToContents() return table_widget
class capture(QWidget): filterApplied = pyqtSignal() def __init__(self): super().__init__() self.initUI() self.packet_list = [] def initUI(self): mainLayout = QVBoxLayout(self) filter_layout = QHBoxLayout() filter_label = QLabel('Filter') self.filter_lineEdit = QLineEdit() filter_apply_btn = QPushButton('Apply') filter_layout.addWidget(filter_label) filter_layout.addWidget(self.filter_lineEdit) filter_layout.addWidget(filter_apply_btn) mainLayout.addLayout(filter_layout) self.filter = '' splitterMain = QSplitter(Qt.Vertical, self) self.QuickView = QTableWidget(splitterMain) #self.QuickView.setUniformItemSizes(True) self.QuickView.setColumnCount(6) self.QuickView.setHorizontalHeaderLabels( ['No.', 'Time', 'Source', 'Destination', 'Protocol', 'Size']) self.QuickView.setColumnWidth(0, 60) self.QuickView.verticalHeader().setVisible(False) self.QuickView.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) # self.QuickView.horizontalHeader().setStretchLastSection(True) self.QuickView.setShowGrid(False) self.QuickView.setSelectionBehavior(QAbstractItemView.SelectRows) self.QuickView.setSelectionMode(QTableWidget.ExtendedSelection) self.QuickView.setEditTriggers(QAbstractItemView.NoEditTriggers) # self.QuickView.setLayoutMode(QListView.Batched) # self.QuickView.setBatchSize(20) self.DetailView = QTreeWidget(splitterMain) self.DetailView.setColumnCount(2) self.DetailView.setHeaderLabels(['Item', 'Detail']) mainLayout.addWidget(splitterMain) bottomLayout = QHBoxLayout() self.start_btn = QPushButton('Start') self.stop_btn = QPushButton('Stop') self.restart_btn = QPushButton('Restart') self.clear_btn = QPushButton('Clear') self.intercept_CheckBox = QCheckBox('Intercept Packets') bottomLayout.addWidget(self.start_btn) bottomLayout.addWidget(self.stop_btn) bottomLayout.addWidget(self.restart_btn) bottomLayout.addWidget(self.clear_btn) bottomLayout.addWidget(self.intercept_CheckBox) bottomLayout.addStretch() self.stop_btn.setEnabled(False) self.restart_btn.setEnabled(False) mainLayout.addLayout(bottomLayout) self.start_btn.clicked.connect(self.start_sniff) # filter_apply_btn.clicked.connect(self.apply_filter) self.stop_btn.clicked.connect(self.stop_sniff) self.QuickView.currentItemChanged.connect(self.show_current_detail) self.restart_btn.clicked.connect(self.restart_sniff) self.clear_btn.clicked.connect(self.clear_widget) self.count = 0 def start_sniff(self): self.cap_thread = capturethread() self.cap_thread.newPkt.connect(self.init_display) self.cap_thread.start() self.start_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.restart_btn.setEnabled(False) def init_display(self, item_list, pkt): self.packet_list.append(pkt) self.QuickView.insertRow(self.QuickView.rowCount()) for i in range(6): self.QuickView.setItem(self.QuickView.rowCount() - 1, i, item_list[i]) # def apply_filter(self): # filter_queue=CaptureQueue.get_filter() # filter_queue.queue.clear() # filter_queue.put(self.filter_lineEdit.text()) # if self.cap_thread.isRunning(): # self.cap_thread.set_stopper(True) # self.cap_thread.wait() # self.cap_thread.start() # filter_pkt_queue=CaptureQueue.get_packet_to_filter() # filter_pkt_queue.put(self.packet_list) # filter_pkt_queue.put(self.filter_lineEdit.text()) # self.filter_thread=filterthread() # self.filter_thread.start() # self.filter_thread.filtered.connect(self.get_filtered_pkt) # def get_filtered_pkt(self,packetlist): # pass # def formatString(self, tmp): # self.final_dict = collections.OrderedDict() # title_pattern = re.compile(r'###[ [a-zA-Z]+ ]###') #abstract titles # tmp_titles = title_pattern.findall(tmp) # self.titles = [] # for title in tmp_titles: # refine_pattern = re.compile(r'###\[ | \]###') # self.titles.append(refine_pattern.sub('', title)) # #print(self.titles) # content_split_pattern = title_pattern #abstract contents # tmp_content = re.split(content_split_pattern, tmp) # self.contents = [i for i in tmp_content if i != ''] # #print(self.contents) # for (title, content) in zip(self.titles, self.contents): # tmp_dict = {} # tmp_lists = re.split(r'\n', content) # tmp_lists = [i.replace(' ', '') for i in tmp_lists if i != ''] # #print(tmp_lists) # for i in tmp_lists: # tmp_item = i.split('=') # #print(tmp_item) # if len(tmp_item) == 2: # tmp_dict[tmp_item[0]] = tmp_item[1] # self.final_dict[title] = tmp_dict # #print(self.final_dict) def buildTree(self): self.DetailView.clear() for title in self.packetDict.keys(): tree_item = QTreeWidgetItem(self.DetailView) tree_item.setText(0, title) tree_item.setExpanded(True) detail_dic = self.packetDict[title] for i in detail_dic.keys(): leaf = QTreeWidgetItem(tree_item, [i, str(detail_dic[i])]) leaf.setToolTip(1, str(detail_dic[i])) tree_item.addChild(leaf) self.DetailView.addTopLevelItem(tree_item) def stop_sniff(self): self.cap_thread.set_stopper(True) self.start_btn.setEnabled(True) self.restart_btn.setEnabled(True) self.stop_btn.setEnabled(False) def restart_sniff(self): self.pkt_queue = CaptureQueue.get_pkt() self.label_queue = CaptureQueue.get_label() with self.label_queue.mutex: self.label_queue.queue.clear() with self.pkt_queue.mutex: self.pkt_queue.queue.clear() self.packet_list.clear() self.QuickView.clearContents() self.DetailView.clear() self.start_sniff() def show_current_detail(self): if self.packet_list: pkt = self.packet_list[self.QuickView.currentRow()] # self.text = FakeOut() # old = sys.stdout # sys.stdout = self.text # packet.show2() # sys.stdout = old # tmp = self.text.str # self.formatString(tmp) self.packetDict = scapy2ordereddict.to_dict(pkt) self.buildTree() def clear_widget(self): self.pkt_queue = CaptureQueue.get_pkt() self.label_queue = CaptureQueue.get_label() with self.label_queue.mutex: self.label_queue.queue.clear() with self.pkt_queue.mutex: self.pkt_queue.queue.clear() self.packet_list.clear() self.QuickView.clearContents() self.DetailView.clear()
class Waterfall(QWidget, waterfall.Ui_Waterfall): general_settings_signal = QtCore.pyqtSignal(list) #send list of plotting params updated_rectangles_signal = QtCore.pyqtSignal(list) #send list of updated artists for redrawing def __init__(self, parent): super(Waterfall,self).__init__(parent) self.setupUi(self) self.test = {'a':'red','b':'green','c':'blue'} #Button functions self.btn_apply_general_settings.clicked.connect(self.send_settings) self.patient_tree = self.create_patient_tree() self.data_viewer_container.addWidget(self.patient_tree) self.get_settings() def get_settings(self): try: with shelve.open('WaterfallSettings') as shelfFile: self.keys_and_colors = shelfFile['keys_and_colors'] shelfFile.close() except: #default self.keys_and_colors = {'CR':,'PR':,'PD':,'SD':,''} def on_waterfall_data_signal(self,signal): self.waterfall_data = signal['waterfall_data'] #pandas dataframe def on_generated_rectangles_signal(self,signal): self.rectangles_received = signal[0] self.add_items() #display in table def send_settings(self,signal): self.list_general_settings = [ self.plot_title.text(), self.x_label.text(), self.y_label.text(), self.twenty_percent_line.isChecked(), self.thirty_percent_line.isChecked(), self.zero_percent_line.isChecked(), self.display_responses_as_text.isChecked() ] self.general_settings_signal.emit(self.list_general_settings) def create_patient_tree(self): ''' Create QTreeWidget populated with a patient's data for the DataEntry dialog. Assumes that self.temp_patient is the patient of interest and that the variable belongs to the dialog. ''' self.tree = QTreeWidget() self.root = self.tree.invisibleRootItem() self.headers = [ 'Patient #', 'Best response %', 'Response', 'Cancer', 'Color key', ] self.headers_item = QTreeWidgetItem(self.headers) self.tree.setColumnCount(len(self.headers)) self.tree.setHeaderItem(self.headers_item) self.root.setExpanded(True) self.tree.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.tree.header().setStretchLastSection(False) return self.tree def add_items(self): ''' Populate viewing tree ''' self.tree.clear() #clear prior to entering items, prevent aggregation i=0 for rect in self.rectangles_received: #populate editable tree with rect data self.rect_item = QTreeWidgetItem(self.root) self.rect_params = [ self.waterfall_data['Patient number'][i], rect.get_height(), self.waterfall_data['Overall response'][i], self.waterfall_data['Cancer'][i] ] for col in range(0,4): self.rect_item.setText(col,str(self.rect_params[col])) self.rect_item.setTextAlignment(col,4) self.tree.setItemWidget(self.rect_item, 4, CustomCombo(self,self.test)) self.rect_item.setFlags(self.rect_item.flags() | QtCore.Qt.ItemIsEditable) i+=1 def on_updated_tree_item(self): #update the rectangle which was edited pass
class CueListDialog(QDialog): def __init__(self, **kwargs): super().__init__(**kwargs) self.setMinimumSize(400, 300) self._cues = [] self.list = QTreeWidget(self) self.list.setSelectionMode(self.list.SingleSelection) self.list.setSelectionBehavior(self.list.SelectRows) self.list.setAlternatingRowColors(True) self.list.setIndentation(0) self.list.setHeaderLabels(['Index', 'Name']) self.list.header().setSectionResizeMode(QHeaderView.Fixed) self.list.header().setSectionResizeMode(1, QHeaderView.Stretch) self.list.header().setStretchLastSection(False) self.setLayout(QVBoxLayout()) self.layout().addWidget(self.list) self.buttons = QDialogButtonBox(self) self.buttons.addButton(QDialogButtonBox.Cancel) self.buttons.addButton(QDialogButtonBox.Ok) self.layout().addWidget(self.buttons) self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) def add_cue(self, cue): try: item = QTreeWidgetItem() item.setTextAlignment(0, Qt.AlignCenter) for n, prop in enumerate(['index', 'name']): item.setText(n, str(cue.properties().get(prop, 'Undefined'))) self._cues.append(cue) self.list.addTopLevelItem(item) except Exception: pass def add_cues(self, cues): for cue in cues: self.add_cue(cue) def remove_cue(self, index): self.list.takeTopLevelItem(index) return self._cues.pop(index) def reset(self): self.list.clear() self._cues.clear() def selected_cues(self): cues = [] for item in self.list.selectedItems(): index = self.list.indexOfTopLevelItem(item) cues.append(self._cues[index]) return cues
class Waterfall(QWidget, waterfall.Ui_Waterfall): plot_settings_signal = QtCore.pyqtSignal(list) #send list of plotting params updated_rectangles_signal = QtCore.pyqtSignal(list) #send list of updated artists for redrawing def __init__(self, parent): super(Waterfall,self).__init__(parent) self.setupUi(self) self.get_settings() self.send_settings() #Button functions self.btn_apply_general_settings.clicked.connect(self.send_settings) self.btn_apply_keys_and_colors_settings.clicked.connect(self.send_settings) self.patient_tree = self.create_patient_tree() self.data_viewer_container.addWidget(self.patient_tree) self.btn_color_test.clicked.connect(self.get_color) def get_color(self): self.color = QColorDialog.getColor() #returns a color object print(color) def get_settings(self): try: with shelve.open('WaterfallSettings') as shelfFile: self.keys_and_colors = shelfFile['keys_and_colors'] shelfFile.close() except: #set and use default settings self.keys_and_colors = { 'CR':'#03945D', 'PR':'#B1EE97', 'PD':'#FF6F69', 'SD':'#707070'} with shelve.open('WaterfallSettings') as shelfFile: shelfFile['keys_and_colors'] = self.keys_and_colors shelfFile.close() def on_waterfall_data_signal(self,signal): self.waterfall_data = signal['waterfall_data'] #pandas dataframe def on_generated_rectangles_signal(self,signal): self.rectangles_received = signal[0] self.add_items() #display in table self.btn_apply_general_settings.setEnabled(True) self.btn_finalize_plot.setEnabled(True) self.btn_apply_keys_and_colors_settings.setEnabled(True) def send_settings(self): ''' Emit both general plot settings, and color labeling settings. These are the settings to be used when the plot is created. ''' self.general_settings = [ self.plot_title.text(), self.x_label.text(), self.y_label.text(), [self.twenty_percent_line.isChecked(), self.thirty_percent_line.isChecked(), self.zero_percent_line.isChecked()], [self.display_responses_as_text.isChecked(), self.display_responses_as_color.isChecked(), self.display_no_responses.isChecked()], self.include_table.isChecked(), self.show_cancer_type.isChecked(), self.updated_color_coding ] self.plot_settings_signal.emit(self.general_settings) def get_updating_color_coding(self): def create_patient_tree(self): ''' Create QTreeWidget populated with a patient's data for the DataEntry dialog. Assumes that self.temp_patient is the patient of interest and that the variable belongs to the dialog. ''' self.tree = QTreeWidget() self.root = self.tree.invisibleRootItem() self.headers = [ 'Patient #', 'Best response %', 'Response', 'Cancer', 'Color key', ] self.headers_item = QTreeWidgetItem(self.headers) self.tree.setColumnCount(len(self.headers)) self.tree.setHeaderItem(self.headers_item) self.root.setExpanded(True) self.tree.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.tree.header().setStretchLastSection(False) return self.tree def add_items(self): ''' Populate viewing tree ''' self.tree.clear() #clear prior to entering items, prevent aggregation i=0 for rect in self.rectangles_received: #populate editable tree with rect data self.rect_item = QTreeWidgetItem(self.root) self.rect_params = [ self.waterfall_data['Patient number'][i], rect.get_height(), self.waterfall_data['Overall response'][i], self.waterfall_data['Cancer'][i] ] for col in range(0,4): self.rect_item.setText(col,str(self.rect_params[col])) self.rect_item.setTextAlignment(col,4) self.tree.setItemWidget(self.rect_item, 4, CustomCombo(self,self.keys_and_colors,self.waterfall_data['Overall response'][i])) self.rect_item.setFlags(self.rect_item.flags() | QtCore.Qt.ItemIsEditable) i+=1 def on_updated_tree_item(self): #update the rectangle which was edited pass class WaterfallPlotter(QWidget): generated_rectangles_signal = QtCore.pyqtSignal(list) #send list of rects for data display in tree def __init__(self,parent): super(WaterfallPlotter,self).__init__(parent) self.get_settings() self.settings_update = False self.figure = plt.figure() self.canvas = FigureCanvas(self.figure) self.toolbar = NavigationToolbar(self.canvas,self) self.btn_plot = QPushButton('Default Plot') self.btn_plot.clicked.connect(self.default_plot) self.layout = QVBoxLayout() self.layout.addWidget(self.toolbar) self.layout.addWidget(self.canvas) self.layout.addWidget(self.btn_plot) self.setLayout(self.layout) def on_waterfall_data_signal(self,signal): self.waterfall_data = signal['waterfall_data'] #pandas dataframe self.btn_plot.setEnabled(True) def get_settings(self): try: with shelve.open('WaterfallSettings') as shelfFile: self.keys_and_colors = shelfFile['keys_and_colors'] shelfFile.close() except: #set and use default settings self.keys_and_colors = { 'CR':'#03945D', 'PR':'#B1EE97', 'PD':'#FF6F69', 'SD':'#707070'} with shelve.open('WaterfallSettings') as shelfFile: shelfFile['keys_and_colors'] = self.keys_and_colors shelfFile.close() def on_general_settings_signal(self,signal): self.gen_settings = signal self.settings_update = True self.default_plot() def get_bar_colors(self,responses): return [self.keys_and_colors[x] for x in responses] def default_plot(self): ''' Plot waterfall data ''' self.figure.clear() self.rect_locations = np.arange(len(self.waterfall_data['Best response percent change'])) self.ax = self.figure.add_subplot(111) self.bar_colors = self.get_bar_colors(self.waterfall_data['Overall response']) if self.settings_update == False: self.ax.tick_params( axis='x', # changes apply to the x-axis which='both', # both major and minor ticks are affected bottom='on', # ticks along the bottom edge are off top='on', # ticks along the top edge are off labelbottom='on' ) # labels along the bottom edge are off self.ax.axhline(y=20, linestyle='--', c='k', alpha=0.5, lw=2.0, label='twenty_percent') self.ax.axhline(y=-30, linestyle='--', c='k', alpha=0.5, lw=2.0, label='thirty_percent') self.ax.axhline(y=0, c='k', alpha=1, lw=2.0, label='zero_percent') self.ax.grid(color = 'k', axis = 'y', alpha=0.25) self.rects = self.ax.bar(self.rect_locations, self.waterfall_data['Best response percent change'], color=self.bar_colors) else: #settings were updated, we received them and stored in variable self.gen_settings self.ax.set_title(self.gen_settings[0]) self.ax.set_xlabel(self.gen_settings[1]) self.ax.set_ylabel(self.gen_settings[2]) if self.gen_settings[3][0]: self.ax.axhline(y=20, linestyle='--', c='k', alpha=0.5, lw=2.0, label='twenty_percent') if self.gen_settings[3][1]: self.ax.axhline(y=-30, linestyle='--', c='k', alpha=0.5, lw=2.0, label='thirty_percent') if self.gen_settings[3][2]: self.ax.axhline(y=0, c='k', alpha=1, lw=2.0, label='zero_percent') if self.gen_settings[4][0] and ~self.gen_settings[6]: #show responses as labels, default color bars #legend depends on user specified keys self.rects = self.ax.bar(self.rect_locations, self.waterfall_data['Best response percent change']) self.add_labels(self.ax, self.rects, self.waterfall_data, 1) elif self.gen_settings[4][1]: #color bars with response type self.rects = self.ax.bar(self.rect_locations, self.waterfall_data['Best response percent change'], color=self.bar_colors) self.patches = [] for key in self.keys_and_colors.keys(): self.patches.append(mpatches.Patch(color = self.keys_and_colors[key],label=key)) self.ax.legend(handles=self.patches) else: self.rects = self.ax.bar(self.rect_locations, self.waterfall_data['Best response percent change']) if self.gen_settings[5]: self.plot_table() if self.gen_settings[6] and ~self.gen_settings[4][0]: self.add_labels(self.ax, self.rects, self.waterfall_data, 0) if ~self.gen_settings[4][1]: #response not shown as color coding, custom color code the bars self.ax.grid(color = 'k', axis = 'y', alpha=0.25) self.canvas.draw() self.generated_rectangles_signal.emit([self.rects]) def plot_table(self): rows = ['%s' % x for x in self.waterfall_data.keys()] rows = rows[4:] #skip first three, they are the 4 standard headers, rest are table rows columns = self.waterfall_data['Patient number'] #patient numbers cell_text = [] for row in rows: cell_text_temp = [] for col in range(len(columns)): cell_text_temp.append(self.waterfall_data[row][col]) cell_text.append(cell_text_temp) the_table = self.ax.table(cellText=cell_text, rowLabels=rows, colLabels=columns, loc='bottom', cellLoc='center', colLoc='center') plt.subplots_adjust(bottom=0.15,left=0.5) self.ax.set_xlim(-0.5,len(columns)-0.5) self.ax.tick_params( axis='x', # changes apply to the x-axis which='both', # both major and minor ticks are affected bottom='off', # ticks along the bottom edge are off top='off', # ticks along the top edge are off labelbottom='off' ) # labels along the bottom edge are off def update_plot(self): ''' TODO ''' pass def add_labels(self, ax, rects, waterfall_data, label_type): ''' Add labels above/below bars. label_type == 1 --> display responses; == 0 --> display cancer type ''' i = 0 if label_type: for rect in rects: height = rect.get_height() if height >= 0: valign = 'bottom' else: valign = 'top' ax.text(rect.get_x() + rect.get_width()/2., height, '%s' % waterfall_data['Overall response'][i], ha='center', va=valign) i+=1 else: for rect in rects: height = rect.get_height() if height >= 0: valign = 'top' hgt = -1 else: valign = 'bottom' hgt = 1 ax.text(rect.get_x() + rect.get_width()/2., hgt, '%s' % waterfall_data['Cancer'][i], ha='center', va=valign, rotation='vertical') i+=1
class WidgetMP3Frames(QWidget): def __init__(self): super(WidgetMP3Frames, self).__init__() self.treeWidget = QTreeWidget() self.treeWidget.setHeaderLabel("Offset") self.gbox = QGroupBox("MP3 Frames") self.gbox.setMaximumWidth(100) self.gbox.setLayout(self.genlayout_gbox()) self.chunks = [] self.setLayout(self.genlayout()) def genlayout(self): lay = QVBoxLayout() lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.gbox) return lay def genlayout_gbox(self): lay = QVBoxLayout() lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.treeWidget) return lay def setupWidget(self): self.treeWidget.clear() for offset in self.chunks: item = QTreeWidgetItem(self.treeWidget) item.setText(0, "%.X" % offset) self.treeWidget.addTopLevelItem(item) def scanChunks(self, fname: str) -> {}: self.chunks = [] f = open(fname, "rb") fsize = os.fstat(f.fileno()).st_size offset = 0 flagFirst = False # Scan all chunks while offset < fsize: bval = f.read(1) val = int(bval[0]) if flagFirst: # if (val >> 5) == 7: if val == 0xFB: self.chunks.append(offset - 1) flagFirst = False if not flagFirst: if val == 0xFF: flagFirst = True offset += 1 # Filter invalid for idx in range(len(self.chunks) - 1): offs1 = self.chunks[idx] offs2 = self.chunks[idx + 1] print("%X:%X %i" % (offs1, offs2, offs2 - offs1)) self.setupWidget() return {}
class OTMainWindow(QWidget): def __init__(self, parent=None): super(OTMainWindow, self).__init__(parent, Qt.Window) self.setWindowTitle('OPC Python Tester') self.layout = QVBoxLayout() # self.tree = QTreeWidget(self) self.tree.setHeaderLabel('OPC server tree') self.tree_root = QTreeWidgetItem() self.tree_root.setText(0, 'not connected') self.tree.addTopLevelItem(self.tree_root) self.tree.itemDoubleClicked.connect(self.on_tree_item_double_clicked) # self.table = QTableWidget(self) self.table.setRowCount(0) self.table_column_labels = [ 'item_id', 'value', 'type', 'access', 'quality', 'timestamp'] self.table.setColumnCount(len(self.table_column_labels)) self.table.setHorizontalHeaderLabels(self.table_column_labels) self.table.horizontalHeader().setStretchLastSection(True) # self.splitter = QSplitter(Qt.Horizontal, self) self.splitter.setChildrenCollapsible(False) self.splitter.setHandleWidth(10) self.layout.addWidget(self.splitter) # final self.splitter.addWidget(self.tree) self.splitter.addWidget(self.table) self.splitter.setSizes([150, 300]) self.setLayout(self.layout) # self.opcsrv = None self.cur_server_info = {} self.cur_comp_name = '' self.watched_itemids = [] self.ssdialog = ServerSelectDialog(self) ssel_ret = self.ssdialog.exec_() if ssel_ret == QDialog.Accepted: self.do_connect(self.ssdialog.selected_server, self.ssdialog.selected_comp_name) else: print('Connection cancelled') self.timer = QTimer(self) self.timer.timeout.connect(self.on_timer_timeout) self.timer.start(1000) # every 1 second def do_connect(self, srv_info: dict, comp_name: str): print('Connecting to "{0}" ({1}) on comp: {2}...'.format( srv_info['desc'], srv_info['guid'], comp_name)) self.opcsrv = opc_helper.opc_connect(srv_info['guid'], comp_name) if self.opcsrv is None: return self.cur_comp_name = comp_name self.cur_server_info = srv_info print(self.opcsrv.get_status()) self.fill_tree() def fill_tree(self): self.tree.clear() if self.opcsrv is None: return self.tree_root = QTreeWidgetItem(self.tree) self.tree_root.setChildIndicatorPolicy(QTreeWidgetItem.DontShowIndicatorWhenChildless) root_title = '{0}'.format(self.cur_server_info['desc']) if self.cur_comp_name != '': root_title = '{0} ({1})'.format(self.cur_server_info['desc'], self.cur_comp_name) self.tree_root.setText(0, root_title) self.tree.addTopLevelItem(self.tree_root) server_tree = self.opcsrv.browse(flat=False) # for oitem in server_tree: self.fill_item(oitem, self.tree_root) def fill_item(self, item: dict, parent: QTreeWidgetItem): tree_item = QTreeWidgetItem() tree_item.setChildIndicatorPolicy(QTreeWidgetItem.DontShowIndicatorWhenChildless) tree_item.setText(0, item['name']) if item['children'] is None: # set userdata = item_id only if this IS a LEAF node tree_item.setData(0, Qt.UserRole, item['item_id']) # column, role, data parent.addChild(tree_item) # recurse into children if item['children'] is not None: for oitem in item['children']: self.fill_item(oitem, tree_item) @pyqtSlot(QTreeWidgetItem, int) def on_tree_item_double_clicked(self, item: QTreeWidgetItem, column: int): # void itemDoubleClicked(QTreeWidgetItem * item, int column) # virtual QVariant data(int column, int role) const item_data = item.data(0, Qt.UserRole) if item_data is None: return item_id = str(item_data) print('Double click on [{0}]'.format(item_id)) self.opcsrv.get_item(item_id) if item_id not in self.watched_itemids: self.watched_itemids.append(item_id) @pyqtSlot() def on_timer_timeout(self): num_items = len(self.watched_itemids) self.table.setRowCount(num_items) i = 0 while i < num_items: item_id = self.watched_itemids[i] item_value = self.opcsrv.get_item(item_id) item_info = self.opcsrv.get_item_info(item_id) # twi = QTableWidgetItem(str(item_id)) self.table.setItem(i, 0, twi) # twi = QTableWidgetItem(str(item_value)) self.table.setItem(i, 1, twi) # twi = QTableWidgetItem(str(item_info['type'])) self.table.setItem(i, 2, twi) # twi = QTableWidgetItem(str(item_info['access_rights'])) self.table.setItem(i, 3, twi) # twi = QTableWidgetItem(str(item_info['quality'])) self.table.setItem(i, 4, twi) # ts_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(item_info['timestamp'])) twi = QTableWidgetItem(str(ts_str)) self.table.setItem(i, 5, twi) # i += 1
class DBogUI(object): def __init__(self): self.db_handler = MyDBHandler() self.sqlTranslator = SqlTranslator() self.page_size = 10 self.former_table_data = [] self.current_table_data = [] self.changedTableData = [] self.cell_editable_flg = 0 self.mainWidget = None self.centerLayout = None self.stackedWidget = None self.statusbar = None self.data_page = None self.data_page_layout = None self.menubar = None self.dataPanel = None self.data_panel_layout = None self.sqlTextEdit = None self.table_widget = None self.btn_panel = None self.btn_panel_layout = None self.addLnBtn = None self.rmvLnBtn = None self.queryBtn = None self.editBtn = None self.saveBtn = None self.treePanel = None self.treePanelLayout = None self.tableTree = None self.translate_page = None self.translate_page_layout = None self.translate_up_panel = None self.translate_up_panel_layout = None self.src_sql_text_edit = None self.dest_sql_text_edit = None self.translate_down_panel = None self.translate_down_panel_layout = None self.from_sql_type_combobox = None self.to_sql_type_combobox = None self.translate_btn = None self.swap_btn = None self.clear_btn = None self.login_page = None self.usmLabel = None self.unmTextEdit = None self.pwdLabel = None self.pwdTextEdit = None self.hostLabel = None self.hostTextEdit = None self.dbNameLabel = None self.dbNameTextEdit = None self.cnntDbBtn = None self.load_cfg_btn = None self.error_widgets = [] self.plain_color = None # setup UI def setup_ui(self, MainWindow): # UI MainWindow MainWindow.setObjectName("MainWindow") MainWindow.resize(900, 600) # center widgets self.mainWidget = QWidget(MainWindow) # 设置字体 self.mainWidget.setFont(QFont("Roman times", 11)) self.mainWidget.setObjectName("mainWidget") MainWindow.setCentralWidget(self.mainWidget) self.centerLayout = QVBoxLayout(self.mainWidget) # 设置stacked widget self.stackedWidget = QStackedWidget(self.mainWidget) self.centerLayout.addWidget(self.stackedWidget) # 设置data page self.build_data_page() # layout不同的比例区分大小 self.data_page_layout.setStretchFactor(self.dataPanel, 4) self.data_page_layout.setStretchFactor(self.treePanel, 1) # 设置login面板 self.build_login_page() # 设置translate面板 self.build_translate_page() # 将三个面板,加入stackedWidget self.stackedWidget.addWidget(self.data_page) self.stackedWidget.addWidget(self.login_page) self.stackedWidget.addWidget(self.translate_page) # menu self.build_menu_bar(MainWindow) # status bar self.statusbar = QStatusBar(MainWindow) self.statusbar.setObjectName("statusbar") MainWindow.setStatusBar(self.statusbar) # 刷新 self.re_translate_ui(MainWindow) QMetaObject.connectSlotsByName(MainWindow) # refresh tree list self.db_handler.config(None, None, None, None) self.paint_tree() # data page def build_data_page(self): self.data_page = QWidget(self.mainWidget) self.centerLayout.addWidget(self.data_page) self.data_page_layout = QHBoxLayout(self.data_page) # 设置data面板 self.build_tree_panel() # 设置data面板 self.build_data_panel() # set event self.set_data_page_event() # menu def build_menu_bar(self, MainWindow): self.menubar = QMenuBar(MainWindow) self.menubar.setGeometry(QRect(0, 0, 400, 50)) self.menubar.move(100, 100) self.menubar.setObjectName("menubar") MainWindow.setMenuBar(self.menubar) # 创建一个事件和一个特定图标 show_data_action = QAction(QIcon('../resources/icon/database.png'), 'Operate', self) # show_data_action.setShortcut('Ctrl+Q') # 设置事件的快捷方式 show_data_action.setStatusTip('show data panel') # 设置事件的状态提示 show_data_action.triggered.connect(lambda: self.switch_page(0)) # 事件的触发 login_menu = self.menubar.addMenu('&Data') # 添加菜单file login_menu.addAction(show_data_action) # 菜单添加事件 # 创建一个事件和一个特定图标 show_login_action = QAction(QIcon('../resources/icon/configure.png'), 'Config', self) # show_login_action.setShortcut('Ctrl+P') # 设置事件的快捷方式 show_login_action.setStatusTip('show login panel') # 设置事件的状态提示 show_login_action.triggered.connect(lambda: self.switch_page(1)) # 事件的触发 login_menu = self.menubar.addMenu('&Login') # 添加菜单file login_menu.addAction(show_login_action) # 菜单添加事件 # 创建一个事件和一个特定图标 show_translate_action = QAction(QIcon('../resources/icon/translate.png'), 'Translate', self) # show_login_action.setShortcut('Ctrl+P') # 设置事件的快捷方式 show_translate_action.setStatusTip('show translate page') # 设置事件的状态提示 show_translate_action.triggered.connect(lambda: self.switch_page(2)) # 事件的触发 translate_menu = self.menubar.addMenu('&Translate') # 添加菜单file translate_menu.addAction(show_translate_action) # 菜单添加事件 # 设置data面板 def build_data_panel(self): # data panel self.dataPanel = QWidget(self.data_page) self.data_page_layout.addWidget(self.dataPanel) self.data_panel_layout = QVBoxLayout(self.dataPanel) # SQL输入框 self.sqlTextEdit = QTextEdit(self.dataPanel) self.sqlTextEdit.setObjectName("textEdit") # self.sqlTextEdit.setText("Enter your SQL here...") self.data_panel_layout.addWidget(self.sqlTextEdit) # 数据表格 self.table_widget = TableWidget(self.dataPanel) self.table_widget.init_ui(0, 0, self.dataPanel) self.table_widget.set_page_controller(0) # 表格设置页码控制 self.table_widget.control_signal.connect(self.control_page) self.table_widget.setFixedSize(700, 400) self.data_panel_layout.addWidget(self.table_widget) # build inside panel self.build_btn_panel() self.data_panel_layout.setStretchFactor(self.sqlTextEdit, 2) self.data_panel_layout.setStretchFactor(self.table_widget, 6) self.data_panel_layout.setStretchFactor(self.btn_panel, 2) # button panel def build_btn_panel(self): # button panel self.btn_panel = QWidget(self.data_page) self.btn_panel.setFont(QFont("YaHei", 10)) self.data_panel_layout.addWidget(self.btn_panel) self.btn_panel_layout = QHBoxLayout(self.btn_panel) # 表格操作:增加数据行按钮 self.addLnBtn = QPushButton(self.btn_panel) self.addLnBtn.setMaximumSize(100, 80) self.addLnBtn.setObjectName("addLineButton") self.btn_panel_layout.addWidget(self.addLnBtn) # 表格操作:增加数据行按钮 self.rmvLnBtn = QPushButton(self.btn_panel) self.rmvLnBtn.setObjectName("rmvLineButton") self.btn_panel_layout.addWidget(self.rmvLnBtn) # 查询按钮 self.queryBtn = QPushButton(self.btn_panel) # self.queryBtn.setGeometry(QRect(150, 500, 100, 50)) self.queryBtn.setObjectName("QueryButton") self.btn_panel_layout.addWidget(self.queryBtn) # 更新按钮 self.editBtn = QPushButton(self.btn_panel) # self.editBtn.setGeometry(QRect(150, 500, 100, 50)) self.editBtn.setObjectName("EditButton") self.btn_panel_layout.addWidget(self.editBtn) # 保存按钮 self.saveBtn = QPushButton(self.btn_panel) # self.saveBtn.setGeometry(QRect(225, 500, 100, 50)) self.saveBtn.setObjectName("SaveButton") self.btn_panel_layout.addWidget(self.saveBtn) # set partition / relative size self.btn_panel_layout.setStretchFactor(self.addLnBtn, 1) self.btn_panel_layout.setStretchFactor(self.rmvLnBtn, 1) self.btn_panel_layout.setStretchFactor(self.queryBtn, 1) self.btn_panel_layout.setStretchFactor(self.editBtn, 1) self.btn_panel_layout.setStretchFactor(self.saveBtn, 1) # left catalog tree panel def build_tree_panel(self): self.treePanel = QWidget(self.data_page) self.treePanel.setFont(QFont("SimHei", 11)) self.treePanelLayout = QVBoxLayout(self.treePanel) # self.treePanel.setGeometry(QRect(0, 0, 100, 600)) self.data_page_layout.addWidget(self.treePanel) # tableTree list self.tableTree = QTreeWidget(self.treePanel) self.treePanelLayout.addWidget(self.tableTree) # 设置列数 self.tableTree.setColumnCount(1) self.tableTree.setColumnWidth(0, 100) # 设置头的标题 self.tableTree.setHeaderLabel('Table List') # 绑定点击事件 self.tableTree.clicked.connect(self.on_tree_clicked) # 加载树形菜单 def paint_tree(self): if not CTools.isEmpty(self.db_handler.db_name): self.tableTree.clear() self.sqlTextEdit.setText("use %s;show tables;" % self.db_handler.db_name) table_lst = self.show_tables() # repaint tree nodes root = QTreeWidgetItem(self.tableTree) root.setText(0, self.db_handler.db_name) for i in range(len(table_lst)): table_name = table_lst[i] table_node = QTreeWidgetItem(root) table_node.setText(0, table_name) self.tableTree.addTopLevelItem(root) # data page def build_translate_page(self): self.translate_page = QWidget(self.mainWidget) self.centerLayout.addWidget(self.translate_page) self.translate_page_layout = QVBoxLayout(self.translate_page) self.build_translate_panel() # 设置data面板 def build_translate_panel(self): # translate panel self.translate_up_panel = QWidget(self.translate_page) self.translate_page_layout.addWidget(self.translate_up_panel) # append widget self.translate_up_panel_layout = QHBoxLayout(self.translate_up_panel) # SQL输入框 self.src_sql_text_edit = QTextEdit(self.translate_up_panel) self.src_sql_text_edit.setObjectName("src_sql_text_edit") self.src_sql_text_edit.setText("Enter source SQL to translate here...") self.translate_up_panel_layout.addWidget(self.src_sql_text_edit) # SQL输入框 self.dest_sql_text_edit = QTextEdit(self.translate_up_panel) self.dest_sql_text_edit.setObjectName("dest_sql_text_edit") self.dest_sql_text_edit.setText("SQL translate result will be placed here...") self.dest_sql_text_edit.setReadOnly(True) self.translate_up_panel_layout.addWidget(self.dest_sql_text_edit) # translate panel self.translate_down_panel = QWidget(self.translate_page) self.translate_page_layout.addWidget(self.translate_down_panel) # append widget self.translate_down_panel_layout = QHBoxLayout(self.translate_down_panel) # select from sql type self.from_sql_type_combobox = QComboBox(self.translate_down_panel) self.from_sql_type_combobox.setMaximumSize(150, 120) self.from_sql_type_combobox.setObjectName("from_sql_type_combobox") self.from_sql_type_combobox.addItems(self.sqlTranslator.valid_from_dialect) self.translate_down_panel_layout.addWidget(self.from_sql_type_combobox) self.plain_color = self.from_sql_type_combobox.palette().color(QPalette.Background).toRgb() # select to sql type self.to_sql_type_combobox = QComboBox(self.translate_down_panel) self.to_sql_type_combobox.setMaximumSize(150, 120) self.to_sql_type_combobox.setObjectName("to_sql_type_combobox") # self.to_sql_type_combobox.addItems(['', 'mssql', 'sqlserver', 'mssqlserver', 'mysql', 'oracle', 'db2', 'db2udb']) self.to_sql_type_combobox.addItems(self.sqlTranslator.valid_to_dialect) self.translate_down_panel_layout.addWidget(self.to_sql_type_combobox) # translate按钮 self.translate_btn = QPushButton(self.translate_down_panel) self.translate_btn.setMaximumSize(150, 120) self.translate_btn.setObjectName("Translate") self.translate_btn.setText("Translate") self.translate_down_panel_layout.addWidget(self.translate_btn) self.translate_btn.clicked.connect(self.on_translate) # 交换按钮 self.swap_btn = QPushButton(self.translate_down_panel) self.swap_btn.setMaximumSize(150, 120) self.swap_btn.setObjectName("Swap") self.swap_btn.setText("Swap") self.translate_down_panel_layout.addWidget(self.swap_btn) self.swap_btn.clicked.connect(self.on_swap) # 清空按钮 self.clear_btn = QPushButton(self.translate_down_panel) self.clear_btn.setMaximumSize(150, 120) self.clear_btn.setObjectName("Clear") self.clear_btn.setText("Clear") self.translate_down_panel_layout.addWidget(self.clear_btn) self.clear_btn.clicked.connect(self.on_clear) def on_translate(self): src_sql_str = self.src_sql_text_edit.toPlainText() from_type = self.from_sql_type_combobox.currentText() to_type = self.to_sql_type_combobox.currentText() error_msg = "" if not src_sql_str or src_sql_str == "Enter source SQL to translate here...": error_msg += "source SQL should not be empty\n" self.src_sql_text_edit.setTextBackgroundColor(QColor(255, 0, 0, 255)) self.error_widgets.append(self.src_sql_text_edit) else: if self.src_sql_text_edit in self.error_widgets: self.error_widgets.remove(self.src_sql_text_edit) if not from_type: error_msg += "from SQL Type should not be empty\n" self.from_sql_type_combobox.setStyleSheet("QComboBox{color: rgb(255,0,0,255)}") self.error_widgets.append(self.from_sql_type_combobox) else: if self.from_sql_type_combobox in self.error_widgets: self.error_widgets.remove(self.from_sql_type_combobox) if not to_type: error_msg += "to SQL Type should not be empty\n" self.to_sql_type_combobox.setStyleSheet("QComboBox{color: rgb(255,0,0,255)}") self.error_widgets.append(self.to_sql_type_combobox) else: if self.to_sql_type_combobox in self.error_widgets: self.error_widgets.remove(self.to_sql_type_combobox) if self.error_widgets: QMessageBox.warning(None, "Warning", error_msg) return if self.src_sql_text_edit in self.error_widgets: self.src_sql_text_edit.setTextBackgroundColor(QColor(255, 255, 255, 255)) if self.from_sql_type_combobox in self.error_widgets: self.from_sql_type_combobox.setStyleSheet("QComboBox{color: rgb%s}" % str(self.plain_color.getRgb())) if self.to_sql_type_combobox in self.error_widgets: self.to_sql_type_combobox.setStyleSheet("QComboBox{color: rgb%s}" % str(self.plain_color.getRgb())) dest_sql_str = self.sqlTranslator.web_translate(src_sql_str, from_type, to_type) self.dest_sql_text_edit.setText(dest_sql_str) def on_swap(self): # origin # from_sql_type_index = self.from_sql_type_combobox.currentIndex() # to_sql_type_index = self.to_sql_type_combobox.currentIndex() from_sql_type_text = self.from_sql_type_combobox.currentText() to_sql_type_text = self.to_sql_type_combobox.currentText() src_sql_str = self.src_sql_text_edit.toPlainText() dest_sql_str = self.dest_sql_text_edit.toPlainText() # change content self.from_sql_type_combobox.setCurrentIndex( self.sqlTranslator.get_opposite_sql_type(from_sql_type_text, 'from')) self.to_sql_type_combobox.setCurrentIndex(self.sqlTranslator.get_opposite_sql_type(to_sql_type_text, 'to')) self.dest_sql_text_edit.setText(src_sql_str) self.src_sql_text_edit.setText(dest_sql_str) def on_clear(self): self.from_sql_type_combobox.setCurrentIndex(0) self.to_sql_type_combobox.setCurrentIndex(0) self.dest_sql_text_edit.setText("") self.src_sql_text_edit.setText("") # show data after clicking the menu item in tree def on_tree_clicked(self): item = self.tableTree.currentItem() table_name = item.text(0) # no response when click database label if table_name != self.db_handler.db_name: cTool.logger.info("switch to data of table, name: %s" % table_name) self.sqlTextEdit.setText("select * from %s limit 0, %d;" % (table_name, self.page_size)) self.do_query() # when press append record button def on_append_record(self, table_name, page_size): if self.is_cell_editable(): self.sqlTextEdit.setText("select * from %s limit 0, %d;" % (table_name, page_size)) self.do_query() else: self.warn_action("Table is not allowed to append record now") # when action is fobidden def warn_action(self, err_msg="illegal action"): cTool.logger.info("%s" % err_msg) QMessageBox.warning(None, "Warning", err_msg) # when press remove record button def on_remove_record(self): if self.is_cell_editable(): cell_item_list = self.table_widget.table.selectedItems() count = len(cell_item_list) row_num_set = set() for x in range(0, count): table_item = cell_item_list[x] row_num_set.add(self.table_widget.table.row(table_item)) selected_pk_set = set( self.former_table_data[row_num][self.db_handler.def_pk_index] for row_num in row_num_set) self.db_handler.delete_sql = self.db_handler.buildDeleteSQL(self.db_handler.def_table_name, self.db_handler.def_pk_name, selected_pk_set) # rmv_cnt = self.db_handler.modifyRecords(delete_sql) # print("updated count is %d for SQL %s" % (rmv_cnt, delete_sql)) # self.do_query() self.former_table_data = [former_row_data for former_row_data in self.former_table_data if former_row_data[self.db_handler.def_pk_index] not in selected_pk_set] self.update_table_data(self.former_table_data, self.db_handler.def_field_name_lst, False) else: self.warn_action("Table is not allowed to delete record now") # 设置login面板 def build_login_page(self): # login panel self.login_page = QWidget(self.stackedWidget) self.login_page.setGeometry(QRect(100, 100, 600, 600)) # self.login_page_layout = QVBoxLayout(self.login_page) # username label self.usmLabel = QLabel(self.login_page) self.usmLabel.setGeometry(QRect(100, 100, 150, 50)) self.usmLabel.setText("username ") # self.login_page_layout.addWidget(self.usmLabel) # username input self.unmTextEdit = QLineEdit(self.login_page) self.unmTextEdit.setText("") self.unmTextEdit.setGeometry(QRect(300, 100, 450, 50)) # self.login_page_layout.addWidget(self.unmTextEdit) # password label self.pwdLabel = QLabel(self.login_page) self.pwdLabel.setGeometry(QRect(100, 150, 150, 50)) self.pwdLabel.setText("password ") # self.login_page_layout.addWidget(self.pwdLabel) # password input self.pwdTextEdit = QLineEdit(self.login_page) self.pwdTextEdit.setText("") self.pwdTextEdit.setGeometry(QRect(300, 150, 450, 50)) # self.login_page_layout.addWidget(self.pwdTextEdit) # host label self.hostLabel = QLabel(self.login_page) self.hostLabel.setGeometry(QRect(100, 200, 120, 50)) self.hostLabel.setText("host address ") # self.login_page_layout.addWidget(self.hostLabel) # host input self.hostTextEdit = QLineEdit(self.login_page) self.hostTextEdit.setText("") self.hostTextEdit.setGeometry(QRect(300, 200, 450, 50)) # self.login_page_layout.addWidget(self.hostTextEdit) # database name label self.dbNameLabel = QLabel(self.login_page) self.dbNameLabel.setGeometry(QRect(100, 250, 150, 50)) self.dbNameLabel.setText("database name ") # self.login_page_layout.addWidget(self.dbNameLabel) # database name input self.dbNameTextEdit = QLineEdit(self.login_page) self.dbNameTextEdit.setText("") self.dbNameTextEdit.setGeometry(QRect(300, 250, 450, 50)) # self.login_page_layout.addWidget(self.dbNameTextEdit) # confirm to connect database self.cnntDbBtn = QPushButton(self.login_page) self.cnntDbBtn.setObjectName("DbButton") self.cnntDbBtn.setGeometry(QRect(100, 350, 300, 50)) # self.login_page_layout.addWidget(self.cnntDbBtn) # connect database with config file self.load_cfg_btn = QPushButton(self.login_page) self.load_cfg_btn.setObjectName("LoadConfigButton") self.load_cfg_btn.setGeometry(QRect(450, 350, 300, 50)) # self.login_page_layout.addWidget(self.load_cfg_btn) # set event self.set_login_panel() # build login panel def set_login_panel(self): self.cnntDbBtn.clicked.connect(self.cnnt_db) self.load_cfg_btn.clicked.connect(self.re_config_db) # connect to database def cnnt_db(self): unmStr = self.unmTextEdit.text() pwdStr = self.pwdTextEdit.text() hostStr = self.hostTextEdit.text() dbNameStr = self.dbNameTextEdit.text() self.db_handler.config(hostStr, unmStr, pwdStr, dbNameStr) self.paint_tree() self.switch_page(0) # connect to database with configuration file def re_config_db(self): self.db_handler.config(mVars.host, mVars.username, mVars.password, mVars.database_name) # set label text of widget def re_translate_ui(self, MainWindow): _translate = QCoreApplication.translate MainWindow.setWindowTitle(_translate("MainWindow", "DBog")) self.setWindowIcon(QIcon("../resources/icon/ball.ico")) self.queryBtn.setText(_translate("MainWindow", "Query")) self.editBtn.setText(_translate("MainWindow", "Edit")) self.saveBtn.setText(_translate("MainWindow", "Save")) self.cnntDbBtn.setText(_translate("MainWindow", "ConnectByInputConfiguration")) self.load_cfg_btn.setText(_translate("MainWindow", "LoadConfigurationFile")) self.addLnBtn.setText(_translate("MainWindow", "AppendRecord")) self.rmvLnBtn.setText(_translate("MainWindow", "RemoveRecord")) # set event of data page def set_data_page_event(self): self.queryBtn.clicked.connect(self.do_query) self.editBtn.clicked.connect(self.do_edit) self.saveBtn.clicked.connect(self.do_save) self.addLnBtn.clicked.connect(lambda: self.on_append_record(self.db_handler.def_table_name, self.page_size - 1)) self.rmvLnBtn.clicked.connect(self.on_remove_record) # switch to another page def switch_page(self, pnIndex): self.stackedWidget.setCurrentIndex(int(pnIndex)) # execute query action def do_query(self): cmd_str = self.sqlTextEdit.toPlainText().replace(";", "") # 获取数据 result_set = self.db_handler.get_all_result(cmd_str) # 获取表结构定义 # db_filed_lst = self.db_handler.gen_alter(self.db_handler.def_table_name) # self.db_handler.field_name_lst = [db_filed["Field"] for db_filed in db_filed_lst] self.db_handler.gen_filed_name_list() # withdraw former table data / cast value None to empty string self.former_table_data = [[str(item) if item else "" for item in row] for row in result_set] # change button status to enable or not self.change_btn_status() # fill in table with empty line self.refresh_table(cmd_str) cTool.logger.info("result set for %s is %s" % ( cmd_str, ','.join([str(row_data) for row_data in result_set]))) # change button status to enable or not def change_btn_status(self): if self.is_cell_editable(): self.addLnBtn.setEnabled(True) self.rmvLnBtn.setEnabled(True) self.saveBtn.setEnabled(True) else: self.addLnBtn.setEnabled(False) self.rmvLnBtn.setEnabled(False) self.saveBtn.setEnabled(False) # refresh table widget def refresh_table(self, cmd_str): row_cnt = len(self.former_table_data) if row_cnt > 0: col_cnt = len(self.former_table_data[0]) while row_cnt < self.page_size: self.former_table_data.append(["" for i in range(col_cnt)]) row_cnt = row_cnt + 1 # count record page_cnt = self.count_record(cmd_str) # refresh table self.update_table_data(self.former_table_data, self.db_handler.def_field_name_lst, True) # refresh table page self.refresh_table_pager(page_cnt) # execute count of record def count_record(self, cmd_str): if "limit" in cmd_str: cmd_str = cmd_str.split("limit")[0] cnt_sql = "select count(1) from (%s) sub" % cmd_str ret_cnt = self.db_handler.get_single_result(cnt_sql) page_cnt = 0 if ret_cnt and len(ret_cnt) > 0: ret_cnt = int(ret_cnt[0]) page_cnt = int(ret_cnt / 10) if page_cnt % 10 != 0: page_cnt = page_cnt + 1 return page_cnt # make new name of each field def rename_fields(self): ret_dict = self.count_list(self.db_handler.def_field_name_lst) for field_name in self.db_handler.def_field_name_lst: if ret_dict[field_name] > 1: field_name = "%s_%d" % (field_name, ret_dict[field_name]) ret_dict[field_name] = ret_dict[field_name] - 1 # statistic item count in a list def count_list(self, target_lst): ret_dict = {} for i in set(target_lst): ret_dict[i] = target_lst.count(i) return ret_dict # show table of current database def show_tables(self): tables_sql = "show tables;" self.sqlTextEdit.setText(tables_sql) return [tbl_name_tp[0] for tbl_name_tp in self.db_handler.get_all_result(tables_sql)] # execute update def do_edit(self): cmd_str = self.sqlTextEdit.toPlainText() row_count = self.db_handler.modifyRecords(cmd_str) info_msg = "Edit finish, effected row count: %d" % row_count cTool.logger.info(info_msg) QMessageBox.information(self, "Info", info_msg) # @decorator() # execute save action def do_save(self): # edit result set of multi tables is forbidden if self.is_cell_editable(): # clear first self.current_table_data.clear() row_count = self.table_widget.table.rowCount() column_count = self.table_widget.table.columnCount() for i in range(0, row_count): curRowData = [] for j in range(0, column_count): cellValStr = self.table_widget.table.item(i, j).text() curRowData.append(cellValStr) self.current_table_data.append(curRowData) # assemble SQL from data deleteSQL = self.db_handler.delete_sql if deleteSQL: # reset to empty string self.db_handler.delete_sql = "" else: dataLst = self.get_delete_data() deleteSQL = self.db_handler.buildDeleteSQL(self.db_handler.def_table_name, self.db_handler.def_pk_name, dataLst) if dataLst else "" cTool.logger.info(deleteSQL) dataLst = self.get_insert_data() insertSQL = self.db_handler.buildInsertSQL(self.db_handler.def_table_name, dataLst) if dataLst else "" cTool.logger.info(insertSQL) rmCnt = self.db_handler.modifyRecords(deleteSQL) if deleteSQL else 0 inCnt = self.db_handler.modifyRecords(insertSQL) if insertSQL else 0 info_msg = "Save finish, effect record count removed: %d,inserted: %d" % (rmCnt, inCnt) cTool.logger.info(info_msg) QMessageBox.information(self, "Info", info_msg) else: self.warn_action("Table is not allowed to update now") # is table cell item editable def is_cell_editable(self): self.cell_editable_flg = QtCore.Qt.ItemIsEnabled if self.db_handler.is_combine_sql else QtCore.Qt.ItemIsEditable return self.cell_editable_flg == 2 # withdraw delete data def get_delete_data(self): fmrSet = set(map(lambda x: ",".join(x), self.former_table_data)) curSet = set(map(lambda x: ",".join(x), self.current_table_data)) diffSet = fmrSet.difference(curSet) return list(map(lambda diffStr: diffStr.split(",")[self.db_handler.def_pk_index], diffSet)) # withdraw insert data def get_insert_data(self): curSet = set(map(lambda x: ",".join(x), self.current_table_data)) fmrSet = set(map(lambda x: ",".join(x), self.former_table_data)) diffSet = curSet.difference(fmrSet) return list(map(lambda diffStr: diffStr.split(","), diffSet)) # update/refresh table data def update_table_data(self, matrix_data, fd_name_list, update_header_flg): row_cnt = len(matrix_data) col_cnt = len(matrix_data[0]) if len(matrix_data) > 0 and len(matrix_data[0]) > 0 else 0 # refresh table model struct self.clear_table_data() # if update_header_flg: self.refresh_table_header(fd_name_list, row_cnt, col_cnt) # refresh table data for row_num in range(0, row_cnt): for col_num in range(0, col_cnt): cell_data = str(matrix_data[row_num][col_num]) if matrix_data[row_num][col_num] else "" # 设置表格内容(行, 列) 文字 self.table_widget.table.setItem(row_num, col_num, QTableWidgetItem(cell_data)) # update/refresh table header def refresh_table_header(self, fd_name_list, row_cnt, col_cnt): self.table_widget.table.setRowCount(row_cnt) self.table_widget.table.setColumnCount(col_cnt) self.table_widget.table.setHorizontalHeaderLabels(fd_name_list) # update/refresh table pager def refresh_table_pager(self, page_count): self.table_widget.totalPageNum.setText(str(page_count)) # clear table data def clear_table_data(self): self.table_widget.table.setRowCount(0) self.table_widget.table.setColumnCount(0) # config table item def touch_table_item(self): flag = QtCore.Qt.ItemIsEnabled if self.db_handler.is_combine_sql else QtCore.Qt.ItemIsEditable col_cnt = self.table_widget.table.colorCount() row_cnt = self.table_widget.table.rowCount() for col_num in range(0, col_cnt): for row_num in range(0, row_cnt): QTableWidgetItem(self.table_widget.table.item(row_cnt, col_cnt)).setFlags(flag) # 设置第二列不可编辑 # self.table_widget.table.setItemDelegateForColumn(col_num, EmptyDelegate(self)) # self.table_widget.table.setItemDelegateForColumn(col_num, QTableWidget.itemDelegate()) # control page def control_page(self, signal): total_page = self.table_widget.show_total_page() if "home" == signal[0]: self.table_widget.curPage.setText("1") elif "pre" == signal[0]: if 1 == int(signal[1]): QMessageBox.information(self, "提示", "已经是第一页了", QMessageBox.Yes) return self.table_widget.curPage.setText(str(int(signal[1]) - 1)) elif "next" == signal[0]: if total_page == int(signal[1]): QMessageBox.information(self, "提示", "已经是最后一页了", QMessageBox.Yes) return self.table_widget.curPage.setText(str(int(signal[1]) + 1)) elif "final" == signal[0]: self.table_widget.curPage.setText(str(total_page)) elif "confirm" == signal[0]: if total_page < int(signal[1]) or int(signal[1]) < 0: QMessageBox.information(self, "提示", "跳转页码超出范围", QMessageBox.Yes) return self.table_widget.curPage.setText(signal[1]) self.change_table_content() # 改变表格内容 # change table pager and content def change_table_content(self): """根据当前页改变表格的内容""" cur_page = int(self.table_widget.curPage.text()) # 每页默认十条数据 cTool.logger.info("current page: %s" % cur_page) page_limit_sql = "limit %s, %s" % ((cur_page - 1) * 10, 10) query_sql = "select * from %s %s;" % (self.db_handler.def_table_name, page_limit_sql) self.sqlTextEdit.setText(query_sql) self.do_query()
class select_param(QWidget): def init(self,treeview): self.dest_treeview=treeview def __init__(self): QWidget.__init__(self) self.win_list=windows() self.setFixedSize(400,700) self.main_vbox=QVBoxLayout() self.setWindowIcon(QIcon(os.path.join(get_image_file_path(),"scan.png"))) self.setWindowTitle(_("Select simulation parameter (www.gpvdm.com)")) self.tab = QTreeWidget() #self.tab.setHeaderItem("Scan items") self.main_vbox.addWidget(self.tab) self.hwidget=QWidget() okButton = QPushButton("OK") cancelButton = QPushButton("Cancel") hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(okButton) hbox.addWidget(cancelButton) self.hwidget.setLayout(hbox) self.main_vbox.addWidget(self.hwidget) self.setLayout(self.main_vbox) okButton.clicked.connect(self.tree_apply_click) cancelButton.clicked.connect(self.close) #self.tab.itemSelectionChanged.connect(self.tree_apply_click) self.tab.header().close() self.update() return def make_entry(self,root,text): depth=0 pointer=root for depth in range(0,len(text)): found=False for i in range(0,pointer.childCount()): if pointer.child(i).text(0)==text[depth]: found=True pointer=pointer.child(i) break if found==False: pointer=QTreeWidgetItem(pointer, [text[depth]]) def update(self): self.tab.clear() root = QTreeWidgetItem(self.tab, [_("Simulation parameters")]) root.setExpanded(True) param_list=scan_items_get_list() i=0 for item in range(0, len(param_list)): div_str=param_list[item].name.replace("\\", "/") div_str=div_str.split("/") piter=None self.make_entry(root,div_str) def on_destroy(self): self.win_list.update(self,"scan_select") self.hide() return True def cal_path(self): getSelected = self.tab.selectedItems() if getSelected: item = getSelected[0] # getChildNode = baseNode.text(0) path = [] while item is not None: path.append(str(item.text(0))) item = item.parent() ret="/".join(reversed(path)) ret=ret.split('/', 1)[-1] ret=ret.replace("/", os.path.sep) return ret def tree_apply_click(self): index = self.dest_treeview.selectionModel().selectedRows() if len(index)>0: print("row=",index[0].row(),len(index)) pos=index[0].row() #print(path,scan_items_get_file(path),scan_items_get_token(path)) path=self.cal_path() file_name=scan_items_get_file(path) token=scan_items_get_token(path) print("adding",path,file_name,token) tab_set_value(self.dest_treeview,pos,0,file_name) tab_set_value(self.dest_treeview,pos,1,token) tab_set_value(self.dest_treeview,pos,2,path) self.close() else: error_dlg(self,"No row selected in the scan window, can't insert the selection")
class materials_select(QWidget): def init(self, treeview): self.dest_treeview = treeview def set_save_function(self, save_function): self.save_function = save_function def __init__(self): QWidget.__init__(self) self.setFixedSize(500, 700) self.file_name_tab_pos = 2 self.main_vbox = QVBoxLayout() self.save_function = None self.setWindowIcon(icon_get("scan")) self.setWindowTitle(_("Select material") + " (https://www.gpvdm.com)") self.tab = QTreeWidget() #self.tab.setHeaderItem("Scan items") self.font = QFont() # self.font.setFamily('DejaVu Sans') # self.font.setBold(True) # self.font.setStyleHint(QFont.Monospace) # self.font.setFixedPitch(True) self.font.setPointSize(int(20)) self.tab.setFont(self.font) self.main_vbox.addWidget(self.tab) self.hwidget = QWidget() okButton = QPushButton(_("OK")) cancelButton = QPushButton(_("Cancel")) hbox = QHBoxLayout() hbox.addStretch(1) hbox.addWidget(okButton) hbox.addWidget(cancelButton) self.hwidget.setLayout(hbox) self.main_vbox.addWidget(self.hwidget) self.setLayout(self.main_vbox) okButton.clicked.connect(self.tree_apply_click) cancelButton.clicked.connect(self.close) #self.tab.itemSelectionChanged.connect(self.tree_apply_click) self.tab.header().close() self.update() #return def make_entry(self, root, text): depth = 0 pointer = root for depth in range(0, len(text)): found = False for i in range(0, pointer.childCount()): if pointer.child(i).text(0) == text[depth]: found = True pointer = pointer.child(i) break if found == False: pointer = QTreeWidgetItem(pointer, [text[depth]]) if depth == len(text) - 1: pointer.setIcon(0, icon_get("organic_material")) else: pointer.setIcon(0, icon_get("folder")) def update(self): self.tab.clear() root = QTreeWidgetItem(self.tab, [_("Materials")]) root.setExpanded(True) param_list = find_materials() i = 0 for item in range(0, len(param_list)): div_str = param_list[item].replace("\\", "/") div_str = div_str.split("/") piter = None self.make_entry(root, div_str) def on_destroy(self): self.hide() return True def cal_path(self): path = [] getSelected = self.tab.selectedItems() if getSelected: item = getSelected[0] # getChildNode = baseNode.text(0) while item is not None: path.append(str(item.text(0))) item = item.parent() ret = "/".join(reversed(path)) ret = ret.split('/', 1)[-1] #ret=ret.replace("/", os.path.sep) return ret def tree_apply_click(self): getSelected = self.tab.selectedItems() if len(getSelected) == 0: error_dlg(self, _("You need to select a material")) return index = self.dest_treeview.selectionModel().selectedRows() if len(index) > 0: print("row=", index[0].row(), len(index)) pos = index[0].row() path = self.cal_path() tab_set_value(self.dest_treeview, pos, self.file_name_tab_pos, path) if self.save_function != None: self.save_function() self.close() else: error_dlg( self, _("No row selected in the layer editor, can't insert the selection" ))
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.dbm_obj = Dbm() self.curFile = '' self.textEdit = QTextEdit() self.sectionTreeWidget = QTreeWidget() self.notesListWidget = QListWidget() self.createHorizontalGroupBox() self.setCentralWidget(self.horizontalGroupBox) self.createActions() self.createMenus() # self.createToolBars() self.createStatusBar() self.readSettings() [self.hierarchy_dict, self.notebook_dict, self.section_dict, self.page_dict] = [{}, {}, {}, {}] self.setCurrentFile('') # For binding slots and signals self.fetchPageThread = FetchPage() self.fetchPageThread.setObjectName('fetchPageThread') self.syncAllThread = SyncAllThread() self.syncAllThread.setObjectName('syncAllThread') self.textEdit.document().contentsChanged.connect(self.documentWasModified) self.sectionTreeWidget.setObjectName("sectionTreeWidget") self.notesListWidget.setObjectName("notesListWidget") QMetaObject.connectSlotsByName(self) self.readDB() @pyqtSlot() def on_sectionTreeWidget_itemSelectionChanged(self): for x in self.sectionTreeWidget.selectedItems(): if x.text(1) in self.section_dict.keys(): self.populate_notes_list(x.parent().text(1), x.text(1)) @pyqtSlot() def on_notesListWidget_itemSelectionChanged(self): for x in self.notesListWidget.selectedItems(): self.fetchPageThread.fetchSignal.connect(self.on_fetchPageThread_fetchComplete) self.titleLabel.setText("Syncing") self.statusBar().showMessage("Syncing") # self.fetchPageThread.fetchSignal.connect(lambda:self.view.setHtml("<body>hello world</body>")) self.fetchPageThread.fetch(self.page_dict[x.data(1)]) def on_fetchPageThread_fetchComplete(self, string): self.view.setHtml(string) self.titleLabel.setText(self.view.title()) self.statusBar().showMessage("Page fetched") def on_syncAllThread_syncComplete(self, dbm): self.dbm_obj = dbm self.statusBar().showMessage("Sync complete") def createHorizontalGroupBox(self): self.horizontalGroupBox = QGroupBox() layout = QHBoxLayout() self.sectionTreeWidget.setHeaderHidden(1) layout.addWidget(self.sectionTreeWidget, 0) self.sectionTreeWidget.setStyleSheet("background-color: rgb(215,227,229)") self.notesListWidget.setWindowTitle('Notes') layout.addWidget(self.notesListWidget, 0) self.notesListWidget.setStyleSheet("QListWidget {background-color: rgb(196,226,233)}") subVBox = QGroupBox() vLayout = QVBoxLayout() self.titleLabel = QLabel() vLayout.addWidget(self.titleLabel, 0) self.view = QWebView() vLayout.addWidget(self.view, 1) subVBox.setLayout(vLayout) layout.addWidget(subVBox, 1) self.horizontalGroupBox.setLayout(layout) def closeEvent(self, event): if self.maybeSave(): self.writeSettings() event.accept() else: event.ignore() def newFile(self): self.readDB() # if self.maybeSave(): # self.textEdit.clear() # self.setCurrentFile('') def open(self): if self.maybeSave(): fileName, _ = QFileDialog.getOpenFileName(self) if fileName: self.loadFile(fileName) def save(self): if self.curFile: return self.saveFile(self.curFile) return self.saveAs() def saveAs(self): fileName, _ = QFileDialog.getSaveFileName(self) if fileName: return self.saveFile(fileName) return False def on_sectionList_selection_changed(self): print('selected item index changed '), def populate_section_list(self, hierarchy, notebook_dict, section_dict): self.sectionTreeWidget.clear() for notebook_id in hierarchy.keys(): notebook_sectionTreeWidget = QTreeWidgetItem(self.sectionTreeWidget, [notebook_dict[notebook_id].name, notebook_id]) for section_id in hierarchy[notebook_id].keys(): QTreeWidgetItem(notebook_sectionTreeWidget, [section_dict[section_id].name, section_id]) self.sectionTreeWidget.show() self.sectionTreeWidget.expandAll() def populate_notes_list(self, notebook_id, section_id): self.notesListWidget.clear() for page_id in self.hierarchy_dict[notebook_id][section_id]: item = QListWidgetItem(self.page_dict[page_id].title, self.notesListWidget) item.setData(1, QVariant(page_id)) def readDB(self): self.dbm_obj.read() [self.hierarchy_dict, self.notebook_dict, self.section_dict, self.page_dict] = self.dbm_obj.get_hierarchy_dict() self.populate_section_list(self.hierarchy_dict, self.notebook_dict, self.section_dict) def sync(self): self.syncAllThread.syncCompleteSignal.connect(self.on_syncAllThread_syncComplete) self.statusBar().showMessage("Syncing") self.syncAllThread.sync() def about(self): QMessageBox.about(self, "About Application", "The <b>Application</b> example demonstrates how to write " "modern GUI applications using Qt, with a menu bar, " "toolbars, and a status bar.") def documentWasModified(self): self.setWindowModified(self.textEdit.document().isModified()) def createActions(self): root = QFileInfo(__file__).absolutePath() self.newAct = QAction(QIcon(root + '/images/new.png'), "&New", self, shortcut=QKeySequence.New, statusTip="Create a new file", triggered=self.newFile) self.openAct = QAction(QIcon(root + '/images/open.png'), "&Open...", self, shortcut=QKeySequence.Open, statusTip="Open an existing file", triggered=self.open) self.saveAct = QAction(QIcon(root + '/images/save.png'), "&Save", self, shortcut=QKeySequence.Save, statusTip="Save the document to disk", triggered=self.save) self.saveAsAct = QAction("Save &As...", self, shortcut=QKeySequence.SaveAs, statusTip="Save the document under a new name", triggered=self.saveAs) self.syncAct = QAction("S&ync", self, statusTip="Sync everything", triggered=self.sync) self.readDBAct = QAction("Read DB", self, statusTip="Read stored DB", triggered=self.readDB) self.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q", statusTip="Exit the application", triggered=self.close) self.cutAct = QAction(QIcon(root + '/images/cut.png'), "Cu&t", self, shortcut=QKeySequence.Cut, statusTip="Cut the current selection's contents to the clipboard", triggered=self.textEdit.cut) self.copyAct = QAction(QIcon(root + '/images/copy.png'), "&Copy", self, shortcut=QKeySequence.Copy, statusTip="Copy the current selection's contents to the clipboard", triggered=self.textEdit.copy) self.pasteAct = QAction(QIcon(root + '/images/paste.png'), "&Paste", self, shortcut=QKeySequence.Paste, statusTip="Paste the clipboard's contents into the current selection", triggered=self.textEdit.paste) self.aboutAct = QAction("&About", self, statusTip="Show the application's About box", triggered=self.about) self.aboutQtAct = QAction("About &Qt", self, statusTip="Show the Qt library's About box", triggered=QApplication.instance().aboutQt) self.cutAct.setEnabled(False) self.copyAct.setEnabled(False) self.textEdit.copyAvailable.connect(self.cutAct.setEnabled) self.textEdit.copyAvailable.connect(self.copyAct.setEnabled) def createMenus(self): self.fileMenu = self.menuBar().addMenu("&File") self.fileMenu.addAction(self.newAct) self.fileMenu.addAction(self.openAct) self.fileMenu.addAction(self.saveAct) self.fileMenu.addAction(self.saveAsAct) self.fileMenu.addAction(self.syncAct) self.fileMenu.addAction(self.readDBAct) self.fileMenu.addSeparator() self.fileMenu.addAction(self.exitAct) self.editMenu = self.menuBar().addMenu("&Edit") self.editMenu.addAction(self.cutAct) self.editMenu.addAction(self.copyAct) self.editMenu.addAction(self.pasteAct) self.menuBar().addSeparator() self.helpMenu = self.menuBar().addMenu("&Help") self.helpMenu.addAction(self.aboutAct) self.helpMenu.addAction(self.aboutQtAct) def createToolBars(self): self.fileToolBar = self.addToolBar("File") self.fileToolBar.addAction(self.newAct) self.fileToolBar.addAction(self.openAct) self.fileToolBar.addAction(self.saveAct) self.editToolBar = self.addToolBar("Edit") self.editToolBar.addAction(self.cutAct) self.editToolBar.addAction(self.copyAct) self.editToolBar.addAction(self.pasteAct) def createStatusBar(self): self.statusBar().showMessage("Ready") def readSettings(self): settings = QSettings("Trolltech", "Application Example") pos = settings.value("pos", QPoint(200, 200)) size = settings.value("size", QSize(400, 400)) self.resize(size) self.move(pos) def writeSettings(self): settings = QSettings("Trolltech", "Application Example") settings.setValue("pos", self.pos()) settings.setValue("size", self.size()) def maybeSave(self): if self.textEdit.document().isModified(): ret = QMessageBox.warning(self, "Application", "The document has been modified.\nDo you want to save " "your changes?", QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return self.save() if ret == QMessageBox.Cancel: return False return True def loadFile(self, fileName): file = QFile(fileName) if not file.open(QFile.ReadOnly | QFile.Text): QMessageBox.warning(self, "Application", "Cannot read file %s:\n%s." % (fileName, file.errorString())) return inf = QTextStream(file) QApplication.setOverrideCursor(Qt.WaitCursor) self.textEdit.setPlainText(inf.readAll()) QApplication.restoreOverrideCursor() self.setCurrentFile(fileName) self.statusBar().showMessage("File loaded", 2000) def saveFile(self, fileName): file = QFile(fileName) if not file.open(QFile.WriteOnly | QFile.Text): QMessageBox.warning(self, "Application", "Cannot write file %s:\n%s." % (fileName, file.errorString())) return False outf = QTextStream(file) QApplication.setOverrideCursor(Qt.WaitCursor) outf << self.textEdit.toPlainText() QApplication.restoreOverrideCursor() self.setCurrentFile(fileName); self.statusBar().showMessage("File saved", 2000) return True def setCurrentFile(self, fileName): self.curFile = fileName self.textEdit.document().setModified(False) self.setWindowModified(False) if self.curFile: shownName = self.strippedName(self.curFile) else: shownName = 'OneNote for linux' self.setWindowTitle(shownName) def strippedName(self, fullFileName): return QFileInfo(fullFileName).fileName() def write_at_exit(self): self.dbm_obj.write([self.hierarchy_dict, self.notebook_dict, self.section_dict, self.page_dict])
class SessionsManager(QDialog): """Session Manager, to load different configurations of ninja.""" def __init__(self, parent=None): super(SessionsManager, self).__init__(parent, Qt.Dialog) self._ninja = parent self.setModal(True) self.setWindowTitle(translations.TR_SESSIONS_TITLE) self.setMinimumWidth(550) self.setMinimumHeight(450) self._manager = _SessionManager(parent) self._load_ui() def install(self): self._manager.load_sessions() def _load_ui(self): main_layout = QVBoxLayout(self) main_layout.addWidget(QLabel(translations.TR_SESSIONS_DIALOG_BODY)) main_hbox = QHBoxLayout() # Session list session_layout = QVBoxLayout() self._session_list = QTreeWidget() self._session_list.setHeaderLabels(["Session", "Last Modified"]) session_layout.addWidget(self._session_list) # Content frame content_frame = QFrame() content_frame.hide() frame_layout = QVBoxLayout(content_frame) content_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) session_layout.addWidget(content_frame) frame_layout.setContentsMargins(0, 0, 0, 0) self._content_list = QTreeWidget() self._content_list.setHeaderHidden(True) frame_layout.addWidget(self._content_list) # Separator line line_frame = QFrame() line_frame.setFrameStyle(QFrame.VLine | QFrame.Sunken) # Buttons btn_layout = QVBoxLayout() btn_create = QPushButton(translations.TR_SESSIONS_BTN_CREATE) btn_activate = QPushButton(translations.TR_SESSIONS_BTN_ACTIVATE) btn_update = QPushButton(translations.TR_SESSIONS_BTN_UPDATE) btn_delete = QPushButton(translations.TR_SESSIONS_BTN_DELETE) btn_details = QPushButton(translations.TR_SESSIONS_BTN_DETAILS) btn_details.setCheckable(True) # Add buttons to layout btn_layout.addWidget(btn_create) btn_layout.addWidget(btn_activate) btn_layout.addWidget(btn_update) btn_layout.addWidget(btn_delete) btn_layout.addStretch() btn_layout.addWidget(btn_details) # Add widgets and layouts to the main layout main_layout.addLayout(main_hbox) main_hbox.addLayout(session_layout) main_hbox.addWidget(line_frame) main_hbox.addLayout(btn_layout) main_hbox.setSizeConstraint(QLayout.SetFixedSize) btn_details.toggled[bool].connect(content_frame.setVisible) # Connections self._session_list.itemSelectionChanged.connect( self.load_session_content) btn_activate.clicked.connect(self.open_session) btn_update.clicked.connect(self.save_session) btn_create.clicked.connect(self.create_session) btn_delete.clicked.connect(self.delete_session) def __load_sessions(self): for session_name in self._manager.sessions: item = QTreeWidgetItem() item.setText(0, session_name) item.setText(1, "FIXME: manage this!") self._session_list.addTopLevelItem(item) self._session_list.setCurrentItem( self._session_list.topLevelItem(0)) def load_session_content(self): """Load the selected session, replacing the current session.""" item = self._session_list.currentItem() self._content_list.clear() if item is not None: key = item.text(0) files, projects = self._manager.get_session(key) files_parent = QTreeWidgetItem( self._content_list, [translations.TR_FILES]) for ffile in files: QTreeWidgetItem(files_parent, [ffile[0]]) projects_parent = QTreeWidgetItem( self._content_list, [translations.TR_PROJECT]) for project in projects: QTreeWidgetItem(projects_parent, [project]) files_parent.setExpanded(True) projects_parent.setExpanded(True) def create_session(self): """Create a new Session.""" session_info = QInputDialog.getText( None, translations.TR_SESSIONS_CREATE_TITLE, translations.TR_SESSIONS_CREATE_BODY) if session_info[1]: session_name = session_info[0] if not session_name or session_name in settings.SESSIONS: QMessageBox.information( self, translations.TR_SESSIONS_MESSAGE_TITLE, translations.TR_SESSIONS_MESSAGE_BODY) return self._manager.save_session_data(session_name) self.close() def save_session(self): """Save current session""" if self._session_list.currentItem(): session_name = self._session_list.currentItem().text(0) self._manager.save_session_data(session_name) self._ninja.show_message( translations.TR_SESSIONS_UPDATED_NOTIF.format(session_name)) self.load_session_content() def open_session(self): """Open a saved session""" if self._session_list.currentItem(): session_name = self._session_list.currentItem().text(0) self._manager.load_session(session_name) self._manager.set_current_session(session_name) self.close() def delete_session(self): """Delete a session""" if self._session_list.currentItem(): key = self._session_list.currentItem().text(0) self._manager.delete_session(key) self._session_list.takeTopLevelItem( self._session_list.currentIndex().row()) @property def current_session(self): return self._manager.current_session() def showEvent(self, event): super().showEvent(event) self.__load_sessions() def hideEvent(self, event): super().hideEvent(event) self._session_list.clear()
class JackConnectionsDialog(QDialog): def __init__(self, jack_client, parent=None, **kwargs): super().__init__(parent) self.resize(600, 400) self.setLayout(QGridLayout()) # self.layout().setContentsMargins(0, 0, 0, 0) self.output_widget = QTreeWidget(self) self.output_widget.setHeaderLabels(['Output ports']) self.input_widget = QTreeWidget(self) self.input_widget.setHeaderLabels(['Input ports']) self.connections_widget = ConnectionsWidget(self.output_widget, self.input_widget, parent=self) self.output_widget.itemExpanded.connect(self.connections_widget.update) self.output_widget.itemCollapsed.connect(self.connections_widget.update) self.input_widget.itemExpanded.connect(self.connections_widget.update) self.input_widget.itemCollapsed.connect(self.connections_widget.update) self.input_widget.itemSelectionChanged.connect( self.__input_selection_changed) self.output_widget.itemSelectionChanged.connect( self.__output_selection_changed) self.layout().addWidget(self.output_widget, 0, 0) self.layout().addWidget(self.connections_widget, 0, 1) self.layout().addWidget(self.input_widget, 0, 2) self.layout().setColumnStretch(0, 2) self.layout().setColumnStretch(1, 1) self.layout().setColumnStretch(2, 2) self.connectButton = QPushButton('Connect', self) self.connectButton.clicked.connect(self.__disconnect_selected) self.connectButton.setEnabled(False) self.layout().addWidget(self.connectButton, 1, 1) self.dialogButtons = QDialogButtonBox( QDialogButtonBox.Cancel | QDialogButtonBox.Ok) self.dialogButtons.accepted.connect(self.accept) self.dialogButtons.rejected.connect(self.reject) self.layout().addWidget(self.dialogButtons, 2, 0, 1, 3) self.__jack_client = jack_client self.__selected_in = None self.__selected_out = None self.connections = [] self.update_graph() def set_connections(self, connections): self.connections = connections self.connections_widget.connections = self.connections self.connections_widget.update() def update_graph(self): input_ports = self.__jack_client.get_ports(is_audio=True, is_input=True) self.output_widget.clear() for port in range(8): self.output_widget.addTopLevelItem( QTreeWidgetItem(['output_' + str(port)])) self.input_widget.clear() clients = {} for port in input_ports: client_name = port.name[:port.name.index(':')] if client_name not in clients: clients[client_name] = ClientItem(client_name) self.input_widget.addTopLevelItem(clients[client_name]) clients[client_name].add_port(port.name) def __input_selection_changed(self): if self.input_widget.selectedItems(): self.__selected_in = self.input_widget.selectedItems()[0] else: self.__selected_in = None self.__check_selection() def __output_selection_changed(self): if self.output_widget.selectedItems(): self.__selected_out = self.output_widget.selectedItems()[0] else: self.__selected_out = None self.__check_selection() def __check_selection(self): if self.__selected_in is not None and self.__selected_out is not None: output = self.output_widget.indexOfTopLevelItem(self.__selected_out) self.connectButton.clicked.disconnect() self.connectButton.setEnabled(True) if self.__selected_in.name in self.connections[output]: self.connectButton.setText('Disconnect') self.connectButton.clicked.connect(self.__disconnect_selected) else: self.connectButton.setText('Connect') self.connectButton.clicked.connect(self.__connect_selected) else: self.connectButton.setEnabled(False) def __connect_selected(self): output = self.output_widget.indexOfTopLevelItem(self.__selected_out) self.connections[output].append(self.__selected_in.name) self.connections_widget.update() self.__check_selection() def __disconnect_selected(self): output = self.output_widget.indexOfTopLevelItem(self.__selected_out) self.connections[output].remove(self.__selected_in.name) self.connections_widget.update() self.__check_selection()
class SqlConnectionWidget(QWidget): """ Class implementing a widget showing the SQL connections. @signal tableActivated(str) emitted after the entry for a table has been activated @signal schemaRequested(str) emitted when the schema display is requested @signal cleared() emitted after the connection tree has been cleared """ tableActivated = pyqtSignal(str) schemaRequested = pyqtSignal(str) cleared = pyqtSignal() def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(SqlConnectionWidget, self).__init__(parent) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) self.__connectionTree = QTreeWidget(self) self.__connectionTree.setObjectName("connectionTree") self.__connectionTree.setHeaderLabels([self.tr("Database")]) if qVersion() >= "5.0.0": self.__connectionTree.header().setSectionResizeMode( QHeaderView.Stretch) else: self.__connectionTree.header().setResizeMode(QHeaderView.Stretch) refreshAction = QAction(self.tr("Refresh"), self.__connectionTree) self.__schemaAction = QAction( self.tr("Show Schema"), self.__connectionTree) refreshAction.triggered.connect(self.refresh) self.__schemaAction.triggered.connect(self.showSchema) self.__connectionTree.addAction(refreshAction) self.__connectionTree.addAction(self.__schemaAction) self.__connectionTree.setContextMenuPolicy(Qt.ActionsContextMenu) layout.addWidget(self.__connectionTree) self.__activating = False self.__connectionTree.itemActivated.connect(self.__itemActivated) self.__connectionTree.currentItemChanged.connect( self.__currentItemChanged) self.__activeDb = "" def refresh(self): """ Public slot to refresh the connection tree. """ self.__connectionTree.clear() self.cleared.emit() connectionNames = QSqlDatabase.connectionNames() foundActiveDb = False for name in connectionNames: root = QTreeWidgetItem(self.__connectionTree) db = QSqlDatabase.database(name, False) root.setText(0, self.__dbCaption(db)) if name == self.__activeDb: foundActiveDb = True self.__setActive(root) if db.isOpen(): tables = db.tables() for table in tables: itm = QTreeWidgetItem(root) itm.setText(0, table) if not foundActiveDb and connectionNames: self.__activeDb = connectionNames[0] self.__setActive(self.__connectionTree.topLevelItem(0)) def showSchema(self): """ Public slot to show schema data of a database. """ cItm = self.__connectionTree.currentItem() if cItm is None or cItm.parent() is None: return self.__setActive(cItm.parent()) self.schemaRequested.emit(cItm.text(0)) def __itemActivated(self, itm, column): """ Private slot handling the activation of an item. @param itm reference to the item (QTreeWidgetItem) @param column column that was activated (integer) """ if itm is None: return if not self.__activating: self.__activating = True if itm.parent() is None: self.__setActive(itm) else: self.__setActive(itm.parent()) self.tableActivated.emit(itm.text(0)) self.__activating = False def __currentItemChanged(self, current, previous): """ Private slot handling a change of the current item. @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the previous current item (QTreeWidgetItem) """ self.__schemaAction.setEnabled( current is not None and current.parent() is not None) def __dbCaption(self, db): """ Private method to assemble a string for the caption. @param db reference to the database object (QSqlDatabase) @return caption string (string) """ nm = db.driverName() nm += ":" if db.userName(): nm += db.userName() nm += "@" nm += db.databaseName() return nm def __setBold(self, itm, bold): """ Private slot to set the font to bold. @param itm reference to the item to be changed (QTreeWidgetItem) @param bold flag indicating bold (boolean) """ font = itm.font(0) font.setBold(bold) itm.setFont(0, font) def currentDatabase(self): """ Public method to get the current database. @return reference to the current database (QSqlDatabase) """ return QSqlDatabase.database(self.__activeDb) def __setActive(self, itm): """ Private slot to set an item to active. @param itm reference to the item to set as the active item (QTreeWidgetItem) """ for index in range(self.__connectionTree.topLevelItemCount()): if self.__connectionTree.topLevelItem(index).font(0).bold(): self.__setBold( self.__connectionTree.topLevelItem(index), False) if itm is None: return self.__setBold(itm, True) self.__activeDb = QSqlDatabase.connectionNames()[ self.__connectionTree.indexOfTopLevelItem(itm)]
class SessionsManager(QDialog): """Session Manager, to load different configurations of ninja.""" def __init__(self, parent=None): super(SessionsManager, self).__init__(parent, Qt.Dialog) self._ninja = parent self.setModal(True) self.setWindowTitle(translations.TR_SESSIONS_TITLE) self.setMinimumWidth(550) self.setMinimumHeight(450) self._manager = _SessionManager(parent) self._load_ui() def install(self): self._manager.load_sessions() def _load_ui(self): main_layout = QVBoxLayout(self) main_layout.addWidget(QLabel(translations.TR_SESSIONS_DIALOG_BODY)) main_hbox = QHBoxLayout() # Session list session_layout = QVBoxLayout() self._session_list = QTreeWidget() self._session_list.setHeaderLabels(["Session", "Last Modified"]) session_layout.addWidget(self._session_list) # Content frame content_frame = QFrame() content_frame.hide() frame_layout = QVBoxLayout(content_frame) content_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) session_layout.addWidget(content_frame) frame_layout.setContentsMargins(0, 0, 0, 0) self._content_list = QTreeWidget() self._content_list.setHeaderHidden(True) frame_layout.addWidget(self._content_list) # Separator line line_frame = QFrame() line_frame.setFrameStyle(QFrame.VLine | QFrame.Sunken) # Buttons btn_layout = QVBoxLayout() btn_create = QPushButton(translations.TR_SESSIONS_BTN_CREATE) btn_activate = QPushButton(translations.TR_SESSIONS_BTN_ACTIVATE) btn_update = QPushButton(translations.TR_SESSIONS_BTN_UPDATE) btn_delete = QPushButton(translations.TR_SESSIONS_BTN_DELETE) btn_details = QPushButton(translations.TR_SESSIONS_BTN_DETAILS) btn_details.setCheckable(True) # Add buttons to layout btn_layout.addWidget(btn_create) btn_layout.addWidget(btn_activate) btn_layout.addWidget(btn_update) btn_layout.addWidget(btn_delete) btn_layout.addStretch() btn_layout.addWidget(btn_details) # Add widgets and layouts to the main layout main_layout.addLayout(main_hbox) main_hbox.addLayout(session_layout) main_hbox.addWidget(line_frame) main_hbox.addLayout(btn_layout) main_hbox.setSizeConstraint(QLayout.SetFixedSize) btn_details.toggled[bool].connect(content_frame.setVisible) # Connections self._session_list.itemSelectionChanged.connect( self.load_session_content) btn_activate.clicked.connect(self.open_session) btn_update.clicked.connect(self.save_session) btn_create.clicked.connect(self.create_session) btn_delete.clicked.connect(self.delete_session) def __load_sessions(self): for session_name in self._manager.sessions: item = QTreeWidgetItem() item.setText(0, session_name) item.setText(1, "FIXME: manage this!") self._session_list.addTopLevelItem(item) self._session_list.setCurrentItem(self._session_list.topLevelItem(0)) def load_session_content(self): """Load the selected session, replacing the current session.""" item = self._session_list.currentItem() self._content_list.clear() if item is not None: key = item.text(0) files, projects = self._manager.get_session(key) files_parent = QTreeWidgetItem(self._content_list, [translations.TR_FILES]) for ffile in files: QTreeWidgetItem(files_parent, [ffile[0]]) projects_parent = QTreeWidgetItem(self._content_list, [translations.TR_PROJECT]) for project in projects: QTreeWidgetItem(projects_parent, [project]) files_parent.setExpanded(True) projects_parent.setExpanded(True) def create_session(self): """Create a new Session.""" session_info = QInputDialog.getText( None, translations.TR_SESSIONS_CREATE_TITLE, translations.TR_SESSIONS_CREATE_BODY) if session_info[1]: session_name = session_info[0] if not session_name or session_name in settings.SESSIONS: QMessageBox.information(self, translations.TR_SESSIONS_MESSAGE_TITLE, translations.TR_SESSIONS_MESSAGE_BODY) return self._manager.save_session_data(session_name) self.close() def save_session(self): """Save current session""" if self._session_list.currentItem(): session_name = self._session_list.currentItem().text(0) self._manager.save_session_data(session_name) self._ninja.show_message( translations.TR_SESSIONS_UPDATED_NOTIF.format(session_name)) self.load_session_content() def open_session(self): """Open a saved session""" if self._session_list.currentItem(): session_name = self._session_list.currentItem().text(0) self._manager.load_session(session_name) self._manager.set_current_session(session_name) self.close() def delete_session(self): """Delete a session""" if self._session_list.currentItem(): key = self._session_list.currentItem().text(0) self._manager.delete_session(key) self._session_list.takeTopLevelItem( self._session_list.currentIndex().row()) @property def current_session(self): return self._manager.current_session() def showEvent(self, event): super().showEvent(event) self.__load_sessions() def hideEvent(self, event): super().hideEvent(event) self._session_list.clear()
class FinalExamsTab: model: GuiModel def __init__(self, model): self.__final_exam_list = QTreeWidget() self.__final_exam_list.setHeaderLabels(["Date and time", "Room"]) self.__final_exam_list.setHeaderLabels(["Date", "Time", "Day", "Room"]) self.__final_exam_list.itemSelectionChanged.connect( self.__final_exam_selected) self.__final_exam_details = QTreeWidget() self.__final_exam_details.setColumnCount(3) self.__final_exam_details.setHeaderLabels( ["Student", "Points", "Grade"]) self.__final_exam_details.itemActivated.connect( lambda item, column: file_double_clicked(self.model, item)) self.__final_exam_details.itemSelectionChanged.connect( self.__final_exam_file_selected) self.__final_exam_file_details = FileDetailsWidget(model) details_splitter = QSplitter(Qt.Horizontal) details_splitter.addWidget(self.__final_exam_details) details_splitter.addWidget(self.__final_exam_file_details.widget) self.widget = QSplitter(Qt.Vertical) self.widget.addWidget(self.__final_exam_list) self.widget.addWidget(details_splitter) self.widget.setStretchFactor(0, 3) self.widget.setStretchFactor(1, 1) self.model = model self.__load_final_exam() self.model.subject_changed.connect(self.__subject_changed) def __subject_changed(self): self.__load_final_exam() def __load_final_exam(self): self.__final_exam_list.clear() self.__final_exam_details.clear() if self.model.subject is not None: for final_exam in self.model.subject.final_exams: final_exam_item = FinalExamItem([ final_exam.date_time.strftime("%d.%m.%Y"), final_exam.date_time.strftime("%H:%M"), final_exam.date_time.strftime("%A"), final_exam.room ], final_exam) self.__final_exam_list.addTopLevelItem(final_exam_item) def __final_exam_selected(self): self.__final_exam_details.clear() final_exam_items = self.__final_exam_list.selectedItems() if final_exam_items: final_exam: FinalExam = final_exam_items[0].final_exam for grade in final_exam.get_submissions(): student_item = StudentItem([ f"{grade.student.surname} {grade.student.name}", points_or_none(grade.points), grade_or_none(grade.grade) ], grade.student) self.__final_exam_details.addTopLevelItem(student_item) add_file_items(grade.files, student_item) self.__final_exam_details.expandAll() def __final_exam_file_selected(self): self.__final_exam_file_details.master_selection_changed( self.__final_exam_details.selectedItems())
class WidgetList(QWidget): def __init__(self, parent=None): super().__init__(parent) self.id_dict: Dict[int, Tuple[List[str], int]] = {} self.item_dict: DefaultDict[str, Set[tuple]] = defaultdict(set) self._init_ui() def _init_ui(self): layout = QVBoxLayout(self) self.splitter = QSplitter(orientation=Qt.Vertical, parent=self) self.label = QLabel('Number of widgets (not calculated)') self.update_button = QPushButton('Update') self.widget_list = QTreeWidget(self) self.differ_list = QListWidget(self) self.search_widget = QWidget(self) self.search_line = QLineEdit(self.search_widget) self.search_tree = QTreeWidget(self.search_widget) self.widget_list.setHeaderLabels( ['Class name', 'Object name', 'Number of children', 'id']) self.search_tree.setHeaderLabels( ['Class name', 'Object name', 'Number of children', 'id']) s_layout = QVBoxLayout(self.search_widget) s_layout.addWidget(self.search_line) s_layout.addWidget(self.search_tree) layout.addWidget(self.label) layout.addWidget(self.update_button) layout.addWidget(self.splitter) self.splitter.addWidget(self.widget_list) self.splitter.addWidget(self.differ_list) self.splitter.addWidget(self.search_widget) self.update_button.clicked.connect(self.update_widget_list) self.search_line.editingFinished.connect(self._update_search_list) self.differ_list.hide() @pyqtSlot(name='updateSearchList') def _update_search_list(self): search: str = self.search_line.text().lower() self.search_tree.clear() for key, value in self.item_dict.items(): if key.lower().startswith(search): for name_list in value: TreeWidgetItem(self.search_tree, name_list) def update_widget_list(self): self.widget_list.clear() self.differ_list.clear() self.item_dict.clear() count = 0 new_id_dict: Dict[int, Tuple[list, int]] = {} for obj in QApplication.topLevelWidgets(): count += self._update_list(obj, self.widget_list, new_id_dict) self.widget_list.update() self.label.setText(f'Number of widgets = {count}') self.widget_list.sortByColumn(2, Qt.DescendingOrder) for value in self.id_dict.values(): QListWidgetItem(' '.join(value[0]), self.differ_list) self.id_dict = new_id_dict def _update_list(self, parent_obj: QObject, parent_node: QTreeWidget or QTreeWidgetItem, new_id_dict: Dict[int, Tuple[List[str], int]]): count = 0 for obj in parent_obj.children(): name_list = self._get_name_list(obj) item = TreeWidgetItem(parent_node, name_list) obj_count = 0 for o in obj.children(): obj_count += self._update_list(o, item, new_id_dict) item.setText(2, str(obj_count)) name_list[2] = str(obj_count) self.item_dict[str(obj.__class__.__name__)].add(tuple(name_list)) id_ = id(obj) new_id_dict[id_] = [item.text(i) for i in range(4)], obj_count _, prev_count = self.id_dict.pop(id_, (None, None)) if prev_count is None: fr = 1 elif prev_count + obj_count == 0: fr = 0 else: fr = abs(prev_count - obj_count) / (prev_count + obj_count) item.setBackground(3, get_color(fr)) count += 1 + obj_count return count @staticmethod def _get_name_list(obj: QObject): return [ str(obj.__class__.__name__), obj.objectName(), '', str(id(obj)) ]
class tree(QWidget): '''Copied from Jiliang's Github: https://github.com/ligerliu/pyH5_GUI/blob/master/pyH5_GUI/NewTree.py ''' (FILE, FILE_PATH, H5GROUP) = range(3) def __init__(self): super().__init__() self.title = 'Tree of h5 data' self.left = 10 self.top = 10 self.width = 720 self.height = 640 self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.datalayout = QVBoxLayout() self.tree = QTreeWidget() header = QTreeWidgetItem(['File']) self.tree.setHeaderItem(header) self.datalayout.addWidget(self.tree) self.group_root = None def clear(self): self.tree.clear() def add_group(self, group): self.group_name = os.path.basename(group) self.group_file_path = group self.group_root = QTreeWidgetItem( self.tree, [self.group_name, self.group_file_path, '']) def add_file(self, h5file, group=None): self.h5_file_path = h5file self.f = h5py.File(h5file, 'r') self.filename = self.f.filename.split('/')[-1] if group is None: self.tree_root = QTreeWidgetItem( self.tree, [self.filename, self.h5_file_path, '']) self.add_branch(self.tree_root, self.f) else: #print('add group here') if self.group_root is None: self.add_group(group) hdf_branch = QTreeWidgetItem( [self.filename, self.h5_file_path, '']) print(self.filename, self.h5_file_path) self.group_root.addChild(hdf_branch) self.add_branch(hdf_branch, self.f) self.tree.setColumnWidth(0, 250) self.tree.setColumnWidth(1, 0) self.tree.setColumnWidth(2, 0) def add_branch(self, tree_root, h5file): for _ in h5file.keys(): #print(_) branch = QTreeWidgetItem([ str(h5file[_].name).split('/')[-1], str(self.h5_file_path), str(h5file[_].name) ]) tree_root.addChild(branch) if isinstance(h5file[_], h5py.Group): self.add_branch(branch, h5file[_]) @pyqtSlot(QTreeWidgetItem, int) def onItemClicked(self, item): print(self.filename, item.text(2))
class DebugViewer(QWidget): """ Class implementing a widget conatining various debug related views. The individual tabs contain the interpreter shell (optional), the filesystem browser (optional), the two variables viewers (global and local), a breakpoint viewer, a watch expression viewer and the exception logger. Additionally a list of all threads is shown. @signal sourceFile(string, int) emitted to open a source file at a line """ sourceFile = pyqtSignal(str, int) def __init__(self, debugServer, docked, vm, parent=None, embeddedShell=True, embeddedBrowser=True): """ Constructor @param debugServer reference to the debug server object @param docked flag indicating a dock window @param vm reference to the viewmanager object @param parent parent widget (QWidget) @param embeddedShell flag indicating whether the shell should be included. This flag is set to False by those layouts, that have the interpreter shell in a separate window. @param embeddedBrowser flag indicating whether the file browser should be included. This flag is set to False by those layouts, that have the file browser in a separate window or embedded in the project browser instead. """ super(DebugViewer, self).__init__(parent) self.debugServer = debugServer self.debugUI = None self.setWindowIcon(UI.PixmapCache.getIcon("eric.png")) self.__mainLayout = QVBoxLayout() self.__mainLayout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.__mainLayout) self.__tabWidget = E5TabWidget() self.__mainLayout.addWidget(self.__tabWidget) self.embeddedShell = embeddedShell if embeddedShell: from QScintilla.Shell import ShellAssembly # add the interpreter shell self.shellAssembly = ShellAssembly(debugServer, vm, False) self.shell = self.shellAssembly.shell() index = self.__tabWidget.addTab( self.shellAssembly, UI.PixmapCache.getIcon("shell.png"), '') self.__tabWidget.setTabToolTip(index, self.shell.windowTitle()) self.embeddedBrowser = embeddedBrowser if embeddedBrowser: from UI.Browser import Browser # add the browser self.browser = Browser() index = self.__tabWidget.addTab( self.browser, UI.PixmapCache.getIcon("browser.png"), '') self.__tabWidget.setTabToolTip(index, self.browser.windowTitle()) from .VariablesViewer import VariablesViewer # add the global variables viewer self.glvWidget = QWidget() self.glvWidgetVLayout = QVBoxLayout(self.glvWidget) self.glvWidgetVLayout.setContentsMargins(0, 0, 0, 0) self.glvWidgetVLayout.setSpacing(3) self.glvWidget.setLayout(self.glvWidgetVLayout) self.globalsViewer = VariablesViewer(self.glvWidget, True) self.glvWidgetVLayout.addWidget(self.globalsViewer) self.glvWidgetHLayout = QHBoxLayout() self.glvWidgetHLayout.setContentsMargins(3, 3, 3, 3) self.globalsFilterEdit = QLineEdit(self.glvWidget) self.globalsFilterEdit.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed) self.glvWidgetHLayout.addWidget(self.globalsFilterEdit) self.globalsFilterEdit.setToolTip( self.tr("Enter regular expression patterns separated by ';'" " to define variable filters. ")) self.globalsFilterEdit.setWhatsThis( self.tr("Enter regular expression patterns separated by ';'" " to define variable filters. All variables and" " class attributes matched by one of the expressions" " are not shown in the list above.")) self.setGlobalsFilterButton = QPushButton( self.tr('Set'), self.glvWidget) self.glvWidgetHLayout.addWidget(self.setGlobalsFilterButton) self.glvWidgetVLayout.addLayout(self.glvWidgetHLayout) index = self.__tabWidget.addTab( self.glvWidget, UI.PixmapCache.getIcon("globalVariables.png"), '') self.__tabWidget.setTabToolTip(index, self.globalsViewer.windowTitle()) self.setGlobalsFilterButton.clicked.connect( self.__setGlobalsFilter) self.globalsFilterEdit.returnPressed.connect(self.__setGlobalsFilter) # add the local variables viewer self.lvWidget = QWidget() self.lvWidgetVLayout = QVBoxLayout(self.lvWidget) self.lvWidgetVLayout.setContentsMargins(0, 0, 0, 0) self.lvWidgetVLayout.setSpacing(3) self.lvWidget.setLayout(self.lvWidgetVLayout) self.lvWidgetHLayout1 = QHBoxLayout() self.lvWidgetHLayout1.setContentsMargins(3, 3, 3, 3) self.stackComboBox = QComboBox(self.lvWidget) self.stackComboBox.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed) self.lvWidgetHLayout1.addWidget(self.stackComboBox) self.sourceButton = QPushButton(self.tr('Source'), self.lvWidget) self.lvWidgetHLayout1.addWidget(self.sourceButton) self.sourceButton.setEnabled(False) self.lvWidgetVLayout.addLayout(self.lvWidgetHLayout1) self.localsViewer = VariablesViewer(self.lvWidget, False) self.lvWidgetVLayout.addWidget(self.localsViewer) self.lvWidgetHLayout2 = QHBoxLayout() self.lvWidgetHLayout2.setContentsMargins(3, 3, 3, 3) self.localsFilterEdit = QLineEdit(self.lvWidget) self.localsFilterEdit.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed) self.lvWidgetHLayout2.addWidget(self.localsFilterEdit) self.localsFilterEdit.setToolTip( self.tr( "Enter regular expression patterns separated by ';' to define " "variable filters. ")) self.localsFilterEdit.setWhatsThis( self.tr( "Enter regular expression patterns separated by ';' to define " "variable filters. All variables and class attributes matched" " by one of the expressions are not shown in the list above.")) self.setLocalsFilterButton = QPushButton( self.tr('Set'), self.lvWidget) self.lvWidgetHLayout2.addWidget(self.setLocalsFilterButton) self.lvWidgetVLayout.addLayout(self.lvWidgetHLayout2) index = self.__tabWidget.addTab( self.lvWidget, UI.PixmapCache.getIcon("localVariables.png"), '') self.__tabWidget.setTabToolTip(index, self.localsViewer.windowTitle()) self.sourceButton.clicked.connect(self.__showSource) self.stackComboBox.currentIndexChanged[int].connect( self.__frameSelected) self.setLocalsFilterButton.clicked.connect(self.__setLocalsFilter) self.localsFilterEdit.returnPressed.connect(self.__setLocalsFilter) from .CallStackViewer import CallStackViewer # add the call stack viewer self.callStackViewer = CallStackViewer(self.debugServer) index = self.__tabWidget.addTab( self.callStackViewer, UI.PixmapCache.getIcon("step.png"), "") self.__tabWidget.setTabToolTip( index, self.callStackViewer.windowTitle()) self.callStackViewer.sourceFile.connect(self.sourceFile) self.callStackViewer.frameSelected.connect( self.__callStackFrameSelected) from .CallTraceViewer import CallTraceViewer # add the call trace viewer self.callTraceViewer = CallTraceViewer(self.debugServer) index = self.__tabWidget.addTab( self.callTraceViewer, UI.PixmapCache.getIcon("callTrace.png"), "") self.__tabWidget.setTabToolTip( index, self.callTraceViewer.windowTitle()) self.callTraceViewer.sourceFile.connect(self.sourceFile) from .BreakPointViewer import BreakPointViewer # add the breakpoint viewer self.breakpointViewer = BreakPointViewer() self.breakpointViewer.setModel(self.debugServer.getBreakPointModel()) index = self.__tabWidget.addTab( self.breakpointViewer, UI.PixmapCache.getIcon("breakpoints.png"), '') self.__tabWidget.setTabToolTip( index, self.breakpointViewer.windowTitle()) self.breakpointViewer.sourceFile.connect(self.sourceFile) from .WatchPointViewer import WatchPointViewer # add the watch expression viewer self.watchpointViewer = WatchPointViewer() self.watchpointViewer.setModel(self.debugServer.getWatchPointModel()) index = self.__tabWidget.addTab( self.watchpointViewer, UI.PixmapCache.getIcon("watchpoints.png"), '') self.__tabWidget.setTabToolTip( index, self.watchpointViewer.windowTitle()) from .ExceptionLogger import ExceptionLogger # add the exception logger self.exceptionLogger = ExceptionLogger() index = self.__tabWidget.addTab( self.exceptionLogger, UI.PixmapCache.getIcon("exceptions.png"), '') self.__tabWidget.setTabToolTip( index, self.exceptionLogger.windowTitle()) if self.embeddedShell: self.__tabWidget.setCurrentWidget(self.shellAssembly) else: if self.embeddedBrowser: self.__tabWidget.setCurrentWidget(self.browser) else: self.__tabWidget.setCurrentWidget(self.glvWidget) # add the threads viewer self.__mainLayout.addWidget(QLabel(self.tr("Threads:"))) self.__threadList = QTreeWidget() self.__threadList.setHeaderLabels( [self.tr("ID"), self.tr("Name"), self.tr("State"), ""]) self.__threadList.setSortingEnabled(True) self.__mainLayout.addWidget(self.__threadList) self.__doThreadListUpdate = True self.__threadList.currentItemChanged.connect(self.__threadSelected) self.__mainLayout.setStretchFactor(self.__tabWidget, 5) self.__mainLayout.setStretchFactor(self.__threadList, 1) self.currPage = None self.currentStack = None self.framenr = 0 self.debugServer.clientStack.connect(self.handleClientStack) self.__autoViewSource = Preferences.getDebugger("AutoViewSourceCode") self.sourceButton.setVisible(not self.__autoViewSource) def preferencesChanged(self): """ Public slot to handle the preferencesChanged signal. """ self.__autoViewSource = Preferences.getDebugger("AutoViewSourceCode") self.sourceButton.setVisible(not self.__autoViewSource) def setDebugger(self, debugUI): """ Public method to set a reference to the Debug UI. @param debugUI reference to the DebugUI object (DebugUI) """ self.debugUI = debugUI self.debugUI.clientStack.connect(self.handleClientStack) self.callStackViewer.setDebugger(debugUI) def handleResetUI(self): """ Public method to reset the SBVviewer. """ self.globalsViewer.handleResetUI() self.localsViewer.handleResetUI() self.__setGlobalsFilter() self.__setLocalsFilter() self.sourceButton.setEnabled(False) self.currentStack = None self.stackComboBox.clear() self.__threadList.clear() if self.embeddedShell: self.__tabWidget.setCurrentWidget(self.shellAssembly) else: if self.embeddedBrowser: self.__tabWidget.setCurrentWidget(self.browser) else: self.__tabWidget.setCurrentWidget(self.glvWidget) self.breakpointViewer.handleResetUI() def handleRawInput(self): """ Public slot to handle the switch to the shell in raw input mode. """ if self.embeddedShell: self.saveCurrentPage() self.__tabWidget.setCurrentWidget(self.shellAssembly) def initCallStackViewer(self, projectMode): """ Public method to initialize the call stack viewer. @param projectMode flag indicating to enable the project mode (boolean) """ self.callStackViewer.clear() self.callStackViewer.setProjectMode(projectMode) def isCallTraceEnabled(self): """ Public method to get the state of the call trace function. @return flag indicating the state of the call trace function (boolean) """ return self.callTraceViewer.isCallTraceEnabled() def clearCallTrace(self): """ Public method to clear the recorded call trace. """ self.callTraceViewer.clear() def setCallTraceToProjectMode(self, enabled): """ Public slot to set the call trace viewer to project mode. In project mode the call trace info is shown with project relative path names. @param enabled flag indicating to enable the project mode (boolean) """ self.callTraceViewer.setProjectMode(enabled) def showVariables(self, vlist, globals): """ Public method to show the variables in the respective window. @param vlist list of variables to display @param globals flag indicating global/local state """ if globals: self.globalsViewer.showVariables(vlist, self.framenr) else: self.localsViewer.showVariables(vlist, self.framenr) def showVariable(self, vlist, globals): """ Public method to show the variables in the respective window. @param vlist list of variables to display @param globals flag indicating global/local state """ if globals: self.globalsViewer.showVariable(vlist) else: self.localsViewer.showVariable(vlist) def showVariablesTab(self, globals): """ Public method to make a variables tab visible. @param globals flag indicating global/local state """ if globals: self.__tabWidget.setCurrentWidget(self.glvWidget) else: self.__tabWidget.setCurrentWidget(self.lvWidget) def saveCurrentPage(self): """ Public slot to save the current page. """ self.currPage = self.__tabWidget.currentWidget() def restoreCurrentPage(self): """ Public slot to restore the previously saved page. """ if self.currPage is not None: self.__tabWidget.setCurrentWidget(self.currPage) def handleClientStack(self, stack): """ Public slot to show the call stack of the program being debugged. @param stack list of tuples with call stack data (file name, line number, function name, formatted argument/values list) """ block = self.stackComboBox.blockSignals(True) self.framenr = 0 self.stackComboBox.clear() self.currentStack = stack self.sourceButton.setEnabled(len(stack) > 0) for s in stack: # just show base filename to make it readable s = (os.path.basename(s[0]), s[1], s[2]) self.stackComboBox.addItem('{0}:{1}:{2}'.format(*s)) self.stackComboBox.blockSignals(block) def setVariablesFilter(self, globalsFilter, localsFilter): """ Public slot to set the local variables filter. @param globalsFilter filter list for global variable types (list of int) @param localsFilter filter list for local variable types (list of int) """ self.globalsFilter = globalsFilter self.localsFilter = localsFilter def __showSource(self): """ Private slot to handle the source button press to show the selected file. """ index = self.stackComboBox.currentIndex() if index > -1 and self.currentStack: s = self.currentStack[index] self.sourceFile.emit(s[0], int(s[1])) def __frameSelected(self, frmnr): """ Private slot to handle the selection of a new stack frame number. @param frmnr frame number (0 is the current frame) (int) """ self.framenr = frmnr self.debugServer.remoteClientVariables(0, self.localsFilter, frmnr) if self.__autoViewSource: self.__showSource() def __setGlobalsFilter(self): """ Private slot to set the global variable filter. """ filter = self.globalsFilterEdit.text() self.debugServer.remoteClientSetFilter(1, filter) self.debugServer.remoteClientVariables(2, self.globalsFilter) def __setLocalsFilter(self): """ Private slot to set the local variable filter. """ filter = self.localsFilterEdit.text() self.debugServer.remoteClientSetFilter(0, filter) if self.currentStack: self.debugServer.remoteClientVariables( 0, self.localsFilter, self.framenr) def handleDebuggingStarted(self): """ Public slot to handle the start of a debugging session. This slot sets the variables filter expressions. """ self.__setGlobalsFilter() self.__setLocalsFilter() self.showVariablesTab(False) def currentWidget(self): """ Public method to get a reference to the current widget. @return reference to the current widget (QWidget) """ return self.__tabWidget.currentWidget() def setCurrentWidget(self, widget): """ Public slot to set the current page based on the given widget. @param widget reference to the widget (QWidget) """ self.__tabWidget.setCurrentWidget(widget) def showThreadList(self, currentID, threadList): """ Public method to show the thread list. @param currentID id of the current thread (integer) @param threadList list of dictionaries containing the thread data """ citm = None self.__threadList.clear() for thread in threadList: if thread['broken']: state = self.tr("waiting at breakpoint") else: state = self.tr("running") itm = QTreeWidgetItem(self.__threadList, ["{0:d}".format(thread['id']), thread['name'], state]) if thread['id'] == currentID: citm = itm self.__threadList.header().resizeSections(QHeaderView.ResizeToContents) self.__threadList.header().setStretchLastSection(True) if citm: self.__doThreadListUpdate = False self.__threadList.setCurrentItem(citm) self.__doThreadListUpdate = True def __threadSelected(self, current, previous): """ Private slot to handle the selection of a thread in the thread list. @param current reference to the new current item (QTreeWidgetItem) @param previous reference to the previous current item (QTreeWidgetItem) """ if current is not None and self.__doThreadListUpdate: tid = int(current.text(0)) self.debugServer.remoteSetThread(tid) def __callStackFrameSelected(self, frameNo): """ Private slot to handle the selection of a call stack entry of the call stack viewer. @param frameNo frame number (index) of the selected entry (integer) """ if frameNo >= 0: self.stackComboBox.setCurrentIndex(frameNo)
class PackageEditor(QGridLayout): """ A Python package editor. Note that this is a QLayout and not a QWidget. """ # Emitted when the package has changed. package_changed = pyqtSignal() def __init__(self, show_root=False, scan="Scan", scan_whats_this='', whats_this=''): """ Initialise the editor. """ super().__init__() self.package = None self.project = None self._show_root = show_root self._package_edit = QTreeWidget(whatsThis=whats_this) self._package_edit.header().hide() self._package_edit.itemChanged.connect(self._package_changed) self.addWidget(self._package_edit, 0, 0, 3, 1) self._scan_button = QPushButton(scan, whatsThis=scan_whats_this, clicked=self._scan, enabled=False) self.addWidget(self._scan_button, 0, 1) self._remove_button = QPushButton("Remove all", whatsThis="Remove all of the scanned directories and files.", clicked=self._remove_all, enabled=False) self.addWidget(self._remove_button, 0, 2) self._include_button = QPushButton("Include all", whatsThis="Select all of the scanned directories and files.", clicked=self._include_all, enabled=False) self.addWidget(self._include_button, 1, 1) self._exclude_button = QPushButton("Exclude all", whatsThis="Deselect all of the scanned directories and files.", clicked=self._exclude_all, enabled=False) self.addWidget(self._exclude_button, 1, 2) self._exclusions_edit = QTreeWidget( whatsThis="Any directory or file that matches any of the " "these patterns will be automatically ignored when " "scanning. Double-click on a pattern to edit or remove " "it. Double-click below the last pattern in order to " "add a new one.") self._exclusions_edit.setHeaderLabel("Exclusions") self._exclusions_edit.setEditTriggers( QTreeWidget.DoubleClicked|QTreeWidget.SelectedClicked| QTreeWidget.EditKeyPressed) self._exclusions_edit.setRootIsDecorated(False) self._exclusions_edit.itemChanged.connect(self._exclusion_changed) self.addWidget(self._exclusions_edit, 2, 1, 1, 2) def configure(self, package, project): """ Configure the editor with the contents of the given package and project. """ # Save the configuration. self.package = package self.project = project # Set the package itself. self._visualise() # Set the exclusions. self._exclusions_edit.clear() for exclude in package.exclusions: self._add_exclusion_item(exclude) # Add one to be edited to create a new entry. self._add_exclusion_item() self._scan_button.setEnabled(package is not None) def get_root_dir(self): """ Return the root directory to scan, or '' if there was an error or the user cancelled. """ raise NotImplementedError def filter(self, name): """ See if a scanned name should be discarded. """ # Include everything by default. return False def required(self, name): """ See if a scanned name is required. """ # Nothing is required by default. return False def _add_exclusion_item(self, exclude=''): """ Add a QTreeWidgetItem that holds an exclusion. """ itm = QTreeWidgetItem([exclude]) itm.setFlags( Qt.ItemIsSelectable|Qt.ItemIsEditable|Qt.ItemIsEnabled| Qt.ItemNeverHasChildren) self._exclusions_edit.addTopLevelItem(itm) def _exclusion_changed(self, itm, column): """ Invoked when an exclusion has changed. """ exc_edit = self._exclusions_edit new_exc = itm.data(0, Qt.DisplayRole).strip() itm_index = exc_edit.indexOfTopLevelItem(itm) if new_exc != '': # See if we have added a new one. if itm_index == exc_edit.topLevelItemCount() - 1: self._add_exclusion_item() else: # It is empty so remove it. exc_edit.takeTopLevelItem(itm_index) # Save the new exclusions. self.package.exclusions = [ exc_edit.topLevelItem(i).data(0, Qt.DisplayRole).strip() for i in range(exc_edit.topLevelItemCount() - 1)] self.package_changed.emit() def _get_items(self): """ Return an iterator over the tree widget items. """ it = QTreeWidgetItemIterator(self._package_edit) if self._show_root: it += 1 itm = it.value() while itm is not None: yield itm it += 1 itm = it.value() def _include_all(self, _): """ Invoked when the user clicks on the include all button. """ for itm in self._get_items(): itm.setCheckState(0, Qt.Checked) def _exclude_all(self, _): """ Invoked when the user clicks on the exclude all button. """ for itm in self._get_items(): if not itm.isDisabled(): itm.setCheckState(0, Qt.Unchecked) itm.setExpanded(False) def _remove_all(self, _): """ Invoked when the use clicks on the remove all button. """ blocked = self._package_edit.blockSignals(True) self._package_edit.clear() self._package_edit.blockSignals(blocked) self._enable_buttons() # This is a bit of a hack but is currently the only way to completely # remove the application package. if self._show_root: self.package.name = '' del self.package.contents[:] self.package_changed.emit() def _enable_buttons(self): """ Set the enabled state of those buttons that require content. """ enable = (len(list(self._get_items())) != 0) self._remove_button.setEnabled(enable) self._include_button.setEnabled(enable) self._exclude_button.setEnabled(enable) def _scan(self, _): """ Invoked when the user clicks on the scan button. """ project = self.project package = self.package # Get the root directory to scan. root = self.get_root_dir() if root == '': return # Save the included state of any existing contents so that they can be # restored after the scan. old_state = {} for itm in self._get_items(): rel_path = [itm.data(0, Qt.DisplayRole)] parent = itm.parent() while parent is not None: rel_path.append(parent.data(0, Qt.DisplayRole)) parent = parent.parent() rel_path.reverse() if self._show_root: rel_path = rel_path[1:] old_state['/'.join(rel_path)] = (itm.checkState(0) == Qt.Checked) # Walk the package. root_dir = QDir(root) if not root_dir.exists(): QMessageBox.warning(self.parentWidget(), "Scan Directory", "{0} is not a valid directory.".format( QDir.toNativeSeparators(root))) return self._add_to_container(package, root_dir, [], old_state) self._visualise() self.package_changed.emit() def _add_to_container(self, container, content_dir, dir_stack, old_state): """ Add the files and directories of a package or sub-package to a container. """ dir_contents = content_dir.entryInfoList( QDir.Files|QDir.Dirs|QDir.NoDotAndDotDot) # Make sure any filter is applied in a predictable order. dir_contents.sort(key=lambda fi: fi.fileName().lower()[1:] if fi.fileName().startswith('_') else fi.fileName().lower()) dir_stack.append(content_dir.dirName()) contents = [] for content in dir_contents: name = content.fileName() # Apply any exclusions. for exc in self.package.exclusions: if fnmatch.fnmatch(name, exc): name = None break if name is None: continue # Apply any filter. if len(dir_stack) > 1: module_path = dir_stack[1:] module_path.append(name) path_name = '/'.join(module_path) else: path_name = name if self.filter(path_name): continue # See if we already know the included state. included = old_state.get(path_name, False) # Add the content. if content.isDir(): qrc = QrcDirectory(name, included) self._add_to_container(qrc, QDir(content.canonicalFilePath()), dir_stack, old_state) elif content.isFile(): qrc = QrcFile(name, included) else: continue contents.append(qrc) container.contents = contents dir_stack.pop() def _visualise(self): """ Update the GUI with the package content. """ blocked = self._package_edit.blockSignals(True) self._package_edit.clear() if self.package.name is not None: if self._show_root: parent = QTreeWidgetItem([':/' + self.package.name]) self._package_edit.addTopLevelItem(parent) parent.setExpanded(True) else: parent = self._package_edit self._visualise_contents(self.package.contents, parent) self._package_edit.blockSignals(blocked) self._enable_buttons() def _visualise_contents(self, contents, parent): """ Visualise the contents for a parent. """ p = parent while p is not None and isinstance(p, QTreeWidgetItem): p = p.parent() for content in contents: itm = QTreeWidgetItem(parent, [content.name]) itm.setCheckState(0, Qt.Checked if content.included else Qt.Unchecked) itm.setData(0, Qt.UserRole, content) if isinstance(content, QrcDirectory): self._visualise_contents(content.contents, itm) def _package_changed(self, itm, column): """ Invoked when part of the package changes. """ if itm.checkState(0) == Qt.Checked: itm.data(0, Qt.UserRole).included = True itm.setExpanded(True) else: self._exclude(itm) itm.setExpanded(False) self.package_changed.emit() def _exclude(self, itm): """ Exclude an item and any children it may have. """ for idx in range(itm.childCount()): self._exclude(itm.child(idx)) itm.data(0, Qt.UserRole).included = False itm.setCheckState(0, Qt.Unchecked)
class OtherPackagesPage(QWidget): """ The GUI for the other packages page of a project. """ # The page's label. label = "Other Packages" @property def project(self): """ The project property getter. """ return self._project @project.setter def project(self, value): """ The project property setter. """ if self._project != value: self._project = value self._package_delegate.set_project(value) self._update_page() def __init__(self): """ Initialise the page. """ super().__init__() self._project = None # Create the page's GUI. layout = QHBoxLayout() self._package_selector = QTreeWidget( whatsThis="This shows a list of directories containing " "additional Python packages which can be scanned when " "selected. Double-click on a directory to edit or " "remove it. Double-click below the last directory in " "order to add a new one.") self._package_selector.setHeaderLabel("Packages Directory") self._package_selector.setEditTriggers( QTreeWidget.DoubleClicked|QTreeWidget.SelectedClicked| QTreeWidget.EditKeyPressed) self._package_selector.setRootIsDecorated(False) self._package_selector.currentItemChanged.connect( self._package_selector_changed) self._package_selector.itemChanged.connect(self._package_dir_changed) self._package_delegate = FilenameEditorDelegate("Packages Directory", directory=True) self._package_selector.setItemDelegateForColumn(0, self._package_delegate) layout.addWidget(self._package_selector) self._package_edit = _PackageDirectoryEditor() self._package_edit.package_changed.connect(self._package_changed) package_edit_gb = QGroupBox(self._package_edit.title) package_edit_gb.setLayout(self._package_edit) layout.addWidget(package_edit_gb) self.setLayout(layout) def _update_page(self): """ Update the page using the current project. """ project = self.project self._package_selector.clear() for package in project.other_packages: self._add_package_dir(package) self._add_package_dir() def _add_package_dir(self, package=None): """ Add a QTreeWidgetItem that holds a package directory. """ if package is None: package = QrcPackage() name = '' else: name = package.name itm = QTreeWidgetItem([name]) itm.setData(0, Qt.UserRole, package) itm.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled|Qt.ItemNeverHasChildren|Qt.ItemIsEditable) self._package_selector.addTopLevelItem(itm) def _package_selector_changed(self, new, old): """ Invoked when the user selects a package directory. """ if new is not None: self._package_edit.configure(new.data(0, Qt.UserRole), self.project) def _package_dir_changed(self, itm, column): """ Invoked when the user edits a package directory name. """ project = self.project selector = self._package_selector new_dir = itm.data(0, Qt.DisplayRole).strip() itm_index = selector.indexOfTopLevelItem(itm) if new_dir != '': itm.data(0, Qt.UserRole).name = project.path_to_user(new_dir) # See if we have added a new one. if itm_index == selector.topLevelItemCount() - 1: self._add_package_dir() else: # It is empty so remove it. selector.takeTopLevelItem(itm_index) # Save the new packages. project.other_packages = [ selector.topLevelItem(i).data(0, Qt.UserRole) for i in range(selector.topLevelItemCount() - 1)] self.project.modified = True def _package_changed(self): """ Invoked when the user edits a package contents. """ self.project.modified = True
class UIOpenPatientWindow(object): patient_info_initialized = QtCore.pyqtSignal(object) def setup_ui(self, open_patient_window_instance): if platform.system() == 'Darwin': self.stylesheet_path = "src/res/stylesheet.qss" else: self.stylesheet_path = "src/res/stylesheet-win-linux.qss" stylesheet = open(resource_path(self.stylesheet_path)).read() window_icon = QIcon() window_icon.addPixmap( QPixmap(resource_path("src/res/images/icon.ico")), QIcon.Normal, QIcon.Off) open_patient_window_instance.setObjectName("OpenPatientWindowInstance") open_patient_window_instance.setWindowIcon(window_icon) open_patient_window_instance.resize(840, 530) # Create a vertical box for containing the other elements and layouts self.open_patient_window_instance_vertical_box = QVBoxLayout() self.open_patient_window_instance_vertical_box.setObjectName( "OpenPatientWindowInstanceVerticalBox") # Create a label to prompt the user to enter the path to the directory that contains the DICOM files self.open_patient_directory_prompt = QLabel() self.open_patient_directory_prompt.setObjectName( "OpenPatientDirectoryPrompt") self.open_patient_directory_prompt.setAlignment(Qt.AlignLeft) self.open_patient_window_instance_vertical_box.addWidget( self.open_patient_directory_prompt) # Create a horizontal box to hold the input box for the directory and the choose button self.open_patient_directory_input_horizontal_box = QHBoxLayout() self.open_patient_directory_input_horizontal_box.setObjectName( "OpenPatientDirectoryInputHorizontalBox") # Create a textbox to contain the path to the directory that contains the DICOM files self.open_patient_directory_input_box = UIOpenPatientWindowDragAndDropEvent( self) self.open_patient_directory_input_box.setObjectName( "OpenPatientDirectoryInputBox") self.open_patient_directory_input_box.setSizePolicy( QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)) self.open_patient_directory_input_box.returnPressed.connect( self.scan_directory_for_patient) self.open_patient_directory_input_horizontal_box.addWidget( self.open_patient_directory_input_box) # Create a choose button to open the file dialog self.open_patient_directory_choose_button = QPushButton() self.open_patient_directory_choose_button.setObjectName( "OpenPatientDirectoryChooseButton") self.open_patient_directory_choose_button.setSizePolicy( QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)) self.open_patient_directory_choose_button.resize( self.open_patient_directory_choose_button.sizeHint().width(), self.open_patient_directory_input_box.height()) self.open_patient_directory_choose_button.setCursor( QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.open_patient_directory_input_horizontal_box.addWidget( self.open_patient_directory_choose_button) self.open_patient_directory_choose_button.clicked.connect( self.choose_button_clicked) # Create a widget to hold the input fields self.open_patient_directory_input_widget = QWidget() self.open_patient_directory_input_horizontal_box.setStretch(0, 4) self.open_patient_directory_input_widget.setLayout( self.open_patient_directory_input_horizontal_box) self.open_patient_window_instance_vertical_box.addWidget( self.open_patient_directory_input_widget) # Create a horizontal box to hold the stop button and direction to the user on where to select the patient self.open_patient_appear_prompt_and_stop_horizontal_box = QHBoxLayout() self.open_patient_appear_prompt_and_stop_horizontal_box.setObjectName( "OpenPatientAppearPromptAndStopHorizontalBox") # Create a label to show direction on where the files will appear self.open_patient_directory_appear_prompt = QLabel() self.open_patient_directory_appear_prompt.setObjectName( "OpenPatientDirectoryAppearPrompt") self.open_patient_directory_appear_prompt.setAlignment(Qt.AlignLeft) self.open_patient_appear_prompt_and_stop_horizontal_box.addWidget( self.open_patient_directory_appear_prompt) self.open_patient_appear_prompt_and_stop_horizontal_box.addStretch(1) # Create a button to stop searching self.open_patient_window_stop_button = QPushButton() self.open_patient_window_stop_button.setObjectName( "OpenPatientWindowStopButton") self.open_patient_window_stop_button.setSizePolicy( QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)) self.open_patient_window_stop_button.resize( self.open_patient_window_stop_button.sizeHint().width(), self.open_patient_window_stop_button.sizeHint().height()) self.open_patient_window_stop_button.setCursor( QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.open_patient_window_stop_button.clicked.connect( self.stop_button_clicked) self.open_patient_window_stop_button.setProperty( "QPushButtonClass", "fail-button") self.open_patient_window_stop_button.setVisible( False) # Button doesn't show until a search commences self.open_patient_appear_prompt_and_stop_horizontal_box.addWidget( self.open_patient_window_stop_button) # Create a widget to hold the layout self.open_patient_appear_prompt_and_stop_widget = QWidget() self.open_patient_appear_prompt_and_stop_widget.setLayout( self.open_patient_appear_prompt_and_stop_horizontal_box) self.open_patient_window_instance_vertical_box.addWidget( self.open_patient_appear_prompt_and_stop_widget) # Create a tree view list to list out all patients in the directory selected above self.open_patient_window_patients_tree = QTreeWidget() self.open_patient_window_patients_tree.setObjectName( "OpenPatientWindowPatientsTree") self.open_patient_window_patients_tree.setSizePolicy( QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)) self.open_patient_window_patients_tree.resize( self.open_patient_window_patients_tree.sizeHint().width(), self.open_patient_window_patients_tree.sizeHint().height()) self.open_patient_window_patients_tree.setHeaderHidden(True) self.open_patient_window_patients_tree.setHeaderLabels([""]) self.open_patient_window_instance_vertical_box.addWidget( self.open_patient_window_patients_tree) # Create a label to show what would happen if they select the patient self.open_patient_directory_result_label = QtWidgets.QLabel() self.open_patient_directory_result_label.setObjectName( "OpenPatientDirectoryResultLabel") self.open_patient_directory_result_label.setAlignment(Qt.AlignLeft) self.open_patient_window_instance_vertical_box.addWidget( self.open_patient_directory_result_label) # Create a horizontal box to hold the Cancel and Open button self.open_patient_window_patient_open_actions_horizontal_box = QHBoxLayout( ) self.open_patient_window_patient_open_actions_horizontal_box.setObjectName( "OpenPatientWindowPatientOpenActionsHorizontalBox") self.open_patient_window_patient_open_actions_horizontal_box.addStretch( 1) # Add a button to go back/exit from the application self.open_patient_window_exit_button = QPushButton() self.open_patient_window_exit_button.setObjectName( "OpenPatientWindowExitButton") self.open_patient_window_exit_button.setSizePolicy( QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)) self.open_patient_window_exit_button.resize( self.open_patient_window_stop_button.sizeHint().width(), self.open_patient_window_stop_button.sizeHint().height()) self.open_patient_window_exit_button.setCursor( QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.open_patient_window_exit_button.clicked.connect( self.exit_button_clicked) self.open_patient_window_exit_button.setProperty( "QPushButtonClass", "fail-button") self.open_patient_window_patient_open_actions_horizontal_box.addWidget( self.open_patient_window_exit_button) # Add a button to confirm opening of the patient self.open_patient_window_confirm_button = QPushButton() self.open_patient_window_confirm_button.setObjectName( "OpenPatientWindowConfirmButton") self.open_patient_window_confirm_button.setSizePolicy( QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding)) self.open_patient_window_confirm_button.resize( self.open_patient_window_confirm_button.sizeHint().width(), self.open_patient_window_confirm_button.sizeHint().height()) self.open_patient_window_confirm_button.setCursor( QtGui.QCursor(QtCore.Qt.PointingHandCursor)) self.open_patient_window_confirm_button.clicked.connect( self.confirm_button_clicked) self.open_patient_window_confirm_button.setProperty( "QPushButtonClass", "success-button") self.open_patient_window_patient_open_actions_horizontal_box.addWidget( self.open_patient_window_confirm_button) # Create a widget to house all of the actions button for open patient window self.open_patient_window_patient_open_actions_widget = QWidget() self.open_patient_window_patient_open_actions_widget.setLayout( self.open_patient_window_patient_open_actions_horizontal_box) self.open_patient_window_instance_vertical_box.addWidget( self.open_patient_window_patient_open_actions_widget) # Set the vertical box fourth element, the tree view, to stretch out as far as possible self.open_patient_window_instance_vertical_box.setStretch( 3, 4) # Stretch the treeview out as far as possible self.open_patient_window_instance_central_widget = QWidget() self.open_patient_window_instance_central_widget.setObjectName( "OpenPatientWindowInstanceCentralWidget") self.open_patient_window_instance_central_widget.setLayout( self.open_patient_window_instance_vertical_box) # Create threadpool for multithreading self.threadpool = QThreadPool() print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount()) # Create interrupt event for stopping the directory search self.interrupt_flag = threading.Event() # Bind all texts into the buttons and labels self.retranslate_ui(open_patient_window_instance) # Set the central widget, ready for display open_patient_window_instance.setCentralWidget( self.open_patient_window_instance_central_widget) # Set the current stylesheet to the instance and connect it back to the caller through slot open_patient_window_instance.setStyleSheet(stylesheet) QtCore.QMetaObject.connectSlotsByName(open_patient_window_instance) def retranslate_ui(self, open_patient_window_instance): _translate = QtCore.QCoreApplication.translate open_patient_window_instance.setWindowTitle( _translate("OpenPatientWindowInstance", "OnkoDICOM - Select Patient")) self.open_patient_directory_prompt.setText( _translate( "OpenPatientWindowInstance", "Choose the path of the folder containing DICOM files to load Patient's details:" )) self.open_patient_directory_input_box.setPlaceholderText( _translate( "OpenPatientWindowInstance", "Enter DICOM Files Path (For example, C:\path\\to\your\DICOM\Files)" )) self.open_patient_directory_choose_button.setText( _translate("OpenPatientWindowInstance", "Choose")) self.open_patient_directory_appear_prompt.setText( _translate( "OpenPatientWindowInstance", "Patient File directory shown below once file path chosen. Please select the file(s) you want to open:" )) self.open_patient_directory_result_label.setText( "The selected directory(s) above will be opened in the OnkoDICOM program." ) self.open_patient_window_stop_button.setText( _translate("OpenPatientWindowInstance", "Stop Search")) self.open_patient_window_exit_button.setText( _translate("OpenPatientWindowInstance", "Exit")) self.open_patient_window_confirm_button.setText( _translate("OpenPatientWindowInstance", "Confirm")) def exit_button_clicked(self): QCoreApplication.exit(0) def scan_directory_for_patient(self): self.filepath = self.open_patient_directory_input_box.text() # Proceed if a folder was selected if self.filepath != "": # Update the QTreeWidget to reflect data being loaded # First, clear the widget of any existing data self.open_patient_window_patients_tree.clear() # Next, update the tree widget self.open_patient_window_patients_tree.addTopLevelItem( QTreeWidgetItem(["Loading selected directory..."])) # The choose button is disabled until the thread finishes executing self.open_patient_directory_choose_button.setEnabled(False) # Reveals the Stop Search button for the duration of the search self.open_patient_window_stop_button.setVisible(True) # The interrupt flag is then un-set if a previous search has been stopped. self.interrupt_flag.clear() # Then, create a new thread that will load the selected folder worker = Worker(DICOMDirectorySearch.get_dicom_structure, self.filepath, self.interrupt_flag, progress_callback=True) worker.signals.result.connect(self.on_search_complete) worker.signals.progress.connect(self.search_progress) # Execute the thread self.threadpool.start(worker) def choose_button_clicked(self): """ Executes when the choose button is clicked. Gets filepath from the user and loads all files and subdirectories. """ # Get folder path from pop up dialog box self.filepath = QtWidgets.QFileDialog.getExistingDirectory( None, 'Select patient folder...', '') self.open_patient_directory_input_box.setText(self.filepath) self.scan_directory_for_patient() def stop_button_clicked(self): self.interrupt_flag.set() def search_progress(self, progress_update): """ Current progress of the file search. """ self.open_patient_window_patients_tree.clear() self.open_patient_window_patients_tree.addTopLevelItem( QTreeWidgetItem([ "Loading selected directory... (%s files searched)" % progress_update ])) def on_search_complete(self, dicom_structure): """ Executes once the directory search is complete. :param dicom_structure: DICOMStructure object constructed by the directory search. """ self.open_patient_directory_choose_button.setEnabled(True) self.open_patient_window_stop_button.setVisible(False) self.open_patient_window_patients_tree.clear() if dicom_structure is None: # dicom_structure will be None if function was interrupted. return for patient_item in dicom_structure.get_tree_items_list(): self.open_patient_window_patients_tree.addTopLevelItem( patient_item) if len(dicom_structure.patients) == 0: QMessageBox.about(self, "No files found", "Selected directory contains no DICOM files.") def confirm_button_clicked(self): """ Begins loading of the selected files. """ selected_files = [] for item in self.get_checked_leaves(): selected_files += item.dicom_object.get_files() if len(selected_files) > 0: self.progress_window = ProgressWindow( self, QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowCloseButtonHint) self.progress_window.signal_loaded.connect(self.on_loaded) self.progress_window.signal_error.connect(self.on_loading_error) self.progress_window.start_loading(selected_files) self.progress_window.exec_() else: QMessageBox.about(self, "Unable to open selection", "No files selected.") def on_loaded(self, results): """ Executes when the progress bar finishes loaded the selected files. """ if results[0] is True: # Will be NoneType if loading was interrupted. self.patient_info_initialized.emit( results[1]) # Emits the progress window. def on_loading_error(self, error_code): """ Error handling for progress window. """ if error_code == 0: QMessageBox.about( self.progress_window, "Unable to open selection", "Selected files cannot be opened as they are not a DICOM-RT set." ) self.progress_window.close() elif error_code == 1: QMessageBox.about( self.progress_window, "Unable to open selection", "Selected files cannot be opened as they contain unsupported DICOM classes." ) self.progress_window.close() def get_checked_leaves(self): """ :return: A list of all QTreeWidgetItems in the QTreeWidget that are both leaves and checked. """ checked_items = [] def recurse(parent_item: QTreeWidgetItem): for i in range(parent_item.childCount()): child = parent_item.child(i) grand_children = child.childCount() if grand_children > 0: recurse(child) else: if child.checkState(0) == Qt.Checked: checked_items.append(child) recurse(self.open_patient_window_patients_tree.invisibleRootItem()) return checked_items
class BookmarksWindow(QDialog): """ A simple UI for showing bookmarks and navigating to them. FIXME: For now, this window is tied to a particular lane. If your project has more than one lane, then each one will have it's own bookmark window, which is kinda dumb. """ def __init__(self, parent, topLevelOperatorView): super(BookmarksWindow, self).__init__(parent) self.setWindowTitle("Bookmarks") self.topLevelOperatorView = topLevelOperatorView self.bookmark_tree = QTreeWidget(self) self.bookmark_tree.setHeaderLabels( ["Location", "Notes"] ) self.bookmark_tree.setSizePolicy( QSizePolicy.Preferred, QSizePolicy.Preferred ) self.bookmark_tree.setColumnWidth(0, 200) self.bookmark_tree.setColumnWidth(1, 300) self.note_edit = QLineEdit(self) self.add_bookmark_button = QPushButton("Add Bookmark", self, clicked=self.add_bookmark) geometry = self.geometry() geometry.setSize( QSize(520, 520) ) self.setGeometry(geometry) layout = QVBoxLayout() layout.addWidget(self.bookmark_tree) layout.addWidget(self.note_edit) layout.addWidget(self.add_bookmark_button) self.setLayout(layout) self._load_bookmarks() self.bookmark_tree.setContextMenuPolicy( Qt.CustomContextMenu ) self.bookmark_tree.customContextMenuRequested.connect( self.showContextMenu ) self.bookmark_tree.itemDoubleClicked.connect(self._handle_doubleclick) def _handle_doubleclick(self, item, col): """ Navigate to the bookmark """ data = item.data(0, Qt.UserRole).toPyObject() if data is None: return (coord, notes) = data axes = self.topLevelOperatorView.InputImages.meta.getAxisKeys() axes = axes[:-1] # drop channel axes = sorted(axes) assert len(axes) == len(coord) tagged_coord = dict(list(zip(axes, coord))) tagged_location = OrderedDict(list(zip('txyzc', (0,0,0,0,0)))) tagged_location.update(tagged_coord) t = list(tagged_location.values())[0] coord3d = list(tagged_location.values())[1:4] self.parent().editor.posModel.time = t self.parent().editor.navCtrl.panSlicingViews( coord3d, [0,1,2] ) self.parent().editor.posModel.slicingPos = coord3d def showContextMenu(self, pos): item = self.bookmark_tree.itemAt(pos) data = item.data(0, Qt.UserRole).toPyObject() if data is None: return def delete_bookmark(): (coord, notes) = data bookmarks = list(self.topLevelOperatorView.Bookmarks.value) i = bookmarks.index((coord, notes)) bookmarks.pop(i) self.topLevelOperatorView.Bookmarks.setValue(bookmarks) self._load_bookmarks() menu = QMenu(parent=self) menu.addAction( QAction("Delete", menu, triggered=delete_bookmark) ) globalPos = self.bookmark_tree.viewport().mapToGlobal( pos ) menu.exec_( globalPos ) #selection = menu.exec_( globalPos ) #if selection is removeLanesAction: # self.removeLanesRequested.emit( self._selectedLanes ) def add_bookmark(self): coord_txyzc = self.parent().editor.posModel.slicingPos5D tagged_coord_txyzc = dict( list(zip('txyzc', coord_txyzc)) ) axes = self.topLevelOperatorView.InputImages.meta.getAxisKeys() axes = axes[:-1] # drop channel axes = sorted(axes) coord = tuple(tagged_coord_txyzc[c] for c in axes) notes = str(self.note_edit.text()) bookmarks = list(self.topLevelOperatorView.Bookmarks.value) bookmarks.append((coord, notes)) self.topLevelOperatorView.Bookmarks.setValue(bookmarks) self._load_bookmarks() def _load_bookmarks(self): self.bookmark_tree.clear() lane_index = self.topLevelOperatorView.current_view_index() lane_nickname = self.topLevelOperatorView.InputImages.meta.nickname or "Lane {}".format(lane_index) bookmarks = self.topLevelOperatorView.Bookmarks.value group_item = QTreeWidgetItem( self.bookmark_tree, [lane_nickname] ) for coord, notes in bookmarks: item = QTreeWidgetItem( group_item, [] ) item.setText(0, str(coord)) item.setData(0, Qt.UserRole, (coord, notes)) item.setText(1, notes) self.bookmark_tree.expandAll()
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) # Initialize main-widgets # 画像をスライドするボタンの設定 self.prevButton = QPushButton('&Prev') self.prevButton.clicked.connect(self.showPrevImage) self.prevButton.setShortcut(QtCore.Qt.Key_Left) self.nextButton = QPushButton('&Next') self.nextButton.clicked.connect(self.showNextImage) self.nextButton.setShortcut(QtCore.Qt.Key_Right) buttonLayout = QGridLayout() buttonLayout.addWidget(self.prevButton, 0, 0) buttonLayout.addWidget(self.nextButton, 0, 1) # 画像を表示するラベルの設定 self.pictureLabel = QLabel() self.pictureLabel.setAlignment(QtCore.Qt.AlignCenter) self.putPixmap('startimage.png') self.indexLabel = QLabel('') self.indexLabel.setAlignment(QtCore.Qt.AlignCenter) self.pathLabel = QLabel('') self.pathLabel.setAlignment(QtCore.Qt.AlignCenter) self.classLabel = QLabel('') self.classLabel.setAlignment(QtCore.Qt.AlignCenter) classLabelFont = QFont() classLabelFont.setPointSize(32) self.classLabel.setFont(classLabelFont) pictureLayout = QGridLayout() pictureLayout.addWidget(self.indexLabel, 0, 0) pictureLayout.addWidget(self.pictureLabel, 1, 0) pictureLayout.addWidget(self.pathLabel, 2, 0) pictureLayout.addWidget(self.classLabel, 3, 0) # アノテーションをするためのボタンの設定 annotationButtonLayout = QGridLayout() for k, conf in enumerate(BUTTON_CONFIG): button = QPushButton('&{0} : {1}'.format(conf['class'], conf['key'])) button.clicked.connect(self.anotateClass) button.setShortcut(conf['key']) annotationButtonLayout.addWidget(button, 0, k) # 画像のパス一覧を表示するためのリスト self.treeWidget = QTreeWidget() self.treeWidget.setColumnCount(2) self.treeWidget.setHeaderLabels(['#', 'filepath', 'class', 'corrected']) self.treeWidget.itemSelectionChanged.connect(self.showImageFromTree) mainLayout = QVBoxLayout() mainLayout.addLayout(pictureLayout) mainLayout.addLayout(annotationButtonLayout) mainLayout.addLayout(buttonLayout) topLayout = QHBoxLayout() topLayout.addWidget(self.treeWidget) topLayout.addLayout(mainLayout) topWidget = QWidget() topWidget.setLayout(topLayout) self.setCentralWidget(topWidget) self.setWindowTitle('koala') # Initialize menu bar and status bar openAction = QAction('&Open', self) openAction.setShortcut('Ctrl+O') openAction.setStatusTip('Open annotation file') openAction.triggered.connect(self.openAnnotationFile) saveAction = QAction('&Save', self) saveAction.setShortcut('Ctrl+S') saveAction.setStatusTip('Save annotation file') saveAction.triggered.connect(self.saveAnnotationFile) newSaveAction = QAction('&Save As', self) newSaveAction.setShortcut('Ctrl+Shift+S') newSaveAction.setStatusTip('Save annotation as new file') newSaveAction.triggered.connect(self.newSaveAnnotationFile) addImageAction = QAction('&Add Image', self) addImageAction.setShortcut('Ctrl+I') addImageAction.setStatusTip('Add image files') addImageAction.triggered.connect(self.addImageFile) self.statusBar() menuBar = self.menuBar() fileMenu = menuBar.addMenu('&File') fileMenu.addAction(openAction) fileMenu.addAction(saveAction) fileMenu.addAction(newSaveAction) fileMenu.addAction(addImageAction) # Initialize image annotation information list self.imageDataList = [] self.imageIndex = 0 # 開いているファイルのパス self.openingFilePath = '' def putPixmap(self, filepath): # Pixmapをパスから読み込んでラベルにセットする pixmap = QPixmap(filepath) self.pictureLabel.setPixmap(pixmap.scaled(256, 256, QtCore.Qt.KeepAspectRatio)) self.pictureLabel.show() def anotateClass(self): # 押されたボタンの情報から,ラベリングを行う if len(self.imageDataList) > 0: text = self.sender().text() dataClass = text[1:text.index(':')-1].strip() # ラベルの変更があったときだけデータの変更を行う if self.imageDataList[self.imageIndex]['class'] != dataClass: self.imageDataList[self.imageIndex]['class'] = dataClass self.imageDataList[self.imageIndex]['corrected'] = not self.imageDataList[self.imageIndex]['corrected'] self.updateDataInformation() # Update Image List root = self.treeWidget.invisibleRootItem() item = root.child(self.imageIndex) item.setText(2, self.imageDataList[self.imageIndex]['class']) item.setBackground(2, QColor(CLASS_TO_COLOR[self.imageDataList[self.imageIndex]['class']])) item.setText(3, str(self.imageDataList[self.imageIndex]['corrected'])) def updateDataInformation(self): # 現在選択しているデータのパスとクラスをラベルに表示する if len(self.imageDataList) > 0: self.indexLabel.setText('{0} / {1}'.format(self.imageIndex+1, len(self.imageDataList))) self.pathLabel.setText(self.imageDataList[self.imageIndex]['filepath']) self.classLabel.setText(self.imageDataList[self.imageIndex]['class']) self.classLabel.setStyleSheet('color : {0}'.format(CLASS_TO_COLOR[self.imageDataList[self.imageIndex]['class']])) def showPrevImage(self): if len(self.imageDataList) > 0: self.imageIndex = (self.imageIndex - 1) % len(self.imageDataList) self.putPixmap(self.imageDataList[self.imageIndex]['filepath']) self.updateDataInformation() def showNextImage(self): if len(self.imageDataList) > 0: self.imageIndex = (self.imageIndex + 1) % len(self.imageDataList) self.putPixmap(self.imageDataList[self.imageIndex]['filepath']) self.updateDataInformation() def showIntendedImage(self, idx): """ 番号で指定した画像を表示する """ if len(self.imageDataList) > 0 and 0 <= idx <= len(self.imageDataList): self.imageIndex = idx self.putPixmap(self.imageDataList[self.imageIndex]['filepath']) self.updateDataInformation() def showImageFromTree(self): """ TreeWidget上でアイテムが指定されたらその番号の画像にジャンプする """ indices = self.treeWidget.selectedIndexes() if indices: selectedIndex = indices[0].row() self.showIntendedImage(selectedIndex) def openAnnotationFile(self): # アノテーションの情報を読み込む openFilePath = QFileDialog.getOpenFileName(parent=self, filter='*.json')[0] # キャンセルされなければ読み込み if len(openFilePath) > 0: with open(openFilePath, 'r') as f: self.imageDataList = json.load(f) self.imageIndex = 0 self.putPixmap(self.imageDataList[self.imageIndex]['filepath']) self.updateDataInformation() self.updateImageList() self.openingFilePath = openFilePath self.setWindowTitle('koala - {0}'.format(self.openingFilePath)) def saveAnnotationFile(self): # 新規のファイルなら保存のダイアログを出す if self.openingFilePath == '': # アノテーションの情報を保存する saveFilePath = QFileDialog.getSaveFileName(parent=self, filter='*.json')[0] # キャンセルされなければ保存 if len(saveFilePath) > 0: with open(saveFilePath, 'w') as f: json.dump(self.imageDataList, f, ensure_ascii=False, indent=4) self.openingFilePath = saveFilePath self.setWindowTitle('koala - {0}'.format(self.openingFilePath)) self.statusBar().showMessage('New annotation file is saved as "{0}"'.format(self.openingFilePath)) else: with open(self.openingFilePath, 'w') as f: json.dump(self.imageDataList, f, ensure_ascii=False, indent=4) self.statusBar().showMessage('Annotation file is saved.'.format(self.openingFilePath)) def newSaveAnnotationFile(self): # 名前をつけて保存 saveFilePath = QFileDialog.getSaveFileName(parent=self, filter='*.json')[0] # キャンセルされなければ保存 if len(saveFilePath) > 0: with open(saveFilePath, 'w') as f: json.dump(self.imageDataList, f, ensure_ascii=False, indent=4) self.openingFilePath = saveFilePath self.setWindowTitle('koala - {0}'.format(self.openingFilePath)) self.statusBar().showMessage('New annotation file is saved as "{0}"'.format(self.openingFilePath)) def addImageFile(self): # Open file dialog for adding images selectedImagePathList = QFileDialog.getOpenFileNames(parent=self, filter='*.png *.jpg *.jpeg *.bmp')[0] # Image path list imagePathList = [imageData['filepath'] for imageData in self.imageDataList] # Add selected images to imagePathList for imagePath in selectedImagePathList: # まだ追加されていない画像ならリストに追加する if not (imagePath in imagePathList): self.imageDataList.append({'#' : len(self.imageDataList), 'filepath' : imagePath, 'class' : None, 'corrected' : False}) # 画像があるなら開く if len(self.imageDataList) > 0: self.imageIndex = len(self.imageDataList) - 1 self.putPixmap(self.imageDataList[self.imageIndex]['filepath']) self.updateDataInformation() self.updateImageList() def updateImageList(self): self.treeWidget.clear() for idx, imageData in enumerate(self.imageDataList): treeWidgetItem = QTreeWidgetItem(['{0:7d}'.format(idx + 1), imageData['filepath'], imageData['class'], str(imageData['corrected'])]) treeWidgetItem.setBackground(2, QColor(CLASS_TO_COLOR[imageData['class']])) self.treeWidget.addTopLevelItem(treeWidgetItem)