예제 #1
0
파일: layer_tree.py 프로젝트: ssec/sift
    def _init_widget(self, listbox: QTreeView):
        listbox.setModel(self)
        listbox.setItemDelegate(self.item_delegate)
        listbox.setContextMenuPolicy(Qt.CustomContextMenu)
        # listbox.customContextMenuRequested.connect(self.context_menu)
        listbox.customContextMenuRequested.connect(self.menu)
        listbox.setDragEnabled(True)
        listbox.setAcceptDrops(True)
        listbox.setDropIndicatorShown(True)
        listbox.setSelectionMode(listbox.ExtendedSelection)
        # listbox.setMovement(QTreeView.Snap)
        # listbox.setDragDropMode(QTreeView.InternalMove)
        listbox.setDragDropMode(QAbstractItemView.DragDrop)
        # listbox.setAlternatingRowColors(True)
        # listbox.setDefaultDropAction(Qt.MoveAction)
        # listbox.setDragDropOverwriteMode(True)
        # listbox.entered.connect(self.layer_entered)
        # listbox.setFont(QFont('Andale Mono', 13))

        # the various signals that may result from the user changing the selections
        # listbox.activated.connect(self.changedSelection)
        # listbox.clicked.connect(self.changedSelection)
        # listbox.doubleClicked.connect(self.changedSelection)
        # listbox.pressed.connect(self.changedSelection)
        listbox.selectionModel().selectionChanged.connect(
            self.changedSelection)

        self.widgets.append(listbox)
예제 #2
0
def set_tree_view(view: QTreeView):
    view.setRootIsDecorated(False)
    view.setAlternatingRowColors(True)
    view.setSortingEnabled(True)
    view.sortByColumn(1, Qt.AscendingOrder)
    view.setContextMenuPolicy(Qt.CustomContextMenu)
    view.setSelectionMode(QAbstractItemView.ExtendedSelection)
예제 #3
0
class Window(QWidget):
    def __init__(self):

        QWidget.__init__(self)

        self.treeView = QTreeView()
        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.customContextMenuRequested.connect(self.openMenu)

        self.model = QStandardItemModel()
        self.addItems(self.model, data)
        self.treeView.setModel(self.model)

        self.model.setHorizontalHeaderLabels([self.tr("Object")])

        layout = QVBoxLayout()
        layout.addWidget(self.treeView)
        self.setLayout(layout)

    def addItems(self, parent, elements):

        for text, children in elements:
            item = QStandardItem(text)
            parent.appendRow(item)
            if children:
                self.addItems(item, children)

    def openMenu(self, position):

        indexes = self.treeView.selectedIndexes()
        if len(indexes) > 0:

            level = 0
            index = indexes[0]
            while index.parent().isValid():
                index = index.parent()
                level += 1

        menu = QMenu()
        if level == 0:
            menu.addAction(self.tr("Edit person"))
        elif level == 1:
            menu.addAction(self.tr("Edit object/container"))
        elif level == 2:
            menu.addAction(self.tr("Edit object"))

        menu.exec_(self.treeView.viewport().mapToGlobal(position))
예제 #4
0
class Window(QWidget):
    def __init__(self):

        QWidget.__init__(self)

        self.treeView = QTreeView()
        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.customContextMenuRequested.connect(self.openMenu)

        self.model = QStandardItemModel()
        self.addItems(self.model, data)
        self.treeView.setModel(self.model)

        self.model.setHorizontalHeaderLabels([self.tr("Object")])

        layout = QVBoxLayout()
        layout.addWidget(self.treeView)
        self.setLayout(layout)

    def addItems(self, parent, elements):

        for text, children in elements:
            item = QStandardItem(text)
            parent.appendRow(item)
            if children:
                self.addItems(item, children)

        title = os.path.basename(path)
        item = QStandardItem()
        if os.path.isdir(path):
            icon_path = DIR_ICON_PATH
        else:
            if path.endswith('PNG') or path.endswith('png') or path.endswith(
                    'JPG') or path.endswith('jpg') or path.endswith(
                        'JPEG') or path.endswith('jpeg') or path.endswith(
                            'GIF') or path.endswith('gif'):
                icon_path = IMAGE_ICON_PATH
            elif path.endswith('.mp4'):
                icon_path = VIDEO_ICON_PATH
            else:
                icon_path = FILE_ICON_PATH
        icon = QIcon(icon_path)
        item.setText(title)
        item.setIcon(icon)
        return item
예제 #5
0
    def __init__(self, parent=None):
        super(LayoutRight, self).__init__(parent)

        self.query = {
            "date": {
                "start": {
                    "title": QLabel("Start Date:"),
                    "value": QDateEdit(QDate.currentDate()),
                },
                "end": {
                    "title": QLabel("Start Date:"),
                    "value": QDateEdit(QDate.currentDate()),
                }
            }
        }

        for k, date in self.query["date"].items():
            date["value"].setCalendarPopup(True)

        self.btn_query = QPushButton("Query")
        self.btn_query.setFixedSize(self.btn_query.sizeHint())
        self.btn_query.clicked.connect(parent.update_data)

        self.addWidget(self.query["date"]["start"]["title"])
        self.addWidget(self.query["date"]["start"]["value"])
        self.addWidget(self.query["date"]["end"]["title"])
        self.addWidget(self.query["date"]["end"]["value"])
        self.addWidget(self.btn_query)

        # Test
        tree_view = QTreeView()
        tree_view.setHeaderHidden(True)
        tree_view.setContextMenuPolicy(Qt.CustomContextMenu)
        model = QtGui.QStandardItemModel()

        item1 = QtGui.QStandardItem("Item 1")
        item1.appendRow(QtGui.QStandardItem("Item 1-1"))
        model.appendRow(item1)
        model.appendRow(QtGui.QStandardItem("Item 2"))
        tree_view.setModel(model)

        self.addWidget(QLabel("Details:"))
        self.addWidget(tree_view)
예제 #6
0
    def dormant(self):

        treeView = QTreeView()

        treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        treeView.customContextMenuRequested.connect(self.tabMenu)

        fileSystemModel = QFileSystemModel(treeView)
        fileSystemModel.setReadOnly(False)
        self.dormant_path = expanduser('~') + '\Documents'
        root = fileSystemModel.setRootPath(self.dormant_path)
        treeView.setModel(fileSystemModel)
        treeView.setRootIndex(root)
        treeView.setSortingEnabled(True)

        treeView.clicked.connect(self.on_clicked)

        dormant_clearAll_button = QPushButton("Clear all Files")
        dormant_clearAll_button.setFixedSize(90, 30)
        self.dormant_delete_button = QPushButton('Delete')
        self.dormant_delete_button.setFixedSize(90, 30)

        self.dormant_delete_button.clicked.connect(self.delete_file)

        dormant_zip_button = QPushButton('Zip file')
        dormant_zip_button.setFixedSize(90, 30)

        dormant_zip_button.clicked.connect(self.zip_file)

        Layout = QHBoxLayout(self)
        Layout.addWidget(dormant_clearAll_button)
        Layout.addWidget(self.dormant_delete_button)
        Layout.addWidget(dormant_zip_button)
        Layout.addWidget(treeView)

        #dormant_clearAll_button.clicked.connect(self.clear_Files())

        self.UnUsed_Tab.setLayout(Layout)
예제 #7
0
class ZeroConfExplorer(QWidget):
    """
    create a zeroconf qgroubbox with a qlist view 
    """
    def __init__(self, name):
        super(ZeroConfExplorer, self).__init__()
        # init explorer to None
        self.oscquery_device = None
        if not name:
            name = 'OSCJSON thru TCP Explorer'
        # create the view
        self.explorer = QTreeView()
        # Hide Useless Header
        self.explorer.header().hide()
        self.panel = Panel()
        # create right-click menu
        self.explorer.setContextMenuPolicy(Qt.CustomContextMenu)
        self.explorer.customContextMenuRequested.connect(self.contextual_menu)
        # create the model
        self.devices_model = QStandardItemModel()
        # link model to the view
        self.explorer.setModel(self.devices_model)
        self.explorer.selectionModel().selectionChanged.connect(
            self.selection_updated)
        # set selection
        self.device_selection_model = self.explorer.selectionModel()
        # set layout and group
        Layout = QGridLayout()
        # add the view to the layout
        Layout.addWidget(self.explorer, 0, 0)
        Layout.addWidget(self.panel, 0, 1)
        # add the layout to the GroupBox
        self.setLayout(Layout)
        #self.setMinimumSize(300, 300)
        self.explorer.setFixedSize(300, 300)
        # start zeroconf services
        zeroconf = Zeroconf()
        # start the callback, it will create items
        listener = ZeroConfListener(self.devices_model)
        listener.add_device.connect(self.connect_device)
        browser = ServiceBrowser(zeroconf, "_oscjson._tcp.local.", listener)
        self.current_remote = None

    def connect_device(self, device):
        self.panel.device = device

    def contextual_menu(self, position):

        indexes = self.explorer.selectedIndexes()
        if len(indexes) > 0:

            level = 0
            index = indexes[0]
            while index.parent().isValid():
                index = index.parent()
                level += 1
            node = self.devices_model.itemFromIndex(index)
            menu = QMenu()
            if level == 0:
                menu.addAction("Refresh Device Namespace", node.update)
            elif level > 0:
                menu.addAction("Refresh Node", node.update)
            menu.exec_(self.explorer.viewport().mapToGlobal(position))

    def selection_updated(self, *args, **kwargs):
        """
        called when device selection is updated
        we will disconnect our ossia.OSCQueryDevice from the previous device if there was one
        and then we will reconnect it to the current instance of the Device model
        """
        index = self.device_selection_model.selectedIndexes()
        # we consider unique selection
        modelIndex = index[0]
        if modelIndex:
            if self.current_remote:
                self.panel.layout.removeWidget(self.current_remote)
                self.current_remote.deleteLater()
                del self.current_remote
                self.current_remote = None
            node = self.devices_model.itemFromIndex(modelIndex).node
            if node.__class__.__name__ == 'OSCQueryDevice':
                print('Device')
            else:
                if node.parameter:
                    self.current_remote = self.panel.add_remote(node.parameter)
                else:
                    self.current_remote = self.panel.add_inspector(node)
        else:
            print('no node selected')
예제 #8
0
        :type pos: QPoint
        :return:
        """
        index = tree.indexAt(pos)
        node = model.getNode(index)
        if node.is_fetch_more and node.parent() == model.root:
            return
        menu = QMenu()
        menu.addAction('Add preview')
        menu.addAction('Load footprints')

        menu.exec_(tree.viewport().mapToGlobal(pos))

    @pyqtSlot('QModelIndex')
    def item_clicked(index):
        if index.column() == 0:
            model.fetchMoreTopItems(index)
        elif index.column() == 1:
            open_menu(tree.viewport().mapFromGlobal(QCursor.pos()))

    tree.setContextMenuPolicy(Qt.CustomContextMenu)
    # noinspection PyUnresolvedReferences
    tree.customContextMenuRequested['QPoint'].connect(open_menu)

    # noinspection PyUnresolvedReferences
    tree.clicked['QModelIndex'].connect(item_clicked)

    tree.show()

    sys.exit(app.exec_())
예제 #9
0
class DanaBrowseWindow(QMainWindow):
    def __init__(self, args):
        super(DanaBrowseWindow, self).__init__()
        #Leeo la configuracion
        self.configFile = args.configFile
        self.secure = args.secure
        self.cubeFile = args.cubeFile
        self.sysExclude = args.sysExclude

        self.maxLevel = 1  #para poder modificarlo luego
        self.dictionary = DataDict(defFile=args.configFile,
                                   secure=args.secure,
                                   sysExclude=args.sysExclude)
        #TODO variables asociadas del diccionario. Reevaluar al limpiar

        self.baseModel = self.dictionary.baseModel
        self.configData = self.dictionary.configData
        self.conn = self.dictionary.conn
        if self.dictionary.isEmpty:
            self.newConfigData()
            #self.dictionary._cargaModelo(self.dictionary.baseModel)
        self.setupView()
        self.cubeMgr = None  # necesito mas adelante que este definida
        if config.DEBUG:
            print('inicializacion completa')
        #CHANGE here

        self.queryView = TableBrowse(None)

        self.dictMenu = self.menuBar().addMenu("&Conexiones")
        self.dictMenu.addAction("&New ...", self.newConnection, "Ctrl+N")
        self.dictMenu.addAction("&Modify ...", self.modConnection, "Ctrl+M")
        self.dictMenu.addAction("&Delete ...", self.delConnection, "Ctrl+D")
        self.dictMenu.addAction("&Save Config File", self.saveConfigFile,
                                "Ctrl+S")
        self.dictMenu.addAction("E&xit", self.close, "Ctrl+Q")

        self.queryMenu = self.menuBar().addMenu('Consulta de &datos')
        self.queryMenu.addAction("Cerrar", self.hideDatabrowse)
        self.queryMenu.setEnabled(False)

        self.cubeMenu = self.menuBar().addMenu("C&ubo")
        self.cubeMenu.addAction("&Salvar", self.saveCubeFile, "Ctrl+S")
        #self.cubeMenu.addAction("&Restaurar", self.restoreCubeFile, "Ctrl+M")
        self.cubeMenu.addAction("S&alir", self.hideCube, "Ctrl+C")
        self.cubeMenu.addSeparator()
        self.cubeMenu.addAction("Ver &ejemplo de datos del cubo",
                                self.previewCube, "Ctrl+E")
        self.cubeMenu.setEnabled(False)

        #self.queryModel = self.queryView.baseModel

        self.querySplitter = QSplitter(Qt.Vertical)
        self.querySplitter.addWidget(self.view)
        #self.querySplitter.addWidget(self.queryView)

        self.configSplitter = QSplitter(Qt.Horizontal)
        self.configSplitter.addWidget(self.querySplitter)

        self.setCentralWidget(self.configSplitter)

        self.setWindowTitle("Visualizador de base de datos")
        """
            estas funciones son para soportar para los decoradores keep position y tal
        """

    def model(self):
        return self.baseModel

    def isExpanded(self, idx):
        return self.view.isExpanded(idx)

    def setExpanded(self, idx, state):
        return self.view.setExpanded(idx, state)

    def setupView(self):
        self.view = QTreeView(self)
        self.view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.view.customContextMenuRequested.connect(self.openContextMenu)
        self.view.doubleClicked.connect(self.test)
        self.view.setModel(self.baseModel)
        #self.view.resizeColumnToContents(0)
        self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)

        self.view.expandAll()
        for m in range(self.baseModel.columnCount()):
            self.view.resizeColumnToContents(m)
        self.view.collapseAll()
        self.view.expandToDepth(0)
        #self.view.setHeaderHidden(True)
        #self.view.setSortingEnabled(True)
        #self.view.setRootIsDecorated(False)
        self.view.setAlternatingRowColors(True)
        #self.view.sortByColumn(0, Qt.AscendingOrder)

    def newConfigData(self):
        self.configData = dict()
        self.configData['Conexiones'] = dict()
        self.editConnection(None)
        if self.configData['Conexiones']:
            self.saveConfigFile()
            print(self.configData)
            self.dictionary._cargaModelo(confData=self.configData['Conexiones']
                                         )  #self.dictionary.baseModel)
        else:
            QMessageBox.critical(
                self, "Error Fatal",
                "No se ha encontrado una conexión valida.\nFin de proceso")
            self.close()

    def saveConfigFile(self):
        dump_config(self.configData,
                    getConfigFileName(self.configFile),
                    secure=self.secure)  #TODO de momento

    def closeEvent(self, event):
        self.close()

    def close(self):

        if self.cubeMgr:
            self.saveConfigFile()

        for conid in self.conn:
            if self.conn[conid] is None:
                continue
            if self.conn[conid].closed:
                self.conn[conid].close()
        self.saveConfigFile()

        sys.exit()

    def newConnection(self):
        confName = self.editConnection(None)
        # esta claro que sobran parametros
        self.dictionary.appendConnection(confName)

    def modConnection(self, nombre=None):
        if nombre is None:
            selDialog = SelectConnectionDlg(self.configData['Conexiones'])
            if selDialog.exec_():
                confName = selDialog.conexion
            else:
                return
        else:
            confName = nombre
        self.editConnection(confName)
        self.updateModel(confName)

    @waiting_effects
    def updateModel(self, nombre=None):
        self.dictionary.updateModel(nombre)

    def delConnection(self, nombre=None):
        if nombre is None:
            selDialog = SelectConnectionDlg(self.configData['Conexiones'])
            if selDialog.exec_():
                confName = selDialog.conexion
            else:
                return
        else:
            confName = nombre
        self.dictionary.dropConnection(confName)

    def editConnection(self, nombre=None):
        attr_list = ('driver', 'dbname', 'dbhost', 'dbuser', 'dbpass',
                     'dbport', 'debug')
        if nombre is None:
            datos = [None for k in range(len(attr_list) + 1)]
        else:
            datos = [
                nombre,
            ] + dict2row(self.configData['Conexiones'][nombre], attr_list)
            datos[1] = DRIVERS.index(datos[1])
        #contexto
        context = (
            (
                'Nombre',
                QLineEdit,
                {
                    'setReadOnly': True
                } if nombre is not None else None,
                None,
            ),
            # driver
            (
                "Driver ",
                QComboBox,
                None,
                DRIVERS,
            ),
            (
                "DataBase Name",
                QLineEdit,
                None,
                None,
            ),
            (
                "Host",
                QLineEdit,
                None,
                None,
            ),
            (
                "User",
                QLineEdit,
                None,
                None,
            ),
            (
                "Password",
                QLineEdit,
                {
                    'setEchoMode': QLineEdit.Password
                },
                None,
            ),
            (
                "Port",
                QLineEdit,
                None,
                None,
            ),
            (
                "Debug",
                QCheckBox,
                None,
                None,
            ))
        parmDialog = ConnectionSheetDlg('Edite la conexion', context, datos,
                                        self)
        if parmDialog.exec_():
            #TODO deberia verificar que se han cambiado los datos
            #datos[1]=DRIVERS[datos[1]]
            self.configData['Conexiones'][datos[0]] = row2dict(
                datos[1:], attr_list)
            return datos[0]

    @keep_tree_layout()
    def openContextMenu(self, position):
        """
        """
        item = None
        indexes = self.view.selectedIndexes()
        if len(indexes) > 0:
            index = indexes[0]
            item = self.baseModel.itemFromIndex(index)
        menu = QMenu()
        if item:
            item.setMenuActions(menu, self)
            action = menu.exec_(self.view.viewport().mapToGlobal(position))
        #getContextMenu(item,action,self)

    @waiting_effects
    def databrowse(self, confName, schema, table, iters=0):
        #print(confName,schema,table,self.dictionary.conn[confName])
        self.queryView.reconnect(
            self.queryView.getConnection(self.dictionary, confName, schema,
                                         table, iters))
        self.queryView.executeNewScript(
            self.queryView.generateSQL(confName,
                                       schema,
                                       table,
                                       iters,
                                       pFilter=None))
        if self.queryView.isHidden():
            self.queryView.show()
        self.queryMenu.setEnabled(True)
        if self.querySplitter.count(
        ) == 1:  #de momento parece un modo sencillo de no multiplicar en exceso
            self.querySplitter.addWidget(self.queryView)

    def hideDatabrowse(self):
        self.queryView.hide()
        self.queryMenu.setEnabled(False)

    def prepareNewCube(self, confName, schema, table):
        # aqui tiene que venir un dialogo para seleccionar nombre del cubo
        maxLevel = self.maxLevel
        parmDlg = GenerationSheetDlg('Parámetros de generación', table,
                                     maxLevel)
        if parmDlg.exec_():
            kname = parmDlg.data[0]
            maxLevel = parmDlg.data[1]
        infox = info2cube(self.dictionary, confName, schema, table, maxLevel)
        if kname != table:
            infox[kname] = infox.pop(table)
        return infox

    def cubebrowse(self, confName, schema, table):

        infox = self.prepareNewCube(confName, schema, table)
        if self.cubeMgr and not self.cubeMgr.isHidden():
            self.hideCube()
        self.cubeMgr = CubeMgr(self,
                               confName,
                               schema,
                               table,
                               self.dictionary,
                               rawCube=infox,
                               cubeFile=self.cubeFile)
        self.cubeMgr.expandToDepth(1)
        #if self.configSplitter.count() == 1:  #de momento parece un modo sencillo de no multiplicar en exceso
        self.configSplitter.addWidget(self.cubeMgr)

        self.cubeMgr.show()
        self.cubeMenu.setEnabled(True)

    def saveCubeFile(self):
        self.cubeMgr.saveCubeFile()

    def restoreCubeFile(self):
        self.cubeMgr.restoreConfigFile()

    def hideCube(self):
        self.cubeMgr.saveCubeFile()
        self.cubeMgr.hide()
        self.cubeMenu.setEnabled(False)

    def test(self, index):
        return
        print(index.row(), index.column())
        item = self.baseModel.itemFromIndex(index)
        print(item.text(), item.model())

    def refreshTable(self):
        self.baseModel.emitModelReset()

    def previewCube(self):
        startItem = self.cubeMgr.model().item(0, 0)
        conName = self.cubeMgr.defaultConnection
        self.queryView.reconnect(
            self.queryView.getConnection(self.dictionary, confName=conName))
        query = self.cubeMgr.getPreviewQuery(startItem)
        self.queryView.executeNewScript(query)
        if self.queryView.isHidden():
            self.queryView.show()
        self.queryMenu.setEnabled(True)
        if self.querySplitter.count(
        ) == 1:  #de momento parece un modo sencillo de no multiplicar en exceso
            self.querySplitter.addWidget(self.queryView)
예제 #10
0
class FileTreeView(QWidget):
    on_menu_select = pyqtSignal(str, dict)

    def __init__(self):
        super().__init__()
        self.list_file = []
        self.get_data_file()

        v_box = QVBoxLayout()
        v_box.addLayout(self.finder())
        v_box.addWidget(self.tree_view())
        self.setLayout(v_box)

    def finder(self):
        w_find = QLineEdit()
        w_find.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))

        w_find_button = QPushButton()
        w_find_button.setText("Find")
        w_find_button.setFixedWidth(50)

        h_box = QHBoxLayout()
        h_box.addWidget(w_find)
        h_box.addWidget(w_find_button, 1)
        return h_box

    def get_data_file(self):
        self.list_file = self.create_list(get_data_folder())

    def create_list(self, dir):
        lst = os.listdir(path=dir)
        list_file = []
        for f in lst:
            path = dir + "\\" + f
            file = {}
            if os.path.isdir(path):
                file["dir"] = dir
                file["name"] = f
                file["isDir"] = True
                file["child"] = self.create_list(path)
            else:
                file["dir"] = dir
                file["name"] = f
                file["isDir"] = False

            list_file.append(file)
        return list_file

    def tree_view(self):
        self.tree = QTreeView()
        self.tree.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))

        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.openMenu)

        self.model = QtGui.QStandardItemModel()
        self.model.setHorizontalHeaderLabels(['List API'])
        self.tree.header().setDefaultSectionSize(180)
        self.tree.setModel(self.model)
        self.import_data(self.model.invisibleRootItem(), self.list_file)
        self.tree.expandAll()

        return self.tree

    def import_data(self, parent_item, list):
        for i in list:
            if i["isDir"]:
                item = QStandardItem(i["name"])
                item.setEditable(False)
                item.setIcon(QIcon(get_icon_link("folder_yellow.svg")))
                item.setData(i)
                parent_item.appendRow(item)
                self.import_data(item, i["child"])
            else:
                item = QStandardItem(i["name"])
                item.setEditable(False)
                item.setIcon(QIcon(get_icon_link("text_snippet.svg")))
                item.setData(i)
                parent_item.appendRow(item)

    def openMenu(self, position):
        indexes = self.tree.selectedIndexes()
        level = 0
        data = {}
        if len(indexes) > 0:
            index = indexes[0]
            item = self.model.itemFromIndex(index)
            data = item.data()
            if data['isDir']:
                level = 1
            else:
                level = 2

        menu = QMenu()
        menu.setStyleSheet(open('stylesheet/default.qss').read())

        # Create preference action
        rename_action = QAction(QIcon(get_icon_link('edit.svg')), '&Rename',
                                self)
        rename_action.setStatusTip('Rename')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        new_action = QAction(QIcon(get_icon_link('create_new_folder.svg')),
                             '&New Folder', self)
        new_action.setStatusTip('New Folder')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        refresh_action = QAction(QIcon(get_icon_link('refresh.svg')),
                                 '&Refresh', self)
        refresh_action.setStatusTip('Refresh')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        delete_action = QAction(QIcon(get_icon_link('delete_forever.svg')),
                                '&Delete', self)
        delete_action.setStatusTip('Delete')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        open_action = QAction(QIcon(get_icon_link('open_in_new.svg')), '&Open',
                              self)
        open_action.setStatusTip('Open file')
        # open_action.triggered.connect(self.open_file)

        # Create preference action
        expand_action = QAction(QIcon(), '&Expand', self)
        expand_action.setStatusTip('Expand')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        collapse_action = QAction(QIcon(), '&Collapse', self)
        collapse_action.setStatusTip('Collapse')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        copy_action = QAction(QIcon(get_icon_link('content_copy.svg')),
                              '&Copy', self)
        copy_action.setStatusTip('Copy')
        # exitAction.triggered.connect(self.exitCall)

        # Create preference action
        move_action = QAction(QIcon(get_icon_link('zoom_out_map.svg')),
                              '&Move', self)
        move_action.setStatusTip('Move')
        # exitAction.triggered.connect(self.exitCall)

        if level == 1:
            menu.addAction(rename_action)
            menu.addAction(new_action)
            menu.addSeparator()
            menu.addAction(refresh_action)
            menu.addAction(expand_action)
            menu.addAction(collapse_action)
            menu.addSeparator()
            menu.addAction(delete_action)
        elif level == 2:
            menu.addAction(open_action)
            menu.addAction(refresh_action)
            menu.addSeparator()
            menu.addAction(rename_action)
            menu.addAction(copy_action)
            menu.addAction(move_action)
            menu.addSeparator()
            menu.addAction(delete_action)
        else:
            menu.addAction(new_action)
            menu.addAction(refresh_action)

        action = menu.exec_(self.tree.viewport().mapToGlobal(position))
        if action == open_action:
            self.on_menu_select.emit("open", data)
예제 #11
0
class OpenedFileExplorer(DockWidget):
    """Opened File Explorer is list widget with list of opened files.
    It implements switching current file, files sorting. Uses _OpenedFileModel internally.
    Class instance created by Workspace.
    """

    def __init__(self, workspace):
        DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O")

        self._workspace = workspace

        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.tvFiles = QTreeView(self)
        self.tvFiles.setHeaderHidden(True)
        self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked)
        self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tvFiles.setDragEnabled(True)
        self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove)
        self.tvFiles.setRootIsDecorated(False)
        self.tvFiles.setTextElideMode(Qt.ElideMiddle)
        self.tvFiles.setUniformRowHeights(True)

        self.tvFiles.customContextMenuRequested.connect(self._onTvFilesCustomContextMenuRequested)

        self.setWidget(self.tvFiles)
        self.setFocusProxy(self.tvFiles)

        self.model = _OpenedFileModel(self)  # Not protected, because used by Configurator
        self.tvFiles.setModel(self.model)
        self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.tvFiles.setAttribute(Qt.WA_MacSmallSize)

        self._workspace.currentDocumentChanged.connect(self._onCurrentDocumentChanged)

        # disconnected by startModifyModel()
        self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged)

        self.tvFiles.activated.connect(self._workspace.focusCurrentDocument)

        core.actionManager().addAction("mView/aOpenedFiles", self.showAction())

    def terminate(self):
        """Explicitly called destructor
        """
        core.actionManager().removeAction("mView/aOpenedFiles")

    def startModifyModel(self):
        """Blocks signals from model while it is modified by code
        """
        self.tvFiles.selectionModel().selectionChanged.disconnect(self._onSelectionModelSelectionChanged)

    def finishModifyModel(self):
        """Unblocks signals from model
        """
        self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged)

    @pyqtSlot(Document, Document)
    def _onCurrentDocumentChanged(self, oldDocument, currentDocument):  # pylint: disable=W0613
        """ Current document has been changed on workspace
        """
        if currentDocument is not None:
            index = self.model.documentIndex(currentDocument)

            self.startModifyModel()
            self.tvFiles.setCurrentIndex(index)
            # scroll the view
            self.tvFiles.scrollTo(index)
            self.finishModifyModel()

    @pyqtSlot(QItemSelection, QItemSelection)
    def _onSelectionModelSelectionChanged(self, selected, deselected):  # pylint: disable=W0613
        """ Item selected in the list. Switch current document
        """
        if not selected.indexes():  # empty list, last file closed
            return

        index = selected.indexes()[0]
        # backup/restore current focused widget as setting active mdi window will steal it
        focusWidget = self.window().focusWidget()

        # set current document
        document = self._workspace.sortedDocuments[index.row()]
        self._workspace.setCurrentDocument(document)

        # restore focus widget
        if focusWidget:
            focusWidget.setFocus()

    @pyqtSlot(QPoint)
    def _onTvFilesCustomContextMenuRequested(self, pos):
        """Connected automatically by uic
        """
        menu = QMenu()

        menu.addAction(core.actionManager().action("mFile/mClose/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mSave/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mReload/aCurrent"))
        menu.addSeparator()
        menu.addAction(core.actionManager().action("mFile/mFileSystem/aRename"))
        toggleExecutableAction = core.actionManager().action("mFile/mFileSystem/aToggleExecutable")
        if toggleExecutableAction:  # not available on Windows
            menu.addAction(toggleExecutableAction)
        core.actionManager().action("mFile/mFileSystem").menu().aboutToShow.emit()  # to update aToggleExecutable

        menu.exec_(self.tvFiles.mapToGlobal(pos))
예제 #12
0
파일: navigation.py 프로젝트: lheido/Mojuru
class Navigation(QWidget):
    """
    Navigation class definition.
    
    Provide a combobox to switch on each opened directories and display it into
    a tree view
    
    Provide 2 useful function (to use in alter module):
      - add_action(name, shortcut, callback)
         - callback take 2 arguments : file_info and parent
      - add_separator()
    
    """
    
    SETTINGS_DIRECTORIES = 'navigation_dirs'
    SETTINGS_CURRENT_DIR = 'navigation_current_dir'
    
    onFileItemActivated = pyqtSignal(QFileInfo, name="onFileItemActivated")
    onDirItemActivated = pyqtSignal(QFileInfo, name="onDirItemActivated")
    
    def __init__(self, parent=None):
        super(Navigation, self).__init__(parent)
        self.setObjectName("Navigation")
        
        self.layout = QVBoxLayout(self)
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0,0,0,0)
        
        self.menu_button = QPushButton('Select directory', self)
        self.menu_button.setFlat(True)
#        self.menu_button.clicked.connect(self.on_menu_button_clicked)
        self.menu = QMenu(self)
        self.menu_button.setMenu(self.menu)
        self.menu_directories = QMenu(self)
        self.menu_directories.setTitle('Directories')
        self.menu_add_action(
            'Open directory', self.open_directory, None, QKeySequence.Open)
        self.menu_add_separator()
        self.menu_add_action('Refresh', self.reset, None, QKeySequence.Refresh)
        # @TODO invoke_all
        self.menu_add_separator()
        self.menu.addMenu(self.menu_directories)
        
        self.tree = QTreeView(self)
        self.model = FileSystemModel(self)
        self.tree.setModel(self.model)
        self.tree.setColumnHidden(1, True)
        self.tree.setColumnHidden(2, True)
        self.tree.setColumnHidden(3, True)
        self.tree.setHeaderHidden(True)
        # only to expand directory or activated with one click
        self.tree.clicked.connect(self.on_item_clicked)
        # else, for file use activated signal
        self.tree.activated.connect(self.on_item_activated)
        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.on_context_menu)
        
        self.widgets = collections.OrderedDict()
        self.widgets['menu_button'] = self.menu_button
        self.widgets['tree'] = self.tree
        
        # @ToDo: Alter.invoke_all('add_widget', self.widgets)
        
        for name, widget in self.widgets.items():
            if name == 'menu_button':
                self.layout.addWidget(widget, 0, Qt.AlignLeft)
            else:
                self.layout.addWidget(widget)
        
        self.context_menu = QMenu(self)
        self.add_action('New file', QKeySequence.New, 
                        FileSystemHelper.new_file)
        self.add_action('New Directory', '', 
                        FileSystemHelper.new_directory)
        self.add_separator()
        self.add_action('Rename', '', FileSystemHelper.rename)
        self.add_action('Copy', QKeySequence.Copy, FileSystemHelper.copy)
        self.add_action('Cut', QKeySequence.Cut, FileSystemHelper.cut)
        self.add_action('Paste', QKeySequence.Paste, FileSystemHelper.paste)
        self.add_separator()
        self.add_action('Delete', QKeySequence.Delete, 
                        FileSystemHelper.delete)
        
        # @ToDo Alter.invoke_all('navigation_add_action', self)
        
        #restore previous session and data
        dirs = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_DIRECTORIES, None, True)
        for directory_path in dirs:
            name = os.path.basename(directory_path)
            self.menu_add_directory(name, directory_path)
        current_dir = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_CURRENT_DIR, '')
        if current_dir:
            for action in self.menu_directories.actions():
                if action.data() == current_dir:
                    action.trigger()
        
        self.menu_button.setFocusPolicy(Qt.NoFocus)
        self.menu_button.setFocusProxy(self.tree)
    
    def reset(self, file_info):
        self.model.beginResetModel()
        current_dir = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_CURRENT_DIR, '')
        if current_dir:
            for action in self.menu_directories.actions():
                if action.data() == current_dir:
                    action.trigger()
    
    def on_menu_button_clicked(self):
        pos = self.mapToGlobal(self.menu_button.pos())
        menu_width = self.menu.sizeHint().width()
        pos.setY(pos.y() + self.menu_button.height())
#        pos.setX(pos.x() + self.menu_button.width() - menu_width)
        if len(self.menu.actions()) > 0:
            self.menu.exec(pos)
    
    def menu_add_action(self, name, callback, data=None, shortcut=None, icon=None):
        action = QAction(name, self)
        if icon:
            action.setIcon(icon)
        if shortcut:
            action.setShortcut(shortcut)
            action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        if data:
            action.setData(data)
        action.triggered.connect(callback)
        self.addAction(action)
        self.menu.addAction(action)
    
    def menu_add_directory(self, name, data):
        action = QAction(name, self)
        action.setData(data)
        action.triggered.connect(self.on_menu_action_triggered)
        self.menu_directories.addAction(action)
        return action
    
    def menu_add_separator(self):
        self.menu.addSeparator()
    
    def add_action(self, name, shortcut, callback, icon = None):
        """
        Ajoute une action au context menu et au widget navigation lui même.
        Créer une fonction à la volé pour fournir des arguments aux fonctions
        associé aux actions.
        """
        action = QAction(name, self)
        if icon:
            action.setIcon(icon)
        action.setShortcut(shortcut)
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.__wrapper(callback))
        self.addAction(action)
        self.context_menu.addAction(action)
    
    def add_separator(self):
        """Simple abstraction of self.context_menu.addSeparator()"""
        self.context_menu.addSeparator()
    
    def __wrapper(self, callback):
        def __new_function():
            """
            __new_function représente la forme de tous les callbacks connecté
            à une action pour pouvoir utiliser les raccourcis en même temps que
            le menu contextuel.
            """
            action = self.sender()
            file_info = action.data()
            if not file_info:
                indexes = self.tree.selectedIndexes()
                if indexes:
                    model_index = indexes[0]
                    file_info = self.model.fileInfo(model_index)
                    callback(file_info, self)
                elif action.shortcut() == QKeySequence.New:
                    file_info = self.model.fileInfo(self.tree.rootIndex())
                    callback(file_info, self)
            else:
                callback(file_info, self)
                action.setData(None)
        return __new_function
    
    def question(self, text, informative_text = None):
        message_box = QMessageBox(self)
        message_box.setText(text)
        if informative_text:
            message_box.setInformativeText(informative_text)
        message_box.setStandardButtons(
            QMessageBox.No | QMessageBox.Yes)
        message_box.setDefaultButton(QMessageBox.No)
        return message_box.exec()
    
    def on_context_menu(self, point):
        model_index = self.tree.indexAt(point)
        file_info = self.model.fileInfo(model_index)
        # pour chaque action on met a jour les data (file_info)
        # puis on altère les actions (ex enabled)
        for action in self.context_menu.actions():
            if not action.isSeparator():
                action.setData(file_info)
                action.setEnabled(model_index.isValid())
                if action.shortcut() == QKeySequence.New:
                    action.setEnabled(True)
                    if not model_index.isValid():
                        file_info = self.model.fileInfo(self.tree.rootIndex())
                        action.setData(file_info)
                if action.shortcut() == QKeySequence.Paste:
                    enable = FileSystemHelper.ready() and model_index.isValid()
                    action.setEnabled(enable)
                if action.shortcut() == QKeySequence.Delete:
                    # remove directory only if is an empty directory
                    if model_index.isValid() and file_info.isDir():
                        path = file_info.absoluteFilePath()
                        # QDir(path).count() always contains '.' and '..'
                        action.setEnabled(QDir(path).count() == 2)
                # @ToDo 
                #Alter.invoke_all(
                #    'navigation_on_menu_action', 
                #    model_index, file_info, action, self)
        if len(self.context_menu.actions()) > 0:
            self.context_menu.exec(self.tree.mapToGlobal(point))
        # reset action data, sinon y a des problèmes dans _new_function
        for action in self.context_menu.actions():
            action.setData(None)
    
    def on_item_activated(self, index):
        qFileInfo = self.model.fileInfo(index)
        if qFileInfo.isDir():
            self.onDirItemActivated.emit(qFileInfo)
        else:
            self.onFileItemActivated.emit(qFileInfo)
    
    def on_item_clicked(self, index):
        qFileInfo = self.model.fileInfo(index)
        if qFileInfo.isDir():
            self.onDirItemActivated.emit(qFileInfo)
            self.tree.setExpanded(index, not self.tree.isExpanded(index))
        else:
            self.onFileItemActivated.emit(qFileInfo)
    
    def open_directory(self):
        project = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_CURRENT_DIR, '')
        path = QFileDialog.getExistingDirectory(self, "Open Directory", project)
        if path:
            name = os.path.basename(path)
            action = self.menu_add_directory(name, path)
            self.save_directories_path()
            action.trigger()
    
    def on_menu_action_triggered(self):
        action = self.sender()
        path = action.data()
        if path:
            self.model.setRootPath(path)
            self.tree.setRootIndex(self.model.index(path))
            self.menu_button.setText(os.path.basename(path))
            self.save_current_dir(path)
    
    def save_directories_path(self):
        ModuleManager.core['settings'].Settings.set_value(
            self.SETTINGS_DIRECTORIES,
            [action.data() for action in self.menu_directories.actions()]    
        )
    
    def save_current_dir(self, path):
        ModuleManager.core['settings'].Settings.set_value(
            self.SETTINGS_CURRENT_DIR,
            path
        )
예제 #13
0
class ParamEditor(QTabWidget):
    jsonData = {}
    isParseError = False
    error = pyqtSignal(str, str)
    format_data = []

    horizontal_tab = 0
    vertical_tab = 1

    def __init__(self, mode=0):
        super().__init__()
        self.setObjectName("ParamEditor")
        self.json_edit = JSONEditor()
        self.table_edit = QTreeView()
        self.model = QStandardItemModel()
        self.table_edit.setAlternatingRowColors(True)

        self.table_edit.setModel(self.model)

        self.addTab(self.table_edit, "UI")
        self.addTab(self.json_edit, "JSON")

        self.table_edit.setContextMenuPolicy(Qt.CustomContextMenu)
        self.table_edit.customContextMenuRequested.connect(self.open_menu)
        self.model.itemChanged.connect(self.data_change)
        self.currentChanged.connect(self.tab_selected)

        if mode == self.vertical_tab:
            self.setTabPosition(QTabWidget.West)

    def set_json_data(self, param):
        self.jsonData = param
        self.update()

    def update(self):
        self.load_param_2_table(self.jsonData)
        self.load_param_2_json_editor(self.jsonData)

    def load_param_2_table(self, param):
        self.model.clear()
        self.model.setHorizontalHeaderLabels(['key', 'value'])
        header = self.table_edit.header()
        header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents)
        self.create_param_ui(self.model.invisibleRootItem(), param, 0, "", [])
        self.table_edit.expandAll()

    def create_param_ui(self,
                        parent,
                        obj,
                        level,
                        parent_name="",
                        item_link=None):
        if item_link is None:
            item_link = []
        if isinstance(obj, list):
            for i in range(len(obj)):
                if isinstance(obj[i], dict) or isinstance(obj[i], list):
                    item = QStandardItem(str(i) + ": ")
                    item.setEditable(False)
                    data = {
                        "key": ":" + str(i),
                        "parent": parent_name,
                        'level': level,
                        'link': item_link
                    }
                    item.setData(data)
                    parent.appendRow([item])
                    link = item_link.copy()
                    link.append(":" + str(i))
                    self.create_param_ui(item, obj[i], level, parent_name,
                                         link)
                else:
                    item = QStandardItem(str(i) + ": ")
                    item.setEditable(False)
                    value = QStandardItem(str(obj[i]))
                    value.setEditable(True)
                    link = item_link.copy()
                    link.append(":" + str(i))
                    data = {
                        "key": ":" + str(i),
                        "parent": parent_name,
                        'level': level,
                        'link': link
                    }
                    item.setData(data)
                    value.setData(data)
                    parent.appendRow([item, value])
        elif isinstance(obj, dict):
            for key, value in obj.items():
                if isinstance(value, dict) or isinstance(value, list):
                    item = QStandardItem(key)
                    item.setEditable(False)
                    data = {
                        "key": key,
                        "parent": parent_name,
                        'level': level,
                        "link": item_link
                    }
                    item.setData(data)
                    parent.appendRow([item])
                    link = item_link.copy()
                    link.append(key)
                    self.create_param_ui(item, value, level + 1, key, link)
                else:
                    self.add_object(parent, obj[key], level, key, item_link)
        else:
            self.add_object(parent, obj, level, parent_name, item_link)

    def add_object(self, parent, obj, level, parent_name="", item_link=None):
        if item_link is None:
            item_link = []
        item = QStandardItem(parent_name)
        item.setEditable(False)
        value = QStandardItem(str(obj))
        value.setEditable(True)
        data = {
            "key": parent_name,
            "parent": "",
            'level': level,
            'link': item_link
        }
        item.setData(data)
        value.setData(data)
        parent.appendRow([item, value])

    def load_param_2_json_editor(self, param):
        self.json_edit.setDocument(color_json(param))

    def sync(self):
        if self.currentIndex() == 0:
            self.load_param_2_json_editor(self.jsonData)
        elif self.currentIndex() == 1:
            self.try_sync_table()

    def try_sync_table(self):
        self.isParseError = False
        str_param = self.json_edit.toPlainText()
        try:
            if str_param == "" or str_param.isspace():
                self.jsonData = {}
            else:
                self.jsonData = json.loads(str_param)
            self.load_param_2_json_editor(self.jsonData)
            self.isParseError = False
        except Exception as ex:
            print(ex)
            self.error.emit("JSON Param", str(ex))
            self.isParseError = True
        finally:
            if self.isParseError:
                self.load_param_2_json_editor(str_param)
            else:
                self.load_param_2_json_editor(self.jsonData)
            self.load_param_2_table(self.jsonData)
        return self.isParseError

    def data_change(self, item: QStandardItem):
        data = item.data()
        if data is not None and "link" in data:
            item_link = data['link']
            link = item_link.copy()
            link.append(data['key'])
            self.update_data(self.jsonData, link, item.text(), 0, len(link))

    def update_data(self, obj, link, new, index, end):
        i = link[index]
        if i.startswith(":"):
            i = int(i.replace(":", ""))
        if index == end - 1:
            obj[i] = new
        else:
            self.update_data(obj[i], link, new, index + 1, end)

    def tab_selected(self, arg=None):
        if arg is not None:
            if arg == 0:
                self.try_sync_table()
            else:
                if not self.isParseError:
                    self.load_param_2_json_editor(self.jsonData)

    def open_menu(self, position):
        indexes = self.table_edit.selectedIndexes()
        level = 0
        data_link = []
        link = []
        item = None
        if len(indexes) > 0:
            index = indexes[0]
            item = self.model.itemFromIndex(index)
            data = item.data()
            if data is not None:
                link = data['link']
                data_link = link.copy()
                data_link.append(data['key'])
                level = 1

        menu = QMenu()
        menu.setStyleSheet(open(get_stylesheet()).read())

        delete_action = QAction(QIcon(get_icon_link('delete_forever.svg')),
                                '&Delete key', self)
        delete_action.setStatusTip('Delete')

        new_action = QAction(QIcon(get_icon_link('create_new_folder.svg')),
                             '&Add key', self)
        new_action.setStatusTip('Add key')

        menu.addAction(new_action)
        if level == 1:
            menu.addAction(delete_action)

        action = menu.exec_(self.table_edit.viewport().mapToGlobal(position))

        if action == delete_action:
            if data_link is not None and data_link != []:
                self.remove_data(self.jsonData, data_link, 0, len(data_link))
                self.update()
        elif action == new_action:
            if self.is_list(self.jsonData, link, 0, len(link)):
                data_new = self.get_data(self.jsonData, data_link, 0,
                                         len(data_link))
                self.duplicate_data(self.jsonData, link, data_new.copy(), 0,
                                    len(link))
                self.update()
            elif self.is_list(self.jsonData, data_link, 0, len(data_link)):
                data_new = self.get_data(self.jsonData, data_link, 0,
                                         len(data_link))
                self.add_data(self.jsonData, data_link, data_new.copy(), 0,
                              len(data_link))
                self.update()
            else:
                input_name = QInputDialog()
                input_name.setStyleSheet(open(get_stylesheet()).read())
                text, ok = input_name.getText(self, 'New field', 'Field key:')
                if ok:
                    if data_link:
                        self.add_data(self.jsonData, data_link, text, 0,
                                      len(data_link))
                    else:
                        self.jsonData[text] = ""
                    self.update()

    def remove_data(self, obj, link, index, end):
        if index <= end - 1:
            i = link[index]
            if i.startswith(":"):
                i = int(i.replace(":", ""))
            if index == end - 1:
                del obj[i]
            else:
                self.remove_data(obj[i], link, index + 1, end)

    def is_list(self, obj, link, index, end):
        if index <= end - 1:
            i = link[index]
            if i.startswith(":"):
                i = int(i.replace(":", ""))
            if index == end - 1:
                return isinstance(obj[i], list)
            else:
                return self.is_list(obj[i], link, index + 1, end)
        else:
            return False

    def get_data(self, obj, link, index, end):
        if index <= end - 1:
            i = link[index]
            if i.startswith(":"):
                i = int(i.replace(":", ""))
            if index == end - 1:
                return obj[i]
            else:
                return self.get_data(obj[i], link, index + 1, end)
        else:
            return ""

    def add_data(self, obj, link, new, index, end):
        if index <= end - 1:
            i = link[index]
            if i.startswith(":"):
                i = int(i.replace(":", ""))
            if index == end - 1:
                if isinstance(obj[i], list):
                    if len(obj[i]) > 1:
                        obj[i].append(obj[i][0])
                    else:
                        obj[new] = ""
                elif isinstance(obj[i], dict) or str(obj[i]) == "":
                    obj[i] = {}
                    obj[i][new] = ""
                else:
                    obj[new] = ""
            else:
                self.add_data(obj[i], link, new, index + 1, end)

    def duplicate_data(self, obj, link, new, index, end):
        if index <= end - 1:
            i = link[index]
            if i.startswith(":"):
                i = int(i.replace(":", ""))
            if index == end - 1:
                if isinstance(obj[i], list):
                    obj[i].append(new)
            else:
                self.duplicate_data(obj[i], link, new, index + 1, end)

    def text(self):
        return self.json_edit.toPlainText()

    def set_format_data(self, data):
        self.format_data = data
예제 #14
0
class FileBrowserWidget(QWidget):
    on_open = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.model = QFileSystemModel()
        self.rootFolder = ''
        self.model.setRootPath(self.rootFolder)
        self.tree = QTreeView()
        self.tree.setModel(self.model)

        self.tree.setAnimated(False)
        self.tree.setIndentation(20)
        self.tree.setSortingEnabled(True)
        self.tree.sortByColumn(0, 0)
        self.tree.setColumnWidth(0, 200)
        self.tree.setDragEnabled(True)

        self.tree.setWindowTitle("Dir View")
        self.tree.resize(640, 480)
        self.tree.doubleClicked.connect(self.onDblClick)
        self.tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(
            self.onCustomContextMenuRequested)

        windowLayout = QVBoxLayout()
        windowLayout.addWidget(self.tree)
        windowLayout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(windowLayout)

    def onCustomContextMenuRequested(self, point):
        index = self.tree.indexAt(point)
        selectedFile = None
        selectedFolder = None
        ctx = QMenu("Context menu", self)
        if index.isValid():
            file = self.model.fileInfo(index)
            selectedFile = file.absoluteFilePath()
            selectedFolder = selectedFile if file.isDir(
            ) else file.absolutePath()
            if file.isDir():
                ctx.addAction(
                    "Open in file manager", lambda: QDesktopServices.openUrl(
                        QUrl.fromLocalFile(selectedFile)))
            if not file.isDir():
                for wndTyp, meta in WindowTypes.types:
                    text = 'Open with ' + meta.get('displayName', meta['name'])
                    print(wndTyp, meta)
                    ctx.addAction(
                        QAction(text,
                                self,
                                statusTip=text,
                                triggered=lambda dummy, meta=meta: navigate(
                                    "WINDOW", "Type=" + meta['name'],
                                    "FileName=" + selectedFile)))
                ctx.addSeparator()

        ctx.addAction("Set root folder ...",
                      lambda: self.selectRootFolder(preselect=selectedFolder))
        ctx.exec(self.tree.viewport().mapToGlobal(point))

    def selectRootFolder(self, preselect=None):
        if preselect == None: preselect = self.rootFolder
        dir = QFileDialog.getExistingDirectory(self, "Set root folder",
                                               preselect)
        if dir != None:
            self.setRoot(dir)

    def setRoot(self, dir):
        self.rootFolder = dir
        self.model.setRootPath(dir)
        self.tree.setRootIndex(self.model.index(dir))

    def onDblClick(self, index):
        if index.isValid():
            file = self.model.fileInfo(index)
            if not file.isDir():
                navigate("OPEN", "FileName=" + file.absoluteFilePath())

    def saveState(self):
        if self.tree.currentIndex().isValid():
            info = self.model.fileInfo(self.tree.currentIndex())
            return {"sel": info.absoluteFilePath(), "root": self.rootFolder}

    def restoreState(self, state):
        try:
            self.setRoot(state["root"])
        except:
            pass
        try:
            idx = self.model.index(state["sel"])
            if idx.isValid():
                self.tree.expand(idx)
                self.tree.setCurrentIndex(idx)
                self.tree.scrollTo(idx, QAbstractItemView.PositionAtCenter)
        except:
            pass
예제 #15
0
class Editor(QMainWindow):
    """This is the main class.
    """

    FORMATS = ("Aiken (*.txt);;Cloze (*.cloze);;GIFT (*.gift);;JSON (*.json)"
               ";;LaTex (*.tex);;Markdown (*.md);;PDF (*.pdf);;XML (*.xml)")

    SHORTCUTS = {
        "Create file": Qt.CTRL + Qt.Key_N,
        "Find questions": Qt.CTRL + Qt.Key_F,
        "Read file": Qt.CTRL + Qt.Key_O,
        "Read folder": Qt.CTRL + Qt.SHIFT + Qt.Key_O,
        "Save": Qt.CTRL + Qt.Key_S,
        "Save as": Qt.CTRL + Qt.SHIFT + Qt.Key_S,
        "Add hint": Qt.CTRL + Qt.SHIFT + Qt.Key_H,
        "Remove hint": Qt.CTRL + Qt.SHIFT + Qt.Key_Y,
        "Add answer": Qt.CTRL + Qt.SHIFT + Qt.Key_A,
        "Remove answer": Qt.CTRL + Qt.SHIFT + Qt.Key_Q,
        "Open datasets": Qt.CTRL + Qt.SHIFT + Qt.Key_D
    }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setWindowTitle("QAS Editor GUI")

        self._items: List[QWidget] = []
        self._main_editor = None
        self.path: str = None
        self.top_quiz = Category()
        self.cxt_menu = QMenu(self)
        self.cxt_item: QStandardItem = None
        self.cxt_data: _Question | Category= None
        self.cur_question: _Question = None
        self.tagbar: GTagBar = None
        self.main_editor: GTextEditor = None
        self.is_open_find = self.is_open_dataset = False

        with resources.open_text("qas_editor.gui", "stylesheet.css") as ifile:
            self.setStyleSheet(ifile.read())

        self._add_menu_bars()

        # Left side
        self.data_view = QTreeView()
        self.data_view.setIconSize(QSize(18, 18))
        xframe_vbox = self._block_datatree()
        left = QWidget()
        left.setLayout(xframe_vbox)

        # Right side
        self.cframe_vbox = QVBoxLayout()
        self._block_general_data()
        self._block_answer()
        self._block_hints()
        self._block_units()
        self._block_zones()
        self._block_solution()
        self._block_template()
        self.cframe_vbox.addStretch()
        self.cframe_vbox.setSpacing(5)
        for value in self._items:
            value.setEnabled(False)

        frame = QFrame()
        frame.setLineWidth(2)
        frame.setLayout(self.cframe_vbox)
        right = QScrollArea()
        right.setWidget(frame)
        right.setWidgetResizable(True)

        # Create main window divider for the splitter
        splitter = QSplitter()
        splitter.addWidget(left)
        splitter.addWidget(right)
        splitter.setStretchFactor(1, 1)
        splitter.setSizes([250, 100])
        self.setCentralWidget(splitter)

        # Create lower status bar.
        status = QStatusBar()
        self.setStatusBar(status)
        self.cat_name = QLabel()
        status.addWidget(self.cat_name)

        self._update_tree_item(self.top_quiz, self.root_item)
        self.data_view.expandAll()
        self.setGeometry(50, 50, 1200, 650)
        self.show()

    def _debug_me(self):
        self.path = "./test_lib/datasets/moodle/all.xml"
        self.top_quiz = Category.read_files(["./test_lib/datasets/moodle/all.xml"])
        gtags = {}
        self.top_quiz.get_tags(gtags)
        self.tagbar.set_gtags(gtags)
        self.root_item.clear()
        self._update_tree_item(self.top_quiz, self.root_item)
        self.data_view.expandAll()

    def _add_menu_bars(self):
        file_menu = self.menuBar().addMenu("&File")
        tmp = QAction("New file", self)
        tmp.setStatusTip("New file")
        tmp.triggered.connect(self._create_file)
        tmp.setShortcut(self.SHORTCUTS["Create file"])
        file_menu.addAction(tmp)
        tmp = QAction("Open file", self)
        tmp.setStatusTip("Open file")
        tmp.triggered.connect(self._read_file)
        tmp.setShortcut(self.SHORTCUTS["Read file"])
        file_menu.addAction(tmp)
        tmp = QAction("Open folder", self)
        tmp.setStatusTip("Open folder")
        tmp.triggered.connect(self._read_folder)
        tmp.setShortcut(self.SHORTCUTS["Read folder"])
        file_menu.addAction(tmp)
        tmp = QAction("Save", self)
        tmp.setStatusTip("Save top category to specified file on disk")
        tmp.triggered.connect(lambda: self._write_file(False))
        tmp.setShortcut(self.SHORTCUTS["Save"])
        file_menu.addAction(tmp)
        tmp = QAction("Save As...", self)
        tmp.setStatusTip("Save top category to specified file on disk")
        tmp.triggered.connect(lambda: self._write_file(True))
        tmp.setShortcut(self.SHORTCUTS["Save as"])
        file_menu.addAction(tmp)

        file_menu = self.menuBar().addMenu("&Edit")
        tmp = QAction("Shortcuts", self)
        #tmp.setShortcut(self.SHORTCUTS["Read file"])
        file_menu.addAction(tmp)
        tmp = QAction("Datasets", self)
        tmp.triggered.connect(self._open_dataset_popup)
        tmp.setShortcut(self.SHORTCUTS["Open datasets"])
        file_menu.addAction(tmp)
        tmp = QAction("Find Question", self)
        tmp.triggered.connect(self._open_find_popup)
        tmp.setShortcut(self.SHORTCUTS["Find questions"])
        file_menu.addAction(tmp)
        
        self.toolbar = GTextToolbar(self)
        self.addToolBar(Qt.TopToolBarArea, self.toolbar)

    def _add_new_category(self):
        popup = PopupName(self, True)
        popup.show()
        if not popup.exec():
            return
        self.cxt_data.add_subcat(popup.data)
        self._new_item(popup.data, self.cxt_item, "question")

    def _add_new_question(self):
        popup = PopupQuestion(self, self.cxt_data)
        popup.show()
        if not popup.exec():
            return
        self._new_item(popup.question, self.cxt_item, "question")

    @action_handler
    def _append_category(self):
        path, _ = QFileDialog.getOpenFileName(self, "Open file", "",
                                              self.FORMATS)
        if not path:
            return
        quiz = Category.read_files([path], path.rsplit("/", 1)[-1])
        self.cxt_data[quiz.name] = quiz
        self._update_tree_item(quiz, self.cxt_item)

    def _block_answer(self) -> None:
        frame = GCollapsible(self, "Answers")
        self.cframe_vbox.addLayout(frame)
        self._items.append(GOptions(self.toolbar, self.main_editor))
        _shortcut = QShortcut(self.SHORTCUTS["Add answer"], self)
        _shortcut.activated.connect(self._items[-1].add)
        _shortcut = QShortcut(self.SHORTCUTS["Remove answer"], self)
        _shortcut.activated.connect(self._items[-1].pop)
        frame.setLayout(self._items[-1])

    def _block_datatree(self) -> QVBoxLayout:
        self.data_view.setStyleSheet("margin: 5px 5px 0px 5px")
        self.data_view.setHeaderHidden(True)
        self.data_view.doubleClicked.connect(self._update_item)
        self.data_view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.data_view.customContextMenuRequested.connect(self._data_view_cxt)
        self.data_view.setDragEnabled(True)
        self.data_view.setAcceptDrops(True)
        self.data_view.setDropIndicatorShown(True)
        self.data_view.setDragDropMode(QAbstractItemView.InternalMove)
        self.data_view.original_dropEvent = self.data_view.dropEvent
        self.data_view.dropEvent = self._dataview_dropevent
        self.root_item = QStandardItemModel(0, 1)
        self.root_item.setHeaderData(0, Qt.Horizontal, "Classification")
        self.data_view.setModel(self.root_item)

        xframe_vbox = QVBoxLayout()
        xframe_vbox.addWidget(self.data_view)
        return xframe_vbox

    def _block_general_data(self) -> None:
        clayout = GCollapsible(self, "Question Header")
        self.cframe_vbox.addLayout(clayout, 1)

        grid = QVBoxLayout()    # No need of parent. It's inside GCollapsible
        grid.setSpacing(2)

        self.main_editor = GTextEditor(self.toolbar, "question")
        self._items.append(self.main_editor)
        self._items[-1].setToolTip("Question's description text")
        self._items[-1].setMinimumHeight(200)
        grid.addWidget(self._items[-1], 1)
        self.tagbar = GTagBar(self)
        self.tagbar.setToolTip("List of tags used by the question.")
        self._items.append(self.tagbar)
        grid.addWidget(self._items[-1], 0)

        others = QHBoxLayout()  # No need of parent. It's inside GCollapsible
        grid.addLayout(others, 0)

        group_box = QGroupBox("General", self)
        _content = QVBoxLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GField("dbid", self, int))
        self._items[-1].setToolTip("Optional ID for the question.")
        self._items[-1].setFixedWidth(50)
        _content.addWidget(self._items[-1], 0)
        self._items.append(GField("default_grade", self, int))
        self._items[-1].setToolTip("Default grade.")
        self._items[-1].setFixedWidth(50)
        self._items[-1].setText("1.0")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GField("penalty", self, str))
        self._items[-1].setToolTip("Penalty")
        self._items[-1].setFixedWidth(50)
        self._items[-1].setText("0.0")
        _content.addWidget(self._items[-1], 0)
        _content.addStretch()

        others.addWidget(group_box, 0)

        group_box = QGroupBox("Unit Handling", self)
        _content = QVBoxLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GDropbox("grading_type", self, Grading))
        self._items[-1].setToolTip("Grading")
        self._items[-1].setMinimumWidth(80)
        _content.addWidget(self._items[-1], 0)
        self._items.append(GDropbox("show_units", self, ShowUnits))
        self._items[-1].setToolTip("Show units")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GField("unit_penalty", self, float))
        self._items[-1].setToolTip("Unit Penalty")
        self._items[-1].setText("0.0")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GCheckBox("left", "Left side", self))
        _content.addWidget(self._items[-1], 0)
        others.addWidget(group_box, 1)

        group_box = QGroupBox("Multichoices", self)
        _content = QVBoxLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GDropbox("numbering", self, Numbering))
        self._items[-1].setToolTip("How options will be enumerated")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GCheckBox("show_instr", "Instructions", self))
        self._items[-1].setToolTip("If the structions 'select one (or more "
                                   " options)' should be shown")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GCheckBox("single", "Multi answer", self))
        self._items[-1].setToolTip("If there is just a single or multiple "
                                   "valid answers")
        _content.addWidget(self._items[-1], 0)
        self._items.append(GCheckBox("shuffle", "Shuffle", self))
        self._items[-1].setToolTip("If answers should be shuffled (e.g. order "
                                   "of options will change each time)")
        _content.addWidget(self._items[-1], 0)
        others.addWidget(group_box, 1)

        group_box = QGroupBox("Documents", self)
        _content = QGridLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GDropbox("rsp_format", self, ResponseFormat))
        self._items[-1].setToolTip("The format to be used in the reponse.")
        _content.addWidget(self._items[-1], 0, 0, 1, 2)
        self._items.append(GCheckBox("rsp_required",
                                     "Required", self))
        self._items[-1].setToolTip("Require the student to enter some text.")
        _content.addWidget(self._items[-1], 0, 2)
        self._items.append(GField("min_words", self, int))
        self._items[-1].setToolTip("Minimum word limit")
        self._items[-1].setText("0")
        _content.addWidget(self._items[-1], 1, 0)
        self._items.append(GField("max_words", self, int))
        self._items[-1].setToolTip("Maximum word limit")
        self._items[-1].setText("10000")
        _content.addWidget(self._items[-1], 2, 0)
        self._items.append(GField("attachments", self, int))
        self._items[-1].setToolTip("Number of attachments allowed. 0 is none."
                                   " -1 is unlimited. Should be bigger than "
                                   "field below.")
        self._items[-1].setText("-1")
        _content.addWidget(self._items[-1], 1, 1)
        self._items.append(GField("atts_required", self, int))
        self._items[-1].setToolTip("Number of attachments required. 0 is none."
                                   " -1 is unlimited. Should be smaller than "
                                   "field above.")
        self._items[-1].setText("0")
        _content.addWidget(self._items[-1], 2, 1)
        self._items.append(GField("lines", self, int))
        self._items[-1].setToolTip("Input box size.")
        self._items[-1].setText("15")
        _content.addWidget(self._items[-1], 1, 2)
        self._items.append(GField("max_bytes", self, int))
        self._items[-1].setToolTip("Maximum file size.")
        self._items[-1].setText("1Mb")
        _content.addWidget(self._items[-1], 2, 2)
        self._items.append(GField("file_types", self, str))
        self._items[-1].setToolTip("Accepted file types (comma separeted).")
        self._items[-1].setText(".txt, .pdf")
        _content.addWidget(self._items[-1], 3, 0, 1, 3)
        others.addWidget(group_box, 1)

        _wrapper = QVBoxLayout()  # No need of parent. It's inside GCollapsible
        group_box = QGroupBox("Random", self)
        _wrapper.addWidget(group_box)
        _content = QVBoxLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GCheckBox("subcats", "Subcats", self))
        self._items[-1].setToolTip("If questions wshould be choosen from "
                                   "subcategories too.")
        _content.addWidget(self._items[-1])
        self._items.append(GField("choose", self, int))
        self._items[-1].setToolTip("Number of questions to select.")
        self._items[-1].setText("5")
        self._items[-1].setFixedWidth(85)
        _content.addWidget(self._items[-1])

        group_box = QGroupBox("Fill-in", self)
        _wrapper.addWidget(group_box)
        _content = QVBoxLayout(group_box)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GCheckBox("use_case", "Match case", self))
        self._items[-1].setToolTip("If text is case sensitive.")
        _content.addWidget(self._items[-1])

        others.addLayout(_wrapper, 0)

        group_box = QGroupBox("Datasets", self)
        _content = QGridLayout(group_box)
        _content.setSpacing(5)
        _content.setContentsMargins(5, 3, 5, 3)
        self._items.append(GList("datasets", self))
        self._items[-1].setFixedHeight(70)
        self._items[-1].setToolTip("List of datasets used by this question.")
        _content.addWidget(self._items[-1], 0, 0, 1, 2)
        self._items.append(GDropbox("synchronize", self, Synchronise))
        self._items[-1].setToolTip("How should the databases be synchronized.")
        self._items[-1].setMinimumWidth(70)
        _content.addWidget(self._items[-1], 1, 0)
        _gen = QPushButton("Gen", self)
        _gen.setToolTip("Generate new items based on the max, min and decimal "
                        "values of the datasets, and the current solution.")
        _gen.clicked.connect(self._gen_items)
        _content.addWidget(_gen, 1, 1)
        others.addWidget(group_box, 2)

        others.addStretch()
        clayout.setLayout(grid)
        clayout._toggle()

    def _block_hints(self) -> None:
        clayout = GCollapsible(self, "Hints")
        self.cframe_vbox.addLayout(clayout)
        self._items.append(GHintsList(None, self.toolbar))
        _shortcut = QShortcut(self.SHORTCUTS["Add hint"], self)
        _shortcut.activated.connect(self._items[-1].add)
        _shortcut = QShortcut(self.SHORTCUTS["Remove hint"], self)
        _shortcut.activated.connect(self._items[-1].pop)
        clayout.setLayout(self._items[-1])

    def _block_solution(self) -> None:
        collapsible = GCollapsible(self, "Solution and Feedback")
        self.cframe_vbox.addLayout(collapsible)
        layout = QVBoxLayout()
        collapsible.setLayout(layout)
        self._items.append(GTextEditor(self.toolbar, "feedback"))
        self._items[-1].setMinimumHeight(100)
        self._items[-1].setToolTip("General feedback for the question. May "
                                   "also be used to describe solutions.")
        layout.addWidget(self._items[-1])
        sframe = QFrame(self)
        sframe.setStyleSheet(".QFrame{border:1px solid rgb(41, 41, 41);"
                             "background-color: #e4ebb7}")
        layout.addWidget(sframe)
        _content = QGridLayout(sframe)
        self._items.append(GTextEditor(self.toolbar, "if_correct"))
        self._items[-1].setToolTip("Feedback for correct answer")
        _content.addWidget(self._items[-1], 0, 0)
        self._items.append(GTextEditor(self.toolbar, "if_incomplete"))
        self._items[-1].setToolTip("Feedback for incomplete answer")
        _content.addWidget(self._items[-1], 0, 1)
        self._items.append(GTextEditor(self.toolbar, "if_incorrect"))
        self._items[-1].setToolTip("Feedback for incorrect answer")
        _content.addWidget(self._items[-1], 0, 2)
        self._items.append(GCheckBox("show_num", "Show the number of correct "
                                     "responses once the question has finished"
                                     , self))
        _content.addWidget(self._items[-1], 2, 0, 1, 3)
        _content.setColumnStretch(3, 1)

    def _block_template(self) -> None:
        collapsible = GCollapsible(self, "Templates")
        self.cframe_vbox.addLayout(collapsible)
        layout = QVBoxLayout()
        collapsible.setLayout(layout)
        self._items.append(GTextEditor(self.toolbar, "template"))
        self._items[-1].setMinimumHeight(70)
        self._items[-1].setToolTip("Text displayed in the response input box "
                                    "when a new attempet is started.")
        layout.addWidget(self._items[-1])
        self._items.append(GTextEditor(self.toolbar, "grader_info"))
        self._items[-1].setMinimumHeight(50)
        self._items[-1].setToolTip("Information for graders.")
        layout.addWidget(self._items[-1])

    def _block_units(self):
        collapsible = GCollapsible(self, "Units")
        self.cframe_vbox.addLayout(collapsible)

    def _block_zones(self):
        collapsible = GCollapsible(self, "Background and Zones")
        self.cframe_vbox.addLayout(collapsible)

    @action_handler
    def _clone_shallow(self) -> None:
        new_data = copy.copy(self.cxt_data)
        self._new_item(new_data, self.cxt_item.parent(), "question")

    @action_handler
    def _clone_deep(self) -> None:
        new_data = copy.deepcopy(self.cxt_data)
        self._new_item(new_data, self.cxt_itemparent(), "question")

    @action_handler
    def _create_file(self, *_):
        self.top_quiz = Category()
        self.path = None
        self.root_item.clear()
        self._update_tree_item(self.top_quiz, self.root_item)

    @action_handler
    def _dataview_dropevent(self, event: QDropEvent):
        from_obj = self.data_view.selectedIndexes()[0].data(257)
        to_obj = self.data_view.indexAt(event.pos()).data(257)
        if isinstance(to_obj, Category):
            if isinstance(from_obj, _Question):
                to_obj.add_subcat(from_obj)
            else:
                to_obj.add_question(from_obj)
        else:
            event.ignore()
        self.data_view.original_dropEvent(event)

    def _data_view_cxt(self, event):
        model_idx = self.data_view.indexAt(event)
        self.cxt_item = self.root_item.itemFromIndex(model_idx)
        self.cxt_data = model_idx.data(257)
        self.cxt_menu.clear()
        rename = QAction("Rename", self)
        rename.triggered.connect(self._rename_category)
        self.cxt_menu.addAction(rename)
        if self.cxt_item != self.root_item.item(0):
            tmp = QAction("Delete", self)
            tmp.triggered.connect(self._delete_item)
            self.cxt_menu.addAction(tmp)
            tmp = QAction("Clone (Shallow)", self)
            tmp.triggered.connect(self._clone_shallow)
            self.cxt_menu.addAction(tmp)
            tmp = QAction("Clone (Deep)", self)
            tmp.triggered.connect(self._clone_deep)
            self.cxt_menu.addAction(tmp)
        if isinstance(self.cxt_data, Category):
            tmp = QAction("Save as", self)
            tmp.triggered.connect(lambda: self._write_quiz(self.cxt_data, True))
            self.cxt_menu.addAction(tmp)
            tmp = QAction("Append", self)
            tmp.triggered.connect(self._append_category)
            self.cxt_menu.addAction(tmp)
            tmp = QAction("Sort", self)
            #tmp.triggered.connect(self._add_new_category)
            self.cxt_menu.addAction(tmp)
            tmp = QAction("New Question", self)
            tmp.triggered.connect(self._add_new_question)
            self.cxt_menu.addAction(tmp)
            tmp = QAction("New Category", self)
            tmp.triggered.connect(self._add_new_category)
            self.cxt_menu.addAction(tmp)
        self.cxt_menu.popup(self.data_view.mapToGlobal(event))

    @action_handler
    def _delete_item(self, *_):
        self.cxt_item.parent().removeRow(self.cxt_item.index().row())
        cat = self.cxt_data.parent
        if isinstance(self.cxt_data, _Question):
            cat.pop_question(self.cxt_data)
        elif isinstance(self.cxt_data, Category):
            cat.pop_subcat(self.cxt_data)

    @action_handler
    def _gen_items(self, _):
        pass

    def _new_item(self, data: Category, parent: QStandardItem, title: str):

        name = f"{data.__class__.__name__}_icon.png".lower()
        item = None
        with resources.path("qas_editor.images", name) as path:
            item = QStandardItem(QIcon(path.as_posix()), data.name)
            item.setEditable(False)
            item.setData(QVariant(data))
            parent.appendRow(item)
        return item

    @action_handler
    def _open_dataset_popup(self, _):
        if not self.is_open_dataset:
            popup = PopupDataset(self, self.top_quiz)
            popup.show()
            self.is_open_dataset = True

    @action_handler
    def _open_find_popup(self, _):
        if not self.is_open_find:
            popup = PopupFind(self, self.top_quiz, self.tagbar.cat_tags)
            popup.show()
            self.is_open_find = True

    @action_handler
    def _read_file(self, _):
        files, _ = QFileDialog.getOpenFileNames(self, "Open file", "",
                                                self.FORMATS)
        if not files:
            return
        if len(files) == 1:
            self.path = files[0]
        self.top_quiz = Category.read_files(files)
        gtags = {}
        self.top_quiz.get_tags(gtags)
        self.tagbar.set_gtags(gtags)
        self.root_item.clear()
        self._update_tree_item(self.top_quiz, self.root_item)
        self.data_view.expandAll()

    @action_handler
    def _read_folder(self, _):
        dialog = QFileDialog(self)
        dialog.setFileMode(QFileDialog.FileMode.Directory)
        if not dialog.exec():
            return
        self.top_quiz = Category()
        self.path = None
        for folder in dialog.selectedFiles():
            cat = folder.rsplit("/", 1)[-1]
            quiz = Category.read_files(glob.glob(f"{folder}/*"), cat)
            self.top_quiz.add_subcat(quiz)
        gtags = {}
        self.top_quiz.get_tags(gtags)
        self.tagbar.set_gtags(gtags)
        self.root_item.clear()
        self._update_tree_item(self.top_quiz, self.root_item)
        self.data_view.expandAll()

    @action_handler
    def _rename_category(self, *_):
        popup = PopupName(self, False)
        popup.show()
        if not popup.exec():
            return
        self.cxt_data.name = popup.data
        self.cxt_item.setText(popup.data)

    @action_handler
    def _update_item(self, model_index: QModelIndex) -> None:
        item = model_index.data(257)
        if isinstance(item, _Question):
            for key in self._items:
                attr = key.get_attr()
                if attr in item.__dict__:
                    key.setEnabled(True)
                    key.from_obj(item)
                else:
                    key.setEnabled(False)
            self.cur_question = item
        path = [f" ({item.__class__.__name__})"]
        while item.parent:
            path.append(item.name)
            item = item.parent
        path.append(item.name)
        path.reverse()
        self.cat_name.setText(" > ".join(path[:-1]) + path[-1])

    def _update_tree_item(self, data: Category, parent: QStandardItem) -> None:
        item = self._new_item(data, parent, "category")
        for k in data.questions:
            self._new_item(k, item, "question")
        for k in data:
            self._update_tree_item(data[k], item)

    @action_handler
    def _write_quiz(self, quiz: Category, save_as: bool):
        if save_as or self.path is None:
            path, _ = QFileDialog.getSaveFileName(self, "Save file", "",
                                                  self.FORMATS)
            if not path:
                return None
        else:
            path = self.path
        ext = path.rsplit('.', 1)[-1]
        getattr(quiz, quiz.SERIALIZERS[ext][1])(path)
        return path

    def _write_file(self, save_as: bool) -> None:
        path = self._write_quiz(self.top_quiz, save_as)
        if path:
            self.path = path
예제 #16
0
class TreeView(QWidget):
    def __init__(self, table, parent):
        QWidget.__init__(self, parent)
        self.window = parent
        self.tree = QTreeView(self)
        indent = self.tree.indentation()
        self.tree.setIndentation(indent / 2)

        self.model = DataModel(table)
        self.sorter = sorter = FilterModel(self)
        sorter.setSourceModel(self.model)
        self.tree.setModel(sorter)
        for col in range(3, 9):
            self.tree.setItemDelegateForColumn(col, PercentDelegate(self))
        self.tree.header().setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tree.header().customContextMenuRequested.connect(
            self._on_header_menu)
        self.tree.setSortingEnabled(True)
        self.tree.setAutoExpandDelay(0)
        self.tree.resizeColumnToContents(0)
        self.tree.resizeColumnToContents(NAME_COLUMN)
        self.tree.expand(self.sorter.index(0, 0))
        #self.tree.expandAll()

        self.tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self._on_tree_menu)

        searchbox = QHBoxLayout()
        self.search = QLineEdit(self)

        searchbox.addWidget(self.search)

        self.search_type = QComboBox(self)
        self.search_type.addItem("Contains", SEARCH_CONTAINS)
        self.search_type.addItem("Exact", SEARCH_EXACT)
        self.search_type.addItem("Reg.Exp", SEARCH_REGEXP)
        searchbox.addWidget(self.search_type)

        btn = QPushButton("&Search", self)
        searchbox.addWidget(btn)
        btn.clicked.connect(self._on_search)

        btn = QPushButton("&Next", self)
        searchbox.addWidget(btn)
        btn.clicked.connect(self._on_search_next)

        filterbox = QHBoxLayout()

        label = QLabel("Time Individual", self)
        filterbox.addWidget(label)
        self.individual_time = QSpinBox(self)
        self.individual_time.setMinimum(0)
        self.individual_time.setMaximum(100)
        self.individual_time.setSuffix(" %")
        filterbox.addWidget(self.individual_time)

        label = QLabel("Alloc Individual", self)
        filterbox.addWidget(label)
        self.individual_alloc = QSpinBox(self)
        self.individual_alloc.setMinimum(0)
        self.individual_alloc.setMaximum(100)
        self.individual_alloc.setSuffix(" %")
        filterbox.addWidget(self.individual_alloc)

        label = QLabel("Time Inherited", self)
        filterbox.addWidget(label)
        self.inherited_time = QSpinBox(self)
        self.inherited_time.setMinimum(0)
        self.inherited_time.setMaximum(100)
        self.inherited_time.setSuffix(" %")
        filterbox.addWidget(self.inherited_time)

        label = QLabel("Alloc Inherited", self)
        filterbox.addWidget(label)
        self.inherited_alloc = QSpinBox(self)
        self.inherited_alloc.setMinimum(0)
        self.inherited_alloc.setMaximum(100)
        self.inherited_alloc.setSuffix(" %")
        filterbox.addWidget(self.inherited_alloc)

        btn = QPushButton("&Filter", self)
        btn.clicked.connect(self._on_filter)
        filterbox.addWidget(btn)
        btn = QPushButton("&Reset", self)
        filterbox.addWidget(btn)
        btn.clicked.connect(self._on_reset_filter)

        vbox = QVBoxLayout()
        vbox.addLayout(searchbox)
        vbox.addLayout(filterbox)
        vbox.addWidget(self.tree)
        self.setLayout(vbox)

        self._search_idxs = None
        self._search_idx_no = 0

    def _expand_to(self, idx):
        idxs = [idx]
        parent = idx
        while parent and parent.isValid():
            parent = self.sorter.parent(parent)
            idxs.append(parent)
        #print(idxs)
        for idx in reversed(idxs[:-1]):
            data = self.sorter.data(idx, QtCore.Qt.DisplayRole)
            #print(data)
            self.tree.expand(idx)

    def _on_search(self):
        text = self.search.text()
        selected = self.tree.selectedIndexes()
        #         if selected:
        #             start = selected[0]
        #         else:
        start = self.sorter.index(0, NAME_COLUMN)
        search_type = self.search_type.currentData()
        if search_type == SEARCH_EXACT:
            method = QtCore.Qt.MatchFixedString
        elif search_type == SEARCH_CONTAINS:
            method = QtCore.Qt.MatchContains
        else:
            method = QtCore.Qt.MatchRegExp

        self._search_idxs = idxs = self.sorter.search(start, text, search_type)
        if idxs:
            self.window.statusBar().showMessage(
                "Found: {} occurence(s)".format(len(idxs)))
            self._search_idx_no = 0
            idx = idxs[0]
            self._locate(idx)
        else:
            self.window.statusBar().showMessage("Not found")

    def _locate(self, idx):
        self.tree.resizeColumnToContents(0)
        self.tree.resizeColumnToContents(NAME_COLUMN)
        self._expand_to(idx)
        self.tree.setCurrentIndex(idx)
        #self.tree.selectionModel().select(idx, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Current | QItemSelectionModel.Rows)
        #self.tree.scrollTo(idx, QAbstractItemView.PositionAtCenter)

    def _on_search_next(self):
        if self._search_idxs:
            n = len(self._search_idxs)
            self._search_idx_no = (self._search_idx_no + 1) % n
            idx = self._search_idxs[self._search_idx_no]
            self.window.statusBar().showMessage("Occurence {} of {}".format(
                self._search_idx_no, n))
            self._locate(idx)
        else:
            self.window.statusBar().showMessage("No search results")

    def _on_filter(self):
        self.sorter.setFilter(self.search.text(), self.individual_time.value(),
                              self.individual_alloc.value(),
                              self.inherited_time.value(),
                              self.inherited_alloc.value())

    def _on_reset_filter(self):
        self.sorter.reset()

    def _on_header_menu(self, pos):
        menu = make_header_menu(self.tree)
        menu.exec_(self.mapToGlobal(pos))

    def _on_tree_menu(self, pos):
        index = self.tree.indexAt(pos)
        #print("index: {}".format(index))
        if index.isValid():
            record = self.sorter.data(index, QtCore.Qt.UserRole + 1)
            #print("okay?..")
            #print("context: {}".format(record))
            menu = self.window.make_item_menu(self.model, record)
            menu.exec_(self.tree.viewport().mapToGlobal(pos))
예제 #17
0
class VGExplorer(QWidget):
    def __init__(self, app, config):
        super().__init__()

        self.clipboard = app.clipboard()

        self.config = config
        self.setWindowTitle(config.server_name)

        rootPath = self.get_cwd()

        self.model = QFileSystemModel()
        index = self.model.setRootPath(rootPath)

        self.tree = QTreeView()
        self.tree.setModel(self.model)

        self.tree.setRootIndex(index)

        self.tree.setAnimated(False)
        self.tree.setIndentation(20)

        self.tree.hideColumn(1)
        self.tree.hideColumn(2)
        self.tree.hideColumn(3)
        self.tree.setHeaderHidden(True)

        self.tree.doubleClicked.connect(self.on_double_click)
        self.tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.show_menu)

        windowLayout = QVBoxLayout()
        windowLayout.addWidget(self.tree)
        self.setLayout(windowLayout)

        # Shortcut for hide
        self.shortcut = QShortcut(QKeySequence(config.toggle_key), self)
        self.shortcut.activated.connect(self.hide)

        if not config.hidden:
            self.show()

    def toggle_show(self):
        if self.isHidden():
            self.show()
        else:
            self.hide()

    def open_file(self, index):
        path = self.sender().model().filePath(index)
        if os.path.isfile(path):
            subprocess.call([
                self.config.vim, "--servername", self.config.server_name,
                "--remote", path
            ])

    def get_cwd(self):
        path = subprocess.check_output([
            self.config.vim, "--servername", self.config.server_name,
            "--remote-expr", "getcwd()"
        ])
        return path.decode("utf-8").strip()

    def on_double_click(self, index):
        self.open_file(index)

    def show_menu(self, clickPos):
        index = self.tree.indexAt(clickPos)
        selected_path = self.tree.model().filePath(index)
        enclosing_dir = self.find_enclosing_dir(selected_path)

        menu = QMenu(self)
        openAction = menu.addAction("Open")
        newFolderAction = menu.addAction("New Folder")
        newFileAction = menu.addAction("New File")
        copyAction = menu.addAction("Copy")
        pasteAction = menu.addAction("Paste")
        renameAction = menu.addAction("Rename")
        fileInfo = menu.addAction("Properties")

        menuPos = QPoint(clickPos.x() + 15, clickPos.y() + 15)
        action = menu.exec_(self.mapToGlobal(menuPos))

        if action == openAction:
            self.open_file(index)

        elif action == newFolderAction:
            path = self.get_dialog_str("New Folder",
                                       "Enter name for new folder:")
            if path:
                self.mkdir(os.path.join(enclosing_dir, path))

        elif action == newFileAction:
            path = self.get_dialog_str("New File", "Enter name for new file:")
            if path:
                self.touch(os.path.join(enclosing_dir, path))

        elif action == renameAction:
            path = self.get_dialog_str("Rename File", "Enter new name:")

            # Naive validation
            if "/" in path:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Critical)
                msg.setText("Filename cannot contain '/'")
                msg.setWindowTitle("Error")
                msg.exec_()
                return

            new_path = os.path.join(enclosing_dir, path)

            self.move(selected_path, new_path)

        elif action == copyAction:
            mime_data = QMimeData()

            # TODO: support multiple selections
            mime_data.setUrls([QUrl(Path(selected_path).as_uri())])
            self.clipboard.setMimeData(mime_data)

        elif action == pasteAction:
            mime_data = self.clipboard.mimeData()
            if not mime_data:
                return

            if mime_data.hasUrls():
                for src_url in mime_data.urls():
                    self.copy(src_url.path(), enclosing_dir)

    def get_dialog_str(self, title, message):
        text, confirm = QInputDialog.getText(self, title, message,
                                             QLineEdit.Normal, "")
        if confirm and text != '':
            return text
        return None

    '''
    Filesystem and OS Functions
    '''

    def copy(self, src_file, dest_dir):
        src_basename = os.path.basename(src_file)
        dest_file = os.path.join(dest_dir, src_basename)

        # First confirm file doesn't already exist
        if os.path.exists(dest_file):
            print(f"Destination path '{dest_file}' already exists, skipping")
            return

        print(f"Pasting {src_file} -> {dest_file}")
        shutil.copy2(src_file, dest_file)

    def move(self, old_path, new_path):
        os.rename(old_path, new_path)

    def mkdir(self, path):
        if not os.path.exists(path):
            os.mkdir(path)

    def touch(self, path):
        subprocess.run(["touch", path])

    def find_enclosing_dir(self, path):
        '''
        If path is file, return dir it is in
        If path is dir, return itself
        '''
        if os.path.isdir(path):
            return path

        if os.path.isfile(path):
            return str(Path(path).parent)
예제 #18
0
class OpenedFileExplorer(DockWidget):
    """Opened File Explorer is list widget with list of opened files.
    It implements switching current file, files sorting. Uses _OpenedFileModel internally.
    Class instance created by Workspace.
    """

    def __init__(self, workspace):
        DockWidget.__init__(self, workspace, "&Opened Files", QIcon(":/enkiicons/filtered.png"), "Alt+O")

        self._workspace = workspace

        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.tvFiles = QTreeView(self)
        self.tvFiles.setHeaderHidden(True)
        self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked)
        self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tvFiles.setDragEnabled(True)
        self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove)
        self.tvFiles.setRootIsDecorated(False)
        self.tvFiles.setTextElideMode(Qt.ElideMiddle)
        self.tvFiles.setUniformRowHeights(True)

        self.tvFiles.customContextMenuRequested.connect(self._onTvFilesCustomContextMenuRequested)

        self.setWidget(self.tvFiles)
        self.setFocusProxy(self.tvFiles)

        self.model = _OpenedFileModel(self)  # Not protected, because used by Configurator
        self.tvFiles.setModel(self.model)
        self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.tvFiles.setAttribute(Qt.WA_MacSmallSize)

        self._workspace.currentDocumentChanged.connect(self._onCurrentDocumentChanged)

        # disconnected by startModifyModel()
        self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged)

        self.tvFiles.activated.connect(self._workspace.focusCurrentDocument)

        core.actionManager().addAction("mView/aOpenedFiles", self.showAction())

        # Add auto-hide capability.
        self._waitForCtrlRelease = False
        core.actionManager().action("mNavigation/aNext").triggered.connect(
          self._setWaitForCtrlRelease)
        core.actionManager().action("mNavigation/aPrevious").triggered.connect(
          self._setWaitForCtrlRelease)
        QApplication.instance().installEventFilter(self)

    def terminate(self):
        """Explicitly called destructor
        """
        core.actionManager().removeAction("mView/aOpenedFiles")
        QApplication.instance().removeEventFilter(self)

    def startModifyModel(self):
        """Blocks signals from model while it is modified by code
        """
        self.tvFiles.selectionModel().selectionChanged.disconnect(self._onSelectionModelSelectionChanged)

    def finishModifyModel(self):
        """Unblocks signals from model
        """
        self.tvFiles.selectionModel().selectionChanged.connect(self._onSelectionModelSelectionChanged)

    @pyqtSlot(Document, Document)
    def _onCurrentDocumentChanged(self, oldDocument, currentDocument):  # pylint: disable=W0613
        """ Current document has been changed on workspace
        """
        if currentDocument is not None:
            index = self.model.documentIndex(currentDocument)

            self.startModifyModel()
            self.tvFiles.setCurrentIndex(index)
            # scroll the view
            self.tvFiles.scrollTo(index)
            self.finishModifyModel()

    @pyqtSlot(QItemSelection, QItemSelection)
    def _onSelectionModelSelectionChanged(self, selected, deselected):  # pylint: disable=W0613
        """ Item selected in the list. Switch current document
        """
        if not selected.indexes():  # empty list, last file closed
            return

        index = selected.indexes()[0]
        # backup/restore current focused widget as setting active mdi window will steal it
        focusWidget = self.window().focusWidget()

        # set current document
        document = self._workspace.sortedDocuments[index.row()]
        self._workspace.setCurrentDocument(document)

        # restore focus widget
        if focusWidget:
            focusWidget.setFocus()

    @pyqtSlot(QPoint)
    def _onTvFilesCustomContextMenuRequested(self, pos):
        """Connected automatically by uic
        """
        menu = QMenu()

        menu.addAction(core.actionManager().action("mFile/mClose/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mSave/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mReload/aCurrent"))
        menu.addSeparator()
        menu.addAction(core.actionManager().action("mFile/mFileSystem/aRename"))
        toggleExecutableAction = core.actionManager().action("mFile/mFileSystem/aToggleExecutable")
        if toggleExecutableAction:  # not available on Windows
            menu.addAction(toggleExecutableAction)
        core.actionManager().action("mFile/mFileSystem").menu().aboutToShow.emit()  # to update aToggleExecutable

        menu.exec_(self.tvFiles.mapToGlobal(pos))

    def _setWaitForCtrlRelease(self):
        # We can't see actual Ctrl+PgUp/PgDn keypresses, since these get eaten
        # by the QAction and don't even show up in the event filter below. We
        # want to avoid waiting for a Ctrl release if the menu item brought us
        # here. As a workaround, check that Ctrl is pressed. If so, it's
        # unlikely to be the menu item.
        if QApplication.instance().keyboardModifiers() & Qt.ControlModifier:
            self._waitForCtrlRelease = True
            self.show()
        else:
            # If this was a menu selection, then update the MRU list. We can't
            # do this now, since the current document hasn't been changed yet.
            QTimer.singleShot(0, self.model.sortDocuments)

    def eventFilter(self, obj, event):
        """An event filter that looks for ctrl key releases and focus out
           events."""
        # Wait for the user to release the Ctrl key.
        if ( self._waitForCtrlRelease and event.type() == QEvent.KeyRelease and
          event.key() == Qt.Key_Control and
          event.modifiers() == Qt.NoModifier):
            self.model.sortDocuments()
            self._waitForCtrlRelease = False
            if not self.isPinned():
                self.hide()
        # Look for a focus out event sent by the containing widget's focus
        # proxy.
        if event.type() == QEvent.FocusOut and obj == self.focusProxy():
            self.model.sortDocuments()
        return QObject.eventFilter(self, obj, event)
예제 #19
0
class PYCFScape(QMainWindow):
    def __init__(self):
        super().__init__()

        # We determine where the script is placed, for acessing files we use (such as the icon)
        self.we_live_in = sys.argv[0]
        self.we_live_in = os.path.split(self.we_live_in)[0]

        self.build_interface()

        self.opened_file = None  # stores the filepath
        self.opened_vpk = None  # stores the opened vpk
        self.internal_directory_understanding = {
        }  # a dictionary version of the paths
        self.vpk_loaded = False  # whether or not we have a vpk file open
        self.export_paths = [
        ]  # Paths we want to export when file->export is selected

    def build_interface(self):
        self.setWindowTitle("PYCFScape")
        self.setWindowIcon(
            QIcon(os.path.join(self.we_live_in, 'res/Icon64.ico')))

        self.main_content = QWidget()
        self.main_content_layout = QVBoxLayout()
        self.main_content.setLayout(self.main_content_layout)

        # set up the interface parts
        self.output_console = QTextEdit()  # Will basically just copy stdout
        self.output_console.setReadOnly(True)
        sys.stdout = bob_logger(self.output_console, False, sys.stdout)
        sys.stderr = bob_logger(self.output_console, True, sys.stderr)

        self.vpk_tree = QTreeView()  # Displays the tree of the vpk's content
        self.vpk_tree_model = QStandardItemModel(self.vpk_tree)
        self.vpk_tree.setModel(self.vpk_tree_model)
        self.vpk_tree._mousePressEvent = self.vpk_tree.mousePressEvent  # store it so we can call it
        self.vpk_tree.mousePressEvent = self.vpk_tree_item_clicked

        self.vpk_tree.setHeaderHidden(True)

        # We use a QTreeView to also display headers.
        # We however, still treat it as a list view.
        self.dir_list = QTreeView(
        )  # Displays the list of the current vpk's directory's content
        self.dir_list_model = QStandardItemModel(self.dir_list)
        self.dir_list.setModel(self.dir_list_model)
        self.dir_list.doubleClicked.connect(self.dir_list_item_double_clicked)
        self.dir_list_model.itemChanged.connect(self.dir_list_item_updated)
        self.dir_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.dir_list.customContextMenuRequested.connect(
            self.dir_list_context_menu)

        self.dir_list_model.setColumnCount(2)
        self.dir_list_model.setHorizontalHeaderLabels(["Name", "Type"])
        self.dir_list.header().resizeSection(0, 250)

        # The tool bar - WARNING: MESSY CODE!
        self.actions_toolbar = self.addToolBar("")
        # OPEN BUTTON
        self.open_button = self.actions_toolbar.addAction(
            QIcon.fromTheme("document-open"), "Open File")
        self.open_button.triggered.connect(self.open_file)

        self.actions_toolbar.addSeparator()

        self.back_button = QToolButton()  # GO BACK BUTTON
        self.back_button.setIcon(QIcon.fromTheme("go-previous"))
        self.back_button.setDisabled(True)
        self.actions_toolbar.addWidget(self.back_button)

        self.forward_button = QToolButton()  # GO FORWARD BUTTON
        self.forward_button.setIcon(QIcon.fromTheme("go-next"))
        self.forward_button.setDisabled(True)
        self.actions_toolbar.addWidget(self.forward_button)

        self.up_button = QToolButton()  # GO UP BUTTON
        self.up_button.setIcon(QIcon.fromTheme("go-up"))
        self.up_button.setDisabled(True)
        self.actions_toolbar.addWidget(self.up_button)

        self.actions_toolbar.addSeparator()

        # FIND BUTTON
        self.search_button = self.actions_toolbar.addAction(
            QIcon.fromTheme("system-search"), "Find in file")
        self.search_button.setDisabled(True)
        self.search_button.triggered.connect(self.search_for_file)

        self.actions_toolbar.addSeparator()

        # EXPORT BUTTON
        self.export_button = self.actions_toolbar.addAction(
            QIcon.fromTheme("extract-archive"), "Export Selection")
        self.export_button.setDisabled(False)
        self.export_button.triggered.connect(self.export_selection)

        self.main_content_layout.addWidget(self.actions_toolbar)

        # now we want the menubar
        self.menu_bar = self.menuBar()
        self.file_menu = self.menu_bar.addMenu("&File")
        self.edit_menu = self.menu_bar.addMenu("&Edit")
        self.menu_bar.addSeparator()
        self.help_menu = self.menu_bar.addMenu("&Help")

        self.file_menu.addActions([self.open_button])

        self.close_button = self.file_menu.addAction(
            QIcon.fromTheme("document-close"),
            "Close File"  # bit redundant, i actually see no use
        )
        self.close_button.triggered.connect(self.close_vpk)

        self.file_menu.addSeparator()

        self.file_menu.addAction(QIcon.fromTheme("application-exit"),
                                 "Exit").triggered.connect(self.close)

        self.edit_menu.addActions([self.search_button])

        self.help_menu.addAction(QIcon.fromTheme("help-about"),
                                 "About && License").triggered.connect(
                                     self.about)

        # the statusbar
        self.status_bar = self.statusBar()

        # set up the splitters

        # horizontal
        self.horz_splitter_container = QWidget()
        self.horz_splitter_container_layout = QVBoxLayout()
        self.horz_splitter_container.setLayout(
            self.horz_splitter_container_layout)

        self.horz_splitter = QSplitter(Qt.Horizontal)

        self.horz_splitter.addWidget(self.vpk_tree)
        self.horz_splitter.addWidget(self.dir_list)

        self.horz_splitter.setSizes([50, 200])

        self.horz_splitter_container_layout.addWidget(self.horz_splitter)

        # vertical
        self.vert_splitter = QSplitter(Qt.Vertical)

        self.vert_splitter.addWidget(self.horz_splitter_container)
        self.vert_splitter.addWidget(self.output_console)

        self.vert_splitter.setSizes([200, 50])

        self.main_content_layout.addWidget(self.vert_splitter)
        self.setCentralWidget(self.main_content)

        self.show()

    ##
    # Update Functions
    ##
    def update_console(self, text, is_stdout):
        colour = Qt.Red if not is_stdout else Qt.black
        self.output_console.setTextColor(colour)
        self.output_console.moveCursor(QTextCursor.End)
        self.output_console.insertPlainText(text)

    def update_interface(self):

        # update the tree view
        self.update_vpk_tree()

        # update the list view
        self.update_dir_list()

        self.search_button.setDisabled(not self.vpk_loaded)

    def update_vpk_tree(self):
        self.vpk_tree_model.removeRows(0, self.vpk_tree_model.rowCount())
        if self.opened_vpk:
            self.tree_magic(self.internal_directory_understanding)

    def update_dir_list(self):
        self.dir_list_model.removeRows(0, self.dir_list_model.rowCount(
        ))  # remove anyway (for instances such as opening a new file)

        selected = self.vpk_tree.selectedIndexes()
        if not selected:
            return
        selected = selected[0]
        selected_item = self.vpk_tree_model.itemFromIndex(selected)
        if not selected_item:
            return

        if not selected_item.is_dir:
            return

        path = selected_item.path
        understanding = self.get_understanding_from(
            self.internal_directory_understanding, path)
        self.list_magic(understanding, path)

    ##
    # Events
    ##
    def vpk_tree_item_clicked(self, event):
        self.vpk_tree._mousePressEvent(event)

        index = self.vpk_tree.indexAt(event.pos())

        # We can rather safely assume any items in the vpk tree will have OUR special attributes
        if index.isValid():
            item = self.vpk_tree_model.itemFromIndex(index)
            print("selected", item.path)

            if item.is_dir:
                self.update_dir_list()

    def dir_list_item_double_clicked(self, index):

        item = self.dir_list_model.itemFromIndex(index)
        if not item.column() == 0:
            return
        print("double clicked", item.path)

        if item.is_dir:
            print("is dir")
            # this is probably a REALLY **REALLY** awful way of doing this, but you're welcome to PR a better way. :)
            index_in_tree = self.find_in_model(self.vpk_tree_model, item.path)

            if index_in_tree.isValid():
                self.vpk_tree.setCurrentIndex(index_in_tree)
                self.update_dir_list()
        else:
            self.status_bar.showMessage(MSG_EXPORT)

            # we clearly wanna export the file and open it
            bits = self.opened_vpk[item.path[1:]].read()

            path = compat.write_to_temp(bits, os.path.split(item.path)[1])
            print("Wrote to", path)
            compat.tell_os_open(path)

            self.status_bar.clearMessage()

    def dir_list_item_updated(self, item):
        if item.checkState() == Qt.Checked:
            if not item.is_dir:
                self.export_paths.append(item.path)
            else:
                index_in_tree = self.find_in_model(self.vpk_tree_model,
                                                   item.path)

                if index_in_tree.isValid():
                    paths = self.recursively_get_paths_from_dir_index_item(
                        index_in_tree, self.vpk_tree_model)

                    self.export_paths += paths

        elif item.checkState() == Qt.Unchecked:
            if not item.is_dir:
                if item.path in self.export_paths:
                    self.export_paths.remove(item.path)
            else:
                index_in_tree = self.find_in_model(self.vpk_tree_model,
                                                   item.path)

                if index_in_tree.isValid():
                    paths = self.recursively_get_paths_from_dir_index_item(
                        index_in_tree, self.vpk_tree_model)

                    for path in paths:
                        if path in self.export_paths:
                            self.export_paths.remove(path)

    ##
    # The next 3 functions are the original pathtodir but merged with the program
    ##
    def nested_dict(self):
        return defaultdict(self.nested_dict)

    def nested_dict_to_regular(self, d):
        if not isinstance(d, defaultdict):
            return d
        return {k: self.nested_dict_to_regular(v) for k, v in d.items()}

    def understand_directories(self, list_of_paths):
        use_dict = defaultdict(self.nested_dict)

        for path in list_of_paths:
            parts = path.split('/')
            if parts:
                marcher = use_dict
                for key in parts[:-1]:
                    marcher = marcher[key]
                marcher[parts[-1]] = parts[-1]

        return dict(use_dict)

    def get_understanding_from(self, understanding, path):
        keys = path.split('/')
        if keys[0] == '':
            keys = keys[1:]

        if keys:
            marcher = understanding
            for key in keys:
                marcher = marcher[key]

            # we can now assume marcher is the understanding we want
            return marcher

    ##
    # Utility
    ##
    def tree_magic(self, dict_of_things, parent=None, root=''):

        if not parent:
            parent = self.vpk_tree_model

        # Stack overflow 14478170
        for thing in sorted(dict_of_things, key=lambda f: os.path.splitext(f)):

            path = root + '/{}'.format(thing)

            thing_item = QStandardItem()
            thing_item.setText(thing)
            thing_item.setEditable(False)

            thing_item.path = path
            thing_item.is_dir = False

            icon, _ = self.get_info_from_path(path)

            thing_item.setIcon(icon)

            if isinstance(dict_of_things[thing], dict):
                thing_item.setIcon(QIcon.fromTheme("folder"))
                self.tree_magic(dict_of_things[thing], thing_item, path)

                thing_item.is_dir = True

            parent.appendRow(thing_item)

    def list_magic(self, dict_of_things, root=''):
        # like tree_magic but operates on dir_list

        for thing in sorted(dict_of_things, key=lambda f: os.path.splitext(f)):

            path = root + '/{}'.format(thing)

            thing_item = QStandardItem()
            thing_item.setText(thing)
            thing_item.setEditable(False)
            thing_item.setCheckable(True)

            thing_item_type = QStandardItem(
            )  # This doesn't actually do anything but convey more information to the user
            thing_item_type.setEditable(False)

            if path in self.export_paths:
                thing_item.setCheckState(Qt.Checked)

            thing_item.path = path
            thing_item.is_dir = False

            icon, desc = self.get_info_from_path(path)

            thing_item.setIcon(icon)
            thing_item_type.setText(desc)

            if isinstance(dict_of_things[thing], dict):
                thing_item.setIcon(QIcon.fromTheme("folder"))
                thing_item.is_dir = True
                thing_item_type.setText("Directory")

            self.dir_list_model.appendRow([thing_item, thing_item_type])

    def get_info_from_path(self,
                           path):  # returns the icon AND description string

        icon = None
        desc = None

        # first we test against mimetype
        # probably bad code, but it works!

        thing_mimetype = mimetypes.guess_type(path)[0]

        if thing_mimetype:
            if thing_mimetype[:6] == "audio/":
                icon = QIcon.fromTheme("audio-x-generic")
            elif thing_mimetype[:12] == "application/":
                icon = QIcon.fromTheme("application-x-generic")
            elif thing_mimetype[:5] == "text/":
                icon = QIcon.fromTheme("text-x-generic")
            elif thing_mimetype[:6] == "image/":
                icon = QIcon.fromTheme("image-x-generic")
            elif thing_mimetype[:6] == "video/":
                icon = QIcon.fromTheme("video-x-generic")

            desc = thing_mimetype

        # well ok, maybe that didn't work, let's test the filepath ourselves.

        file_ext = os.path.splitext(path)[1]

        if file_ext:
            if file_ext in [".vtf"]:
                icon = QIcon.fromTheme("image-x-generic")
                desc = "Valve Texture File"
            elif file_ext in [".vmt"]:
                icon = QIcon.fromTheme("text-x-generic")
                desc = "Valve Material File"
            elif file_ext in [
                    ".pcf"
            ]:  # we can safely assume they are not fonts in this context, but rather
                icon = QIcon.fromTheme("text-x-script")
                desc = "Valve DMX Implementation"  # TODO: is there a better name
            elif file_ext in [".bsp"]:
                icon = QIcon.fromTheme("text-x-generic")
                desc = "Binary Space Partition"
            elif file_ext in [".res"]:
                icon = QIcon.fromTheme("text-x-generic")
                desc = "Valve Key Value"
            elif file_ext in [".vcd"]:
                icon = QIcon.fromTheme("text-x-generic")
                desc = "Valve Choreography Data"

        if not icon:  # If all else fails, display SOMETHING
            icon = QIcon.fromTheme("text-x-generic")
        if not desc:
            desc = "File"

        return icon, desc

    def find_in_model(self, model: QStandardItemModel, path):
        for i in range(0, model.rowCount()):
            index_in_tree = model.index(i, 0)

            if model.itemFromIndex(index_in_tree).path == path:
                return index_in_tree

            if model.itemFromIndex(index_in_tree).is_dir:
                index_in_tree = self.find_in_model_parent(model,
                                                          path,
                                                          parent=index_in_tree)

                if not index_in_tree.isValid():
                    continue

                if model.itemFromIndex(index_in_tree).path == path:
                    return index_in_tree

    def find_in_model_parent(self, model: QStandardItemModel, path, parent):
        for i in range(0, model.rowCount(parent)):
            index_in_tree = model.index(i, 0, parent)

            if model.itemFromIndex(index_in_tree).path == path:
                return index_in_tree

            if model.itemFromIndex(index_in_tree).is_dir:
                index_in_tree = self.find_in_model_parent(model,
                                                          path,
                                                          parent=index_in_tree)

                if not index_in_tree.isValid():
                    continue

                if model.itemFromIndex(index_in_tree).path == path:
                    return index_in_tree

        return QModelIndex()

    def export_file(self, path, out_dir):
        filepath = os.path.split(path)[0]

        if not os.path.isdir('{}{}'.format(out_dir, filepath)):
            os.makedirs('{}{}'.format(out_dir, filepath))

        print("Attempting to export to", "{}{}".format(out_dir, filepath),
              "from", path[1:], "in the vpk")
        outcontents = self.opened_vpk.get_file(path[1:]).read()

        outfile = open('{}{}'.format(out_dir, path), 'wb')
        outfile.write(outcontents)
        outfile.close()

    def recursively_get_paths_from_dir_index_item(self, index_in_tree, model):
        paths = []

        for i in range(
                self.vpk_tree_model.itemFromIndex(index_in_tree).rowCount()):
            index = self.vpk_tree_model.index(i, 0, index_in_tree)
            index_item = self.vpk_tree_model.itemFromIndex(index)

            if not index_item.is_dir:
                paths.append(index_item.path)
            else:
                paths += self.recursively_get_paths_from_dir_index_item(
                    index, model)

        return paths

    def close_vpk(self):  # We trash everything!
        self.vpk_loaded = False
        self.opened_file = None
        self.opened_vpk = {}
        self.export_paths = []
        self.internal_directory_understanding = {}

        self.status_bar.showMessage(MSG_UPDATE_UI)
        self.update_interface()
        self.status_bar.clearMessage()

    def open_vpk(self, vpk_path):
        if self.vpk_loaded:  # if we already have a file open, close it.
            self.close_vpk()

        self.status_bar.showMessage(MSG_OPEN_VPK)

        if not os.path.exists(vpk_path):
            print(
                "Attempted to open {}, which doesn't exist.".format(vpk_path))
            return

        self.opened_file = vpk_path

        try:
            self.opened_vpk = vpk.open(vpk_path)
        except Exception as e:
            print("Ran into an error from the VPK Library.")
            sys.stdout.write(str(e))
            self.error_box(str(e))
            return

        self.vpk_loaded = True

        self.status_bar.showMessage(MSG_UNDERSTAND_VPK)

        # Now we attempt to understand the vpk
        self.internal_directory_understanding = self.understand_directories(
            self.opened_vpk)

        self.status_bar.showMessage(MSG_UPDATE_UI)
        self.update_interface()
        self.status_bar.clearMessage()

    ##
    # Dialogs
    ##
    def open_dialog(self):
        fn = QFileDialog.getOpenFileName(None,
                                         "Open Package",
                                         str(pathlib.Path.home()),
                                         filter=("Valve Pak Files (*.vpk)"))

        return fn

    def open_dir_dialog(self, title="Open Directory"):
        fn = QFileDialog.getExistingDirectory(None, title,
                                              str(pathlib.Path.home()))

        return fn

    def error_box(self, text="...", title="Error"):
        box = QMessageBox()
        box.setIcon(QMessageBox.Critical)
        box.setText(text)
        box.setWindowTitle(title)
        box.setStandardButtons(QMessageBox.Ok)

        return box.exec()

    def info_box(self, text="...", title="Info"):
        box = QMessageBox()
        box.setIcon(QMessageBox.Information)
        box.setText(text)
        box.setWindowTitle(title)
        box.setStandardButtons(QMessageBox.Ok)

        return box.exec()

    def dir_list_context_menu(self, event):
        menu = QMenu(self)

        selected = self.dir_list.selectedIndexes()
        if not selected:
            return
        selected = selected[0]
        selected_item = self.dir_list_model.itemFromIndex(selected)
        path = selected_item.path

        extract = menu.addAction(QIcon.fromTheme("extract-archive"), "Extract")
        validate = menu.addAction(QIcon.fromTheme("view-certificate"),
                                  "Validate")  # TODO: better validation icon
        menu.addSeparator()
        gotodirectory = menu.addAction(QIcon.fromTheme("folder"),
                                       "Go To Directory")
        menu.addSeparator()
        properties = menu.addAction(QIcon.fromTheme("settings-configure"),
                                    "Properties")

        extract.setDisabled(selected_item.is_dir)
        validate.setDisabled(selected_item.is_dir)

        action = menu.exec_(self.dir_list.mapToGlobal(event))
        if action == extract:
            self.export_selected_file(path)
        elif action == validate:
            self.validate_file(path)
        elif action in [gotodirectory, properties]:
            self.info_box(
                "I'm not sure what this does in the original GCFScape.\nIf you know, please make an issue on github!"
            )

    def about(self):
        box = QMessageBox()
        box.setWindowTitle(
            "About PYCFScape")  # TODO: what do we version and how
        box.setText("""PYCFScape
Version 0
MIT LICENSE V1.00 OR LATER
Python {}
QT {}
AUTHORS (Current Version):
ACBob - https://acbob.gitlab.io

Project Homepage
https://github.com/acbob/pycfscape""".format(sys.version, QT_VERSION_STR))

        box.setIcon(QMessageBox.Information)

        box.exec()

    ##
    # Actions
    ##
    def open_file(self):
        self.status_bar.showMessage(MSG_USER_WAIT)

        fn = self.open_dialog()[0]
        if not fn:
            self.status_bar.clearMessage()
            return

        self.open_vpk(fn)

    def search_for_file(self, event):
        print(event)

    def export_selection(self):
        if not self.export_paths:
            self.info_box(
                "You can't export nothing!\n(Please select some items to export.)"
            )
            return

        self.status_bar.showMessage(MSG_USER_WAIT)
        output_dir = self.open_dir_dialog("Export to...")

        if output_dir:
            self.status_bar.showMessage(MSG_EXPORT)
            print("attempting export to", output_dir)

            for file in self.export_paths:
                self.export_file(file, output_dir)

        self.status_bar.clearMessage()

    def export_selected_file(self, file):
        self.status_bar.showMessage(MSG_USER_WAIT)
        output_dir = self.open_dir_dialog("Export to...")

        if output_dir:
            self.status_bar.showMessage(MSG_EXPORT)
            print("attempting export to", output_dir)

            self.export_file(file, output_dir)

        self.status_bar.clearMessage()

    def validate_file(self, file):
        filetoverify = self.opened_vpk.get_file(file[1:])

        if filetoverify:
            verified = filetoverify.verify()
            if verified:
                self.info_box("{} is a perfectly healthy file.".format(file),
                              "All's good.")
            else:
                self.error_box("{} is not valid!".format(file), "Uh oh.")
        else:
            print("What? file doesn't exist? HOW IS THIS MAN")
예제 #20
0
    def createGUI(self):
        root = TagManager.createSimpleTree()

        model = TagTreeModel(root,
                             self._tag_checker,
                             self._color_helper,
                             parent=self)
        model.invalidValueSetted.connect(
            self.invalidValueSettedByUserToTreeViewModel)
        #model.dataChanged.connect(dataChanged)

        self.setMinimumSize(300, 150)
        self.setWindowTitle('My Tag Manager')
        layout = QVBoxLayout(self)
        ## Menu Layout
        menu_layout = QHBoxLayout(self)
        but_save = QPushButton('Save', self)
        but_save.setIcon(QApplication.style().standardIcon(
            QStyle.SP_DialogSaveButton))
        menu_layout.addWidget(but_save)
        but_save.clicked.connect(self.but_save_clicked)

        but_load = QPushButton('Load', self)
        but_load.setIcon(QApplication.style().standardIcon(
            QStyle.SP_DirOpenIcon))
        menu_layout.addWidget(but_load)
        but_load.clicked.connect(self.but_load_clicked)

        but_new_f = QPushButton('New tag collections', self)
        but_new_f.setIcon(QApplication.style().standardIcon(
            QStyle.SP_FileIcon))
        but_new_f.setToolTip('New tag collections')
        menu_layout.addWidget(but_new_f)
        but_new_f.clicked.connect(self.but_new_tree_clicked)
        layout.addLayout(menu_layout)
        ## Menu Layout END

        tv = QTreeView(self)
        tv.setDragDropMode(QAbstractItemView.InternalMove)
        tv.setDragEnabled(True)
        tv.setAcceptDrops(True)
        tv.setDropIndicatorShown(True)
        tv.setContextMenuPolicy(Qt.CustomContextMenu)
        tv.customContextMenuRequested.connect(
            self.customContextMenuRequestedForTreeView)

        tv.setModel(model)
        #tv.setAlternatingRowColors(True)
        layout.addWidget(tv)
        self._widget_tv = tv

        but = QPushButton('Remove Selected', self)
        but.setIcon(QApplication.style().standardIcon(QStyle.SP_TrashIcon))
        layout.addWidget(but)
        but.clicked.connect(self.but_remove_clicked)

        but_add = QPushButton('Add', self)
        layout.addWidget(but_add)
        but_add.clicked.connect(self.but_add_clicked)

        but_add_to_root = QPushButton('Add new element to root')
        but_add_to_root.setToolTip("Add new element to root of tree.")
        layout.addWidget(but_add_to_root)
        but_add_to_root.clicked.connect(self.but_add_to_root_clicked)

        but_item_up = QPushButton("Up")
        but_item_up.setToolTip("Up selected item.")
        but_item_up.clicked.connect(self.but_item_up_clicked)
        layout.addWidget(but_item_up)

        but_item_down = QPushButton("Down")
        but_item_down.setToolTip("Down selected item.")
        but_item_down.clicked.connect(self.but_item_down_clicked)
        layout.addWidget(but_item_down)

        but_font = QPushButton('Set Font', self)
        layout.addWidget(but_font)
        but_font.clicked.connect(self.but_font_dialog_clicked)
        self.layout().deleteLater()  # Remove default layout
        self.setLayout(layout)
예제 #21
0
class OpenedFileExplorer(DockWidget):
    """Opened File Explorer is list widget with list of opened files.
    It implements switching current file, files sorting. Uses _OpenedFileModel internally.
    Class instance created by Workspace.
    """
    def __init__(self, workspace):
        DockWidget.__init__(self, workspace, "&Opened Files",
                            QIcon(":/enkiicons/filtered.png"), "Alt+O")

        self._workspace = workspace

        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.tvFiles = QTreeView(self)
        self.tvFiles.setHeaderHidden(True)
        self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked)
        self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tvFiles.setDragEnabled(True)
        self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove)
        self.tvFiles.setRootIsDecorated(False)
        self.tvFiles.setTextElideMode(Qt.ElideMiddle)
        self.tvFiles.setUniformRowHeights(True)

        self.tvFiles.customContextMenuRequested.connect(
            self._onTvFilesCustomContextMenuRequested)

        self.setWidget(self.tvFiles)
        self.setFocusProxy(self.tvFiles)

        self.model = _OpenedFileModel(
            self)  # Not protected, because used by Configurator
        self.tvFiles.setModel(self.model)
        self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.tvFiles.setAttribute(Qt.WA_MacSmallSize)

        self._workspace.currentDocumentChanged.connect(
            self._onCurrentDocumentChanged)

        # disconnected by startModifyModel()
        self.tvFiles.selectionModel().selectionChanged.connect(
            self._onSelectionModelSelectionChanged)

        self.tvFiles.activated.connect(self._workspace.focusCurrentDocument)

        core.actionManager().addAction("mView/aOpenedFiles", self.showAction())

        # Add auto-hide capability.
        self._waitForCtrlRelease = False
        core.actionManager().action("mNavigation/aNext").triggered.connect(
            self._setWaitForCtrlRelease)
        core.actionManager().action("mNavigation/aPrevious").triggered.connect(
            self._setWaitForCtrlRelease)
        QApplication.instance().installEventFilter(self)

    def terminate(self):
        """Explicitly called destructor
        """
        core.actionManager().removeAction("mView/aOpenedFiles")
        QApplication.instance().removeEventFilter(self)

    def startModifyModel(self):
        """Blocks signals from model while it is modified by code
        """
        self.tvFiles.selectionModel().selectionChanged.disconnect(
            self._onSelectionModelSelectionChanged)

    def finishModifyModel(self):
        """Unblocks signals from model
        """
        self.tvFiles.selectionModel().selectionChanged.connect(
            self._onSelectionModelSelectionChanged)

    @pyqtSlot(Document, Document)
    def _onCurrentDocumentChanged(self, oldDocument, currentDocument):  # pylint: disable=W0613
        """ Current document has been changed on workspace
        """
        if currentDocument is not None:
            index = self.model.documentIndex(currentDocument)

            self.startModifyModel()
            self.tvFiles.setCurrentIndex(index)
            # scroll the view
            self.tvFiles.scrollTo(index)
            self.finishModifyModel()

    @pyqtSlot(QItemSelection, QItemSelection)
    def _onSelectionModelSelectionChanged(self, selected, deselected):  # pylint: disable=W0613
        """ Item selected in the list. Switch current document
        """
        if not selected.indexes():  # empty list, last file closed
            return

        index = selected.indexes()[0]
        # backup/restore current focused widget as setting active mdi window will steal it
        focusWidget = self.window().focusWidget()

        # set current document
        document = self._workspace.sortedDocuments[index.row()]
        self._workspace.setCurrentDocument(document)

        # restore focus widget
        if focusWidget:
            focusWidget.setFocus()

    @pyqtSlot(QPoint)
    def _onTvFilesCustomContextMenuRequested(self, pos):
        """Connected automatically by uic
        """
        menu = QMenu()

        menu.addAction(core.actionManager().action("mFile/mClose/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mSave/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mReload/aCurrent"))
        menu.addSeparator()
        menu.addAction(
            core.actionManager().action("mFile/mFileSystem/aRename"))
        toggleExecutableAction = core.actionManager().action(
            "mFile/mFileSystem/aToggleExecutable")
        if toggleExecutableAction:  # not available on Windows
            menu.addAction(toggleExecutableAction)
        core.actionManager().action("mFile/mFileSystem").menu(
        ).aboutToShow.emit()  # to update aToggleExecutable

        menu.exec_(self.tvFiles.mapToGlobal(pos))

    def _setWaitForCtrlRelease(self):
        # We can't see actual Ctrl+PgUp/PgDn keypresses, since these get eaten
        # by the QAction and don't even show up in the event filter below. We
        # want to avoid waiting for a Ctrl release if the menu item brought us
        # here. As a workaround, check that Ctrl is pressed. If so, it's
        # unlikely to be the menu item.
        if QApplication.instance().keyboardModifiers() & Qt.ControlModifier:
            self._waitForCtrlRelease = True
            self.show()
        else:
            # If this was a menu selection, then update the MRU list. We can't
            # do this now, since the current document hasn't been changed yet.
            QTimer.singleShot(0, self.model.sortDocuments)

    def eventFilter(self, obj, event):
        """An event filter that looks for ctrl key releases and focus out
           events."""
        # Wait for the user to release the Ctrl key.
        if (self._waitForCtrlRelease and event.type() == QEvent.KeyRelease
                and event.key() == Qt.Key_Control
                and event.modifiers() == Qt.NoModifier):
            self.model.sortDocuments()
            self._waitForCtrlRelease = False
            if not self.isPinned():
                self.hide()
        # Look for a focus out event sent by the containing widget's focus
        # proxy.
        if event.type() == QEvent.FocusOut and obj == self.focusProxy():
            self.model.sortDocuments()
        return QObject.eventFilter(self, obj, event)
예제 #22
0
class MainWindow(QtWidgets.QMainWindow):

    Folder = 1
    File = 2
    Table = 3

    LogInfo = 101
    LogWarning = 102
    LogError = 103


    def LoadTableData(self,data,model):
        
        try:
            value = json.loads(data)
            table = value['table']
            model.setColumnCount(len(table))

            data = value['data']
            model.setRowCount(len(data) + 2)

            for v in table:
                model.setHeaderData(v[0],Qt.Horizontal,v[1])
                model.setData(model.index(0,v[0]),v[2])
                model.setData(model.index(1,v[0]),v[3])

            for i in range(0,len(data)):
                v = data[i]
                for j in range(0,len(v)):
                    model.setData(model.index(i+2,j),v[j])
                
            model.activeColumn = value['activeColumn']
        except Exception as e:
            pass

    def AddTreeItem(self,parent,text,type,isexpand = True):

        if parent == None:
            texts = text.split('.')
            if len(texts) > 1:
                rootItem = self.rootItem
                for i in range(0,len(texts)-1):
                    t = texts[i]
                    childItem = None
                    for j in range(0,rootItem.rowCount()):
                        childItem = rootItem.child(j,0)
                        if t == childItem.data():
                            break

                    rootItem = childItem
                parent = rootItem
                text = texts[-1]
            else:
                parent = self.rootItem

        lastFolderItem = None
        for i in range(0,parent.rowCount()):
            childItem = self.model.itemFromIndex(self.model.index(i,0,parent.index()))
            if childItem.data() == MainWindow.Folder:
                lastFolderItem = childItem

            if text == childItem.text():
                return None

        icon = None
        if type == MainWindow.Folder:
            icon = self.iconProvider.icon(QFileIconProvider.Folder)
        elif type == MainWindow.File:
            icon = self.iconProvider.icon(QFileIconProvider.File)
        elif type == MainWindow.Table:
            icon = self.iconProvider.icon(QFileIconProvider.Desktop)

        item = QStandardItem(parent)
        
        item.setIcon(icon)
        item.setText(text)
        item.setData(type)
        item.setFlags(QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable)

        if type == MainWindow.Folder and lastFolderItem != None:
            parent.insertRow(lastFolderItem.row()+1,item)
        else:
            parent.appendRow(item)
        
        if isexpand == True:
            self.tree.expand(parent.index())

        return item

    def SetRootTreeItem(self,text):
        self.rootItem = QStandardItem()
        self.rootItem.setIcon(self.iconProvider.icon(QFileIconProvider.Folder))
        self.rootItem.setText(text)
        self.rootItem.setData(MainWindow.Folder)

        self.model.appendRow(self.rootItem)

        for i in range(0,self.model.columnCount()):
            colItem = self.model.itemFromIndex(self.model.index(0,i))
            colItem.setFlags(QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsSelectable)

    def GetTreeItemShortPath(self,item):
        tempItem = item
        names = []
        while True:
            names.append(tempItem.text())
            tempItem = tempItem.parent()
            if tempItem == self.rootItem:
                break

        return '.'.join(reversed(names))

    def OnTreeCustomContextMenuRequested(self,pt):
        index = self.tree.indexAt(pt);  
        if index.isValid():

            item = self.model.itemFromIndex(index)

            parent = item.parent()
            if parent != None:
                item = self.model.itemFromIndex(self.model.index(item.row(),0,parent.index()))

            def OnAddTreeItem(self,item,type):
                inputDialog = InputDialog.InputDialog(self)
                ret = inputDialog.exec_()
                inputDialog.destroy()

                if QtWidgets.QDialog.Rejected == ret:
                    return

                if len(inputDialog.GetTextValue()) == 0:
                    return

                itemTable = self.AddTreeItem(item,str(inputDialog.GetTextValue()),type)

                if MainWindow.Table == type:
                    model = GridTableView.TableViewItemModel(2,0)
                    model.setParent(self.tree)
                    itemTable.setData(model,Qt.UserRole+2)

                cursor = None
                try:
                    cursor = self.db.cursor()
                    cursor.execute('insert into datas (k, v, t) values (\'{}\', \'{}\', {})'.format(self.GetTreeItemShortPath(itemTable),"None",type))
                    self.db.commit()
                except Exception as e:
                    pass
                finally:
                    cursor.close()

            def OnAddFolder(index,self = self,item = item):
                OnAddTreeItem(self,item,MainWindow.Folder)

            def OnAddFile(index,self = self,item = item):
                OnAddTreeItem(self,item,MainWindow.File)

            def OnAddTable(index,self = self,item = item):
                OnAddTreeItem(self,item,MainWindow.Table)

            def OnRename(index,self = self,item = item):
                inputDialog = InputDialog.InputDialog(self,item.text())
                ret = inputDialog.exec_()
                inputDialog.destroy()

                if QtWidgets.QDialog.Rejected == ret:
                    return

                text = inputDialog.GetTextValue()
                if len(text) == 0:
                    return

                #old_shortpath = self.GetTreeItemShortPath(item)
                

                items = []
                oldpaths = []

                if item.data() == MainWindow.Table:
                    items.append(item)
                else:
                    def GetAllChildItems(items,item):
                        for i in range(0,item.rowCount()):
                            childItem = item.child(i,0)

                            if childItem.data() != MainWindow.Table:
                                GetAllChildItems(items,childItem)
                            else:
                                items.append(childItem)
                        items.append(item)

                    GetAllChildItems(items,item)
                for v in items:
                    oldpaths.append(self.GetTreeItemShortPath(v))

                item.setText(text)
                cursor = self.db.cursor()
                for i in range(0,len(items)):
                    v = items[i]
                    oldpath = oldpaths[i]
                    cursor.execute('update datas set k=? where k=?', (self.GetTreeItemShortPath(v),oldpath))

                    findTabIndex = False
                    for i in range(0,self.tabWidget.count()):
                        if findTabIndex == True:
                            continue

                        if oldpath == self.tabWidget.tabToolTip(i):
                            findTabIndex = True
                            self.tabWidget.setTabToolTip(i,self.GetTreeItemShortPath(v))
                            if v == item and item.data() == MainWindow.Table:
                                self.tabWidget.setTabText(i,text)  
                    
                cursor.close()      
                self.db.commit()

            def OnDelete(index,self = self,item = item):
                if item == self.rootItem:
                    return

                deleyeKeys = set()
                cursor = self.db.cursor()

                if item.data() == MainWindow.Folder or item.data() == MainWindow.File:
                    cursor.execute('select * from datas')
                    shortpath = self.GetTreeItemShortPath(item)
                    for i in range(0,self.tabWidget.count()):
                        tabText = self.tabWidget.tabToolTip(i)
                        if len(tabText) >= len(shortpath) and tabText[0:len(shortpath)] == shortpath:
                            self.tabWidget.removeTab(i)
                            #if self.OnCloseTab(i) == False:
                            #    return

                    def DeleteChildItems(cursor,item):
                        for i in range(0,item.rowCount()):
                            childItem = item.child(i,0)

                            if item.data() != MainWindow.Table:
                                cursor.execute('delete from datas where k=?', (self.GetTreeItemShortPath(childItem),))
                                DeleteChildItems(cursor,childItem)
                        cursor.execute('delete from datas where k=?', (self.GetTreeItemShortPath(item),))

                    DeleteChildItems(cursor,item)
                    self.model.removeRow(item.row(),item.parent().index())

                elif item.data() == MainWindow.Table:
                    shortpath = self.GetTreeItemShortPath(item)
                    for i in range(0,self.tabWidget.count()):
                        if self.tabWidget.tabToolTip(i) == shortpath:
                            self.tabWidget.removeTab(i)
                            #if self.OnCloseTab(i) == False:
                            #    return
                    deleyeKeys.add(shortpath)
                    self.model.removeRow(item.row(),item.parent().index())

                for v in deleyeKeys:
                    try:
                        cursor.execute('delete from datas where k=?', (v,))
                    except Exception as e:
                        pass
                cursor.close()      
                self.db.commit()
            
            action_AddDir = QtWidgets.QAction("添加目录",None,triggered=OnAddFolder)
            action_AddConfig = QtWidgets.QAction("添加文件",None,triggered=OnAddFile)
            action_AddTable = QtWidgets.QAction("添加配置表",None,triggered=OnAddTable)
            action_Rename = QtWidgets.QAction("重命名",None,triggered=OnRename)
            action_Delete = QtWidgets.QAction("删除",None,triggered=OnDelete)

            menuTree = QtWidgets.QMenu("menuTree",self.tree)
            menuTree.addAction(action_AddDir)
            menuTree.addAction(action_AddConfig)
            menuTree.addAction(action_AddTable)
            menuTree.addSeparator()
            menuTree.addAction(action_Rename)
            menuTree.addSeparator()
            menuTree.addAction(action_Delete)

            if item == self.rootItem:
                action_Rename.setDisabled(True)

            if item.data() == MainWindow.Folder:
                action_AddTable.setDisabled(True)
                if item == self.rootItem:
                    action_Delete.setDisabled(True)
            elif item.data() == MainWindow.File:
                action_AddDir.setDisabled(True)
                action_AddConfig.setDisabled(True)
            elif item.data() == MainWindow.Table:
                action_AddTable.setDisabled(True)
                action_AddDir.setDisabled(True)
                action_AddConfig.setDisabled(True)
            else:
                return

            menuTree.exec_(QtGui.QCursor.pos())
            menuTree.destroy()

    def closeEvent(self, event):
        
        count = self.tabWidget.count()
        for i in reversed(range(0,count)):
            self.OnCloseTab(i)

        event.accept()

    def OnPaste(self):
        tableView = self.tabWidget.currentWidget()
        if tableView != None:
            if tableView.IsChanged == True:
                if QMessageBox.Yes == QMessageBox.information(self,'Save','Do you save changes?',QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel,QMessageBox.Yes):
                    tableView.Save()

            fileName,fileType = QtWidgets.QFileDialog.getOpenFileName(self,'Open File','','Excel File(*.xls *.xlsx)')
            if os.path.exists(fileName) and os.path.isfile(fileName):
                tableView.Paste(fileName)

    def OnExport(self):

        dialog = ExportDialog.ExportDialog(self)
        dialog.exec_()

    def DoSave(self,tableView):
        datas = tableView.Save()
        if datas != None:
            tabIndex = self.tabWidget.indexOf(tableView)

            cursor = None
        
            try:
                cursor = self.db.cursor()

                k = self.tabWidget.tabToolTip(tabIndex)
                cursor.execute('select * from datas where k=?', (k,))
                values = cursor.fetchall()

                if len(values) > 0 and values[0][0] == k:
                    cursor.execute('update datas set v=? where k=?', (datas,k))
                else:
                    cursor.execute('insert into datas (k, v, t) values (\'{}\', \'{}\', {})', (k,datas,MainWindow.Table))
                self.db.commit()
            except Exception as e:
                pass
            finally:
                if cursor != None:
                    cursor.close()

    def OnSave(self):
        tableView = self.tabWidget.currentWidget()
        if tableView != None and tableView.IsChanged == True:
            self.DoSave(tableView)
            tabIndex = self.tabWidget.indexOf(tableView)
            self.tabWidget.tabBar().setTabTextColor(tabIndex,QColor(0,0,0))

    def OnSaveAll(self):
        for i in range(0,self.tabWidget.count()):
            tableView = self.tabWidget.widget(i)
            if tableView.IsChanged == True:
                self.DoSave(tableView)
                self.tabWidget.tabBar().setTabTextColor(i,QColor(0,0,0))


    def OnUndo(self):
        tableView = self.tabWidget.currentWidget()
        if tableView != None:
            tableView.Undo()

    def OnRedo(self):
        tableView = self.tabWidget.currentWidget()
        if tableView != None:
            tableView.Redo()

    def EnableSave(self,enable,tableView):

        if enable == True:
            self.tabWidget.tabBar().setTabTextColor(self.tabWidget.indexOf(tableView),QColor(233,21,10))
        else:
            self.tabWidget.tabBar().setTabTextColor(self.tabWidget.indexOf(tableView),QColor(0,0,0))

    def OnTreeDoubleClicked(self,index):
        if index.isValid() == False:
            return

        item = self.model.itemFromIndex(index)
        shortpath = self.GetTreeItemShortPath(item)

        findTabIndex = -1
        if item.data() == MainWindow.Table:
            for i in range(0,self.tabWidget.count()):
                if self.tabWidget.tabToolTip(i) == shortpath:
                    findTabIndex = i
                    break
            if findTabIndex != -1:
                self.tabWidget.setCurrentIndex(findTabIndex)
            else:
                tableView = GridTableView.GridTableView(item.data(Qt.UserRole+2),self.tabWidget)

                tabIndex = self.tabWidget.addTab(tableView,item.text())
                self.tabWidget.setTabToolTip(tabIndex,shortpath)
                self.tabWidget.setCurrentWidget(tableView)
            pass
        pass

    def OnCloseTab(self,tabId):
        tableView = self.tabWidget.widget(tabId)
        if tableView.IsChanged == True:
            ret = QMessageBox.information(self,'Save','Do you save changes?',QMessageBox.Yes|QMessageBox.No|QMessageBox.Cancel,QMessageBox.Yes)
            if QMessageBox.Yes == ret:
                self.DoSave(tableView)
            elif QMessageBox.Cancel == ret:
                return False

        self.tabWidget.removeTab(tabId)
        return True

    @property
    def Settings(self):

        if self.setting == None:
            self.setting = {}
            try:
                with open("Settings.cfg",'r') as f:
                    self.setting = json.load(f)
            except IOError as e:
                pass

        return self.setting

    def GetTreeModel(self):
        return self.model

    def __del__(self):
        print('MainWindow.__del__')

        if self.db!= None:
            self.db.close()

        try:
            with open("Settings.cfg",'w') as f:
                json.dump(self.setting,f)
        except IOError as e:
            pass
        finally:
            pass
        pass


    def __init__(self): 
        super(MainWindow, self).__init__()
        
        uic.loadUi('MainWindow.ui', self)

        self.db = None
        self.rootItem = None
        self.setting = None
        self.fileSystemWatcher = QFileSystemWatcher()

        self.oldWindowTitle = self.windowTitle()
        
        self.iconProvider = QFileIconProvider()

        splitterH = QSplitter(self.centralwidget)
        self.verticalLayout.addWidget(splitterH)

        self.tree = QTreeView(splitterH)

        self.model = QStandardItemModel(self.tree)
        self.model.setHorizontalHeaderLabels(['Name'])
        self.model.setColumnCount(1)

        self.tree.setModel(self.model)

        selectionModel = QItemSelectionModel(self.model)
        self.tree.setSelectionModel(selectionModel)
        self.tree.setUniformRowHeights(True)
        self.tree.header().setStretchLastSection(False)
        self.tree.viewport().setAttribute(Qt.WA_StaticContents)
        self.tree.setAttribute(Qt.WA_MacShowFocusRect, False)

        self.tree.header().setSectionResizeMode(QHeaderView.ResizeToContents)
        self.tree.header().setStretchLastSection(False);

        self.tree.setHeaderHidden(True)

        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested['QPoint'].connect(self.OnTreeCustomContextMenuRequested)
        self.tree.doubleClicked.connect(self.OnTreeDoubleClicked)

        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(1)
        self.tree.setSizePolicy(sizePolicy)

        splitterH.addWidget(self.tree)

        self.setStatusBar(None)

        self.tabWidget = QTabWidget(splitterH)
        self.tabWidget.setTabsClosable(True)

        self.tabWidget.resize(self.tabWidget.size().width(),self.size().height()/3*1)

        self.tabWidget.tabCloseRequested['int'].connect(self.OnCloseTab)
        

        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(7)
        self.tabWidget.setSizePolicy(sizePolicy)

        splitterH.addWidget(self.tabWidget)
       
        self.action_Save.setShortcut(Qt.CTRL|Qt.Key_S)
        self.action_Save.triggered.connect(self.OnSave)

        self.actionParse_Excel.triggered.connect(self.OnPaste)
        #self.action_Export_Code.triggered.connect(self.OnExportData)

        self.actionUndo.setShortcut(Qt.CTRL|Qt.Key_Z)
        self.actionUndo.triggered.connect(self.OnUndo)

        self.actionRedo.setShortcut(Qt.CTRL|Qt.Key_Y)
        self.actionRedo.triggered.connect(self.OnRedo)

        self.SetRootTreeItem('root')

        #self.currentZip = ''
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.DelayStart)
        self.timer.start(100)

    def GetAllDatasFromDB(self):
        cursor = self.db.cursor()
        cursor.execute('select * from datas order by k asc')
        values = cursor.fetchall()
        cursor.close()
        return values

    @property
    def TreeRootItem(self):
        return self.rootItem

    def DelayStart(self):
        self.timer.stop()
        self.timer = None
   
        sf = SelectFolder.SelectFolder(self)
        if sf.exec_() == QtWidgets.QDialog.Rejected:
            self.close()

        sf.destroy()
        currentPath = sf.GetFolder()

        if os.path.exists(currentPath) == False:
            return

        self.setWindowTitle(self.oldWindowTitle + ' - ' + currentPath)

        self.db = sqlite3.connect(currentPath)
        values = self.GetAllDatasFromDB()
        for k,v,t in values:
            item = self.AddTreeItem(None,k, t,False)

            if t == MainWindow.Table:  
                model = GridTableView.TableViewItemModel(2,0)
                model.setParent(self.tree)
                if v != 'None':
                    self.LoadTableData(v,model)

                item.setData(model,Qt.UserRole+2)

        self.tree.expand(self.rootItem.index())



        
예제 #23
0
class DataViewer(QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setMinimumSize(500, 400)
        main_layout = QHBoxLayout()
        main_layout.setSpacing(5)
        self.setLayout(main_layout)

        control_column = QVBoxLayout()
        main_layout.addLayout(control_column, stretch=1)

        refresh_button = QPushButton('🔄 Refresh')
        refresh_button.setFont(cn.EMOJI_FONT)
        refresh_button.clicked.connect(self.refresh_list)
        control_column.addWidget(refresh_button)

        self.gesture_tree_view = QTreeView()
        self.gesture_tree_view.setMinimumWidth(250)
        self.gesture_tree_view.header().hide()
        self.gesture_tree_view.setEditTriggers(
            QAbstractItemView.NoEditTriggers)
        self.gesture_tree_view.clicked.connect(self.show_selected)
        self.gesture_tree_view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.gesture_tree_view.customContextMenuRequested.connect(
            self.gesture_context_menu)

        self.gesture_model = QStandardItemModel()
        self.gesture_tree_view.setModel(self.gesture_model)
        self.gesture_tree_view.setAnimated(True)
        control_column.addWidget(self.gesture_tree_view)

        self.displayed_gestures_layout = QVBoxLayout()

        display_column = QVBoxLayout()

        close_all_button = QPushButton('❌ Close all opened')
        close_all_button.setFont(cn.EMOJI_FONT)

        def close_all_displayed_gestures():
            for i in range(self.displayed_gestures_layout.count()):
                self.displayed_gestures_layout.itemAt(i).widget().close()

        close_all_button.clicked.connect(close_all_displayed_gestures)

        control_column.addWidget(close_all_button)
        display_column.addLayout(
            VerticalScrollableExtension(self.displayed_gestures_layout))
        main_layout.addLayout(display_column, stretch=2)

        self.refresh_list()

    def refresh_list(self):
        gestures = sorted(os.listdir(cn.DATA_FOLDER))

        gesture_tree = {}
        for gesture in gestures:
            parts = gesture.split(cn.FILE_NAME_SEPARATOR)
            if parts[0] == cn.SESSION_PREFIX:
                continue

            if len(parts) < 3 or parts[0] != cn.GESTURE_PREFIX:
                logger.debug(f'Skipping file {gesture}, unknown naming.')
                continue

            index = int(parts[1])
            if index < 0 or index >= len(cn.GESTURES):
                logger.debug(f'Invalid index on {gesture}, skipping.')
                continue

            gesture = cn.GESTURES[index]
            parts[1] = str(gesture)

            current_node = gesture_tree
            for part in parts[1:]:
                current_node = current_node.setdefault(part, {})

        self.gesture_model.clear()
        root = self.gesture_model.invisibleRootItem()

        def add_tree(tree: dict, node: QStandardItem):
            for item in tree:
                child_node = QStandardItem(item)
                node.appendRow(child_node)
                add_tree(tree[item], child_node)

        add_tree(gesture_tree, root)

    @staticmethod
    def get_filename(model_index):
        name = []
        node = model_index
        while node.isValid():
            name.append(node.data())
            node = node.parent()
        name.append(cn.GESTURE_PREFIX)

        # TODO this could be nicer
        for i, gesture_spec in enumerate(cn.GESTURES):
            if str(gesture_spec) == name[-2]:
                name[-2] = str(i)
        return cn.FILE_NAME_SEPARATOR.join(name[::-1])

    def show_selected(self, model_index):
        is_leaf = not model_index.child(0, 0).isValid()
        if not is_leaf:
            self.gesture_tree_view.setExpanded(
                model_index,
                not self.gesture_tree_view.isExpanded(model_index))
            return

        filename = DataViewer.get_filename(model_index)
        selected_file = cn.DATA_FOLDER / filename
        data = np.load(selected_file)

        signal_widget = StaticSignalWidget()
        signal_widget.plot_data(data)
        widget = NamedExtension(filename, signal_widget)
        widget = BlinkExtension(widget)
        widget = ClosableExtension(widget)
        widget.setMinimumWidth(600)
        widget.setFixedHeight(200)

        self.displayed_gestures_layout.addWidget(widget)

    def gesture_context_menu(self, point):
        model_index = self.gesture_tree_view.indexAt(point)
        is_leaf = not model_index.child(0, 0).isValid()
        if not is_leaf:
            self.gesture_tree_view.setExpanded(
                model_index,
                not self.gesture_tree_view.isExpanded(model_index))
            return

        menu = QMenu()

        def move_dialog():
            Renamer(DataViewer.get_filename(model_index)).exec()

        menu.addAction('Move', move_dialog)

        def trash_and_remove_from_tree():
            if Renamer.trash_gesture(DataViewer.get_filename(model_index)):
                self.gesture_model.removeRow(model_index.row(),
                                             model_index.parent())

        menu.addAction('Trash', trash_and_remove_from_tree)
        menu.exec(QCursor.pos())
예제 #24
0
class OpenedFileExplorer(DockWidget):
    """Opened File Explorer is list widget with list of opened files.
    It implements switching current file, files sorting. Uses _OpenedFileModel internally.
    Class instance created by Workspace.
    """
    def __init__(self, workspace):
        DockWidget.__init__(self, workspace, "&Opened Files",
                            QIcon(":/enkiicons/filtered.png"), "Alt+O")

        self._workspace = workspace

        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.tvFiles = QTreeView(self)
        self.tvFiles.setHeaderHidden(True)
        self.tvFiles.setEditTriggers(QAbstractItemView.SelectedClicked)
        self.tvFiles.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tvFiles.setDragEnabled(True)
        self.tvFiles.setDragDropMode(QAbstractItemView.InternalMove)
        self.tvFiles.setRootIsDecorated(False)
        self.tvFiles.setTextElideMode(Qt.ElideMiddle)
        self.tvFiles.setUniformRowHeights(True)

        self.tvFiles.customContextMenuRequested.connect(
            self._onTvFilesCustomContextMenuRequested)

        self.setWidget(self.tvFiles)
        self.setFocusProxy(self.tvFiles)

        self.model = _OpenedFileModel(
            self)  # Not protected, because used by Configurator
        self.tvFiles.setModel(self.model)
        self.tvFiles.setAttribute(Qt.WA_MacShowFocusRect, False)
        self.tvFiles.setAttribute(Qt.WA_MacSmallSize)

        self._workspace.currentDocumentChanged.connect(
            self._onCurrentDocumentChanged)

        # disconnected by startModifyModel()
        self.tvFiles.selectionModel().selectionChanged.connect(
            self._onSelectionModelSelectionChanged)

        self.tvFiles.activated.connect(self._workspace.focusCurrentDocument)

        core.actionManager().addAction("mView/aOpenedFiles", self.showAction())

    def terminate(self):
        """Explicitly called destructor
        """
        core.actionManager().removeAction("mView/aOpenedFiles")

    def startModifyModel(self):
        """Blocks signals from model while it is modified by code
        """
        self.tvFiles.selectionModel().selectionChanged.disconnect(
            self._onSelectionModelSelectionChanged)

    def finishModifyModel(self):
        """Unblocks signals from model
        """
        self.tvFiles.selectionModel().selectionChanged.connect(
            self._onSelectionModelSelectionChanged)

    @pyqtSlot(Document, Document)
    def _onCurrentDocumentChanged(self, oldDocument, currentDocument):  # pylint: disable=W0613
        """ Current document has been changed on workspace
        """
        if currentDocument is not None:
            index = self.model.documentIndex(currentDocument)

            self.startModifyModel()
            self.tvFiles.setCurrentIndex(index)
            # scroll the view
            self.tvFiles.scrollTo(index)
            self.finishModifyModel()

    @pyqtSlot(QItemSelection, QItemSelection)
    def _onSelectionModelSelectionChanged(self, selected, deselected):  # pylint: disable=W0613
        """ Item selected in the list. Switch current document
        """
        if not selected.indexes():  # empty list, last file closed
            return

        index = selected.indexes()[0]
        # backup/restore current focused widget as setting active mdi window will steal it
        focusWidget = self.window().focusWidget()

        # set current document
        document = self._workspace.sortedDocuments[index.row()]
        self._workspace.setCurrentDocument(document)

        # restore focus widget
        if focusWidget:
            focusWidget.setFocus()

    @pyqtSlot(QPoint)
    def _onTvFilesCustomContextMenuRequested(self, pos):
        """Connected automatically by uic
        """
        menu = QMenu()

        menu.addAction(core.actionManager().action("mFile/mClose/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mSave/aCurrent"))
        menu.addAction(core.actionManager().action("mFile/mReload/aCurrent"))
        menu.addSeparator()
        menu.addAction(
            core.actionManager().action("mFile/mFileSystem/aRename"))
        toggleExecutableAction = core.actionManager().action(
            "mFile/mFileSystem/aToggleExecutable")
        if toggleExecutableAction:  # not available on Windows
            menu.addAction(toggleExecutableAction)
        core.actionManager().action("mFile/mFileSystem").menu(
        ).aboutToShow.emit()  # to update aToggleExecutable

        menu.exec_(self.tvFiles.mapToGlobal(pos))
예제 #25
0
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        dockWidget = QDockWidget("Explorer", self)

        self.editor = QPlainTextEdit()
        self.editor.document().setDefaultFont(QFont("monospace"))
        self.editor.zoomIn(2)

        self.explorer = QTreeView()
        self.model = QFileSystemModel(self.explorer)
        self.root = self.model.setRootPath("../..")
        self.explorer.setModel(self.model)
        self.explorer.setRootIndex(self.root)
        self.file_path = None

        # Instances of menus and message
        self.custom_menu = Custom_menu(self, self.explorer, self.model,
                                       self.editor, app)
        self.menu = Menu(self, self.explorer, self.editor, self.model)
        self.message = Message(self)

        # Menu bar
        self.file_menu = self.menuBar().addMenu("&File")
        self.help_menu = self.menuBar().addMenu("&Help")
        self.menu.menu_actions()

        # Other custom tweaks
        self.explorer.setSortingEnabled(True)
        self.explorer.setMinimumWidth(400)
        self.explorer.setContextMenuPolicy(Qt.CustomContextMenu)
        self.resize(1500, 900)

        # Enabling renaming on context menu
        self.model.setReadOnly(False)
        self.explorer.setEditTriggers(self.explorer.NoEditTriggers)

        # Change default explorers column width
        self.header = self.explorer.header()
        self.header.setSectionResizeMode(0, QHeaderView.Interactive)
        self.header.setDefaultSectionSize(200)
        self.header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
        self.header.setSectionResizeMode(2, QHeaderView.ResizeToContents)

        # Setting editor as central widget
        self.setCentralWidget(self.editor)
        self.addDockWidget(Qt.LeftDockWidgetArea, dockWidget)

        # Setting view as widget of dock widget
        dockWidget.setWidget(self.explorer)
        dockWidget.setFloating(False)

        ### Double & right click
        self.explorer.doubleClicked.connect(self.custom_menu.on_double_click)
        self.explorer.customContextMenuRequested.connect(
            self.custom_menu.context_menu)

    def closeEvent(self, e):
        """This function prevents from closing without saving,
		 it works with the "Close" event"""

        if not self.editor.document().isModified():
            return
        answer = self.message.ask_for_confirmation()
        if answer == QMessageBox.Save:
            if not self.menu.save():
                e.ignore()
        elif answer == QMessageBox.Cancel:
            e.ignore()
예제 #26
0
class FileTreeView(QWidget):
    on_menu_select = pyqtSignal(str, str)
    on_dir_change = pyqtSignal()

    def __init__(self, parent: Application):
        super().__init__()
        self.stopped = threading.Event()
        self.tree = QTreeView()
        self.handle = self.Handler(self)
        self.get_data_file()
        self.model = QtGui.QStandardItemModel()
        self.item_construct = {}

        v_box = QVBoxLayout()
        v_box.addWidget(self.finder())
        v_box.addWidget(self.tree_view())
        self.setLayout(v_box)
        self.watch_dog()
        parent.app_close.connect(self.exit_push)

        self.tree.setAlternatingRowColors(True)

    def exit_push(self):
        self.stopped.set()

    def finder(self):
        w_find = QLineEdit()
        w_find.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed))
        w_find.textChanged.connect(self.sort_list)
        w_find.setPlaceholderText("Search file..")

        return w_find

    def sort_list(self, text):
        list_sort = []
        if text == "":
            self.show_tree(self.list_file)
        else:
            for data in self.list_file:
                if text.lower() in data.name().lower():
                    list_sort.append(data)
            self.show_tree(list_sort)

    def watch_dog(self):
        watch = ProcessRunnable(target=watch_winform,
                                args=(get_data_folder(), self.handle,
                                      self.stopped))
        watch.start()

    class Handler(watchdog.events.PatternMatchingEventHandler):
        def __init__(self, parent):
            super().__init__()
            self.parent = parent

        def on_created(self, event):
            print("Watchdog received created event", event.src_path, sep=" : ")
            asyncio.run(
                self.parent.file_change('create', event.src_path,
                                        event.is_directory))

        def on_modified(self, event):
            print("Watchdog received modified event",
                  event.src_path,
                  sep=" : ")
            asyncio.run(
                self.parent.file_change('modify', event.src_path,
                                        event.is_directory))

        def on_moved(self, event):
            print("Watchdog received move event",
                  event.src_path,
                  event.dest_path,
                  sep=" : ")
            asyncio.run(
                self.parent.file_change('move', event.src_path,
                                        event.is_directory, event.dest_path))

        def on_deleted(self, event):
            print("Watchdog received delete event", event.src_path, sep=" : ")
            asyncio.run(
                self.parent.file_change('delete', event.src_path,
                                        event.is_directory))

    async def file_change(self, tpe, old, is_directory, new=""):
        if tpe == "move":
            self.import_single(self.model.invisibleRootItem(), new)
            self.remove_single(old)
        elif tpe == "delete":
            self.remove_single(old)
        elif tpe == "create":
            self.import_single(self.model.invisibleRootItem(), old)

        if is_directory:
            self.on_dir_change.emit()

    def get_data_file(self):
        self.list_file = []
        self.create_list(get_data_folder())

    def create_list(self, dir):
        lst = os.listdir(path=dir)
        for f in lst:
            path = os.path.join(dir, f)
            file = MyFile()
            if os.path.isdir(path):
                file.setParentName(os.path.basename(dir))
                file.setParent(dir)
                file.setName(f)
                file.setDir(True)
                self.list_file.append(file)
                self.create_list(path)
            else:
                file.setParentName(os.path.basename(dir))
                file.setParent(dir)
                file.setName(f)
                file.setDir(False)
                self.list_file.append(file)

    def tree_view(self):
        self.tree.setSizePolicy(
            QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding))

        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.open_menu)
        self.tree.doubleClicked.connect(self.open_event)
        # self.model.itemChanged.connect(self.data_change)
        self.tree.setModel(self.model)
        self.show_tree(self.list_file)
        return self.tree

    def show_tree(self, list_file):
        self.model.clear()
        self.model.setHorizontalHeaderLabels(['List API'])
        self.tree.header().setDefaultSectionSize(180)
        parent = self.model.invisibleRootItem()
        self.item_construct = {}
        self.import_data(parent, list_file)
        self.tree.expandAll()

    def import_data_by_path(self, parent, path: list, index):
        if index < len(path):
            full = os.sep.join(path[:index + 1])
            if full in self.item_construct:
                item = self.item_construct[full]
            else:
                item = QStandardItem(path[index])
                if os.path.isfile(os.path.join(get_data_folder(), full)):
                    item.setToolTip(
                        self.read_description(
                            os.path.join(get_data_folder(), full)))
                item.setEditable(False)
                if not os.path.isdir(os.path.join(get_data_folder(), full)):
                    item.setIcon(QIcon(get_icon_link("text_snippet.svg")))
                else:
                    item.setIcon(QIcon(get_icon_link("folder_yellow.svg")))

                item.setData(full)
                parent.appendRow(item)
                self.item_construct[full] = item

            self.import_data_by_path(item, path, index + 1)

    def read_description(self, path):
        try:
            data = json.loads(open(path, encoding='utf-8').read())
            json_data = APIData()
            json_data.construct(data)
            x = json_data.parseSave().description()
            if x.isspace() or x == "":
                return ".."
            else:
                return x
        except Exception as ex:
            print(ex)
            return ".."

    def import_data(self, parent, list_data):
        for i in list_data:
            self.import_single(parent, i)

    def import_single(self, parent, file_path):
        path = self.path_extract(file_path)
        self.import_data_by_path(parent, path, 0)

    def remove_single(self, file_path):
        path = self.path_extract(file_path)
        full = os.sep.join(path[:len(path)])
        if full in self.item_construct:
            item = self.item_construct[full]
            (item.parent()
             or self.model.invisibleRootItem()).removeRow(item.row())
            del self.item_construct[full]

    def path_extract(self, file_path):
        if isinstance(file_path, MyFile):
            path = os.path.join(file_path.parent(), file_path.name())
        else:
            path = file_path

        path = path.replace(get_data_folder(), "")
        if path.startswith(os.sep):
            path = path.replace(os.sep, "", 1)
        path = path.split(os.sep)
        return path

    def open_menu(self, position):
        indexes = self.tree.selectedIndexes()
        level = 0
        data = ""
        item = None
        if len(indexes) > 0:
            index = indexes[0]
            item = self.model.itemFromIndex(index)
            data = item.data()
            data = os.path.join(get_data_folder(), data)
            if os.path.isdir(data):
                level = 1
            else:
                level = 2

        menu = QMenu()
        menu.setStyleSheet(open(get_stylesheet()).read())

        rename_action = QAction(QIcon(get_icon_link('edit.svg')), '&Rename',
                                self)
        rename_action.setStatusTip('Rename')

        new_action = QAction(QIcon(get_icon_link('create_new_folder.svg')),
                             '&New Folder', self)
        new_action.setStatusTip('New Folder')

        refresh_action = QAction(QIcon(get_icon_link('refresh.svg')),
                                 '&Refresh', self)
        refresh_action.setStatusTip('Refresh')

        delete_action = QAction(QIcon(get_icon_link('delete_forever.svg')),
                                '&Delete', self)
        delete_action.setStatusTip('Delete')

        open_action = QAction(QIcon(get_icon_link('open_in_new.svg')), '&Open',
                              self)
        open_action.setStatusTip('Open file')

        expand_action = QAction(QIcon(), '&Expand', self)
        expand_action.setStatusTip('Expand')

        collapse_action = QAction(QIcon(), '&Collapse', self)
        collapse_action.setStatusTip('Collapse')

        duplicate_action = QAction(QIcon(get_icon_link('content_copy.svg')),
                                   '&Duplicate', self)
        duplicate_action.setStatusTip('Duplicate')

        copy_action = QAction(QIcon(get_icon_link('content_copy.svg')),
                              '&Copy', self)
        copy_action.setStatusTip('Copy')

        move_action = QAction(QIcon(get_icon_link('zoom_out_map.svg')),
                              '&Move', self)
        move_action.setStatusTip('Move')

        if level == 1:
            menu.addAction(rename_action)
            menu.addAction(new_action)
            menu.addSeparator()
            menu.addAction(refresh_action)
            menu.addAction(expand_action)
            menu.addAction(collapse_action)
            menu.addSeparator()
            menu.addAction(delete_action)
        elif level == 2:
            menu.addAction(open_action)
            menu.addAction(new_action)
            menu.addAction(refresh_action)
            menu.addSeparator()
            menu.addAction(rename_action)
            menu.addAction(duplicate_action)
            menu.addAction(copy_action)
            menu.addAction(move_action)
            menu.addSeparator()
            menu.addAction(delete_action)
        else:
            menu.addAction(new_action)
            menu.addAction(refresh_action)

        action = menu.exec_(self.tree.viewport().mapToGlobal(position))

        if action == open_action:
            if data != "":
                self.on_menu_select.emit("open", data)
        elif action == refresh_action:
            self.get_data_file()
            self.show_tree(self.list_file)
        elif action == expand_action:
            if item is not None:
                self.tree.expand(item.index())
        elif action == collapse_action:
            if item is not None:
                self.tree.collapse(item.index())
        elif action == delete_action:
            if data != "":
                msg = QMessageBox()
                msg.setStyleSheet(open(get_stylesheet()).read())
                msg.setIcon(QMessageBox.Warning)
                msg.setBaseSize(QSize(500, 300))
                msg.setText("Delete file.")
                msg.setInformativeText("Are you sure to detele " +
                                       os.path.basename(data) + "?")
                msg.setWindowTitle("Delete Warning!!!")
                msg.addButton('Delete', QMessageBox.YesRole)
                msg.addButton('Move to Trash', QMessageBox.YesRole)
                msg.addButton('Cancel', QMessageBox.NoRole)

                rs = msg.exec_()
                if rs == 0:
                    if os.path.isdir(data):
                        shutil.rmtree(data)
                    else:
                        os.remove(data)
                elif rs == 1:
                    send2trash(data)
        elif action == new_action:
            if data == "":
                data = get_data_folder()
            # input_name = QInputDialog()
            # input_name.setStyleSheet(open(get_stylesheet()).read())
            # text, ok = input_name.getText(self, 'New Folder', 'Folder name:')
            inp = QComboDialog('New Folder', 'Folder name:', QComboDialog.Text)
            ok = inp.exec_()
            if ok and inp.select:
                if os.path.isdir(data):
                    try:
                        os.mkdir(os.path.join(data, inp.select))
                    except Exception as ex:
                        alert = Alert("Error", "Create folder error", str(ex))
                        alert.exec_()
                        print(ex)
                else:
                    new = os.path.join(os.path.dirname(data), inp.select)
                    try:
                        os.mkdir(new)
                    except Exception as ex:
                        alert = Alert("Error", "Create folder error", str(ex))
                        alert.exec_()
                        print(ex)
        elif action == rename_action:
            if data != "":
                # input_name = QInputDialog()
                # input_name.setStyleSheet(open(get_stylesheet()).read())
                # text, ok = input_name.getText(self, 'Rename file', 'New name:')
                inp = QComboDialog('Rename file', 'New name:',
                                   QComboDialog.Text)
                ok = inp.exec_()
                if ok and inp.select:
                    if os.path.isdir(data):
                        new = os.path.join(os.path.dirname(data), inp.select)
                        try:
                            os.rename(data, new)
                        except Exception as ex:
                            alert = Alert("Error", "Rename folder error",
                                          str(ex))
                            alert.exec_()
                            print(ex)
                    else:
                        filename, file_extension = os.path.splitext(data)
                        new = os.path.join(os.path.dirname(data),
                                           inp.select + file_extension)
                        try:
                            os.rename(data, new)
                        except Exception as ex:
                            alert = Alert("Error", "Rename file error",
                                          str(ex))
                            alert.exec_()
                            print(ex)
        elif action == move_action:
            if data != "":
                items = get_list_folder(get_data_folder(), get_data_folder())
                #
                # item, ok = QInputDialog.getItem(self, "Select folder dialog",
                #                                 "Select the destination folder", items, 0, False)

                inp = QComboDialog("Move", "Select the destination folder",
                                   QComboDialog.ComboBox, items)
                ok = inp.exec_()

                if ok and inp.select:
                    folder = inp.select
                    if inp.select.startswith(os.sep):
                        folder = inp.select.replace(os.sep, "", 1)
                    new = os.path.join(get_data_folder(), folder,
                                       os.path.basename(data))
                    try:
                        os.rename(data, new)
                    except Exception as ex:
                        alert = Alert("Error", "Move file error", str(ex))
                        alert.exec_()
                        print(ex)
        elif action == duplicate_action:
            if data != "":
                # input_name = QInputDialog()
                # input_name.setStyleSheet(open(get_stylesheet()).read())
                # text, ok = input_name.getText(self, 'Duplicate file', 'New name:')
                inp = QComboDialog('Duplicate file', 'New name:',
                                   QComboDialog.Text)
                filename, file_extension = os.path.splitext(data)
                inp.set_init_text(filename)
                ok = inp.exec_()
                if ok and inp.select:
                    new = os.path.join(os.path.dirname(data),
                                       inp.select + file_extension)
                    try:
                        copyfile(data, new)
                    except Exception as ex:
                        alert = Alert("Error", "Duplicate file error", str(ex))
                        alert.exec_()
                        print(ex)
        elif action == copy_action:
            if data != "":
                items = get_list_folder(get_data_folder(), get_data_folder())
                inp = QComboDialog("Copy", "Select the destination folder",
                                   QComboDialog.ComboBox, items)
                ok = inp.exec_()
                # item, ok = QInputDialog.getItem(self, "Select folder dialog",
                #                                 "Select the destination folder", items, 0, False)

                if ok and inp.select:
                    folder = inp.select
                    if inp.select.startswith(os.sep):
                        folder = inp.select.replace(os.sep, "", 1)
                    new = os.path.join(get_data_folder(), folder,
                                       os.path.basename(data))
                    try:
                        copyfile(data, new)
                    except Exception as ex:
                        alert = Alert("Error", "Copy file error", str(ex))
                        alert.exec_()
                        print(ex)

    def open_event(self, index):
        item = self.model.itemFromIndex(index)
        if item is not None:
            data = item.data()
            data = os.path.join(get_data_folder(), data)
            if os.path.isdir(data):
                level = 1
            else:
                level = 2
            if data != "":
                if level == 2:
                    self.on_menu_select.emit("open", data)
                elif level == 1:
                    if not self.tree.isExpanded(item.index()):
                        self.tree.collapse(item.index())
                    else:
                        self.tree.expand(item.index())
예제 #27
0
class AssetUploader(QMainWindow):
    def __init__(self):
        super().__init__()

        # Setup Box interface
        self.keyPath = Path("./authfiles")
        self.keyName = "appauth.json"
        self.box = None
        if os.path.isdir(str(self.keyPath)):
            if os.path.exists(str(self.keyPath / self.keyName)):
                self.box = BoxInterface.BoxInterface(
                    str(self.keyPath / self.keyName))
        else:
            os.makedirs(str(self.keyPath))

        if self.box is None:
            auth = AuthWindowDialog(self)
            choice = auth.exec_()  # blocks while dialog open
            if choice == 0:  # cancelled
                sys.exit()
            else:
                if auth.importKey:
                    copy(auth.path, (self.keyPath / self.keyName))
                    self.box = BoxInterface.BoxInterface(self.keyPath /
                                                         self.keyName)
                else:
                    self.box = BoxInterface.BoxInterface(auth.path)

        # Icons
        self.folderIcon = self.style().standardIcon(
            getattr(QStyle, 'SP_DirClosedIcon'))
        self.fileIcon = self.style().standardIcon(
            getattr(QStyle, 'SP_FileIcon'))
        self.windowIcon = self.style().standardIcon(
            getattr(QStyle, 'SP_ArrowUp'))

        self.title = ("Asset Uploader")

        self.width = 640
        self.height = 240

        self.initUI()

    def initUI(self):
        # Title/icon
        self.setWindowTitle(self.title)

        # Menu bar
        menu = self.menuBar()
        utilMenu = menu.addMenu('File')
        refreshButton = QAction('Refresh', self)
        utilMenu.addAction(refreshButton)
        refreshButton.triggered.connect(self.rebuildModel)

        # Status bar w/ logging
        statusBarHandler = StatusBarLogger(self)
        logging.getLogger().addHandler(statusBarHandler)
        logging.getLogger().setLevel(logging.WARNING)

        # Icons
        self.setWindowIcon(self.windowIcon)

        # Tree view
        self.treeView = QTreeView()
        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.treeView.setHeaderHidden(True)

        self.model = QStandardItemModel()
        self.model.dataChanged.connect(self.updateIndex)
        self.rebuildModel()

        self.treeView.setModel(self.model)

        # Add all to main layout
        mainWidget = QWidget(self)
        mainLayout = QVBoxLayout(mainWidget)
        self.setCentralWidget(mainWidget)
        mainLayout.addWidget(self.treeView)

        # Communication
        self.treeView.customContextMenuRequested.connect(self.contextMenu)

        self.show()

    def buildFileTree(self, root):
        self.model.dataChanged.disconnect(self.updateIndex)
        self.__recursBuildFileTree(root)
        self.model.dataChanged.connect(self.updateIndex)

    def __recursBuildFileTree(self, root):
        for item in self.box.get_folder_contents(root.boxData):
            new = BoxItem.BoxItem(self.box, item)
            root.appendRow(new)
            if item['type'] == "folder":
                new.setIcon(self.folderIcon)
                self.__recursBuildFileTree(new)
            else:
                new.setIcon(self.fileIcon)
        self.treeView.setSortingEnabled(True)

    def rebuildModel(self):
        self.model.clear()
        root = BoxItem.BoxItem(self.box, self.box.get_folder('0'))
        root.setIcon(self.folderIcon)
        root.setIcon(self.folderIcon)
        self.model.appendRow(root)
        self.buildFileTree(root)

    def updateIndex(self, index):
        modified = index.model().itemFromIndex(index)
        parent = modified.parent()
        if parent is None:  # Must be the root that was modified
            parent = modified
        parent.removeRows(0, parent.rowCount())
        self.buildFileTree(parent)
        self.treeView

    def contextMenu(self, position):
        index = self.treeView.selectedIndexes()[0]
        selected = index.model().itemFromIndex(index)
        selected.showContextMenu(self.treeView, position)
class LogInspectorWindow(QMainWindow):
    def __init__(self, configFilePath, parent=None):
        super(LogInspectorWindow, self).__init__(parent)
        self.initMatPlotLib()
        self.configFilePath = configFilePath

        folder = os.path.dirname(self.configFilePath)
        if not os.path.exists(folder):
            os.makedirs(folder)

        if os.path.exists(self.configFilePath):
            # config.yaml found.  Read from file.
            file = open(self.configFilePath, 'r')
            self.config = yaml.safe_load(file)
            file.close()
        else:
            # config.yaml not found.  Create new file.
            self.config = {}
            self.config['logs_directory'] = os.path.join(
                os.path.expanduser("~"), "Documents", "Inertial_Sense", "Logs")
            self.config['directory'] = ""
            self.config['serials'] = ["ALL"]
            file = open(self.configFilePath, 'w')
            yaml.dump(self.config, file)
            file.close()

        self.currently_selected = 'posNEDMap'
        self.downsample = 5
        self.plotargs = None
        self.log = None
        self.plotter = None

    def initMatPlotLib(self):
        self.figure = plt.figure()
        self.canvas = FigureCanvas(self.figure)
        self.toolbar = NavigationToolbar(self.canvas, self)
        self.figure.subplots_adjust(left=0.05,
                                    right=0.99,
                                    bottom=0.05,
                                    top=0.91,
                                    wspace=0.2,
                                    hspace=0.2)

    def addButton(self, name, function, multithreaded=True, layout=None):
        setattr(self, name + "button", QPushButton(name))
        # if multithreaded:
        # setattr(self, name+"buttonThread", Thread(target=function))
        # getattr(self, name+"button").pressed.connect(self.startLoadingIndicator)
        # getattr(self, name+"button").released.connect(getattr(self, name+"buttonThread").start)
        # else:
        getattr(self, name + "button").clicked.connect(function)
        # getattr(self, name + "button").setMinimumWidth(220)
        if layout is None:
            if self.buttonLayoutRightCol.count(
            ) < self.buttonLayoutMiddleCol.count():
                self.buttonLayoutRightCol.addWidget(
                    getattr(self, name + "button"))
            elif self.buttonLayoutMiddleCol.count(
            ) < self.buttonLayoutLeftCol.count():
                self.buttonLayoutMiddleCol.addWidget(
                    getattr(self, name + "button"))
            else:
                self.buttonLayoutLeftCol.addWidget(
                    getattr(self, name + "button"))
        else:
            layout.addWidget(getattr(self, name + "button"))

    def updatePlot(self):
        self.plot(self.currently_selected, self.plotargs)

    def updateWindowTitle(self):
        if np.shape(self.log.data[0, DID_DEV_INFO])[0] != 0:
            info = self.log.data[0, DID_DEV_INFO][0]
            infoStr = 'SN' + str(info[1]) + ', H:' + verArrayToString(
                info[2]) + ', F:' + verArrayToString(
                    info[3]) + ' build ' + str(
                        info[4]) + ', ' + dateTimeArrayToString(
                            info[8], info[9]) + ', ' + info[10].decode('UTF-8')
            self.setWindowTitle("LogInspector  -  " + infoStr)

    def choose_directory(self):
        log_dir = config['logs_directory']
        directory = QFileDialog.getExistingDirectory(
            parent=self, caption='Choose Log Directory', directory=log_dir)

        if directory != '':
            try:
                self.load(directory)
            except Exception as e:
                msg = QMessageBox()
                msg.setIcon(QMessageBox.Critical)
                msg.setText("Unable to load log: " + e.__str__())
                msg.setDetailedText(traceback.format_exc())
                msg.exec()

    def load(self, directory):
        print("loading files from " + directory)
        # if self.log is None:
        self.log = Log()
        self.log.load(directory)
        print("done loading")
        self.plotter = logPlot(False, False, 'svg', self.log)
        self.plotter.setDownSample(self.downsample)
        # str = ''
        # if self.log.navMode:
        #     str += 'NAV '
        # if self.log.rtk:
        #     str += 'RTK '
        # if self.log.compassing:
        #     str += 'Comp '
        # self.statusLabel.setText(str)
        self.updatePlot()
        self.updateWindowTitle()
        self.stopLoadingIndicator()

    def setupUi(self):
        self.setObjectName("LogInspector")
        self.setWindowTitle("LogInspector")
        self.resize(1280, 900)
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowSystemMenuHint
                            | QtCore.Qt.WindowMinMaxButtonsHint)
        self.setWindowIcon(QIcon("assets/Magnifying_glass_icon.png"))

        # MainWindow.showMaximized()

        self.createFileTree()
        self.createButtonColumn()
        self.formatButtonColumn()
        self.createBottomToolbar()

        self.figureLayout = QVBoxLayout()
        self.figureLayout.addWidget(self.canvas)
        self.figureLayout.addLayout(self.toolLayout)
        self.figureLayout.setStretchFactor(self.canvas, 1)

        layout = QHBoxLayout()
        layout.addLayout(self.controlLayout)
        layout.addLayout(self.figureLayout)
        layout.setStretch(1, 1)
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)
        # self.resize(1280, 900)
        self.resize(1450, 1000)
        self.setAcceptDrops(True)

    def createButtonColumn(self):
        self.controlLayout = QVBoxLayout()
        self.buttonLayoutLeftCol = QVBoxLayout()
        self.buttonLayoutMiddleCol = QVBoxLayout()
        self.buttonLayoutRightCol = QVBoxLayout()
        self.addButton('Pos NED Map', lambda: self.plot('posNEDMap'))
        self.addButton('Pos NED', lambda: self.plot('posNED'))
        self.addButton('Pos LLA', lambda: self.plot('posLLA'))
        self.addButton('GPS LLA', lambda: self.plot('llaGps'))
        self.addButton('Vel NED', lambda: self.plot('velNED'))
        self.addButton('Vel UVW', lambda: self.plot('velUVW'))
        self.addButton('Attitude', lambda: self.plot('attitude'))
        self.addButton('Heading', lambda: self.plot('heading'))
        self.addButton('INS Status', lambda: self.plot('insStatus'))
        self.addButton('HDW Status', lambda: self.plot('hdwStatus'))
        self.addButton('GPS 1 Stats', lambda: self.plot('gpsStats'))
        self.addButton('GPS 2 Stats', lambda: self.plot('gps2Stats'))
        self.addButton('RTK Pos Stats', lambda: self.plot('rtkPosStats'))
        self.addButton('RTK Cmp Stats', lambda: self.plot('rtkCmpStats'))
        self.addButton('Flash Config', lambda: self.showFlashConfig())
        self.addButton('Device Info', lambda: self.showDeviceInfo())
        self.addButton('IMU PQR', lambda: self.plot('imuPQR'))
        self.addButton('IMU Accel', lambda: self.plot('imuAcc'))
        self.addButton('PSD PQR', lambda: self.plot('gyroPSD'))
        self.addButton('PSD Accel', lambda: self.plot('accelPSD'))
        self.addButton('Magnetometer', lambda: self.plot('magnetometer'))
        self.addButton('Temp', lambda: self.plot('temp'))

    def formatButtonColumn(self):
        self.buttonLayoutLeftCol.setAlignment(QtCore.Qt.AlignTop)
        self.buttonLayoutMiddleCol.setAlignment(QtCore.Qt.AlignTop)
        self.buttonLayoutRightCol.setAlignment(QtCore.Qt.AlignTop)
        self.buttonColumnLayout = QHBoxLayout()
        self.buttonColumnLayout.addLayout(self.buttonLayoutLeftCol)
        self.buttonColumnLayout.addLayout(self.buttonLayoutMiddleCol)
        self.buttonColumnLayout.addLayout(self.buttonLayoutRightCol)
        self.controlLayout.addLayout(self.buttonColumnLayout)
        self.controlDirLayout = QHBoxLayout()
        self.controlDirLayout.addWidget(self.dirLineEdit)
        self.controlLayout.addLayout(self.controlDirLayout)
        self.controlLayout.addWidget(self.fileTree)
        # self.buttonLayout.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
        # self.addButton('load', self.choose_directory, multithreaded=False)

    def createBottomToolbar(self):
        self.toolLayout = QHBoxLayout()
        self.toolLayout.addWidget(self.toolbar)

        self.loadingIndictator = QLabel()
        self.loadingMovie = QMovie('assets/loader.gif')
        self.emptyLoadingPicture = QPicture()
        self.emptyLoadingPicture.load('assets/empty_loader.png')
        self.stopLoadingIndicator()
        self.toolLayout.addWidget(self.loadingIndictator)

        self.toolLayout.addItem(
            QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
        # self.toolLayout.addWidget(QSpacerItem(150, 10, QSizePolicy.Expanding))

        self.copyImagePushButton = QPushButton()
        # self.copyImagePushButton.setText("Copy")
        # self.copyImagePushButton.setMinimumWidth(1)
        # self.copyImagePushButton.style().standardIcon(QStyle.SP_DialogOpenButton)
        self.copyImagePushButton.setIcon(self.style().standardIcon(
            QStyle.SP_DialogSaveButton))
        self.toolLayout.addWidget(self.copyImagePushButton)
        self.copyImagePushButton.clicked.connect(self.copyPlotToClipboard)

        downsampleLabel = QLabel()
        downsampleLabel.setText("DS")
        self.downSampleInput = QSpinBox()
        self.downSampleInput.setValue(self.downsample)
        self.toolLayout.addWidget(downsampleLabel)
        self.toolLayout.addWidget(self.downSampleInput)
        self.downSampleInput.valueChanged.connect(self.changeDownSample)

        self.statusLabel = QLabel()
        self.toolLayout.addWidget(self.statusLabel)

    def changeDownSample(self, val):
        self.downsample = val
        self.plotter.setDownSample(self.downsample)
        self.updatePlot()

    def copyPlotToClipboard(self):
        # pixmap = QPixmap.grabWidget(self.canvas)
        # QApplication.clipboard().setPixmap(pixmap)
        # pixmap.save('test.png')

        # store the image in a buffer using savefig(), this has the
        # advantage of applying all the default savefig parameters
        # such as background color; those would be ignored if you simply
        # grab the canvas using Qt
        buf = io.BytesIO()
        self.figure.savefig(buf)

        QApplication.clipboard().setImage(QImage.fromData(buf.getvalue()))
        buf.close()

    def startLoadingIndicator(self):
        self.loadingIndictator.setMovie(self.loadingMovie)
        self.loadingMovie.start()

    def dragEnterEvent(self, e):
        if (e.mimeData().hasUrls()):
            e.acceptProposedAction()

    def dropEvent(self, e):
        try:
            directory = e.mimeData().urls()[0].toLocalFile()
            self.load(directory)
        except Exception as e:
            self.showError(e)

    def showError(self, e):
        msg = QMessageBox()
        msg.setIcon(QMessageBox.Critical)
        msg.setText("Unable to load log: " + e.__str__())
        msg.setDetailedText(traceback.format_exc())
        msg.exec()

    def createFileTree(self):
        self.dirModel = QFileSystemModel()
        self.dirModel.setRootPath(self.config["logs_directory"])
        self.dirModel.setFilter(QtCore.QDir.Dirs | QtCore.QDir.NoDotAndDotDot)
        self.dirLineEdit = QLineEdit()
        self.dirLineEdit.setText(self.config["logs_directory"])
        self.dirLineEdit.setFixedHeight(25)
        self.dirLineEdit.returnPressed.connect(self.handleTreeDirChange)
        self.fileTree = QTreeView()
        self.fileTree.setModel(self.dirModel)
        self.fileTree.setRootIndex(
            self.dirModel.index(self.config['logs_directory']))
        self.fileTree.setColumnHidden(1, True)
        self.fileTree.setColumnHidden(2, True)
        self.fileTree.setColumnHidden(3, True)
        self.fileTree.setMinimumWidth(300)
        self.fileTree.clicked.connect(self.handleTreeViewClick)
        self.fileTree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.fileTree.setSelectionMode(QAbstractItemView.SingleSelection)
        self.fileTree.customContextMenuRequested.connect(
            self.handleTreeViewRightClick)
        # self.populateRMSCheck(self.config['logs_directory'])

    def updateFileTree(self):
        self.dirModel.setRootPath(self.config["logs_directory"])
        self.fileTree.setRootIndex(
            self.dirModel.index(self.config['logs_directory']))

    def populateRMSCheck(self, directory):
        for subdir in os.listdir(directory):
            path = os.path.join(directory, subdir)
            if os.path.isdir(path):
                self.populateRMSCheck(path)
            elif 'RMS' in subdir:
                f = open(path)
                rms_report = f.read()
                p = re.compile(r'(?<=^PASS/FAIL).*\n', re.M)
                line = re.search(p, rms_report).group()
                failed = True if "FAIL" in line else False
                if failed:
                    pass
                else:
                    pass

    def handleTreeDirChange(self):
        self.config["logs_directory"] = self.dirLineEdit.text()
        self.updateFileTree()

        file = open(self.configFilePath, 'w')
        yaml.dump(self.config, file)
        file.close()

    def handleTreeViewClick(self):
        selected_directory = self.fileTree.model().filePath(
            self.fileTree.selectedIndexes()[0])
        for fname in os.listdir(selected_directory):
            if fname.endswith('.dat'):
                try:
                    self.load(selected_directory)
                except Exception as e:
                    self.showError(e)
                break

    def handleTreeViewRightClick(self, event):
        selected_directory = os.path.normpath(self.fileTree.model().filePath(
            self.fileTree.selectedIndexes()[0]))
        menu = QMenu(self)
        copyAction = menu.addAction("Copy path")
        nppActionHot = menu.addAction("Run NPP, HOT start")
        nppActionCold = menu.addAction("Run NPP, COLD start")
        nppActionFactory = menu.addAction("Run NPP, FACTORY start")
        setDataInfoDirHotAction = menu.addAction(
            "Set dataInfo.json directory, HOT start")
        setDataInfoDirColdAction = menu.addAction(
            "Set dataInfo.json directory, COLD start")
        setDataInfoDirFactoryAction = menu.addAction(
            "Set dataInfo.json directory, FACTORY start")
        exploreAction = menu.addAction("Explore folder")
        cleanFolderAction = menu.addAction("Clean folder")
        deleteFolderAction = menu.addAction("Delete folder")
        action = menu.exec_(self.fileTree.viewport().mapToGlobal(event))
        if action == copyAction:
            cb = QApplication.clipboard()
            cb.clear(mode=cb.Clipboard)
            cb.setText(selected_directory, mode=cb.Clipboard)
        if action == nppActionHot:
            cleanFolder(selected_directory)
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_HOT)
            sys.path.insert(1, '../../../../python/src')
            from supernpp.supernpp import SuperNPP
            spp = SuperNPP(selected_directory, self.config['serials'])
            spp.run()
        if action == nppActionCold:
            cleanFolder(selected_directory)
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_COLD)
            sys.path.insert(1, '../../../../python/src')
            from supernpp.supernpp import SuperNPP
            spp = SuperNPP(selected_directory,
                           self.config['serials'],
                           startMode=START_MODE_COLD)
            spp.run()
        if action == nppActionFactory:
            cleanFolder(selected_directory)
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_FACTORY)
            sys.path.insert(1, '../../../../python/src')
            from supernpp.supernpp import SuperNPP
            spp = SuperNPP(selected_directory,
                           self.config['serials'],
                           startMode=START_MODE_FACTORY)
            spp.run()
        if action == setDataInfoDirHotAction:
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_HOT)
        if action == setDataInfoDirColdAction:
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_COLD)
        if action == setDataInfoDirFactoryAction:
            setDataInformationDirectory(selected_directory,
                                        startMode=START_MODE_FACTORY)
        if action == exploreAction:
            openFolderWithFileBrowser(selected_directory)
        if action == cleanFolderAction:
            cleanFolder(selected_directory)
        if action == deleteFolderAction:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Question)
            msg.setText("Are you sure you want to delete this folder?\n\n" +
                        selected_directory)
            msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
            result = msg.exec()
            if result == QMessageBox.Yes:
                removeDirectory(selected_directory)

    def stopLoadingIndicator(self):
        self.loadingMovie.stop()
        self.loadingIndictator.clear()
        self.loadingIndictator.setPicture(self.emptyLoadingPicture)

    def showDeviceInfo(self):
        dlg = DeviceInfoDialog(self.log, self)
        dlg.show()
        dlg.exec_()

    def showFlashConfig(self):
        dlg = FlashConfigDialog(self.log, self)
        dlg.show()
        dlg.exec_()

    def plot(self, func, args=None):
        print("plotting " + func)
        self.currently_selected = func
        self.plotargs = args

        self.figure.clear()

        if hasattr(self, 'plotter'):
            if args is not None:
                getattr(self.plotter, func)(*args, self.figure)
            else:
                getattr(self.plotter, func)(self.figure)

        self.canvas.draw()
        self.stopLoadingIndicator()
        print("done plotting")
예제 #29
0
class ProjectWidget(QWidget):
    """
    A widget for displaying & editing properties of objects etc.

    Also see the properties this likes to display:
    also see: supertux/property.py
    """
    def __init__(self, parent):
        super().__init__(parent)
        self.items = []
        self.addon = None

        self.vbox = QVBoxLayout()
        self.vbox.setSpacing(0)
        self.vbox.setContentsMargins(0, 0, 0, 0)

        self.heading_label = QLabel("No project")
        self.label = QLabel(
            "Create a project by selecting File > New > Project...")
        self.vbox.addWidget(self.heading_label)
        self.vbox.addWidget(self.label)
        self.setLayout(self.vbox)

    def init_gui(self):
        # Clear from previous:
        self.heading_label.setVisible(False)
        self.label.setVisible(False)

        self.toolbar = QToolBar()
        self.toolbar.setStyleSheet('QToolBar{spacing:0px;}')
        package_icon = QIcon("data/images/icons16/addon_package-16.png")
        add_icon = QIcon("data/images/supertux/plus.png")
        self.toolbar.addAction(package_icon, 'Package add-on...',
                               self.package_addon)
        self.toolbar.addAction(add_icon, "Add content...", self.add_content)

        self.tree_view = QTreeView()
        self.vbox.addWidget(self.toolbar)
        self.model = QFileSystemModel()
        # self.data = [
        #      ("SuperTux addon", [
        #          ("levels", []),
        #          ("images", []),
        #          ("sounds", []),
        #          ("music", []),
        #          ("scripts", []),
        #          ("metadata", [])
        #      ])]
        # self.model = QStandardItemModel()
        # self.add_items(self.model, self.data)
        self.tree_view.setModel(self.model)
        self.tree_view.doubleClicked.connect(self.on_tree_view_double_click)
        self.tree_view.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree_view.customContextMenuRequested.connect(self.on_context_menu)
        self.vbox.addWidget(self.tree_view)

        self.layout = QFormLayout()
        self.vbox.addLayout(self.layout)

        self.setLayout(self.vbox)
        self.setMinimumWidth(300)

        # Called in many cases. This should have functions connected
        # which cause the changes in the widget to be applied
        # Called by hitting "Apply", "Ok" or "Finish"
        self.call_signal = Signal()

        self.call_signal.connect(self.call_callbacks)

    def call_callbacks(self, *args):
        for item in self.items:
            if item.callback is not None:
                item.callback(item.get_value())

    def add_callback(self, callback):
        """Adds a callback to the callback signal"""
        self.call_signal.connect(callback)

    def add_items(self, parent, elements):
        for text, children in elements:
            item = QStandardItem(text)
            parent.appendRow(item)
            if children:
                self.add_items(item, children)

    def call(self):
        self.call_signal(*self.get_values())

    def on_tree_view_double_click(self, item):
        print("double-clicked!")

    def on_context_menu(self, position):

        menu = QMenu()
        menu.addAction(self.tr("Add image..."))
        menu.addAction(self.tr("Add sound..."))
        menu.addAction(self.tr("Add level..."))
        menu.addAction(self.tr("Add script..."))

        menu.exec_(self.tree_view.viewport().mapToGlobal(position))

    def set_project_directory(self, project_dir):
        self.tree_view.setRootIndex(self.model.setRootPath(project_dir))

    def set_addon(self, addon):
        self.addon = addon
        # We now have an add-on set, initialize the GUI
        self.init_gui()

    def package_addon(self):
        print("Package add-on!")

    def add_content(self):
        print("Add content to add-on!")
예제 #30
0
class main(QMainWindow, DiskCleaner_ui.Ui_MainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)

        self.openMenu()

        systray_icon = QIcon("DC.png")
        self.systray = QSystemTrayIcon(systray_icon, self)
        self.systray.setContextMenu(self.menu)
        self.systray.show()
        self.systray.showMessage("DC", "Started...",
                                 QSystemTrayIcon.Information)
        self.closeapp.triggered.connect(self.close)

        self.setWindowIcon(systray_icon)

        self.Duplicate()
        self.dormant()
        self.Temp()

    def closeEvent(self, event):
        reply = QMessageBox.question(self, 'Exit',
                                     "Are you sure you want to exit?",
                                     QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()

    def scan(self):
        if os.listdir(self.temp_path) or os.listdir(self.dormant_path) == []:
            self.systray.showMessage("DC", "No Temporary files..",
                                     QSystemTrayIcon.Information)
        else:
            pass

    def clear_Files(self):
        reply = QMessageBox.question(
            self, 'Clear Files', "Are you sure you want delete all files?",
            QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            try:
                self.scan()
                with os.scandir(self.temp_path) or os.scandir(
                        self.dormant_path) as entries:
                    for entry in entries:
                        if entry.is_file() or entry.is_symlink():
                            os.remove(entry.path)
                            self.systray.showMessage(
                                "DC", "Temporary files/folders cleared",
                                QSystemTrayIcon.Information)
                        elif entry.is_dir():
                            shutil.rmtree(entry.path)
                            self.systray.showMessage(
                                "DC", "Temporary files/folders cleared",
                                QSystemTrayIcon.Information)
                        else:
                            pass
            except Exception:
                pass

    def openMenu(self):
        self.menu = QMenu()
        self.restore = QAction("Restore", self)
        self.closeapp = QAction("Close", self)

        self.menu.addActions([self.restore, self.closeapp])

    def on_clicked(self, index):
        self.path = self.fileSystemModel.filePath(index)
        print(self.path)

    def zip_file(self):
        try:
            if os.path.exists(self.path):
                try:
                    dask = QFileDialog.getSaveFileName(
                        self, 'Select Folder to store Zip file',
                        expanduser("~"), '.zip')
                    dpath = str(dask[0])
                    if os.path.isfile(self.path):
                        with ZipFile(dpath, 'w') as zip:
                            zip.write(self.path, basename(self.path))

                            self.systray.showMessage(
                                "DC", "File Zipped succesfully",
                                QSystemTrayIcon.Information)
                    else:
                        zf = zipfile.ZipFile(dpath, "w")
                        for dirname, subdirs, files in os.walk(self.path):
                            zf.write(dirname, basename(dirname))
                            for filename in files:
                                zf.write(
                                    os.path.join(dirname, filename),
                                    basename(os.path.join(dirname, filename)))
                        zf.close()
                        self.systray.showMessage("DC",
                                                 "Folder Zipped Succesfully",
                                                 QSystemTrayIcon.Information)
                except Exception:
                    pass
            else:
                pass
        except Exception:
            pass

    def delete_file(self):
        reply = QMessageBox.question(self, 'Delete File',
                                     "Are you sure you want to delete file?",
                                     QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.Yes:
            try:
                if os.path.exists(self.path):
                    try:
                        os.remove(self.path)
                        self.systray.showMessage("DC",
                                                 "Temporary  file Deleted",
                                                 QSystemTrayIcon.Information)
                    except Exception:
                        shutil.rmtree(self.path)
                        self.systray.showMessage("DC",
                                                 "Temporary  folder Deleted",
                                                 QSystemTrayIcon.Information)
            except Exception:
                self.systray.showMessage("DC", "Error deleting file",
                                         QSystemTrayIcon.Critical)
        else:
            pass

    def tabMenu(self, position):

        self.tmenu = QMenu()

        self.open = self.tmenu.addAction('Open')
        self.open_file_location = self.tmenu.addAction('Open File Location')

        self.tmenu.addActions([self.open, self.open_file_location])
        action = self.tmenu.exec_(
            self.temp_treeView.viewport().mapToGlobal(position))

        if action == self.open:
            os.startfile(self.path, 'open')
        elif action == self.open_file_location:
            try:
                subprocess.Popen(r'explorer /select,' +
                                 "{}".format(self.path).replace('/', '\\'))
            except Exception:
                pass

    def Temp(self):
        self.temp_treeView = QTreeView()

        self.temp_treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.temp_treeView.customContextMenuRequested.connect(self.tabMenu)

        self.fileSystemModel = QFileSystemModel(self.temp_treeView)
        self.fileSystemModel.setReadOnly(False)
        self.temp_path = expanduser('~') + '\AppData\Local\Temp'
        root = self.fileSystemModel.setRootPath(self.temp_path)
        self.temp_treeView.setModel(self.fileSystemModel)
        self.temp_treeView.setRootIndex(root)
        self.temp_treeView.setSortingEnabled(True)

        self.temp_treeView.clicked.connect(self.on_clicked)

        self.clearAll_button = QPushButton("Clear all Files")
        self.clearAll_button.setFixedSize(90, 30)

        self.clearAll_button.clicked.connect(self.clear_Files)

        self.temp_delete_button = QPushButton('Delete')
        self.temp_delete_button.setFixedSize(90, 30)

        self.temp_delete_button.clicked.connect(self.delete_file)

        self.temp_zip_button = QPushButton('Zip file')
        self.temp_zip_button.setFixedSize(90, 30)

        self.temp_zip_button.clicked.connect(self.zip_file)

        Layout = QHBoxLayout(self)
        Layout.addWidget(self.clearAll_button)
        Layout.addWidget(self.temp_delete_button)
        Layout.addWidget(self.temp_zip_button)
        Layout.addWidget(self.temp_treeView)

        self.Temp_Tab.setLayout(Layout)

    def Duplicate(self):

        dup_button = QPushButton('Duplicate Finder')
        dup_button.setFixedSize(90, 30)

        Layout = QHBoxLayout(self)
        Layout.addWidget(dup_button)

        dup_button.clicked.connect(self.dup_finder)

        self.Duplicate_Tab.setLayout(Layout)

    def dup_finder(self):
        def chunk_reader(fobj, chunk_size=2048):
            """ Generator that reads a file in chunks of bytes """
            while True:
                chunk = fobj.read(chunk_size)
                if not chunk:
                    return
                yield chunk

        class SignalHelper(QObject):
            error = pyqtSignal(tuple)
            result = pyqtSignal(object)
            finished = pyqtSignal()
            progress = pyqtSignal(str, str)

        class Worker(QRunnable):
            def __init__(self, fn, *args, **kwargs):
                super(Worker, self).__init__()

                self.fn = fn
                self.args = args
                self.kwargs = kwargs
                self.signals = SignalHelper()

                # Add a callback to our kwargs
                kwargs['progress_callback'] = self.signals.progress

            @pyqtSlot()
            def run(self):
                try:
                    result = self.fn(*self.args, **self.kwargs)
                except:
                    traceback.print_exc()
                    exctype, value = sys.exc_info()[:2]
                    self.signals.error.emit(
                        (exctype, value, traceback.format_exc()))
                else:
                    self.signals.result.emit(result)
                finally:
                    self.signals.finished.emit()

        class MainWindow(QMainWindow):
            def __init__(self, *args, **kwargs):
                super(MainWindow, self).__init__(*args, **kwargs)

                self.setWindowTitle('Duplicate Finder')

                systray_icon = QIcon("s.png")
                self.systray = QSystemTrayIcon(systray_icon, self)
                self.systray.show()

                layout = QVBoxLayout()
                self.textEdit = QTextEdit("Display duplicate files :")
                self.textEdit.setReadOnly(True)

                self.b = QPushButton("Scan files")
                self.b.setCheckable(True)
                self.b.pressed.connect(self.watcher)

                self.d = QPushButton('Delete dup files')
                self.d.clicked.connect(self.delete_duplicate_files)

                layout.addWidget(self.textEdit)
                layout.addWidget(self.b)
                layout.addWidget(self.d)

                w = QWidget()
                w.setLayout(layout)
                self.setCentralWidget(w)

                self.threadpool = QThreadPool()

            def delete_duplicate_files(self):
                def remove_duplicates(dir, hashfun=hashlib.sha512):

                    unique = set()
                    for filename in os.listdir(dir):
                        filepath = os.path.join(dir, filename)
                        if os.path.isfile(filepath):
                            hashobj = hashfun()
                            for chunk in chunk_reader(open(filepath, 'rb')):
                                hashobj.update(chunk)
                                # the size of the hashobj is constant
                            hashfile = hashobj.hexdigest()
                            if hashfile not in unique:
                                unique.add(hashfile)
                            else:
                                try:
                                    os.remove(filepath)
                                    print('delete')

                                except Exception:
                                    print(' cant delete')

                try:
                    reply = QMessageBox.question(
                        self, 'Delete File',
                        "Are you sure you want to delete file?",
                        QMessageBox.Yes | QMessageBox.No)
                    if reply == QMessageBox.Yes:
                        hashfun = hashlib.sha256
                        remove_duplicates(self.path)
                        self.systray.showMessage("DC",
                                                 'Duplicate files deleted',
                                                 QSystemTrayIcon.Information)
                    else:
                        pass
                except IndexError:
                    self.systray.showMessage("DC",
                                             "Error deleting duplicate files",
                                             QSystemTrayIcon.Critical)

            def watcher(self):
                self.path = QFileDialog.getExistingDirectory(
                    self, 'select folder to scan', expanduser('~'))

                worker = Worker(self.check_for_duplicates)
                worker.signals.progress.connect(self.progress_fn)
                self.threadpool.start(worker)

            def progress_fn(self, duplicate, full_path):
                self.textEdit.append(
                    "<font color=red>Duplicate found:</font> %s <b>and</b> %s"
                    % (full_path, duplicate))
                self.textEdit.append("")
                self.textEdit.append("")

            def check_for_duplicates(self,
                                     progress_callback,
                                     hash=hashlib.sha1):
                # specify your path !!!
                hashes = {}
                for dirpath, dirnames, filenames in os.walk(self.path):
                    for filename in filenames:
                        full_path = os.path.join(dirpath,
                                                 filename).replace('/', '\\')
                        # print(full_path)
                        hashobj = hash()
                        for chunk in chunk_reader(open(full_path, 'rb')):
                            hashobj.update(chunk)
                        file_id = (hashobj.digest(),
                                   os.path.getsize(full_path))
                        self.duplicate = hashes.get(file_id, None)
                        if self.duplicate:
                            progress_callback.emit(self.duplicate, full_path)
                            print("Duplicate found: %s and %s" %
                                  (full_path, self.duplicate))
                            try:
                                print(self.duplicate)
                            except Exception:

                                print('could not delete')
                        else:
                            hashes[file_id] = full_path

                # return duplicate

        global w
        w = MainWindow()
        w.setGeometry(700, 200, 550, 300)
        w.show()

    def dormant(self):

        treeView = QTreeView()

        treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        treeView.customContextMenuRequested.connect(self.tabMenu)

        fileSystemModel = QFileSystemModel(treeView)
        fileSystemModel.setReadOnly(False)
        self.dormant_path = expanduser('~') + '\Documents'
        root = fileSystemModel.setRootPath(self.dormant_path)
        treeView.setModel(fileSystemModel)
        treeView.setRootIndex(root)
        treeView.setSortingEnabled(True)

        treeView.clicked.connect(self.on_clicked)

        dormant_clearAll_button = QPushButton("Clear all Files")
        dormant_clearAll_button.setFixedSize(90, 30)
        self.dormant_delete_button = QPushButton('Delete')
        self.dormant_delete_button.setFixedSize(90, 30)

        self.dormant_delete_button.clicked.connect(self.delete_file)

        dormant_zip_button = QPushButton('Zip file')
        dormant_zip_button.setFixedSize(90, 30)

        dormant_zip_button.clicked.connect(self.zip_file)

        Layout = QHBoxLayout(self)
        Layout.addWidget(dormant_clearAll_button)
        Layout.addWidget(self.dormant_delete_button)
        Layout.addWidget(dormant_zip_button)
        Layout.addWidget(treeView)

        #dormant_clearAll_button.clicked.connect(self.clear_Files())

        self.UnUsed_Tab.setLayout(Layout)
예제 #31
0
파일: __init__.py 프로젝트: Zubax/kucher
class RegisterViewWidget(WidgetBase):
    def __init__(self, parent: QWidget):
        super(RegisterViewWidget, self).__init__(parent)

        self._registers = []
        self._running_task: asyncio.Task = None

        self._visibility_selector = QComboBox(self)
        self._visibility_selector.addItem("Show all registers", lambda _: True)
        self._visibility_selector.addItem("Only configuration parameters",
                                          lambda r: r.mutable and r.persistent)

        # noinspection PyUnresolvedReferences
        self._visibility_selector.currentIndexChanged.connect(
            lambda _: self._on_visibility_changed())

        self._reset_selected_button = make_button(
            self,
            "Reset selected",
            icon_name="clear-symbol",
            tool_tip=f"Reset the currently selected registers to their default "
            f"values. The restored values will be committed "
            f"immediately. This function is available only if a "
            f"default value is defined. [{RESET_SELECTED_SHORTCUT}]",
            on_clicked=self._do_reset_selected,
        )

        self._reset_all_button = make_button(
            self,
            "Reset all",
            icon_name="skull-crossbones",
            tool_tip=f"Reset the all registers to their default "
            f"values. The restored values will be committed "
            f"immediately.",
            on_clicked=self._do_reset_all,
        )

        self._read_selected_button = make_button(
            self,
            "Read selected",
            icon_name="process",
            tool_tip=f"Read the currently selected registers only "
            f"[{READ_SELECTED_SHORTCUT}]",
            on_clicked=self._do_read_selected,
        )

        self._read_all_button = make_button(
            self,
            "Read all",
            icon_name="process-plus",
            tool_tip="Read all registers from the device",
            on_clicked=self._do_read_all,
        )

        self._export_button = make_button(
            self,
            "Export",
            icon_name="export",
            tool_tip="Export configuration parameters",
            on_clicked=self._do_export,
        )

        self._import_button = make_button(
            self,
            "Import",
            icon_name="import",
            tool_tip="Import configuration parameters",
            on_clicked=self._do_import,
        )

        self._expand_all_button = make_button(
            self,
            "",
            icon_name="expand-arrow",
            tool_tip="Expand all namespaces",
            on_clicked=lambda: self._tree.expandAll(),
        )

        self._collapse_all_button = make_button(
            self,
            "",
            icon_name="collapse-arrow",
            tool_tip="Collapse all namespaces",
            on_clicked=lambda: self._tree.collapseAll(),
        )

        self._status_display = QLabel(self)
        self._status_display.setWordWrap(True)

        self._reset_selected_button.setEnabled(False)
        self._reset_all_button.setEnabled(False)
        self._read_selected_button.setEnabled(False)
        self._read_all_button.setEnabled(False)
        self._export_button.setEnabled(False)
        self._import_button.setEnabled(False)

        self._tree = QTreeView(self)
        self._tree.setVerticalScrollMode(QTreeView.ScrollPerPixel)
        self._tree.setHorizontalScrollMode(QTreeView.ScrollPerPixel)
        self._tree.setAnimated(True)
        self._tree.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self._tree.setAlternatingRowColors(True)
        self._tree.setContextMenuPolicy(Qt.ActionsContextMenu)

        # Not sure about this one. This hardcoded value may look bad on some platforms.
        self._tree.setIndentation(20)

        def add_action(
            callback: typing.Callable[[], None],
            icon_name: str,
            name: str,
            shortcut: typing.Optional[str] = None,
        ):
            action = QAction(get_icon(icon_name), name, self)
            # noinspection PyUnresolvedReferences
            action.triggered.connect(callback)
            if shortcut:
                action.setShortcut(shortcut)
                action.setAutoRepeat(False)
                try:
                    action.setShortcutVisibleInContextMenu(True)
                except AttributeError:
                    pass  # This feature is not available in PyQt before 5.10

            self._tree.addAction(action)

        add_action(self._do_read_all, "process-plus", "Read all registers")
        add_action(
            self._do_read_selected,
            "process",
            "Read selected registers",
            READ_SELECTED_SHORTCUT,
        )
        add_action(
            self._do_reset_selected,
            "clear-symbol",
            "Reset selected to default",
            RESET_SELECTED_SHORTCUT,
        )

        self._tree.setItemDelegateForColumn(
            int(Model.ColumnIndices.VALUE),
            EditorDelegate(self._tree, self._display_status),
        )

        # It doesn't seem to be explicitly documented, but it seems to be necessary to select either top or bottom
        # decoration position in order to be able to use center alignment. Left or right positions do not work here.
        self._tree.setItemDelegateForColumn(
            int(Model.ColumnIndices.FLAGS),
            StyleOptionModifyingDelegate(
                self._tree,
                decoration_position=QStyleOptionViewItem.Top,  # Important
                decoration_alignment=Qt.AlignCenter,
            ),
        )

        header: QHeaderView = self._tree.header()
        header.setSectionResizeMode(QHeaderView.ResizeToContents)
        header.setStretchLastSection(
            False)  # Horizontal scroll bar doesn't work if this is enabled

        buttons_layout = QGridLayout()
        buttons_layout.addWidget(self._read_selected_button, 0, 0)
        buttons_layout.addWidget(self._reset_selected_button, 0, 2)
        buttons_layout.addWidget(self._read_all_button, 1, 0)
        buttons_layout.addWidget(self._reset_all_button, 1, 2)
        buttons_layout.addWidget(self._import_button, 2, 0)
        buttons_layout.addWidget(self._export_button, 2, 2)

        for col in range(3):
            buttons_layout.setColumnStretch(col, 1)

        layout = lay_out_vertically(
            (self._tree, 1),
            buttons_layout,
            lay_out_horizontally(
                self._visibility_selector,
                (None, 1),
                self._expand_all_button,
                self._collapse_all_button,
            ),
            self._status_display,
        )

        self.setLayout(layout)

    def reset(self):
        self.setup([])

    def setup(self, registers: typing.Iterable[Register]):
        self._registers = list(registers)
        self._on_visibility_changed()

    def _replace_model(
            self, register_visibility_predicate: typing.Callable[[Register],
                                                                 bool]):
        # Cancel all operations that might be pending on the old model
        self._cancel_task()

        old_model = self._tree.model()

        # Configure the new model
        filtered_registers = list(
            filter(register_visibility_predicate, self._registers))
        # It is important to set the Tree widget as the parent in order to let the widget take ownership
        new_model = Model(self._tree, filtered_registers)
        _logger.info("New model %r", new_model)
        self._tree.setModel(new_model)

        # The selection model is implicitly replaced when we replace the model, so it has to be reconfigured
        self._tree.selectionModel().selectionChanged.connect(
            lambda *_: self._on_selection_changed())

        # TODO: Something fishy is going on. Something keeps the old model alive when we're replacing it.
        #       We could call deleteLater() on it, but it seems dangerous, because if that something ever decided
        #       to refer to that dead model later for any reason, we'll get a rougue dangling pointer access on
        #       our hands. The horror!
        if old_model is not None:
            import gc

            model_referrers = gc.get_referrers(old_model)
            if len(model_referrers) > 1:
                _logger.warning(
                    "Extra references to the old model %r: %r",
                    old_model,
                    model_referrers,
                )

        # Update the widget - all root items are expanded by default
        for row in itertools.count():
            index = self._tree.model().index(row, 0)
            if not index.isValid():
                break

            self._tree.expand(index)

        self._reset_selected_button.setEnabled(False)
        self._read_selected_button.setEnabled(False)
        self._read_all_button.setEnabled(len(filtered_registers) > 0)
        self._reset_all_button.setEnabled(len(filtered_registers) > 0)
        self._export_button.setEnabled(len(filtered_registers) > 0)
        self._import_button.setEnabled(len(filtered_registers) > 0)

        self._display_status(f"{len(filtered_registers)} registers loaded")

    def _on_visibility_changed(self):
        self._replace_model(self._visibility_selector.currentData())

    def _on_selection_changed(self):
        selected = self._get_selected_registers()

        self._reset_selected_button.setEnabled(
            any(map(lambda r: r.has_default_value, selected)))
        self._read_selected_button.setEnabled(len(selected) > 0)

    def _do_read_selected(self):
        selected = self._get_selected_registers()
        if selected:
            self._read_specific(selected)
        else:
            self._display_status("No registers are selected, nothing to read")

    def _do_reset_selected(self):
        rv = {}
        for r in self._get_selected_registers():
            if r.has_default_value:
                rv[r] = r.default_value

        self._write_specific(rv)

    def _do_reset_all(self):
        rv = {}
        for r in self._registers:
            if r.has_default_value:
                rv[r] = r.default_value

        self._write_specific(rv)

    def _do_read_all(self):
        self._read_specific(self._tree.model().registers)

    def _do_import(self):
        import_registers(parent=self, registers=self._registers)

    def _do_export(self):
        export_registers(parent=self, registers=self._registers)

    def _read_specific(self, registers: typing.List[Register]):
        total_registers_read = None

        def progress_callback(register: Register, current_register_index: int,
                              total_registers: int):
            nonlocal total_registers_read
            total_registers_read = total_registers
            self._display_status(
                f"Reading {register.name!r} "
                f"({current_register_index + 1} of {total_registers})")

        async def executor():
            try:
                _logger.info("Reading registers: %r",
                             [r.name for r in registers])
                mod: Model = self._tree.model()
                await mod.read(registers=registers,
                               progress_callback=progress_callback)
            except asyncio.CancelledError:
                self._display_status(f"Read has been cancelled")
                raise
            except Exception as ex:
                _logger.exception("Register read failed")
                show_error("Read failed", "Could not read registers", repr(ex),
                           self)
                self._display_status(f"Could not read registers: {ex!r}")
            else:
                self._display_status(
                    f"{total_registers_read} registers have been read")

        self._cancel_task()
        self._running_task = asyncio.get_event_loop().create_task(executor())

    def _write_specific(self, register_value_mapping: typing.Dict[Register,
                                                                  typing.Any]):
        total_registers_assigned = None

        def progress_callback(register: Register, current_register_index: int,
                              total_registers: int):
            nonlocal total_registers_assigned
            total_registers_assigned = total_registers
            self._display_status(
                f"Writing {register.name!r} "
                f"({current_register_index + 1} of {total_registers})")

        async def executor():
            try:
                _logger.info(
                    "Writing registers: %r",
                    [r.name for r in register_value_mapping.keys()],
                )
                mod: Model = self._tree.model()
                await mod.write(
                    register_value_mapping=register_value_mapping,
                    progress_callback=progress_callback,
                )
            except asyncio.CancelledError:
                self._display_status(f"Write has been cancelled")
                raise
            except Exception as ex:
                _logger.exception("Register write failed")
                show_error("Write failed", "Could not read registers",
                           repr(ex), self)
                self._display_status(f"Could not write registers: {ex!r}")
            else:
                self._display_status(
                    f"{total_registers_assigned} registers have been written")

        self._cancel_task()
        self._running_task = asyncio.get_event_loop().create_task(executor())

    def _get_selected_registers(self) -> typing.List[Register]:
        selected_indexes: typing.List[
            QModelIndex] = self._tree.selectedIndexes()
        selected_registers = set()
        for si in selected_indexes:
            r = Model.get_register_from_index(si)
            if r is not None:
                selected_registers.add(r)
        # Beware that sets are not sorted, this may lead to weird user experience when watching the registers
        # read in a funny order.
        return list(sorted(selected_registers, key=lambda x: x.name))

    def _cancel_task(self):
        # noinspection PyBroadException
        try:
            self._running_task.cancel()
        except Exception:
            pass
        else:
            _logger.info("A running task had to be cancelled: %r",
                         self._running_task)
        finally:
            self._running_task = None

    def _display_status(self, text=None):
        self._status_display.setText(text)
예제 #32
0
class App(QWidget):

    Country, IPAddress, SessionName, Status, OS, CPU, CPU_Core, RAM, PING = range(
        0, 9)

    def __init__(self):

        super().__init__()

        #=========== Main GUI block ==================
        # Main Window Setting
        self.title = 'COINPAIGN: Remote Administration Tool'
        self.setWindowIcon(QIcon('logo.ico'))
        self.left = 500
        self.top = 250
        self.width = 1000
        self.height = 500

        # Getting subnet, LAN and WAN
        host = self.get_Host_name_IP()
        self.local = host[0]
        self.subnet_mask = host[1]

        self.initUI()
        self.displayItem()

    def initUI(self):

        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        self.dataGroupBox = QGroupBox()
        self.dataView = QTreeView()
        # self.dataView.move(0,100)
        # self.dataView.resize(500, 500)

        self.dataView.setRootIsDecorated(False)
        self.dataView.setAlternatingRowColors(True)
        self.dataView.setSortingEnabled(True)

        dataLayout = QVBoxLayout()
        dataLayout.addWidget(self.dataView)
        self.dataGroupBox.setLayout(dataLayout)

        mainLayout = QVBoxLayout()
        mainLayout.addWidget(self.dataGroupBox)
        self.setLayout(mainLayout)

        self.dataView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.dataView.customContextMenuRequested.connect(self.openMenu)

        # Layout "Refresh" button
        button = QPushButton('Refresh', self)
        button.move(900, 5)
        button.resize(80, 20)
        button.clicked.connect(self.execute_Refresh)

        # Layout "IP Lookup" button
        button = QPushButton('IP Lookup', self)
        button.move(15, 5)
        button.resize(80, 20)
        button.clicked.connect(self.execute_IPlookup)

        # Layout "MAC Vendor" button
        button = QPushButton('MAC Vendor', self)
        button.move(110, 5)
        button.resize(80, 20)
        button.clicked.connect(self.execute_MACvendor)

    def createMailModel(
            self,
            parent=None
    ):  #--- create information table view of remote computer

        model = QStandardItemModel(0, 9, parent)

        model.setHeaderData(self.Country, Qt.Horizontal, "Country/City ")
        model.setHeaderData(self.IPAddress, Qt.Horizontal, "IP LAN/WAN")
        model.setHeaderData(self.SessionName, Qt.Horizontal, "SessionName")
        model.setHeaderData(self.Status, Qt.Horizontal, "Status")
        model.setHeaderData(self.OS, Qt.Horizontal, "OS")
        model.setHeaderData(self.CPU, Qt.Horizontal, "CPU")
        model.setHeaderData(self.CPU_Core, Qt.Horizontal, "CPU Core")
        model.setHeaderData(self.RAM, Qt.Horizontal, "RAM(GB)")
        model.setHeaderData(self.PING, Qt.Horizontal, "PING(ms)")

        return model

#=========== Get and dislay information of remote computers block ==================

    def displayItem(
        self
    ):  #--- get and display information of remote computer when applictoin start

        progressBar.setMaximum(254)

        #--- get information of remote computer
        for i in range(1, 254):
            host = self.subnet_mask + ".%d" % i
            data = AgentClient.client_program(
                host)  #--- get data from Agentclient module

            if data != "0":
                pi = self.get_pingtime(host)
                data = data + ":" + str(pi)
                serverdata.append(data)

            progressBar.setValue(i)
            t = time.time()
            while time.time() < t + 0.1:
                app.processEvents()

        #--- display information of remote computer
        self.onReload()

        splash.hide()
        self.show()

    def get_pingtime(self, host):  #--- get ping time of live computer

        ping = subprocess.Popen(["ping", "-n", "1", host],
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE)

        out, error = ping.communicate()
        out = str(out).replace("\\r\\n", "")
        temp = out.split("ms")
        temp1 = temp[3]
        temp2 = temp1.split(" ")
        result = temp2[3]

        return result

    def onReload(self):  #--- display information of remote computer

        self.model = self.createMailModel(self)
        self.dataView.setModel(self.model)

        for data in serverdata:
            itemData = data.split(":")
            cn = pycountry.countries.get(alpha_2=itemData[0])
            countryname = cn.name
            amount = int(itemData[8])
            GB = round(amount / int(1024 * 1024 * 1024), 2)
            self.addMail(self.model,
                         './images/' + itemData[0].lower() + '/30.png',
                         countryname + "/" + itemData[1], itemData[2],
                         itemData[3], itemData[4],
                         './icons/' + itemData[5] + '.png', itemData[5],
                         itemData[6], itemData[7], str(GB), itemData[9])

    def addMail(self, model, nflag, country, ipaddress, ss_name, status,
                os_logo, os, cpu, cpu_core, ram, ping):

        icon = QIcon()
        icon.addPixmap(QPixmap(nflag))
        logo = QIcon()
        logo.addPixmap(QPixmap(os_logo))

        item_col_0 = QStandardItem(icon, country)
        item_col_1 = QStandardItem(ipaddress)
        item_col_2 = QStandardItem(ss_name)
        item_col_3 = QStandardItem(status)
        item_col_4 = QStandardItem(logo, os)
        item_col_5 = QStandardItem(cpu)
        item_col_6 = QStandardItem(cpu_core)
        item_col_7 = QStandardItem(ram)
        item_col_8 = QStandardItem(ping)

        model.appendRow([
            item_col_0, item_col_1, item_col_2, item_col_3, item_col_4,
            item_col_5, item_col_6, item_col_7, item_col_8
        ])

#=========== Refresh button process block ==================

    def execute_Refresh(self):

        splash.show()
        progressBar.setMaximum(255)
        new_serverdata = []

        for i in range(1, 254):
            host = self.subnet_mask + ".%d" % i
            data = AgentClient.client_program(
                host
            )  #--- get information of remote computer from AgentClient module again

            if data != "0":
                pi = self.get_pingtime(host)
                data = data + ":" + str(pi)
                new_serverdata.append(data)

            progressBar.setValue(i)
            t = time.time()
            while time.time() < t + 0.1:
                app.processEvents()

        for old_item in serverdata:  #--- update new data
            k = 0
            for new_item in new_serverdata:
                old1 = old_item.split(":")
                oldip = old1[2]
                new1 = new_item.split(":")
                newip = new1[2]
                if oldip == newip:
                    break
                k = k + 1
            if k == len(new_serverdata):
                item = old_item.replace("Connected", "Disconnected")
                new_serverdata.append(item)

        serverdata.clear()

        for i in range(0, len(new_serverdata)):
            serverdata.append(new_serverdata[i])

        self.onReload()  #--- display new data on GUI

        splash.hide()
        self.show()

#=========== IP lookup button process block ==================

    def execute_IPlookup(self):  #---- IP lookup Diaglog GUI
        dlgEdit = QDialog()
        dlgEdit.setWindowTitle("IP Lookup")
        dlgEdit.setWindowIcon(QIcon("./icons/iplookup.png"))
        dlgEdit.resize(600, 300)

        # getservIP GUI   ( get IP from domain address)
        self.dnsName = QLabel(dlgEdit)
        self.dnsName.setText("Target WebSite Domain")
        self.dnsName.move(20, 12)

        self.tagDns = QLineEdit(dlgEdit)
        self.tagDns.setPlaceholderText('google.com')
        self.tagDns.setReadOnly(False)
        self.tagDns.setEnabled(True)
        self.tagDns.move(20, 30)
        self.tagDns.resize(170, 25)

        Subm1 = QPushButton('Submit', dlgEdit)
        Subm1.resize(80, 25)
        Subm1.move(200, 30)
        Subm1.clicked.connect(self.submit1_click)

        self.ipValue = QTextEdit(dlgEdit)
        self.ipValue.move(0, 60)
        self.ipValue.resize(300, 240)

        # Whois GUI ( get informaion corresponding to IP)
        self.ipAdrr = QLabel(dlgEdit)
        self.ipAdrr.setText("Target IP Address")
        self.ipAdrr.move(320, 12)

        self.tagIP = QLineEdit(dlgEdit)
        self.tagIP.setPlaceholderText('216.58.200.46')

        ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"  # IP addres validation ( regular expression )
        ipRegex = QRegExp("^" + ipRange + "\\." + ipRange + "\\." + ipRange +
                          "\\." + ipRange + "$")
        ipValidator = QRegExpValidator(ipRegex, self)
        self.tagIP.setValidator(ipValidator)

        self.tagIP.setReadOnly(False)
        self.tagIP.setEnabled(True)
        self.tagIP.move(320, 30)
        self.tagIP.resize(170, 25)

        Subm2 = QPushButton('Submit', dlgEdit)
        Subm2.resize(80, 25)
        Subm2.move(500, 30)
        Subm2.clicked.connect(self.submit2_click)

        self.ipInfo = QTextEdit(dlgEdit)
        self.ipInfo.move(300, 60)
        self.ipInfo.resize(300, 240)

        dlgEdit.exec_()

    def submit1_click(self):  #---  getservIP processing

        tagDns = ""
        tagDns = self.tagDns.text()  #--- get domain address from LineEdit()

        # type website domain address
        if tagDns == "":
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("Warning")
            msg.setText("Please type website domain.")
            msg.exec_()
        else:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))

            ipResult = IP_Lookup.func_getservip(
                tagDns)  #--- get IP from IP_Lookup module

            self.ipValue.append("  " + ipResult)
            self.ipValue.append("")

            QApplication.restoreOverrideCursor()

    def submit2_click(self):  #--- whois processing

        tagIP = ""
        tagIP = self.tagIP.text()  #--- get ip from LineEdit()

        # IP validation
        numItem = tagIP.split(".")
        if len(numItem) != 4 or tagIP == "":
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("Warning")
            msg.setText("Please type IP address.")
            msg.exec_()
        elif numItem[3] == "":
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("Warning")
            msg.setText("Please type IP address.")
            msg.exec_()
        else:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            self.ipInfo.append("Detail Information on " + tagIP)
            getResult = IP_Lookup.func_whois(
                tagIP)  # get information from IP_Lookup module
            if getResult == []:
                self.ipInfo.append("   No result.")
            else:
                self.ipInfo.append("  " + "IP:" + "\t " + getResult[0])
                self.ipInfo.append("  " + "City:" + "\t " + getResult[1])
                self.ipInfo.append("  " + "Region:" + "\t " + getResult[2])
                self.ipInfo.append("  " + "Country:" + "\t " + getResult[3])
                self.ipInfo.append("  " + "Location:" + "\t " + getResult[4])
                self.ipInfo.append("  " + "Postal:" + "\t " + getResult[5])
                self.ipInfo.append("")

            QApplication.restoreOverrideCursor()

#=========== MAC Vendor button process block ==================

    def execute_MACvendor(self):  #--- MAC Vendor GUI

        dlgEdit = QDialog()
        dlgEdit.setWindowTitle("Mac Vendor")
        dlgEdit.setWindowIcon(QIcon("./icons/default.png"))
        dlgEdit.resize(600, 300)

        self.ipName = QLabel(dlgEdit)
        self.ipName.setText("MAC Address:")
        self.ipName.move(20, 25)

        self.mac = QLineEdit(dlgEdit)
        self.mac.setPlaceholderText('30-B4-9E-F1-C1-A5')
        self.mac.setReadOnly(False)
        self.mac.setEnabled(True)
        self.mac.move(100, 20)
        self.mac.resize(170, 25)

        self.efName = QTextEdit(dlgEdit)
        self.efName.move(0, 50)
        self.efName.resize(600, 250)

        Scan = QPushButton('Submit', dlgEdit)
        Scan.resize(80, 25)
        Scan.move(500, 20)
        Scan.clicked.connect(self.macvendor_click)

        dlgEdit.exec_()

    def macvendor_click(self):  #--- mac vendor processing

        mac = ""
        mac = self.mac.text()  #--- get mac from LineEdit()
        # mac validation
        if len(mac) < 6 or mac == "":
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("Warning")
            msg.setText("Please type MAC address.")
            msg.exec_()
        else:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            self.efName.append("Vendor Information on " + mac)
            vendorInfo = Mac_Vendor.getMacVendor(
                mac)  #--- get vendor informaion from Mac_Vendor module
            strven = str(vendorInfo)
            errcheck = strven.split(":")
            print(errcheck)
            if errcheck[0] == "{'error'":
                self.efName.append("   Incorrect MAC address.")
            else:
                self.efName.append("   Address:" + "\t " +
                                   vendorInfo['address'])
                self.efName.append("")
                self.efName.append("   Company:" + "\t " +
                                   vendorInfo['company'])
                self.efName.append("   Country:" + "\t " +
                                   vendorInfo['country'])
                self.efName.append("   End_Hex:" + "\t " +
                                   vendorInfo['end_hex'])
                self.efName.append("   Mac_Prefix:" + "\t " +
                                   vendorInfo['mac_prefix'])
                self.efName.append("   Start_Hex:" + "\t " +
                                   vendorInfo['start_hex'])
                self.efName.append("   Type:" + "\t " + vendorInfo['type'])
            self.efName.append("")

            QApplication.restoreOverrideCursor()

#=========== PopUP Menu  process block ==================

    def openMenu(self, position):  #--- popup menu main GUI

        menu = QMenu()

        ipscanItem = QAction(QIcon("./icons/IPScan.png"), "&IPScan", self)
        menu.addAction(ipscanItem)
        ipscanItem.triggered.connect(
            self.execute_ipscan)  #--- click ipscan menu

        portscanItem = QAction(QIcon("./icons/PortScan.png"), "&PortScan",
                               self)
        menu.addAction(portscanItem)
        portscanItem.triggered.connect(
            self.execute_portscan)  #--- click port scan menu

        reverseItem = QAction(QIcon("./icons/ReverseShell.png"),
                              "&ReverseShell", self)
        menu.addAction(reverseItem)
        reverseItem.triggered.connect(
            self.execute_reverse_shell)  #--- click reverse shell menu

        remoteItem = QAction(QIcon("./icons/RemoteDesktop.png"),
                             "&RemoteDesktop", self)
        menu.addAction(remoteItem)
        remoteItem.triggered.connect(
            self.execute_RDC)  #--- click remote desktop menu

        menu.exec_(self.dataView.viewport().mapToGlobal(position))

    #------- ip scan procee block ---------
    def execute_ipscan(self):  # ip scan diaglog GUI

        dlgEdit = QDialog()
        dlgEdit.setWindowTitle("IP Scan")
        dlgEdit.setWindowIcon(QIcon("./icons/IPScan.png"))
        dlgEdit.resize(600, 300)

        self.ipName = QLabel(dlgEdit)
        self.ipName.setText("Subnet: ")
        self.ipName.move(20, 25)

        self.ips = QLineEdit(dlgEdit)
        self.ips.setPlaceholderText('192.168.1.0/24')
        ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"  # ips validation ( regular expression )
        ipRegex = QRegExp("^" + ipRange + "\\." + ipRange + "\\." + ipRange +
                          "\\." + ipRange + "\\/" + ipRange + "$")
        ipValidator = QRegExpValidator(ipRegex, self)
        self.ips.setValidator(ipValidator)
        self.ips.setReadOnly(False)
        self.ips.setEnabled(True)
        self.ips.move(70, 20)
        self.ips.resize(170, 25)

        self.comments = QLabel(dlgEdit)
        self.comments.setText("Ex: 192.168.1.0/24")
        self.comments.move(245, 25)

        self.efName = QTextEdit(dlgEdit)
        self.efName.move(0, 50)
        self.efName.resize(600, 250)

        Scan = QPushButton('Scan', dlgEdit)
        Scan.resize(80, 25)
        Scan.move(500, 20)
        Scan.clicked.connect(self.ipscan_click)  # call ip scan process

        dlgEdit.exec_()

    def ipscan_click(self):  #--- ip scan process

        ips = ""
        ips = self.ips.text()  #--- get ips from LineEdit()
        #--- ips validation
        numItem = ips.split(".")
        if len(numItem) != 4 or ips == "":
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("Warning")
            msg.setText("Please type correct subnet...")
            msg.exec_()
        elif numItem[3] == "":
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("Warning")
            msg.setText("Please type correct subnet...")
            msg.exec_()
        # display ip scan result
        else:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            self.efName.append("IP Address and MAC on " + ips)

            devices = IP_Scan.arp(
                str(ips))  #--- ip scan process from IP_Scan module
            rcv = str(devices)
            print(rcv)
            ans = rcv.split(":")
            if ans[5] == "0>":
                self.efName.append("  No result.")
            else:
                for snd, rcv in devices:
                    self.efName.append("  " + rcv.psrc + "\t" + ":   " +
                                       rcv.src)
            self.efName.append("")

            QApplication.restoreOverrideCursor()

    #------- port scan procee block ---------
    def execute_portscan(self):  #---  GUI-dialog for port scan

        dlgEdit = QDialog()
        dlgEdit.setWindowTitle("Port Scan")
        dlgEdit.setWindowIcon(QIcon("./icons/PortScan.png"))
        dlgEdit.resize(600, 300)

        self.ipName = QLabel(dlgEdit)
        self.ipName.setText("Host IP:")
        self.ipName.move(20, 25)

        self.hostip = QLineEdit(dlgEdit)
        self.hostip.setPlaceholderText('192.168.1.131')
        ipRange = "(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"  # Part of the regular expression
        ipRegex = QRegExp("^" + ipRange + "\\." + ipRange + "\\." + ipRange +
                          "\\." + ipRange + "$")
        ipValidator = QRegExpValidator(ipRegex, self)
        self.hostip.setValidator(ipValidator)
        self.hostip.setReadOnly(False)
        self.hostip.setEnabled(True)
        self.hostip.move(70, 20)
        self.hostip.resize(170, 25)

        self.efName = QTextEdit(dlgEdit)
        self.efName.move(0, 50)
        self.efName.resize(600, 250)

        Scan = QPushButton('Scan', dlgEdit)
        Scan.resize(80, 25)
        Scan.move(500, 20)
        Scan.clicked.connect(self.portscan_click)  #--- call port scan process

        dlgEdit.exec_()

    def portscan_click(self):  #---  port scan process
        shost = ""
        shost = self.hostip.text()  #--- get host ip from GUI LineEdit()

        #--- IP address validation processing
        numItem = shost.split(".")
        if len(numItem) != 4 or shost == "":
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("Warning")
            msg.setText("Please type correct host IP.")
            msg.exec_()
        elif numItem[3] == "":
            msg = QMessageBox()
            msg.setIcon(QMessageBox.Warning)
            msg.setWindowTitle("Warning")
            msg.setText("Please type correct host IP.")
            msg.exec_()
        #---- get open port and display
        else:
            QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
            self.efName.append("Open Ports on " + shost)
            port = ""
            openPorts = Port_Scan.port_scan(
                str(shost))  #--- call function of Port_Scan module
            if openPorts == []:
                self.efName.append("  No result.")
            else:
                for i in openPorts:
                    port = port + "  " + i
                self.efName.append("  " + port)
                self.efName.append("")

            QApplication.restoreOverrideCursor()

#========== reverse shell  processing block  ================

    def execute_reverse_shell(self):  #--- run reverse shell program
        window_command = "cmd.exe /c start cmd.exe /k python Reverse_Shell.py"
        Popen(window_command)

#========== remote desktop connection  block  ================

    def execute_RDC(self):  #--- run remote desktop client program
        window_command = "python RDC_Client.py"
        Popen(window_command)


# Function to get Local IP address
# def get_Host_name_IP(self):
#     interfaces = netifaces.interfaces()
#     gws=netifaces.gateways()
#     ss = gws['default']
#     dd = ss[2]
#     aa = dd[0].split('.')
#     subnet = aa[0]+"."+aa[1]+"."+aa[2]
#     UserIP = []
#     ii = 0
#     for i in interfaces:
#         if i == 'lo':
#             continue
#         iface = netifaces.ifaddresses(i).get(netifaces.AF_INET)
#         if iface != None:
#             for j in iface:
#                 UserIP.append(j['addr'])
#         ii = ii+1
#     for ip in UserIP:
#         sub = ip.split(".")
#         subip = sub[0]+"."+sub[1]+"."+sub[2]
#         if subip == subnet:
#             lan = ip
#     # array = [lan, subnet, wan]
#     array = [lan, subnet]
#     return array

    def get_Host_name_IP(
            self):  #--- get IP address and subnet of host(this computer)

        hostname = socket.gethostname()
        host = socket.gethostbyname(hostname)
        print(host)

        sub = host.split(".")
        subnet = sub[0] + "." + sub[1] + "." + sub[2]

        array = [host, subnet]
        return array
예제 #33
0
class Navigation(QWidget):
    """
    Navigation class definition.
    
    Provide a combobox to switch on each opened directories and display it into
    a tree view
    
    Provide 2 useful function (to use in alter module):
      - add_action(name, shortcut, callback)
         - callback take 2 arguments : file_info and parent
      - add_separator()
    
    """

    SETTINGS_DIRECTORIES = 'navigation_dirs'
    SETTINGS_CURRENT_DIR = 'navigation_current_dir'

    onFileItemActivated = pyqtSignal(QFileInfo, name="onFileItemActivated")
    onDirItemActivated = pyqtSignal(QFileInfo, name="onDirItemActivated")

    def __init__(self, parent=None):
        super(Navigation, self).__init__(parent)
        self.setObjectName("Navigation")

        self.layout = QVBoxLayout(self)
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)

        self.menu_button = QPushButton('Select directory', self)
        self.menu_button.setFlat(True)
        #        self.menu_button.clicked.connect(self.on_menu_button_clicked)
        self.menu = QMenu(self)
        self.menu_button.setMenu(self.menu)
        self.menu_directories = QMenu(self)
        self.menu_directories.setTitle('Directories')
        self.menu_add_action('Open directory', self.open_directory, None,
                             QKeySequence.Open)
        self.menu_add_separator()
        self.menu_add_action('Refresh', self.reset, None, QKeySequence.Refresh)
        # @TODO invoke_all
        self.menu_add_separator()
        self.menu.addMenu(self.menu_directories)

        self.tree = QTreeView(self)
        self.model = FileSystemModel(self)
        self.tree.setModel(self.model)
        self.tree.setColumnHidden(1, True)
        self.tree.setColumnHidden(2, True)
        self.tree.setColumnHidden(3, True)
        self.tree.setHeaderHidden(True)
        # only to expand directory or activated with one click
        self.tree.clicked.connect(self.on_item_clicked)
        # else, for file use activated signal
        self.tree.activated.connect(self.on_item_activated)
        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.on_context_menu)

        self.widgets = collections.OrderedDict()
        self.widgets['menu_button'] = self.menu_button
        self.widgets['tree'] = self.tree

        # @ToDo: Alter.invoke_all('add_widget', self.widgets)

        for name, widget in self.widgets.items():
            if name == 'menu_button':
                self.layout.addWidget(widget, 0, Qt.AlignLeft)
            else:
                self.layout.addWidget(widget)

        self.context_menu = QMenu(self)
        self.add_action('New file', QKeySequence.New,
                        FileSystemHelper.new_file)
        self.add_separator()
        self.add_action('Copy', QKeySequence.Copy, FileSystemHelper.copy)
        self.add_action('Cut', QKeySequence.Cut, FileSystemHelper.cut)
        self.add_action('Paste', QKeySequence.Paste, FileSystemHelper.paste)
        self.add_separator()
        self.add_action('Delete', QKeySequence.Delete, FileSystemHelper.delete)

        # @ToDo Alter.invoke_all('navigation_add_action', self)

        #restore previous session and data
        dirs = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_DIRECTORIES, None, True)
        for directory_path in dirs:
            name = os.path.basename(directory_path)
            self.menu_add_directory(name, directory_path)
        current_dir = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_CURRENT_DIR, '')
        if current_dir:
            for action in self.menu_directories.actions():
                if action.data() == current_dir:
                    action.trigger()

        self.menu_button.setFocusPolicy(Qt.NoFocus)
        self.menu_button.setFocusProxy(self.tree)

    def reset(self, file_info):
        self.model.beginResetModel()
        current_dir = ModuleManager.core['settings'].Settings.value(
            self.SETTINGS_CURRENT_DIR, '')
        if current_dir:
            for action in self.menu_directories.actions():
                if action.data() == current_dir:
                    action.trigger()

    def on_menu_button_clicked(self):
        pos = self.mapToGlobal(self.menu_button.pos())
        menu_width = self.menu.sizeHint().width()
        pos.setY(pos.y() + self.menu_button.height())
        #        pos.setX(pos.x() + self.menu_button.width() - menu_width)
        if len(self.menu.actions()) > 0:
            self.menu.exec(pos)

    def menu_add_action(self,
                        name,
                        callback,
                        data=None,
                        shortcut=None,
                        icon=None):
        action = QAction(name, self)
        if icon:
            action.setIcon(icon)
        if shortcut:
            action.setShortcut(shortcut)
            action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        if data:
            action.setData(data)
        action.triggered.connect(callback)
        self.addAction(action)
        self.menu.addAction(action)

    def menu_add_directory(self, name, data):
        action = QAction(name, self)
        action.setData(data)
        action.triggered.connect(self.on_menu_action_triggered)
        self.menu_directories.addAction(action)
        return action

    def menu_add_separator(self):
        self.menu.addSeparator()

    def add_action(self, name, shortcut, callback, icon=None):
        """
        Ajoute une action au context menu et au widget navigation lui même.
        Créer une fonction à la volé pour fournir des arguments aux fonctions
        associé aux actions.
        """
        action = QAction(name, self)
        if icon:
            action.setIcon(icon)
        action.setShortcut(shortcut)
        action.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        action.triggered.connect(self.__wrapper(callback))
        self.addAction(action)
        self.context_menu.addAction(action)

    def add_separator(self):
        """Simple abstraction of self.context_menu.addSeparator()"""
        self.context_menu.addSeparator()

    def __wrapper(self, callback):
        def __new_function():
            """
            __new_function représente la forme de tous les callbacks connecté
            à une action pour pouvoir utiliser les raccourcis en même temps que
            le menu contextuel.
            """
            action = self.sender()
            file_info = action.data()
            if not file_info:
                indexes = self.tree.selectedIndexes()
                if indexes:
                    model_index = indexes[0]
                    file_info = self.model.fileInfo(model_index)
                    callback(file_info, self)
                elif action.shortcut() == QKeySequence.New:
                    file_info = self.model.fileInfo(self.tree.rootIndex())
                    callback(file_info, self)
            else:
                callback(file_info, self)
                action.setData(None)

        return __new_function

    def question(self, text, informative_text=None):
        message_box = QMessageBox(self)
        message_box.setText(text)
        if informative_text:
            message_box.setInformativeText(informative_text)
        message_box.setStandardButtons(QMessageBox.No | QMessageBox.Yes)
        message_box.setDefaultButton(QMessageBox.No)
        return message_box.exec()

    def on_context_menu(self, point):
        model_index = self.tree.indexAt(point)
        file_info = self.model.fileInfo(model_index)
        # pour chaque action on met a jour les data (file_info)
        # puis on altère les actions (ex enabled)
        for action in self.context_menu.actions():
            if not action.isSeparator():
                action.setData(file_info)
                action.setEnabled(model_index.isValid())
                if action.shortcut() == QKeySequence.New:
                    action.setEnabled(True)
                    if not model_index.isValid():
                        file_info = self.model.fileInfo(self.tree.rootIndex())
                        action.setData(file_info)
                if action.shortcut() == QKeySequence.Paste:
                    enable = FileSystemHelper.ready() and model_index.isValid()
                    action.setEnabled(enable)
                if action.shortcut() == QKeySequence.Delete:
                    # remove directory only if is an empty directory
                    if model_index.isValid() and file_info.isDir():
                        path = file_info.absoluteFilePath()
                        # QDir(path).count() always contains '.' and '..'
                        action.setEnabled(QDir(path).count() == 2)
                # @ToDo
                #Alter.invoke_all(
                #    'navigation_on_menu_action',
                #    model_index, file_info, action, self)
        if len(self.context_menu.actions()) > 0:
            self.context_menu.exec(self.tree.mapToGlobal(point))
        # reset action data, sinon y a des problèmes dans _new_function
        for action in self.context_menu.actions():
            action.setData(None)

    def on_item_activated(self, index):
        qFileInfo = self.model.fileInfo(index)
        if qFileInfo.isDir():
            self.onDirItemActivated.emit(qFileInfo)
        else:
            self.onFileItemActivated.emit(qFileInfo)

    def on_item_clicked(self, index):
        qFileInfo = self.model.fileInfo(index)
        if qFileInfo.isDir():
            self.onDirItemActivated.emit(qFileInfo)
            self.tree.setExpanded(index, not self.tree.isExpanded(index))
        else:
            self.onFileItemActivated.emit(qFileInfo)

    def open_directory(self):
        path = QFileDialog.getExistingDirectory(self, "Open Directory", ".")
        if path:
            name = os.path.basename(path)
            action = self.menu_add_directory(name, path)
            self.save_directories_path()
            action.trigger()

    def on_menu_action_triggered(self):
        action = self.sender()
        path = action.data()
        if path:
            self.model.setRootPath(path)
            self.tree.setRootIndex(self.model.index(path))
            self.menu_button.setText(os.path.basename(path))
            self.save_current_dir(path)

    def save_directories_path(self):
        ModuleManager.core['settings'].Settings.set_value(
            self.SETTINGS_DIRECTORIES,
            [action.data() for action in self.menu_directories.actions()])

    def save_current_dir(self, path):
        ModuleManager.core['settings'].Settings.set_value(
            self.SETTINGS_CURRENT_DIR, path)