def build_central_content(self): main_panel = QSplitter(self) main_panel.setHandleWidth(1) main_panel.setOrientation(Qt.Vertical) main_panel.setContentsMargins(0, 0, 0, 0) from ui.panel_registers import RegistersPanel self.registers_panel = RegistersPanel(self.app, 0, 0) main_panel.addWidget(self.registers_panel) box = QVBoxLayout() box.setContentsMargins(0, 0, 0, 0) from ui.panel_memory import MemoryPanel self.memory_panel = MemoryPanel(self.app) box.addWidget(self.memory_panel) from ui.panel_java_explorer import JavaExplorerPanel self.java_explorer_panel = JavaExplorerPanel(self.app) self.java_explorer_panel.hide() box.addWidget(self.java_explorer_panel) q = QWidget() q.setLayout(box) main_panel.addWidget(q) from ui.panel_log import LogPanel self.log_panel = LogPanel(self.app) main_panel.addWidget(self.log_panel) main_panel.setStretchFactor(0, 1) main_panel.setStretchFactor(1, 3) main_panel.setStretchFactor(2, 1) return main_panel
class OpenedDialogWidget(QFrame): def __init__(self): super().__init__() self.splitter = QSplitter(Qt.Vertical) self.message_input = MessageInputWidget() self.messages_list = QListWidget() self.init_ui() def init_ui(self): layout = QVBoxLayout() self.messages_list.setStyleSheet( ThemeLoader.loaded_theme.apply_to_stylesheet(styles.messages_list)) self.messages_list.setSpacing(1) self.messages_list.showMinimized() self.messages_list.setWordWrap(True) self.messages_list.setContextMenuPolicy(Qt.CustomContextMenu) self.messages_list.customContextMenuRequested.connect( self.message_context_menu_event) self.message_input.setMinimumHeight(50) self.message_input.setMaximumHeight(100) self.splitter.addWidget(self.messages_list) self.splitter.addWidget(self.message_input) layout.addWidget(self.splitter) layout.setContentsMargins(0, 0, 0, 0) self.splitter.setContentsMargins(0, 0, 0, 0) self.setLayout(layout) def message_context_menu_event(self, _): message_item: QListWidgetItem = self.messages_list.currentItem() if message_item: message_widget: MessageItemWidget = self.messages_list.itemWidget( message_item) # same thing # noinspection PyAttributeOutsideInit self.menu = QMenu(self) reply_action = QAction('Reply', self) forward_action = QAction('Forward', self) delete_action = QAction('Delete', self) delete_action.triggered.connect( lambda: delete_message_item_selected_callback( self.messages_list, message_item)) self.menu.addAction(reply_action) self.menu.addAction(forward_action) if message_widget.message.mine: edit_action = QAction('Edit', self) edit_action.triggered.connect( lambda: edit_message_item_selected_callback( self, message_widget)) self.menu.addAction(edit_action) self.menu.addAction(delete_action) # add other required actions self.menu.popup(QCursor.pos())
class Ui_Mode(QMainWindow): """ 模式选择界面 """ def __init__(self, prev, parent=None): QMainWindow.__init__(self, parent) self.prev = prev self.setWindowTitle("规则录制界面") self.split = QSplitter(self) self.split.setOrientation(Qt.Vertical) self.split.setContentsMargins(0, 10, 10, 0) self.label = QLabel(self.split) self.label.setText("请选择您要采集的网页类型") self.label.setStyleSheet("font:bold;color:#8A2BE2") self.label.setContentsMargins(10, 10, 10, 10) self.mode_widget = QWidget(self.split) self.mode_widget_layout = QHBoxLayout() self.mode_widget.setLayout(self.mode_widget_layout) self.mode_widget.setStyleSheet("background:#7CCD7C") self.mode_1 = My_Label(1, self) self.mode_2 = My_Label(2, self) self.mode_3 = My_Label(3, self) mode_image_1 = QPixmap('../images/mode-1.png') mode_image_2 = QPixmap('../images/mode-2.png') mode_image_3 = QPixmap('../images/mode-3.png') self.mode_1.setPixmap(mode_image_1) self.mode_2.setPixmap(mode_image_2) self.mode_3.setPixmap(mode_image_3) self.mode_widget_layout.addStretch() self.mode_widget_layout.addWidget(self.mode_1) self.mode_widget_layout.addWidget(self.mode_2) self.mode_widget_layout.addWidget(self.mode_3) self.mode_widget_layout.addStretch() self.mode_illustrate_widget = QWidget(self.split) self.mode_illustrate_widget_layout = QHBoxLayout() self.mode_illustrate_widget.setLayout( self.mode_illustrate_widget_layout) # self.mode_illustrate_widget.setStyleSheet("background:white") self.i_mode = QLabel(self.mode_illustrate_widget) i_mode_image = QPixmap('../images/i-mode-1.png') self.i_mode.setPixmap(i_mode_image) self.mode_illustrate_widget_layout.addStretch() self.mode_illustrate_widget_layout.addWidget(self.i_mode) self.mode_illustrate_widget_layout.addStretch() self.split.addWidget(self.label) self.split.addWidget(self.mode_widget) self.split.addWidget(self.mode_illustrate_widget) self.split.setStretchFactor(0, 1) self.split.setStretchFactor(2, 50) self.split.setStretchFactor(3, 500) self.setCentralWidget(self.split)
def __init__(self, parent=None): super().__init__(parent) # initialize class attributes self._INPUTS_FIXED_WIDTH = 180 self._BUTTON_MIN_WIDTH = 80 self._OXYGEN_PATH_32 = os.path.join("resources", "icons", "oxygen", "32") # locale and language settings self.language = self.locale().name()[:2] self.qtTl = QTranslator() self.appTl = QTranslator() self.switchTranslator(self.qtTl, "qtbase", self.language) self.switchTranslator(self.appTl, "wuchshuellenrechner", self.language) # TODO(th) self.model = TreeModel() # setup gui self.setMinimumSize(1200, 760) self.createActions() self.createMainMenu() # plot widget self.plotWidget = VariantPlotView() self.plotWidget.setModel(self.model) # create vertical splitter splitter = QSplitter(Qt.Vertical) splitter.setContentsMargins(11, 11, 11, 11) splitter.addWidget(self.createEditBox()) splitter.addWidget(self.plotWidget) self.setCentralWidget(splitter) self.dataWidget.setModel(self.model) selectionModel = QItemSelectionModel(self.model) self.dataWidget.setSelectionModel(selectionModel) self.plotWidget.setSelectionModel(selectionModel) self.retranslateUi() self.model.dataChanged.connect(self.updateInputs) self.model.itemsInserted.connect(self.taxInput.setDisabled) self.model.allItemsRemoved.connect(self.taxInput.setEnabled) self.operationInput.textChanged.connect(self.updateOperation) self.districtInput.textChanged.connect(self.updateDistrict) self.managerInput.textChanged.connect(self.updateManager) self.locationInput.textChanged.connect(self.updateLocation) self.taxInput.stateChanged.connect(self.updateTax)
def build_left_column(self): splitter = QSplitter() splitter.setHandleWidth(1) splitter.setOrientation(Qt.Vertical) splitter.setContentsMargins(0, 0, 0, 0) from ui.panel_hooks import HooksPanel self.hooks_panel = HooksPanel(self.app) splitter.addWidget(self.hooks_panel) from ui.panel_watchers import WatchersPanel self.watchers_panel = WatchersPanel(self.app) splitter.addWidget(self.watchers_panel) return splitter
def build_right_column(self): splitter = QSplitter() splitter.setHandleWidth(1) splitter.setOrientation(Qt.Vertical) splitter.setContentsMargins(0, 0, 0, 0) from ui.panel_contexts import ContextsPanel self.contexts_panel = ContextsPanel(self.app) splitter.addWidget(self.contexts_panel) from ui.panel_backtrace import BacktracePanel self.backtrace_panel = BacktracePanel(self.app) splitter.addWidget(self.backtrace_panel) return splitter
def build_left_column(self): splitter = QSplitter() splitter.setHandleWidth(1) splitter.setOrientation(Qt.Vertical) splitter.setContentsMargins(0, 0, 0, 0) self.hooks_panel = HooksPanel(self.app) splitter.addWidget(self.hooks_panel) self.contexts_panel = ContextsPanel(self.app) splitter.addWidget(self.contexts_panel) self.backtrace_panel = BacktracePanel(self.app) splitter.addWidget(self.backtrace_panel) return splitter
def loadData(self): self.btn_stop.setDisabled(True) self.txt_log.setReadOnly(True) self.txt_log.clear() self.txt_email.clear() self.txt_keyword.clear() self.txt_keyword_flag.clear() self.txt_pattern.clear() self.loadEmail() self.timer = QTimer() self.timer.timeout.connect(self.setTimer) self.timer.start(1000) self.log_timer = QTimer() self.log_timer.timeout.connect(self.clearSystem) self.log_timer.start(self.spider.clearSystemInterval) self.network_timer = QTimer() self.network_timer.timeout.connect(self.checkNetworkStatus) self.network_timer.start(self.spider.networkConnectionCheckInterval) #self.startTime = time.time() self.loadSettingData() #check upgrade t1 = threading.Thread(target=self.spider.checkUpgrade,args=()) t1.setDaemon(True) t1.start() if self.config['debug'] == '1': self.usage_timer = QTimer() self.usage_timer.timeout.connect(self.loadUsage) self.usage_timer.start(self.spider.systemMonitorInterval) self.loadUsage() #spitter splitter = QSplitter(self) splitter.addWidget(self.groupBox) splitter.addWidget(self.groupBox_2) splitter.addWidget(self.groupBox_3) splitter.setContentsMargins(9,9,9,9) splitter.setOrientation(Qt.Vertical) self.setCentralWidget(splitter)
def __init__(self): super().__init__() self.leftWidget = JSONNavigation() self.leftWidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.leftWidget.currentChange.connect(self.fileSelected) self.leftWidget.alert.connect(lambda txt: self.alert.emit( txt, Parapluie.Alert_Error, None, None)) self.addWidget(self.leftWidget) self.jsonEdit = TextEditWidget() # self.jsonEdit.setObjectName(Parapluie.Object_Editor_Flat) self.jsonEdit.alert.connect(lambda txt, tpe, button1, button2: self. alert.emit(txt, tpe, button1, button2)) self.jsonEdit.saveDataDone.connect(self.leftWidget.fileChanged) self.paramEdit = ParamEditor() # self.paramEdit.setObjectName(Parapluie.Object_Table) self.paramEdit.error.connect( lambda s: self.alert.emit(s, Parapluie.Alert_Error, None, None)) self.paramConnect = ParamEditorConnect(self.paramEdit, self.jsonEdit) splitter = QSplitter() splitter.setObjectName(Parapluie.Object_BorderPane) splitter.addWidget(self.jsonEdit) splitter.addWidget(self.paramEdit) splitter.setContentsMargins(8, 8, 8, 8) PFunction.applyShadow(splitter) layout = QVBoxLayout() layout.addWidget(splitter) layout.setContentsMargins(0, 8, 8, 8) widget = PWidget() widget.setLayout(layout) self.addWidget(widget) self.setChildrenCollapsible(False) self.setObjectName(Parapluie.Object_QSplitter) self.leftWidget.newFile() self.jsonEdit.setEditorType(EditorType.JSON)
def build_central_content(self): main_panel = QSplitter(self) main_panel.setHandleWidth(1) main_panel.setOrientation(Qt.Vertical) main_panel.setContentsMargins(0, 0, 0, 0) self.registers_panel = RegistersPanel(self.app, 0, 0) main_panel.addWidget(self.registers_panel) self.memory_panel = MemoryPanel(self.app) main_panel.addWidget(self.memory_panel) self.log_panel = LogPanel(self.app) main_panel.addWidget(self.log_panel) main_panel.setStretchFactor(0, 1) main_panel.setStretchFactor(1, 3) main_panel.setStretchFactor(2, 1) return main_panel
def __init__(self, channelName, clientIRC, jsonDecoder): super(ChatTab, self).__init__() userList = UserList(self) self.userList = userList self.setAttribute(Qt.WA_DeleteOnClose) self.clientIRC = clientIRC channelChat = ChannelChat(self, channelName, jsonDecoder) layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) splitter = QSplitter(Qt.Horizontal) splitter.addWidget(channelChat) splitter.addWidget(userList) splitter.setContentsMargins(0, 0, 0, 0) splitter.setHandleWidth(0) splitter.setStretchFactor(0, 7) splitter.setStretchFactor(1, 1) splitter.setChildrenCollapsible(False) layout.addWidget(splitter) self.jsonDecoder = jsonDecoder self.clientIRC.joinChannel(channelName) self.setLayout(layout) self.channelChat = channelChat self.channelName = '#' + channelName
def __init__(self, application): super().__init__() self.setWindowTitle(config.TITLE + " (v" + config.VERSION + ")") self.setGeometry(10, 10, 1220, 500) self.setWindowFlags(self.windowFlags() & Qt.WindowMaximizeButtonHint) self.data_panel = DataPanel(application) self.graph_panel = GraphPanel() self.status_bar = StatusBarWidget(application.signals) splitter = QSplitter(Qt.Horizontal) splitter.addWidget(self.data_panel) splitter.addWidget(self.graph_panel) splitter.setSizes([10000, 10000]) splitter.setContentsMargins(1, 1, 1, 1) layout = QVBoxLayout() layout.addWidget(splitter, 1) layout.addWidget(self.status_bar, 0) self.setLayout(layout) application.signals.csv_imported.connect(self.on_csv_imported) self.showMaximized()
class MainWindow(QMainWindow): def __init__(self, settings): super().__init__() # Save the settings object for now and shutdown time. self.settings = settings # Initialize extras and dicts paths first, as other modules use them paths.initialize(settings) # Initialize our font db fonts.initialize(settings) # Set our font, which will propogate to our child widgets. fonts.notify_me(self._font_change) # ask for a signal self._font_change(False) # fake a signal now # Initialize the dictionary apparatus dictionaries.initialize(settings) # Initialize the color choices colors.initialize(settings) # Initialize the sequence number for opened files self.book_number = 0 # Initialize the path to the last-opened file, used to # start file-open dialogs. self.last_open_path = '.' # Initialize our dict of active panels self.panel_dict = PANEL_DICT.copy() # Initialize our dict of open documents {seqno:Book} self.open_books = {} self.focus_book = None # seqno of book in focus, see _focus_me # Initialize the list of recent files self.recent_files = [] # Create the main window and set up the menus. self._uic() # Initialize the set of files actually open when we shut down. last_session = self._read_flist('mainwindow/open_files') if len(last_session) : # there were some files open if len(last_session) == 1 : msg = _TR('Start-up dialog', 'One book was open at the end of the last session.') else: msg = _TR('Start-up dialog', '%n books were open at the end of the last session.', n=len(last_session) ) info = _TR("Start-up dialog", "Click OK to re-open all") if utilities.ok_cancel_msg( msg, info) : for file_path in last_session : ftbs = utilities.path_to_stream(file_path) if ftbs : self._open(ftbs) if 0 == len(self.open_books) : # We did not re-open any books, either because there were # none, or the user said No, or perhaps they were not found. self._new() # open one, new, book. # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Slot to receive the currentChanged signal from the editview tabset. # Look through self.open_books and find the one whose edit widget is # now current, and do a focus_me for it. def _editview_change(self, index): if index > -1 : eview = self.editview_tabset.widget(index) for (seqno, book) in self.open_books.items() : if eview == book.get_edit_view() : self.focus_me(seqno) return mainwindow_logger.error('cannot relate editview tab index to book') # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Make a selected book the focus of all panels. This is called explicitly # when a book is first created, and when the editview tabset changes the # current selection. It is called also when an editview gets a focus-in # event. # Display that Book's various "-view" objects in panels, in the order # that the user left them and with the same active panel as before. Note # that a book (editview) can get a focus-in event when it was already the # focus in this sense, for example if this app was hidden and then # brought to the front. So be prepared for redundant calls. def focus_me(self, book_index): outgoing = self.focus_book if book_index == outgoing : return # redundant call mainwindow_logger.debug( 'focusing {0} = {1}'.format(book_index,self.open_books[book_index].get_book_name()) ) self.focus_book = book_index # Record the user's arrangement of panels for the outgoing book, # as a list of tuples ('tabname', widget) in correct sequence. if outgoing is not None : # false first time and after File>Close out_panel_dict = self.open_books[outgoing].panel_dict widg_list = [] for ix in range( self.panel_tabset.count() ): widg_list.append ( (self.panel_tabset.tabText(ix), self.panel_tabset.widget(ix)) ) out_panel_dict['tab_list'] = widg_list out_panel_dict['current'] = self.panel_tabset.currentIndex() # Change all the panels to the widgets, in the sequence, of the new book in_panel_dict = self.open_books[book_index].panel_dict widg_list = in_panel_dict['tab_list'] self.panel_tabset.clear() for ix in range( len(widg_list) ): (tab_text, widget) = widg_list[ix] self.panel_tabset.insertTab(ix, widget, tab_text) self.panel_tabset.setCurrentIndex(in_panel_dict['current']) self.editview_tabset.setCurrentIndex( self.editview_tabset.indexOf( self.open_books[book_index].get_edit_view() ) ) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Called by the current book to make a particular tab the visible one, # e.g. to make the Find visible on a ^F. The argument is a widget # that should occupy one of the current tabs. Ask the tabset for its # index, and if it is found, make that the current index. (If it is # not found, log it and do nothing.) def make_tab_visible(self, tabwidg): ix = self.panel_tabset.indexOf(tabwidg) if ix >= 0 : # widget exists in this tabset self.panel_tabset.setCurrentIndex(ix) return mainwindow_logger.error('Request to show nonexistent widget') # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Implement File>New: # Create a Book object # Call its new_empty() method, # Add it to the open_books dict keyed by its sequence number, # Display its text editor in a tab with the document name, and # Give it the focus. def _new(self): seq = self.book_number self.book_number += 1 new_book = book.Book( seq, self ) new_book.new_empty() self.open_books[seq] = new_book index = self.editview_tabset.addTab( new_book.get_edit_view(), new_book.get_book_name() ) self.editview_tabset.setTabToolTip(index, _TR('Tooltip of edit of new unsaved file', 'this file has not been saved') ) self.focus_me(seq) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Quick check to see if a file path is already open. Called from _open # and from _build_recent (menu). Returned value is the sequence number # of the open book, or None. def _is_already_open(self, path): for (seq, book_object) in self.open_books.items(): if path == book_object.get_book_full_path() : return seq return None # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Implement File>Open. Dialog with the user (file dialog starts with # last-used book path). Result is None or a FileBasedTextStream that we # pass to _open(). def _file_open(self) : fbts = utilities.ask_existing_file( _TR( 'File:Open dialog','Select a book file to open'), parent=self, starting_path=self.last_open_path) if fbts : # yes a readable file was chosen. self._open( fbts ) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Open a file, given the document as a FileBasedTextStream # * If file opened is fname.meta, look for a file named fname; if it # exists open it instead, e.g. given foo.txt.meta, open foo.txt. # If it doesn't exist, tell the user and exit. # * If a file of the same name and path is already open, just focus # it and exit. # * Determine if there is a .meta file, a .bin file, or neither # * Create a metadata input stream if possible # * If no .meta, look for good_words and bad_words # * If the only open book is an "Untitled-n" and # it is unmodified, delete it. # * Call Book.old_book() or .new_book() as appropriate # * Add this book's editview to the edit tabset # * Give this book the focus. def _open(self, fbts): # look for opening a .meta file if 'meta' == fbts.suffix(): fb2 = utilities.file_less_suffix(fbts) if fb2 is None : m1 = _TR('File:Open','Cannot open a .meta file alone') m2 = _TR('File:Open','There is no book file matching ', 'filename follows this') + fbts.filename() utilities.warning_msg(m1, m2) return # we see foo.txt with foo.txt.meta, silently open it fbts = fb2 # look for already-open file seq = self._is_already_open(fbts.fullpath()) if seq is not None : self.focus_me(seq) return # start collecting auxiliary streams gw_stream = None bw_stream = None gg_stream = None # open the metadata stream, which is always UTF-8 meta_stream = utilities.related_suffix(fbts, 'meta', encoding=C.ENCODING_UTF) if meta_stream is None : # opening book without .meta; look for .bin which is always LTN1 bin_stream = utilities.related_suffix(fbts,'bin',encoding=C.ENCODING_LATIN) if bin_stream : gg_stream = metadata.translate_bin(bin_stream,fbts) # Look for good_words.txt, bad_words.txt. gw_stream = utilities.related_file( fbts, 'good_words*.*' ) bw_stream = utilities.related_file( fbts, 'bad_words*.*' ) seq = self.book_number # If the only open book is the new one created at startup or when all # books are closed (which will have key 0), and it has not been # modified, get rid of it. if len(self.open_books) == 1 \ and 0 == list(self.open_books.keys())[0] \ and self.open_books[0].get_book_name().startswith('Untitled-') \ and not self.open_books[0].get_save_needed() : self.editview_tabset.clear() self.panel_tabset.clear() self.focus_book = None seq = 0 else: # Some other book open, or user typed into the default New one. self.book_number += 1 # Make the Book object and stow it in our open book dict a_book = book.Book( seq, self ) self.open_books[seq] = a_book if meta_stream : # opening a book we previously saved a_book.old_book( fbts, meta_stream ) else : a_book.new_book( fbts, gg_stream, gw_stream, bw_stream ) index = self.editview_tabset.addTab( a_book.get_edit_view(), a_book.get_book_name()) self.editview_tabset.setTabToolTip(index, a_book.get_book_folder() ) self.focus_me(seq) self.last_open_path = fbts.folderpath() # start for next open or save self._add_to_recent(fbts.fullpath()) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Save the book that is currently in focus under its present name, if it # is modified. Return True if the save completed, else False. # If the active book is a New one, force a Save-As action instead. def _save(self): active_book = self.open_books[self.focus_book] if active_book.get_save_needed() : if active_book.get_book_name().startswith('Untitled-'): return self._save_as() doc_stream = utilities.path_to_output( active_book.get_book_full_path() ) if doc_stream : # successfully opened for output meta_stream = utilities.related_output(doc_stream,'meta') if not meta_stream: utilities.warning_msg( _TR('File:Save', 'Unable to open metadata file for writing.'), _TR('File:Save', 'Use loglevel=error for details.') ) return False else: utilities.warning_msg( _TR('File:Save', 'Unable to open book file for writing.'), _TR('File:Save', 'Use loglevel=error for details.') ) return False return active_book.save_book(doc_stream, meta_stream) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Implement Save As. Query the user for a file path and get that as an # output FileBasedTextStream. Call the book to rename itself, which makes # it modified. Change the text in the edit tab to match. Discard the FBTS # and call _save which will make another one. def _save_as(self): active_book = self.open_books[self.focus_book] fbts = utilities.ask_saving_file( _TR('File:Save As dialog', 'Choose a new location and filename for this book' ), self, active_book.get_book_folder() ) if fbts : active_book.rename_book(fbts) self.editview_tabset.setTabText( self.editview_tabset.currentIndex(), fbts.filename() ) self._add_to_recent(fbts.fullpath()) fbts = None # discard that object return self._save() else: return False # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Implement Close. If the active book is modified, ask if it should # be saved. If it is 'Untitled-' that will turn into Save As. def _close(self): target_index = self.focus_book # active edit tab is to close target_book = self.open_books[target_index] if target_book.get_save_needed() : # Compose message of translated parts because _TR does not # allow for incorporating strings, only numbers. msg = _TR('File Close dialog', 'Book file ', 'filename follows here') msg += target_book.get_book_name() msg += _TR('File Close dialog', ' has been modified!', 'filename precedes this') ret = utilities.save_discard_cancel_msg( msg, info = _TR('File Close dialog', 'Save it, Discard changes, or Cancel Closing?') ) if ret is None : # Cancel return if ret : # True==Save self._save() # Now, get rid of the active book in 3 steps, # 1, close the book's tab in the editview tabset. We don't know which # tab it is, because the user can drag tabs around. i = self.editview_tabset.indexOf(target_book.get_edit_view()) # The following causes another tab to be focussed, changing self.focus_book # and saving target_book's tabs in target_book, not that we care. self.editview_tabset.removeTab(i) # 2, remove the book from our dict of open books. del self.open_books[target_index] # 3, if there are any open books remaining, the tab widget has # activated one of them by its rules, which caused a show signal and # entry to _focus_me already. However if there are no remaining books # there was no show signal or focus_me and the closed book's panels # are still in the tabset. if 0 == len(self.open_books) : self.book_number = 0 # restart the sequence self.focus_book = None self._new() # One way or the other, a focus_me has removed all references to # active_book's view panels except those in its PANEL_DICT. So the # following assignment should remove the last reference to the book, # and schedule the book and associated objects for garbage collect. target_book = None # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Implement loading and saving find panel user buttons. Start the search # for files in the active book's folder. User can navigate to extras # if need be. def _find_save(self): target_book = self.open_books[self.focus_book] find_panel = target_book.get_find_panel() stream = utilities.ask_saving_file( _TR('File:Save Find Buttons open dialog', 'Choose file to contain find button definitions'), self, starting_path=target_book.get_book_full_path(), encoding='UTF-8') if stream : # is not None, file is open find_panel.user_button_output(stream) # else user hit cancel, forget it def _find_load(self): target_book = self.open_books[self.focus_book] find_panel = target_book.get_find_panel() stream = utilities.ask_existing_file( _TR('File:Load Find Buttons open dialog', 'Choose a file of find button definitions'), self, starting_path=target_book.get_book_full_path(), encoding='UTF-8') if stream :# is not None, we opened it find_panel.user_button_input(stream) target_book.metadata_modified(True,C.MD_MOD_FLAG) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Maintain the list of "recent" file paths. The list is kept in usage # order, so if a path is in the list now, delete it and then add it to # the front. Keep it at a max of 9 items by deleting the oldest if # necessary. def _add_to_recent(self, path): if path in self.recent_files : del self.recent_files[self.recent_files.index(path)] self.recent_files.insert(0,path) self.recent_files = self.recent_files[:9] # Upon the aboutToShow signal from the File menu, populate the Recent # submenu with a list of files, but only the ones that are currently # accessible. If one is on a volume (e.g. USB stick) and you unmount the # volume, the path should not appear in the menu until the volume is # mounted again. def _open_recent(self, path): fbts = utilities.path_to_stream(path) if fbts : self._open(fbts) def _build_recent(self): active_files = [] for path in self.recent_files: seq = self._is_already_open(path) if (seq is None) and utilities.file_is_accessible(path) : active_files.append( (utilities.file_split(path),path) ) if 0 == len(active_files): self.recent_menu.setEnabled(False) return self.recent_menu.setEnabled(True) self.recent_menu.clear() i = 1 for ((fname, folder), path) in active_files: act = self.recent_menu.addAction( '{0} {1} {2}'.format(i,fname,folder) ) act.triggered.connect( lambda: self._open_recent(path) ) i += 1 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # User has chosen a different font; if it is the general font, set # that here so it will propogate to our children. def _font_change(self, is_mono): if not is_mono: self.setFont(fonts.get_general()) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Create the UI contained within this QMainWindow object. This is a lean # main window indeed. We have no toolbar, no status bar, no dock, # nothing. Just a splitter with, on the left, a tabset for editviews, and # on the right, a scrollbar containing a tabset for panels. (Qt Designer # note: it is not possible to build this structure with the Designer. It # will not let you put the scroll area into the splitter.) # # TODO: create a custom QTabWidget using a custom QTabBar to implement # drag-out-of-tabset behavior, and use those here. def _uic(self): # Create the tabset that displays editviews self.editview_tabset = QTabWidget() self.editview_tabset.setMovable(True) # let user move tabs around self.editview_tabset.currentChanged.connect(self._editview_change) # Create the tabset that displays find, notes, help &etc. self.panel_tabset = QTabWidget() self.panel_tabset.setMovable(True) # Create the splitter that contains the above two parts. self.splitter = QSplitter(Qt.Horizontal, self) self.splitter.setChildrenCollapsible(False) # Give just a little margin to the left of the editor self.splitter.setContentsMargins(8,0,0,0) self.splitter.addWidget(self.editview_tabset) self.splitter.addWidget(self.panel_tabset) # Set that splitter as the main window's central (and only) widget self.setCentralWidget(self.splitter) # Populate the panel tabset with empty widgets just so there will # be tabs that _swap can reference. for key in self.panel_dict.keys(): widj = QWidget() self.panel_tabset.addTab(widj,key) self.panel_dict[key] = widj # Size and position ourself based on saved settings. self.move(self.settings.value("mainwindow/position", QPoint(50,50))) self.resize(self.settings.value("mainwindow/size", C.STARTUP_DEFAULT_SIZE)) self.splitter.restoreState( self.settings.value("mainwindow/splitter",C.STARTUP_DEFAULT_SPLITTER) ) # Store a reference to the application menubar. In Mac OS this # is a parentless menubar; other platforms it is the default. if C.PLATFORM_IS_MAC : self.menu_bar = QMenuBar() # parentless menu bar for Mac OS else : self.menu_bar = self.menuBar # refer to the default one set_menu_bar(self.menu_bar) # Create the File menu, located in our menu_bar. self.file_menu = self.menu_bar.addMenu(_TR('Menu name', '&File')) # Populate the File menu with actions. # File:New -> _new() work = self.file_menu.addAction( _TR('File menu command','&New') ) work.setShortcut(QKeySequence.New) work.setToolTip( _TR('File:New tooltip','Create a new, empty document') ) work.triggered.connect(self._new) # File:Open -> _file_open() work = self.file_menu.addAction( _TR('File menu command','&Open') ) work.setShortcut(QKeySequence.Open) work.setToolTip( _TR('File:Open tooltip','Open an existing book') ) work.triggered.connect(self._file_open) # File:Save -> _file_save() work = self.file_menu.addAction( _TR('File menu command', '&Save') ) work.setShortcut(QKeySequence.Save) work.setToolTip( _TR('File:Save tooltip','Save the active book') ) work.triggered.connect(self._save) # Save As -> _file_save_as() work = self.file_menu.addAction( _TR('File menu command', 'Save &As') ) work.setShortcut(QKeySequence.SaveAs) work.setToolTip( _TR('File:Save As tooltip','Save the active book under a new name') ) work.triggered.connect(self._save_as) # Close -> _close() work = self.file_menu.addAction( _TR('File menu command', 'Close') ) work.setShortcut(QKeySequence.Close) work.setToolTip( _TR('File:Close tooltip', 'Close the active book') ) work.triggered.connect(self._close) # Load Find Buttons -> _find_load() work = self.file_menu.addAction( _TR('File menu command', 'Load Find Buttons') ) work.setToolTip( _TR('File:Load Find Buttons tooltip', 'Load a file of definitions for the custom buttons in the Find panel' ) ) work.triggered.connect(self._find_load) # Save Find Buttons -> _find_save() work = self.file_menu.addAction( _TR('File menu command', 'Save Find Buttons') ) work.setToolTip( _TR('File:Save Find Buttons tooltip', 'Save definitions of the custom buttons in the Find panel' ) ) work.triggered.connect(self._find_save) # Open Recent gets a submenu that is added to the File menu. # The aboutToShow signal is connected to our _build_recent slot. self.recent_menu = QMenu( _TR('Sub-menu name', '&Recent Files') ) work = self.file_menu.addMenu( self.recent_menu ) work.setToolTip( _TR('File:Recent tooltip', 'List of recently-used files to open') ) self.file_menu.aboutToShow.connect(self._build_recent) # divider if not Mac if not C.PLATFORM_IS_MAC: self.file_menu.addSeparator() # TODO Preferences with the menu role that on mac, moves to the app menu # Quit with the menu role that moves it to the app menu work = QAction( _TR('Quit command','&Quit'), self ) work.setMenuRole(QAction.QuitRole) work.setShortcut(QKeySequence.Quit) work.triggered.connect(self.close) self.file_menu.addAction(work) # Initialize the list of "recent" files for the File sub-menu. # These files were not necessarily open at shutdown, just sometime # in the not too distant past. self.recent_files = self._read_flist('mainwindow/recent_files') # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Functions related to shutdown and management of settings. # # Factor out the job of reading/writing a list of files in the settings. # Input is a settings array key string like 'mainwindow/recent_files' # Output is a possibly empty list of canonical-file-path strings. def _read_flist(self, array_key): f_list = [] f_count = self.settings.beginReadArray(array_key) for f in range(f_count): # which may be 0 self.settings.setArrayIndex(f) f_list.append( self.settings.value('filepath') ) self.settings.endArray() return f_list # Input is an array key and a possibly empty list of path strings def _write_flist(self, file_list, array_key): if len(file_list): self.settings.beginWriteArray( array_key, len(file_list) ) for f in range(len(file_list)) : self.settings.setArrayIndex( f ) self.settings.setValue( 'filepath',file_list[f] ) self.settings.endArray() # Reimplement QWidget.closeEvent in order to save any open files # and update the settings. def closeEvent(self, event): # If there are any unsaved books, ask the user if they should be # saved. If the answer is yes, try to do so. unsaved = [] for (seq, book_object) in self.open_books.items() : if book_object.get_save_needed() : unsaved.append(seq) if len(unsaved): if len(unsaved) == 1 : msg = _TR('Shutdown message', 'There is one unsaved file') else : msg = _TR('Shutdown message', 'There are %n unsaved files', n=len(unsaved)) ret = utilities.save_discard_cancel_msg( msg, _TR('Shutdown message', 'Save, Discard changes, or Cancel Quit?') ) if ret is None : # user wants to cancel shutdown event.ignore() return if ret : # User want to save. Focus each unsaved file and call _save. # For all but "Untitled-n" documents this will be silent. For # those, it will open a save-as dialog. We ignore the return # from this because we cannot distinguish between a cancelled # file-open dialog and a file write error. for seq in unsaved : self.focus_me(seq) self._save() # Clear the settings so that old values don't hang around self.settings.clear() # Tell the submodules to save their current global values. colors.shutdown(self.settings) fonts.shutdown(self.settings) dictionaries.shutdown(self.settings) paths.shutdown(self.settings) # Save the list of currently-open files in the settings, but do not # save any whose filename matches "Untitled-#" because that is an # unsaved New file (which the user chose not to save, above). open_paths = [] for (index, book_obj) in self.open_books.items() : if not book_obj.get_book_name().startswith('Untitled-'): open_paths.append( book_obj.get_book_full_path() ) self._write_flist( open_paths, 'mainwindow/open_files' ) # Save the list of "recent" files in the settings. self._write_flist(self.recent_files, 'mainwindow/recent_files') # Save this window's position and size and splitter state self.settings.setValue("mainwindow/size",self.size()) self.settings.setValue("mainwindow/position",self.pos()) self.settings.setValue("mainwindow/splitter",self.splitter.saveState()) # and that's it, we are done finished, over & out. event.accept()
class SessionUi(QTabWidget): TAB_MODULES = 'modules' TAB_RANGES = 'ranges' TAB_DATA = 'data' TAB_ASM = 'asm' TAB_JAVA_CLASSES = 'java' TAB_TRACE = 'trace' TAB_FTRACE = 'ftrace' def __init__(self, app, *__args): super().__init__(*__args) self.app = app self.setTabsClosable(True) self.setMovable(True) self.tabCloseRequested.connect(self.close_tab) self.setContentsMargins(2, 2, 2, 2) self.setStyleSheet(""" QListWidget:hover, QTableWidget:hover { border: 1px solid transparent; } QTabWidget QFrame{ border: 0; } QTabWidget::pane { border: 0px solid transparent; border-radius: 0px; padding: 0px; margin: 0px; } QTabWidget::pane:selected { background-color: transparent; border: 0px solid transparent; } QWidget { padding: 0; margin-top: 2px; margin-right: 2px; margin-left: 1px; } """) self.session_panel = QSplitter() self.asm_panel = None self.backtrace_panel = None self.contexts_panel = None self.data_panel = None self.ftrace_panel = None self.hooks_panel = None self.java_class_panel = None self.java_explorer_panel = None self.log_panel = None self.memory_panel = None self.modules_panel = None self.ranges_panel = None self.registers_panel = None self.trace_panel = None self.watchers_panel = None self.session_panel.addWidget(self.build_left_column()) self.session_panel.addWidget(self.build_central_content()) self.session_panel.addWidget(self.build_right_column()) self.session_panel.setHandleWidth(1) self.session_panel.setStretchFactor(0, 2) self.session_panel.setStretchFactor(1, 6) self.session_panel.setStretchFactor(2, 2) self.session_panel.setContentsMargins(0, 0, 0, 0) self.addTab(self.session_panel, 'session') bt = self.tabBar().tabButton(0, QTabBar.LeftSide) if not bt: bt = self.tabBar().tabButton(0, QTabBar.RightSide) if bt: bt.resize(0, 0) self.add_main_tabs() def add_main_tabs(self): self.add_dwarf_tab(SessionUi.TAB_MODULES) self.add_dwarf_tab(SessionUi.TAB_RANGES) def build_left_column(self): splitter = QSplitter() splitter.setHandleWidth(1) splitter.setOrientation(Qt.Vertical) splitter.setContentsMargins(0, 0, 0, 0) from ui.panel_hooks import HooksPanel self.hooks_panel = HooksPanel(self.app) splitter.addWidget(self.hooks_panel) from ui.panel_watchers import WatchersPanel self.watchers_panel = WatchersPanel(self.app) splitter.addWidget(self.watchers_panel) return splitter def build_central_content(self): main_panel = QSplitter(self) main_panel.setHandleWidth(1) main_panel.setOrientation(Qt.Vertical) main_panel.setContentsMargins(0, 0, 0, 0) from ui.panel_registers import RegistersPanel self.registers_panel = RegistersPanel(self.app, 0, 0) main_panel.addWidget(self.registers_panel) box = QVBoxLayout() box.setContentsMargins(0, 0, 0, 0) from ui.panel_memory import MemoryPanel self.memory_panel = MemoryPanel(self.app) box.addWidget(self.memory_panel) from ui.panel_java_explorer import JavaExplorerPanel self.java_explorer_panel = JavaExplorerPanel(self.app) self.java_explorer_panel.hide() box.addWidget(self.java_explorer_panel) q = QWidget() q.setLayout(box) main_panel.addWidget(q) from ui.panel_log import LogPanel self.log_panel = LogPanel(self.app) main_panel.addWidget(self.log_panel) main_panel.setStretchFactor(0, 1) main_panel.setStretchFactor(1, 3) main_panel.setStretchFactor(2, 1) return main_panel def build_right_column(self): splitter = QSplitter() splitter.setHandleWidth(1) splitter.setOrientation(Qt.Vertical) splitter.setContentsMargins(0, 0, 0, 0) from ui.panel_contexts import ContextsPanel self.contexts_panel = ContextsPanel(self.app) splitter.addWidget(self.contexts_panel) from ui.panel_backtrace import BacktracePanel self.backtrace_panel = BacktracePanel(self.app) splitter.addWidget(self.backtrace_panel) return splitter def on_script_loaded(self): pass def on_script_destroyed(self): for i in range(0, self.count()): if i > 0: self.removeTab(i) self.contexts_panel.clear() self.log_panel.clear() if self.data_panel is not None: self.data_panel.clear() if self.watchers_panel is not None: self.watchers_panel.clear() if self.asm_panel is not None: self.asm_panel.clear() self.hooks_panel.setRowCount(0) self.hooks_panel.setColumnCount(0) if self.ranges_panel is not None: self.ranges_panel.setRowCount(0) if self.modules_panel is not None: self.modules_panel.setRowCount(0) self.backtrace_panel.setRowCount(0) self.backtrace_panel.setColumnCount(0) self.registers_panel.setRowCount(0) self.registers_panel.setColumnCount(0) self.memory_panel.on_script_destroyed() self.java_class_panel = None self.trace_panel = None self.ftrace_panel = None def close_tab(self, index): w = self.widget(index) try: w.on_tab_closed() except: pass # invalidate the object in the current session # this is the fastest way i can think to achieve this v = vars(self) for obj in v: if v[obj] is not None and v[obj] == w: try: v[obj].clear() except: pass v[obj] = None self.removeTab(index) def add_dwarf_tab(self, tab_id, request_focus=False): if tab_id == SessionUi.TAB_DATA: if self.data_panel is None: from ui.panel_data import DataPanel self.data_panel = DataPanel(self.app) self.addTab(self.data_panel, 'data') if request_focus: self.setCurrentWidget(self.data_panel) return self.hooks_panel elif tab_id == SessionUi.TAB_MODULES: if self.modules_panel is None: from ui.panel_modules import ModulesPanel self.modules_panel = ModulesPanel(self.app) self.addTab(self.modules_panel, 'modules') if request_focus: self.setCurrentWidget(self.modules_panel) return self.modules_panel elif tab_id == SessionUi.TAB_RANGES: if self.ranges_panel is None: from ui.panel_ranges import RangesPanel self.ranges_panel = RangesPanel(self.app) self.addTab(self.ranges_panel, 'ranges') if request_focus: self.setCurrentWidget(self.ranges_panel) return self.ranges_panel elif tab_id == SessionUi.TAB_ASM: if self.asm_panel is None: from ui.panel_asm import AsmPanel self.asm_panel = AsmPanel(self.app) self.addTab(self.asm_panel, 'asm') if request_focus: self.setCurrentWidget(self.asm_panel) return self.asm_panel elif tab_id == SessionUi.TAB_JAVA_CLASSES: if self.java_class_panel is None: from ui.panel_java_classes import JavaClassesPanel self.java_class_panel = JavaClassesPanel(self.app) if self.java_class_panel.parent() is None: self.addTab(self.java_class_panel, 'Java Classes') if request_focus: self.setCurrentWidget(self.java_class_panel) return self.java_class_panel elif tab_id == SessionUi.TAB_TRACE: if self.trace_panel is None: from ui.panel_trace import TracePanel self.trace_panel = TracePanel(self.app) if self.trace_panel.parent() is None: self.addTab(self.trace_panel, 'Trace') if request_focus: self.setCurrentWidget(self.trace_panel) return self.trace_panel elif tab_id == SessionUi.TAB_FTRACE: if self.ftrace_panel is None: from ui.panel_ftrace import FTracePanel self.ftrace_panel = FTracePanel(self.app) if self.ftrace_panel.parent() is None: self.addTab(self.ftrace_panel, 'ftrace') if request_focus: self.setCurrentWidget(self.ftrace_panel) return self.trace_panel def add_tab(self, tab_widget, tab_label): self.addTab(tab_widget, tab_label) self.setCurrentWidget(tab_widget) def disasm(self, ptr=0, _range=None): self.add_dwarf_tab(SessionUi.TAB_ASM, True) if _range: self.asm_panel.disasm(_range=_range) else: self.asm_panel.read_memory(ptr) def request_session_ui_focus(self): self.setCurrentWidget(self.session_panel) def show_java_panel(self): self.memory_panel.hide() self.java_explorer_panel.show() def show_memory_panel(self): self.java_explorer_panel.hide() self.memory_panel.show()
class Filter(QWidget): label_map = { 'K': 'Keep', 'T': 'Take', 'S': 'Stitch', 'M': 'Compare', 'C': 'Crop', 'D': 'Delete', None: '' } def __init__(self, parent, config, new_files): QWidget.__init__(self, parent) self.zoomLevel = 1.0 self.rotation = 0 self.all_images = ImageList() self.compare_set = ImageList() # start with all images self.images = self.all_images self.comparing = False self.src = config['Directories']['mid'] self.dst = os.getcwd() self.scan(self.src) self.new_files = new_files self.image = None self.image_actions = defaultdict(lambda: None) self.image_positions = {} self.original_position = None self.buildUI(parent) self.dir_dialog = QFileDialog(self) self.dir_dialog.setFileMode(QFileDialog.Directory) def buildUI(self, parent): # left labels self.splitter = QSplitter(self) self.splitter.setContentsMargins(QMargins(0, 0, 0, 0)) self.splitter.setOrientation(Qt.Horizontal) self.widget = QWidget(self.splitter) self.label_layout = QVBoxLayout(self.widget) for count, name in enumerate([ 'exposure_time', 'fnumber', 'iso_speed', 'focal_length', 'date', ]): key_label = QLabel(name.replace('_', ' ').title(), self.widget) # setattr(self, "key_label_%02d" % count, key_label) self.label_layout.addWidget(key_label) value_label = QLabel(self.widget) value_label.setAlignment(Qt.AlignRight) setattr(self, name, value_label) self.label_layout.addWidget(value_label) # TODO s = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.label_layout.addItem(s) # main view self.scene = QGraphicsScene() self.item = QGraphicsPixmapItem() self.scene.addItem(self.item) self.view = QGraphicsView(self.scene, parent) self.view.setFrameShadow(QFrame.Plain) self.view.setFrameStyle(QFrame.NoFrame) self.view.setDragMode(QGraphicsView.ScrollHandDrag) self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) brush = QBrush(QColor(20, 20, 20)) brush.setStyle(Qt.SolidPattern) self.view.setBackgroundBrush(brush) self.view.show() # "status bar" self.fname = QLabel(self) self.fname.setTextInteractionFlags(Qt.TextSelectableByMouse) spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.tag_view = QLabel(self) status_bar = QHBoxLayout() status_bar.addWidget(self.fname) status_bar.addItem(spacer) status_bar.addWidget(self.tag_view) w = QWidget(self.splitter) v = QVBoxLayout(w) v.setContentsMargins(QMargins(0, 0, 0, 0)) v.addWidget(self.view) v.addLayout(status_bar) # TODO self.splitter.setSizes([10, 90]) h = QHBoxLayout(self) h.setContentsMargins(QMargins(0, 0, 0, 0)) h.addWidget(self.splitter) # now... ACTION!(s) for key, slot in ( (Qt.Key_Home, self.first_image), (Qt.Key_PageUp, self.prev_ten), (Qt.Key_Backspace, self.prev_image), (Qt.Key_Space, self.next_image), (Qt.Key_PageDown, self.next_ten), (Qt.Key_End, self.last_image), (Qt.Key_F, self.toggle_fullsize), (Qt.Key_K, self.keep), (Qt.Key_T, self.tag), (Qt.Key_S, self.stitch), (Qt.Key_M, self.select_for_compare), (Qt.Key_C, self.crop), (Qt.Key_D, self.delete), (Qt.Key_U, self.untag), (Qt.Key_X, self.expunge), (Qt.Key_Return, self.apply), (Qt.CTRL + Qt.Key_M, self.compare), (Qt.CTRL + Qt.Key_O, self.new_src), (Qt.CTRL + Qt.Key_S, self.save), ): action = QAction(parent) action.setShortcut(QKeySequence(key)) action.triggered.connect(slot) self.view.addAction(action) def scan(self, src): index = 0 logger.debug('scanning %r', src) for r, dirs, files in os.walk(os.path.abspath(src)): for name in sorted(files): if name[-4:].lower() in ('.jpg', '.png'): # logger.info ('found %s' % name) self.images.append(Image(index, os.path.join(r, name))) index += 1 def rotate_view(self): # we have to 'undo' the rotations, so the numbers are negative rotate = -self.image.rotation # undo the last rotation and apply the new one self.view.rotate(-self.rotation + rotate) self.rotation = rotate logger.debug(rotate, self.rotation) def zoom_to_fit(self): winSize = self.view.size() logger.debug(self.image.size, winSize) hZoom = winSize.width() / self.image.size.width() vZoom = winSize.height() / self.image.size.height() zoomLevel = min(hZoom, vZoom) self.zoom(zoomLevel) def zoom(self, zoomLevel): # logger.info (zoomLevel) scale = zoomLevel / self.zoomLevel # logger.info ("scaling", scale) self.view.scale(scale, scale) self.zoomLevel = zoomLevel def move_index(self, to=None, how_much=0): if self.image is not None: self.save_position() self.images.move_index(to, how_much) self.image = self.images.current_image self.show_image() def view_position(self): view_size = self.view.size() center = QPoint(view_size.width() / 2, view_size.height() / 2) position = self.view.mapToScene(center) return position def show_image(self): logger.info(self.image.path) self.image.read() self.rotate_view() self.item.setPixmap(self.image.pixmap) if self.zoomLevel != 1.0: self.zoom_to_fit() # we might have rotated the view, but the scene still has the image # in its original size, so we use that as bounding rect boundingRect = QRectF(self.item.pixmap().rect()) logger.debug(boundingRect) self.scene.setSceneRect(boundingRect) if self.image.index in self.image_positions: self.original_position = None position = self.image_positions[self.image.index] logger.debug("previously moved, back to that point: %f x %f", position.x(), position.y()) self.view.centerOn(position) else: # TODO: 'undo' the move position = self.view_position() logger.debug("original position: %f x %f", position.x(), position.y()) self.original_position = position self.view.centerOn(self.item) self.update_view() def update_view(self): self.fname.setText(self.image.path) label = self.label_map[self.image_actions[self.image]] self.tag_view.setText(label) meta = self.image.metadata date = read_image_date(self.image.path, meta) if date is None: self.date.setText('unknown') else: self.date.setText(date.isoformat()) self.fnumber.setText(str(meta.get_fnumber())) self.focal_length.setText(str(meta.get_focal_length())) self.iso_speed.setText(str(meta.get_iso_speed())) f = meta.get_exposure_time() if f is None: s = 'unknown' elif f.denominator == 1: s = '%ds' % f.numerator else: s = '%d/%ds' % (f.numerator, f.denominator) self.exposure_time.setText(s) def save_position(self): position = self.view_position() if (self.original_position is None or position.x() != self.original_position.x() or position.y() != self.original_position.y()): logger.debug("saving position: %f x %f", position.x(), position.y()) # this way (I hope) I only remember those positions which changed self.image_positions[self.image.index] = position # movements def first_image(self, *args): self.move_index(to=0) def prev_ten(self, *args): self.move_index(how_much=-10) def prev_image(self, *args): self.move_index(how_much=-1) def next_image(self, *args): self.move_index(how_much=+1) def next_ten(self, *args): self.move_index(how_much=+10) def last_image(self, *args): self.move_index(to=len(self.images) - 1) def toggle_fullsize(self, *args): # noooooooooooooooothing compares... if abs(self.zoomLevel - 1.0) < 0.000001: # logger.info ('fit') self.zoom_to_fit() else: # logger.info ('orig') self.zoom(1.0) # image actions # Keep -> /gallery/foo, resized def keep(self, *args): self.image_actions[self.image] = 'K' self.next_image() # Tag -> /gallery/foo, as-is def tag(self, *args): self.image_actions[self.image] = 'T' self.next_image() # Stitch -> 02-new/stitch def stitch(self, *args): self.image_actions[self.image] = 'S' self.next_image() # coMpare def select_for_compare(self, *args): if self.image_actions[self.image] == 'M': # TODO?: undo/toggle # NOTE: this can already be achieved by Untag pass else: self.image_actions[self.image] = 'M' # ugh insort(self.compare_set, self.image) logger.debug(self.compare_set.images) self.next_image() def compare(self): logger.info('comparing') self.comparing = True self.images = self.compare_set self.move_index() # Crop -> launch gwenview def crop(self, *args): self.image_actions[self.image] = 'C' self.next_image() # Delete -> /dev/null def delete(self, *args): self.image_actions[self.image] = 'D' if self.comparing: # remove the image from the list and refresh the view self.images.remove() self.image = self.images.current_image self.show_image() else: self.next_image() def untag(self, *args): try: del self.image_actions[self.image] # don't move, most probably I'm reconsidering what to do # but change the label self.tag_view.setText('') except KeyError: # tried to untag a non-tagged image pass def resize(self, src, dst): src_meta = GExiv2.Metadata(src) src_p = QPixmap(src) dst_p = src_p.scaled(4500, 3000, Qt.KeepAspectRatio, Qt.SmoothTransformation) dst_p.save(src) shutil.move(src, dst) # copy all the metadata dst_meta = GExiv2.Metadata(dst) for tag in src_meta.get_tags(): dst_meta[tag] = src_meta[tag] dst_meta.save_file() def apply(self, *args): if not self.comparing: hugin = False if len([ action for action in self.image_actions.values() if action in ('K', 'T') ]) > 0: self.new_dst() for img, action in sorted( self.image_actions.items(), key=lambda s: s[0].path): # sort by fname src = img.path dst = os.path.join(self.dst, os.path.basename(src)) try: if src in self.new_files and action not in ('C', 'D'): # rename src = rename_file(src) if action == 'K': # Keep -> /gallery/foo, as-is logger.info("%s -> %s" % (src, dst)) shutil.move(src, dst) elif action == 'T': # Tag -> /gallery/foo, resized self.resize(src, dst) elif action == 'S': # Stitch -> 02-new/stitch dst = os.path.join( '/home/mdione/Pictures/incoming/02-new/stitch', os.path.basename(src)) logger.info("%s -> %s" % (src, dst)) shutil.move(src, dst) hugin = True elif action == 'M': # coMpare -> 03-cur dst = os.path.join( '/home/mdione/Pictures/incoming/03-cur', os.path.basename(src)) logger.info("%s -> %s" % (src, dst)) shutil.move(src, dst) new_root = '/home/mdione/Pictures/incoming/03-cur' old_root = self.src elif action == 'C': # Crop -> launch gwenview os.system('gwenview %s' % src) # asume the file was saved under a new name # logger.info ("%s -> %s" % (src, dst)) # shutil.move (src, dst) elif action == 'D': # Delete -> /dev/null os.unlink(src) logger.info("%s deleted" % (src, )) except FileNotFoundError as e: logger.info(e) if hugin: os.system('hugin') self.reset() else: logger.info('back to all') self.comparing = False # untag all images marked for compare for image in self.compare_set: # but only those still marked 'M' if self.image_actions[image] == 'M': del self.image_actions[image] self.compare_set.clear() self.images = self.all_images self.move_index() def expunge(self, *args): for img, action in self.image_actions.items(): src = img.path try: if action == 'D': # Delete -> /dev/null os.unlink(src) logger.info("%s deleted" % (src, )) except FileNotFoundError as e: logger.info(e) self.reset() def reset(self, new_root=None): if new_root is not None: self.src = new_root self.image_actions.clear() self.all_images.clear() self.compare_set.clear() self.comparing = False self.scan(self.src) def new_dst(self, *args): self.dir_dialog.setDirectory(self.dst) if self.dir_dialog.exec(): self.dst = self.dir_dialog.selectedFiles()[0] def new_src(self, *args): self.dir_dialog.setDirectory(self.src) if self.dir_dialog.exec(): self.src = self.dir_dialog.selectedFiles()[0] self.reset() def save(self, *args): src = self.image.path self.dir_dialog.setDirectory(self.dst) if self.dir_dialog.exec(): dst_dir = self.dir_dialog.selectedFiles()[0] if src in self.new_files: src = rename_file(src) dst = os.path.join(dst_dir, os.path.basename(src)) logger.info("%s -> %s" % (src, dst)) self.resize(src, dst) self.next_image()
class SearcherWidget(QWidget): def __init__(self, wheres_the_fck_receipt: api_interface.WheresTheFckReceipt, parent=None): QWidget.__init__(self, parent) self.wheres_the_fck_receipt = wheres_the_fck_receipt # type: api_interface.WheresTheFckReceipt self.results = None # type List[api_interface.Result] self.current_preview_image = None # query self.query = QLineEdit() self.query.mousePressEvent = lambda _: self.query.selectAll() self.query.returnPressed.connect(self.search_button_clicked) self.limit_box = QSpinBox() self.limit_box.valueChanged.connect(self.search_button_clicked) self.cs_box = QCheckBox("Case Sensitive") self.cs_box.stateChanged.connect(self.search_button_clicked) search_button = QPushButton('Search') search_button.clicked.connect(self.search_button_clicked) query_bar_layout = QHBoxLayout() query_bar_layout.setContentsMargins(0, 0, 0, 0) query_bar_layout.addWidget(QLabel("Search Term")) query_bar_layout.addWidget(self.query) query_bar_layout.addWidget(QLabel("Max. Results")) query_bar_layout.addWidget(self.limit_box) query_bar_layout.addWidget(self.cs_box) query_bar_layout.addWidget(search_button) # the file_list self.match_list = MatcherTableWidget() self.match_list.setShowGrid(True) self.match_list.setAutoScroll(True) self.match_list.setSelectionMode(QAbstractItemView.SingleSelection) self.match_list.setSelectionBehavior(QAbstractItemView.SelectRows) self.match_list.itemSelectionChanged.connect(self.match_list_item_selection_changed) self.match_list.itemDoubleClicked.connect(self.match_list_double_clicked) self.match_list.setEditTriggers(QAbstractItemView.NoEditTriggers) self.preview = QLabel() self.preview_widget = QSplitter() self.preview_widget.setContentsMargins(0, 0, 0, 0) self.preview_widget.addWidget(self.match_list) self.preview_widget.addWidget(self.preview) # review settings settings = QSettings('WheresTheFckReceipt', 'WheresTheFckReceipt') self.query.setText(settings.value("query_text", "")) self.limit_box.setValue(settings.value("limit_box_value", 0)) self.cs_box.setChecked(bool(settings.value("cs_box_checked", False))) # my layout layout = QVBoxLayout() layout.addLayout(query_bar_layout) layout.addWidget(self.preview_widget) self.setLayout(layout) def match_list_double_clicked(self, mi): row = mi.row() result = self.results[row] self.open_file(result.get_path()) def open_file(self, filepath): if platform.system() == 'Darwin': # macOS subprocess.call(('open', filepath)) elif platform.system() == 'Windows': # Windows os.startfile(filepath) else: # linux variants subprocess.call(('xdg-open', filepath)) def match_list_item_selection_changed(self): selected_items = self.match_list.selectedItems() if len(selected_items) == 0: return curr_row = self.match_list.currentRow() result = self.results[curr_row] im = result.get_preview_image() if im is not None: self.current_preview_image = QtGui.QImage(im.data, im.shape[1], im.shape[0], im.strides[0], QtGui.QImage.Format_RGB888).rgbSwapped() w = self.preview_widget.width() / 2.0 h = self.preview_widget.height() self.preview_widget.setSizes([w, w]) self.preview.setPixmap(QPixmap(self.current_preview_image).scaled(w, h, Qt.KeepAspectRatio)) else: self.preview.setText("Preview could not be loaded.") def splitter_moved(self, pos, index): w = self.preview.width() h = self.preview.height() self.preview.setPixmap(QPixmap(self.current_preview_image).scaled(w, h, Qt.KeepAspectRatio)) def search_button_clicked(self): self.preview.setText("No image selected.") settings = QSettings('WheresTheFckReceipt', 'WheresTheFckReceipt') settings.setValue("query_text", self.query.text()) settings.setValue("limit_box_value", self.limit_box.value()) settings.setValue("cs_box_checked", self.cs_box.isChecked()) del settings self.results = self.wheres_the_fck_receipt.search(self.query.text(), self.limit_box.value(), self.cs_box.isChecked()) self.match_list.clear() self.match_list.setColumnCount(3) self.match_list.setHorizontalHeaderLabels(['File', 'Page', 'Path']) header = self.match_list.horizontalHeader() header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) # header.setStretchLastSection(True) self.match_list.setRowCount(len(self.results)) for i in range(len(self.results)): result = self.results[i] path = result.get_path() self.match_list.setItem(i, 0, QTableWidgetItem(os.path.basename(path))) self.match_list.setItem(i, 1, QTableWidgetItem(str(result.get_page()))) self.match_list.setItem(i, 2, QTableWidgetItem(os.path.dirname(path))) self.query.setFocus() self.query.selectAll()
def __init__(self, app, *__args): super().__init__(*__args) self.app = app self.handle_history = [] self.setContentsMargins(0, 0, 0, 0) # main wrapper main_wrap = QVBoxLayout() main_wrap.setContentsMargins(1, 1, 1, 1) # create label wrapping_wdgt = QWidget() self.clazz = QLabel(wrapping_wdgt) self.clazz.setContentsMargins(10, 10, 10, 10) font = QFont() font.setBold(True) font.setPixelSize(19) self.clazz.setFont(font) self.clazz.setAttribute(Qt.WA_TranslucentBackground, True) # keep this wrapping_wdgt.setMaximumHeight(self.clazz.height() + 20) # add to mainwrapper main_wrap.addWidget(wrapping_wdgt) # create splitter splitter = QSplitter() splitter.setContentsMargins(0, 0, 0, 0) # left side left_col = QWidget() left_layout = QVBoxLayout() left_layout.setContentsMargins(0, 0, 0, 0) label = QLabel('methods'.upper()) font = label.font() font.setBold(True) label.setFont(font) label.setStyleSheet('color: #ef5350;') label.setContentsMargins(10, 0, 10, 2) label.setAttribute(Qt.WA_TranslucentBackground, True) # keep this left_layout.addWidget(label) self.methods = JavaMethodsWidget(self) left_layout.addWidget(self.methods) left_col.setLayout(left_layout) splitter.addWidget(left_col) # middle central_col = QWidget() central_layout = QVBoxLayout() central_layout.setContentsMargins(0, 0, 0, 0) label = QLabel('native fields'.upper()) label.setFont(font) label.setStyleSheet('color: #ef5350;') label.setContentsMargins(10, 0, 10, 2) label.setAttribute(Qt.WA_TranslucentBackground, True) # keep this central_layout.addWidget(label) self.native_fields = JavaFieldsWidget(self, ['name', 'value'], True) central_layout.addWidget(self.native_fields) central_col.setLayout(central_layout) splitter.addWidget(central_col) # right side right_col = QWidget() right_layout = QVBoxLayout() right_layout.setContentsMargins(0, 0, 0, 0) label = QLabel('fields'.upper()) label.setFont(font) label.setContentsMargins(10, 0, 10, 2) label.setAttribute(Qt.WA_TranslucentBackground, True) # keep this label.setStyleSheet('color: #ef5350;') right_layout.addWidget(label) self.fields = JavaFieldsWidget(self, ['name', 'class'], False) right_layout.addWidget(self.fields) right_col.setLayout(right_layout) splitter.addWidget(right_col) splitter.setStretchFactor(0, 2) splitter.setStretchFactor(1, 1) splitter.setStretchFactor(2, 1) main_wrap.addWidget(splitter) main_wrap.setSpacing(0) self.setLayout(main_wrap)
class Ui_ShiftWindow(): def __init__(self, shift_page): self.shift_page = shift_page def setupUi(self): self.setupShiftTitle() self.setupFileListUi() self.setupFileListView() # self.setupAdjustTab() self.setupAdjustmentView() self.setupShiftResult() def setupShiftTitle(self): self.outer_vertical_layout = QtWidgets.QVBoxLayout(self.shift_page) self.outer_vertical_layout.setContentsMargins(11, 0, 11, 0) self.outer_vertical_layout.setSpacing(6) self.outer_vertical_layout.setObjectName("outer_vertical_layout") self.shift_title_label = QtWidgets.QLabel(self.shift_page) sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.shift_title_label.sizePolicy().hasHeightForWidth()) self.shift_title_label.setSizePolicy(sizePolicy) self.shift_title_label.setMinimumSize(QtCore.QSize(0, 20)) font = QtGui.QFont() font.setPointSize(15) font.setBold(True) font.setWeight(75) self.shift_title_label.setFont(font) self.shift_title_label.setAlignment(QtCore.Qt.AlignCenter) self.shift_title_label.setObjectName("shift_title_label") self.outer_vertical_layout.addWidget(self.shift_title_label) self.shift_title_label.setText( translate("MainWindow", "Forward/Backward Adjustment")) self.outer_horizontalSplitter = QSplitter() self.outer_horizontalSplitter.setContentsMargins(11, 6, 11, 0) #self.outer_horizontalSplitter.setSpacing(6) self.outer_horizontalSplitter.setObjectName("outer_horizontalSplitter") #self.outer_horizontalSplitter.setOrientation(Qt.Vertical) self.outer_vertical_layout.addWidget(self.outer_horizontalSplitter) def setupFileListUi(self): # self.file_window = QtWidgets.QWidget() # self.outer_horizontalSplitter.addWidget(self.file_window) self.file_verticalSplitter = QSplitter() self.file_verticalSplitter.setContentsMargins(11, 6, 11, 0) #self.file_verticalSplitter.setSpacing(6) self.file_verticalSplitter.setObjectName("file_verticalSplitter") self.file_verticalSplitter.setOrientation(Qt.Vertical) self.outer_horizontalSplitter.addWidget(self.file_verticalSplitter) #self.outer_horizontalSplitter.addWidget(self.file_verticalSplitter) #self.outer_horizontalSplitter.addLayout(self.file_verticalLayout) self.filelist_widget = QtWidgets.QWidget() self.file_verticalSplitter.addWidget(self.filelist_widget) self.filelist_verticalLayout = QtWidgets.QVBoxLayout( self.filelist_widget) self.filelist_verticalLayout.setContentsMargins(0, 11, 0, 0) self.filelist_verticalLayout.setSpacing(6) self.filelist_verticalLayout.setObjectName("filelist_verticalLayout") self.filelistButton_horizontalLayout = QtWidgets.QHBoxLayout( self.filelist_widget) self.filelistButton_horizontalLayout.setContentsMargins(0, 11, 0, 6) self.filelistButton_horizontalLayout.setSpacing(6) self.filelistButton_horizontalLayout.setObjectName( "filelistButton_horizontalLayout") self.filelist_verticalLayout.addLayout( self.filelistButton_horizontalLayout) self.add_files_button = QtWidgets.QPushButton() sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.add_files_button.sizePolicy().hasHeightForWidth()) self.add_files_button.setSizePolicy(sizePolicy) self.add_files_button.setObjectName("add_files_button") self.add_files_button.setText(translate("MainWindow", "Add Files")) self.filelistButton_horizontalLayout.addWidget(self.add_files_button) self.clear_list_button = QtWidgets.QPushButton() sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.clear_list_button.sizePolicy().hasHeightForWidth()) self.clear_list_button.setSizePolicy(sizePolicy) self.clear_list_button.setObjectName("clear_list_button") self.clear_list_button.setText(translate("MainWindow", "Clear List")) self.filelistButton_horizontalLayout.addWidget(self.clear_list_button) spacerItem = QSpacerItem(20, 40, QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self.filelistButton_horizontalLayout.addItem(spacerItem) def setupFileListView(self): tableviewTitle = QLabel() tableviewTitle.setText("Contract List") font = QFont() font.setPointSize(10) #font.setBold(True) tableviewTitle.setFont(font) tableviewTitle.setAlignment(Qt.AlignLeft) self.filelist_verticalLayout.addWidget(tableviewTitle) self.filelistView = QtWidgets.QTableView() self.filelistView.setObjectName("filelistView") self.filelist_verticalLayout.addWidget(self.filelistView) def setupAdjustmentView(self): self.adjustWidget = QtWidgets.QWidget() self.file_verticalSplitter.addWidget(self.adjustWidget) self.adjust_veritcalLayout = QtWidgets.QVBoxLayout(self.adjustWidget) self.adjust_veritcalLayout.setContentsMargins(0, 11, 0, 0) adjustViewTitle = QLabel() adjustViewTitle.setText("Adjustment contract pair") font = QFont() font.setPointSize(10) adjustViewTitle.setFont(font) adjustViewTitle.setAlignment(Qt.AlignLeft) self.adjust_veritcalLayout.addWidget(adjustViewTitle) self.adjustView = QtWidgets.QTableView(self.adjustWidget) self.adjustView.setObjectName("adjustView") self.adjust_veritcalLayout.addWidget(self.adjustView) def setupShiftResult(self): self.result_window = QtWidgets.QWidget() self.outer_horizontalSplitter.addWidget(self.result_window) self.result_verticalLayout = QtWidgets.QVBoxLayout(self.result_window) self.result_verticalLayout.setContentsMargins(0, 8, 0, 0) #self.result_verticalLayout.setSpacing(6) self.result_verticalLayout.setObjectName("result_verticalLayout") self.result_option_horizontalLayout = QtWidgets.QHBoxLayout() self.result_option_horizontalLayout.setContentsMargins(0, 0, 0, 0) #self.result_option_horizontalLayout.setSpacing(6) self.result_option_horizontalLayout.setObjectName( "result_option_horizontalLayout") self.result_verticalLayout.addLayout( self.result_option_horizontalLayout) self.matchingLabel = QLabel() self.matchingLabel.setText("Matching price:") sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.matchingLabel.setSizePolicy(sizePolicy) self.matchingLabel.setMinimumSize(QtCore.QSize(20, 0)) font = QtGui.QFont() font.setPointSize(10) font.setBold(True) self.matchingLabel.setFont(font) self.result_option_horizontalLayout.addWidget(self.matchingLabel) self.matchingCombo = QComboBox() self.result_option_horizontalLayout.addWidget(self.matchingCombo) spacerItem = QtWidgets.QSpacerItem(20, 40, QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self.result_option_horizontalLayout.addItem(spacerItem) self.result_button_horizontalLayout = QtWidgets.QHBoxLayout() self.result_button_horizontalLayout.setContentsMargins(0, 0, 0, 6) self.result_button_horizontalLayout.setSpacing(6) self.result_button_horizontalLayout.setObjectName( "result_button_horizontalLayout") self.result_verticalLayout.addLayout( self.result_button_horizontalLayout) self.start_adjust_button = QtWidgets.QPushButton(self.shift_page) sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.start_adjust_button.sizePolicy().hasHeightForWidth()) self.start_adjust_button.setSizePolicy(sizePolicy) self.start_adjust_button.setObjectName("start_adjust_button") self.start_adjust_button.setText( translate("MainWindow", "Start Adjustment")) self.result_button_horizontalLayout.addWidget(self.start_adjust_button) self.draw_graph_button = QtWidgets.QPushButton(self.shift_page) sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.draw_graph_button.sizePolicy().hasHeightForWidth()) self.draw_graph_button.setSizePolicy(sizePolicy) self.draw_graph_button.setObjectName("draw_graph_button") self.draw_graph_button.setText(translate("MainWindow", "Draw Graph")) self.result_button_horizontalLayout.addWidget(self.draw_graph_button) self.save_result_button = QtWidgets.QPushButton(self.shift_page) sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.save_result_button.sizePolicy().hasHeightForWidth()) self.save_result_button.setSizePolicy(sizePolicy) self.save_result_button.setObjectName("save_result_button") self.save_result_button.setText(translate("MainWindow", "Save Result")) self.result_button_horizontalLayout.addWidget(self.save_result_button) resultviewTitle = QLabel() resultviewTitle.setText("Adjustment Result") font = QFont() font.setPointSize(10) #font.setBold(True) resultviewTitle.setFont(font) resultviewTitle.setAlignment(Qt.AlignLeft) self.result_verticalLayout.addWidget(resultviewTitle) self.resultView = QtWidgets.QTableView(self.shift_page) self.resultView.setObjectName("resultView") self.result_verticalLayout.addWidget(self.resultView)
class MyApp(QtWidgets.QWidget): def __init__(self): super(MyApp, self).__init__() self.setContentsMargins(0, 0, 0, 0) self.setWindowOpacity(5) self.setWindowTitle("youtube-get") self.resize(1100, 700) #setWindowState(Qt.Qt.WindowMaximized) #mainWidget=QtWidgets.QWidget() #窗口中的主Widget,不知如何在QMainWidow中加入布局 #所以要设置一个Widget将其放置在QMainWindow 的中心 #self.setWindowFlags(Qt.FramelessWindowHint) palette1 = QtGui.QPalette() palette1.setColor(self.backgroundRole(), QColor(0, 0, 0)) # 设置背景颜色 self.setPalette(palette1) self.InitData() self.init3() def init3(self): icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(":/Images/Icon256x256.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.setWindowIcon(icon) self.setWindowFlags(Qt.FramelessWindowHint) main_lay = QVBoxLayout() # ====================move widget to the center of screen============================== desktop = QtWidgets.QApplication.desktop() width = desktop.width() height = desktop.height() self.move((width - self.width()) / 2, (height - self.height()) / 2) self.setMouseTracking(True) #=================================================== #===================================================== self.main_sp = QSplitter(Qt.Horizontal) self.main_sp.setContentsMargins(0, 0, 0, 0) self.InitLeftWidget() self.InitRightWidget() self.main_sp.addWidget(self.LeftWidget) self.main_sp.setHandleWidth(0.1) self.main_sp.addWidget(self.RightWidget) self.main_sp.setStretchFactor(0, 8) self.main_sp.setStretchFactor(1, 6.5) self.main_sp.setContentsMargins(0, 0, 0, 0) main_lay.addWidget(self.main_sp) main_lay.setContentsMargins(0, 0, 0, 0) self.setLayout(main_lay) def closeEvent(self, QCloseEvent): promote = self.getDownloaingCount() reply = None if promote >= 1: if QMessageBox.warning(self, "promote", "任务正在下载,确定退出吗?", QMessageBox.Yes | QMessageBox.Cancel) == QMessageBox.Yes: super().closeEvent(QCloseEvent) else: super().closeEvent(QCloseEvent) def InitLeftWidget(self): #self.LeftWidget=QtWidgets.QWidget self.LeftWidget.setBackgrd(":/Images/tube_logo.jpg") left_layout = QGridLayout() left_layout.setRowStretch(1, 1) left_layout.setContentsMargins(0, 0, 0, 0) self.finished = MyButton(self.tr("已完成"), ":/Images/1.png", ":/Images/1-1.png") self.finished.clicked.connect(self.Btn_finished_clicked) left_layout.addWidget(self.finished, 2, 2, 1, 2) self.downloading = MyButton(self.tr("正在下载"), ":/Images/2.png", ":/Images/2-1.png") left_layout.addWidget(self.downloading, 3, 2, 1, 2) self.Btn_config = MyButton(self.tr("设置"), ":/Images/3.png", ":/Images/3-1.png") self.Btn_config.clicked.connect(self.Btn_finished_clicked) left_layout.addWidget(self.Btn_config, 4, 2, 1, 2) left_layout.setRowStretch(5, 3) left_layout.setColumnStretch(2, 1) left_layout.setColumnStretch(1, 0) left_layout.setSpacing(0) left_layout.setAlignment(Qt.AlignLeft) self.LeftWidget.setLayout(left_layout) def InitData(self): self.DLT_List = [] # For storing downloding thread self.SBL = [] # self.URL_dict = {} # For self.Status = [] self.finished_clik_count = 0 # =========================================================== self.LeftWidget = MyBaseWidget() self.RightWidget = MyBaseWidget() def InitRightWidget(self): #self.RightWidget=QtWidgets.QWidget() self.RightWidget.setBackgrd(":/Images/right_back2.jpg") self.RightWidget.setWindowOpacity(0.0) main_right_layout = QVBoxLayout() self.Init_right_title() main_right_layout.addLayout(self.right_title_layout) self.Init_right_top() main_right_layout.addLayout(self.right_top_layout) self.Init_right_bottom() main_right_layout.addWidget(self.MyTable) # main_right_layout.setStretchFactor(self.right_title_layout,4) # main_right_layout.setStretchFactor(self.right_top_layout, 2) # main_right_layout.setStretchFactor(self.MyTable, 1) self.RightWidget.setLayout(main_right_layout) def Init_right_title(self): # ==================close Button======================= self.right_title_layout = QHBoxLayout() self.close_btn = AeroButton(":/Images/close_1.png", ":/Images/close_2.png") self.close_btn.clicked.connect(self.closeEvent) # ===================maxmal button============================ self.maxmal_btn = AeroButton(":/Images/maxmal_1.png", ":/Images/maxmal_2.png") self.maxmal_btn.clicked.connect(self.showMaximized) # ===================minmal button========================================= self.minmal_btn = AeroButton(":/Images/minmal_1.png", ":/Images/minmal_2.png") self.minmal_btn.clicked.connect(self.showMinimized) #======================================================== self.right_title_layout.addWidget(self.minmal_btn) self.right_title_layout.addWidget(self.maxmal_btn) self.right_title_layout.addWidget(self.close_btn) self.right_title_layout.setAlignment(Qt.AlignRight) self.right_title_layout.setContentsMargins(0, 0, 0, 0) def Init_right_top(self): self.right_top_layout = QGridLayout() self.url_Label = QLabel("Vedio URL:") self.Proxy_Label = QLabel("Proxy:") self.Vedio_url = QLineEdit() self.Vedio_url.setStyleSheet('''QLineEdit(max-width:200px) ''') self.Proxy_Url = QLineEdit() self.Proxy_Url.setText("https://127.0.0.1:1080") # self.path_label = QLabel('output:') # self.path = QLineEdit() # self.path.setText('download\\') self.Btn_dl = AeroButton(":/Images/dlb-1.png", ":/Images/dlb-2.png") self.Btn_dl.clicked.connect(self.Btn_dl_clicked) # self.main_layout.setSpacing(1) self.right_top_layout.addWidget(self.Proxy_Label, 1, 0) self.right_top_layout.addWidget(self.Proxy_Url, 1, 1) self.right_top_layout.addWidget(self.url_Label, 2, 0) self.right_top_layout.addWidget(self.Vedio_url, 2, 1) self.right_top_layout.addWidget(self.Btn_dl, 2, 3) #self.right_top_layout.addWidget(self.Btn_config,2,4) self.right_top_layout.setColumnStretch(5, 1) self.right_top_layout.setColumnStretch(1, 1) # self.right_top_layout.addWidget(self.path_label, 1, 3) # self.right_top_layout.addWidget(self.path, 1, 4) def Init_right_bottom(self): self.Horz_header = ['片名', '地址', '大小', '状态', '速度', '剩余时间'] self.Horz_wid_ratio = [0.2, 0.2, 0.2, 0.3, 0.2, 0.1] self.MyTable = QTableWidget(15, len(self.Horz_header)) self.MyTable.setContentsMargins(0, 0, 0, 0) self.MyTable.setContextMenuPolicy(Qt.CustomContextMenu) self.MyTable.setWindowOpacity(0) self.MyTable.setStyleSheet('''background:transparent; selection-background-color:lightblue;''') self.MyTable.setStyleSheet('''background:transparent; selection-background-color:lightblue;QScrollBar{background: transparent;width: 10px;}''' ) self.MyTable.verticalHeader().setVisible(False) self.MyTable.horizontalHeader().setVisible(False) self.MyTable.customContextMenuRequested.connect(self.generateMenu) parent_width = self.MyTable.width() print('Table width:', parent_width) self.MyTable.setHorizontalHeaderLabels(self.Horz_header) self.MyTable.setShowGrid(False) self.MyTable.horizontalHeader().setStretchLastSection(True) for i in range(len(self.Horz_header)): print(i, parent_width * self.Horz_wid_ratio[i]) self.MyTable.setColumnWidth(i, parent_width * self.Horz_wid_ratio[i]) def generateMenu(self, pos): print(pos) row_num = -1 for i in self.MyTable.selectionModel().selection().indexes(): row_num = i.row() print("test") menu = QMenu() suspend = menu.addAction(u"暂停") goon = menu.addAction(u"继续") open_file_dir = menu.addAction(u"打开文件位置") action = menu.exec_(self.MyTable.mapToGlobal(pos)) selected = self.MyTable.item(row_num, 1) if action == suspend: if selected: #test=QThread() #test.wait() #self.DLT_List[row_num].wait(10000) print(row_num, "线程挂起!") elif action == goon: if selected: print(u'您选了选项二,当前行文字内容是:', self.MyTable.item(row_num, 1).text()) elif action == open_file_dir: if selected: print(u'您选了选项三,当前行文字内容是:', self.MyTable.item(row_num, 1).text()) else: return def Btn_dl_clicked(self): # when button download clicked, append a download task to the end of table url_normed, check = self.verifyDlParameters() Id = len(self.DLT_List) user_opt = {"ID": Id, "url_list": [url_normed]} youtube_opts = { 'proxy': self.Proxy_Url.text() } # "download_archive": []} if check == 100: # 可以添加 # print('+++++++++++++') self.URL_dict[url_normed] = {'ID': Id, 'position': Id, 'status': 1} self.DLT_List.append( YoutubeDL(params=youtube_opts, auto_init=True, user_opt=user_opt)) self.SBL.append(QProgressBar()) self.MyTable.setCellWidget(Id, 3, self.SBL[Id]) newItem = QTableWidgetItem("未知") self.MyTable.setItem(Id, 0, newItem) self.MyTable.setItem(Id, 2, newItem) self.MyTable.setItem(Id, 4, newItem) newItem = QTableWidgetItem(self.Vedio_url.text()) self.MyTable.setItem(Id, 1, newItem) self.DLT_List[-1].mySignal.connect(self.update_Dl_status) self.DLT_List[-1].exceptSignal.connect(self.dl_thread_except) self.DLT_List[-1].start() # lunch the downloading thread. self.downloading.setText('正在下载(' + str(self.getDownloaingCount()) + ')') print("The task has been added to the downloading list!") elif check == 101: # 下载异常 expt = self.URL_dict[url_normed]['position'] # print('restart task:' ,expt,url_normed) self.DLT_List[expt].params['proxy'] = self.Proxy_Url.text() # print('restart task:', expt, url_normed) self.DLT_List[expt].user_opt["url_list"] = [url_normed] self.DLT_List[expt].start() self.URL_dict[url_normed]['status'] = 1 self.downloading.setText('正在下载(' + str(self.getDownloaingCount()) + ')') elif check == 1011: #正在下载 QMessageBox.warning( self, "Warning", "The task has been added in downloading queue", QMessageBox.Reset | QMessageBox.Help | QMessageBox.Cancel, QMessageBox.Reset) elif check == 1010: # finished QMessageBox.warning(self, "Warning", "Download finished!", QMessageBox.Yes | QMessageBox.Cancel) elif check == 102: QMessageBox.warning( self, "Warning", "invalid URL!", QMessageBox.Reset | QMessageBox.Help | QMessageBox.Cancel, QMessageBox.Reset) def update_Dl_status(self, my_message): message = my_message.get('message') Id = my_message.get("ID") url_normed = self.getUrlwithId(Id) name = re.findall( re.compile(r"\[download\].*?Destination:.*?(.*)", re.S), message) if name: self.MyTable.setItem(Id, 0, QTableWidgetItem(name[0])) name = re.findall( re.compile(r"\[download\](.*)has already been downloaded", re.S), message) if name: self.MyTable.setItem(Id, 0, QTableWidgetItem(name[0])) complete = re.findall(r'\[download\](.*)%.*of.*?([0-9]+\.?[0-9]*\S*)', message) if complete and len(complete[0]) == 2: temp = complete[0] value = float(temp[0]) self.SBL[Id].setValue(value) print('update status:', temp[1]) self.MyTable.setItem(Id, 2, QTableWidgetItem(temp[1])) if abs(value - 100.0) < 1e-3: self.URL_dict[url_normed]['status'] = 0 # finished self.downloading.setText('正在下载(' + str(self.getDownloaingCount()) + ')') row = re.findall( re.compile(r".*?([0-9]+\.?[0-9]*)%.*of(.*)at(.*)ETA(.*)", re.S), message) if row and len(row[0]) >= 4: temp = row[0] # Horz_header=['片名','地址','大小','状态','速度','剩余时间'] self.MyTable.setItem(Id, 4, QTableWidgetItem(temp[2])) self.MyTable.setItem(Id, 2, QTableWidgetItem(temp[1])) self.MyTable.setItem(Id, 5, QTableWidgetItem(temp[3])) # self.MyTable.setItem(Id,1, QTableWidgetItem(row[0][0])) self.SBL[Id].setValue(float(temp[0])) def getUrlwithId(self, id): for key in self.URL_dict.keys(): if self.URL_dict[key]['ID'] == id: return key return None def getDownloaingCount(self): count = 0 for key in self.URL_dict.keys(): if self.URL_dict[key]['status'] == 1: count += 1 return count def dl_thread_except(self, message): except_ID = message.get("ID") url_normed = self.getUrlwithId(except_ID) self.URL_dict[url_normed]['status'] = -1 # 下载异常 except_url = "Task " + str(except_ID) # self.MyTable.item(except_ID,1) QMessageBox.warning( self, "Warning", except_url + " downloading error!", QMessageBox.Reset | QMessageBox.Help | QMessageBox.Cancel, QMessageBox.Reset) print(" dl_thread_except() receive exception message!") self.DLT_List[except_ID].terminate() self.downloading.setText('正在下载(' + str(self.getDownloaingCount()) + ')') def verifyDlParameters(self): url_normed = self.Vedio_url.text() print("self.URL_dict.keys()----->", self.URL_dict.keys()) pattern = re.compile(r'\s*http|https://.*?', re.S) if not re.match(pattern, url_normed): return url_normed, 102 if not re.match(pattern, self.Proxy_Url.text()): return url_normed, 102 if url_normed in self.URL_dict.keys(): status = self.URL_dict[url_normed]['status'] print(url_normed, ":", status) if status == -1: #download error! return url_normed, 101 elif status == 0: return url_normed, 1010 elif status == 1: return url_normed, 1011 return url_normed, 100 # def mouseMoveEvent(self, QMouseEvent): if self.__leftButtonPress: globalPos = QMouseEvent.globalPos() self.move(globalPos - self.__movePoint) def mousePressEvent(self, QMouseEvent): if QMouseEvent.button() == Qt.LeftButton: self.__leftButtonPress = True self.__movePoint = QMouseEvent.pos() def Btn_finished_clicked(self): self.finished_clik_count += 1 if self.finished_clik_count % 2 == 1: self.RightWidget.hide() else: self.RightWidget.show()
class MainWindow(QMainWindow): def __init__(self, settings): super().__init__() # Save the settings object for now and shutdown time. self.settings = settings # Initialize extras and dicts paths first, as other modules use them paths.initialize(settings) # Initialize our font db fonts.initialize(settings) # Set our font, which will propogate to our child widgets. fonts.notify_me(self._font_change) # ask for a signal self._font_change(False) # fake a signal now # Initialize the dictionary apparatus dictionaries.initialize(settings) # Initialize the color choices colors.initialize(settings) # Initialize the sequence number for opened files self.book_number = 0 # Initialize the path to the last-opened file, used to # start file-open dialogs. self.last_open_path = '.' # Initialize our dict of active panels self.panel_dict = PANEL_DICT.copy() # Initialize our dict of open documents {seqno:Book} self.open_books = {} self.focus_book = None # seqno of book in focus, see _focus_me # Initialize the list of recent files self.recent_files = [] # Initialize the handle of a help display widget self.help_widget = None # later, if at all # Finished initializing after the app is running QTimer.singleShot(300, self.finish_init) # Create the main window and set up the menus. self._uic() # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # As part of setup we often need to show the user a dialog, but when our # __init__ is first called, our window has not been shown and the # app.exec_() call has not been made. If ok_cancel_msg() is used in that # condition, there is a big delay and spinning cursor on the mac. So this # code is called from a one-shot timer 300ms after the window has been # created, so we are sure the app is processing events etc. def finish_init(self): #self.finish_init = False # never do this again # Initialize the set of files actually open when we shut down. last_session = self._read_flist('mainwindow/open_files') if len(last_session): # there were some files open if len(last_session) == 1: msg = _TR('Start-up dialog', 'One book was open at the end of the last session.') else: msg = _TR('Start-up dialog', '%n books were open at the end of the last session.', n=len(last_session)) info = _TR("Start-up dialog", "Click OK to re-open all") if utilities.ok_cancel_msg(msg, info, parent=self): for file_path in last_session: ftbs = utilities.path_to_stream(file_path) if ftbs: self._open(ftbs) if 0 == len(self.open_books): # We did not re-open any books, either because there were # none, or the user said No, or perhaps they were not found. self._new() # open one, new, book. # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Slot to receive the currentChanged signal from the editview tabset. # Look through self.open_books and find the one whose edit widget is # now current, and do a focus_me for it. def _editview_change(self, index): if index > -1: eview = self.editview_tabset.widget(index) for (seqno, book) in self.open_books.items(): if eview == book.get_edit_view(): self.focus_me(seqno) return mainwindow_logger.error('cannot relate editview tab index to book') # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Make a selected book the focus of all panels. This is called explicitly # when a book is first created, and when the editview tabset changes the # current selection. It is called also when an editview gets a focus-in # event. # Display that Book's various "-view" objects in panels, in the order # that the user left them and with the same active panel as before. Note # that a book (editview) can get a focus-in event when it was already the # focus in this sense, for example if this app was hidden and then # brought to the front. So be prepared for redundant calls. def focus_me(self, book_index): outgoing = self.focus_book if book_index == outgoing: return # redundant call mainwindow_logger.debug('focusing {0} = {1}'.format( book_index, self.open_books[book_index].get_book_name())) self.focus_book = book_index # Record the user's arrangement of panels for the outgoing book, # as a list of tuples ('tabname', widget) in correct sequence. if outgoing is not None: # false first time and after File>Close out_panel_dict = self.open_books[outgoing].panel_dict widg_list = [] for ix in range(self.panel_tabset.count()): widg_list.append((self.panel_tabset.tabText(ix), self.panel_tabset.widget(ix))) out_panel_dict['tab_list'] = widg_list out_panel_dict['current'] = self.panel_tabset.currentIndex() # Change all the panels to the widgets, in the sequence, of the new book in_panel_dict = self.open_books[book_index].panel_dict widg_list = in_panel_dict['tab_list'] self.panel_tabset.clear() for ix in range(len(widg_list)): (tab_text, widget) = widg_list[ix] self.panel_tabset.insertTab(ix, widget, tab_text) self.panel_tabset.setCurrentIndex(in_panel_dict['current']) self.editview_tabset.setCurrentIndex( self.editview_tabset.indexOf( self.open_books[book_index].get_edit_view())) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Called by the current book to make a particular tab the visible one, # e.g. to make the Find visible on a ^F. The argument is a widget # that should occupy one of the current tabs. Ask the tabset for its # index, and if it is found, make that the current index. (If it is # not found, log it and do nothing.) def make_tab_visible(self, tabwidg): ix = self.panel_tabset.indexOf(tabwidg) if ix >= 0: # widget exists in this tabset self.panel_tabset.setCurrentIndex(ix) return mainwindow_logger.error('Request to show nonexistent widget') # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Implement File>New: # Create a Book object # Call its new_empty() method, # Add it to the open_books dict keyed by its sequence number, # Display its text editor in a tab with the document name, and # Give it the focus. def _new(self): seq = self.book_number self.book_number += 1 new_book = book.Book(seq, self) new_book.new_empty() self.open_books[seq] = new_book index = self.editview_tabset.addTab(new_book.get_edit_view(), new_book.get_book_name()) self.editview_tabset.setTabToolTip( index, _TR('Tooltip of edit of new unsaved file', 'this file has not been saved')) self.focus_me(seq) # # For use from translators, do the New operation and return the Book # that is created so it can be loaded with translated text. def do_new(self): self._new() return self.open_books[self.focus_book] # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Quick check to see if a file path is already open. Called from _open # and from _build_recent (menu). Returned value is the sequence number # of the open book, or None. def _is_already_open(self, path): for (seq, book_object) in self.open_books.items(): if path == book_object.get_book_full_path(): return seq return None # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Implement File>Open. Dialog with the user (file dialog starts with # last-used book path). Result is None or a FileBasedTextStream that we # pass to _open(). def _file_open(self): fbts = utilities.ask_existing_file(_TR('File:Open dialog', 'Select a book file to open'), parent=self, starting_path=self.last_open_path) if fbts: # yes a readable file was chosen. self._open(fbts) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Open a file, given the document as a FileBasedTextStream, and META # means our metafile suffix (C.METAFILE_SUFFIX). # * If file opened is fname.META, look for a file named fname; if it # exists open it instead, e.g. given foo.txt.META, open foo.txt. # If it doesn't exist, tell the user and exit. # * If a file of the same name and path is already open, just focus # it and exit. # * Determine if there is a .META file, a .bin file, or neither # * Create a metadata input stream if possible # * If no .META, look for good_words and bad_words # * If the only open book is an "Untitled-n" and # it is unmodified, delete it. # * Call Book.old_book() or .new_book() as appropriate # * Add this book's editview to the edit tabset # * Give this book the focus. def _open(self, fbts): # look for opening a .META file if C.METAFILE_SUFFIX == fbts.suffix(): fb2 = utilities.file_less_suffix(fbts) if fb2 is None: m1 = _TR('File:Open', 'Cannot open a metadata file alone') m2 = _TR('File:Open', 'There is no book file matching ', 'filename follows this') + fbts.filename() utilities.warning_msg(m1, m2, parent=self) return # we see foo.txt with foo.txt.META, silently open it fbts = fb2 # look for already-open file seq = self._is_already_open(fbts.fullpath()) if seq is not None: self.focus_me(seq) return # start collecting auxiliary streams gw_stream = None bw_stream = None #gg_stream = None # open the metadata stream, which is always UTF-8 meta_stream = utilities.related_suffix(fbts, C.METAFILE_SUFFIX, encoding=C.ENCODING_UTF) if meta_stream is None: # opening book without metadata; look for .bin which is always LTN1 # This is no longer supported - somebody who cares, can write a # .bin-to-JSON utility if they want. #bin_stream = utilities.related_suffix(fbts,'bin',encoding=C.ENCODING_LATIN) #if bin_stream : #gg_stream = metadata.translate_bin(bin_stream,fbts) # Look for good_words.txt, bad_words.txt. gw_stream = utilities.related_file(fbts, 'good_words*.*') bw_stream = utilities.related_file(fbts, 'bad_words*.*') seq = self.book_number # If the only open book is the new one created at startup or when all # books are closed (which will have key 0), and it has not been # modified, get rid of it. if len(self.open_books) == 1 \ and 0 == list(self.open_books.keys())[0] \ and self.open_books[0].get_book_name().startswith('Untitled-') \ and not self.open_books[0].get_save_needed() : self.editview_tabset.clear() self.panel_tabset.clear() self.focus_book = None seq = 0 else: # Some other book open, or user typed into the default New one. self.book_number += 1 # Make the Book object and stow it in our open book dict a_book = book.Book(seq, self) self.open_books[seq] = a_book if meta_stream: # opening a book we previously saved a_book.old_book(fbts, meta_stream) else: a_book.new_book(fbts, gw_stream, bw_stream) index = self.editview_tabset.addTab(a_book.get_edit_view(), a_book.get_book_name()) self.editview_tabset.setTabToolTip(index, a_book.get_book_folder()) self.focus_me(seq) self.last_open_path = fbts.folderpath() # start for next open or save self._add_to_recent(fbts.fullpath()) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Save the book that is currently in focus under its present name, if it # is modified. Return True if the save completed, else False. # If the active book is a New one, force a Save-As action instead. def _save(self): active_book = self.open_books[self.focus_book] if active_book.get_save_needed(): if active_book.get_book_name().startswith('Untitled-'): return self._save_as() doc_stream = utilities.path_to_output( active_book.get_book_full_path()) if doc_stream: # successfully opened for output meta_stream = utilities.related_output(doc_stream, C.METAFILE_SUFFIX) if not meta_stream: utilities.warning_msg( _TR('File:Save', 'Unable to open metadata file for writing.'), _TR('File:Save', 'Use loglevel=error for details.'), parent=self) return False else: utilities.warning_msg(_TR( 'File:Save', 'Unable to open book file for writing.'), _TR('File:Save', 'Use loglevel=error for details.'), parent=self) return False return active_book.save_book(doc_stream, meta_stream) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Implement Save As. Query the user for a file path and get that as an # output FileBasedTextStream. Call the book to rename itself, which makes # it modified. Change the text in the edit tab to match. Discard the FBTS # and call _save which will make another one. def _save_as(self): active_book = self.open_books[self.focus_book] fbts = utilities.ask_saving_file( _TR('File:Save As dialog', 'Choose a new location and filename for this book'), self, active_book.get_book_folder()) if fbts: active_book.rename_book(fbts) self.editview_tabset.setTabText( self.editview_tabset.currentIndex(), fbts.filename()) self.editview_tabset.setTabToolTip( self.editview_tabset.currentIndex(), active_book.get_book_folder()) self._add_to_recent(fbts.fullpath()) fbts = None # discard that object return self._save() else: return False # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Implement Close. If the active book is modified, ask if it should # be saved. If it is 'Untitled-' that will turn into Save As. def _close(self): target_index = self.focus_book # active edit tab is to close target_book = self.open_books[target_index] if target_book.get_save_needed(): # Compose message of translated parts because _TR does not # allow for incorporating strings, only numbers. msg = _TR('File Close dialog', 'Book file ', 'filename follows here') msg += target_book.get_book_name() msg += _TR('File Close dialog', ' has been modified!', 'filename precedes this') ret = utilities.save_discard_cancel_msg( msg, info=_TR('File Close dialog', 'Save it, Discard changes, or Cancel Closing?'), parent=self) if ret is None: # Cancel return if ret: # True==Save self._save() # Now, get rid of the active book in 3 steps, # 1, close the book's tab in the editview tabset. We don't know which # tab it is, because the user can drag tabs around. i = self.editview_tabset.indexOf(target_book.get_edit_view()) # The following causes another tab to be focussed, changing self.focus_book # and saving target_book's tabs in target_book, not that we care. self.editview_tabset.removeTab(i) # 2, remove the book from our dict of open books. del self.open_books[target_index] # 3, if there are any open books remaining, the tab widget has # activated one of them by its rules, which caused a show signal and # entry to _focus_me already. However if there are no remaining books # there was no show signal or focus_me and the closed book's panels # are still in the tabset. if 0 == len(self.open_books): self.book_number = 0 # restart the sequence self.focus_book = None self._new() # One way or the other, a focus_me has removed all references to # active_book's view panels except those in its PANEL_DICT. So the # following assignment should remove the last reference to the book, # and schedule the book and associated objects for garbage collect. target_book = None # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Implement a Translate... submenu command. Call translators.xlt_book # with the book object that is currently in focus. If the process works # xlt_book will create a new book by calling do_new() and load it. # def _xlt_a_book(self): book = self.open_books[self.focus_book] datum = self.sender().data() translators.xlt_book(book, datum, self) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Implement loading and saving find panel user buttons. Start the search # for files in the active book's folder. User can navigate to extras # if need be. def _find_save(self): target_book = self.open_books[self.focus_book] find_panel = target_book.get_find_panel() stream = utilities.ask_saving_file( _TR('File:Save Find Buttons open dialog', 'Choose file to contain find button definitions'), self, starting_path=target_book.get_last_find_button_path(), encoding='UTF-8') if stream: # is not None, file is open target_book.set_last_find_button_path(stream.fullpath()) find_panel.user_button_output(stream) # else user hit cancel, forget it def _find_load(self): target_book = self.open_books[self.focus_book] find_panel = target_book.get_find_panel() stream = utilities.ask_existing_file( _TR('File:Load Find Buttons open dialog', 'Choose a file of find button definitions'), self, starting_path=target_book.get_last_find_button_path(), encoding='UTF-8') if stream: # is not None, we opened it target_book.set_last_find_button_path(stream.fullpath()) find_panel.user_button_input(stream) target_book.metadata_modified(True, C.MD_MOD_FLAG) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Maintain the list of "recent" file paths. The list is kept in usage # order, so if a path is in the list now, delete it and then add it to # the front. Keep it at a max of 9 items by deleting the oldest if # necessary. def _add_to_recent(self, path): if path in self.recent_files: del self.recent_files[self.recent_files.index(path)] self.recent_files.insert(0, path) self.recent_files = self.recent_files[:9] # Upon the aboutToShow signal from the File menu, populate the Recent # submenu with a list of files, but only the ones that are currently # accessible. If one is on a volume (e.g. USB stick) and you unmount the # volume, the path should not appear in the menu until the volume is # mounted again. def _open_recent(self): path = self.sender().data() fbts = utilities.path_to_stream(path) if fbts: self._open(fbts) def _build_recent(self): active_files = [] self.recent_menu.clear() self.recent_menu.setEnabled(False) for path in self.recent_files: seq = self._is_already_open(path) if (seq is None) and utilities.file_is_accessible(path): active_files.append(path) if 0 == len(active_files): return self.recent_menu.setEnabled(True) for (i, path) in enumerate(active_files, start=1): (folder, fname) = os.path.split(path) act = self.recent_menu.addAction('{0} {1} {2}'.format( i, fname, folder)) act.setData(path) act.triggered.connect(self._open_recent) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # User has chosen a different font; if it is the general font, set # that here so it will propogate to our children. n.b. this is never # used as the preference for UI font is not implemented. def _font_change(self, is_mono): if not is_mono: self.setFont(fonts.get_general()) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Preferences menu action triggered. Create a Preferences dialog and # show it. def _preferences(self): p = preferences.PreferenceDialog(self) r = p.exec_() # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Help menu action triggered. If the Help widget has not yet been # created, create it. Otherwise just show it and raise it. def _show_help(self): if self.help_widget is None: self.help_widget = helpview.HelpWidget() self.help_widget.show() self.help_widget.raise_() # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Create the UI contained within this QMainWindow object. This is a lean # main window indeed. We have no toolbar, no status bar, no dock, # nothing. Just a splitter with, on the left, a tabset for editviews, and # on the right, a scrollbar containing a tabset for panels. (Qt Designer # note: it is not possible to build this structure with the Designer. It # will not let you put the scroll area into the splitter.) # # TODO: create a custom QTabWidget using a custom QTabBar to implement # drag-out-of-tabset behavior, and use those here. def _uic(self): global _EDIT_MENU # Create the tabset that displays editviews self.editview_tabset = QTabWidget() self.editview_tabset.setMovable(True) # let user move tabs around self.editview_tabset.currentChanged.connect(self._editview_change) # Create the tabset that displays find, notes, help &etc. self.panel_tabset = QTabWidget() self.panel_tabset.setMovable(True) # Create the splitter that contains the above two parts. self.splitter = QSplitter(Qt.Horizontal, self) self.splitter.setChildrenCollapsible(False) # Give just a little margin to the left of the editor self.splitter.setContentsMargins(8, 0, 0, 0) self.splitter.addWidget(self.editview_tabset) self.splitter.addWidget(self.panel_tabset) # Set that splitter as the main window's central (and only) widget self.setCentralWidget(self.splitter) # Populate the panel tabset with empty widgets just so there will # be tabs that _swap can reference. for key in self.panel_dict.keys(): widj = QWidget() self.panel_tabset.addTab(widj, key) self.panel_dict[key] = widj # Size and position ourself based on saved settings. self.move(self.settings.value("mainwindow/position", QPoint(50, 50))) self.resize( self.settings.value("mainwindow/size", C.STARTUP_DEFAULT_SIZE)) self.splitter.restoreState( self.settings.value("mainwindow/splitter", C.STARTUP_DEFAULT_SPLITTER)) self.restoreState( self.settings.value("mainwindow/windowstate", QByteArray())) # Store a reference to the application menubar. In Mac OS this # is a parentless menubar; other platforms it is the default. if C.PLATFORM_IS_MAC: self.menu_bar = QMenuBar() # parentless menu bar for Mac OS else: self.menu_bar = self.menuBar() # refer to the default one # Create the File menu, located in our menu_bar. self.file_menu = self.menu_bar.addMenu(_TR('Menu name', '&File')) # Populate the File menu with actions. # File:New -> _new() work = self.file_menu.addAction(_TR('File menu command', '&New')) work.setShortcut(QKeySequence.New) work.setToolTip(_TR('File:New tooltip', 'Create a new, empty document')) work.triggered.connect(self._new) # File:Open -> _file_open() work = self.file_menu.addAction(_TR('File menu command', '&Open')) work.setShortcut(QKeySequence.Open) work.setToolTip(_TR('File:Open tooltip', 'Open an existing book')) work.triggered.connect(self._file_open) # File:Save -> _file_save() work = self.file_menu.addAction(_TR('File menu command', '&Save')) work.setShortcut(QKeySequence.Save) work.setToolTip(_TR('File:Save tooltip', 'Save the active book')) work.triggered.connect(self._save) # Save As -> _file_save_as() work = self.file_menu.addAction(_TR('File menu command', 'Save &As')) work.setShortcut(QKeySequence.SaveAs) work.setToolTip( _TR('File:Save As tooltip', 'Save the active book under a new name')) work.triggered.connect(self._save_as) # Close -> _close() work = self.file_menu.addAction(_TR('File menu command', 'Close')) work.setShortcut(QKeySequence.Close) work.setToolTip(_TR('File:Close tooltip', 'Close the active book')) work.triggered.connect(self._close) # Load Find Buttons -> _find_load() work = self.file_menu.addAction( _TR('File menu command', 'Load Find Buttons')) work.setToolTip( _TR( 'File:Load Find Buttons tooltip', 'Load a file of definitions for the custom buttons in the Find panel' )) work.triggered.connect(self._find_load) # Save Find Buttons -> _find_save() work = self.file_menu.addAction( _TR('File menu command', 'Save Find Buttons')) work.setToolTip( _TR('File:Save Find Buttons tooltip', 'Save definitions of the custom buttons in the Find panel')) work.triggered.connect(self._find_save) # Translate... gets a submenu with an entry for every Translator # in extras/Translators (if any). The actions connect to _xlt_a_book. self.translate_submenu = translators.build_xlt_menu( self, self._xlt_a_book) self.file_menu.addMenu(self.translate_submenu) # Open Recent gets a submenu that is added to the File menu. # The aboutToShow signal is connected to our _build_recent slot. self.recent_menu = QMenu(_TR('Sub-menu name', '&Recent Files')) work = self.file_menu.addMenu(self.recent_menu) work.setToolTip( _TR('File:Recent tooltip', 'List of recently-used files to open')) self.file_menu.aboutToShow.connect(self._build_recent) # Put in a divider above the Help, Preferences and Quit actions. self.file_menu.addSeparator() # Help opens or un-hides the Help viewer work = self.file_menu.addAction(_TR('Help menu item', 'Help')) work.setToolTip( _TR('Help menu item tooltip', 'Display the Help/User Manual in a separate window')) work.triggered.connect(self._show_help) self.file_menu.addAction(work) # Preferences: On the Mac, Preferences is automatically moved to the app menu. work = self.file_menu.addAction( _TR('Preferences menu item', 'Preferences')) work.setToolTip( _TR( 'Preferences menu item tooltip', 'Open the Preferences dialog to set paths, fonts, and text styles' )) work.setMenuRole(QAction.PreferencesRole) work.triggered.connect(self._preferences) # Quit choice, with the menu role that moves it to the app menu work = QAction(_TR('Quit command', '&Quit'), self) work.setMenuRole(QAction.QuitRole) work.setShortcut(QKeySequence.Quit) work.triggered.connect(self.close) self.file_menu.addAction(work) # Initialize the list of "recent" files for the File sub-menu. # These files were not necessarily open at shutdown, just sometime # in the not too distant past. self.recent_files = self._read_flist('mainwindow/recent_files') # Create the Edit menu in the menu_bar, store a reference to it # in a static global, and immediately clear it. _EDIT_MENU = self.menu_bar.addMenu(C.ED_MENU_EDIT) hide_edit_menu() # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= # Functions related to shutdown and management of settings. # # Factor out the job of reading/writing a list of files in the settings. # Input is a settings array key string like 'mainwindow/recent_files' # Output is a possibly empty list of canonical-file-path strings. def _read_flist(self, array_key): f_list = [] f_count = self.settings.beginReadArray(array_key) for f in range(f_count): # which may be 0 self.settings.setArrayIndex(f) f_list.append(self.settings.value('filepath')) self.settings.endArray() return f_list # Input is an array key and a possibly empty list of path strings def _write_flist(self, file_list, array_key): if len(file_list): self.settings.beginWriteArray(array_key, len(file_list)) for f in range(len(file_list)): self.settings.setArrayIndex(f) self.settings.setValue('filepath', file_list[f]) self.settings.endArray() # Reimplement QWidget.closeEvent in order to save any open files # and update the settings. def closeEvent(self, event): # If there are any unsaved books, ask the user if they should be # saved. If the answer is yes, try to do so. unsaved = [] for (seq, book_object) in self.open_books.items(): if book_object.get_save_needed(): unsaved.append(seq) if len(unsaved): if len(unsaved) == 1: msg = _TR('Shutdown message', 'There is one unsaved file') else: msg = _TR('Shutdown message', 'There are %n unsaved files', n=len(unsaved)) ret = utilities.save_discard_cancel_msg( msg, _TR('Shutdown message', 'Save, Discard changes, or Cancel Quit?'), parent=self) if ret is None: # user wants to cancel shutdown event.ignore() return if ret: # User want to save. Focus each unsaved file and call _save. # For all but "Untitled-n" documents this will be silent. For # those, it will open a save-as dialog. We ignore the return # from this because we cannot distinguish between a cancelled # file-open dialog and a file write error. for seq in unsaved: self.focus_me(seq) self._save() # Clear the settings so that old values don't hang around self.settings.clear() # Tell the submodules to save their current global values. colors.shutdown(self.settings) fonts.shutdown(self.settings) dictionaries.shutdown(self.settings) paths.shutdown(self.settings) # Save the list of currently-open files in the settings, but do not # save any whose filename matches "Untitled-#" because that is an # unsaved New file (which the user chose not to save, above). open_paths = [] for (index, book_obj) in self.open_books.items(): if not book_obj.get_book_name().startswith('Untitled-'): open_paths.append(book_obj.get_book_full_path()) self._write_flist(open_paths, 'mainwindow/open_files') # Save the list of "recent" files in the settings. self._write_flist(self.recent_files, 'mainwindow/recent_files') # Save this window's position and size and splitter state self.settings.setValue("mainwindow/size", self.size()) self.settings.setValue("mainwindow/position", self.pos()) self.settings.setValue("mainwindow/splitter", self.splitter.saveState()) self.settings.setValue("mainwindow/windowstate", self.saveState()) # If the Help window is open, close it -- as a modeless widget, # it won't close just because we do. if self.help_widget: # is not None, self.help_widget.close() # and that's it, we are done finished, over & out. event.accept()
class Ui_PerpetualWindow(): def __init__(self, perpetual_page): self.perpetual_page = perpetual_page def setupUi(self): self.setupTitle() self.setupFileListUi() self.setupFileListView() # self.setupAdjustTab() self.setupAdjustmentView() self.setupResult() def setupTitle(self): self.outer_vertical_layout = QtWidgets.QVBoxLayout(self.perpetual_page) self.outer_vertical_layout.setContentsMargins(11, 0, 11, 0) self.outer_vertical_layout.setSpacing(6) self.outer_vertical_layout.setObjectName("outer_vertical_layout") self.perpetual_title_label = QtWidgets.QLabel(self.perpetual_page) sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.perpetual_title_label.sizePolicy().hasHeightForWidth()) self.perpetual_title_label.setSizePolicy(sizePolicy) self.perpetual_title_label.setMinimumSize(QtCore.QSize(0, 20)) font = QtGui.QFont() font.setPointSize(15) font.setBold(True) font.setWeight(75) self.perpetual_title_label.setFont(font) self.perpetual_title_label.setAlignment(QtCore.Qt.AlignCenter) self.perpetual_title_label.setObjectName("perpetual_title_label") self.outer_vertical_layout.addWidget(self.perpetual_title_label) self.perpetual_title_label.setText(translate("MainWindow", "Perpetual Series Maker")) self.outer_horizontalSplitter = QSplitter() self.outer_horizontalSplitter.setContentsMargins(11, 6, 11, 0) #total_size = self.outer_horizontalSplitter.size() self.outer_horizontalSplitter.setSizes([320, 320]) #self.outer_horizontalSplitter.setSpacing(6) self.outer_horizontalSplitter.setObjectName("outer_horizontalSplitter") #self.outer_horizontalSplitter.setOrientation(Qt.Vertical) self.outer_vertical_layout.addWidget(self.outer_horizontalSplitter) def setupFileListUi(self): # self.file_window = QtWidgets.QWidget() # self.outer_horizontalSplitter.addWidget(self.file_window) self.file_verticalSplitter = QSplitter() self.file_verticalSplitter.setContentsMargins(11, 6, 11, 0) #self.file_verticalSplitter.setSpacing(6) self.file_verticalSplitter.setObjectName("file_verticalSplitter") self.file_verticalSplitter.setOrientation(Qt.Vertical) self.outer_horizontalSplitter.addWidget(self.file_verticalSplitter) #self.outer_horizontalSplitter.addWidget(self.file_verticalSplitter) #self.outer_horizontalSplitter.addLayout(self.file_verticalLayout) self.filelist_widget = QtWidgets.QWidget() self.filelist_widget.setMinimumWidth(550) policy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.filelist_widget.setSizePolicy(policy) self.file_verticalSplitter.addWidget(self.filelist_widget) self.filelist_verticalLayout = QtWidgets.QVBoxLayout(self.filelist_widget) self.filelist_verticalLayout.setContentsMargins(0, 11, 0, 0) self.filelist_verticalLayout.setSpacing(6) self.filelist_verticalLayout.setObjectName("filelist_verticalLayout") self.filelistButton_horizontalLayout = QtWidgets.QHBoxLayout(self.filelist_widget) self.filelistButton_horizontalLayout.setContentsMargins(0, 11, 0, 6) self.filelistButton_horizontalLayout.setSpacing(6) self.filelistButton_horizontalLayout.setObjectName("filelistButton_horizontalLayout") self.filelist_verticalLayout.addLayout(self.filelistButton_horizontalLayout) self.add_files_button = QtWidgets.QPushButton() sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.add_files_button.sizePolicy().hasHeightForWidth()) self.add_files_button.setSizePolicy(sizePolicy) self.add_files_button.setObjectName("add_files_button") self.add_files_button.setText(translate("MainWindow", "Add Files")) self.filelistButton_horizontalLayout.addWidget(self.add_files_button) self.clear_list_button = QtWidgets.QPushButton() sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.clear_list_button.sizePolicy().hasHeightForWidth()) self.clear_list_button.setSizePolicy(sizePolicy) self.clear_list_button.setObjectName("clear_list_button") self.clear_list_button.setText(translate("MainWindow", "Clear List")) self.filelistButton_horizontalLayout.addWidget(self.clear_list_button) # spacerItem = QSpacerItem(20, 40, QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) # self.filelistButton_horizontalLayout.addItem(spacerItem) def setupFileListView(self): tableviewTitle = QLabel() tableviewTitle.setText("Contract List") font = QFont() font.setPointSize(10) #font.setBold(True) tableviewTitle.setFont(font) tableviewTitle.setAlignment(Qt.AlignLeft) self.filelist_verticalLayout.addWidget(tableviewTitle) self.filelistView = QtWidgets.QTableView() self.filelistView.setObjectName("filelistView") self.filelist_verticalLayout.addWidget(self.filelistView) def setupAdjustmentView(self): self.adjustWidget = QtWidgets.QWidget() self.file_verticalSplitter.addWidget(self.adjustWidget) self.adjust_veritcalLayout = QtWidgets.QVBoxLayout(self.adjustWidget) self.adjust_veritcalLayout.setContentsMargins(0, 11, 0, 0) adjustViewTitle = QLabel() adjustViewTitle.setText("Adjustment contract pair") font = QFont() font.setPointSize(10) adjustViewTitle.setFont(font) adjustViewTitle.setAlignment(Qt.AlignLeft) self.adjust_veritcalLayout.addWidget(adjustViewTitle) self.adjustView = QtWidgets.QTableView(self.adjustWidget) self.adjustView.setObjectName("adjustView") self.adjust_veritcalLayout.addWidget(self.adjustView) def setupResult(self): self.result_window = QtWidgets.QWidget() self.outer_horizontalSplitter.addWidget(self.result_window) self.result_verticalLayout = QtWidgets.QVBoxLayout(self.result_window) self.result_verticalLayout.setContentsMargins(0, 8, 0, 0) #self.result_verticalLayout.setSpacing(6) self.result_verticalLayout.setObjectName("result_verticalLayout") self.result_option_horizontalLayout = QtWidgets.QHBoxLayout() self.result_option_horizontalLayout.setContentsMargins(0, 0, 0, 0) #self.result_option_horizontalLayout.setSpacing(6) self.result_option_horizontalLayout.setObjectName("result_option_horizontalLayout") self.result_verticalLayout.addLayout(self.result_option_horizontalLayout) self.matchingLabel = QLabel() self.matchingLabel.setText("Matching price:") sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.matchingLabel.setSizePolicy(sizePolicy) self.matchingLabel.setMinimumSize(QtCore.QSize(20, 0)) font = QtGui.QFont() font.setPointSize(10) font.setBold(True) self.matchingLabel.setFont(font) self.result_option_horizontalLayout.addWidget(self.matchingLabel) self.matchingCombo = QComboBox() self.result_option_horizontalLayout.addWidget(self.matchingCombo) # # vline = QFrame() # vline.setGeometry(QRect(0, 0, 2, 2)) # vline.setFrameShape(QFrame.VLine) # vline.setFrameShadow(QFrame.Sunken) # self.result_option_horizontalLayout.addWidget(vline) self.rollingPeriodLabel = QLabel() self.rollingPeriodLabel.setText("Rolling period:") self.rollingPeriodLabel.setIndent(30) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.rollingPeriodLabel.setSizePolicy(sizePolicy) self.rollingPeriodLabel.setMinimumSize(QtCore.QSize(20, 0)) font = QtGui.QFont() font.setPointSize(10) font.setBold(True) self.rollingPeriodLabel.setFont(font) self.result_option_horizontalLayout.addWidget(self.rollingPeriodLabel) self.rollingPeriodCombo = QComboBox() self.result_option_horizontalLayout.addWidget(self.rollingPeriodCombo) self.weightLabel = QLabel() self.weightLabel.setText("Adjust Weight:") self.weightLabel.setIndent(30) sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.weightLabel.setSizePolicy(sizePolicy) self.weightLabel.setMinimumSize(QtCore.QSize(20, 0)) font = QtGui.QFont() font.setPointSize(10) font.setBold(True) self.weightLabel.setFont(font) self.result_option_horizontalLayout.addWidget(self.weightLabel) self.weightCombo = QComboBox() self.result_option_horizontalLayout.addWidget(self.weightCombo) spacerItem = QtWidgets.QSpacerItem(20, 40, QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self.result_option_horizontalLayout.addItem(spacerItem) self.result_button_horizontalLayout = QtWidgets.QHBoxLayout() self.result_button_horizontalLayout.setContentsMargins(0,0, 0, 6) self.result_button_horizontalLayout.setSpacing(6) self.result_button_horizontalLayout.setObjectName("result_button_horizontalLayout") self.result_verticalLayout.addLayout(self.result_button_horizontalLayout) self.start_adjust_button = QtWidgets.QPushButton(self.perpetual_page) sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.start_adjust_button.sizePolicy().hasHeightForWidth()) self.start_adjust_button.setSizePolicy(sizePolicy) self.start_adjust_button.setObjectName("start_adjust_button") self.start_adjust_button.setText(translate("MainWindow", "Start Adjustment")) self.result_button_horizontalLayout.addWidget(self.start_adjust_button) self.draw_graph_button = QtWidgets.QPushButton(self.perpetual_page) sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.draw_graph_button.sizePolicy().hasHeightForWidth()) self.draw_graph_button.setSizePolicy(sizePolicy) self.draw_graph_button.setObjectName("draw_graph_button") self.draw_graph_button.setText(translate("MainWindow", "Draw Graph")) self.result_button_horizontalLayout.addWidget(self.draw_graph_button) self.save_result_button = QtWidgets.QPushButton(self.perpetual_page) sizePolicy = QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.save_result_button.sizePolicy().hasHeightForWidth()) self.save_result_button.setSizePolicy(sizePolicy) self.save_result_button.setObjectName("save_result_button") self.save_result_button.setText(translate("MainWindow", "Save Result")) self.result_button_horizontalLayout.addWidget(self.save_result_button) resultviewTitle = QLabel() resultviewTitle.setText("Adjustment Result") font = QFont() font.setPointSize(10) #font.setBold(True) resultviewTitle.setFont(font) resultviewTitle.setAlignment(Qt.AlignLeft) self.result_verticalLayout.addWidget(resultviewTitle) self.resultView = QtWidgets.QTableView(self.perpetual_page) self.resultView.setObjectName("resultView") self.result_verticalLayout.addWidget(self.resultView)
class GuiMain(QMainWindow): def __init__(self): QMainWindow.__init__(self) logger.debug("Initialising GUI ...") self.setObjectName("GuiMain") self.mainConf = nw.CONFIG self.threadPool = QThreadPool() # System Info # =========== logger.info("OS: %s" % self.mainConf.osType) logger.info("Kernel: %s" % self.mainConf.kernelVer) logger.info("Host: %s" % self.mainConf.hostName) logger.info("Qt5 Version: %s (%d)" % ( self.mainConf.verQtString, self.mainConf.verQtValue) ) logger.info("PyQt5 Version: %s (%d)" % ( self.mainConf.verPyQtString, self.mainConf.verPyQtValue) ) logger.info("Python Version: %s (0x%x)" % ( self.mainConf.verPyString, self.mainConf.verPyHexVal) ) # Core Classes # ============ # Core Classes and Settings self.theTheme = GuiTheme(self) self.theProject = NWProject(self) self.theIndex = NWIndex(self.theProject, self) self.hasProject = False self.isFocusMode = False # Prepare Main Window self.resize(*self.mainConf.getWinSize()) self._updateWindowTitle() self.setWindowIcon(QIcon(self.mainConf.appIcon)) # Build the GUI # ============= # Main GUI Elements self.statusBar = GuiMainStatus(self) self.treeView = GuiProjectTree(self) self.docEditor = GuiDocEditor(self) self.viewMeta = GuiDocViewDetails(self) self.docViewer = GuiDocViewer(self) self.treeMeta = GuiItemDetails(self) self.projView = GuiOutline(self) self.projMeta = GuiOutlineDetails(self) self.mainMenu = GuiMainMenu(self) # Minor Gui Elements self.statusIcons = [] self.importIcons = [] # Project Tree View self.treePane = QWidget() self.treeBox = QVBoxLayout() self.treeBox.setContentsMargins(0, 0, 0, 0) self.treeBox.addWidget(self.treeView) self.treeBox.addWidget(self.treeMeta) self.treePane.setLayout(self.treeBox) # Splitter : Document Viewer / Document Meta self.splitView = QSplitter(Qt.Vertical) self.splitView.addWidget(self.docViewer) self.splitView.addWidget(self.viewMeta) self.splitView.setSizes(self.mainConf.getViewPanePos()) # Splitter : Document Editor / Document Viewer self.splitDocs = QSplitter(Qt.Horizontal) self.splitDocs.addWidget(self.docEditor) self.splitDocs.addWidget(self.splitView) # Splitter : Project Outlie / Outline Details self.splitOutline = QSplitter(Qt.Vertical) self.splitOutline.addWidget(self.projView) self.splitOutline.addWidget(self.projMeta) self.splitOutline.setSizes(self.mainConf.getOutlinePanePos()) # Main Tabs : Edirot / Outline self.tabWidget = QTabWidget() self.tabWidget.setTabPosition(QTabWidget.East) self.tabWidget.setStyleSheet("QTabWidget::pane {border: 0;}") self.tabWidget.addTab(self.splitDocs, "Editor") self.tabWidget.addTab(self.splitOutline, "Outline") self.tabWidget.currentChanged.connect(self._mainTabChanged) # Splitter : Project Tree / Main Tabs xCM = self.mainConf.pxInt(4) self.splitMain = QSplitter(Qt.Horizontal) self.splitMain.setContentsMargins(xCM, xCM, xCM, xCM) self.splitMain.addWidget(self.treePane) self.splitMain.addWidget(self.tabWidget) self.splitMain.setSizes(self.mainConf.getMainPanePos()) # Indices of All Splitter Widgets self.idxTree = self.splitMain.indexOf(self.treePane) self.idxMain = self.splitMain.indexOf(self.tabWidget) self.idxEditor = self.splitDocs.indexOf(self.docEditor) self.idxViewer = self.splitDocs.indexOf(self.splitView) self.idxViewDoc = self.splitView.indexOf(self.docViewer) self.idxViewMeta = self.splitView.indexOf(self.viewMeta) self.idxTabEdit = self.tabWidget.indexOf(self.splitDocs) self.idxTabProj = self.tabWidget.indexOf(self.splitOutline) # Splitter Behaviour self.splitMain.setCollapsible(self.idxTree, False) self.splitMain.setCollapsible(self.idxMain, False) self.splitDocs.setCollapsible(self.idxEditor, False) self.splitDocs.setCollapsible(self.idxViewer, True) self.splitView.setCollapsible(self.idxViewDoc, False) self.splitView.setCollapsible(self.idxViewMeta, False) # Editor / Viewer Default State self.splitView.setVisible(False) self.docEditor.closeSearch() # Initialise the Project Tree self.treeView.itemSelectionChanged.connect(self._treeSingleClick) self.treeView.itemDoubleClicked.connect(self._treeDoubleClick) self.rebuildTree() # Set Main Window Elements self.setMenuBar(self.mainMenu) self.setCentralWidget(self.splitMain) self.setStatusBar(self.statusBar) # Finalise Initialisation # ======================= # Set Up Auto-Save Project Timer self.asProjTimer = QTimer() self.asProjTimer.timeout.connect(self._autoSaveProject) # Set Up Auto-Save Document Timer self.asDocTimer = QTimer() self.asDocTimer.timeout.connect(self._autoSaveDocument) # Shortcuts and Actions self._connectMenuActions() keyReturn = QShortcut(self.treeView) keyReturn.setKey(QKeySequence(Qt.Key_Return)) keyReturn.activated.connect(self._treeKeyPressReturn) keyEscape = QShortcut(self) keyEscape.setKey(QKeySequence(Qt.Key_Escape)) keyEscape.activated.connect(self._keyPressEscape) # Forward Functions self.setStatus = self.statusBar.setStatus self.setProjectStatus = self.statusBar.setProjectStatus # Force a show of the GUI self.show() # Check that config loaded fine self.reportConfErr() # Initialise Main GUI self.initMain() self.asProjTimer.start() self.asDocTimer.start() self.statusBar.clearStatus() # Handle Windows Mode self.showNormal() if self.mainConf.isFullScreen: self.toggleFullScreenMode() logger.debug("GUI initialisation complete") # Check if a project path was provided at command line, and if # not, open the project manager instead. if self.mainConf.cmdOpen is not None: logger.debug("Opening project from additional command line option") self.openProject(self.mainConf.cmdOpen) else: if self.mainConf.showGUI: self.showProjectLoadDialog() # Show the latest release notes, if they haven't been shown before if hexToInt(self.mainConf.lastNotes) < hexToInt(nw.__hexversion__): if self.mainConf.showGUI: self.showAboutNWDialog(showNotes=True) self.mainConf.lastNotes = nw.__hexversion__ logger.debug("novelWriter is ready ...") self.setStatus("novelWriter is ready ...") return def clearGUI(self): """Wrapper function to clear all sub-elements of the main GUI. """ # Project Area self.treeView.clearTree() self.treeMeta.clearDetails() # Work Area self.docEditor.clearEditor() self.docEditor.setDictionaries() self.closeDocViewer() self.projMeta.clearDetails() # General self.statusBar.clearStatus() self._updateWindowTitle() return True def initMain(self): """Initialise elements that depend on user settings. """ self.asProjTimer.setInterval(int(self.mainConf.autoSaveProj*1000)) self.asDocTimer.setInterval(int(self.mainConf.autoSaveDoc*1000)) return True ## # Project Actions ## def newProject(self, projData=None): """Create new project via the new project wizard. """ if self.hasProject: if not self.closeProject(): self.makeAlert( "Cannot create new project when another project is open.", nwAlert.ERROR ) return False if projData is None: projData = self.showNewProjectDialog() if projData is None: return False projPath = projData.get("projPath", None) if projPath is None or projData is None: logger.error("No projData or projPath set") return False if os.path.isfile(os.path.join(projPath, self.theProject.projFile)): self.makeAlert( "A project already exists in that location. Please choose another folder.", nwAlert.ERROR ) return False logger.info("Creating new project") if self.theProject.newProject(projData): self.rebuildTree() self.saveProject() self.hasProject = True self.docEditor.setDictionaries() self.rebuildIndex(beQuiet=True) self.statusBar.setRefTime(self.theProject.projOpened) self.statusBar.setProjectStatus(True) self.statusBar.setDocumentStatus(None) self.statusBar.setStatus("New project created ...") self._updateWindowTitle(self.theProject.projName) else: self.theProject.clearProject() return False return True def closeProject(self, isYes=False): """Closes the project if one is open. isYes is passed on from the close application event so the user doesn't get prompted twice to confirm. """ if not self.hasProject: # There is no project loaded, everything OK return True if not isYes: msgYes = self.askQuestion( "Close Project", "Close the current project?<br>Changes are saved automatically." ) if not msgYes: return False if self.docEditor.docChanged: self.saveDocument() if self.theProject.projAltered: saveOK = self.saveProject() doBackup = False if self.theProject.doBackup and self.mainConf.backupOnClose: doBackup = True if self.mainConf.askBeforeBackup: msgYes = self.askQuestion( "Backup Project", "Backup the current project?" ) if not msgYes: doBackup = False if doBackup: self.theProject.zipIt(False) else: saveOK = True if saveOK: self.closeDocument() self.docViewer.clearNavHistory() self.projView.closeOutline() self.theProject.closeProject() self.theIndex.clearIndex() self.clearGUI() self.hasProject = False self.tabWidget.setCurrentWidget(self.splitDocs) return saveOK def openProject(self, projFile): """Open a project from a projFile path. """ if projFile is None: return False # Make sure any open project is cleared out first before we load # another one if not self.closeProject(): return False # Switch main tab to editor view self.tabWidget.setCurrentWidget(self.splitDocs) # Try to open the project if not self.theProject.openProject(projFile): # The project open failed. if self.theProject.lockedBy is None: # The project is not locked, so failed for some other # reason handled by the project class. return False try: lockDetails = ( "<br><br>The project was locked by the computer " "'%s' (%s %s), last active on %s" ) % ( self.theProject.lockedBy[0], self.theProject.lockedBy[1], self.theProject.lockedBy[2], datetime.fromtimestamp( int(self.theProject.lockedBy[3]) ).strftime("%x %X") ) except Exception: lockDetails = "" msgBox = QMessageBox() msgRes = msgBox.warning( self, "Project Locked", ( "The project is already open by another instance of novelWriter, and " "is therefore locked. Override lock and continue anyway?<br><br>" "Note: If the program or the computer previously crashed, the lock " "can safely be overridden. If, however, another instance of " "novelWriter has the project open, overriding the lock may corrupt " "the project, and is not recommended.%s" ) % lockDetails, QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) if msgRes == QMessageBox.Yes: if not self.theProject.openProject(projFile, overrideLock=True): return False else: return False # Project is loaded self.hasProject = True # Load the tag index self.theIndex.loadIndex() # Update GUI self._updateWindowTitle(self.theProject.projName) self.rebuildTree() self.docEditor.setDictionaries() self.docEditor.setSpellCheck(self.theProject.spellCheck) self.mainMenu.setAutoOutline(self.theProject.autoOutline) self.statusBar.setRefTime(self.theProject.projOpened) self.statusBar.setStats(self.theProject.currWCount, 0) # Restore previously open documents, if any if self.theProject.lastEdited is not None: self.openDocument(self.theProject.lastEdited, doScroll=True) if self.theProject.lastViewed is not None: self.viewDocument(self.theProject.lastViewed) # Check if we need to rebuild the index if self.theIndex.indexBroken: self.rebuildIndex() # Make sure the changed status is set to false on all that was # just opened qApp.processEvents() self.docEditor.setDocumentChanged(False) self.theProject.setProjectChanged(False) logger.debug("Project load complete") return True def saveProject(self, autoSave=False): """Save the current project. """ if not self.hasProject: logger.error("No project open") return False # If the project is new, it may not have a path, so we need one if self.theProject.projPath is None: projPath = self.selectProjectPath() self.theProject.setProjectPath(projPath) if self.theProject.projPath is None: return False self.treeView.saveTreeOrder() self.theProject.saveProject(autoSave=autoSave) self.theIndex.saveIndex() return True ## # Document Actions ## def closeDocument(self): """Close the document and clear the editor and title field. """ if not self.hasProject: logger.error("No project open") return False self.docEditor.saveCursorPosition() if self.docEditor.docChanged: self.saveDocument() self.docEditor.clearEditor() return True def openDocument(self, tHandle, tLine=None, changeFocus=True, doScroll=False): """Open a specific document, optionally at a given line. """ if not self.hasProject: logger.error("No project open") return False self.closeDocument() self.tabWidget.setCurrentWidget(self.splitDocs) if self.docEditor.loadText(tHandle, tLine): if changeFocus: self.docEditor.setFocus() self.theProject.setLastEdited(tHandle) self.treeView.setSelectedHandle(tHandle, doScroll=doScroll) else: return False return True def openNextDocument(self, tHandle, wrapAround=False): """Opens the next document in the project tree, following the document with the given handle. Stops when reaching the end. """ if not self.hasProject: logger.error("No project open") return False self.treeView.flushTreeOrder() nHandle = None # The next handle after tHandle fHandle = None # The first file handle we encounter foundIt = False # We've found tHandle, pick the next we see for tItem in self.theProject.projTree: if tItem is None: continue if tItem.itemType != nwItemType.FILE: continue if fHandle is None: fHandle = tItem.itemHandle if tItem.itemHandle == tHandle: foundIt = True elif foundIt: nHandle = tItem.itemHandle break if nHandle is not None: self.openDocument(nHandle, tLine=0, doScroll=True) return True elif wrapAround: self.openDocument(fHandle, tLine=0, doScroll=True) return False return False def saveDocument(self): """Save the current documents. """ if not self.hasProject: logger.error("No project open") return False self.docEditor.saveText() return True def viewDocument(self, tHandle=None, tAnchor=None): """Load a document for viewing in the view panel. """ if not self.hasProject: logger.error("No project open") return False if tHandle is None: logger.debug("Viewing document, but no handle provided") if self.docEditor.hasFocus(): logger.verbose("Trying editor document") tHandle = self.docEditor.theHandle if tHandle is not None: self.saveDocument() else: logger.verbose("Trying selected document") tHandle = self.treeView.getSelectedHandle() if tHandle is None: logger.verbose("Trying last viewed document") tHandle = self.theProject.lastViewed if tHandle is None: logger.verbose("No document to view, giving up") return False # Make sure main tab is in Editor view self.tabWidget.setCurrentWidget(self.splitDocs) logger.debug("Viewing document with handle %s" % tHandle) if self.docViewer.loadText(tHandle): if not self.splitView.isVisible(): bPos = self.splitMain.sizes() self.splitView.setVisible(True) vPos = [0, 0] vPos[0] = int(bPos[1]/2) vPos[1] = bPos[1] - vPos[0] self.splitDocs.setSizes(vPos) self.viewMeta.setVisible(self.mainConf.showRefPanel) self.docViewer.navigateTo(tAnchor) return True def importDocument(self): """Import the text contained in an out-of-project text file, and insert the text into the currently open document. """ if not self.hasProject: logger.error("No project open") return False lastPath = self.mainConf.lastPath extFilter = [ "Text files (*.txt)", "Markdown files (*.md)", "novelWriter files (*.nwd)", "All files (*.*)", ] dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.DontUseNativeDialog loadFile, _ = QFileDialog.getOpenFileName( self, "Import File", lastPath, options=dlgOpt, filter=";;".join(extFilter) ) if not loadFile: return False if loadFile.strip() == "": return False theText = None try: with open(loadFile, mode="rt", encoding="utf8") as inFile: theText = inFile.read() self.mainConf.setLastPath(loadFile) except Exception as e: self.makeAlert( ["Could not read file. The file must be an existing text file.", str(e)], nwAlert.ERROR ) return False if self.docEditor.theHandle is None: self.makeAlert( "Please open a document to import the text file into.", nwAlert.ERROR ) return False if not self.docEditor.isEmpty(): msgYes = self.askQuestion("Import Document", ( "Importing the file will overwrite the current content of the document. " "Do you want to proceed?" )) if not msgYes: return False self.docEditor.replaceText(theText) return True def mergeDocuments(self): """Merge multiple documents to one single new document. """ if not self.hasProject: logger.error("No project open") return False dlgMerge = GuiDocMerge(self, self.theProject) dlgMerge.exec_() return True def splitDocument(self): """Split a single document into multiple documents. """ if not self.hasProject: logger.error("No project open") return False dlgSplit = GuiDocSplit(self, self.theProject) dlgSplit.exec_() return True def passDocumentAction(self, theAction): """Pass on document action theAction to the document viewer if it has focus, otherwise pass it to the document editor. """ if self.docViewer.hasFocus(): self.docViewer.docAction(theAction) else: self.docEditor.docAction(theAction) return True ## # Tree Item Actions ## def openSelectedItem(self): """Open the selected documents. """ if not self.hasProject: logger.error("No project open") return False tHandle = self.treeView.getSelectedHandle() if tHandle is None: logger.warning("No item selected") return False logger.verbose("Opening item %s" % tHandle) nwItem = self.theProject.projTree[tHandle] if nwItem.itemType == nwItemType.FILE: logger.verbose("Requested item %s is a file" % tHandle) self.openDocument(tHandle, doScroll=False) else: logger.verbose("Requested item %s is not a file" % tHandle) return True def editItem(self, tHandle=None): """Open the edit item dialog. """ if not self.hasProject: logger.error("No project open") return False if tHandle is None: tHandle = self.treeView.getSelectedHandle() if tHandle is None: logger.warning("No item selected") return tItem = self.theProject.projTree[tHandle] if tItem is None: return if tItem.itemType not in nwLists.REG_TYPES: return logger.verbose("Requesting change to item %s" % tHandle) dlgProj = GuiItemEditor(self, self.theProject, tHandle) dlgProj.exec_() if dlgProj.result() == QDialog.Accepted: self.treeView.setTreeItemValues(tHandle) self.treeMeta.updateViewBox(tHandle) self.docEditor.updateDocInfo(tHandle) self.docViewer.updateDocInfo(tHandle) return def rebuildTree(self): """Rebuild the project tree. """ self._makeStatusIcons() self._makeImportIcons() self.treeView.clearTree() self.treeView.buildTree() return def rebuildIndex(self, beQuiet=False): """Rebuild the entire index. """ if not self.hasProject: logger.error("No project open") return False logger.debug("Rebuilding index ...") qApp.setOverrideCursor(QCursor(Qt.WaitCursor)) tStart = time() self.treeView.saveTreeOrder() self.theIndex.clearIndex() theDoc = NWDoc(self.theProject, self) for nDone, tItem in enumerate(self.theProject.projTree): if tItem is not None: self.setStatus("Indexing: '%s'" % tItem.itemName) else: self.setStatus("Indexing: Unknown item") if tItem is not None and tItem.itemType == nwItemType.FILE: logger.verbose("Scanning: %s" % tItem.itemName) theText = theDoc.openDocument(tItem.itemHandle, showStatus=False) # Build tag index self.theIndex.scanText(tItem.itemHandle, theText) # Get Word Counts cC, wC, pC = self.theIndex.getCounts(tItem.itemHandle) tItem.setCharCount(cC) tItem.setWordCount(wC) tItem.setParaCount(pC) self.treeView.propagateCount(tItem.itemHandle, wC) self.treeView.projectWordCount() tEnd = time() self.setStatus("Indexing completed in %.1f ms" % ((tEnd - tStart)*1000.0)) self.docEditor.updateTagHighLighting() qApp.restoreOverrideCursor() if not beQuiet: self.makeAlert("The project index has been successfully rebuilt.", nwAlert.INFO) return True def rebuildOutline(self): """Force a rebuild of the Outline view. """ if not self.hasProject: logger.error("No project open") return False logger.verbose("Forcing a rebuild of the Project Outline") self.tabWidget.setCurrentWidget(self.splitOutline) self.projView.refreshTree(overRide=True) return True ## # Main Dialogs ## def selectProjectPath(self): """Select where to save project. """ dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.ShowDirsOnly dlgOpt |= QFileDialog.DontUseNativeDialog projPath = QFileDialog.getExistingDirectory( self, "Save novelWriter Project", "", options=dlgOpt ) if projPath: return projPath return None def showProjectLoadDialog(self): """Opens the projects dialog for selecting either existing projects from a cache of recently opened projects, or provide a browse button for projects not yet cached. Selecting to create a new project is forwarded to the new project wizard. """ dlgProj = GuiProjectLoad(self) dlgProj.exec_() if dlgProj.result() == QDialog.Accepted: if dlgProj.openState == GuiProjectLoad.OPEN_STATE: self.openProject(dlgProj.openPath) elif dlgProj.openState == GuiProjectLoad.NEW_STATE: self.newProject() return True def showNewProjectDialog(self): """Open the wizard and assemble a project options dict. """ newProj = GuiProjectWizard(self) newProj.exec_() if newProj.result() == QDialog.Accepted: return self._assembleProjectWizardData(newProj) return None def showPreferencesDialog(self): """Open the preferences dialog. """ dlgConf = GuiPreferences(self, self.theProject) dlgConf.exec_() if dlgConf.result() == QDialog.Accepted: logger.debug("Applying new preferences") self.initMain() self.theTheme.updateTheme() self.saveDocument() self.docEditor.initEditor() self.docViewer.initViewer() self.treeView.initTree() self.projView.initOutline() self.projMeta.initDetails() return def showProjectSettingsDialog(self): """Open the project settings dialog. """ if not self.hasProject: logger.error("No project open") return dlgProj = GuiProjectSettings(self, self.theProject) dlgProj.exec_() if dlgProj.result() == QDialog.Accepted: logger.debug("Applying new project settings") self.docEditor.setDictionaries() self._updateWindowTitle(self.theProject.projName) return def showBuildProjectDialog(self): """Open the build project dialog. """ if not self.hasProject: logger.error("No project open") return dlgBuild = getGuiItem("GuiBuildNovel") if dlgBuild is None: dlgBuild = GuiBuildNovel(self, self.theProject) dlgBuild.setModal(False) dlgBuild.show() qApp.processEvents() dlgBuild.viewCachedDoc() return def showWritingStatsDialog(self): """Open the session log dialog. """ if not self.hasProject: logger.error("No project open") return dlgStats = getGuiItem("GuiWritingStats") if dlgStats is None: dlgStats = GuiWritingStats(self, self.theProject) dlgStats.setModal(False) dlgStats.show() qApp.processEvents() dlgStats.populateGUI() return def showAboutNWDialog(self, showNotes=False): """Show the about dialog for novelWriter. """ dlgAbout = GuiAbout(self) dlgAbout.setModal(True) dlgAbout.show() qApp.processEvents() dlgAbout.populateGUI() if showNotes: dlgAbout.showReleaseNotes() return def showAboutQtDialog(self): """Show the about dialog for Qt. """ msgBox = QMessageBox() msgBox.aboutQt(self, "About Qt") return def makeAlert(self, theMessage, theLevel=nwAlert.INFO): """Alert both the user and the logger at the same time. Message can be either a string or an array of strings. """ if isinstance(theMessage, list): popMsg = "<br>".join(theMessage) logMsg = theMessage else: popMsg = theMessage logMsg = [theMessage] # Write to Log if theLevel == nwAlert.INFO: for msgLine in logMsg: logger.info(msgLine) elif theLevel == nwAlert.WARN: for msgLine in logMsg: logger.warning(msgLine) elif theLevel == nwAlert.ERROR: for msgLine in logMsg: logger.error(msgLine) elif theLevel == nwAlert.BUG: for msgLine in logMsg: logger.error(msgLine) # Popup msgBox = QMessageBox() if theLevel == nwAlert.INFO: msgBox.information(self, "Information", popMsg) elif theLevel == nwAlert.WARN: msgBox.warning(self, "Warning", popMsg) elif theLevel == nwAlert.ERROR: msgBox.critical(self, "Error", popMsg) elif theLevel == nwAlert.BUG: popMsg += "<br>This is a bug!" msgBox.critical(self, "Internal Error", popMsg) return def askQuestion(self, theTitle, theQuestion): """Ask the user a Yes/No question. """ msgBox = QMessageBox() msgRes = msgBox.question(self, theTitle, theQuestion) return msgRes == QMessageBox.Yes def reportConfErr(self): """Checks if the Config module has any errors to report, and let the user know if this is the case. The Config module caches errors since it is initialised before the GUI itself. """ if self.mainConf.hasError: self.makeAlert(self.mainConf.getErrData(), nwAlert.ERROR) return True return False ## # Main Window Actions ## def closeMain(self): """Save everything, and close novelWriter. """ if self.hasProject: msgYes = self.askQuestion( "Exit", "Do you want to exit novelWriter?<br>Changes are saved automatically." ) if not msgYes: return False logger.info("Exiting novelWriter") if not self.isFocusMode: self.mainConf.setMainPanePos(self.splitMain.sizes()) self.mainConf.setDocPanePos(self.splitDocs.sizes()) self.mainConf.setOutlinePanePos(self.splitOutline.sizes()) if self.viewMeta.isVisible(): self.mainConf.setViewPanePos(self.splitView.sizes()) self.mainConf.setShowRefPanel(self.viewMeta.isVisible()) self.mainConf.setTreeColWidths(self.treeView.getColumnSizes()) if not self.mainConf.isFullScreen: self.mainConf.setWinSize(self.width(), self.height()) if self.hasProject: self.closeProject(True) self.mainConf.saveConfig() self.reportConfErr() self.mainMenu.closeHelp() qApp.quit() return True def setFocus(self, paneNo): """Switch focus to one of the three main GUI panes. """ if paneNo == 1: self.treeView.setFocus() elif paneNo == 2: self.docEditor.setFocus() elif paneNo == 3: self.docViewer.setFocus() return def closeDocEditor(self): """Close the document edit panel. This does not hide the editor. """ self.closeDocument() self.theProject.setLastEdited(None) return def closeDocViewer(self): """Close the document view panel. """ self.docViewer.clearViewer() self.theProject.setLastViewed(None) bPos = self.splitMain.sizes() self.splitView.setVisible(False) vPos = [bPos[1], 0] self.splitDocs.setSizes(vPos) return not self.splitView.isVisible() def toggleFocusMode(self): """Main GUI Focus Mode hides tree, view pane and optionally also statusbar and menu. """ if self.docEditor.theHandle is None: logger.error("No document open, so not activating Focus Mode") self.mainMenu.aFocusMode.setChecked(self.isFocusMode) return False self.isFocusMode = not self.isFocusMode self.mainMenu.aFocusMode.setChecked(self.isFocusMode) if self.isFocusMode: logger.debug("Activating Focus Mode") self.tabWidget.setCurrentWidget(self.splitDocs) else: logger.debug("Deactivating Focus Mode") isVisible = not self.isFocusMode self.treePane.setVisible(isVisible) self.statusBar.setVisible(isVisible) self.mainMenu.setVisible(isVisible) self.tabWidget.tabBar().setVisible(isVisible) hideDocFooter = self.isFocusMode and self.mainConf.hideFocusFooter self.docEditor.docFooter.setVisible(not hideDocFooter) if self.splitView.isVisible(): self.splitView.setVisible(False) elif self.docViewer.theHandle is not None: self.splitView.setVisible(True) return True def toggleFullScreenMode(self): """Main GUI full screen mode. The mode is tracked by the flag in config. This only tracks whether the window has been maximised using the internal commands, and may not be correct if the user uses the system window manager. Currently, Qt doesn't have access to the exact state of the window. """ self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) winState = self.windowState() & Qt.WindowFullScreen == Qt.WindowFullScreen if winState: logger.debug("Activated full screen mode") else: logger.debug("Deactivated full screen mode") self.mainConf.isFullScreen = winState return ## # Internal Functions ## def _connectMenuActions(self): """Connect to the main window all menu actions that need to be available also when the main menu is hidden. """ # Project self.addAction(self.mainMenu.aSaveProject) self.addAction(self.mainMenu.aExitNW) # Document self.addAction(self.mainMenu.aSaveDoc) self.addAction(self.mainMenu.aFileDetails) # Edit self.addAction(self.mainMenu.aEditUndo) self.addAction(self.mainMenu.aEditRedo) self.addAction(self.mainMenu.aEditCut) self.addAction(self.mainMenu.aEditCopy) self.addAction(self.mainMenu.aEditPaste) self.addAction(self.mainMenu.aSelectAll) self.addAction(self.mainMenu.aSelectPar) # View self.addAction(self.mainMenu.aFocusMode) self.addAction(self.mainMenu.aFullScreen) # Insert self.addAction(self.mainMenu.aInsENDash) self.addAction(self.mainMenu.aInsEMDash) self.addAction(self.mainMenu.aInsEllipsis) self.addAction(self.mainMenu.aInsQuoteLS) self.addAction(self.mainMenu.aInsQuoteRS) self.addAction(self.mainMenu.aInsQuoteLD) self.addAction(self.mainMenu.aInsQuoteRD) self.addAction(self.mainMenu.aInsMSApos) self.addAction(self.mainMenu.aInsHardBreak) self.addAction(self.mainMenu.aInsNBSpace) self.addAction(self.mainMenu.aInsThinSpace) self.addAction(self.mainMenu.aInsThinNBSpace) for mAction, _ in self.mainMenu.mInsKWItems.values(): self.addAction(mAction) # Format self.addAction(self.mainMenu.aFmtEmph) self.addAction(self.mainMenu.aFmtStrong) self.addAction(self.mainMenu.aFmtStrike) self.addAction(self.mainMenu.aFmtDQuote) self.addAction(self.mainMenu.aFmtSQuote) self.addAction(self.mainMenu.aFmtHead1) self.addAction(self.mainMenu.aFmtHead2) self.addAction(self.mainMenu.aFmtHead3) self.addAction(self.mainMenu.aFmtHead4) self.addAction(self.mainMenu.aFmtComment) self.addAction(self.mainMenu.aFmtNoFormat) # Tools self.addAction(self.mainMenu.aSpellCheck) self.addAction(self.mainMenu.aReRunSpell) self.addAction(self.mainMenu.aPreferences) # Help if self.mainConf.hasHelp and self.mainConf.hasAssistant: self.addAction(self.mainMenu.aHelpLoc) self.addAction(self.mainMenu.aHelpWeb) return True def _updateWindowTitle(self, projName=None): """Set the window title and add the project's working title. """ winTitle = self.mainConf.appName if projName is not None: winTitle += " - %s" % projName self.setWindowTitle(winTitle) return True def _autoSaveProject(self): """Triggered by the auto-save project timer to save the project. """ doSave = self.hasProject doSave &= self.theProject.projChanged doSave &= self.theProject.projPath is not None if doSave: logger.debug("Autosaving project") self.saveProject(autoSave=True) return def _autoSaveDocument(self): """Triggered by the auto-save document timer to save the document. """ if self.hasProject and self.docEditor.docChanged: logger.debug("Autosaving document") self.saveDocument() return def _makeStatusIcons(self): """Generate all the item status icons based on project settings. """ self.statusIcons = {} iPx = self.mainConf.pxInt(32) for sLabel, sCol, _ in self.theProject.statusItems: theIcon = QPixmap(iPx, iPx) theIcon.fill(QColor(*sCol)) self.statusIcons[sLabel] = QIcon(theIcon) return def _makeImportIcons(self): """Generate all the item importance icons based on project settings. """ self.importIcons = {} iPx = self.mainConf.pxInt(32) for sLabel, sCol, _ in self.theProject.importItems: theIcon = QPixmap(iPx, iPx) theIcon.fill(QColor(*sCol)) self.importIcons[sLabel] = QIcon(theIcon) return def _assembleProjectWizardData(self, newProj): """Extract the user choices from the New Project Wizard and store them in a dictionary. """ projData = { "projName": newProj.field("projName"), "projTitle": newProj.field("projTitle"), "projAuthors": newProj.field("projAuthors"), "projPath": newProj.field("projPath"), "popSample": newProj.field("popSample"), "popMinimal": newProj.field("popMinimal"), "popCustom": newProj.field("popCustom"), "addRoots": [], "numChapters": 0, "numScenes": 0, "chFolders": False, } if newProj.field("popCustom"): addRoots = [] if newProj.field("addPlot"): addRoots.append(nwItemClass.PLOT) if newProj.field("addChar"): addRoots.append(nwItemClass.CHARACTER) if newProj.field("addWorld"): addRoots.append(nwItemClass.WORLD) if newProj.field("addTime"): addRoots.append(nwItemClass.TIMELINE) if newProj.field("addObject"): addRoots.append(nwItemClass.OBJECT) if newProj.field("addEntity"): addRoots.append(nwItemClass.ENTITY) projData["addRoots"] = addRoots projData["numChapters"] = newProj.field("numChapters") projData["numScenes"] = newProj.field("numScenes") projData["chFolders"] = newProj.field("chFolders") return projData ## # Events ## def closeEvent(self, theEvent): """Capture the closing event of the GUI and call the close function to handle all the close process steps. """ if self.closeMain(): theEvent.accept() else: theEvent.ignore() return ## # Signal Handlers ## def _treeSingleClick(self): """Single click on a project tree item just updates the details panel below the tree. """ sHandle = self.treeView.getSelectedHandle() if sHandle is not None: self.treeMeta.updateViewBox(sHandle) return def _treeDoubleClick(self, tItem, colNo): """The user double-clicked an item in the tree. If it is a file, we open it. Otherwise, we do nothing. """ tHandle = tItem.data(self.treeView.C_NAME, Qt.UserRole) logger.verbose("User double clicked tree item with handle %s" % tHandle) nwItem = self.theProject.projTree[tHandle] if nwItem is not None: if nwItem.itemType == nwItemType.FILE: logger.verbose("Requested item %s is a file" % tHandle) self.openDocument(tHandle, changeFocus=False, doScroll=False) else: logger.verbose("Requested item %s is a folder" % tHandle) return def _treeKeyPressReturn(self): """The user pressed return on an item in the tree. If it is a file, we open it. Otherwise, we do nothing. Pressing return does not change focus to the editor as double click does. """ tHandle = self.treeView.getSelectedHandle() logger.verbose("User pressed return on tree item with handle %s" % tHandle) nwItem = self.theProject.projTree[tHandle] if nwItem is not None: if nwItem.itemType == nwItemType.FILE: logger.verbose("Requested item %s is a file" % tHandle) self.openDocument(tHandle, changeFocus=False, doScroll=False) else: logger.verbose("Requested item %s is a folder" % tHandle) return def _keyPressEscape(self): """When the escape key is pressed somewhere in the main window, do the following, in order: """ if self.docEditor.docSearch.isVisible(): self.docEditor.closeSearch() elif self.isFocusMode: self.toggleFocusMode() return def _mainTabChanged(self, tabIndex): """Activated when the main window tab is changed. """ if tabIndex == self.idxTabEdit: logger.verbose("Editor tab activated") elif tabIndex == self.idxTabProj: logger.verbose("Project outline tab activated") if self.hasProject: self.projView.refreshTree() return
class MainWindow(QMainWindow): """ Main window """ def __init__(self): """ constructor """ super(MainWindow, self).__init__() self.setWindowTitle('Snort Log Server') self.setWindowIcon(QIcon('app.png')) self.setContentsMargins(0, 7, 0, 0) self.setMinimumSize(1000, 600) self.showMaximized() #self.setGeometry(100, 50, 1200, 600) def closeEvent(self, event): """ customize how closing the window is handled """ event.ignore() self.hide() self.__sys_tray_icon.show() def style(self): """ style the main window """ f = QFont('Lucida Console, Courier, monospace') #f.setBold(True) p = QPalette() p.setColor(QPalette.Window, QColor('#fff')) p.setColor(QPalette.WindowText, QColor('black')) #p.setColor(QPalette.) self.setPalette(p) self.setFont(f) def set_up_ui(self): self.create_menu_bar() self.create_center_widget() self.create_status_bar() self.create_system_tray_icon() self.style() def create_system_tray_icon(self): """ Create and an icon to the system tray """ self.__sys_tray_icon = SystemTrayIcon() self.__sys_tray_icon.setVisible(False) self.__sys_tray_icon.show_app.connect(self.show) self.__sys_tray_icon.close_app.connect(self.exit_app) def create_menu_bar(self): """ set up the menu bar """ # menus self.__app_menu = QMenu('App') # actions self.__clr_logs = QAction('Clear Logs') self.__quit = QAction('Quit') self.__hide = QAction('Hide') self.__quit.triggered.connect(self.exit_app) self.__hide.triggered.connect(self.set_visible) self.__app_menu.addActions([self.__clr_logs, self.__hide, self.__quit]) self.menuBar().addMenu(self.__app_menu) def create_center_widget(self): """ initialize and set a widget at the center of the app """ self.__splitter = QSplitter(Qt.Horizontal) self.__logs = Logs() self.__logs.set_up_widget() self.__hosts = Hosts() self.__hosts.set_up_widget() self.__logs.new_alert.connect(self.__hosts.new_alert) self.__gb = QGroupBox(" Snort Log Messages ") self.__bly = QVBoxLayout(self.__gb) self.__bly.addWidget(self.__logs) self.__splitter.addWidget(self.__gb) self.__splitter.addWidget(self.__hosts) self.__splitter.setSizes([1000, 500]) self.__splitter.setContentsMargins(15, 25, 15, 30) self.setCentralWidget(self.__splitter) self.__hosts.view_notif.connect(self.show) self.__hosts.next_notif.connect(self.__hosts.show_next_notif) #self.__hosts.ignore_notif.connect() def create_status_bar(self): """ create the status bar """ self.__status_bar = StatusBar() self.__status_bar.set_up_statusbar() self.setStatusBar(self.__status_bar) self.__logs.another_msg.connect(self.__status_bar.incr_all_pkts) self.__logs.another_anom_msg.connect( self.__status_bar.incr_no_of_attacks) @pyqtSlot() def exit_app(self): """ close application """ butt = QMessageBox.question(self, 'Log Server',\ 'Are you sure you want to shutdown this Log Server?') if butt == QMessageBox.No: pass else: self.__logs.stop_server.emit() self.__sys_tray_icon.setVisible(False) #time.sleep(3) sys.exit() @pyqtSlot() def set_visible(self): """ hide main window, show tray icon """ self.hide() self.__sys_tray_icon.setVisible(True)
def __init__(self, *args, **kwargs): super(PricePositionWin, self).__init__(*args, **kwargs) """ UI部分 """ layout = QVBoxLayout() layout.setContentsMargins(QMargins(0, 0, 0, 0)) # 操作头 title_layout = QHBoxLayout() title_layout.setAlignment(Qt.AlignLeft) title_layout.addWidget(QLabel("交易所:", self)) self.exchange_combobox = QComboBox(self) title_layout.addWidget(self.exchange_combobox) title_layout.addWidget(QLabel("期货品种:", self)) self.variety_combobox = QComboBox(self) self.variety_combobox.setMinimumWidth(100) title_layout.addWidget(self.variety_combobox) title_layout.addWidget(QLabel("合约:", self)) self.contract_combobox = QComboBox(self) title_layout.addWidget(self.contract_combobox) title_layout.addWidget(QLabel("起始日期:")) self.start_date = QDateEdit(self) self.start_date.setDisplayFormat('yyyy-MM-dd') self.start_date.setCalendarPopup(True) title_layout.addWidget(self.start_date) title_layout.addWidget(QLabel("终止日期:")) self.end_date = QDateEdit(self) self.end_date.setDisplayFormat('yyyy-MM-dd') self.end_date.setCalendarPopup(True) title_layout.addWidget(self.end_date) self.analysis_button = QPushButton("开始分析", self) self.analysis_button.setEnabled(False) title_layout.addWidget(self.analysis_button) self.option_widget = TitleOptionWidget(self) self.option_widget.setLayout(title_layout) layout.addWidget(self.option_widget) # 图形表格拖动区 splitter = QSplitter(Qt.Vertical, self) splitter.setContentsMargins(QMargins(10, 5, 10, 5)) # 请求数据遮罩层(需放在splitter之后才能显示,不然估计是被splitter覆盖) self.loading_cover = LoadingCover() self.loading_cover.setParent(self) self.loading_cover.resize(self.parent().width(), self.parent().height()) # 图形区 self.loading_cover.show(text='正在加载资源') self.chart_container = ChartContainWidget(ChartSourceChannel(), 'file:/templates/price_position.html', self) self.chart_container.page().loadFinished.connect(self.page_load_finished) splitter.addWidget(self.chart_container) # 表格区 self.table_widget = QWidget(self) table_layout = QVBoxLayout() table_layout.setContentsMargins(QMargins(0, 0, 0, 0)) opt_widget = QWidget(self.table_widget) opt_lt = QHBoxLayout(opt_widget) opt_lt.setContentsMargins(0,0,0,0) opt_widget.setLayout(opt_lt) # 日期选择,全品种净持率查看 self.all_date_edit = QDateEdit(self) self.all_date_edit.setCalendarPopup(True) self.all_date_edit.setDisplayFormat('yyyy-MM-dd') self.all_date_edit.setDate(QDate.currentDate()) opt_lt.addWidget(self.all_date_edit) self.all_query_button = QPushButton('查询全净持率', self) opt_lt.addWidget(self.all_query_button) # 增加净持率 self.add_net_rate = QPushButton('增加列', self) opt_lt.addWidget(self.add_net_rate) opt_lt.addStretch() # 导出数据按钮 self.export_button = QPushButton('导出EXCEL', self) self.export_button.setEnabled(False) opt_lt.addWidget(self.export_button) opt_widget.setFixedHeight(30) table_layout.addWidget(opt_widget) self.data_table = QTableWidget(self) self.data_table.verticalHeader().hide() self.data_table.setFocusPolicy(Qt.NoFocus) self.data_table.setColumnCount(7) self.data_table.setHorizontalHeaderLabels(['日期', '收盘价', '总持仓', '多头', '空头', '净持仓', '净持仓率%']) self.data_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.data_table.setSelectionBehavior(QAbstractItemView.SelectRows) self.data_table.setSelectionMode(QAbstractItemView.SingleSelection) table_layout.addWidget(self.data_table) # 全品种净持仓率显示表 self.all_table = QTableWidget(self) self.all_table.hide() table_layout.addWidget(self.all_table) self.table_widget.setLayout(table_layout) splitter.addWidget(self.table_widget) splitter.setSizes([self.parent().height() * 0.6, self.parent().height() * 0.4]) layout.addWidget(splitter) # 设置表行高,各行颜色 self.data_table.verticalHeader().setDefaultSectionSize(18) # 设置行高(与下行代码同时才生效) self.data_table.verticalHeader().setMinimumSectionSize(18) self.data_table.setAlternatingRowColors(True) self.data_table.setObjectName('dataTable') self.setStyleSheet( "#dataTable{selection-color:rgb(80,100,200);selection-background-color:rgb(220,220,220);" "alternate-background-color:rgb(230,254,238);gridline-color:rgb(60,60,60)}" ) # 设置表头,表滚动条样式 self.data_table.horizontalHeader().setStyleSheet(HORIZONTAL_HEADER_STYLE) self.data_table.horizontalScrollBar().setStyleSheet(HORIZONTAL_SCROLL_STYLE) self.data_table.verticalScrollBar().setStyleSheet(VERTICAL_SCROLL_STYLE) self.setLayout(layout) """ 业务逻辑部分 """ self.query_type = 'contract' # 当前查询的数据类型 # 网管器 self.network_manager = getattr(qApp, 'network') # 关联交易所变化信号 self.exchange_combobox.currentTextChanged.connect(self.get_variety_with_exchange) # 关联品种变化信号 self.variety_combobox.currentTextChanged.connect(self.get_contract_with_variety) # 关联合约变化的信号 self.contract_combobox.currentTextChanged.connect(self.get_min_max_date_with_contract) # 添加交易所 for exchange_item in EXCHANGES: self.exchange_combobox.addItem(exchange_item['name'], exchange_item['id']) # 关联开始计算按钮信号 self.analysis_button.clicked.connect(self.get_analysis_data) # 关联导出数据信息 self.export_button.clicked.connect(self.export_table_to_excel) # 查询全品种净持仓率的信号 self.all_query_button.clicked.connect(self.to_query_all_position) # 增加净持率列的信号 self.add_net_rate.setEnabled(False) self.add_net_rate.clicked.connect(self.to_add_new_column_rate) # 点击表头排序 self.all_table.horizontalHeader().sectionClicked.connect(self.all_table_horizontal_clicked) self.current_table = 'single_contract'
class GuiMain(QMainWindow): def __init__(self): QMainWindow.__init__(self) logger.info("Starting %s" % nw.__package__) logger.debug("Initialising GUI ...") self.mainConf = nw.CONFIG self.theTheme = GuiTheme(self) self.theProject = NWProject(self) self.theIndex = NWIndex(self.theProject, self) self.hasProject = False self.isZenMode = False logger.info("OS: %s" % ( self.mainConf.osType) ) logger.info("Qt5 Version: %s (%d)" % ( self.mainConf.verQtString, self.mainConf.verQtValue) ) logger.info("PyQt5 Version: %s (%d)" % ( self.mainConf.verPyQtString, self.mainConf.verPyQtValue) ) logger.info("Python Version: %s (0x%x)" % ( self.mainConf.verPyString, self.mainConf.verPyHexVal) ) self.resize(*self.mainConf.winGeometry) self._setWindowTitle() self.setWindowIcon(QIcon(path.join(self.mainConf.appIcon))) # Main GUI Elements self.statusBar = GuiMainStatus(self) self.noticeBar = GuiNoticeBar(self) self.docEditor = GuiDocEditor(self, self.theProject) self.docViewer = GuiDocViewer(self, self.theProject) self.viewMeta = GuiDocViewDetails(self, self.theProject) self.searchBar = GuiSearchBar(self) self.treeMeta = GuiDocDetails(self, self.theProject) self.treeView = GuiDocTree(self, self.theProject) self.mainMenu = GuiMainMenu(self, self.theProject) # Minor Gui Elements self.statusIcons = [] self.importIcons = [] # Assemble Main Window self.treePane = QFrame() self.treeBox = QVBoxLayout() self.treeBox.setContentsMargins(0,0,0,0) self.treeBox.addWidget(self.treeView) self.treeBox.addWidget(self.treeMeta) self.treePane.setLayout(self.treeBox) self.editPane = QFrame() self.docEdit = QVBoxLayout() self.docEdit.setContentsMargins(0,0,0,0) self.docEdit.addWidget(self.searchBar) self.docEdit.addWidget(self.noticeBar) self.docEdit.addWidget(self.docEditor) self.editPane.setLayout(self.docEdit) self.viewPane = QFrame() self.docView = QVBoxLayout() self.docView.setContentsMargins(0,0,0,0) self.docView.addWidget(self.docViewer) self.docView.addWidget(self.viewMeta) self.docView.setStretch(0, 1) self.viewPane.setLayout(self.docView) self.splitView = QSplitter(Qt.Horizontal) self.splitView.setOpaqueResize(False) self.splitView.addWidget(self.editPane) self.splitView.addWidget(self.viewPane) self.splitMain = QSplitter(Qt.Horizontal) self.splitMain.setContentsMargins(4,4,4,4) self.splitMain.setOpaqueResize(False) self.splitMain.addWidget(self.treePane) self.splitMain.addWidget(self.splitView) self.splitMain.setSizes(self.mainConf.mainPanePos) self.setCentralWidget(self.splitMain) self.idxTree = self.splitMain.indexOf(self.treePane) self.idxMain = self.splitMain.indexOf(self.splitView) self.idxEditor = self.splitView.indexOf(self.editPane) self.idxViewer = self.splitView.indexOf(self.viewPane) self.splitMain.setCollapsible(self.idxTree, False) self.splitMain.setCollapsible(self.idxMain, False) self.splitView.setCollapsible(self.idxEditor, False) self.splitView.setCollapsible(self.idxViewer, True) self.viewPane.setVisible(False) self.searchBar.setVisible(False) # Build The Tree View self.treeView.itemSelectionChanged.connect(self._treeSingleClick) self.treeView.itemDoubleClicked.connect(self._treeDoubleClick) self.rebuildTree() # Set Main Window Elements self.setMenuBar(self.mainMenu) self.setStatusBar(self.statusBar) self.statusBar.setStatus("Ready") # Set Up Autosaving Project Timer self.asProjTimer = QTimer() self.asProjTimer.timeout.connect(self._autoSaveProject) # Set Up Autosaving Document Timer self.asDocTimer = QTimer() self.asDocTimer.timeout.connect(self._autoSaveDocument) # Shortcuts and Actions self._connectMenuActions() keyReturn = QShortcut(self.treeView) keyReturn.setKey(QKeySequence(Qt.Key_Return)) keyReturn.activated.connect(self._treeKeyPressReturn) keyEscape = QShortcut(self) keyEscape.setKey(QKeySequence(Qt.Key_Escape)) keyEscape.activated.connect(self._keyPressEscape) # Forward Functions self.setStatus = self.statusBar.setStatus self.setProjectStatus = self.statusBar.setProjectStatus if self.mainConf.showGUI: self.show() self.initMain() self.asProjTimer.start() self.asDocTimer.start() self.statusBar.clearStatus() self.showNormal() if self.mainConf.isFullScreen: self.toggleFullScreenMode() logger.debug("GUI initialisation complete") if self.mainConf.cmdOpen is not None: logger.debug("Opening project from additional command line option") self.openProject(self.mainConf.cmdOpen) return def clearGUI(self): """Wrapper function to clear all sub-elements of the main GUI. """ self.treeView.clearTree() self.docEditor.clearEditor() self.closeDocViewer() self.statusBar.clearStatus() return True def initMain(self): self.asProjTimer.setInterval(int(self.mainConf.autoSaveProj*1000)) self.asDocTimer.setInterval(int(self.mainConf.autoSaveDoc*1000)) return True ## # Project Actions ## def newProject(self, projPath=None, forceNew=False): if self.hasProject: msgBox = QMessageBox() msgRes = msgBox.warning( self, "New Project", "Please close the current project<br>before making a new one." ) return False if projPath is None: projPath = self.newProjectDialog() if projPath is None: return False if path.isfile(path.join(projPath,self.theProject.projFile)) and not forceNew: msgBox = QMessageBox() msgRes = msgBox.critical( self, "New Project", "A project already exists in that location.<br>Please choose another folder." ) return False logger.info("Creating new project") self.theProject.newProject() self.theProject.setProjectPath(projPath) self.rebuildTree() self.saveProject() self.hasProject = True self.statusBar.setRefTime(self.theProject.projOpened) return True def closeProject(self, isYes=False): """Closes the project if one is open. isYes is passed on from the close application event so the user doesn't get prompted twice. """ if not self.hasProject: # There is no project loaded, everything OK return True if self.mainConf.showGUI and not isYes: msgBox = QMessageBox() msgRes = msgBox.question( self, "Close Project", "Save changes and close current project?" ) if msgRes != QMessageBox.Yes: return False if self.docEditor.docChanged: self.saveDocument() if self.theProject.projAltered: saveOK = self.saveProject() doBackup = False if self.theProject.doBackup and self.mainConf.backupOnClose: doBackup = True if self.mainConf.showGUI and self.mainConf.askBeforeBackup: msgBox = QMessageBox() msgRes = msgBox.question( self, "Backup Project", "Backup current project?" ) if msgRes != QMessageBox.Yes: doBackup = False if doBackup: self.backupProject() else: saveOK = True if saveOK: self.closeDocument() self.theProject.closeProject() self.theIndex.clearIndex() self.clearGUI() self.hasProject = False return saveOK def openProject(self, projFile=None): """Open a project. The parameter projFile is passed from the open recent projects menu, so it can be set. If not, we pop the dialog. """ if projFile is None: projFile = self.openProjectDialog() if projFile is None: return False # Make sure any open project is cleared out first before we load # another one if not self.closeProject(): return False # Try to open the project if not self.theProject.openProject(projFile): return False # project is loaded self.hasProject = True # Load the tag index self.theIndex.loadIndex() # Update GUI self._setWindowTitle(self.theProject.projName) self.rebuildTree() self.docEditor.setDictionaries() self.docEditor.setSpellCheck(self.theProject.spellCheck) self.statusBar.setRefTime(self.theProject.projOpened) self.mainMenu.updateMenu() # Restore previously open documents, if any if self.theProject.lastEdited is not None: self.openDocument(self.theProject.lastEdited) if self.theProject.lastViewed is not None: self.viewDocument(self.theProject.lastViewed) # Check if we need to rebuild the index if self.theIndex.indexBroken: self.rebuildIndex() return True def saveProject(self): """Save the current project. """ if not self.hasProject: return False # If the project is new, it may not have a path, so we need one if self.theProject.projPath is None: projPath = self.saveProjectDialog() self.theProject.setProjectPath(projPath) if self.theProject.projPath is None: return False self.treeView.saveTreeOrder() self.theProject.saveProject() self.theIndex.saveIndex() self.mainMenu.updateRecentProjects() return True def backupProject(self): theBackup = NWBackup(self, self.theProject) theBackup.zipIt() return True ## # Document Actions ## def closeDocument(self): if self.hasProject: if self.docEditor.docChanged: self.saveDocument() self.docEditor.clearEditor() return True def openDocument(self, tHandle): if self.hasProject: self.closeDocument() if self.docEditor.loadText(tHandle): self.docEditor.setFocus() self.theProject.setLastEdited(tHandle) else: return False return True def saveDocument(self): if self.hasProject: self.docEditor.saveText() return True def viewDocument(self, tHandle=None): if tHandle is None: tHandle = self.treeView.getSelectedHandle() if tHandle is None: logger.debug("No document selected, trying editor document") tHandle = self.docEditor.theHandle if tHandle is None: logger.debug("No document selected, trying last viewed") tHandle = self.theProject.lastViewed if tHandle is None: logger.debug("No document selected, giving up") return False if self.docViewer.loadText(tHandle) and not self.viewPane.isVisible(): bPos = self.splitMain.sizes() self.viewPane.setVisible(True) vPos = [0,0] vPos[0] = int(bPos[1]/2) vPos[1] = bPos[1]-vPos[0] self.splitView.setSizes(vPos) return True def importDocument(self): lastPath = self.mainConf.lastPath extFilter = [ "Text files (*.txt)", "Markdown files (*.md)", "All files (*.*)", ] dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.DontUseNativeDialog inPath = QFileDialog.getOpenFileName( self,"Import File",lastPath,options=dlgOpt,filter=";;".join(extFilter) ) if inPath: loadFile = inPath[0] else: return False if loadFile.strip() == "": return False theText = None try: with open(loadFile,mode="rt",encoding="utf8") as inFile: theText = inFile.read() self.mainConf.setLastPath(loadFile) except Exception as e: self.makeAlert( ["Could not read file. The file must be an existing text file.",str(e)], nwAlert.ERROR ) return False if self.docEditor.theHandle is None: self.makeAlert( ["Please open a document to import the text file into."], nwAlert.ERROR ) return False if not self.docEditor.isEmpty(): if self.mainConf.showGUI: msgBox = QMessageBox() msgRes = msgBox.question(self, "Import Document",( "Importing the file will overwrite the current content of the document. " "Do you want to proceed?" )) if msgRes != QMessageBox.Yes: return False else: return False self.docEditor.replaceText(theText) return True def mergeDocuments(self): """Merge multiple documents to one single new document. """ if self.mainConf.showGUI: dlgMerge = GuiDocMerge(self, self.theProject) dlgMerge.exec_() return True def splitDocument(self): """Split a single document into multiple documents. """ if self.mainConf.showGUI: dlgSplit = GuiDocSplit(self, self.theProject) dlgSplit.exec_() return True def passDocumentAction(self, theAction): """Pass on document action theAction to whatever document has the focus. If no document has focus, the action is discarded. """ if self.docEditor.hasFocus(): self.docEditor.docAction(theAction) elif self.docViewer.hasFocus(): self.docViewer.docAction(theAction) else: logger.debug("Document action requested, but no document has focus") return True ## # Tree Item Actions ## def openSelectedItem(self): tHandle = self.treeView.getSelectedHandle() if tHandle is None: logger.warning("No item selected") return False logger.verbose("Opening item %s" % tHandle) nwItem = self.theProject.getItem(tHandle) if nwItem.itemType == nwItemType.FILE: logger.verbose("Requested item %s is a file" % tHandle) self.openDocument(tHandle) else: logger.verbose("Requested item %s is not a file" % tHandle) return True def editItem(self): tHandle = self.treeView.getSelectedHandle() if tHandle is None: logger.warning("No item selected") return logger.verbose("Requesting change to item %s" % tHandle) if self.mainConf.showGUI: dlgProj = GuiItemEditor(self, self.theProject, tHandle) if dlgProj.exec_(): self.treeView.setTreeItemValues(tHandle) return def rebuildTree(self): self._makeStatusIcons() self._makeImportIcons() self.treeView.clearTree() self.treeView.buildTree() return def rebuildIndex(self): if not self.hasProject: return False logger.debug("Rebuilding indices ...") self.treeView.saveTreeOrder() self.theIndex.clearIndex() nItems = len(self.theProject.treeOrder) dlgProg = QProgressDialog("Scanning files ...", "Cancel", 0, nItems, self) dlgProg.setWindowModality(Qt.WindowModal) dlgProg.setMinimumDuration(0) dlgProg.setFixedWidth(480) dlgProg.setLabelText("Starting file scan ...") dlgProg.setValue(0) dlgProg.show() time.sleep(0.5) nDone = 0 for tHandle in self.theProject.treeOrder: tItem = self.theProject.getItem(tHandle) dlgProg.setValue(nDone) dlgProg.setLabelText("Scanning: %s" % tItem.itemName) logger.verbose("Scanning: %s" % tItem.itemName) if tItem is not None and tItem.itemType == nwItemType.FILE: theDoc = NWDoc(self.theProject, self) theText = theDoc.openDocument(tHandle, False) # Run Word Count cC, wC, pC = countWords(theText) tItem.setCharCount(cC) tItem.setWordCount(wC) tItem.setParaCount(pC) self.treeView.propagateCount(tHandle, wC) self.treeView.projectWordCount() # Build tag index self.theIndex.scanText(tHandle, theText) nDone += 1 if dlgProg.wasCanceled(): break dlgProg.setValue(nItems) return True ## # Main Dialogs ## def openProjectDialog(self): dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.DontUseNativeDialog projFile, _ = QFileDialog.getOpenFileName( self, "Open novelWriter Project", "", "novelWriter Project File (%s);;All Files (*)" % nwFiles.PROJ_FILE, options=dlgOpt ) if projFile: return projFile return None def saveProjectDialog(self): dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.ShowDirsOnly dlgOpt |= QFileDialog.DontUseNativeDialog projPath = QFileDialog.getExistingDirectory( self, "Save novelWriter Project", "", options=dlgOpt ) if projPath: return projPath return None def newProjectDialog(self): dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.ShowDirsOnly dlgOpt |= QFileDialog.DontUseNativeDialog projPath = QFileDialog.getExistingDirectory( self, "Select Location for New novelWriter Project", "", options=dlgOpt ) if projPath: return projPath return None def editConfigDialog(self): dlgConf = GuiConfigEditor(self, self.theProject) if dlgConf.exec_() == QDialog.Accepted: logger.debug("Applying new preferences") self.initMain() self.theTheme.updateTheme() self.saveDocument() self.docEditor.initEditor() self.docViewer.initViewer() return True def editProjectDialog(self): if self.hasProject: dlgProj = GuiProjectEditor(self, self.theProject) dlgProj.exec_() self._setWindowTitle(self.theProject.projName) return True def exportProjectDialog(self): if self.hasProject: dlgExport = GuiExport(self, self.theProject) dlgExport.exec_() return True def showTimeLineDialog(self): if self.hasProject: dlgTLine = GuiTimeLineView(self, self.theProject, self.theIndex) dlgTLine.exec_() return True def showSessionLogDialog(self): if self.hasProject: dlgTLine = GuiSessionLogView(self, self.theProject) dlgTLine.exec_() return True def makeAlert(self, theMessage, theLevel=nwAlert.INFO): """Alert both the user and the logger at the same time. Message can be either a string or an array of strings. Severity level is 0 = info, 1 = warning, and 2 = error. """ if isinstance(theMessage, list): popMsg = " ".join(theMessage) logMsg = theMessage else: popMsg = theMessage logMsg = [theMessage] msgBox = QMessageBox() if theLevel == nwAlert.INFO: for msgLine in logMsg: logger.info(msgLine) msgBox.information(self, "Information", popMsg) elif theLevel == nwAlert.WARN: for msgLine in logMsg: logger.warning(msgLine) msgBox.warning(self, "Warning", popMsg) elif theLevel == nwAlert.ERROR: for msgLine in logMsg: logger.error(msgLine) msgBox.critical(self, "Error", popMsg) elif theLevel == nwAlert.BUG: for msgLine in logMsg: logger.error(msgLine) popMsg += "<br>This is a bug!" msgBox.critical(self, "Internal Error", popMsg) return ## # Main Window Actions ## def closeMain(self): if self.mainConf.showGUI and self.hasProject: msgBox = QMessageBox() msgRes = msgBox.question( self, "Exit", "Do you want to save changes and exit?" ) if msgRes != QMessageBox.Yes: return False logger.info("Exiting %s" % nw.__package__) self.closeProject(True) self.mainConf.setTreeColWidths(self.treeView.getColumnSizes()) if not self.mainConf.isFullScreen: self.mainConf.setWinSize(self.width(), self.height()) if not self.isZenMode: self.mainConf.setMainPanePos(self.splitMain.sizes()) self.mainConf.setDocPanePos(self.splitView.sizes()) self.mainConf.saveConfig() qApp.quit() return True def setFocus(self, paneNo): if paneNo == 1: self.treeView.setFocus() elif paneNo == 2: self.docEditor.setFocus() elif paneNo == 3: self.docViewer.setFocus() return def closeDocEditor(self): self.closeDocument() self.theProject.setLastEdited(None) return def closeDocViewer(self): self.docViewer.clearViewer() self.theProject.setLastViewed(None) bPos = self.splitMain.sizes() self.viewPane.setVisible(False) vPos = [bPos[1],0] self.splitView.setSizes(vPos) return not self.viewPane.isVisible() def toggleZenMode(self): """Main GUI Zen Mode hides tree, view pane and optionally also statusbar and menu. """ if self.docEditor.theHandle is None: logger.error("No document open, so not activating Zen Mode") return False self.isZenMode = not self.isZenMode if self.isZenMode: logger.debug("Activating Zen mode") else: logger.debug("Deactivating Zen mode") isVisible = not self.isZenMode self.treePane.setVisible(isVisible) self.statusBar.setVisible(isVisible) self.mainMenu.setVisible(isVisible) if self.viewPane.isVisible(): self.viewPane.setVisible(False) elif self.docViewer.theHandle is not None: self.viewPane.setVisible(True) return True def toggleFullScreenMode(self): """Main GUI full screen mode. The mode is tracked by the flag in config. This only tracks whether the window has been maximised using the internal commands, and may not be correct if the user uses the system window manager. Currently, Qt doesn't have access to the exact state of the window. """ self.setWindowState(self.windowState() ^ Qt.WindowFullScreen) winState = self.windowState() & Qt.WindowFullScreen == Qt.WindowFullScreen if winState: logger.debug("Activated full screen mode") else: logger.debug("Deactivated full screen mode") self.mainConf.isFullScreen = winState return ## # Internal Functions ## def _connectMenuActions(self): """Connect to the main window all menu actions that need to be available also when the main menu is hidden. """ self.addAction(self.mainMenu.aSaveProject) self.addAction(self.mainMenu.aExitNW) self.addAction(self.mainMenu.aSaveDoc) self.addAction(self.mainMenu.aFileDetails) self.addAction(self.mainMenu.aZenMode) self.addAction(self.mainMenu.aFullScreen) self.addAction(self.mainMenu.aViewTimeLine) self.addAction(self.mainMenu.aEditUndo) self.addAction(self.mainMenu.aEditRedo) self.addAction(self.mainMenu.aEditCut) self.addAction(self.mainMenu.aEditCopy) self.addAction(self.mainMenu.aEditPaste) self.addAction(self.mainMenu.aSelectAll) self.addAction(self.mainMenu.aSelectPar) self.addAction(self.mainMenu.aFmtBold) self.addAction(self.mainMenu.aFmtItalic) self.addAction(self.mainMenu.aFmtULine) self.addAction(self.mainMenu.aFmtDQuote) self.addAction(self.mainMenu.aFmtSQuote) self.addAction(self.mainMenu.aFmtHead1) self.addAction(self.mainMenu.aFmtHead2) self.addAction(self.mainMenu.aFmtHead3) self.addAction(self.mainMenu.aFmtHead4) self.addAction(self.mainMenu.aFmtComment) self.addAction(self.mainMenu.aFmtNoFormat) self.addAction(self.mainMenu.aSpellCheck) self.addAction(self.mainMenu.aReRunSpell) self.addAction(self.mainMenu.aPreferences) self.addAction(self.mainMenu.aHelp) return True def _setWindowTitle(self, projName=None): winTitle = "%s" % nw.__package__ if projName is not None: winTitle += " - %s" % projName self.setWindowTitle(winTitle) return True def _autoSaveProject(self): if (self.hasProject and self.theProject.projChanged and self.theProject.projPath is not None): logger.debug("Autosaving project") self.saveProject() return def _autoSaveDocument(self): if self.hasProject and self.docEditor.docChanged: logger.debug("Autosaving document") self.saveDocument() return def _makeStatusIcons(self): self.statusIcons = {} for sLabel, sCol, _ in self.theProject.statusItems: theIcon = QPixmap(32,32) theIcon.fill(QColor(*sCol)) self.statusIcons[sLabel] = QIcon(theIcon) return def _makeImportIcons(self): self.importIcons = {} for sLabel, sCol, _ in self.theProject.importItems: theIcon = QPixmap(32,32) theIcon.fill(QColor(*sCol)) self.importIcons[sLabel] = QIcon(theIcon) return ## # Events ## def closeEvent(self, theEvent): if self.closeMain(): theEvent.accept() else: theEvent.ignore() return ## # Signal Handlers ## def _treeSingleClick(self): sHandle = self.treeView.getSelectedHandle() if sHandle is not None: self.treeMeta.buildViewBox(sHandle) return def _treeDoubleClick(self, tItem, colNo): tHandle = tItem.text(3) logger.verbose("User double clicked tree item with handle %s" % tHandle) nwItem = self.theProject.getItem(tHandle) if nwItem.itemType == nwItemType.FILE: logger.verbose("Requested item %s is a file" % tHandle) self.openDocument(tHandle) else: logger.verbose("Requested item %s is a folder" % tHandle) return def _treeKeyPressReturn(self): tHandle = self.treeView.getSelectedHandle() logger.verbose("User pressed return on tree item with handle %s" % tHandle) nwItem = self.theProject.getItem(tHandle) if nwItem.itemType == nwItemType.FILE: logger.verbose("Requested item %s is a file" % tHandle) self.openDocument(tHandle) else: logger.verbose("Requested item %s is a folder" % tHandle) return def _keyPressEscape(self): """When the escape key is pressed somewhere in the main window, do the following, in order. """ if self.searchBar.isVisible(): self.searchBar.setVisible(False) return elif self.isZenMode: self.toggleZenMode() return
def _setup_ui(self): self.setContentsMargins(0, 0, 0, 0) top_font = QFont() top_font.setBold(True) top_font.setPixelSize(19) # main wrapper main_wrapper = QVBoxLayout() main_wrapper.setContentsMargins(1, 1, 1, 1) # wrapwdgt wrap_wdgt = QWidget() self._top_class_name = QLabel(wrap_wdgt) self._top_class_name.setContentsMargins(10, 10, 10, 10) self._top_class_name.setAttribute(Qt.WA_TranslucentBackground, True) # keep this self._top_class_name.setFont(top_font) self._top_class_name.setStyleSheet('color: #ef5350;') wrap_wdgt.setMaximumHeight(self._top_class_name.height() + 20) main_wrapper.addWidget(wrap_wdgt) # left list left_wrap_wdgt = QWidget() left_v_box = QVBoxLayout(left_wrap_wdgt) left_v_box.setContentsMargins(0, 0, 0, 0) methods_label = QLabel('METHODS') font = methods_label.font() font.setBold(True) methods_label.setFont(font) methods_label.setContentsMargins(10, 0, 10, 2) methods_label.setAttribute(Qt.WA_TranslucentBackground, True) # keep this left_v_box.addWidget(methods_label) self._methods_list = DwarfListView() left_v_box.addWidget(self._methods_list) # center list center_wrap_wdgt = QWidget() center_v_box = QVBoxLayout(center_wrap_wdgt) center_v_box.setContentsMargins(0, 0, 0, 0) methods_label = QLabel('NATIVE FIELDS') methods_label.setFont(font) methods_label.setContentsMargins(10, 0, 10, 2) methods_label.setAttribute(Qt.WA_TranslucentBackground, True) # keep this center_v_box.addWidget(methods_label) self._native_fields_list = DwarfListView() self._native_fields_list.doubleClicked.connect( self._on_native_field_dblclicked) center_v_box.addWidget(self._native_fields_list) # right list right_wrap_wdgt = QWidget() right_v_box = QVBoxLayout(right_wrap_wdgt) right_v_box.setContentsMargins(0, 0, 0, 0) methods_label = QLabel('FIELDS') methods_label.setFont(font) methods_label.setContentsMargins(10, 0, 10, 2) methods_label.setAttribute(Qt.WA_TranslucentBackground, True) # keep this right_v_box.addWidget(methods_label) self._fields_list = DwarfListView() self._fields_list.doubleClicked.connect(self._on_field_dblclicked) right_v_box.addWidget(self._fields_list) # main splitter main_splitter = QSplitter(Qt.Horizontal) main_splitter.setContentsMargins(0, 0, 0, 0) main_splitter.addWidget(left_wrap_wdgt) main_splitter.addWidget(center_wrap_wdgt) main_splitter.addWidget(right_wrap_wdgt) main_splitter.setSizes([250, 100, 100]) main_wrapper.addWidget(main_splitter) main_wrapper.setSpacing(0) self.setLayout(main_wrapper)
class MainWindow(QMainWindow): def __init__(self): super().__init__() self.title = 'Convolutional Neural Network' self.left = 300 self.top = 300 self.width = 960 self.height = 560 self.first_run = True self.current_save = None self.running = False self.iteration_stats = [] self.settings = QSettings("Theo Styles", "Convolutional Neural Network") self.img_size = self.settings.value("img_size", 44) self.init_ui() def update_progress(self, amount=0, status="Running"): self.left_area.progressModule.progress.setText(status + " - " + str(amount) + "%") self.left_area.progressModule.progress.setValue(amount) def testing_finished(self, cls_pred): self.running = False self.main_area_reports.focus() for i, prob_array in enumerate(cls_pred): image = self.dataset.testing_images[i] pred_index = np.argmax(prob_array) prob = prob_array[pred_index] labelForDigit = self.dataset.labels[pred_index] for set in self.main_area_neural.sets: if set.name == labelForDigit: item = set.add_image(image) if prob <= 0.5: item.set_important() self.toolbar.enable_action(0, 0) def run_neural_net(self): self.left_area.progressModule.progress.setDefault() self.obj = RunNeuralNet(self.dataset, self.img_size, len(self.main_area_neural.sets)) # no parent! self.thread = QThread() # no parent! self.obj.moveToThread(self.thread) self.obj.one_iteration.connect(self.update_progress) self.obj.testing_finished.connect(self.testing_finished) self.thread.started.connect(self.obj.long_running) self.thread.start() self.toolbar.disable_action(0, 0) def add_toolbar_clicked(self): folder_name = QFileDialog.getExistingDirectory( self, "Select Directory With Testing Images") if folder_name: self.main_area_neural.top_widget.setVisible(True) self.main_area_neural.initial_image_grid_visible = True if self.main_area_neural.empty_label: self.main_area_neural.empty_label.setText( "Now create sets by selecting images and using the + button to the right of the images!" ) self.main_area_neural.initial_image_grid.populate_from_folder( folder_name, self.update_progress) def export_sets(self): sets = self.main_area_neural.sets if len(sets) == 0: ErrorDialog.dialog(self, "There are no sets to export") return folder_name = QFileDialog.getExistingDirectory( self, "Select a directory to export to") if folder_name: self.left_area.progressModule.progress.setDefault() self.obj = SaveLoad(sets=self.main_area_neural.sets, folder_name=folder_name) # no parent! self.thread = QThread() # no parent! self.obj.moveToThread(self.thread) self.obj.one_iteration.connect(self.update_progress) self.thread.started.connect(self.obj.export_sets) self.thread.start() def run_clicked(self): sets = self.main_area_neural.sets if len(sets) == 0: return ErrorDialog.dialog( self, "Please create at least one set before running the neural network" ) if self.running: return ErrorDialog.dialog(self, "The neural network is already running") itemNames = [] itemData = [] setCount = 0 total_incorrect = 0 self.running = True for set in sets: setCount += 1 itemCount = len(set.all_images) total_incorrect += set.incorrectly_classified_local set.incorrectly_classified_local = 0 for index in range(itemCount): item = set.all_images[index] if item == None: continue itemNames.append(set.name) itemData.append(item.imageData) set.clear() self.iteration_stats.append(total_incorrect) self.dataset.add_sets_to_training_data(setCount, itemNames, itemData) if self.first_run or self.main_area_neural.initial_image_grid_visible: all_image_count = self.main_area_neural.initial_image_grid.count() testing_images = [] for index in range(all_image_count): item = self.main_area_neural.initial_image_grid.item(index) if item == None: continue testing_images.append(item.imageData) self.main_area_neural.initial_image_grid.clear() self.main_area_neural.top_widget.setVisible(False) self.main_area_neural.initial_image_grid_visible = False self.dataset.set_testing_data(testing_images) else: self.dataset.new_testing_data() self.first_run = False self.run_neural_net() def finished_opening_sets(self): self.dataset.new_testing_data() self.main_area_reports.focus() def set_iteration_stats(self, iteration_stats): self.iteration_stats = iteration_stats def open_sets(self): fileName, filter = QFileDialog.getOpenFileName( self, 'Open sets save', PathHelpers.getPath("saves"), "Set Files (*.sets)") if fileName: self.current_save = fileName self.main_area_neural.clear_sets() self.left_area.progressModule.progress.setDefault() self.obj = SaveLoad(self.current_save) # no parent! self.thread = QThread() # no parent! self.obj.moveToThread(self.thread) self.obj.one_iteration.connect(self.update_progress) self.obj.create_set.connect(self.main_area_neural.create_new_set) self.obj.add_to_training_set.connect( self.main_area_neural.add_images_to_set) self.obj.add_to_testing_set.connect( self.dataset.add_to_testing_data) self.obj.set_iteration_stats.connect(self.set_iteration_stats) self.obj.set_classified_info.connect( self.main_area_neural.set_classified_for_set) self.obj.finished.connect(self.finished_opening_sets) self.thread.started.connect(self.obj.load_images) self.thread.start() def save_sets_as(self): self.current_save = None self.save_sets() def save_sets(self): if self.current_save: fileName = self.current_save else: fileName, filter = QFileDialog.getSaveFileName( self, 'Save sets', PathHelpers.getPath("saves"), "Set Files (*.sets)") if fileName: sets = self.main_area_neural.sets[:] if self.main_area_neural.trash_set: sets.append(self.main_area_neural.trash_set) self.current_save = fileName self.left_area.progressModule.progress.setDefault() self.obj = SaveLoad( self.current_save, sets, self.dataset.all_testing_images, iteration_stats=self.iteration_stats) # no parent! self.thread = QThread() # no parent! self.obj.moveToThread(self.thread) self.obj.one_iteration.connect(self.update_progress) self.thread.started.connect(self.obj.save_images) self.thread.start() def deleted_set(self, set_name): self.datapanel.delete_training_row(set_name) def added_to_set(self, set_name): self.datapanel.increment_training_table(set_name) def removed_from_set(self, set_name): self.datapanel.decrement_training_table(set_name) def set_testing_amount(self, amount): self.datapanel.set_testing_amount(amount) def switch_to_neural(self): self.left_area.menu.setCurrentRow(0) def switch_to_reports(self): self.left_area.menu.setCurrentRow(1) def switch_to_settings(self): self.left_area.menu.setCurrentRow(2) def menu_changed(self, index): sets = self.main_area_neural.sets[:] if self.main_area_neural.trash_set: sets.append(self.main_area_neural.trash_set) self.main_area_neural.setVisible(False) self.main_area_settings.setVisible(False) self.main_area_reports.setVisible(False) if index == 0: self.main_area_neural.setVisible(True) elif index == 1: self.main_area_reports.setVisible(True) self.main_area_reports.focus(sets, self.iteration_stats) elif index == 2: self.main_area_settings.setVisible(True) self.main_area_settings.changed() def view_all_training(self): sets = self.main_area_neural.sets[:] if self.main_area_neural.trash_set: sets.append(self.main_area_neural.trash_set) self.training_window = TrainingWindow(self, sets) self.training_window.show() def view_all_testing(self): self.testing_window = TestingWindow(self, self.dataset.all_testing_images) self.testing_window.show() def exit_app(self): sys.exit(0) def about_clicked(self): d = QDialog(self) layout = QVBoxLayout() d.setLayout(layout) layout.addWidget( QLabel("Convolutional Neural Network - Created By Theo Styles")) layout.addWidget(QLabel("Credits:")) layout.addWidget( QLabel("Play icon made by Google from www.flaticon.com")) layout.addWidget( QLabel("Plus icon made by Madebyoliver from www.flaticon.com")) layout.addWidget( QLabel("Export icon made by Popcic from www.flaticon.com")) layout.addWidget( QLabel("Up arrow icon made by Google from www.flaticon.com")) layout.addWidget( QLabel("Down arrow icon made by Google from www.flaticon.com")) layout.addWidget( QLabel("Tick icon made by Eleonor Wang from www.flaticon.com")) d.setWindowTitle("About") d.exec_() def init_ui(self): self.settings = QSettings("Theo Styles", "Convolutional Neural Network") self.settings.setValue("test", 1) self.dataset = DataSet(self.img_size) self.dataset.test_set_changed.connect(self.set_testing_amount) qss_file = open( PathHelpers.getPath("GUI/Stylesheets/default.qss")).read() self.setStyleSheet(qss_file) self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.main_grid = QSplitter() self.main_grid.setObjectName("verticalSplitter") self.main_grid.setContentsMargins(0, 0, 0, 0) self.right_layout = QVBoxLayout() self.right_widget = QWidget() self.right_widget.setLayout(self.right_layout) self.right_grid = QSplitter() self.right_grid.setObjectName("verticalSplitter") self.right_layout.setContentsMargins(0, 0, 0, 0) self.right_layout.setSpacing(0) self.right_grid.setContentsMargins(0, 0, 0, 0) self.right_stacking = QWidget() self.right_stacking_grid = QGridLayout() self.right_stacking.setLayout(self.right_stacking_grid) self.right_grid.addWidget(self.right_stacking) self.right_stacking_grid.setContentsMargins(0, 0, 0, 0) self.right_stacking_grid.setSpacing(0) self.main_area_neural = NeuralNetSection() self.main_area_neural.added_to_set.connect(self.added_to_set) self.main_area_neural.removed_from_set.connect(self.removed_from_set) self.main_area_neural.deleted_set.connect(self.deleted_set) self.right_stacking_grid.addWidget(self.main_area_neural, 0, 0) self.main_area_settings = SettingsSection() self.right_stacking_grid.addWidget(self.main_area_settings, 0, 0) self.main_area_settings.setVisible(False) self.main_area_reports = ReportsSection(self.main_area_neural.sets, self.iteration_stats) self.right_stacking_grid.addWidget(self.main_area_reports, 0, 0) self.main_area_reports.setVisible(False) self.datapanel = DataInfoPanel() self.datapanel.clicked_training_view_all_sig.connect( self.view_all_training) self.datapanel.clicked_testing_view_all_sig.connect( self.view_all_testing) self.right_grid.addWidget(self.datapanel) self.right_grid.setStretchFactor(0, 10) self.right_grid.setStretchFactor(1, 11) self.left_area = MenuPanel() self.left_area.selectedItem.connect(self.menu_changed) self.toolbar = ToolbarPanel() self.toolbar.run_clicked.connect(self.run_clicked) self.toolbar.add_clicked.connect(self.add_toolbar_clicked) self.toolbar.export_clicked.connect(self.export_sets) self.right_layout.addWidget(self.toolbar) self.right_layout.addWidget(self.right_grid) self.main_grid.addWidget(self.left_area) self.main_grid.addWidget(self.right_widget) self.setCentralWidget(self.main_grid) self.main_grid.setStretchFactor(0, 6) self.main_grid.setStretchFactor(1, 10) save_action = QAction("&Save", self) save_action.setShortcut("Ctrl+S") save_action.setStatusTip('Save the current sets') save_action.triggered.connect(self.save_sets) save_action_as = QAction("&Save As", self) save_action_as.setStatusTip('New save for the current sets') save_action_as.triggered.connect(self.save_sets_as) open_action = QAction("&Open", self) open_action.setShortcut("Ctrl+O") open_action.setStatusTip('Open a sets save file') open_action.triggered.connect(self.open_sets) exit_action = QAction("&Exit", self) exit_action.setStatusTip('Exit the application') exit_action.triggered.connect(self.exit_app) menubar = self.menuBar() file_menu = menubar.addMenu('&File') file_menu.addAction(open_action) file_menu.addAction(save_action) file_menu.addAction(save_action_as) file_menu.addAction(exit_action) neural_action = QAction("&Neural Net", self) neural_action.setStatusTip('View the neural network') neural_action.triggered.connect(self.switch_to_neural) reports_action = QAction("&Reports", self) reports_action.setStatusTip('View reports and statistics') reports_action.triggered.connect(self.switch_to_reports) settings_action = QAction("&Settings", self) settings_action.setStatusTip('View settings') settings_action.triggered.connect(self.switch_to_settings) view_menu = menubar.addMenu('&View') view_menu.addAction(neural_action) view_menu.addAction(reports_action) view_menu.addAction(settings_action) import_action = QAction("&Import folder", self) import_action.setStatusTip('Import a folder of images') import_action.triggered.connect(self.add_toolbar_clicked) export_action = QAction("&Export sets", self) export_action.setStatusTip('Export sets to folder') export_action.triggered.connect(self.export_sets) settings_action = QAction("&Settings", self) settings_action.setStatusTip('View settings') settings_action.triggered.connect(self.switch_to_settings) tools_menu = menubar.addMenu('&Tools') tools_menu.addAction(import_action) tools_menu.addAction(export_action) run_action = QAction("&Run Neural Network", self) run_action.setStatusTip('Start running the neural network') run_action.triggered.connect(self.run_clicked) run_menu = menubar.addMenu('&Run') run_menu.addAction(run_action) about_action = QAction("&About", self) about_action.setStatusTip('About the application') about_action.triggered.connect(self.about_clicked) help_menu = menubar.addMenu('&Help') help_menu.addAction(about_action) self.show()
class Tab(QWidget): currItemChanged = pyqtSignal(['QModelIndex']) def __init__(self, packItem=QModelIndex(), parent: TabWidget = None): super(Tab, self).__init__(parent) self.tabWidget = parent self.icon = QIcon() self.initActions() self.pathToolBar = ToolBar(self) self.pathToolBar.addAction(self.backAct) self.pathToolBar.addAction(self.forwardAct) self.pathLine: AddressLine = AddressLine(self) self.objTypeLine = LineEdit(self, placeholderText="Object Type") self.objTypeLine.setFixedWidth(168) self.objTypeLine.setReadOnly(True) self.descrLabel = QLabel(self) self.descrLabel.setWordWrap(True) self.descrLabel.setTextInteractionFlags(Qt.TextSelectableByMouse) QWebEngineSettings.defaultSettings().setAttribute( QWebEngineSettings.PluginsEnabled, True) self.mediaWidget = QWebEngineView() self.saveMediaAsBtn = QPushButton( f"Save media as..", self, toolTip="Save media file as..", clicked=lambda: self.saveMediaAsWithDialog( QStandardPaths.writableLocation(QStandardPaths. DocumentsLocation))) self.saveMediaBtn = QPushButton( f"Save media on desktop", self, toolTip="Save media file on desktop", clicked=lambda: self.saveMediaAsWithDialog( QStandardPaths.writableLocation(QStandardPaths.DesktopLocation) )) self.mediaViewWidget = QWidget() mediaViewWidgetLayout = QVBoxLayout(self.mediaViewWidget) mediaViewWidgetLayout.setContentsMargins(0, 0, 0, 0) mediaViewWidgetLayout.addWidget(self.mediaWidget) mediaViewWidgetLayout.addWidget(self.saveMediaBtn) mediaViewWidgetLayout.addWidget(self.saveMediaAsBtn) self.mediaViewWidget.hide() self.attrsTreeView = AttrsTreeView(self) self.attrsTreeView.setFrameShape(QFrame.NoFrame) self.toolBar = ToolBar(self) self.toolBar.addAction(self.attrsTreeView.zoomInAct) self.toolBar.addAction(self.attrsTreeView.zoomOutAct) self.toolBar.addAction(self.attrsTreeView.collapseAllAct) self.toolBar.addAction(self.attrsTreeView.expandAllAct) self.toolBar.addSeparator() self.toolBar.addAction(self.attrsTreeView.undoAct) self.toolBar.addAction(self.attrsTreeView.redoAct) self.toolBar.addAction(self.attrsTreeView.copyAct) self.toolBar.addAction(self.attrsTreeView.cutAct) self.toolBar.addAction(self.attrsTreeView.pasteAct) self.toolBar.addAction(self.attrsTreeView.delClearAct) self.toolBar.addAction(self.attrsTreeView.editCreateInDialogAct) self.toolBar.addAction(self.attrsTreeView.addAct) self.searchBar = SearchBar( self.attrsTreeView, parent=self, filterColumns=[ATTRIBUTE_COLUMN, VALUE_COLUMN], closable=True) self.searchBar.hide() self.openSearchBarSC = QShortcut(SC_SEARCH, self, activated=self.openSearchBar) self.packItem = QPersistentModelIndex(QModelIndex()) self.prevItems = [] self.nextItems = [] if packItem.isValid(): self.openItem(packItem) else: self.openEmptyItem() self._initLayout() # noinspection PyArgumentList def initActions(self): self.backAct = QAction(BACK_ICON, "Back", self, statusTip=f"Go back one item", toolTip=f"Go back one item", shortcut=SC_BACK, triggered=self.openPrevItem, enabled=False) self.forwardAct = QAction(FORWARD_ICON, "Forward", self, statusTip=f"Go forward one item", toolTip=f"Go forward one item", shortcut=SC_FORWARD, triggered=self.openNextItem, enabled=False) def windowTitle(self) -> str: return self.packItem.data(NAME_ROLE) def windowIcon(self) -> QIcon: return self.packItem.data(Qt.DecorationRole) def openSearchBar(self): self.searchBar.show() self.searchBar.searchLine.setFocus() def openItem(self, packItem: QModelIndex): if not packItem == QModelIndex(self.packItem): self.nextItems.clear() if self.packItem.isValid(): self.prevItems.append(self.packItem) self._openItem(packItem) def openPrevItem(self): if self.prevItems: prevItem = self.prevItems.pop() self.nextItems.append(self.packItem) self._openItem(QModelIndex(prevItem)) def openNextItem(self): if self.nextItems: nextItem = self.nextItems.pop() self.prevItems.append(self.packItem) self._openItem(QModelIndex(nextItem)) def openEmptyItem(self): self._openItem(QModelIndex()) def _openItem(self, packItem: QModelIndex): try: currTab: Tab = self.tabWidget.currentWidget() state = currTab.attrsTreeView.header().saveState() except AttributeError: # if there is no curr widget, there is no current header state, it state = None self.packItem = QPersistentModelIndex(packItem.siblingAtColumn(0)) self.descrLabel.setText("") self.packItemObj = self.packItem.data(OBJECT_ROLE) self.updateMediaWidget() self.pathLine.setText(getTreeItemPath(self.packItem)) self.objTypeLine.setText(getTypeName(type(self.packItemObj))) icon = self.packItem.data(Qt.DecorationRole) if icon: self.setWindowIcon(icon) self.setWindowTitle(self.packItem.data(Qt.DisplayRole)) self.attrsTreeView.newPackItem(self.packItem) self.attrsTreeView.selectionModel().currentChanged.connect( self.showDetailInfoItemDoc) self.currItemChanged.emit(QModelIndex(self.packItem)) self.forwardAct.setEnabled( True) if self.nextItems else self.forwardAct.setDisabled(True) self.backAct.setEnabled( True) if self.prevItems else self.backAct.setDisabled(True) if state: self.attrsTreeView.header().restoreState(state) def updateMediaWidget(self): if self.packItem.data(IS_MEDIA_ROLE): self.mediaWidget.setContent(b"loading...") self.mediaViewWidget.show() if not self.mediaWidget.width(): # set equal sizes oldSizes = self.splitter.sizes() newSizes = [ sum(oldSizes) / (len(oldSizes)) for size in oldSizes ] self.splitter.setSizes(newSizes) try: mediaContent = self.packItem.data(MEDIA_CONTENT_ROLE) if self.packItem.data(IS_URL_MEDIA_ROLE): self.mediaWidget.load(QUrl(mediaContent.value)) else: self.mediaWidget.setContent(mediaContent.value, mediaContent.mime_type) except Exception as e: print(e) self.mediaWidget.setContent( b"Error occurred while loading media") self.mediaWidget.setZoomFactor(1.0) else: self.mediaViewWidget.hide() def saveMediaAsWithDialog(self, directory="") -> bool: mediaContent = self.packItem.data(MEDIA_CONTENT_ROLE) file = self.packItem.data(NAME_ROLE) saved = False while not saved: try: file = QFileDialog.getSaveFileName( self, 'Save media File', directory + "/" + file.strip("/"), options=FILE_DIALOG_OPTIONS)[0] except AttributeError as e: QMessageBox.critical(self, "Error", f"{e}") else: if file: saved = self.saveMedia(mediaContent, file) else: # cancel pressed return def saveMedia(self, media=None, file: str = None) -> bool: try: with open(file, "wb") as f: f.write(media.value) return True except (TypeError, ValueError) as e: QMessageBox.critical(self, "Error", f"Media couldn't be saved: {file}: {e}") except AttributeError as e: QMessageBox.critical(self, "Error", f"No chosen media to save: {e}") return False def showDetailInfoItemDoc(self, detailInfoItem: QModelIndex): self.descrLabel.setText(detailInfoItem.data(Qt.WhatsThisRole)) def _initLayout(self): pathWidget = QWidget() pathLayout = QHBoxLayout(pathWidget) pathLayout.setContentsMargins(0, 0, 0, 0) pathLayout.addWidget(self.pathToolBar) pathLayout.addWidget(self.pathLine) pathLayout.addWidget(self.objTypeLine) pathWidget.setFixedHeight(TOOLBARS_HEIGHT) toolBarWidget = QWidget() toolBarLayout = QHBoxLayout(toolBarWidget) toolBarLayout.setContentsMargins(0, 0, 0, 0) toolBarLayout.addWidget(self.toolBar) toolBarLayout.addWidget(self.searchBar) toolBarWidget.setFixedHeight(TOOLBARS_HEIGHT) treeViewWidget = QWidget() treeViewLayout = QVBoxLayout(treeViewWidget) treeViewLayout.setContentsMargins(0, 0, 0, 0) treeViewLayout.addWidget(pathWidget) treeViewLayout.addWidget(self.attrsTreeView) treeViewLayout.addWidget(self.descrLabel) self.splitter = QSplitter() self.splitter.setOrientation(Qt.Horizontal) self.splitter.setContentsMargins(0, 0, 0, 0) self.splitter.addWidget(treeViewWidget) self.splitter.addWidget(self.mediaViewWidget) layout = QVBoxLayout(self) layout.setObjectName("tabLayout") layout.addWidget(pathWidget) layout.addWidget(toolBarWidget) layout.addWidget(self.splitter) layout.setSpacing(2) layout.setContentsMargins(0, 2, 0, 2)
class ViewWidget(QMainWindow): def __init__(self): super(ViewWidget, self).__init__() self.diff = -1 self.OldTableWidget = ExcelWidget() self.NewTableWidget = ExcelWidget() self.MainLayout = QHBoxLayout() self.Splitter = QSplitter(Qt.Horizontal) self.Splitter.addWidget(self.OldTableWidget) self.Splitter.addWidget(self.NewTableWidget) self.Splitter.setContentsMargins(5, 5, 5, 5) self.setCentralWidget(self.Splitter) self.Lock = True self.OldTableWidget.currentChanged.connect( lambda x: self.setSame(x, 0)) self.NewTableWidget.currentChanged.connect( lambda x: self.setSame(x, 1)) self.OldTableWidget.cellClicked.connect( lambda x, y: self.setSameCell(x, y, 0)) self.NewTableWidget.cellClicked.connect( lambda x, y: self.setSameCell(x, y, 1)) self.OldTableWidget.hbarchange.connect( lambda x: self.NewTableWidget.TableWidgets[self.NewTableWidget.currentIndex( )].horizontalScrollBar().setValue(x)) self.NewTableWidget.vbarchange.connect( lambda x: self.OldTableWidget. TableWidgets[self.OldTableWidget.currentIndex()].verticalScrollBar( ).setValue(x)) self.NewTableWidget.hbarchange.connect( lambda x: self.OldTableWidget.TableWidgets[self.OldTableWidget.currentIndex( )].horizontalScrollBar().setValue(x)) self.OldTableWidget.vbarchange.connect( lambda x: self.NewTableWidget. TableWidgets[self.NewTableWidget.currentIndex()].verticalScrollBar( ).setValue(x)) self.initAction() self.initToolbar() # self.MainLayout.addWidget(self.Splitter) # self.setLayout(self.MainLayout) def setSameCell(self, x, y, type1): if self.Lock == False: return if type1 == 0: self.NewTableWidget.currentWidget().setCurrentCell(x, y) else: self.OldTableWidget.currentWidget().setCurrentCell(x, y) def setSame(self, id, type1): if self.Lock == False: return if type1 == 0: text = self.OldTableWidget.tabText(id) for i in range(self.NewTableWidget.count()): if text == self.NewTableWidget.tabText(i): self.NewTableWidget.setCurrentIndex(i) else: text = self.NewTableWidget.tabText(id) for i in range(self.OldTableWidget.count()): if text == self.OldTableWidget.tabText(i): self.OldTableWidget.setCurrentIndex(i) def initToolbar(self): self.toolbar = self.addToolBar("tabletool") def initAction(self): self.LockAction = QAction(QIcon("icon/lock.png"), "锁定", self) self.LockAction.setStatusTip("锁定表格,使得切换标签页时,新旧两个表格同步,且比较时将比较整个文件!") self.LockAction.triggered.connect(self.lockTab) self.UnlockAction = QAction(QIcon("icon/unlock.png"), "解锁", self) self.UnlockAction.setStatusTip("解锁表格,使得切换标签页时,新旧两个表格不会同步,且只比较选定的标签!") self.UnlockAction.triggered.connect(self.unlockTab) def lockTab(self): self.Lock = True self.toolbar.removeAction(self.LockAction) self.toolbar.addAction(self.UnlockAction) def unlockTab(self): self.Lock = False self.toolbar.removeAction(self.UnlockAction) self.toolbar.addAction(self.LockAction) def setOldTable(self, data): self.OldTableWidget.setData(data) def setNewTable(self, data): self.NewTableWidget.setData(data) def ABCToInt(self, s): dict0 = {} for i in range(26): dict0[chr(ord('A') + i)] = i + 1 output = 0 for i in range(len(s)): output = output * 26 + dict0[s[i]] return output def setHighLight(self, widget, difftype, id): """ 0 old 1 new 2 both """ self.ColorSettings = QSettings("ExcelDiffer", "Color") hightlight = self.ColorSettings.value("hightlight") self.setColor(self.diff, self.oi, self.ni) if widget == 0: if difftype == "del_col": col = self.ABCToInt(self.diff[difftype][id]) self.OldTableWidget.TableWidgets[self.oi].setCurrentCell( 0, col - 1) for i in range( self.OldTableWidget.TableWidgets[self.oi].rowCount()): self.OldTableWidget.TableWidgets[self.oi].item( i, col - 1).setBackground(QBrush(QColor(hightlight))) if difftype == "del_row": row = self.diff[difftype][id] self.OldTableWidget.TableWidgets[self.oi].setCurrentCell( row - 1, 0) for j in range(self.OldTableWidget.TableWidgets[ self.oi].columnCount()): self.OldTableWidget.TableWidgets[self.oi].item( row - 1, j).setBackground(QBrush(QColor(hightlight))) if difftype == "change_cell": rec = self.diff[difftype][id] j = self.ABCToInt(rec[0][1]) self.OldTableWidget.TableWidgets[self.oi].setCurrentCell( rec[0][0] - 1, j - 1) self.OldTableWidget.TableWidgets[self.oi].item( rec[0][0] - 1, j - 1).setBackground(QBrush(QColor(hightlight))) j = self.ABCToInt(rec[1][1]) self.NewTableWidget.TableWidgets[self.ni].setCurrentCell( rec[1][0] - 1, j - 1) self.NewTableWidget.TableWidgets[self.ni].item( rec[1][0] - 1, j - 1).setBackground(QBrush(QColor(hightlight))) if difftype == "del_merge": rec = self.diff["del_merge"][id] self.OldTableWidget.TableWidgets[self.oi].setCurrentCell( rec[0], rec[2]) for i in range(rec[0], rec[1]): for j in range(rec[2], rec[3]): self.OldTableWidget.TableWidgets[self.oi].item( i, j).setBackground(QBrush(QColor(hightlight))) if difftype == "row_exchange": i = self.diff["row_exchange"][id] self.OldTableWidget.TableWidgets[self.oi].setCurrentCell( i[0] - 1, 0) self.NewTableWidget.TableWidgets[self.ni].setCurrentCell( i[1] - 1, 0) for j in range(self.OldTableWidget.TableWidgets[ self.oi].columnCount()): self.OldTableWidget.TableWidgets[self.oi].item( i[0] - 1, j).setBackground(QBrush(QColor(hightlight))) for j in range(self.NewTableWidget.TableWidgets[ self.ni].columnCount()): self.NewTableWidget.TableWidgets[self.ni].item( i[1] - 1, j).setBackground(QBrush(QColor(hightlight))) if difftype == "col_exchange": s = self.diff["col_exchange"][id] j1 = self.ABCToInt(s[0]) j2 = self.ABCToInt(s[1]) self.OldTableWidget.TableWidgets[self.oi].setCurrentCell( 0, j1 - 1) self.NewTableWidget.TableWidgets[self.ni].setCurrentCell( 0, j2 - 1) for i in range( self.OldTableWidget.TableWidgets[self.oi].rowCount()): self.OldTableWidget.TableWidgets[self.oi].item( i, j1 - 1).setBackground(QBrush(QColor(hightlight))) for i in range( self.NewTableWidget.TableWidgets[self.ni].rowCount()): self.NewTableWidget.TableWidgets[self.ni].item( i, j2 - 1).setBackground(QBrush(QColor(hightlight))) elif widget == 1: if difftype == "add_col": col = self.ABCToInt(self.diff[difftype][id]) self.NewTableWidget.TableWidgets[self.ni].setCurrentCell( 0, col - 1) for i in range( self.NewTableWidget.TableWidgets[self.ni].rowCount()): self.NewTableWidget.TableWidgets[self.ni].item( i, col - 1).setBackground(QBrush(QColor(hightlight))) if difftype == "add_row": row = self.diff[difftype][id] self.NewTableWidget.TableWidgets[self.ni].setCurrentCell( row - 1, 0) for j in range(self.NewTableWidget.TableWidgets[ self.ni].columnCount()): self.NewTableWidget.TableWidgets[self.ni].item( row - 1, j).setBackground(QBrush(QColor(hightlight))) if difftype == "change_cell": rec = self.diff[difftype][id] j = self.ABCToInt(rec[0][1]) self.OldTableWidget.TableWidgets[self.oi].setCurrentCell( rec[0][0] - 1, j - 1) self.OldTableWidget.TableWidgets[self.oi].item( rec[0][0] - 1, j - 1).setBackground(QBrush(QColor(hightlight))) j = self.ABCToInt(rec[1][1]) self.NewTableWidget.TableWidgets[self.ni].setCurrentCell( rec[1][0] - 1, j - 1) self.NewTableWidget.TableWidgets[self.ni].item( rec[1][0] - 1, j - 1).setBackground(QBrush(QColor(hightlight))) if difftype == "new_merge": rec = self.diff["new_merge"][id] self.NewTableWidget.TableWidgets[self.ni].setCurrentCell( rec[0], rec[2]) for i in range(rec[0], rec[1]): for j in range(rec[2], rec[3]): self.NewTableWidget.TableWidgets[self.ni].item( i, j).setBackground(QBrush(QColor(hightlight))) if difftype == "row_exchange": i = self.diff["row_exchange"][id] self.OldTableWidget.TableWidgets[self.oi].setCurrentCell( i[0] - 1, 0) for j in range(self.OldTableWidget.TableWidgets[ self.oi].columnCount()): self.OldTableWidget.TableWidgets[self.oi].item( i[0] - 1, j).setBackground(QBrush(QColor(hightlight))) self.NewTableWidget.TableWidgets[self.ni].setCurrentCell( i[1] - 1, 0) for j in range(self.NewTableWidget.TableWidgets[ self.ni].columnCount()): self.NewTableWidget.TableWidgets[self.ni].item( i[1] - 1, j).setBackground(QBrush(QColor(hightlight))) if difftype == "col_exchange": s = self.diff["col_exchange"][id] j1 = self.ABCToInt(s[0]) j2 = self.ABCToInt(s[1]) self.OldTableWidget.TableWidgets[self.oi].setCurrentCell( 0, j1 - 1) for i in range( self.OldTableWidget.TableWidgets[self.oi].rowCount()): self.OldTableWidget.TableWidgets[self.oi].item( i, j1 - 1).setBackground(QBrush(QColor(hightlight))) self.NewTableWidget.TableWidgets[self.ni].setCurrentCell( 0, j2 - 1) for i in range( self.NewTableWidget.TableWidgets[self.ni].rowCount()): self.NewTableWidget.TableWidgets[self.ni].item( i, j2 - 1).setBackground(QBrush(QColor(hightlight))) else: pass def setColor(self, diff, oi=-1, ni=-1): self.ColorSettings = QSettings("ExcelDiffer", "Color") hightlight = self.ColorSettings.value("hightlight") background = self.ColorSettings.value("background") exchange = self.ColorSettings.value("exchange") add = self.ColorSettings.value("add") delcolor = self.ColorSettings.value("delcolor") change = self.ColorSettings.value("change") self.diff = diff if oi == -1: oi = self.OldTableWidget.currentIndex() ni = self.NewTableWidget.currentIndex() self.oi = oi self.ni = ni for i in range(self.NewTableWidget.TableWidgets[ni].rowCount()): for j in range(self.NewTableWidget.TableWidgets[ni].columnCount()): self.NewTableWidget.TableWidgets[ni].item(i, j).setBackground( QBrush(QColor(background))) self.NewTableWidget.TableWidgets[ni].item(i, j).setForeground( QBrush(QColor("#000000"))) for i in range(self.OldTableWidget.TableWidgets[oi].rowCount()): for j in range(self.OldTableWidget.TableWidgets[oi].columnCount()): self.OldTableWidget.TableWidgets[oi].item(i, j).setBackground( QBrush(QColor(background))) self.OldTableWidget.TableWidgets[oi].item(i, j).setForeground( QBrush(QColor("#000000"))) for i in diff["row_exchange"]: for j in range(self.OldTableWidget.TableWidgets[oi].columnCount()): self.OldTableWidget.TableWidgets[oi].item( i[0] - 1, j).setBackground(QBrush(QColor(exchange))) for j in range(self.NewTableWidget.TableWidgets[ni].columnCount()): self.NewTableWidget.TableWidgets[ni].item( i[1] - 1, j).setBackground(QBrush(QColor(exchange))) for s in diff["col_exchange"]: j1 = self.ABCToInt(s[0]) j2 = self.ABCToInt(s[1]) for i in range(self.OldTableWidget.TableWidgets[oi].rowCount()): self.OldTableWidget.TableWidgets[oi].item( i, j1 - 1).setBackground(QBrush(QColor(exchange))) for i in range(self.NewTableWidget.TableWidgets[ni].rowCount()): self.NewTableWidget.TableWidgets[ni].item( i, j2 - 1).setBackground(QBrush(QColor(exchange))) for rec in diff["new_merge"]: for i in range(rec[0], rec[1]): for j in range(rec[2], rec[3]): self.NewTableWidget.TableWidgets[ni].item( i, j).setBackground(QBrush(QColor(add))) for rec in diff["del_merge"]: for i in range(rec[0], rec[1]): for j in range(rec[2], rec[3]): self.OldTableWidget.TableWidgets[oi].item( i, j).setBackground(QBrush(QColor(delcolor))) for s in diff["add_col"]: j = self.ABCToInt(s) for i in range(self.NewTableWidget.TableWidgets[ni].rowCount()): self.NewTableWidget.TableWidgets[ni].item( i, j - 1).setBackground(QBrush(QColor(add))) for s in diff["del_col"]: j = self.ABCToInt(s) for i in range(self.OldTableWidget.TableWidgets[oi].rowCount()): self.OldTableWidget.TableWidgets[oi].item( i, j - 1).setBackground(QBrush(QColor(delcolor))) for i in diff["add_row"]: for j in range(self.NewTableWidget.TableWidgets[ni].columnCount()): self.NewTableWidget.TableWidgets[ni].item( i - 1, j).setBackground(QBrush(QColor(add))) for i in diff["del_row"]: for j in range(self.OldTableWidget.TableWidgets[oi].columnCount()): self.OldTableWidget.TableWidgets[oi].item( i - 1, j).setBackground(QBrush(QColor(delcolor))) for rec in diff["change_cell"]: j = self.ABCToInt(rec[0][1]) self.OldTableWidget.TableWidgets[oi].item( rec[0][0] - 1, j - 1).setBackground(QBrush(QColor(change))) j = self.ABCToInt(rec[1][1]) self.NewTableWidget.TableWidgets[ni].item( rec[1][0] - 1, j - 1).setBackground(QBrush(QColor(change)))
class SessionUi(QTabWidget): TAB_MODULES = 0 TAB_RANGES = 1 TAB_DATA = 2 TAB_ASM = 3 TAB_JAVA_CLASSES = 4 def __init__(self, app, *__args): super().__init__(*__args) self.app = app self.setTabsClosable(True) self.setMovable(True) self.tabCloseRequested.connect(self.removeTab) self.setContentsMargins(2, 2, 2, 2) self.setStyleSheet(""" QListWidget:hover, QTableWidget:hover { border: 1px solid transparent; } QTabWidget QFrame{ border: 0; } QTabWidget::pane { border: 0px solid transparent; border-radius: 0px; padding: 0px; margin: 0px; } QTabWidget::pane:selected { background-color: transparent; border: 0px solid transparent; } QWidget { padding: 0; margin-top: 2px; margin-right: 2px; margin-left: 1px; } """) self.session_panel = QSplitter() self.modules_panel = None self.ranges_panel = None self.registers_panel = None self.memory_panel = None self.log_panel = None self.backtrace_panel = None self.hooks_panel = None self.contexts_panel = None self.session_panel.addWidget(self.build_left_column()) self.session_panel.addWidget(self.build_central_content()) self.session_panel.setHandleWidth(1) self.session_panel.setStretchFactor(0, 2) self.session_panel.setStretchFactor(1, 6) self.session_panel.setContentsMargins(0, 0, 0, 0) self.modules_panel = ModulesPanel(self.app) self.ranges_panel = RangesPanel(self.app) self.data_panel = DataPanel(self.app) self.asm_panel = AsmPanel(self.app) self.java_class_panel = None self.addTab(self.session_panel, 'session') bt = self.tabBar().tabButton(0, QTabBar.LeftSide) if not bt: bt = self.tabBar().tabButton(0, QTabBar.RightSide) if bt: bt.resize(0, 0) def add_main_tabs(self): self.add_dwarf_tab(SessionUi.TAB_MODULES) self.add_dwarf_tab(SessionUi.TAB_RANGES) def build_left_column(self): splitter = QSplitter() splitter.setHandleWidth(1) splitter.setOrientation(Qt.Vertical) splitter.setContentsMargins(0, 0, 0, 0) self.hooks_panel = HooksPanel(self.app) splitter.addWidget(self.hooks_panel) self.contexts_panel = ContextsPanel(self.app) splitter.addWidget(self.contexts_panel) self.backtrace_panel = BacktracePanel(self.app) splitter.addWidget(self.backtrace_panel) return splitter def build_central_content(self): main_panel = QSplitter(self) main_panel.setHandleWidth(1) main_panel.setOrientation(Qt.Vertical) main_panel.setContentsMargins(0, 0, 0, 0) self.registers_panel = RegistersPanel(self.app, 0, 0) main_panel.addWidget(self.registers_panel) self.memory_panel = MemoryPanel(self.app) main_panel.addWidget(self.memory_panel) self.log_panel = LogPanel(self.app) main_panel.addWidget(self.log_panel) main_panel.setStretchFactor(0, 1) main_panel.setStretchFactor(1, 3) main_panel.setStretchFactor(2, 1) return main_panel def on_script_loaded(self): self.add_main_tabs() def on_script_destroyed(self): for i in range(0, self.count()): if i > 0: self.removeTab(i) self.log_panel.clear() self.data_panel.clear() self.asm_panel.range = None self.hooks_panel.setRowCount(0) self.hooks_panel.setColumnCount(0) self.hooks_panel.resizeColumnsToContents() self.hooks_panel.horizontalHeader().setStretchLastSection(True) self.ranges_panel.setRowCount(0) self.ranges_panel.resizeColumnsToContents() self.ranges_panel.horizontalHeader().setStretchLastSection(True) self.modules_panel.setRowCount(0) self.modules_panel.resizeColumnsToContents() self.modules_panel.horizontalHeader().setStretchLastSection(True) self.contexts_panel.setRowCount(0) self.hooks_panel.setColumnCount(0) self.contexts_panel.resizeColumnsToContents() self.contexts_panel.horizontalHeader().setStretchLastSection(True) self.backtrace_panel.setRowCount(0) self.hooks_panel.setColumnCount(0) self.backtrace_panel.resizeColumnsToContents() self.backtrace_panel.horizontalHeader().setStretchLastSection(True) self.registers_panel.setRowCount(0) self.hooks_panel.setColumnCount(0) self.registers_panel.resizeColumnsToContents() self.registers_panel.horizontalHeader().setStretchLastSection(True) self.memory_panel.on_script_destroyed() self.java_class_panel = None def close_tab(self, index): self.removeTab(index) def add_dwarf_tab(self, tab_id, request_focus=False): if tab_id == SessionUi.TAB_DATA: self.addTab(self.data_panel, 'data') if request_focus: self.setCurrentWidget(self.hooks_panel) elif tab_id == SessionUi.TAB_MODULES: self.addTab(self.modules_panel, 'modules') if request_focus: self.setCurrentWidget(self.modules_panel) elif tab_id == SessionUi.TAB_RANGES: self.addTab(self.ranges_panel, 'ranges') if request_focus: self.setCurrentWidget(self.ranges_panel) elif tab_id == SessionUi.TAB_ASM: self.addTab(self.asm_panel, 'asm') if request_focus: self.setCurrentWidget(self.asm_panel) elif tab_id == SessionUi.TAB_JAVA_CLASSES: if self.java_class_panel is None: self.java_class_panel = JavaClassesPanel(self.app) if self.java_class_panel.parent() is None: self.addTab(self.java_class_panel, 'Java Classes') if request_focus: self.setCurrentWidget(self.java_class_panel) def add_tab(self, tab_widget, tab_label): self.addTab(tab_widget, tab_label) self.setCurrentWidget(tab_widget) def disasm(self, ptr=0, _range=None): self.add_dwarf_tab(SessionUi.TAB_ASM, True) if _range: self.asm_panel.disasm(_range=_range) else: self.asm_panel.read_memory(ptr) def request_session_ui_focus(self): self.setCurrentWidget(self.session_panel)
class CodecTab(QScrollArea): # BUG: codec_frame should have height 210 but has 480. # WORKAROUND: manually set height to 210 height. # SEE: https://forum.qt.io/topic/42055/qwidget-height-returns-incorrect-value-in-5-3/7 FRAME_HEIGHT = 210 def __init__(self, parent, context, commands): super(QWidget, self).__init__(parent) self._context = context self._logger = context.logger() self._commands = commands self._next_frame_id = 1 self._frames = QSplitter(Qt.Vertical) self._frames.setChildrenCollapsible(False) self._frames.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)) self._frames.setContentsMargins(0, 0, 0, 0) self._main_frame = QFrame(self) self._main_frame_layout = QVBoxLayout() self._main_frame_layout.addWidget(self._frames) self._main_frame_layout.addWidget(VSpacer(self)) self._main_frame.setLayout(self._main_frame_layout) self.newFrame("", "") self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.setWidgetResizable(True) self.setWidget(self._main_frame) def newFrame(self, text, title, previous_frame=None, status=None, msg=None): try: # BUG: Setting complex default values is not possible in python # WORKAROUND: Set default value to None and set real default later. if status is None: status = StatusWidget.DEFAULT if previous_frame and previous_frame.hasNext(): next_frame = previous_frame.next() next_frame.setTitle(title) finished = False if status == StatusWidget.ERROR: while not finished: next_frame.flashStatus(status, msg) # Display error only for the first frame. msg = None finished = not next_frame.hasNext() next_frame = next_frame.next() else: next_frame.setInputText(text, msg is not None and len(msg) > 0) next_frame.flashStatus(status, msg) previous_frame.focusInputText() else: new_frame = CodecFrame(self, self._context, self._next_frame_id, self, self._commands, previous_frame, text) self._next_frame_id += 1 if self._frames.count() > 0: new_frame.flashStatus(status, msg) new_frame.setTitle(title) new_frame.setContentsMargins(0, 0, 0, 0) new_frame.layout().setContentsMargins(0, 0, 0, 0) self._frames.addWidget(new_frame) # BUG: QSplitter does not allow frames to be wider than the surrounding area (here: QScrollArea). # WORKAROUND: Set a fixed size for codec frames and disable handles which prevents users from # trying to resize the codec frames. new_frame.setFixedHeight(self.FRAME_HEIGHT) self._frames.handle(self._frames.count() - 1).setEnabled(False) if previous_frame: previous_frame.focusInputText() else: new_frame.focusInputText() except Exception as e: self._logger.error("Unknown error: {}".format(str(e))) def removeFrames(self, frame): if frame: if frame.previous(): frame.previous().setNext(None) frames_to_remove = [frame] while frame.next(): frames_to_remove.append(frame.next()) frame = frame.next() for frame_to_remove in reversed(frames_to_remove): frame_to_remove.deleteLater() def getFocussedFrame(self): widget = self._frames.focusWidget() while widget: if isinstance(widget, CodecFrame): return widget widget = widget.parent() return self._frames.widget(0)