예제 #1
0
파일: my0401.py 프로젝트: falomsc/pyqtStudy
class QmyMainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.model = QFileSystemModel(self)
        self.model.setRootPath(QDir.currentPath())
        self.ui.qTreeView.setModel(self.model)
        self.ui.qListView.setModel(self.model)
        self.ui.qTableView.setModel(self.model)

        self.ui.qTreeView.clicked.connect(self.ui.qListView.setRootIndex)
        self.ui.qTreeView.clicked.connect(self.ui.qTableView.setRootIndex)

    def on_qTreeView_clicked(self, index):
        self.ui.qCheckBox.setChecked(self.model.isDir(index))
        self.ui.qLabel1.setText(self.model.filePath(index))
        self.ui.qLabel4.setText(self.model.type(index))
        self.ui.qLabel2.setText(self.model.fileName(index))
        fileSize = self.model.size(index)/1024
        if(fileSize<1024):
            self.ui.qLabel3.setText("%d KB" % fileSize)
        else:
            self.ui.qLabel3.setText("%.2f MB" % (fileSize/1024))
예제 #2
0
class QmyMainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)  #调用父类构造函数,创建窗体
        self.ui = Ui_MainWindow()  #创建UI对象
        self.ui.setupUi(self)  #构造UI界面

        self.__buildModelView()

##  ==============自定义功能函数============

    def __buildModelView(self):  ##构造Model/View 系统
        self.model = QFileSystemModel(self)
        self.model.setRootPath(QDir.currentPath())

        self.ui.treeView.setModel(self.model)  #设置数据模型
        self.ui.listView.setModel(self.model)
        self.ui.tableView.setModel(self.model)

        self.ui.treeView.clicked.connect(self.ui.listView.setRootIndex)
        self.ui.treeView.clicked.connect(self.ui.tableView.setRootIndex)

##  ==========由connectSlotsByName() 自动连接的槽函数==================

    def on_treeView_clicked(self, index):  ##treeView单击
        self.ui.chkBox_IsDir.setChecked(self.model.isDir(index))  #是否是目录

        self.ui.LabPath.setText(self.model.filePath(index))  #目录名
        self.ui.LabType.setText(self.model.type(index))  #节点类型
        self.ui.LabFileName.setText(self.model.fileName(index))  #文件名

        fileSize = self.model.size(index) / 1024
        if (fileSize < 1024):
            self.ui.LabFileSize.setText("%d KB" % fileSize)
        else:
            self.ui.LabFileSize.setText("%.2f MB" % (fileSize / 1024.0))
예제 #3
0
class QmyMainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)  #调用父类构造函数,创建窗体
        self.ui = Ui_MainWindow()  #创建Ui对象
        self.ui.setupUi(self)  #构造UI
        self.LabPath = QLabel(self)
        self.ui.statusBar.addWidget(self.LabPath)
        self.__buildModelView()

    ##==========自定义功能函数==========
    def __buildModelView(self):

        self.model = QFileSystemModel(self)  #定义数据模型
        self.model.setRootPath(QDir.currentPath())  #获取当前路径,并设置为model的根目录
        self.ui.treeView.setModel(self.model)  #将self.model设置为自己的数据模型
        self.ui.listView.setModel(self.model)
        self.ui.tableView.setModel(self.model)
        #将treeView的cilcked信号与listView与tableView的槽函数setRootIndex相关联
        self.ui.treeView.clicked.connect(self.ui.listView.setRootIndex)
        self.ui.treeView.clicked.connect(self.ui.tableView.setRootIndex)

    ##==========事件处理函数===========

    ##==========由connectSlotsByName()自动关联的槽函数====
    def on_treeView_clicked(self, index):  #index是模型索引
        print(index)
        self.ui.checkBox.setChecked(self.model.isDir(index))
        self.LabPath.setText(self.model.filePath(index))
        self.ui.LabType.setText(self.model.type(index))
        self.ui.LabFileName.setText(self.model.fileName(index))

        fileSize = self.model.size(index) / 1024
        # print(fileSize)
        if fileSize < 1024:
            self.ui.LabFileSize.setText("%d KB" % fileSize)
        else:
            self.ui.LabFileSize.setText(".2f MB" % (fileSize / 1024.0))
예제 #4
0
class TabTest(QWidget, form_class):
    def __init__(self, status_bar):
        super().__init__()
        self.setupUi(self)

        self.status_bar = status_bar
        self.model = None

        self.set_tree_view()
        self.check_model_loaded()
        self.set_events()

    def set_tree_view(self):
        self.root_path = "../../"
        self.file_model = QFileSystemModel()
        self.file_model.setRootPath(self.root_path)

        self.tree_view.setModel(self.file_model)
        self.tree_view.setRootIndex(self.file_model.index(self.root_path))
        self.tree_view.setColumnHidden(1, True)
        self.tree_view.setColumnHidden(2, True)
        self.tree_view.setColumnHidden(3, True)

    def check_model_loaded(self):
        if self.model is None:
            self.label_state.setText('Not loaded')
        else:
            self.label_state.setText('Loaded')

    def set_events(self):
        self.button_load.clicked.connect(self.on_click_load)
        self.tree_view.clicked.connect(self.on_click_tree_view)

    def on_click_load(self):
        self.status_bar.showMessage('Load model...')

        path = QFileDialog.getOpenFileName(self, 'Select model', '',
                                           'Pytorch model (*.pt)')[0]
        if path:
            self.model = torch.load(path)
            self.device = torch.device(
                "cuda:0" if torch.cuda.is_available() else "cpu")
            self.model.eval()
            self.model = self.model.to(self.device)
        self.check_model_loaded()

        self.status_bar.clearMessage()

    def on_click_tree_view(self, index):
        if not self.file_model.isDir(index):
            path = self.file_model.filePath(index)
            image = cv2.imread(path)
            resized = cv2.resize(
                image, (self.label_image.width(), self.label_image.height()),
                interpolation=cv2.INTER_AREA)
            self.set_image(resized, self.label_image)

            if self.model is not None:
                gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
                data = torch.from_numpy(gray).float() / 255.0
                data = torch.unsqueeze(torch.unsqueeze(data, 0), 0)
                data = data.to(self.device)

                with torch.no_grad():
                    output = self.model(data)
                    _, pred = torch.max(output.data, 1)
                    pred_number = pred.cpu().numpy()[0]
                    self.label_result.setText(str(pred_number))

    def set_image(self, frame, label):
        image = QtGui.QImage(frame, frame.shape[1], frame.shape[0],
                             frame.shape[1] * 3, QtGui.QImage.Format_BGR888)
        pixmap = QtGui.QPixmap(image)
        label.setPixmap(pixmap)
예제 #5
0
파일: gui.py 프로젝트: wflk/pbtk
class PBTKGUI(QApplication):
    def __init__(self):
        super().__init__(argv)
        signal(SIGINT, SIG_DFL)

        if '__file__' in globals():
            views = dirname(realpath(__file__)) + '/views/'
        else:
            views = dirname(realpath(executable)) + '/views/'

        self.welcome = loadUi(views + 'welcome.ui')
        self.choose_extractor = loadUi(views + 'choose_extractor.ui')
        self.choose_proto = loadUi(views + 'choose_proto.ui')
        self.create_endpoint = loadUi(views + 'create_endpoint.ui')
        self.choose_endpoint = loadUi(views + 'choose_endpoint.ui')
        self.fuzzer = loadUi(views + 'fuzzer.ui')

        self.welcome.step1.clicked.connect(self.load_extractors)
        self.choose_extractor.rejected.connect(
            partial(self.set_view, self.welcome))
        self.choose_extractor.extractors.itemClicked.connect(
            self.prompt_extractor)

        self.welcome.step2.clicked.connect(self.load_protos)
        self.proto_fs = QFileSystemModel()
        self.choose_proto.protos.setModel(self.proto_fs)
        self.proto_fs.directoryLoaded.connect(
            self.choose_proto.protos.expandAll)

        for i in range(1, self.proto_fs.columnCount()):
            self.choose_proto.protos.hideColumn(i)
        self.choose_proto.protos.setRootIndex(
            self.proto_fs.index(str(BASE_PATH / 'protos')))
        self.choose_proto.rejected.connect(partial(self.set_view,
                                                   self.welcome))
        self.choose_proto.protos.clicked.connect(self.new_endpoint)

        self.create_endpoint.transports.itemClicked.connect(
            self.pick_transport)
        self.create_endpoint.loadRespPbBtn.clicked.connect(
            self.load_another_pb)
        self.create_endpoint.rejected.connect(
            partial(self.set_view, self.choose_proto))
        self.create_endpoint.buttonBox.accepted.connect(self.write_endpoint)

        self.welcome.step3.clicked.connect(self.load_endpoints)
        self.choose_endpoint.rejected.connect(
            partial(self.set_view, self.welcome))
        self.choose_endpoint.endpoints.itemClicked.connect(self.launch_fuzzer)

        self.fuzzer.rejected.connect(
            partial(self.set_view, self.choose_endpoint))
        self.fuzzer.fuzzFields.clicked.connect(self.fuzz_endpoint)
        self.fuzzer.deleteThis.clicked.connect(self.delete_endpoint)
        self.fuzzer.comboBox.activated.connect(self.launch_fuzzer)

        self.fuzzer.urlField.setWordWrapMode(QTextOption.WrapAnywhere)

        for tree in (self.fuzzer.pbTree, self.fuzzer.getTree):
            tree.itemEntered.connect(lambda item, _: item.edit()
                                     if hasattr(item, 'edit') else None)
            tree.itemClicked.connect(
                lambda item, col: item.updateCheck(col=col))
            tree.header().setSectionResizeMode(QHeaderView.ResizeToContents)

        self.welcome.mydirLabel.setText(self.welcome.mydirLabel.text() %
                                        BASE_PATH)
        self.welcome.mydirBtn.clicked.connect(
            partial(QDesktopServices.openUrl,
                    QUrl.fromLocalFile(str(BASE_PATH))))

        self.set_view(self.welcome)
        self.exec_()

    """
        Step 1 - Extract .proto structures from apps
    """

    def load_extractors(self):
        self.choose_extractor.extractors.clear()

        for name, meta in extractors.items():
            item = QListWidgetItem(meta['desc'],
                                   self.choose_extractor.extractors)
            item.setData(Qt.UserRole, name)

        self.set_view(self.choose_extractor)

    def prompt_extractor(self, item):
        extractor = extractors[item.data(Qt.UserRole)]
        inputs = []
        if not assert_installed(self.view, **extractor.get('depends', {})):
            return

        if not extractor.get('pick_url', False):
            files, mime = QFileDialog.getOpenFileNames()
            for path in files:
                inputs.append((path, Path(path).stem))
        else:
            text, good = QInputDialog.getText(self.view, ' ', 'Input an URL:')
            if text:
                url = urlparse(text)
                inputs.append((url.geturl(), url.netloc))

        if inputs:
            wait = QProgressDialog('Extracting .proto structures...', None, 0,
                                   0)
            wait.setWindowTitle(' ')
            self.set_view(wait)

            self.worker = Worker(inputs, extractor)
            self.worker.progress.connect(self.extraction_progress)
            self.worker.finished.connect(self.extraction_done)
            self.worker.signal_proxy.connect(self.signal_proxy)
            self.worker.start()

    def extraction_progress(self, info, progress):
        self.view.setLabelText(info)

        if progress is not None:
            self.view.setRange(0, 100)
            self.view.setValue(progress * 100)
        else:
            self.view.setRange(0, 0)

    def extraction_done(self, outputs):
        nb_written_all, wrote_endpoints = 0, False

        for folder, output in outputs.items():
            nb_written, wrote_endpoints = extractor_save(
                BASE_PATH, folder, output)
            nb_written_all += nb_written

        if wrote_endpoints:
            self.set_view(self.welcome)
            QMessageBox.information(
                self.view, ' ',
                '%d endpoints and their <i>.proto</i> structures have been extracted! You can now reuse the <i>.proto</i>s or fuzz the endpoints.'
                % nb_written_all)

        elif nb_written_all:
            self.set_view(self.welcome)
            QMessageBox.information(
                self.view, ' ',
                '%d <i>.proto</i> structures have been extracted! You can now reuse the <i>.protos</i> or define endpoints for them to fuzz.'
                % nb_written_all)

        else:
            self.set_view(self.choose_extractor)
            QMessageBox.warning(
                self.view, ' ',
                'This extractor did not find Protobuf structures in the corresponding format for specified files.'
            )

    """
        Step 2 - Link .protos to endpoints
    """

    # Don't load .protos from the filesystem until asked to, in order
    # not to slow down startup.

    def load_protos(self):
        self.proto_fs.setRootPath(str(BASE_PATH / 'protos'))
        self.set_view(self.choose_proto)

    def new_endpoint(self, path):
        if not self.proto_fs.isDir(path):
            path = self.proto_fs.filePath(path)

            if assert_installed(self.choose_proto, binaries=['protoc']):
                if not getattr(self, 'only_resp_combo', False):
                    self.create_endpoint.pbRequestCombo.clear()
                self.create_endpoint.pbRespCombo.clear()

                has_msgs = False
                for name, cls in load_proto_msgs(path):
                    has_msgs = True
                    if not getattr(self, 'only_resp_combo', False):
                        self.create_endpoint.pbRequestCombo.addItem(
                            name, (path, name))
                    self.create_endpoint.pbRespCombo.addItem(
                        name, (path, name))
                if not has_msgs:
                    QMessageBox.warning(
                        self.view, ' ',
                        'There is no message defined in this .proto.')
                    return

                self.create_endpoint.reqDataSubform.hide()

                if not getattr(self, 'only_resp_combo', False):
                    self.create_endpoint.endpointUrl.clear()
                    self.create_endpoint.transports.clear()
                    self.create_endpoint.sampleData.clear()
                    self.create_endpoint.pbParamKey.clear()
                    self.create_endpoint.parsePbCheckbox.setChecked(False)

                    for name, meta in transports.items():
                        item = QListWidgetItem(meta['desc'],
                                               self.create_endpoint.transports)
                        item.setData(Qt.UserRole,
                                     (name, meta.get('ui_data_form')))

                elif getattr(self, 'saved_transport_choice'):
                    self.create_endpoint.transports.setCurrentItem(
                        self.saved_transport_choice)
                    self.pick_transport(self.saved_transport_choice)
                    self.saved_transport_choice = None

                self.only_resp_combo = False
                self.set_view(self.create_endpoint)

    def pick_transport(self, item):
        name, desc = item.data(Qt.UserRole)
        self.has_pb_param = desc and 'regular' in desc
        self.create_endpoint.reqDataSubform.show()
        if self.has_pb_param:
            self.create_endpoint.pbParamSubform.show()
        else:
            self.create_endpoint.pbParamSubform.hide()
        self.create_endpoint.sampleDataLabel.setText(
            'Sample request data, one per line (in the form of %s):' % desc)

    def load_another_pb(self):
        self.only_resp_combo = True
        self.saved_transport_choice = self.create_endpoint.transports.currentItem(
        )
        self.set_view(self.choose_proto)

    def write_endpoint(self):
        request_pb = self.create_endpoint.pbRequestCombo.itemData(
            self.create_endpoint.pbRequestCombo.currentIndex())
        url = self.create_endpoint.endpointUrl.text()
        transport = self.create_endpoint.transports.currentItem()
        sample_data = self.create_endpoint.sampleData.toPlainText()
        pb_param = self.create_endpoint.pbParamKey.text()
        has_resp_pb = self.create_endpoint.parsePbCheckbox.isChecked()
        resp_pb = self.create_endpoint.pbRespCombo.itemData(
            self.create_endpoint.pbRespCombo.currentIndex())

        if not (request_pb and urlparse(url).netloc and transport and
                (not self.has_pb_param or pb_param) and
                (not has_resp_pb or resp_pb)):
            QMessageBox.warning(
                self.view, ' ', 'Please fill all relevant information fields.')

        else:
            json = {
                'request': {
                    'transport':
                    transport.data(Qt.UserRole)[0],
                    'proto_path':
                    request_pb[0].replace(str(BASE_PATH / 'protos'),
                                          '').strip('/\\'),
                    'proto_msg':
                    request_pb[1],
                    'url':
                    url
                }
            }
            if self.has_pb_param:
                json['request']['pb_param'] = pb_param

            sample_data = list(filter(None, sample_data.split('\n')))
            if sample_data:
                transport_obj = transports[transport.data(Qt.UserRole)[0]]
                transport_obj = transport_obj['func'](pb_param, url)

                for sample_id, sample in enumerate(sample_data):
                    try:
                        sample = transport_obj.serialize_sample(sample)
                    except Exception:
                        return QMessageBox.warning(
                            self.view, ' ',
                            'Some of your sample data is not in the specified format.'
                        )
                    if not sample:
                        return QMessageBox.warning(
                            self.view, ' ',
                            "Some of your sample data didn't contain the Protobuf parameter key you specified."
                        )
                    sample_data[sample_id] = sample

                json['request']['samples'] = sample_data

            if has_resp_pb:
                json['response'] = {
                    'format':
                    'raw_pb',
                    'proto_path':
                    resp_pb[0].replace(str(BASE_PATH / 'protos'),
                                       '').strip('/\\'),
                    'proto_msg':
                    resp_pb[1]
                }
            insert_endpoint(BASE_PATH / 'endpoints', json)

            QMessageBox.information(self.view, ' ',
                                    'Endpoint created successfully.')
            self.set_view(self.welcome)

    def load_endpoints(self):
        self.choose_endpoint.endpoints.clear()

        for name in listdir(str(BASE_PATH / 'endpoints')):
            if name.endswith('.json'):
                item = QListWidgetItem(
                    name.split('.json')[0], self.choose_endpoint.endpoints)
                item.setFlags(item.flags() & ~Qt.ItemIsEnabled)

                pb_msg_to_endpoints = defaultdict(list)
                with open(str(BASE_PATH / 'endpoints' / name)) as fd:
                    for endpoint in load(fd):
                        pb_msg_to_endpoints[endpoint['request']['proto_msg'].
                                            split('.')[-1]].append(endpoint)

                for pb_msg, endpoints in pb_msg_to_endpoints.items():
                    item = QListWidgetItem(' ' * 4 + pb_msg,
                                           self.choose_endpoint.endpoints)
                    item.setFlags(item.flags() & ~Qt.ItemIsEnabled)

                    for endpoint in endpoints:
                        item = QListWidgetItem(
                            ' ' * 8 +
                            (urlparse(endpoint['request']['url']).path or '/'),
                            self.choose_endpoint.endpoints)
                        item.setData(Qt.UserRole, endpoint)

        self.set_view(self.choose_endpoint)

    """
        Step 3: Fuzz and test endpoints live
    """

    def launch_fuzzer(self, item):
        if type(item) == int:
            data, sample_id = self.fuzzer.comboBox.itemData(item)
        else:
            data, sample_id = item.data(Qt.UserRole), 0

        if data and assert_installed(self.view, binaries=['protoc']):
            self.current_req_proto = BASE_PATH / 'protos' / data['request'][
                'proto_path']

            self.pb_request = load_proto_msgs(self.current_req_proto)
            self.pb_request = dict(
                self.pb_request)[data['request']['proto_msg']]()

            if data.get('response') and data['response']['format'] == 'raw_pb':
                self.pb_resp = load_proto_msgs(BASE_PATH / 'protos' /
                                               data['response']['proto_path'])
                self.pb_resp = dict(
                    self.pb_resp)[data['response']['proto_msg']]

            self.pb_param = data['request'].get('pb_param')
            self.base_url = data['request']['url']
            self.endpoint = data

            self.transport_meta = transports[data['request']['transport']]
            self.transport = self.transport_meta['func'](self.pb_param,
                                                         self.base_url)

            sample = ''
            if data['request'].get('samples'):
                sample = data['request']['samples'][sample_id]
            self.get_params = self.transport.load_sample(
                sample, self.pb_request)

            # Get initial data into the Protobuf tree view
            self.fuzzer.pbTree.clear()
            self.parse_desc(self.pb_request.DESCRIPTOR, self.fuzzer.pbTree)
            self.parse_fields(self.pb_request)

            # Do the same for transport-specific data
            self.fuzzer.getTree.clear()
            if self.transport_meta.get('ui_tab'):
                self.fuzzer.tabs.setTabText(1, self.transport_meta['ui_tab'])
                if self.get_params:
                    for key, val in self.get_params.items():
                        ProtocolDataItem(self.fuzzer.getTree, key, val, self)
            else:
                self.fuzzer.tabs.setTabText(1, '(disabled)')
                # how to hide it ?

            # Fill the request samples combo box if we're loading a new
            # endpoint.
            if type(item) != int:
                if len(data['request'].get('samples', [])) > 1:
                    self.fuzzer.comboBox.clear()
                    for sample_id, sample in enumerate(
                            data['request']['samples']):
                        self.fuzzer.comboBox.addItem(
                            sample[self.pb_param] if self.pb_param else
                            str(sample), (data, sample_id))
                    self.fuzzer.comboBoxLabel.show()
                    self.fuzzer.comboBox.show()
                else:
                    self.fuzzer.comboBoxLabel.hide()
                    self.fuzzer.comboBox.hide()

                self.set_view(self.fuzzer)

            self.fuzzer.frame.setUrl(QUrl("about:blank"))
            self.update_fuzzer()

    """
        Parsing and rendering the Protobuf message to a tree view:

        Every Protobuf field is fed to ProtobufItem (a class inheriting
        QTreeWidgetItem), and the created object is saved in the _items
        property of the corresponding descriptor.
    """

    # First, parse the descriptor (structure) of the Protobuf message.

    def parse_desc(self, msg, item, path=[]):
        for ds in msg.fields:
            new_item = ProtobufItem(item, ds, self, path)
            if ds.type == ds.TYPE_MESSAGE and ds.full_name not in path:
                self.parse_desc(ds.message_type, new_item,
                                path + [ds.full_name])

    # Then, parse the fields (contents) of the Protobuf message.

    def parse_fields(self, msg, path=[]):
        for ds, val in msg.ListFields():
            if ds.label == ds.LABEL_REPEATED:
                for val_index, val_value in enumerate(val):
                    if ds.type == ds.TYPE_MESSAGE:
                        ds._items[tuple(path)].setExpanded(True)
                        ds._items[tuple(path)].setDefault(parent=msg,
                                                          msg=val,
                                                          index=val_index)
                        self.parse_fields(val_value, path + [ds.full_name])

                    else:
                        ds._items[tuple(path)].setDefault(val_value,
                                                          parent=msg,
                                                          msg=val,
                                                          index=val_index)

                    ds._items[tuple(path)].duplicate(True)

            else:
                if ds.type == ds.TYPE_MESSAGE:
                    ds._items[tuple(path)].setExpanded(True)
                    ds._items[tuple(path)].setDefault(parent=msg, msg=val)
                    self.parse_fields(val, path + [ds.full_name])

                else:
                    ds._items[tuple(path)].setDefault(val, parent=msg, msg=val)

    def update_fuzzer(self):
        resp = self.transport.perform_request(self.pb_request, self.get_params)

        data, text, url, mime = resp.content, resp.text, resp.url, resp.headers[
            'Content-Type'].split(';')[0]

        meta = '%s %d %08x\n%s' % (mime, len(data), crc32(data) & 0xffffffff,
                                   resp.url)
        self.fuzzer.urlField.setText(meta)

        self.fuzzer.frame.update_frame(data, text, url, mime,
                                       getattr(self, 'pb_resp', None))

    def fuzz_endpoint(self):
        QMessageBox.information(self.view, ' ',
                                'Automatic fuzzing is not implemented yet.')

    def delete_endpoint(self):
        if QMessageBox.question(self.view, ' ',
                                'Delete this endpoint?') == QMessageBox.Yes:
            path = str(BASE_PATH / 'endpoints' /
                       (urlparse(self.base_url).netloc + '.json'))

            with open(path) as fd:
                json = load(fd)
            json.remove(self.endpoint)

            with open(path, 'w') as fd:
                dump(json, fd, ensure_ascii=False, indent=4)
            if not json:
                remove(path)

            self.load_endpoints()

    """
        Utility methods follow
    """

    def set_view(self, view):
        if hasattr(self, 'view'):
            self.view.hide()
        view.show()
        self.view = view

        resolution = QDesktopWidget().screenGeometry()
        view.move((resolution.width() / 2) - (view.frameSize().width() / 2),
                  (resolution.height() / 2) - (view.frameSize().height() / 2))

    """
        signal() can't be called from inside a thread, and some
        extractors need it in order not to have their GUI child process
        interrupt signal catched by our main thread, so here is an ugly
        way to reach signal() through a slot.
    """

    def signal_proxy(self, *args):
        signal(*args)
예제 #6
0
class MainWindow(QMainWindow, form_class):
    def __init__(self):
        super().__init__()
        self.setupUi(self)

        self.image = None
        self.set_tree_view()
        self.set_radio_button()

    def set_tree_view(self):
        self.root_path = '../'

        self.file_system_model = QFileSystemModel()
        self.tree_view.setModel(self.file_system_model)
        self.file_system_model.setRootPath(self.root_path)
        self.tree_view.setRootIndex(
            self.file_system_model.index(self.root_path))
        self.tree_view.setColumnWidth(0, 200)

        self.tree_view.setColumnHidden(1, True)
        self.tree_view.setColumnHidden(2, True)
        self.tree_view.setColumnHidden(3, True)

        self.tree_view.doubleClicked.connect(self.tree_view_double_clicked)

    def tree_view_double_clicked(self, index):
        if self.file_system_model.isDir(index):
            # print('Directory')
            pass
        else:
            path = self.file_system_model.filePath(index)
            self.image = cv2.imread(path)
            self.image_processing()

    def set_radio_button(self):
        self.radio_rgb.clicked.connect(self.image_processing)
        self.radio_gray.clicked.connect(self.image_processing)
        self.radio_bin.clicked.connect(self.image_processing)

    def image_processing(self):
        if self.image is None:
            return

        if self.radio_rgb.isChecked():
            processed = self.image
        elif self.radio_gray.isChecked():
            processed = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
        else:
            gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
            processed = (gray > 128).astype(np.uint8) * 255

        if len(processed.shape) == 2:
            show_image = QtGui.QImage(processed, processed.shape[1],
                                      processed.shape[0], processed.shape[1],
                                      QtGui.QImage.Format_Grayscale8)
        else:
            show_image = QtGui.QImage(processed, processed.shape[1],
                                      processed.shape[0],
                                      processed.shape[1] * 3,
                                      QtGui.QImage.Format_BGR888)
        self.label_image.setPixmap(QtGui.QPixmap(show_image))
예제 #7
0
class FileBrowser(QTreeView):
    def __init__(self,
                 parent=None,
                 textPad=None,
                 notebook=None,
                 codeView=None):
        super().__init__()
        self.path = self.checkPath(os.getcwd())
        self.filename = None

        self.text = None

        self.initItems()

        self.textPad = textPad
        self.notebook = notebook
        self.codeView = codeView

        self.mainWindow = parent

        self.index = None

        self.copySourceFilePath = None  # copy / paste items
        self.copySourceFileName = None
        self.isCopyFileFolder = None

        self.setStyleSheet("""
            border: 5 px;
            background-color: black; 
            color: white;
            alternate-background-color: #FFFFFF;
            selection-background-color: #3b5784;
            """)

        # Contextmenu
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.openMenu)

        self.popMenu = QMenu()
        self.popMenu.setStyleSheet("""
            color: #FFFFFF;
            background-color: #2c2c2c;
            selection-background-color: #3b5784;
            alternate-background-color: #FFFFFF;
        """)

        infoAction = QAction('Info', self)
        infoAction.triggered.connect(self.onInfo)
        createFolderAction = QAction('Create New Folder', self)
        createFolderAction.triggered.connect(self.onCreateNewFolder)
        copyAction = QAction('Copy Item', self)
        copyAction.triggered.connect(self.onCopy)
        pasteAction = QAction('Paste Item', self)
        pasteAction.triggered.connect(self.onPaste)
        renameAction = QAction('Rename Item', self)
        renameAction.triggered.connect(self.onRename)
        deleteAction = QAction('Delete Item', self)
        deleteAction.triggered.connect(self.onDelete)
        terminalAction = QAction('Open Terminal', self)
        terminalAction.triggered.connect(self.onTerminal)

        self.popMenu.addAction(infoAction)
        self.popMenu.addSeparator()
        self.popMenu.addAction(createFolderAction)
        self.popMenu.addSeparator()
        self.popMenu.addAction(copyAction)
        self.popMenu.addAction(pasteAction)
        self.popMenu.addAction(renameAction)
        self.popMenu.addSeparator()
        self.popMenu.addAction(deleteAction)
        self.popMenu.addSeparator()
        self.popMenu.addAction(terminalAction)

    def openMenu(self, position):
        # -> open ContextMenu
        self.popMenu.exec_(self.mapToGlobal(position))

    def onInfo(self):
        if not self.index:
            return

        index = self.index
        indexItem = self.model.index(index.row(), 0, index.parent())

        fileName, filePath, fileDir, fileInfo = self.getFileInformation()

        bundle = fileInfo.isBundle()  # bool
        dir = fileInfo.isDir()  # bool
        file = fileInfo.isFile()  # bool
        executable = fileInfo.isExecutable()  # bool
        readable = fileInfo.isReadable()  # bool
        writable = fileInfo.isWritable()  # bool
        created = fileInfo.created()  # QDateTime
        #modified = fileInfo.lastModified()      # QDateTime
        owner = fileInfo.owner()  # QString
        size = fileInfo.size()  # QInt
        s = format(size, ',d')

        text = ''
        text += 'Type:\t'
        if bundle:
            text += 'Bundle\n\n'
        if dir:
            text += 'Path\n\n'
        if file:
            text += 'File\n\n'

        if readable:
            text += 'read:\tyes\n'
        else:
            text += 'read:\tno\n'
        if writable:
            text += 'write:\tyes\n'
        else:
            text += 'write:\tno\n'
        if executable:
            text += 'exec:\tyes\n\n'
        else:
            text += 'exec:\tno\n\n'

        text += 'size:\t' + str(s) + ' bytes' + '\n'
        text += 'owner:\t' + owner + '\n'
        text += 'created:\t' + str(created.date().day()) + '.' + \
                str(created.date().month()) + '.' + \
                str(created.date().year()) + '  ' + \
                created.time().toString() + '\n'

        q = MessageBox(QMessageBox.Information, fileName, text,
                       QMessageBox.NoButton)
        q.exec_()

    def onCreateNewFolder(self):
        if not self.index:
            return
        else:
            index = self.index
            fileName, filePath, fileDir, fileInfo = self.getFileInformation()

            path = os.getcwd() + '/'
            path = self.checkPath(path)

        index = self.index
        fileName, filePath, fileDir, fileInfo = self.getFileInformation()

        dialog = EnterDialog(self.mainWindow, fileName, \
                             filePath, fileDir, fileInfo, False, path)
        dialog.exec_()

        # return

    def onCopy(self):
        if not self.index:
            return

        fileName, filePath, fileDir, fileInfo = self.getFileInformation()

        if fileName == '..':
            self.clearSelection()
            self.mainWindow.statusBar.showMessage("can't copy this item !",
                                                  3000)
            self.copySourceFilePath = None
            self.copySourceFileName = None
            self.isCopyFileFolder = None
            return

        if fileDir:
            self.copySourceFilePath = filePath
            self.copySourceFileName = fileName
            self.isCopyFileFolder = True
            self.mainWindow.statusBar.showMessage('Path: <' + \
                                                   self.copySourceFileName + \
                                                   '> marked', 3000)
        else:
            self.copySourceFilePath = filePath
            self.copySourceFileName = fileName
            self.mainWindow.statusBar.showMessage('File: <' + \
                                                   self.copySourceFileName + \
                                                   '> marked', 3000)

    def onPaste(self):
        if not self.index:
            return

        if not self.copySourceFilePath:
            self.mainWindow.statusBar.showMessage('nothing marked !', 3000)
            return

        if not self.copySourceFileName:
            self.mainWindow.statusBar.showMessage('nothing marked !', 3000)
            return

        fileName, filePath, fileDir, fileInfo = self.getFileInformation()

        rootPath = os.getcwd()
        rootPath = self.checkPath(rootPath)
        fileList = os.listdir(self.path)  # File list at current path

        if fileName == '..':
            # clicked on '..'
            rootPath = os.getcwd()
            rootPath = self.checkPath(rootPath)

        if fileDir and self.isCopyFileFolder:
            # copy path to another path
            if fileName == '..':
                path = os.getcwd()
                path = self.checkPath(path)
            else:
                path = filePath

            newPath = path + '/' + self.copySourceFileName
            fileList = os.listdir(path)

            if self.copySourceFileName in fileList:
                q = MessageBox(
                    QMessageBox.Warning, "Error",
                    "Another path with the same name already exists.",
                    QMessageBox.NoButton)
                q.exec_()

                self.resetMarkedItems(True)
                return

            if self.copySourceFilePath in newPath:

                q = MessageBox(QMessageBox.Critical, 'Error',
                               'Name of path already exists in new path',
                               QMessageBox.NoButton)

                q.exec_()
                self.resetMarkedItems(True)
                return

            try:
                os.mkdir(newPath)
                self.copytree(self.copySourceFilePath, newPath)
                self.mainWindow.statusBar.showMessage('Done !', 3000)

            except Exception as e:
                q = MessageBox(QMessageBox.Critical, "Error", str(e),
                               QMessageBox.NoButton)
                q.exec_()

                self.resetMarkedItems(True)
                return

        elif fileDir and not self.isCopyFileFolder:
            # copy file to path
            if fileName == '..':
                path = os.getcwd()
                path = self.checkPath(path)
            else:
                path = filePath

            fileList = os.listdir(path)  # File list at current path

            if self.copySourceFileName in fileList:
                result = MessageBox(QMessageBox.Warning, "Warning",
                                    "File already exists.\n" + "Continue ?",
                                    QMessageBox.Yes | QMessageBox.No)

                if (result.exec_() == QMessageBox.Yes):

                    try:
                        shutil.copy(self.copySourceFilePath, path)
                        self.mainWindow.statusBar.showMessage('Done !', 3000)

                    except Exception as e:
                        q = MessageBox(QMessageBox.Critical, "Error", str(e),
                                       QMessageBox.NoButton)
                        q.exec_()

                        self.resetMarkedItems(True)
                        return
                else:
                    self.resetMarkedItems()
                    return
            else:

                try:
                    shutil.copy(self.copySourceFilePath, path)
                    self.mainWindow.statusBar.showMessage('Done !', 3000)

                except Exception as e:
                    q = MessageBox(QMessageBox.Critical, "Error", str(e),
                                   QMessageBox.NoButton)
                    q.exec_()

                    self.resetMarkedItems(True)
                    return

        elif not fileDir and self.isCopyFileFolder:
            # copy path to selected file -> correct this user input ...
            newPath = rootPath + '/' + self.copySourceFileName

            if self.copySourceFileName in fileList:
                q = MessageBox(
                    QMessageBox.Information, "Error",
                    "Another path with the same name already exists.",
                    QMessageBox.NoButton)
                q.exec_()

                self.resetMarkedItems(True)
                return

            try:
                os.mkdir(newPath)
                self.copytree(self.copySourceFilePath, newPath)
                self.mainWindow.statusBar.showMessage('Done !', 3000)

            except Exception as e:
                q = MessageBox(QMessageBox.Critical, "Error", str(e),
                               QMessageBox.NoButton)
                q.exec_()

                self.resetMarkedItems(True)
                return

        elif not fileDir and not self.isCopyFileFolder:
            # user copy file to another file -> correct this input
            fileList = os.listdir(rootPath)  # File list in current path

            if self.copySourceFileName in fileList:
                result = MessageBox(
                    QMessageBox.Warning, "Warning",
                    "File with this name already exists !" + "Continue ?",
                    QMessageBox.Yes | QMessageBox.No)

                if (result.exec_() == QMessageBox.Yes):

                    try:
                        shutil.copy(self.copySourceFilePath, rootPath)
                        self.mainWindow.statusBar.showMessage('Done !', 3000)

                    except Exception as e:
                        q = MessageBox(QMessageBox.Critical, "Error", str(e),
                                       QMessageBox.NoButton)
                        q.exec_()

                        self.resetMarkedItems(True)
                    return

                else:
                    self.resetMarkedItems(True)
                    return

            else:

                try:
                    shutil.copy(self.copySourceFilePath, rootPath)
                    self.mainWindow.statusBar.showMessage('Done !', 3000)

                except Exception as e:
                    q = MessageBox(QMessageBox.Critical, "Error", str(e),
                                   QMessageBox.Ok)
                    q.exec_()

                    self.resetMarkedItems(True)
                    return

        self.resetMarkedItems()

    def resetMarkedItems(self, showMessage=False):
        # reset marked items
        self.copySourceFilePath = None
        self.copySourceFileName = None
        self.isCopyFileFolder = None

        if showMessage:
            self.mainWindow.statusBar.showMessage('Mark removed !', 3000)

    # copy path with subfolder
    #  thanks to stackoverflow.com ..... !
    def copytree(self, src, dst, symlinks=False, ignore=None):
        if not os.path.exists(dst):
            os.makedirs(dst)
        if not os.path.exists(src):
            os.makedirs(src)

        for item in os.listdir(src):
            s = os.path.join(src, item)
            d = os.path.join(dst, item)

            if os.path.isdir(s):
                self.copytree(s, d, symlinks, ignore)
            else:
                if not os.path.exists(
                        d) or os.stat(s).st_mtime - os.stat(d).st_mtime > 1:
                    shutil.copy2(s, d)

    def onRename(self):
        if not self.index:
            return

        fileName, filePath, fileDir, fileInfo = self.getFileInformation()

        dialog = EnterDialog(self.mainWindow, fileName, filePath, fileDir,
                             fileInfo, True)
        dialog.exec_()

    def onDelete(self):
        if not self.index:
            return

        fileName, filePath, fileDir, fileInfo = self.getFileInformation()

        if fileDir:
            if fileName == '..':
                self.mainWindow.statusBar.showMessage(
                    'can not delete this item !', 3000)
                return
            else:
                result = MessageBox(QMessageBox.Warning, 'Delete directory',
                                    '<b>' + filePath + '</b>' + "  ?",
                                    QMessageBox.Yes | QMessageBox.No)

                if (result.exec_() == QMessageBox.Yes):
                    try:
                        shutil.rmtree(filePath)
                        self.mainWindow.statusBar.showMessage('Done !', 3000)
                        self.resetMarkedItems()

                    except Exception as e:
                        q = MessageBox(QMessageBox.Critical, "Error", str(e),
                                       QMessageBox.NoButton)
                        q.exec_()

                        self.resetMarkedItems(True)
                else:
                    return

        else:
            pathList = filePath.split('/')[:-1]
            path = ''
            for elem in pathList:
                path += elem + '/'
            file = filePath.split('/')[-1]
            result = MessageBox(QMessageBox.Warning, 'Delete file',
                                path + "<b>" + file + "</b>" + "  ?",
                                QMessageBox.Yes | QMessageBox.No)

            if (result.exec_() == QMessageBox.Yes):
                try:
                    os.remove(filePath)
                    self.mainWindow.statusBar.showMessage('Done !', 3000)
                except Exception as e:
                    q = MessageBox(QMessageBox.Critical, "Error", str(e),
                                   QMessageBox.NoButton)

                    q.exec_()

                    self.resetMarkedItems(True)

    def onTerminal(self):
        c = Configuration()
        system = c.getSystem()
        command = c.getTerminal(system)

        thread = RunThread(command)
        thread.start()

    def initItems(self):
        font = QFont()
        font.setPixelSize(16)

        self.prepareModel(os.getcwd())

        self.setToolTip(os.getcwd())

        # prepare drag and drop
        self.setDragEnabled(False)

        sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding,
                                 QSizePolicy.MinimumExpanding)
        self.setSizePolicy(sizePolicy)
        self.setAutoExpandDelay(2)
        self.setAlternatingRowColors(False)
        self.setAnimated(False)
        self.setIndentation(20)
        self.setSortingEnabled(False)
        self.setRootIsDecorated(False)
        self.setPalette(QPalette(Qt.black))

        self.verticalScrollBar().setStyleSheet("""border: 20px solid black;
            background-color: darkgreen;
            alternate-background-color: #FFFFFF;""")

        self.horizontalScrollBar().setStyleSheet("""border: 20px solid black;
            background-color: darkgreen;
            alternate-background-color: #FFFFFF;""")

        self.setFont(font)

        # signals
        self.doubleClicked.connect(self.onDoubleClicked)
        self.clicked.connect(self.onClicked)
        self.pressed.connect(self.onClicked)
        #self.entered.connect(self.onEntered)
        self.columnMoved()

        # that hides the size, file type and last modified colomns
        self.setHeaderHidden(True)
        self.hideColumn(1)
        self.hideColumn(2)
        self.hideColumn(3)
        self.resize(400, 400)

    def prepareModel(self, path):
        self.model = QFileSystemModel()
        self.model.setRootPath(path)
        #model.setFilter(QDir.AllDirs |QDir.NoDotAndDotDot | QDir.AllEntries)
        self.model.setFilter(QDir.Files | QDir.AllDirs | QDir.NoDot
                             | QDir.Hidden)
        #self.model.setNameFilters(self.filter)

        self.model.rootPathChanged.connect(self.onRootPathChanged)

        self.fsindex = self.model.setRootPath(path)

        self.setModel(self.model)
        self.setRootIndex(self.fsindex)

    def checkPath(self, path):
        if '\\' in path:
            path = path.replace('\\', '/')
        return path

    def getFileInformation(self):
        index = self.index
        indexItem = self.model.index(index.row(), 0, index.parent())

        fileName = self.model.fileName(indexItem)
        filePath = self.model.filePath(indexItem)
        fileDir = self.model.isDir(indexItem)
        fileInfo = self.model.fileInfo(indexItem)

        fileName = self.checkPath(fileName)
        filePath = self.checkPath(filePath)

        return (fileName, filePath, fileDir, fileInfo)

    def onClicked(self, index):
        self.index = index  #.... index des FileSystemModels
        indexItem = self.model.index(index.row(), 0, index.parent())

        fileName, filePath, fileDir, fileInfo = self.getFileInformation()
        self.setToolTip(filePath)

        if fileDir:
            self.path = self.checkPath(os.getcwd())
            self.filename = None
        else:
            self.filename = filePath
            self.path = self.checkPath(os.getcwd())

        #print('self.filename: ', self.filename)
        #print('self.path: ', self.path)

    def refresh(self, dir=None):
        if not dir:
            dir = self.checkPath(os.getcwd())
        else:
            dir = dir

        self.model.setRootPath(dir)

        if self.rootIsDecorated:
            self.setRootIsDecorated(False)

        self.clearSelection()

    def onDoubleClicked(self, index):
        self.index = index  #.... wie oben ... def onClicked(...):
        indexItem = self.model.index(index.row(), 0, index.parent())

        fileName, filePath, fileDir, fileInfo = self.getFileInformation()

        if fileDir:
            filePath = self.checkPath(filePath)
            try:
                os.chdir(filePath)
            except Exception as e:
                self.mainWindow.statusBar.showMessage(str(e), 3000)
            self.path = self.checkPath(os.getcwd())

            self.model.setRootPath(filePath)

            if self.rootIsDecorated:
                self.setRootIsDecorated(False)

        else:
            self.filename = filePath

            try:
                with open(self.filename, 'r') as f:
                    self.text = f.read()
            except Exception as e:
                self.mainWindow.statusBar.showMessage(str(e), 3000)

                self.filename = None
                return

            # debug
            if self.textPad:

                if not self.textPad.filename:
                    editor = CodeEditor(self.mainWindow)
                    editor.setText(self.text)
                    editor.filename = filePath
                    self.notebook.newTab(editor)
                    self.textPad = editor

                    x = self.notebook.count()  # number of tabs
                    index = x - 1
                    self.notebook.setCurrentIndex(index)
                    tabName = os.path.basename(editor.filename)

                    self.notebook.setTabText(x, tabName)
                    self.textPad = editor
                    #self.textPad.filename = filePath

                else:
                    editor = CodeEditor(self.mainWindow)
                    editor.setText(self.text)
                    editor.filename = filePath
                    tabName = os.path.basename(editor.filename)
                    self.notebook.newTab(editor)
                    x = self.notebook.count()  # number of tabs
                    index = x - 1
                    self.notebook.setCurrentIndex(index)
                    self.textPad = editor
                    #self.textPad.filename = filePath

            if not self.textPad:
                editor = CodeEditor(self.mainWindow)
                editor.filename = None
                self.notebook.newTab(editor)
                x = self.notebook.count()
                index = x - 1
                self.notebook.setCurrentIndex(index)
                self.textPad = editor
                #self.textPad.filename = filePath

            # make codeView
            codeViewList = self.codeView.makeDictForCodeView(self.text)
            self.codeView.updateCodeView(codeViewList)

            # update textPad Autocomplete
            autocomplete = Qsci.QsciAPIs(self.textPad.lexer)
            self.textPad.autocomplete = autocomplete
            self.textPad.setPythonAutocomplete()

        self.clearSelection()

    def onRootPathChanged(self):
        self.setModel(None)
        self.setModel(self.model)
        self.fsindex = self.model.setRootPath(QDir.currentPath())
        self.setRootIndex(self.fsindex)
        sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding,
                                 QSizePolicy.MinimumExpanding)
        self.setSizePolicy(sizePolicy)
        self.setAutoExpandDelay(2)
        self.setAlternatingRowColors(False)
        self.setAnimated(True)
        self.setIndentation(20)
        self.setSortingEnabled(False)
        self.setRootIsDecorated(False)

        self.setHeaderHidden(True)
        self.hideColumn(1)
        self.hideColumn(2)
        self.hideColumn(3)
        self.setToolTip(QDir.currentPath())
        self.path = os.getcwd()
        self.path = self.checkPath(self.path)
예제 #8
0
class FileManager(QWidget):
    def __init__(self, parent):
        QWidget.__init__(self)
        self.parent = parent
        self.name = 'File Manager'
        self.port = '9080'
        self.server = None

        drives = win32api.GetLogicalDriveStrings().split('\\\000')[:-1]
        self.logical_drives = drives + [d+'/' for d in drives]
        # create file manager tab
        self.file_manager_layout = QGridLayout(self)

        # create left manager (PC)
        self.left_up_btn = QPushButton()
        self.left_up_btn.setIcon(QIcon('images/up_btn.png'))
        self.left_up_btn.setFixedWidth(25)
        self.file_manager_layout.addWidget(self.left_up_btn, 0, 0, 1, 1)

        self.left_dir_path = QLineEdit(self.parent.expanduser_dir)
        self.file_manager_layout.addWidget(self.left_dir_path, 0, 1, 1, 8)

        self.left_go_to_btn = QPushButton()
        self.left_go_to_btn.setIcon(QIcon('images/right_btn.png'))
        self.left_go_to_btn.setFixedWidth(25)
        self.file_manager_layout.addWidget(self.left_go_to_btn, 0, 9, 1, 1)

        self.lefttableview = QTableView()
        self.lefttableview.setSelectionBehavior(QTableView.SelectRows)
        self.lefttableview.verticalHeader().hide()
        self.lefttableview.setShowGrid(False)
        self.lefttableview.contextMenuEvent = lambda event: self.left_context(event)

        self.left_file_model = QFileSystemModel()
        self.left_file_model.setFilter(QDir.AllEntries | QDir.NoDotAndDotDot)
        self.left_file_model.setRootPath(self.parent.expanduser_dir)
        self.left_file_model_path = self.parent.expanduser_dir
        self.lefttableview.setModel(self.left_file_model)
        self.lefttableview.setColumnWidth(0, 150)
        self.lefttableview.setRootIndex(self.left_file_model.index(self.parent.expanduser_dir))
        self.file_manager_layout.addWidget(self.lefttableview, 1, 0, 5, 10)

        # central buttons
        self.download_file_from_device_btn = QPushButton()
        self.download_file_from_device_btn.setIcon(QIcon('images/left_btn.png'))
        self.download_file_from_device_btn.setFixedWidth(30)
        self.download_file_from_device_btn.setEnabled(False)
        self.upload_file_to_device_btn = QPushButton()
        self.upload_file_to_device_btn.setIcon(QIcon('images/right_btn.png'))
        self.upload_file_to_device_btn.setFixedWidth(30)
        self.upload_file_to_device_btn.setEnabled(False)
        self.delete_file_btn = QPushButton()
        self.delete_file_btn.setIcon(QIcon('images/delete_btn.png'))
        self.delete_file_btn.setFixedWidth(30)
        self.file_manager_layout.addWidget(self.download_file_from_device_btn, 3, 10, 1, 1)
        self.file_manager_layout.addWidget(self.delete_file_btn, 4, 10, 1, 1)

        # create right manager (Device)
        self.right_up_btn = QPushButton()
        self.right_up_btn.setIcon(QIcon('images/up_btn.png'))
        self.right_up_btn.setFixedWidth(25)
        self.right_up_btn.setEnabled(False)
        self.file_manager_layout.addWidget(self.right_up_btn, 0, 11, 1, 1)

        self.add_folder_btn = QPushButton()
        self.add_folder_btn.setIcon(QIcon('images/folder_add.png'))
        self.add_folder_btn.setFixedWidth(25)
        self.add_folder_btn.setToolTip(_('Add new folder'))
        self.add_folder_btn.setEnabled(False)
        self.file_manager_layout.addWidget(self.add_folder_btn, 0, 12, 1, 1)

        self.right_dir_path = QLineEdit()
        self.file_manager_layout.addWidget(self.right_dir_path, 0, 13, 1, 7)

        self.right_update_btn = QPushButton()
        self.right_update_btn.setIcon(QIcon('images/update.png'))
        self.right_update_btn.setFixedWidth(25)
        self.file_manager_layout.addWidget(self.right_update_btn, 0, 20, 1, 1)

        self.righttableview = QTableView()
        self.righttableview.setSelectionBehavior(QTableView.SelectRows)
        self.righttableview.contextMenuEvent = lambda event: self.right_context(event)
        self.righttableview.verticalHeader().hide()
        self.righttableview.setShowGrid(False)
        self.right_file_model = QStandardItemModel()
        self.right_file_model_path = []
        self.right_active_dir = None
        self.righttableview.setModel(self.right_file_model)
        self.file_manager_layout.addWidget(self.righttableview, 1, 11, 5, 10)

        # auto sync
        self.timer = QTimer()
        self.timer.setInterval(10000)
        self.file_models_auto_sync = QCheckBox(_('Auto sync'))
        self.left_file_model_auto_sync_label = QLineEdit()
        self.left_file_model_auto_sync_label.setReadOnly(True)
        self.right_file_model_auto_sync_label = QLineEdit()
        self.right_file_model_auto_sync_label.setReadOnly(True)
        self.file_manager_layout.addWidget(self.file_models_auto_sync, 6, 9, 1, 3, alignment=Qt.AlignCenter)
        self.file_manager_layout.addWidget(self.left_file_model_auto_sync_label, 6, 0, 1, 9)
        self.file_manager_layout.addWidget(self.right_file_model_auto_sync_label, 6, 12, 1, 9)

        self.timer.timeout.connect(lambda: self.check_device_sync())
        self.lefttableview.clicked.connect(lambda idx: self.left_file_model_clicked(idx))
        self.lefttableview.doubleClicked.connect(lambda idx: self.left_file_model_doubleclicked(idx))
        self.left_up_btn.clicked.connect(lambda: self.left_file_model_up(self.left_file_model.index(self.left_dir_path.text())))
        self.left_go_to_btn.clicked.connect(lambda: self.left_file_model_go_to_dir())

        self.right_update_btn.clicked.connect(lambda: self.right_file_model_update())
        self.righttableview.doubleClicked.connect(lambda idx: self.right_file_model_doubleclicked(idx))
        self.right_up_btn.clicked.connect(lambda: self.right_file_model_up())
        self.add_folder_btn.clicked.connect(lambda: self.right_file_model_add_folder())
        self.righttableview.clicked.connect(lambda idx: self.right_file_model_clicked(idx))
        self.download_file_from_device_btn.clicked.connect(lambda: self.download_file_from_device())
        self.upload_file_to_device_btn.clicked.connect(lambda: self.upload_file_to_device())
        self.delete_file_btn.clicked.connect(lambda: self.delete_file_from_file_model())

        self.parent.settings_widget.signal_ip_changed.connect(lambda ip: self.change_ip(ip))

        self.parent.signal_language_changed.connect(lambda: self.retranslate())

    def retranslate(self):
        self.file_models_auto_sync.setText(_('Auto sync'))
        self.right_file_model.setHorizontalHeaderLabels([_('Name'), _('Size'), _('Changed date')])

    def change_ip(self, ip):
        self.server = ':'.join([ip, self.port])
        self.right_file_model_path = []
        self.right_file_model.clear()

    def left_file_model_clicked(self, idx):
        if os.path.isfile(self.left_file_model.filePath(idx)) and self.parent.geoshark_widget.device_on_connect:
            self.upload_file_to_device_btn.setEnabled(True)
        else:
            self.upload_file_to_device_btn.setEnabled(False)

    def left_file_model_doubleclicked(self, idx):
        self.left_up_btn.setEnabled(True)
        fileinfo = self.left_file_model.fileInfo(idx)
        if fileinfo.isDir():
            self.lefttableview.setRootIndex(idx)
            self.left_dir_path.setText(self.left_file_model.filePath(idx))
            self.left_file_model_path = self.left_file_model.filePath(idx)

    def left_file_model_up(self, idx):
        self.upload_file_to_device_btn.setEnabled(False)
        if self.left_dir_path.text() in self.logical_drives:
            self.left_file_model = QFileSystemModel()
            self.left_file_model.setFilter(QDir.AllEntries | QDir.NoDotAndDotDot)
            self.left_file_model.setRootPath('')
            self.lefttableview.setModel(self.left_file_model)
            self.left_dir_path.setText('My computer')
            self.left_up_btn.setEnabled(False)
        else:
            fileinfo = self.left_file_model.fileInfo(idx)
            dir = fileinfo.dir()
            self.left_dir_path.setText(dir.path())
            self.left_file_model_path = dir.path()
            self.lefttableview.setRootIndex(self.left_file_model.index(dir.absolutePath()))

    def left_file_model_go_to_dir(self):
        if os.path.isdir(self.left_dir_path.text()):
            self.left_file_model_path = self.left_dir_path.text()
            self.left_up_btn.setEnabled(True)
            self.upload_file_to_device_btn.setEnabled(False)
            self.left_file_model.setRootPath(self.left_dir_path.text())
            self.lefttableview.setRootIndex(self.left_file_model.index(self.left_dir_path.text()))

    def right_file_model_update(self):
        if not self.parent.geoshark_widget.device_on_connect:
            return
        url = 'http://{}/active_dir'.format(self.server)
        try:
            res = requests.get(url, timeout=5)
            if res.ok:
                self.right_active_dir = res.text
        except requests.exceptions.RequestException:
            pass

        file_list = self.get_folder_list()
        if file_list is None:
            return
        self.fill_right_file_model(file_list)
        self.download_file_from_device_btn.setEnabled(False)

    def get_folder_list(self, folder_path=None):
        if self.server is None:
            return
        if folder_path is None:
            folder_path = '/'.join(self.right_file_model_path)
        url = 'http://{}/data/{}'.format(self.server, folder_path)
        try:
            res = requests.get(url, timeout=1)
        except requests.exceptions.RequestException:
            show_error(_('GeoShark error'), _('GeoShark is not responding.'))
            return
        if res.ok:
            res = res.json()
            return res
        else:
            return None

    def check_device_sync(self):
        pc_path = self.left_file_model_auto_sync_label.text()
        device_path = self.right_file_model_auto_sync_label.text()
        if self.file_models_auto_sync.isChecked() and pc_path != '' and device_path != '':
            file_list = self.get_folder_list(self.right_file_model_auto_sync_label.text())
            left_list_of_files = os.listdir(self.left_file_model_auto_sync_label.text())
            for f in file_list:
                if f['name'] not in left_list_of_files or os.path.getsize('{}/{}'.format(pc_path, f['name'])) != f['size']:
                    self.download_file_from_device(device_path='{}/{}'.format(device_path, f['name']),
                                                   pc_path=pc_path)

    def fill_right_file_model(self, directory):
        self.add_folder_btn.setEnabled(True)
        if len(self.right_file_model_path) < 1:
            self.right_up_btn.setEnabled(False)
        else:
            self.right_up_btn.setEnabled(True)
            self.add_folder_btn.setEnabled(False)
        self.right_file_model.removeRows(0, self.right_file_model.rowCount())
        self.right_dir_path.setText('/'.join(self.right_file_model_path))
        self.right_file_model.setHorizontalHeaderLabels([_('Name'), _('Size'), _('Changed date')])
        for row, instance in enumerate(directory):
            if instance['name'] == self.right_active_dir:
                image = QIcon('images/directory_active.png')
            else:
                image = QIcon('images/{}.png'.format(instance['type']))
            item = QStandardItem(image, instance['name'])
            item.setData(instance['type'], 5)
            item.setEditable(False)
            self.right_file_model.setItem(row, 0, item)
            item = QStandardItem(str(instance['size']))
            item.setEditable(False)
            self.right_file_model.setItem(row, 1, item)
            item = QStandardItem(str(datetime.datetime.fromtimestamp(instance['changed']).strftime('%d.%m.%Y %H:%M')))
            item.setEditable(False)
            self.right_file_model.setItem(row, 2, item)

        self.righttableview.setColumnWidth(0, max(150, self.righttableview.columnWidth(0)))

    def left_context(self, event):
        context_menu = {}
        index = self.lefttableview.indexAt(event.pos())
        if index.row() == -1:
            return
        context_menu[_('Set active directory')] = lambda: self.set_pc_active_directory(self.left_file_model.filePath(index))
        context_menu[_('Remove element')] = lambda: self.delete_file_from_file_model(index)

        if not self.left_file_model.isDir(index):
            del context_menu[_('Set active directory')]

        menu = QMenu()

        actions = [QAction(a) for a in context_menu.keys()]
        menu.addActions(actions)
        action = menu.exec_(event.globalPos())
        if action:
            context_menu[action.text()]()

    def set_pc_active_directory(self, path):
        self.left_file_model_auto_sync_label.setText(path)
        self.parent.settings_widget.left_folder_tracked.setText(path)

    def right_context(self, event):
        context_menu = {}

        index = self.righttableview.indexAt(event.pos())
        if index.row() == -1:
            return
        item = self.right_file_model.itemFromIndex(index)
        item_row = item.row()

        context_menu[_('Set active directory')] = lambda: self.set_active_directory(item)
        context_menu[_('Remove element')] = lambda: self.delete_file_from_file_model(index)

        if self.right_file_model.item(item_row, 0).data(5) != 'directory':
            del context_menu[_('Set active directory')]

        menu = QMenu()

        actions = [QAction(a) for a in context_menu.keys()]
        menu.addActions(actions)
        action = menu.exec_(event.globalPos())
        if action:
            context_menu[action.text()]()

    def set_active_directory(self, item):
        if not self.parent.geoshark_widget.device_on_connect:
            return
        dirname = item.text()
        url = 'http://{}/active_dir'.format(self.server)
        try:
            res = requests.post(url=url, data=dirname, timeout=5)
        except requests.exceptions.RequestException:
            show_error(_('GeoShark error'), _('Can not set active directory.\nGeoShark is not responding.'))
            return
        if res.ok:
            self.right_file_model_update()
            self.set_active_path(dirname)
        elif res.status_code == 400:
            show_error(_('GeoShark error'), _('Request declined - request body specifies invalid path.'))
            return
        elif res.status_code == 409:
            show_error(_('GeoShark error'), _('Request declined - switching active directory is forbidden during active session.'))
            return
        else:
            print(res.status_code)
            return

    def set_active_path(self, dirname):
        path = '/'.join(self.right_file_model_path + [dirname])
        self.parent.settings_widget.right_folder_tracked.setText(path)
        self.right_file_model_auto_sync_label.setText(path)

    def right_file_model_clicked(self, idx):
        if not self.parent.geoshark_widget.device_on_connect:
            return
        if self.right_file_model.item(idx.row(), 0).data(5) == 'file':
            self.download_file_from_device_btn.setEnabled(True)
        else:
            self.download_file_from_device_btn.setEnabled(False)

    def right_file_model_doubleclicked(self, idx):
        if not self.parent.geoshark_widget.device_on_connect:
            return
        model_path = '/'.join(self.right_file_model_path)
        idx_name = self.right_file_model.item(idx.row(), 0).text()
        if model_path != '':
            dir = '{}/{}'.format(model_path, idx_name)
        else:
            dir = '{}'.format(idx_name)

        file_list = self.get_folder_list(dir)
        if file_list is None:
            return
        self.right_file_model_path = dir.split('/')
        self.fill_right_file_model(file_list)

    def right_file_model_up(self):
        if not self.parent.geoshark_widget.device_on_connect:
            return
        self.download_file_from_device_btn.setEnabled(False)
        up_dir = '/'.join(self.right_file_model_path[:-1])

        file_list = self.get_folder_list(up_dir)
        if file_list is None:
            return
        if up_dir == '':
            self.right_file_model_path = []
        else:
            self.right_file_model_path = up_dir.split('/')
        self.fill_right_file_model(file_list)

    def right_file_model_add_folder(self):
        if not self.parent.geoshark_widget.device_on_connect:
            return
        row = self.right_file_model.rowCount()
        item = QStandardItem(QIcon('images/folder.png'), 'New Directory')
        item.setData('directory', 5)
        item.setEditable(True)
        self.right_file_model.setItem(row, 0, item)
        item = QStandardItem(str(0.0))
        item.setEditable(False)
        self.right_file_model.setItem(row, 1, item)
        item = QStandardItem(str(datetime.datetime.today().strftime('%d.%m.%Y %H:%M')))
        item.setEditable(False)
        self.right_file_model.setItem(row, 2, item)

    def download_file_from_device(self, device_path=None, pc_path=None):
        if not self.parent.geoshark_widget.device_on_connect or self.server is None:
            return

        if not device_path:
            fileName = self.find_selected_idx()
            if fileName:
                fileName = fileName.data()
                device_path = '/'.join(self.right_file_model_path + [fileName])
            else:
                return

        right_file_model_filename = device_path.split('/')[-1]
        save_to_file = '{}/{}'.format(self.left_file_model_path, right_file_model_filename) \
            if not pc_path else '{}/{}'.format(pc_path, right_file_model_filename)
        if os.path.isfile(save_to_file):
            answer = show_warning_yes_no(_('File warning'), _('There is a file with the same name in PC.\n'
                                         'Do you want to rewrite <b>{}</b>?'.format(right_file_model_filename)))
            if answer == QMessageBox.No:
                return
        url = 'http://{}/data/{}'.format(self.server, device_path)
        try:
            b = bytearray()
            res = requests.get(url, timeout=5, stream=True)
            if res.ok:
                progress = QProgressBar()
                progress.setFormat(right_file_model_filename)
                self.file_manager_layout.addWidget(progress, 6, 12, 1, 9)
                total_length = int(res.headers.get('content-length'))
                len_b = 0
                for chunk in tee_to_bytearray(res, b):
                    len_b += len(chunk)
                    progress.setValue((len_b/total_length)*99)
                    QApplication.processEvents()
            else:
                return
        except:
            self.file_manager_layout.addWidget(self.right_file_model_auto_sync_label, 6, 12, 1, 9)
            show_error(_('GeoShark error'), _('GeoShark is not responding.'))
            return

        if res.ok:
            progress.setValue(100)

            with open(save_to_file, 'wb') as file:
                file.write(b)
        for i in reversed(range(self.file_manager_layout.count())):
            if isinstance(self.file_manager_layout.itemAt(i).widget(), QProgressBar):
                self.file_manager_layout.itemAt(i).widget().setParent(None)
        self.file_manager_layout.addWidget(self.right_file_model_auto_sync_label, 6, 12, 1, 9)

    def upload_file_to_device(self):
        if not self.parent.geoshark_widget.device_on_connect or self.server is None:
            return
        file = self.left_file_model.filePath(self.lefttableview.currentIndex())
        filename = file.split('/')[-1]
        url = 'http://{}/data/{}'.format(self.server, '/'.join(self.right_file_model_path))
        filesize = os.path.getsize(file)
        if filesize == 0:
            show_error(_('File error'), _('File size must be non zero.'))
            return
        progress = ProgressBar(text=_('Upload File Into GeoShark'), window_title=_('Upload file to GeoShark'))
        encoder = MultipartEncoder(
            fields={'upload_file': (filename, open(file, 'rb'))}  # added mime-type here
        )
        data = MultipartEncoderMonitor(encoder, lambda monitor: progress.update((monitor.bytes_read/filesize)*99))

        try:
            res = requests.post(url, data=data, headers={'Content-Type': encoder.content_type}, timeout=5)
        except requests.exceptions.RequestException:
            progress.close()
            show_error(_('GeoShark error'), _('GeoShark is not responding.'))
            return
        if res.ok:
            progress.update(100)
            self.right_file_model_update()

    def delete_file_from_file_model(self, index=None):
        selected = self.find_selected_idx()
        if index is None and selected is None:
            return
        if index is None:
            index = selected
        model = index.model()
        index_row = index.row()
        path = model.filePath(index) if hasattr(model, 'filePath') else model.index(index_row, 0).data()
        answer = show_warning_yes_no(_('Remove File warning'),
                                     _('Do you really want to remove:\n{}').format(path))
        if answer == QMessageBox.No:
            return

        if isinstance(model, QFileSystemModel):
            model.remove(index)

        elif isinstance(model, QStandardItemModel):
            if not self.parent.geoshark_widget.device_on_connect or self.server is None:
                return
            filename = self.right_file_model.item(index.row(), 0).text()
            path = '/'.join(self.right_file_model_path + [filename])

            url = 'http://{}/data/{}'.format(self.server, path)
            try:
                res = requests.delete(url)
            except requests.exceptions.RequestException:
                show_error(_('GeoShark error'), _('GeoShark is not responding.'))
                return
            if res.ok:
                self.right_file_model_update()
            elif res.status_code == 400:
                self.right_file_model.removeRow(index.row())
            elif res.status_code == 409:
                show_error(_('GeoShark error'),
                           _('Request declined - directory is the part of active session working directory.'))
                return

    def find_selected_idx(self):
        left_indexes = self.lefttableview.selectedIndexes()
        right_indexes = self.righttableview.selectedIndexes()
        if len(left_indexes) == 0 and len(right_indexes) == 0:
            return None
        index = left_indexes[0] if len(left_indexes) > len(right_indexes) else right_indexes[0]
        return index

    def save_file_models_folder(self):
        self.left_file_model_auto_sync_label.setText(self.parent.settings_widget.left_folder_tracked.text())
        self.right_file_model_auto_sync_label.setText(self.parent.settings_widget.right_folder_tracked.text())
예제 #9
0
class MainView(QMainWindow):

    resizeCompleted = pyqtSignal()

    def __init__(self, model, controller, image_path):
        self.settings = SettingsModel()
        self.slideshow = SlideshowModel()

        self.model = model
        self.canvas = self.model.canvas
        self.main_controller = controller
        self.canvas_controller = CanvasController(self.canvas)
        super(MainView, self).__init__()
        self.build_ui()
        self.center_ui()

        # Resize timer to prevent laggy updates
        self.resize_timer = None
        self.resizeCompleted.connect(self.resize_completed)

        # Slideshow
        if self.settings.get('Slideshow', 'reverse') == 'True':
            self.slideshow.updateSignal.connect(self.on_previous_item)
        else:
            self.slideshow.updateSignal.connect(self.on_next_item)
            
        self.model.subscribe_update_func(self.update_ui_from_model)

        self.arguments = {
            'image_path': image_path
        }

    def build_ui(self):
        self.ui = Ui_Hitagi()
        self.ui.setupUi(self)

        # File menu
        self.ui.actionSet_as_wallpaper.triggered.connect(self.on_set_as_wallpaper)
        self.ui.actionCopy_to_clipboard.triggered.connect(self.on_clipboard)
        self.ui.actionOpen_current_directory.triggered.connect(self.on_current_dir)
        self.ui.actionOptions.triggered.connect(self.on_options)
        self.ui.actionExit.triggered.connect(self.on_close)

        # Folder menu 
        self.ui.actionOpen_next.triggered.connect(self.on_next_item)
        self.ui.actionOpen_previous.triggered.connect(self.on_previous_item)
        self.ui.actionChange_directory.triggered.connect(self.on_change_directory)
        self.ui.actionSlideshow.triggered.connect(self.on_slideshow)

        # View menu
        self.ui.actionZoom_in.triggered.connect(self.on_zoom_in)
        self.ui.actionZoom_out.triggered.connect(self.on_zoom_out)
        self.ui.actionOriginal_size.triggered.connect(self.on_zoom_original)
        self.ui.actionRotate_clockwise.triggered.connect(self.on_rotate_clockwise)
        self.ui.actionRotate_counterclockwise.triggered.connect(self.on_rotate_counterclockwise)
        self.ui.actionFlip_horizontally.triggered.connect(self.on_flip_horizontal)
        self.ui.actionFlip_vertically.triggered.connect(self.on_flip_vertical)
        self.ui.actionFit_image_width.triggered.connect(self.on_scale_image_to_width)
        self.ui.actionFit_image_height.triggered.connect(self.on_scale_image_to_height)
        self.ui.actionFile_list.triggered.connect(self.on_toggle_filelist)
        self.ui.actionFullscreen.triggered.connect(self.on_fullscreen)

        # Favorite menu
        self.ui.actionAdd_to_favorites.triggered.connect(self.on_add_to_favorites)
        self.ui.actionRemove_from_favorites.triggered.connect(self.on_remove_from_favorites)

        # Help menu
        self.ui.actionChangelog.triggered.connect(self.on_changelog)
        self.ui.actionAbout.triggered.connect(self.on_about)

        # Load stylesheet
        stylesheet_dir = "resources/hitagi.stylesheet"
        with open(stylesheet_dir, "r") as sh:
            self.setStyleSheet(sh.read())
        
        # File listing
        self.file_model = QFileSystemModel()
        self.file_model.setFilter(QDir.NoDotAndDotDot | QDir.AllDirs | QDir.Files)
        self.file_model.setNameFilters(['*.bmp', '*.gif', '*.jpg', '*.jpeg', '*.png', '*.png', '*.pbm', '*.pgm', '*.ppm', '*.xbm', '*.xpm'])
        self.file_model.setNameFilterDisables(False)
        self.file_model.setRootPath(self.settings.get('Directory', 'default'))

        self.ui.treeView.setModel(self.file_model)
        self.ui.treeView.setColumnWidth(0, 120)
        self.ui.treeView.setColumnWidth(1, 120)
        self.ui.treeView.hideColumn(1)
        self.ui.treeView.hideColumn(2)

        # Double click
        self.ui.treeView.activated.connect(self.on_dir_list_activated)
        # Update file list
        self.ui.treeView.clicked.connect(self.on_dir_list_clicked)
        # Open parent
        self.ui.pushButton_open_parent.clicked.connect(self.on_open_parent)
        self.ui.pushButton_favorite.clicked.connect(self.on_manage_favorite)

        # Shortcuts
        _translate = QCoreApplication.translate
        self.ui.actionExit.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Exit')))

        self.ui.actionOpen_next.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Next')))
        self.ui.actionOpen_previous.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Previous')))
        self.ui.actionChange_directory.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Directory')))
        self.ui.actionAdd_to_favorites.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Add to favorites')))
        self.ui.actionRemove_from_favorites.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Remove from favorites')))
        self.ui.actionSlideshow.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Slideshow')))

        self.ui.actionZoom_in.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Zoom in')))
        self.ui.actionZoom_out.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Zoom out')))
        self.ui.actionOriginal_size.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Zoom original')))
        self.ui.actionRotate_clockwise.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Rotate clockwise')))
        self.ui.actionRotate_counterclockwise.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Rotate counterclockwise')))
        self.ui.actionFlip_horizontally.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Flip horizontal')))
        self.ui.actionFlip_vertically.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Flip vertical')))
        self.ui.actionFit_image_width.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Fit to width')))
        self.ui.actionFit_image_height.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Fit to height')))
        self.ui.actionFile_list.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Toggle filelist')))
        self.ui.actionFullscreen.setShortcut(_translate("Hitagi", self.settings.get('Hotkeys', 'Fullscreen')))

        # Load favorites in UI
        self.load_favorites()

        # Background
        self.ui.graphicsView.setBackgroundBrush(QBrush(QColor(self.settings.get('Look', 'background')), Qt.SolidPattern))

        # Save current height for fullscreen mode
        self.default_menubar_height = self.ui.menubar.height()
        # Save current width for file list
        self.default_filelist_width = self.ui.fileWidget.width()

    def load_favorites(self):
        self.favorites = FavoritesModel()
        self.ui.menuFavorites.clear()
        for item in self.favorites.items():
            self.ui.menuFavorites.addAction(item).triggered.connect((lambda item: lambda: self.on_open_favorite(item))(item))

    def on_open_favorite(self, path):
        self.main_controller.change_directory(path)

    def center_ui(self):
        ui_geometry = self.frameGeometry()
        center_point = QDesktopWidget().availableGeometry().center()
        ui_geometry.moveCenter(center_point)
        self.move(ui_geometry.topLeft())
   
    # Qt show event
    def showEvent(self, event):
        self.main_controller.start(self.arguments['image_path']) # Arguments and starting behaviour

        # Start in fullscreen mode according to settings
        if self.settings.get('Misc', 'fullscreen_mode') == 'True':
            self.on_fullscreen()
            
        # Initialize container geometry to canvas
        self.canvas_controller.update(self.ui.graphicsView.width(), self.ui.graphicsView.height())
        self.main_controller.update_canvas()

    def update_resize_timer(self, interval=None):
        if self.resize_timer is not None:
            self.killTimer(self.resize_timer)
        if interval is not None:
            self.resize_timer = self.startTimer(interval)
        else:
            self.resize_timer = None

    # Qt resize event
    def resizeEvent(self, event):
        self.update_resize_timer(300)

    # Qt timer event
    def timerEvent(self, event):
        if event.timerId() == self.resize_timer:
            self.update_resize_timer()
            self.resizeCompleted.emit()

    def resize_completed(self):
        self.canvas_controller.update(self.ui.graphicsView.width(), self.ui.graphicsView.height())
        self.main_controller.update_canvas()
        
    # Additional static shortcuts
    def keyPressEvent(self, e):
        if e.key() == Qt.Key_Escape and self.model.is_fullscreen:
            self.main_controller.toggle_fullscreen()

    def on_open_parent(self):
        parent_index = self.file_model.parent(self.file_model.index(self.file_model.rootPath()))
        self.file_model.setRootPath(self.file_model.filePath(parent_index))
        self.ui.treeView.setRootIndex(parent_index)

        # Update directory path
        self.model.directory = self.file_model.filePath(parent_index)

        self.update_ui_from_model()

    def on_dir_list_activated(self, index):
        if self.file_model.isDir(index) is not False:
            self.file_model.setRootPath(self.file_model.filePath(index))
            self.ui.treeView.setRootIndex(index)

            # Save current path
            self.model.directory = self.file_model.filePath(index)
            self.update_ui_from_model()
        
    def on_dir_list_clicked(self, index):
        self.main_controller.open_image(self.file_model.filePath(index))

    # File menu
    def on_set_as_wallpaper(self):
        from hitagilib.view.WallpaperView import WallpaperDialog
        from hitagilib.controller.wallpaper import WallpaperController

        image = self.model.get_image()
        if image is not None:
            dialog = WallpaperDialog(self, None, WallpaperController(self.model), image)
            dialog.show()

    def on_clipboard(self):
        self.main_controller.copy_to_clipboard()

    def on_current_dir(self):
        if not self.main_controller.open_in_explorer():
            self.show_explorer_error()

    def on_options(self):
        from hitagilib.view.OptionsView import OptionDialog
        self.dialog = OptionDialog(self)
        self.dialog.show()

    def on_close(self):
        if self.slideshow.isRunning():
            self.slideshow.exit()
        self.close()

    # Folder menu
    def on_next_item(self):
        current_index = self.ui.treeView.currentIndex()
        
        # Slideshow restart - determine if we are at the end of our file list
        if self.slideshow.is_running and self.settings.get('Slideshow', 'restart') == 'True' and not self.ui.treeView.indexBelow(current_index).isValid():
            self.main_controller.open_image(self.file_model.filePath(current_index))
            self.on_slideshow_restart(0) # Restart slideshow
        elif self.slideshow.is_running and self.settings.get('Slideshow', 'random') == 'True':
            # Random index - moveCursor expects constants @http://doc.qt.io/qt-5/qabstractitemview.html#CursorAction-enum
            index = self.ui.treeView.moveCursor(randint(0,9), Qt.NoModifier)
            self.ui.treeView.setCurrentIndex(index)
            self.main_controller.open_image(self.file_model.filePath(index))
        else:
            # Proceed normally, scroll down
            index = self.ui.treeView.moveCursor(QAbstractItemView.MoveDown, Qt.NoModifier)
            self.ui.treeView.setCurrentIndex(index)
            self.main_controller.open_image(self.file_model.filePath(index))

    def on_previous_item(self):
        current_index = self.ui.treeView.currentIndex()
        
        # Slideshow restart (reverse) - determine if we are the the top of our file list
        if self.slideshow.is_running and self.settings.get('Slideshow', 'restart') == 'True' and not self.ui.treeView.indexAbove(current_index).isValid():
            self.main_controller.open_image(self.file_model.filePath(current_index))
            self.on_slideshow_restart(1) # Restart slideshow
        elif self.slideshow.is_running and self.settings.get('Slideshow', 'random') == 'True':
            # Random index
            index = self.ui.treeView.moveCursor(randint(0,9), Qt.NoModifier)
            self.ui.treeView.setCurrentIndex(index)
            self.main_controller.open_image(self.file_model.filePath(index))
        else:
            # Proceed normally, scroll up
            index = self.ui.treeView.moveCursor(QAbstractItemView.MoveUp, Qt.NoModifier)
            self.ui.treeView.setCurrentIndex(index)
            self.main_controller.open_image(self.file_model.filePath(index))

    def on_slideshow(self):
        if self.ui.actionSlideshow.isChecked():
            self.slideshow.start()
            self.slideshow.is_running = True
        else:
            self.slideshow.is_running = False
            self.slideshow.exit()

    def on_slideshow_restart(self, direction):
        # 0: Restart from top to bottom
        # 1: Restart from bottom to top
        if direction == 0:
            index = self.ui.treeView.moveCursor(QAbstractItemView.MoveHome, Qt.NoModifier)
            self.main_controller.open_image(self.file_model.filePath(index))
        else:
            index = self.ui.treeView.moveCursor(QAbstractItemView.MoveEnd, Qt.NoModifier)
            self.main_controller.open_image(self.file_model.filePath(index))

        self.ui.treeView.setCurrentIndex(index)
            
        
    def on_change_directory(self):
        self.main_controller.change_directory()

    # View menu
    def on_zoom_in(self):
        self.canvas_controller.scale_image(1.1)

    def on_zoom_out(self):
        self.canvas_controller.scale_image(0.9)
    
    def on_rotate_clockwise(self):
        self.canvas_controller.rotate_image(90)

    def on_rotate_counterclockwise(self):
        self.canvas_controller.rotate_image(-90)

    def on_flip_horizontal(self):
        self.canvas_controller.flip_image(0)

    def on_flip_vertical(self):
        self.canvas_controller.flip_image(1)

    def on_scale_image_to_width(self):
        self.canvas_controller.update_image(1)

    def on_scale_image_to_height(self):
        self.canvas_controller.update_image(2)

    def on_zoom_original(self):
        self.canvas_controller.update_image(3)

    def on_toggle_filelist(self):
        if self.ui.fileWidget.isHidden():
            self.ui.fileWidget.show()
        else:
            self.ui.fileWidget.hide()
        self.update_resize_timer(300)
        
    def on_fullscreen(self):
        self.main_controller.toggle_fullscreen()
        
        if self.model.is_fullscreen:
            self.showFullScreen()
            if self.settings.get('Misc', 'hide_menubar') == 'True':
                self.ui.menubar.setMaximumHeight(0) # Workaround to preserve shortcuts
        else:
            self.showNormal()
            if self.settings.get('Misc', 'hide_menubar') == 'True':
                self.ui.menubar.setMaximumHeight(self.default_menubar_height)
        self.canvas_controller.update(self.ui.graphicsView.width(), self.ui.graphicsView.height())
        self.main_controller.update_canvas()

    # Favorite button
    def on_manage_favorite(self):
        if self.main_controller.check_favorites(self.model.directory):
            self.on_remove_from_favorites()
        else:
            self.on_add_to_favorites()

    # Favorite menu
    def on_add_to_favorites(self):
        self.main_controller.add_to_favorites()
        self.load_favorites()
        self.update_ui_from_model()

    def on_remove_from_favorites(self):
        self.main_controller.remove_from_favorites()
        self.load_favorites()
        self.update_ui_from_model()

    # Help menu
    def on_changelog(self):
        webbrowser.open('https://github.com/gimu/hitagi-reader/releases')

    def on_about(self):
        from hitagilib.view.AboutView import AboutDialog
        dialog = AboutDialog(self, None, None)
        dialog.show()

    def on_fileWidget_visibilityChanged(self, visible):
        """On file list hide/show and de/attachment"""
        if visible:
            self.ui.actionFile_list.setChecked(True)
        else:
            self.ui.actionFile_list.setChecked(False)
        self.update_resize_timer(300)

    def show_explorer_error(self):
        notify = QMessageBox()
        notify.setWindowTitle("Error")
        notify.setText(QCoreApplication.translate('Hitagi', "Couldn't open the current directory with an appropriate filemanager!"))
        notify.exec_()

    def update_ui_from_model(self):
        """Update UI from model."""
        self.settings = SettingsModel()

        # On changing directory
        self.file_model.setRootPath(self.model.directory)
        self.ui.treeView.setRootIndex(self.file_model.index(self.model.directory))

        # Update favorite button
        if self.main_controller.check_favorites(self.model.directory):
            self.ui.pushButton_favorite.setText(QCoreApplication.translate('Hitagi', "Unfavorite"))
        else:
            self.ui.pushButton_favorite.setText(QCoreApplication.translate('Hitagi', "Favorite"))

        # Canvas update
        self.ui.graphicsView.setScene(self.canvas.scene)
예제 #10
0
class DataWindow(QMdiSubWindow):
    def __init__(self, app, OAuth_token, parent):
        super().__init__()

        self.app = app
        self.token = OAuth_token
        self.parent = parent

        self.__threads = []

        self.initUI()

    def initUI(self):

        # Format the window
        self.format_window()

        # Create a horizontal layout to hold the widgets
        hbox = QHBoxLayout()

        # Add the widgets
        hbox.addWidget(self.set_directory_btn())
        hbox.addWidget(self.create_file_browser())
        hbox.addWidget(self.add_to_selection_btn())

        # Create a central widget for the local data window
        window_widget = QWidget()
        # Add the vertical box layout
        window_widget.setLayout(hbox)
        # Set the projects window widget
        self.setWidget(window_widget)

    def format_window(self):
        """
        Form the local data window
        :return:
        """
        # Gets the QRect of the main window
        geom = self.parent.geometry()
        # Gets the Qrect of the sections window
        section_geom = self.parent.section_geom

        # Define geometries for the projects window
        x0 = section_geom.x() + section_geom.width()
        y0 = section_geom.y()
        w = geom.width() - x0
        h = ((geom.height() - y0) / 3)
        self.setGeometry(x0, y0, w, h)
        # Remove frame from projects window
        self.setWindowFlags(Qt.FramelessWindowHint)

    #####
    # Window Widgets
    #####

    def set_directory_btn(self):
        """
        Creates a QPushButton that can be used to set the current root directory
        :return: QPushButton
        """

        btn = QPushButton()
        btn.setIcon(
            QIcon(os.path.normpath(__file__ + '/../../img/Folder-48.png')))
        press_button(self.app, btn)  # Format button
        btn.setToolTip("Select local directory")
        btn.setToolTipDuration(1)

        btn.pressed.connect(self.on_set_directory_pressed)

        return btn

    def create_file_browser(self):
        """
        Creates a QTreeView with a QFileSystemModel that is used as a file browser
        :return: QTreeview
        """

        self.browser = QTreeView()

        # Set the model of the QTreeView
        self.model = QFileSystemModel()
        home_dir = os.path.expanduser("~")  # Define the initial root directory
        self.model.setRootPath(home_dir)
        self.browser.setModel(self.model)

        # Resize the first column
        self.browser.setColumnWidth(0, self.geometry().width() / 3)

        # Control how selection of items works
        #self.browser.setSelectionBehavior(QAbstractItemView.SelectItems)  # Allow for only single item selection
        self.browser.setSelectionMode(
            QAbstractItemView.ExtendedSelection
        )  # Alow for multiple rows to be selected

        return self.browser

    def add_to_selection_btn(self):
        """
        Creates a QPushButton that can be used to open the metadata window for the selected items in the file browser
        :return: QPushButton
        """
        btn = QPushButton()
        btn.setIcon(
            QIcon(
                os.path.normpath(__file__ +
                                 '/../../img/Insert Row Below-48.png')))
        press_button(self.app, btn)  # Format button
        btn.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
        btn.setToolTip("Add selected items to articles list")
        btn.setToolTipDuration(1)

        btn.pressed.connect(self.on_open_selection_clicked)

        return btn

    #####
    # Widget Actions
    #####

    def on_set_directory_pressed(self):
        """
        Called when the set root directory button is pressed
        :return:
        """
        dir_name = self.user_set_dir()
        self.browser.setRootIndex(self.model.index(dir_name))

    def user_set_dir(self):
        """
        Creates a QFileDialog that prompts the user to choose a root directory
        :return: Sting. directory path
        """
        return str(QFileDialog.getExistingDirectory(self, "Select Directory"))

    def on_open_selection_clicked(self):
        """
        Called when the open selection button is clicked. Either open or closes the metadata window
        :return:
        """
        # Retrieve a set of file paths
        file_paths = self.get_selection_set()

        # Locally reference the Article List Widget
        article_tree = self.parent.data_articles_window.article_tree

        # Prevent the user from editting, searching, etc. while new articles are created.
        article_tree.disable_fields()

        # Create an article creation worker
        worker = ArticleCreationWorker(self.token, self.parent, file_paths)

        # Create the thread.
        load_articles_thread = QThread()
        load_articles_thread.setObjectName('local_articles_thread')
        self.__threads.append((load_articles_thread, worker))

        # Add the worker to the thread
        worker.moveToThread(load_articles_thread)

        # Connect signals from worker
        worker.sig_step.connect(article_tree.add_to_tree)
        worker.sig_step.connect(
            lambda article_id: article_tree.add_to_articles(article_id))
        worker.sig_done.connect(article_tree.enable_fields)
        worker.sig_done.connect(article_tree.update_search_field)
        worker.sig_done.connect(self.parent.data_articles_window.check_edit)
        load_articles_thread.started.connect(worker.work)

        # Begin worker thread
        load_articles_thread.start()

    def get_selection_set(self):
        """
        Creates a set of selected item file paths.
        :return:
        """
        # Get a list of selected items from the QTreeview
        items = self.browser.selectedIndexes()

        # Create an empty set to add file paths to
        file_paths = set()
        for item in items:
            # For items that are not directories
            if not self.model.isDir(item):
                file_paths.add(
                    self.model.filePath(item))  # Add the item file path
            else:
                # Combine the current set with a set of files contained within the directory. Does not recursively
                # open contained directories
                contained_files = self.get_child_files(
                    self.model.filePath(item))
                if contained_files is not None:
                    file_paths |= contained_files

        return file_paths

    @staticmethod
    def get_child_files(path):
        """
        given a path to a directory will return a set of file paths contained within. Does not recursively open internal
        directories
        :param path: string. path to directory
        :return: set. Containing file paths
        """
        dir = os.path.normpath(path)
        if os.path.isdir(dir):
            dir_contents = os.listdir(dir)

            file_set = set()
            for item in dir_contents:
                if not os.path.isdir(item):
                    file_set.add(os.path.join(dir, item))
            return file_set
        else:
            return None
예제 #11
0
class ProjectManager():

    def __init__(self, thread_manager):

        # Set attributes
        self.defaults = copy.deepcopy(defaults.project_settings)
        self.project_info = dict()
        self.FsModel = QFileSystemModel()
        self.ThreadManager = thread_manager
        self.Communicator = Communicator()
        self.root_path = ""
        self.project_loaded = False

    def close_project(self):
        self.root_path = ""
        self.project_loaded = False
        self.project_info = dict()

    def load_project(self, directory_path: str):
        self.root_path = directory_path
        self.FsModel.setRootPath(directory_path)
        self.read_project_info()
        self.project_loaded = True

    def is_project_loaded(self) -> bool:
        return self.project_loaded

    def read_project_info(self) -> None:
        """Reads project configuration data from default storage location"""
        if QFile.exists(self.root_path + "/.manuwrite/project.toml"):
            try:
                file = QFile()
                file.setFileName(self.root_path + "/.manuwrite/project.toml")
                file.open(QFile.ReadOnly)
                data = OrderedDict(toml.loads(file.readAll().data().decode()))
            except OSError:
                raise ProjectError("An error occured while reading project file")
            finally:
                file.close()
            self.project_info = data

            for key in self.defaults.keys():
                if key not in self.project_info:
                    self.project_info[key] = self.defaults[key]

            # Update project structure
            filenames = []
            for filename in self.get_setting_value("Files to render"):
                filenames.append(self.get_setting_value("Absolute path") + "/" + filename)

            self.ThreadManager.perform_operation("parse_project", self.on_MarkdownProjectParserThread_finished,
                                                 filepaths=filenames)
            self.set_setting_value("Absolute path", self.root_path)

        else:
            raise ProjectError("Project file doesn't exits")

    def save_project_data(self) -> None:
        """Saves project configuration data to permanent storage"""
        try:
            file = QFile()
            file.setFileName(self.root_path + "/.manuwrite/project.toml")
            file.open(QFile.WriteOnly)
            file.write(toml.dumps(self.project_info).encode())
        except OSError:
            raise ProjectError("Failed to write project configuration data")
        finally:
            file.close()

    @staticmethod
    def create_project(directory_path: str) -> None:
        """Creates a new project at given path"""

        try:
            directory = QDir(directory_path)

            directory.mkdir(".manuwrite")
            directory.mkdir("images")
            directory.mkdir("notes")
            directory.mkdir("data")

            directory.mkpath(".manuwrite/render")
            file = QFile()
            file.setFileName(directory_path + "/.manuwrite/project.toml")
            file.open(QFile.ReadWrite)

            project_settings = copy.deepcopy(defaults.project_settings)
            project_settings["Absolute path"] = {"type": "str", "value": directory_path}

            file.write(toml.dumps(project_settings).encode())
            file.close()
        except OSError:
            raise ProjectError("Error creating project files")

    def create_folder(self, path: str) -> None:
        """Creates an empty directory at given path"""
        directory = QDir(self.root_path)
        directory.mkdir(path)

    def delete_file(self, item: QModelIndex) -> None:
        """Deletes a file or a directory by given ModelIndex"""

        path = self.FsModel.filePath(item)
        try:
            # Check if path is dir or file
            if self.FsModel.isDir(item):
                directory = QDir(path)
                directory.removeRecursively()
            else:
                file = QFile(path)
                file.moveToTrash()
        except OSError:
            raise ProjectError("Error deleting file or directory")

    def rename(self, item: QModelIndex, name: str) -> None:
        """Renames a file or a directory at a given ModelIndex to given name"""

        directory = QDir(self.root_path)
        old_path = self.FsModel.filePath(item)
        new_path = old_path[:old_path.rfind(directory.separator())] + "/" + name
        try:
            directory.rename(old_path, new_path)
        except OSError:
            raise ProjectError("Error renaming file or directory")

    def create_file(self, path: str) -> None:
        """Creates a file at a given path"""

        try:
            file = QFile()
            file.setFileName(path)
            file.open(QFile.ReadWrite)
        except OSError:
            raise ProjectError
        finally:
            file.close()

    def get_setting_value(self, setting: str):
        """Return the value of a given setting"""
        return copy.deepcopy(self.project_info[setting]["value"])

    def set_setting_value(self, setting: str, value) -> None:
        """Sets the value of a given setting"""

        self.project_info[setting]["value"] = value

    def update_project_structure(self, file_structure: dict) -> None:
        """Updates filestructure of a file in raw project structure. Then generates a new combined project structure
        (from new raw structure) and updates corresponding project settings entry. Before all checks if the file
        structure belongs to a file that is to be rendered according to the current project settings"""

        filepath = file_structure["filepath"]
        del file_structure["filepath"]

        # Check if filepath is to be rendered, otherwise do not include it in project structure
        if not self.is_file_to_be_rendered(filepath):
            return

        # Get raw project structure from settings (in format {filepath: document_info}
        raw_project_structure = self.get_setting_value("Project structure raw")
        raw_project_structure[filepath] = file_structure

        combined_project_structure = self.get_combined_project_structure(raw_project_structure)

        self.set_setting_value("Project structure raw", raw_project_structure)
        self.set_setting_value("Project structure combined", combined_project_structure)

        # Send signal to main window to update project structure tree, if necessary
        self.Communicator.ProjectStructureUpdated.emit()

    def get_combined_project_structure(self, raw_project_structure: dict) -> dict:
        """Convert raw project structure ({filepath: document_info}) to combined document structure (document_info)"""
        combined = copy.deepcopy(defaults.document_info_template)
        del combined["filepath"]

        for key1, value1 in raw_project_structure.items():
            for key2, value2 in value1.items():
                combined[key2].update(value2)

        return combined

    def is_file_to_be_rendered(self, filepath: str) -> bool:
        files_to_render = self.get_setting_value("Files to render").copy()
        for i in range(len(files_to_render)):
            files_to_render[i] = self.get_setting_value("Absolute path") + "/" + files_to_render[i]

        # Check if filepath is to be rendered, otherwise do not include it in project structure
        if filepath in files_to_render:
            return True
        else:
            return False

    # Doesn't work as a slot for some reason
    #@pyqtSlot(dict)
    def on_MarkdownProjectParserThread_finished(self, project_structure: dict) -> None:
        """Update project structure with data received from MarkdownProjectParserThread"""

        self.set_setting_value("Project structure raw", project_structure)

        combined_project_structure = self.get_combined_project_structure(project_structure)
        self.set_setting_value("Project structure combined", combined_project_structure)

        self.Communicator.ProjectStructureUpdated.emit()
예제 #12
0
class MyApp(QWidget):
    def __init__(self):
        super().__init__()

        self.path = "C:"
        self.index = None

        self.tv = QTreeView(self)
        self.model = QFileSystemModel()
        self.btnRen = QPushButton("이름바꾸기")
        self.btnDel = QPushButton("파일삭제")
        self.layout = QVBoxLayout()

        self.setUi()
        self.setSlot()
        self.show()

    def setUi(self):
        self.setGeometry(300, 300, 700, 350)
        self.setWindowTitle("QFileSystemModel")
        self.model.setRootPath(self.path)
        self.tv.setModel(self.model)
        self.tv.setColumnWidth(0, 250)

        self.layout.addWidget(self.tv)
        self.layout.addWidget(self.btnDel)
        self.layout.addWidget(self.btnRen)
        self.setLayout(self.layout)

    def setSlot(self):
        self.tv.clicked.connect(self.setIndex)
        self.btnRen.clicked.connect(self.ren)
        self.btnDel.clicked.connect(self.rm)

    def setIndex(self, index):
        self.index = index

    def ren(self):
        os.chdir(self.model.filePath(self.model.parent(self.index)))
        fname = self.model.fileName(self.index)
        text, res = QInputDialog.getText(self, "이름바꾸기", "바꿀이름을 입력하세요",
                                         QLineEdit.Normal, fname)

        if res:
            while True:
                self.ok = True
                for i in os.listdir(os.getcwd()):
                    print(i)
                    if i == text:
                        text, res = QInputDialog.getText(
                            self, "중복오류!", "바꿀이름을 입력하세요", QLineEdit.Normal,
                            text)

                        if not res:
                            return
                        self.ok = False
                if self.ok:
                    break
            os.rename(fname, text)

    def rm(self):
        os.chdir(self.model.filePath(self.model.parent(self.index)))
        fname = self.model.fileName(self.index)
        try:
            if not self.model.isDir(self.index):
                os.unlike(fname)
                print(fname + '파일 삭제')
            else:
                shutil.rmtree(fname)
                print(fname + '폴더 삭제')

        except:
            print("에러발생")
예제 #13
0
class MenuLeft(QTreeView):
    """
    displays interactive directory on
    left side of text editor
    """
    def __init__(self, layout_props, document, file_manager, abs_path=None):
        """
        creates the directory display
        :param file_manager: instance of FileManager class - manages all file communication
        :param abs_path: default path to file being displayed
        :return: returns nothing
        """
        super().__init__()
        logging.debug("Creating Directory Viewer")
        self.layout_props = layout_props
        self.document = document
        self.fileManager = file_manager

        if abs_path is None:
            abs_path = QDir.currentPath()

        self.model = QFileSystemModel()

        self.initUI()
        self.updateDirectory(abs_path)

        self.updateAppearance()

    def initUI(self):
        """
        Initializes the Directory Viewer properties, signals, and model
        :return: returns nothing
        """
        logging.debug("Initializing Directory Viewer Props")
        self.setModel(self.model)

        # Default hide all columns except Name
        for i in range(1, self.model.columnCount()):
            name = self.model.headerData(i, Qt.Horizontal)
            if name not in self.layout_props.getDefaultLeftMenuCols():
                self.hideColumn(i)

        self.setAnimated(True)
        self.setIndentation(10)
        self.setSortingEnabled(True)
        self.setSelectionMode(QAbstractItemView.SingleSelection)
        self.setExpandsOnDoubleClick(True)

        # Expand or collapse directory on click
        self.doubleClicked.connect(self.onClickItem)
        # Shortcut for pressing enter on directory
        shortcut = QShortcut(Qt.Key_Return, self)
        shortcut.activated.connect(self.onClickItem)

    def updateDirectory(self, abs_path: str):
        """
        Updates the path of the model to the given path, sorts, and looks for the encryption key.
        :param abs_path: default path to file being displayed
        :return: returns nothing
        """
        logging.info(abs_path)

        self.model.setRootPath(abs_path)
        self.setRootIndex(self.model.index(abs_path))
        self.sortByColumn(0, Qt.AscendingOrder)

        self.fileManager.encryptor = None
        # Check for encryption key in Workspace
        path_key = os.path.join(abs_path, ".leafCryptoKey")
        if os.path.exists(path_key):
            logging.debug("Encryption key found! %s", path_key)
            with open(path_key, 'r') as f:
                key = f.read()
                self.fileManager.encryptor = Encryptor(key)
        else:
            logging.info("Workspace not encrypted")

    def onClickItem(self, index: QModelIndex = None):
        """
        functionality of double click on directory
        :param index: location of filePath
        :return: returns nothing
        """
        # If coming from Enter Pressed, resolve index
        if index is None:
            index = self.selectionModel().currentIndex()
        path = self.model.filePath(index)
        # Toggle expand/collapse directory
        if not self.model.isDir(index):
            logging.info("Opening document")
            self.fileManager.openDocument(self.document, path)

    def toggleHeaderColByName(self, name: str):
        """
        Shows or Hides a column based on it's name
        """
        logging.debug("Toggling header column %s", name)
        for i in range(1, self.model.columnCount()):
            col_name = self.model.headerData(i, Qt.Horizontal)
            if col_name == name:
                self.setColumnHidden(i, not self.isColumnHidden(i))

    def resizeColumnsToContent(self):
        """
        Resizes all columns to fit content
        """
        logging.debug("Resizing columns to contents")
        for i in range(1, self.model.columnCount()):
            self.resizeColumnToContents(i)

    def selectItemFromPath(self, path: str):
        """
        "Programmatically selects a path in the File System model
        """
        logging.debug("Selecting path %s", path)
        self.selectionModel().blockSignals(True)
        self.setCurrentIndex(self.model.index(path))
        self.selectionModel().blockSignals(False)

    def updateAppearance(self):
        """
        Updates the layout appearance based on properties
        """
        logging.debug("Setting up appearance")
        prop_header_margin = str(
            self.layout_props.getDefaultLeftMenuHeaderMargin())
        prop_header_color = self.layout_props.getDefaultHeaderColor()
        prop_item_height = str(self.layout_props.getDefaultItemHeight())
        prop_item_select_color = self.layout_props.getDefaultSelectColor()
        prop_item_hover_color = self.layout_props.getDefaultHoverColor()

        style = "QTreeView::item { height: " + prop_item_height + "px; }" + \
                "QTreeView::item:selected {" \
                "background-color: " + prop_item_select_color + "; }" + \
                "QTreeView::item:hover:!selected { " + \
                "background-color: " + prop_item_hover_color + "; }" + \
                "QHeaderView::section { margin: " + prop_header_margin + ";" + \
                " margin-left: 0; " + \
                " background-color: " + prop_header_color + ";" + \
                " color: white; }"
        self.setStyleSheet(style)
예제 #14
0
class FormWidget(QWidget):
    path = os.path.expanduser(
        '~\\Documents')  # to the root directory for explorer
    filepath = []  # contain a list of full path of files
    fpath = ''  # contain full path of file which we have to move,copy in explorer
    operation = ''  # contain operation which we have to perform

    def __init__(self):
        super().__init__()
        self.tree = treeview(
        )  #created new classs inheriting QTreeView class at top  of page
        self.model = QFileSystemModel()
        # self.model.setNameFilters(['TextFile(*.txt *.java *.py)'])
        # self.model.setNameFilterDisables(False)
        self.model.setRootPath('')
        self.tree.setModel(self.model)
        # self.tree.setCurrentIndex(self.model.index(r'C:\Users\user\Dropbox\PycharmProjects'))
        self.tree.setRootIndex(self.model.index(FormWidget.path))
        self.tree.doubleClicked.connect(self.findPath)
        self.tree.hideColumn(1)
        self.tree.hideColumn(2)
        self.tree.hideColumn(3)
        # self.tree.header().hide()
        self.tree.setFixedWidth(200)
        self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.contextmenu)

        self.tab = QTabWidget()
        self.tab.setTabShape(1)
        self.tab.setTabsClosable(True)
        self.tab.tabCloseRequested.connect(self.closeTab)
        self.tab.setMovable(True)

        hBox = QHBoxLayout()
        hBox.addWidget(self.tree)
        hBox.addWidget(self.tab)
        self.setLayout(hBox)

    def contextmenu(self, position):
        path = ''
        index = ''

        menu = QMenu()
        new = QMenu("New")
        newf = QAction("New File")
        newd = QAction("New Directory")
        new.addAction(newf)
        new.addAction(newd)
        cut = QAction("Cut")
        copy = QAction("Copy")
        paste = QAction("Paste")
        delete = QAction("Delete")
        rename = QAction("Rename")
        explorer = QAction("Explorer")
        menu.addMenu(new)
        menu.addAction(cut)
        menu.addAction(copy)
        menu.addAction(paste)
        menu.addAction(delete)
        menu.addAction(rename)
        menu.addAction(explorer)

        try:
            index = self.tree.selectedIndexes()[0]
            path = self.sender().model().filePath(index)
            if os.path.isfile(path):
                path = os.path.dirname(path)
        except:
            path = FormWidget.path
            if FormWidget.operation != 'cc' and FormWidget.operation != 'c':
                paste.setDisabled(True)
                cut.setDisabled(True)
                copy.setDisabled(True)
                delete.setDisabled(True)
                rename.setDisabled(True)
            else:
                cut.setDisabled(True)
                copy.setDisabled(True)
                delete.setDisabled(True)
                rename.setDisabled(True)

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

        if action == newf:
            text, ok = QInputDialog.getText(self, 'New File',
                                            'Enter File name:')
            if ok:
                p = open(path + "/" + text, 'w')
                p.close()
        elif action == newd:
            text, ok = QInputDialog.getText(self, 'New Directory',
                                            'Enter Directory name:')
            if ok:
                os.mkdir(path + "/" + text)
        elif action == delete:
            try:
                if self.model.isDir(index):
                    shutil.rmtree(path)
                else:
                    self.model.remove(index)
            except Exception as s:
                print(s)
        elif action == rename:
            text, ok = QInputDialog.getText(
                self,
                "Rename",
                "Enter new name:",
                text=os.path.basename(self.sender().model().filePath(index)))

            if ok:
                if self.model.isDir(index):
                    os.rename(path, os.path.dirname(path) + "/" + text)
                else:
                    os.rename(self.sender().model().filePath(index),
                              path + '/' + text)
        elif action == cut:
            FormWidget.fpath = self.sender().model().filePath(index)
            FormWidget.operation = 'c'
        elif action == copy:
            FormWidget.fpath = self.sender().model().filePath(index)
            FormWidget.operation = 'cc'

        elif action == paste:
            if FormWidget.operation == 'c':
                shutil.move(FormWidget.fpath, path)

            elif FormWidget.operation == 'cc':
                if os.path.isfile(FormWidget.fpath):
                    shutil.copy(FormWidget.fpath, path)
                else:
                    shutil.copytree(
                        FormWidget.fpath,
                        path + "/" + os.path.basename(FormWidget.fpath))
            FormWidget.operation = ''
        elif action == explorer:
            os.startfile(FormWidget.path)

    def findPath(self, index):
        path = self.sender().model().filePath(index)

        if os.path.isfile(path):
            global name
            FormWidget.filepath.append(path)
            name = os.path.basename(path)
            for i in range(self.tab.count()):
                if name == self.tab.tabText(i):
                    self.tab.removeTab(i)
                    break

            newEdit = QsciScintilla()
            self.checkExtensionToHighlight(path, newEdit)
            newEdit.setFont(QFont('Times', 10))
            newEdit.setMarginType(0, QsciScintilla.NumberMargin)
            newEdit.setMarginWidth(0, '00000000')
            try:
                newEdit.setText(open(path).read())
                newEdit.setBraceMatching(QsciScintilla.SloppyBraceMatch)
                newEdit.setAutoCompletionCaseSensitivity(False)
                newEdit.setAutoCompletionReplaceWord(False)
                newEdit.setAutoCompletionSource(QsciScintilla.AcsDocument)
                newEdit.setAutoCompletionThreshold(1)
                self.tab.insertTab(0, newEdit, QIcon('icon.png'),
                                   os.path.basename(path))
                self.tab.setCurrentIndex(0)
            except:
                QMessageBox.warning(self, "Warning", "Unsupported file type",
                                    QMessageBox.Ok)

    def checkExtensionToHighlight(self, path, editor):
        _, extension = os.path.splitext(path)

        if extension == '.py':
            editor.setLexer(QsciLexerPython(self))
        elif extension == '.html':
            editor.setLexer(QsciLexerHTML(self))
        elif extension == '.java':
            editor.setLexer(QsciLexerJava(self))
        elif extension == '.cs':
            editor.setLexer(QsciLexerCSharp(self))
        elif extension == '.bat':
            editor.setLexer(QsciLexerBatch(self))

    def closeTab(self, index):
        if self.tab.count() != 1:
            self.tab.removeTab(index)
        else:
            QMessageBox.warning(self, 'warning',
                                "You can not close the last tab",
                                QMessageBox.Ok, QMessageBox.Ok)