示例#1
0
    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
示例#2
0
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())
示例#3
0
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)
示例#4
0
    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 __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)
示例#6
0
    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
示例#7
0
    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
示例#8
0
    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
示例#9
0
    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)
示例#10
0
    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)
示例#11
0
    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
示例#13
0
    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()
示例#14
0
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()
示例#15
0
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()
示例#16
0
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()
示例#17
0
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()
示例#18
0
    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)
示例#20
0
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()
示例#21
0
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)
示例#23
0
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
示例#24
0
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)
示例#25
0
    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'
示例#26
0
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
示例#27
0
    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)
示例#28
0
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()
示例#29
0
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)
示例#30
0
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)))
示例#31
0
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)
示例#32
0
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)