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))
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))
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))
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)
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)
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))
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)
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())
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)
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
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()
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("에러발생")
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)
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)