Пример #1
0
class MainWindow(QMainWindow):
    def __init__(self, parent=None, flags=Qt.WindowFlags()):
        super(MainWindow, self).__init__(parent, flags)

        self.dayWidth = 70
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.initModel()
        self.initActions()
        self.initItemDelegate()
        self.initGrid()

        leftView = self.ui.ganttView.leftView()
        leftView.setColumnHidden(1, True)
        leftView.setColumnHidden(2, True)
        leftView.setColumnHidden(3, True)
        leftView.setColumnHidden(4, True)
        leftView.setColumnHidden(5, True)
        leftView.header().setStretchLastSection(True)

        self.ui.ganttView.leftView().customContextMenuRequested.connect(
            self.showContextMenu)
        self.ui.ganttView.selectionModel().selectionChanged.connect(
            self.enableActions)
        self.ui.ganttView.graphicsView().clicked.connect(self.slotClicked)
        self.ui.ganttView.graphicsView().qrealClicked.connect(
            self.slotDoubleClicked)

    def initModel(self):
        self.model = QStandardItemModel(0, 6, self)
        self.model.setHeaderData(0, Qt.Horizontal, "Task")
        self.ui.ganttView.setModel(self.model)

        self.l = QWidget(self)
        self.l.setWindowTitle("Legend")
        self.l.show()
        ##self.l.setModel(self.model)

        self.constraintModel = ConstraintModel(self)
        self.ui.ganttView.setConstraintModel(self.constraintModel)

    def initActions(self):
        self.newEntryAction = QAction("New entry", self)
        self.newEntryAction.setShortcut(QKeySequence.New)
        self.newEntryAction.triggered.connect(self.addNewEntry)

        self.removeEntryAction = QAction("Remove entry", self)
        self.removeEntryAction.setShortcut(QKeySequence.Delete)
        self.removeEntryAction.triggered.connect(self.removeEntry)

        self.demoAction = QAction("Demo entry", self)
        self.demoAction.triggered.connect(self.addDemoEntry)

        self.printAction = QAction("Print Preview...", self)
        self.printAction.triggered.connect(self.printPreview)

        self.zoomInAction = QAction("Zoom In", self)
        self.zoomInAction.setShortcut(QKeySequence.ZoomIn)
        self.zoomInAction.triggered.connect(self.zoomIn)

        self.zoomOutAction = QAction("Zoom Out", self)
        self.zoomOutAction.setShortcut(QKeySequence.ZoomOut)
        self.zoomOutAction.triggered.connect(self.zoomOut)

        self.ui.ganttView.leftView().setContextMenuPolicy(Qt.CustomContextMenu)
        self.ui.ganttView.leftView().addAction(self.newEntryAction)
        self.ui.ganttView.leftView().addAction(self.removeEntryAction)

        menuBar = QMenuBar(self)
        self.setMenuBar(menuBar)
        entryMenu = menuBar.addMenu("Entry")
        entryMenu.addAction(self.newEntryAction)
        entryMenu.addAction(self.removeEntryAction)
        entryMenu.addSeparator()
        entryMenu.addAction(self.demoAction)
        entryMenu.addSeparator()
        entryMenu.addAction(self.printAction)

        zoomMenu = menuBar.addMenu("Zoom")
        zoomMenu.addAction(self.zoomInAction)
        zoomMenu.addAction(self.zoomOutAction)

        ##self.enableActions(QItemSelection())

    def initItemDelegate(self):
        delegate = EntryDelegate(self.constraintModel, self)
        self.ui.ganttView.leftView().setItemDelegate(delegate)

    def initGrid(self):
        self.grid = DateTimeGrid()
        self.grid.setDayWidth(self.dayWidth)
        self.ui.ganttView.setGrid(self.grid)

    def showContextMenu(self, pos):
        if not self.ui.ganttView.leftView().indexAt(pos).isValid():
            self.ui.ganttView.selectionModel().clearSelection()

        menu = QMenu(self.ui.ganttView.leftView())
        menu.addAction(self.newEntryAction)
        menu.addAction(self.removeEntryAction)
        menu.exec_(self.ui.ganttView.leftView().viewport().mapToGlobal(pos))

    def enableActions(self, selected):
        if len(selected.indexes()) == 0:
            self.newEntryAction.setEnabled(True)
            self.removeEntryAction.setEnabled(False)
            return

        selectedIndex = selected.indexes()[0]
        dataType = self.model.data(self.model.index(selectedIndex.row(), 1))
        if dataType in [KDGantt.TypeEvent, KDGantt.TypeTask]:
            self.newEntryAction.setEnabled(False)
            self.removeEntryAction.setEnabled(True)
            return

        self.newEntryAction.setEnabled(True)
        self.removeEntryAction.setEnabled(True)

    def addNewEntry(self):
        dialog = EntryDialog(self.model)
        dialog.setWindowTitle("New Entry")
        if dialog.exec_() == QDialog.Rejected:
            dialog = None
            return

        selectedIndexes = self.ui.ganttView.selectionModel().selectedIndexes()
        if len(selectedIndexes) > 0:
            parent = selectedIndexes[0]
        else:
            parent = QModelIndex()

        if not self.model.insertRow(self.model.rowCount(parent), parent):
            return

        row = self.model.rowCount(parent) - 1
        if row == 0 and parent.isValid():
            self.model.insertColumns(self.model.columnCount(paren), 5, parent)

        self.model.setData(self.model.index(row, 0, parent), dialog.name())
        self.model.setData(self.model.index(row, 1, parent), dialog.type())
        if dialog.type() != KDGantt.TypeSummary:
            self.model.setData(self.model.index(row, 2, parent),
                               dialog.startDate(), KDGantt.StartTimeRole)
            self.model.setData(self.model.index(row, 3, parent),
                               dialog.endDate(), KDGantt.EndTimeRole)

        self.model.setData(self.model.index(row, 4, parent),
                           dialog.completion())
        self.model.setData(self.model.index(row, 5, parent), dialog.legend())

        self.addConstraint(dialog.depends(), self.model.index(row, 0, parent))
        self.setReadOnly(self.model.index(row, 0, parent), dialog.readOnly())

        dialog = None

    def setReadOnly(self, index, readOnly):
        row = index.row()
        parent = index.parent()

        for column in range(0, 5):
            item = self.model.itemFromIndex(
                self.model.index(row, column, parent))
            flags = None
            if readOnly:
                flags = item.flags() & ~Qt.ItemIsEditable
            else:
                flags = item.flags() | Qt.ItemIsEditable
            item.setFlags(flags)

    def addConstraint(self, index1, index2):
        if not index1.isValid() or not index2.isValid():
            return

        c = Constraint(index1, index2)
        self.ui.ganttView.constraintModel().addConstraint(c)

    def addConstraintFromItem(self, item1, item2):
        self.addConstraint(self.model.indexFromItem(item1),
                           self.model.indexFromItem(item2))

    def removeEntry(self):
        selectedIndexes = self.ui.ganttView.selectionModel().selectedIndexes()
        if len(selectedIndexes) > 0:
            index = selectedIndexes[0]
        else:
            index = QModelIndex()

        if not index.isValid():
            return

        self.model.removeRow(index.row(), index.parent())

    def addDemoEntry(self):
        softwareRelease = MyStandardItem("Software Release")
        codeFreeze = MyStandardItem("Code Freeze")
        codeFreeze.setData(KDGantt.TextPositionRole,
                           StyleOptionGanttItem.Right)
        packaging = MyStandardItem("Packaging")
        upload = MyStandardItem("Upload")
        testing = MyStandardItem("Testing")
        updateDocumentation = MyStandardItem("Update Documentation")

        now = QDateTime.currentDateTime()
        softwareRelease.appendRow([
            codeFreeze,
            MyStandardItem(KDGantt.TypeEvent),
            MyStandardItem(now, KDGantt.StartTimeRole)
        ])
        softwareRelease.appendRow([
            packaging,
            MyStandardItem(KDGantt.TypeTask),
            MyStandardItem(now.addDays(5), KDGantt.StartTimeRole),
            MyStandardItem(now.addDays(10), KDGantt.EndTimeRole)
        ])
        softwareRelease.appendRow([
            upload,
            MyStandardItem(KDGantt.TypeTask),
            MyStandardItem(
                now.addDays(10).addSecs(2 * 60 * 60), KDGantt.StartTimeRole),
            MyStandardItem(now.addDays(11), KDGantt.EndTimeRole)
        ])
        softwareRelease.appendRow([
            testing,
            MyStandardItem(KDGantt.TypeTask),
            MyStandardItem(now.addSecs(3 * 60 * 60), KDGantt.StartTimeRole),
            MyStandardItem(now.addDays(5), KDGantt.EndTimeRole)
        ])
        softwareRelease.appendRow([
            updateDocumentation,
            MyStandardItem(KDGantt.TypeTask),
            MyStandardItem(now.addSecs(3 * 60 * 60), KDGantt.StartTimeRole),
            MyStandardItem(now.addDays(3), KDGantt.EndTimeRole)
        ])
        self.model.appendRow(
            [softwareRelease,
             MyStandardItem(KDGantt.TypeSummary)])
        self.addConstraintFromItem(codeFreeze, packaging)
        self.addConstraintFromItem(codeFreeze, testing)
        self.addConstraintFromItem(codeFreeze, updateDocumentation)
        self.addConstraintFromItem(packaging, upload)
        self.addConstraintFromItem(testing, packaging)
        self.addConstraintFromItem(updateDocumentation, packaging)

    def zoomIn(self):
        self.dayWidth += 10
        if self.dayWidth > 400:
            self.grid.setScale(DateTimeGrid.ScaleHour)
        self.grid.setDayWidth(self.dayWidth)

    def zoomOut(self):
        self.dayWidth -= 10

        if self.dayWidth < 10:
            self.dayWidth = 10

        if self.dayWidth <= 400:
            self.grid.setScale(DateTimeGrid.ScaleDay)

        self.grid.setDayWidth(self.dayWidth)

    def printPreview(self):
        preview = QLabel(self, Qt.Window)
        preview.setAttribute(Qt.WA_DeleteOnClose)
        preview.setScaledContents(True)
        preview.setWindowTitle("Print Preview")
        pix = QPixmap(1000, 300)
        pix.fill(Qt.white)

        p = QPainter(pix)
        p.setRenderHints(QPainter.Antialiasing)
        self.ui.ganttView.print_(p, pix.rect())

        preview.setPixmap(pix)
        preview.show()

    def slotClicked(self, index):
        self.statusBar().showMessage(
            "(%d,%d,_,%s) clicked" %
            (index.row(), index.column(), index.model()))

    def slotDoubleClicked(self, index):
        self.statusBar().showMessage(
            "(%d,%d,_,%s) qreal clicked" %
            (index.row(), index.column(), index.model()))
Пример #2
0
class App(QMainWindow):
    def __init__(self):
        super(App, self).__init__()

        self.treeView = None
        self.model = None
        self.pattern_delegate = None
        self.callee_delegate = None
        self.sig_trie = None

        self.searchResults = None
        self.searchIndex = -1
        self.findNextAction = None
        self.findPrevAction = None

        # these two maps are used to make the hyperlinks work
        # mapping from href to FunctionNode
        self.hrefs_to_funcs = {}
        # mapping from FunctionNode to tree view element (QStandardItem)
        self.func_node_items = {}

        self.init_ui()

    def init_ui(self):
        self.setWindowTitle('Signature Explorer')
        self.resize(1000, 640)
        app_icon = QIcon()
        app_icon.addFile('icon.ico', QSize(48, 48))
        self.setWindowIcon(app_icon)

        self.pattern_delegate = PatternDelegate()
        self.callee_delegate = CalleesDelegate()

        self.treeView = TrieView()
        # self.treeView.setAlternatingRowColors(True)

        self.model = QStandardItemModel(0, 7, self.treeView)
        self.model.setHeaderData(0, Qt.Horizontal, 'Signature')
        self.model.setHeaderData(1, Qt.Horizontal, 'Function')
        self.model.setHeaderData(2, Qt.Horizontal, 'Callees')
        self.model.setHeaderData(3, Qt.Horizontal, 'Offset Extra Pattern')
        self.model.setHeaderData(4, Qt.Horizontal, 'Extra Pattern')
        self.model.setHeaderData(5, Qt.Horizontal, 'Source Binary')
        self.model.setHeaderData(6, Qt.Horizontal, 'ID')
        self.treeView.setModel(self.model)

        self.treeView.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.treeView.setColumnWidth(0, 400)
        self.treeView.setColumnWidth(1, 200)
        self.treeView.setColumnWidth(2, 250)
        self.treeView.setColumnWidth(3, 25)
        self.treeView.setColumnWidth(4, 100)
        self.treeView.setColumnWidth(5, 200)
        self.treeView.setColumnWidth(6, 75)
        self.treeView.setItemDelegateForColumn(0, self.pattern_delegate)
        self.treeView.setItemDelegateForColumn(2, self.callee_delegate)
        self.treeView.setItemDelegateForColumn(4, self.pattern_delegate)
        self.treeView.horizontalScrollBar().setEnabled(True)
        self.treeView.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.treeView.linkActivated.connect(self.on_func_link_clicked)
        # self.treeView.expanded.connect(lambda x: self.treeView.resizeColumnToContents(1))
        # self.treeView.collapsed.connect(lambda x: self.treeView.resizeColumnToContents(1))

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.treeView)

        panel = QWidget()
        panel.setLayout(main_layout)
        self.setCentralWidget(panel)

        menuBar = self.menuBar()

        fileMenu = QMenu("File")
        openAction = QAction("&Open", self)
        openAction.setShortcuts(QKeySequence.Open)
        openAction.triggered.connect(self.open_file)
        fileMenu.addAction(openAction)

        closeAction = QAction("&Close", self)
        closeAction.setShortcuts(QKeySequence.Close)
        closeAction.triggered.connect(self.close_file)
        fileMenu.addAction(closeAction)

        saveAsAction = QAction("Save As...", self)
        saveAsAction.setShortcuts(QKeySequence.Save)
        saveAsAction.triggered.connect(self.save_as)
        fileMenu.addAction(saveAsAction)

        menuBar.addMenu(fileMenu)

        editMenu = QMenu("Edit")

        findAction = QAction("&Find", self)
        findAction.setShortcuts(QKeySequence.Find)
        findAction.triggered.connect(self.search)
        editMenu.addAction(findAction)

        self.findNextAction = QAction("&Find Next", self)
        self.findNextAction.setShortcuts(QKeySequence.FindNext)
        self.findNextAction.triggered.connect(self.select_next)
        self.findNextAction.setEnabled(False)
        editMenu.addAction(self.findNextAction)

        self.findPrevAction = QAction("&Find Prev", self)
        self.findPrevAction.setShortcuts(QKeySequence.FindPrevious)
        self.findPrevAction.triggered.connect(self.select_prev)
        self.findPrevAction.setEnabled(False)
        editMenu.addAction(self.findPrevAction)

        menuBar.addMenu(editMenu)

        viewMenu = QMenu("View")

        expandAction = QAction("&Expand All", self)
        expandAction.triggered.connect(self.treeView.expandAll)
        viewMenu.addAction(expandAction)

        collapseAction = QAction("&Collapse All", self)
        collapseAction.triggered.connect(self.treeView.collapseAll)
        viewMenu.addAction(collapseAction)

        menuBar.addMenu(viewMenu)

    def search(self):
        query_string, ok = QInputDialog.getText(self, 'Find in Trie',
                                                'Function name')
        if not ok or not query_string:
            return

        self.searchResults = self.model.findItems(
            query_string, Qt.MatchContains | Qt.MatchRecursive, 1)

        if self.searchResults:
            self.findNextAction.setEnabled(True)
            self.findPrevAction.setEnabled(True)
            self.searchIndex = 0
            self.select_next()
        else:
            self.findNextAction.setEnabled(False)
            self.findPrevAction.setEnabled(False)
            self.searchIndex = -1
            QMessageBox.warning(self, 'Find in Trie', 'No results found')

    def select_next(self):
        next_item = self.searchResults[self.searchIndex]
        self.searchIndex = (self.searchIndex + 1) % len(self.searchResults)
        self.select_tree_item(next_item)

    def select_prev(self):
        prev_item = self.searchResults[self.searchIndex]
        self.searchIndex = (self.searchIndex - 1) % len(self.searchResults)
        self.select_tree_item(prev_item)

    def select_tree_item(self, item):
        path = []
        while item:
            path.insert(0, self.model.indexFromItem(item))
            item = item.parent()
        # print(path)
        for index in path:
            self.treeView.setExpanded(index, True)
        self.treeView.selectionModel().select(
            path[-1],
            QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows)
        self.treeView.scrollTo(path[-1])

    def close_file(self):
        self.model.removeRows(0, self.model.rowCount())
        self.sig_trie = None
        self.hrefs_to_funcs = {}
        self.func_node_items = {}

    def open_file(self):
        sig_filter = 'Signature library (*.sig)'
        json_zlib_filter = 'Compressed JSON signature library (*.json.zlib)'
        json_filter = 'JSON signature library (*.json)'
        pkl_filter = 'Pickled signature library (*.pkl)'
        fname, filter = QFileDialog.getOpenFileName(self,
                                                    'Open file',
                                                    filter=';;'.join([
                                                        sig_filter,
                                                        json_zlib_filter,
                                                        json_filter, pkl_filter
                                                    ]))
        if filter and fname:
            print('Opening signature library %s' % (fname, ))

        if filter == json_zlib_filter:
            with open(fname, 'rb') as f:
                json_trie = zlib.decompress(f.read()).decode('utf-8')
            sig_trie = sig_serialize_json.deserialize(json.loads(json_trie))
        elif filter == json_filter:
            with open(fname, 'r') as f:
                json_trie = f.read()
            sig_trie = sig_serialize_json.deserialize(json.loads(json_trie))
        elif filter == sig_filter:
            with open(fname, 'rb') as f:
                fb_trie = f.read()
            sig_trie = sig_serialize_fb.SignatureLibraryReader().deserialize(
                fb_trie)
        elif filter == pkl_filter:
            with open(fname, 'rb') as f:
                sig_trie = pickle.load(f)
        else:
            return

        self.open_trie(sig_trie, os.path.basename(fname))

    def save_as(self):
        sig_filter = 'Signature library (*.sig)'
        json_zlib_filter = 'Compressed JSON signature library (*.json.zlib)'
        json_filter = 'JSON signature library (*.json)'
        pkl_filter = 'Pickled signature library (*.pkl)'
        fname, filter = QFileDialog.getSaveFileName(self,
                                                    'Open file',
                                                    filter=';;'.join([
                                                        sig_filter,
                                                        json_zlib_filter,
                                                        json_filter, pkl_filter
                                                    ]))

        if filter == json_zlib_filter:
            with open(fname, 'wb') as f:
                f.write(
                    zlib.compress(
                        sig_serialize_json.serialize(
                            self.sig_trie).encode('utf-8')))
        elif filter == json_filter:
            with open(fname, 'w') as f:
                json.dump(sig_serialize_json.serialize(self.sig_trie),
                          f,
                          indent=4)
        elif filter == sig_filter:
            with open(fname, 'wb') as f:
                f.write(sig_serialize_fb.SignatureLibraryWriter().serialize(
                    self.sig_trie))
        elif filter == pkl_filter:
            with open(fname, 'wb') as f:
                pickle.dump(self.sig_trie, f)
        else:
            return
        print('Saved as ' + fname)

    @staticmethod
    def generate_href(func):
        return str(id(func))

    def get_func_name(self, func_node):
        if func_node is None:
            return '<missing>'
        else:
            return '<a href="' + self.generate_href(
                func_node) + '">' + func_node.name + '</a>'

    # handles when the user clicks on a hyperlink to a function node
    def on_func_link_clicked(self, link):
        print('Hyperlink clicked: ' + link)
        self.select_tree_item(self.func_node_items[self.hrefs_to_funcs[link]])

    # Generate treeview row for function (leaf) node in the trie
    def add_func_node(self, parent, pattern_col_item, func):
        self.hrefs_to_funcs[self.generate_href(func)] = func
        self.func_node_items[func] = pattern_col_item

        if not func.callees: func.callees = {}
        callees_text = '<br />'.join([
            str(k) + ': ' + self.get_func_name(v)
            for k, v in func.callees.items()
        ])
        callees_item = QStandardItem(callees_text)
        cols = [
            pattern_col_item,
            QStandardItem(func.name), callees_item,
            QStandardItem(str(func.pattern_offset) if func.pattern else ''),
            QStandardItem(str(func.pattern) if func.pattern else ''),
            QStandardItem(func.source_binary),
            QStandardItem(self.generate_href(func))
        ]
        boldface = cols[1].font()
        boldface.setBold(True)
        cols[1].setFont(boldface)
        parent.appendRow(cols)

    # Recursively add rows for this trie node and its children
    def add_trie_node(self, parent, pattern_text, node):
        left_item = QStandardItem(pattern_text)

        if not node.value:  # Stem node
            parent.appendRow([left_item, QStandardItem('')])
        else:  # Leaf node
            self.add_func_node(parent, left_item, node.value[0])
            for func in node.value[1:]:
                self.add_func_node(parent, QStandardItem(''), func)

        pairs = map(lambda node: (str(node.pattern), node),
                    node.children.values())
        pairs = sorted(pairs, key=lambda kv: kv[0].replace('?', '\xff'))
        for text, child in pairs:
            self.add_trie_node(left_item, text, child)
        return left_item

    # Add bridge nodes to a special node at the root
    def add_bridge_nodes(self, parent, sig_trie):
        bridge_item = QStandardItem('(bridge)')
        parent.appendRow([bridge_item, QStandardItem('')])

        def visit(func, visited):
            if func is None or func in visited: return
            visited.add(func)
            if func.is_bridge:
                self.add_func_node(bridge_item, QStandardItem(''), func)
            for callee in func.callees.values():
                visit(callee, visited)

        visited = set()
        for func in sig_trie.all_values():
            visit(func, visited)

    def open_trie(self, sig_trie, filename):
        self.close_file()
        self.sig_trie = sig_trie
        root_node = self.add_trie_node(self.model, filename, sig_trie)
        self.add_bridge_nodes(root_node, sig_trie)