Ejemplo n.º 1
0
    def init_splitter(self):
        self.frame = QFrame()
        self.frame.setFrameShape(QFrame.StyledPanel)
        hbox = QHBoxLayout()  # here should be no 'self' argument
        self.browser_widget = bv.BrowserWidget(self)
        self.top_splitter = QSplitter(Qt.Horizontal)
        frame = QFrame()
        layout = QVBoxLayout()
        for addon in self.addon_list:
            layout.addWidget(addon)
            addon.hide()
        frame.setLayout(layout)        
        self.top_splitter.addWidget(frame)
        self.current_addon = self.addon_list[0]
        self.current_addon.show_itself()
        self.top_splitter.addWidget(self.browser_widget)
        self.top_splitter.setSizes([100, 200])

        handle_width = 6

        # https://stackoverflow.com/questions/2545577/qsplitter-becoming-undistinguishable-between-qwidget-and-qtabwidget
        self.top_splitter.setOpaqueResize(False)
        self.top_splitter.setChildrenCollapsible(False)
        self.top_splitter.setHandleWidth(handle_width)

        splitter2 = QSplitter(Qt.Vertical)
        splitter2.addWidget(self.top_splitter)
        splitter2.addWidget(self.jupyter_widget)
        splitter2.setOpaqueResize(False)
        splitter2.setChildrenCollapsible(False)
        splitter2.setHandleWidth(handle_width)

        hbox.addWidget(splitter2)

        self.frame.setLayout(hbox)
Ejemplo n.º 2
0
    def initUI(self):
        """Override."""
        ctrl_layout = QHBoxLayout()
        AT = Qt.AlignTop
        ctrl_layout.addWidget(self._roi_ctrl_widget)
        ctrl_layout.addWidget(self._roi_fom_ctrl_widget, alignment=AT)
        ctrl_layout.addWidget(self._roi_hist_ctrl_widget, alignment=AT)
        ctrl_layout.addWidget(self._roi_norm_ctrl_widget, alignment=AT)
        ctrl_layout.addWidget(self._roi_proj_ctrl_widget, alignment=AT)

        subview_splitter = QSplitter(Qt.Vertical)
        subview_splitter.setHandleWidth(9)
        subview_splitter.setChildrenCollapsible(False)
        subview_splitter.addWidget(self._roi_proj_plot)
        subview_splitter.addWidget(self._roi_hist)

        view_splitter = QSplitter(Qt.Horizontal)
        view_splitter.setHandleWidth(9)
        view_splitter.setChildrenCollapsible(False)
        view_splitter.addWidget(self._corrected)
        view_splitter.addWidget(subview_splitter)

        layout = QVBoxLayout()
        layout.addWidget(view_splitter)
        layout.addLayout(ctrl_layout)
        self.setLayout(layout)
Ejemplo n.º 3
0
    def sig_to_stems_clicked(self, row):
        signature = self.sig_to_stems_major_table.item(row, 0).text()
        print(signature)
        signature = tuple(signature.split(SEP_SIG))

        stems = sorted(self.lexicon.signatures_to_stems()[signature])
        number_of_stems_per_column = 5

        # create a master list of sublists, where each sublist contains k stems
        # k = number_of_stems_per_column
        stem_rows = list()
        stem_row = list()

        for i, stem in enumerate(stems, 1):
            stem_row.append(stem)
            if not i % number_of_stems_per_column:
                stem_rows.append(stem_row)
                stem_row = list()
        if stem_row:
            stem_rows.append(stem_row)

        # set up the minor table as table widget
        sig_to_stems_minor_table = QTableWidget()
        sig_to_stems_minor_table.horizontalHeader().hide()
        sig_to_stems_minor_table.verticalHeader().hide()
        sig_to_stems_minor_table.clear()
        sig_to_stems_minor_table.setRowCount(len(stem_rows))
        sig_to_stems_minor_table.setColumnCount(number_of_stems_per_column)

        # fill in the minor table
        for row, stem_row in enumerate(stem_rows):
            for col, stem in enumerate(stem_row):
                item = QTableWidgetItem(stem)
                sig_to_stems_minor_table.setItem(row, col, item)

        sig_to_stems_minor_table.resizeColumnsToContents()

        minor_table_title = QLabel('{} (number of stems: {})'
                                   .format(SEP_SIG.join(signature), len(stems))
                                   )

        minor_table_widget_with_title = QWidget()
        layout = QVBoxLayout()
        layout.addWidget(minor_table_title)
        layout.addWidget(sig_to_stems_minor_table)
        minor_table_widget_with_title.setLayout(layout)

        new_display = QSplitter(Qt.Horizontal)
        new_display.setHandleWidth(10)
        new_display.setChildrenCollapsible(False)

        new_display.addWidget(self.sig_to_stems_major_table)
        new_display.addWidget(minor_table_widget_with_title)
        new_display_width = self.majorDisplay.width() / 2
        new_display.setSizes(
            [new_display_width * 0.4, new_display_width * 0.6])

        self.load_main_window(major_display=new_display)
        self.status.clearMessage()
        self.status.showMessage('{} selected'.format(signature))
Ejemplo n.º 4
0
    def initUI(self):
        """Override."""
        ctrl_widget = QWidget()
        ctrl_layout = QHBoxLayout()
        AT = Qt.AlignTop
        ctrl_layout.addWidget(self._roi_ctrl_widget)
        ctrl_layout.addWidget(self._roi_fom_ctrl_widget, alignment=AT)
        ctrl_layout.addWidget(self._roi_hist_ctrl_widget, alignment=AT)
        ctrl_layout.addWidget(self._roi_norm_ctrl_widget, alignment=AT)
        ctrl_layout.addWidget(self._roi_proj_ctrl_widget, alignment=AT)
        ctrl_layout.setContentsMargins(1, 1, 1, 1)
        ctrl_widget.setLayout(ctrl_layout)
        ctrl_widget.setFixedHeight(
            self._roi_proj_ctrl_widget.minimumSizeHint().height())

        subview_splitter = QSplitter(Qt.Vertical)
        subview_splitter.setHandleWidth(9)
        subview_splitter.setChildrenCollapsible(False)
        subview_splitter.addWidget(self._roi_proj_plot)
        subview_splitter.addWidget(self._roi_hist)

        view_splitter = QSplitter(Qt.Horizontal)
        view_splitter.setHandleWidth(9)
        view_splitter.setChildrenCollapsible(False)
        view_splitter.addWidget(self._corrected)
        view_splitter.addWidget(subview_splitter)
        view_splitter.setSizes([1e6, 1e6])

        layout = QVBoxLayout()
        layout.addWidget(view_splitter)
        layout.addWidget(ctrl_widget)
        layout.setContentsMargins(1, 1, 1, 1)
        self.setLayout(layout)
Ejemplo n.º 5
0
    def initUI(self):
        """Override."""
        self._monitor_tb.setTabPosition(QTabWidget.TabPosition.South)
        self._monitor_tb.addTab(self._avail_src_view, "Available sources")
        self._monitor_tb.addTab(self._process_mon_view, "Process monitor")

        splitter = QSplitter(Qt.Vertical)
        splitter.setHandleWidth(self.SPLITTER_HANDLE_WIDTH)
        splitter.setChildrenCollapsible(False)
        splitter.addWidget(self._con_view)
        splitter.addWidget(self._src_view)
        splitter.addWidget(self._monitor_tb)
        splitter.setStretchFactor(0, 3)
        splitter.setStretchFactor(1, 1)
        h = splitter.sizeHint().height()
        splitter.setSizes([0.1 * h, 0.6 * h, 0.3 * h])

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

        self._con_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)

        self._src_view.expandToDepth(1)
        self._src_view.resizeColumnToContents(0)
        self._src_view.resizeColumnToContents(1)
Ejemplo n.º 6
0
class MrhReadWidget(QWidget):
    """Tab READ."""

    def __init__(self):
        super().__init__()
        self.datatable = MrhReadTable()
        self.datatable.setObjectName('read_datatable')
        self.viewoptiongroup = MrhReadViewGroupbox()
        self.viewoptiongroup.setObjectName('read_viewoption')
        self.info_textedit = QTextEdit()
        self.info_textedit.setObjectName('read_info_textedit')
        self.functiongroup = MrhReadFunctionGroupbox()
        self.functiongroup.setObjectName('read_functiongroup')
        self.memooptiongroup = MrhReferenceMemoGroupbox()
        self.memooptiongroup.setObjectName('read_memooption')
        self.vlayout = QVBoxLayout()
        self.splitter = QSplitter()
        self.splitter.setChildrenCollapsible(False)
        self.splitter.setOrientation(Qt.Vertical)
        self.splitter.addWidget(self.datatable)
        self.splitter.addWidget(self.info_textedit)
        self.splitter.addWidget(self.memooptiongroup)
        self.vlayout.addWidget(self.splitter)
        self.vlayout.addWidget(self.viewoptiongroup)
        self.vlayout.addWidget(self.functiongroup)
        self.setLayout(self.vlayout)
Ejemplo n.º 7
0
    def sig_to_stems_clicked(self, row):
        signature = self.sig_to_stems_major_table.item(row, 0).text()
        print(signature)
        signature = tuple(signature.split(SEP_SIG))

        stems = sorted(self.lexicon.signatures_to_stems()[signature])
        number_of_stems_per_column = 5

        # create a master list of sublists, where each sublist contains k stems
        # k = number_of_stems_per_column
        stem_rows = list()
        stem_row = list()

        for i, stem in enumerate(stems, 1):
            stem_row.append(stem)
            if not i % number_of_stems_per_column:
                stem_rows.append(stem_row)
                stem_row = list()
        if stem_row:
            stem_rows.append(stem_row)

        # set up the minor table as table widget
        sig_to_stems_minor_table = QTableWidget()
        sig_to_stems_minor_table.horizontalHeader().hide()
        sig_to_stems_minor_table.verticalHeader().hide()
        sig_to_stems_minor_table.clear()
        sig_to_stems_minor_table.setRowCount(len(stem_rows))
        sig_to_stems_minor_table.setColumnCount(number_of_stems_per_column)

        # fill in the minor table
        for row, stem_row in enumerate(stem_rows):
            for col, stem in enumerate(stem_row):
                item = QTableWidgetItem(stem)
                sig_to_stems_minor_table.setItem(row, col, item)

        sig_to_stems_minor_table.resizeColumnsToContents()

        minor_table_title = QLabel('{} (number of stems: {})'
                                   .format(SEP_SIG.join(signature), len(stems)))

        minor_table_widget_with_title = QWidget()
        layout = QVBoxLayout()
        layout.addWidget(minor_table_title)
        layout.addWidget(sig_to_stems_minor_table)
        minor_table_widget_with_title.setLayout(layout)

        new_display = QSplitter(Qt.Horizontal)
        new_display.setHandleWidth(10)
        new_display.setChildrenCollapsible(False)

        new_display.addWidget(self.sig_to_stems_major_table)
        new_display.addWidget(minor_table_widget_with_title)
        new_display_width = self.majorDisplay.width() / 2
        new_display.setSizes(
            [new_display_width * 0.4, new_display_width * 0.6])

        self.load_main_window(major_display=new_display)
        self.status.clearMessage()
        self.status.showMessage('{} selected'.format(signature))
Ejemplo n.º 8
0
	def getSplitter(self, index):
		splitter = QSplitter(Qt.Horizontal)
		# Give both boxes a minimum size so the minimumSizeHint will be
		# ignored when splitter.setSizes is called below
		for widget in self.editBoxes[index], self.previewBoxes[index]:
			widget.setMinimumWidth(125)
			splitter.addWidget(widget)
		splitter.setSizes((50, 50))
		splitter.setChildrenCollapsible(False)
		return splitter
Ejemplo n.º 9
0
	def getSplitter(self):
		splitter = QSplitter(Qt.Horizontal)
		# Give both boxes a minimum size so the minimumSizeHint will be
		# ignored when splitter.setSizes is called below
		for widget in self.editBox, self.previewBox:
			widget.setMinimumWidth(125)
			splitter.addWidget(widget)
		splitter.setSizes((50, 50))
		splitter.setChildrenCollapsible(False)
		return splitter
Ejemplo n.º 10
0
class Sidebar(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle = "Longway Sidebar"
        self.setGeometry(1920 - 180, 0, 180, 1080 - 50)

        self.layout = QVBoxLayout()
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.setStyleSheet(
            'background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop: 0 #4d77b3, stop: 0.015 #67a6d0, stop: 0.03 #5578ac, stop: 1 #3364af)')

        self.gadgets = []

        self.splitter = QSplitter(Qt.Vertical)
        self.splitter.setChildrenCollapsible(False)
        self.splitter.setMinimumHeight(20)

        self.clickable = QPushButton()
        self.clickable.setStyleSheet('''
                                    QPushButton {
                                        background-color: #00ffffff;
                                    }
                                    QPushButton:pressed {
                                        background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 #111166, stop: 1 #222277);     
                                    }
                                    QPushButton:hover {
                                        background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop: 0 #4685d9, stop: 1 #89c8f1);
                                    }; height: 24px; border: none; color:white; font-weight: bold;''')
        self.clickable.setText('Add Gadget')

        self.clickable.clicked.connect(self.add_gadget)
        for i in range(0):
            r = randint(0, 1)
            if r == 0:
                self.gadgets.append(GadgetText())
            elif r == 1:
                self.gadgets.append(GadgetWeb())
            # self.gadgets[-1].setFrameShape(QFrame.StyledPanel)
            self.splitter.addWidget(self.gadgets[-1])

        self.layout.addWidget(self.splitter)
        self.layout.addWidget(self.clickable)
        self.setLayout(self.layout)

        self.setWindowFlags(Qt.FramelessWindowHint)
        self.show()

    def add_gadget(self):
        self.gadgets.append(GadgetText())
        self.splitter.addWidget(self.gadgets[-1])
Ejemplo n.º 11
0
    def init_ui(self):
        main_box = QHBoxLayout()
        hsplitter = QSplitter(Qt.Horizontal)

        self.init_peer_list()
        hsplitter.addWidget(self.peer_list)

        message_box = self.create_message_box()
        hsplitter.addWidget(message_box)
        hsplitter.setChildrenCollapsible(False)
        hsplitter.setSizes([1000, 3000])

        main_box.addWidget(hsplitter, 1)
        main_box.setContentsMargins(0, 0, 0, 0)
        self.setLayout(main_box)
    def initUI(self):
        """Override."""
        view_tab = QTabWidget()
        view_tab.setTabPosition(QTabWidget.TabPosition.South)
        view_tab.addTab(self._corrected, "Corrected")
        view_tab.addTab(self._q_view, "Momentum transfer (q)")

        view_splitter = QSplitter()
        view_splitter.setChildrenCollapsible(False)
        view_splitter.addWidget(view_tab)
        view_splitter.addWidget(self._azimuthal_integ_1d_curve)

        layout = QVBoxLayout()
        layout.addWidget(view_splitter)
        layout.addWidget(self._ctrl_widget)
        self.setLayout(layout)
Ejemplo n.º 13
0
class ProfitView(AccountSheetView):
    def _setup(self):
        self._setupUi()
        self.sheet = self.psheet = ProfitSheet(self.model.istatement,
                                               view=self.treeView)
        self.graph = self.pgraph = Chart(self.model.pgraph,
                                         view=self.graphView)
        self.piechart = Chart(self.model.pie, view=self.pieChart)

    def _setupUi(self):
        self.resize(558, 447)
        self.mainLayout = QVBoxLayout(self)
        self.mainLayout.setSpacing(0)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.splitterView = QSplitter()
        self.splitterView.setChildrenCollapsible(False)
        self.splitterView.setOrientation(Qt.Vertical)
        self.subSplitterView = QSplitter()
        self.subSplitterView.setChildrenCollapsible(False)
        self.treeView = TreeView(self)
        self.treeView.setAcceptDrops(True)
        self.treeView.setFrameShape(QFrame.NoFrame)
        self.treeView.setFrameShadow(QFrame.Plain)
        self.treeView.setEditTriggers(QAbstractItemView.EditKeyPressed
                                      | QAbstractItemView.SelectedClicked)
        self.treeView.setDragEnabled(True)
        self.treeView.setDragDropMode(QAbstractItemView.InternalMove)
        self.treeView.setUniformRowHeights(True)
        self.treeView.setAllColumnsShowFocus(True)
        self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.treeView.header().setStretchLastSection(False)
        self.subSplitterView.addWidget(self.treeView)
        self.pieChart = PieChartView(self)
        self.pieChart.setMinimumSize(300, 0)
        self.subSplitterView.addWidget(self.pieChart)
        self.splitterView.addWidget(self.subSplitterView)
        self.graphView = BarGraphView(self)
        self.graphView.setMinimumSize(0, 200)
        self.splitterView.addWidget(self.graphView)
        self.splitterView.setStretchFactor(0, 1)
        self.splitterView.setStretchFactor(1, 0)
        self.subSplitterView.setStretchFactor(0, 1)
        self.subSplitterView.setStretchFactor(1, 0)
        self.mainLayout.addWidget(self.splitterView)
Ejemplo n.º 14
0
class NetWorthView(AccountSheetView):
    def _setup(self):
        self._setupUi()
        self.sheet = self.nwsheet = NetWorthSheet(self.model.bsheet, view=self.treeView)
        self.graph = self.nwgraph = Chart(self.model.nwgraph, view=self.graphView)
        self.piechart = Chart(self.model.pie, view=self.pieChart)

    def _setupUi(self):
        self.resize(558, 447)
        self.mainLayout = QVBoxLayout(self)
        self.mainLayout.setSpacing(0)
        self.mainLayout.setContentsMargins(0, 0, 0, 0)
        self.splitterView = QSplitter()
        self.splitterView.setChildrenCollapsible(False)
        self.splitterView.setOrientation(Qt.Vertical)
        self.subSplitterView = QSplitter()
        self.subSplitterView.setChildrenCollapsible(False)
        self.treeView = TreeView(self)
        self.treeView.setAcceptDrops(True)
        self.treeView.setFrameShape(QFrame.NoFrame)
        self.treeView.setFrameShadow(QFrame.Plain)
        self.treeView.setEditTriggers(QAbstractItemView.EditKeyPressed|QAbstractItemView.SelectedClicked)
        self.treeView.setDragEnabled(True)
        self.treeView.setDragDropMode(QAbstractItemView.InternalMove)
        self.treeView.setUniformRowHeights(True)
        self.treeView.setAllColumnsShowFocus(True)
        self.treeView.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.treeView.header().setStretchLastSection(False)
        self.subSplitterView.addWidget(self.treeView)
        self.pieChart = PieChartView(self)
        self.pieChart.setMinimumSize(300, 0)
        self.subSplitterView.addWidget(self.pieChart)
        self.splitterView.addWidget(self.subSplitterView)
        self.graphView = LineGraphView(self)
        self.graphView.setMinimumSize(0, 200)
        self.splitterView.addWidget(self.graphView)
        self.splitterView.setStretchFactor(0, 1)
        self.splitterView.setStretchFactor(1, 0)
        self.subSplitterView.setStretchFactor(0, 1)
        self.subSplitterView.setStretchFactor(1, 0)
        self.mainLayout.addWidget(self.splitterView)
Ejemplo n.º 15
0
    def decorate(splitter: QSplitter, index: int = 1):

        gripLength = 35
        gripWidth = 1  # may need to be 1 or 2 depending on theme
        gripSpacing = 0
        grips = 3

        splitter.setOpaqueResize(False)
        splitter.setChildrenCollapsible(False)
        splitter.setHandleWidth(7)
        handle = splitter.handle(index)
        orientation = splitter.orientation()
        layout = QHBoxLayout(handle)
        layout.setSpacing(gripSpacing)
        layout.setContentsMargins(0, 0, 0, 0)

        if orientation == Qt.Horizontal:
            for i in range(grips):
                line = QFrame(handle)
                line.setMinimumSize(gripWidth, gripLength)
                line.setMaximumSize(gripWidth, gripLength)
                line.setLineWidth(gripWidth)
                line.setFrameShape(line.StyledPanel)
                line.setStyleSheet("border: 1px solid lightgray;")
                layout.addWidget(line)
        else:
            # center the vertical grip by adding spacers before and after
            layout.addStretch()
            vBox = QVBoxLayout()

            for i in range(grips):
                line = QFrame(handle)
                line.setMinimumSize(gripLength, gripWidth)
                line.setMaximumSize(gripLength, gripWidth)
                line.setFrameShape(line.StyledPanel)
                line.setStyleSheet("border: 1px solid lightgray;")
                vBox.addWidget(line)
            layout.addLayout(vBox)
            layout.addStretch()
Ejemplo n.º 16
0
class PlaylistTab(QWidget):
    '''A widget for administrating the all models of the playlist data.'''
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setObjectName('tabPlaylist')
        self.horizontalLayout = QHBoxLayout(self)
        self.horizontalLayout.setObjectName("horizontalLayoutPlaylist")
        self.horizontalLayout.setContentsMargins(6, 6, 6, 6)
        self.horizontalLayout.setSpacing(6)

        self.horizontalSplitter = QSplitter(self)
        self.horizontalSplitter.setObjectName("horizontalSplitterPlaylist")
        self.horizontalSplitter.setChildrenCollapsible(False)

        # Artists/Albums/Games Layout Widget
        self.verticalSplitter = QSplitter(Qt.Vertical, self)
        self.verticalSplitter.setObjectName('verticalSplitterPlaylist')
        self.verticalSplitter.setChildrenCollapsible(False)
        sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(1)
        sizePolicy.setHeightForWidth(
            self.verticalSplitter.sizePolicy().hasHeightForWidth())
        self.verticalSplitter.setSizePolicy(sizePolicy)
        self.horizontalSplitter.addWidget(self.verticalSplitter)

        # Create the group box widgets for the playlist items
        self.groupBoxArtists = ArtistGroupBox(self)
        self.groupBoxAlbums = AlbumGroupBox(self)
        self.groupBoxGames = GameGroupBox(self)
        self.groupBoxSongs = SongGroupBox(self)

        # Add group boxes to layouts
        self.verticalSplitter.addWidget(self.groupBoxArtists)
        self.verticalSplitter.addWidget(self.groupBoxAlbums)
        self.verticalSplitter.addWidget(self.groupBoxGames)

        self.horizontalSplitter.addWidget(self.groupBoxSongs)
        self.horizontalLayout.addWidget(self.horizontalSplitter)
Ejemplo n.º 17
0
 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
Ejemplo n.º 18
0
class MrhClassifyWidget(QWidget):
    """Tab Classify."""

    def __init__(self):
        super().__init__()
        self.treelist = MrhClassifyTree()
        self.treelist.setObjectName('classify_treelist')
        self.info_textedit = QTextEdit()
        self.info_textedit.setObjectName('classify_info_textedit')
        self.memooptiongroup = MrhReferenceMemoGroupbox()
        self.memooptiongroup.setObjectName('classify_memooption')
        self.functiongroup = MrhClassifyFunctionGroupbox()
        self.functiongroup.setObjectName('classify_functiongroup')
        self.vlayout = QVBoxLayout()
        self.splitter = QSplitter()
        self.splitter.setChildrenCollapsible(False)
        self.splitter.setOrientation(Qt.Vertical)
        self.splitter.addWidget(self.treelist)
        self.splitter.addWidget(self.info_textedit)
        self.splitter.addWidget(self.memooptiongroup)
        self.vlayout.addWidget(self.splitter)
        self.vlayout.addWidget(self.functiongroup)
        self.setLayout(self.vlayout)
Ejemplo n.º 19
0
class MainWindow(QMainWindow):
    def __init__(self, screen_height, screen_width, version, parent=None):
        super(MainWindow, self).__init__(parent)

        self.screen_width = screen_width
        self.screen_height = screen_height
        self.version = version

        # basic main window settings
        self.resize(MAIN_WINDOW_WIDTH, MAIN_WINDOW_HEIGHT)
        self.setWindowTitle('Linguistica {}'.format(self.version))

        # lexicon and lexicon tree
        self.lexicon = None
        self.lexicon_tree = None
        self.initialize_lexicon_tree()

        # set up major display, parameter window, then load main window
        self.majorDisplay = QWidget()
        self.parameterWindow = QWidget()
        self.load_main_window()

        # 'File' menu and actions
        select_corpus_action = self.create_action(text='&Select corpus...',
                                                  slot=self.corpus_dir_dialog,
                                                  tip='Select a corpus file',
                                                  shortcut='Ctrl+N')
        select_wordlist_action = self.create_action(text='&Select wordlist...',
                                                    slot=self.wordlist_dir_dialog,
                                                    tip='Select a wordlist file',
                                                    shortcut='Ctrl+W')
        run_file_action = self.create_action(text='&Run...',
                                             slot=self.run_file,
                                             tip='Run the input file',
                                             shortcut='Ctrl+D')
        parameters_action = self.create_action(text='&Parameters...',
                                               slot=self.parameters_dialog,
                                               tip='Change parameters',
                                               shortcut='Ctrl+P')

        file_menu = self.menuBar().addMenu('&File')
        file_menu.addAction(select_corpus_action)
        file_menu.addAction(select_wordlist_action)
        file_menu.addAction(run_file_action)
        file_menu.addAction(parameters_action)

        self.status = self.statusBar()
        self.status.setSizeGripEnabled(False)
        self.status.showMessage('No input file loaded. To select one: File --> '
                                'Select corpus... or Select wordlist...')

    def initialize_lexicon_tree(self):
        self.lexicon_tree = QTreeWidget()
        self.lexicon_tree.setEnabled(True)
        self.lexicon_tree.setMinimumWidth(TREEWIDGET_WIDTH_MIN)
        self.lexicon_tree.setMaximumWidth(TREEWIDGET_WIDTH_MAX)
        self.lexicon_tree.setMinimumHeight(TREEWIDGET_HEIGHT_MIN)
        self.lexicon_tree.setHeaderLabel('')
        self.lexicon_tree.setItemsExpandable(True)
        # noinspection PyUnresolvedReferences
        self.lexicon_tree.itemClicked.connect(self.tree_item_clicked)

    def create_action(self, text=None, slot=None, tip=None, shortcut=None):
        """
        This create actions for the File menu, things like
        Read Corpus, Rerun Corpus etc
        """
        action = QAction(text, self)
        if shortcut:
            action.setShortcut(shortcut)
        if tip:
            action.setToolTip(tip)
            action.setStatusTip(tip)
        if slot:
            # noinspection PyUnresolvedReferences
            action.triggered.connect(slot)
        if shortcut:
            # noinspection PyUnresolvedReferences
            QShortcut(QKeySequence(shortcut), self).activated.connect(slot)
        return action

    def _get_filename_from_dialog(self, ftype='input'):
        self.determine_last_file()
        if self.last_file_path and self.last_file_type == ftype:
            open_dir = self.last_file_path
        else:
            open_dir = os.getcwd()
        # noinspection PyTypeChecker,PyCallByClass
        fname = QFileDialog.getOpenFileName(self,
                                            'Select the {} file'.format(ftype),
                                            open_dir)
        process_all_gui_events()

        # HACK: fname is supposed to be a string (at least according to the
        # PyQt5 documentation), but for some reason fname is a tuple.
        # So we need this hack to make sure that fname is a string of a filename
        # -- Jackson Lee, 2015/06/22

        # update: it's turned out that this behavior is due to compatibility
        # between PyQt and PySide. The "tuple" behavior is in line with the
        # newer API2 for PyQt. (PyQt on python 3 uses API2 by default.)
        # more here: http://srinikom.github.io/pyside-bz-archive/343.html
        # so perhaps we keep our current hack for now?
        # -- Jackson Lee, 2015/08/24

        if fname and any(fname) and (type(fname) is tuple):
            return fname[0]
        else:
            # if this hack isn't needed somehow...
            return fname

    def corpus_dir_dialog(self):
        """
        Pop up the "open a file" dialog and ask for which corpus text file
        to use
        """
        self.corpus_filename = self._get_filename_from_dialog(ftype='corpus')

        process_all_gui_events()

        if type(self.corpus_filename) != str:
            return

        # note that self.corpus_filename is an absolute full path
        self.corpus_name = os.path.basename(self.corpus_filename)
        self.corpus_stem_name = Path(self.corpus_name).stem

        self.lexicon = read_corpus(self.corpus_filename)
        self.initialize_lexicon_tree()
        self.load_main_window(major_display=QWidget(),
                              parameter_window=QWidget())
        process_all_gui_events()

        self.status.clearMessage()
        self.status.showMessage(
            'Corpus selected: {}'.format(self.corpus_filename))

    def wordlist_dir_dialog(self):
        """
        Pop up the "open a file" dialog and ask for which corpus text file
        to use
        """
        self.corpus_filename = self._get_filename_from_dialog(ftype='wordlist')

        process_all_gui_events()

        if type(self.corpus_filename) != str:
            return

        # note that self.corpus_filename is an absolute full path
        self.corpus_name = os.path.basename(self.corpus_filename)
        self.corpus_stem_name = Path(self.corpus_name).stem

        self.lexicon = read_wordlist(self.corpus_filename)
        self.initialize_lexicon_tree()
        self.load_main_window(major_display=QWidget(),
                              parameter_window=QWidget())
        process_all_gui_events()

        self.status.clearMessage()
        self.status.showMessage(
            'Wordlist selected: {}'.format(self.corpus_filename))

    def parameters_dialog(self):
        if self.lexicon is None:
            warning = QMessageBox()
            warning.setIcon(QMessageBox.Warning)
            warning.setText('Parameters can only be accessed when an input '
                            'file is specified.')
            warning.setWindowTitle('No input file selected')
            warning.setStandardButtons(QMessageBox.Ok)
            warning.exec_()
            return

        process_all_gui_events()

        parameters = self.lexicon.parameters()
        dialog = QDialog()
        layout = QVBoxLayout()
        layout.addWidget(
            QLabel('Filename: {}'.format(Path(self.corpus_filename).name)))
        file_type = 'Wordlist' if self.lexicon.file_is_wordlist else 'Corpus'
        layout.addWidget(QLabel('Type: {}'.format(file_type)))

        grid = QGridLayout()
        self.parameter_spinboxes = [QSpinBox() for _ in range(len(parameters))]

        for i, parameter_name in enumerate(sorted(parameters.keys())):
            self.parameter_spinboxes[i].setObjectName(parameter_name)
            self.parameter_spinboxes[i].setRange(
                *PARAMETERS_RANGES[parameter_name])
            self.parameter_spinboxes[i].setValue(parameters[parameter_name])
            self.parameter_spinboxes[i].setSingleStep(1)
            # noinspection PyUnresolvedReferences
            self.parameter_spinboxes[i].valueChanged.connect(
                self.update_parameter)

            grid.addWidget(QLabel(parameter_name), i, 0)
            grid.addWidget(self.parameter_spinboxes[i], i, 1)
            grid.addWidget(QLabel(PARAMETERS_HINTS[parameter_name]), i, 2)

        layout.addLayout(grid)

        reset_button = QPushButton()
        reset_button.setText('&Reset')
        # noinspection PyUnresolvedReferences
        reset_button.clicked.connect(self.reset_parameters)

        spacer = QWidget()  # just for padding in tool_bar
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        tool_bar = QHBoxLayout()
        tool_bar.addWidget(spacer)  # so that the buttons are right-aligned
        tool_bar.addWidget(reset_button)

        layout.addLayout(tool_bar)

        dialog.setLayout(layout)
        dialog.setWindowTitle('Parameters')
        dialog.exec_()

    def reset_parameters(self):
        self.lexicon.use_default_parameters()

        for i, (_, value) in \
                enumerate(sorted(self.lexicon.parameters().items())):
            self.parameter_spinboxes[i].setValue(value)

    def update_parameter(self):
        for i in range(len(self.lexicon.parameters())):
            parameter_name, new_value = \
                self.parameter_spinboxes[i].objectName(), \
                self.parameter_spinboxes[i].value()
            self.lexicon.change_parameters(**{parameter_name: new_value})

    def update_progress(self, progress_text, target_percentage):
        """
        Update the progress dialog. This function is triggered by the
        "progress_signal" emitted from the linguistica component worker thread.
        """
        self.progressDialog.setLabelText(progress_text)
        self.progressDialog.setValue(target_percentage)
        process_all_gui_events()

    # noinspection PyProtectedMember
    def run_file(self):
        if self.lexicon is None:
            warning = QMessageBox()
            warning.setIcon(QMessageBox.Warning)
            warning.setText('No input file is selected.')
            warning.setWindowTitle('Error')
            warning.setStandardButtons(QMessageBox.Ok)
            warning.exec_()
            return

        self.status.clearMessage()
        self.status.showMessage('Running the file {} now...'
                                .format(self.corpus_name))

        print('\nInput file in use:\n{}\n'.format(self.corpus_filename),
              flush=True)

        # set up the Linguistica components worker
        # The worker is a QThread. We spawn this thread, and the linguistica
        # components run on this new thread but not the main thread for the GUI.
        # This makes the GUI still responsive
        # while the long and heavy running process of
        # the Linguistica components is ongoing.

        self.lxa_worker = LinguisticaWorker(self.lexicon)
        self.lxa_worker.progress_signal.connect(self.update_progress)

        # set up progress dialog

        process_all_gui_events()
        self.progressDialog = QProgressDialog()
        self.progressDialog.setRange(0, 100)  # it's like from 0% to 100%
        self.progressDialog.setLabelText('Initializing...')
        self.progressDialog.setValue(0)  # initialize as 0 (= 0%)
        self.progressDialog.setWindowTitle(
            'Processing {}'.format(self.corpus_name))
        self.progressDialog.setCancelButton(None)
        self.progressDialog.resize(400, 100)
        process_all_gui_events()

        self.progressDialog.show()

        # We disable the "cancel" button
        # Setting up a "cancel" mechanism may not be a good idea,
        # since it would probably involve killing the linguistica component
        # worker at *any* point of its processing.
        # This may have undesirable effects (e.g., freezing the GUI) -- BAD!

        # make sure all GUI stuff up to this point has been processed before
        # doing the real work of running the Lxa components
        process_all_gui_events()

        # Now the real work begins here!
        self.lxa_worker.start()

        process_all_gui_events()

        self.lexicon = self.lxa_worker.get_lexicon()

        print('\nAll Linguistica components run for the file', flush=True)
        self.status.clearMessage()
        self.status.showMessage('{} processed'.format(self.corpus_name))

        self.load_main_window(major_display=QWidget(),
                              parameter_window=QWidget())
        self.populate_lexicon_tree()
        self.update_last_file()
        process_all_gui_events()

        # display corpus name (in the tree header label)
        file_type = 'wordlist' if self.lexicon.file_is_wordlist else 'corpus'
        header_label = 'File: {}\nFile type: {}\n\n# word types: {:,}\n'.format(
            self.corpus_name, file_type, self.lexicon.number_of_word_types())
        if file_type == 'corpus':
            header_label += '# word tokens: {:,}\n'.format(
                self.lexicon.number_of_word_tokens())
        self.lexicon_tree.setHeaderLabel(header_label)

    @staticmethod
    def ensure_config_dir_exists():
        if not os.path.isdir(CONFIG_DIR):
            os.mkdir(CONFIG_DIR)

    def determine_last_file(self):
        self.last_file_path = None
        self.last_file_type = None
        self.last_file_encoding = None

        if not os.path.isfile(CONFIG_LAST_FILE):
            return

        with open(CONFIG_LAST_FILE, encoding='utf8') as f:
            config_last_file = json.load(f)

        self.last_file_path = config_last_file['last_file_path']
        self.last_file_type = config_last_file['last_file_type']
        self.last_file_encoding = config_last_file['last_file_encoding']

    def update_last_file(self):
        self.ensure_config_dir_exists()
        with open(CONFIG_LAST_FILE, 'w', encoding='utf8') as f:
            if self.lexicon.file_is_wordlist:
                file_type = 'wordlist'
            else:
                file_type = 'corpus'

            config = {'last_file_path': self.lexicon.file_abspath,
                      'last_file_type': file_type,
                      'last_file_encoding': self.lexicon.encoding,
                      }

            json.dump(config, f)

    def populate_lexicon_tree(self):
        self.lexicon_tree.clear()
        process_all_gui_events()

        # wordlist
        ancestor = QTreeWidgetItem(self.lexicon_tree, [WORDLIST])
        self.lexicon_tree.expandItem(ancestor)

        # word ngrams
        ancestor = QTreeWidgetItem(self.lexicon_tree, [WORD_NGRAMS])
        self.lexicon_tree.expandItem(ancestor)
        for item_str in [BIGRAMS, TRIGRAMS]:
            item = QTreeWidgetItem(ancestor, [item_str])
            self.lexicon_tree.expandItem(item)

        # signatures
        ancestor = QTreeWidgetItem(self.lexicon_tree, [SIGNATURES])
        self.lexicon_tree.expandItem(ancestor)
        for item in [SIGS_TO_STEMS, WORDS_TO_SIGS]:
            self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item]))

        # tries
        ancestor = QTreeWidgetItem(self.lexicon_tree, [TRIES])
        self.lexicon_tree.expandItem(ancestor)
        for item in [WORDS_AS_TRIES, SUCCESSORS, PREDECESSORS]:
            self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item]))

        # phonology
        ancestor = QTreeWidgetItem(self.lexicon_tree, [PHONOLOGY])
        self.lexicon_tree.expandItem(ancestor)
        for item in [PHONES, BIPHONES, TRIPHONES]:
            self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item]))

        # manifolds
        ancestor = QTreeWidgetItem(self.lexicon_tree, [MANIFOLDS])
        self.lexicon_tree.expandItem(ancestor)
        for item in [WORD_NEIGHBORS, VISUALIZED_GRAPH]:
            self.lexicon_tree.expandItem(QTreeWidgetItem(ancestor, [item]))

        self.status.clearMessage()
        self.status.showMessage('Navigation tree populated')
        print('Lexicon navigation tree populated', flush=True)

    def load_main_window(self, major_display=None, parameter_window=None):
        """
        Refresh the main window for the updated display content
        (most probably after a click or some event is triggered)
        """
        # get sizes of the three major PyQt objects
        major_display_size = self.majorDisplay.size()
        parameter_window_size = self.parameterWindow.size()
        lexicon_tree_size = self.lexicon_tree.size()

        if major_display:
            self.majorDisplay = major_display
        if parameter_window:
            self.parameterWindow = parameter_window

        # apply sizes to the major three objects
        self.majorDisplay.resize(major_display_size)
        self.parameterWindow.resize(parameter_window_size)
        self.lexicon_tree.resize(lexicon_tree_size)

        # set up:
        # 1) main splitter (b/w lexicon-tree+parameter window and major display)
        # 2) minor splitter (b/w lexicon-tree and parameter window)
        self.mainSplitter = QSplitter(Qt.Horizontal)
        self.mainSplitter.setHandleWidth(10)
        self.mainSplitter.setChildrenCollapsible(False)

        self.minorSplitter = QSplitter(Qt.Vertical)
        self.minorSplitter.setHandleWidth(10)
        self.minorSplitter.setChildrenCollapsible(False)

        self.minorSplitter.addWidget(self.lexicon_tree)
        self.minorSplitter.addWidget(self.parameterWindow)

        self.mainSplitter.addWidget(self.minorSplitter)
        self.mainSplitter.addWidget(self.majorDisplay)

        self.setCentralWidget(self.mainSplitter)

    def sig_to_stems_clicked(self, row):
        signature = self.sig_to_stems_major_table.item(row, 0).text()
        print(signature)
        signature = tuple(signature.split(SEP_SIG))

        stems = sorted(self.lexicon.signatures_to_stems()[signature])
        number_of_stems_per_column = 5

        # create a master list of sublists, where each sublist contains k stems
        # k = number_of_stems_per_column
        stem_rows = list()
        stem_row = list()

        for i, stem in enumerate(stems, 1):
            stem_row.append(stem)
            if not i % number_of_stems_per_column:
                stem_rows.append(stem_row)
                stem_row = list()
        if stem_row:
            stem_rows.append(stem_row)

        # set up the minor table as table widget
        sig_to_stems_minor_table = QTableWidget()
        sig_to_stems_minor_table.horizontalHeader().hide()
        sig_to_stems_minor_table.verticalHeader().hide()
        sig_to_stems_minor_table.clear()
        sig_to_stems_minor_table.setRowCount(len(stem_rows))
        sig_to_stems_minor_table.setColumnCount(number_of_stems_per_column)

        # fill in the minor table
        for row, stem_row in enumerate(stem_rows):
            for col, stem in enumerate(stem_row):
                item = QTableWidgetItem(stem)
                sig_to_stems_minor_table.setItem(row, col, item)

        sig_to_stems_minor_table.resizeColumnsToContents()

        minor_table_title = QLabel('{} (number of stems: {})'
                                   .format(SEP_SIG.join(signature), len(stems)))

        minor_table_widget_with_title = QWidget()
        layout = QVBoxLayout()
        layout.addWidget(minor_table_title)
        layout.addWidget(sig_to_stems_minor_table)
        minor_table_widget_with_title.setLayout(layout)

        new_display = QSplitter(Qt.Horizontal)
        new_display.setHandleWidth(10)
        new_display.setChildrenCollapsible(False)

        new_display.addWidget(self.sig_to_stems_major_table)
        new_display.addWidget(minor_table_widget_with_title)
        new_display_width = self.majorDisplay.width() / 2
        new_display.setSizes(
            [new_display_width * 0.4, new_display_width * 0.6])

        self.load_main_window(major_display=new_display)
        self.status.clearMessage()
        self.status.showMessage('{} selected'.format(signature))

    def unavailable_for_wordlist(self):
        self.load_main_window(major_display=QWidget(),
                              parameter_window=QWidget())
        self.status.showMessage('')
        warning = QMessageBox()
        warning.setIcon(QMessageBox.Warning)
        warning.setText('Unavailable for a wordlist')
        warning.setWindowTitle('Error')
        warning.setStandardButtons(QMessageBox.Ok)
        warning.exec_()

    def tree_item_clicked(self, item):
        """
        Trigger the appropriate action when something in the lexicon tree
        is clicked, and update the major display plus parameter window
        """
        item_str = item.text(0)

        if item_str in {WORD_NGRAMS, SIGNATURES, TRIES, PHONOLOGY, MANIFOLDS}:
            return

        print('loading', item_str, flush=True)

        self.status.clearMessage()
        self.status.showMessage('Loading {}...'.format(item_str))

        new_display = None
        new_parameter_window = None

        if item_str == WORDLIST:
            new_display = self.create_major_display_table(
                self.lexicon.word_phonology_dict().items(),
                key=lambda x: x[1].count, reverse=True,
                headers=['Word', 'Count', 'Frequency', 'Phones',
                         'Unigram plog', 'Avg unigram plog',
                         'Bigram plog', 'Avg bigram plog'],
                row_cell_functions=[
                    lambda x: x[0], lambda x: x[1].count,
                    lambda x: x[1].frequency,
                    lambda x: ' '.join(x[1].phones),
                    lambda x: x[1].unigram_plog,
                    lambda x: x[1].avg_unigram_plog,
                    lambda x: x[1].bigram_plog,
                    lambda x: x[1].avg_bigram_plog],
                cutoff=0)

        elif item_str == BIGRAMS:
            if self.lexicon.file_is_wordlist:
                self.unavailable_for_wordlist()
                return
            new_display = self.create_major_display_table(
                self.lexicon.word_bigram_counter().items(),
                key=lambda x: x[1], reverse=True,
                headers=['Bigram', 'Count'],
                row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]),
                                    lambda x: x[1]],
                cutoff=2000)

        elif item_str == TRIGRAMS:
            if self.lexicon.file_is_wordlist:
                self.unavailable_for_wordlist()
                return
            new_display = self.create_major_display_table(
                self.lexicon.word_trigram_counter().items(),
                key=lambda x: x[1], reverse=True,
                headers=['Trigram', 'Count'],
                row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]),
                                    lambda x: x[1]],
                cutoff=2000)

        elif item_str == SIGS_TO_STEMS:
            self.sig_to_stems_major_table = self.create_major_display_table(
                self.lexicon.signatures_to_stems().items(),
                key=lambda x: len(x[1]), reverse=True,
                headers=['Signature', 'Stem count', 'A few stems'],
                row_cell_functions=[lambda x: SEP_SIG.join(x[0]),
                                    lambda x: len(x[1]),
                                    lambda x: ', '.join(sorted(x[1])[:2]) +
                                              ', ...'],
                cutoff=0)
            # noinspection PyUnresolvedReferences
            self.sig_to_stems_major_table.cellClicked.connect(
                self.sig_to_stems_clicked)
            new_display = self.sig_to_stems_major_table

        elif item_str == WORDS_TO_SIGS:
            new_display = self.create_major_display_table(
                self.lexicon.words_to_signatures().items(),
                key=lambda x: len(x[1]), reverse=True,
                headers=['Word', 'Signature count', 'Signatures'],
                row_cell_functions=[lambda x: x[0],
                                    lambda x: len(x[1]),
                                    lambda x: ', '.join([SEP_SIG.join(sig)
                                                         for sig in
                                                         sorted(x[1])])],
                cutoff=2000)

        elif item_str == WORDS_AS_TRIES:
            words = self.lexicon.broken_words_left_to_right().keys()
            words_to_tries = dict()
            # key: word (str)
            # value: tuple of (str, str)
            # for left-to-right and right-to-left tries

            for word in words:
                l_r = ' '.join(self.lexicon.broken_words_left_to_right()[word])
                r_l = ' '.join(self.lexicon.broken_words_right_to_left()[word])
                words_to_tries[word] = (l_r, r_l)  # left-right, right-left

            new_display = self.create_major_display_table(
                words_to_tries.items(),
                key=lambda x: x[0], reverse=False,
                headers=['Word', 'Reversed word',
                         'Left-to-right trie', 'Right-to-left trie'],
                row_cell_functions=[lambda x: x[0], lambda x: x[0][::-1],
                                    lambda x: x[1][0], lambda x: x[1][1]],
                cutoff=0, set_text_alignment=[(3, Qt.AlignRight)])

        elif item_str == SUCCESSORS:
            new_display = self.create_major_display_table(
                self.lexicon.successors().items(),
                key=lambda x: len(x[1]), reverse=True,
                headers=['String', 'Successor count', 'Successors'],
                row_cell_functions=[lambda x: x[0],
                                    lambda x: len(x[1]),
                                    lambda x: ', '.join(sorted(x[1]))],
                cutoff=0)

        elif item_str == PREDECESSORS:
            new_display = self.create_major_display_table(
                self.lexicon.predecessors().items(),
                key=lambda x: len(x[1]), reverse=True,
                headers=['String', 'Predecessor count', 'Predecessors'],
                row_cell_functions=[lambda x: x[0],
                                    lambda x: len(x[1]),
                                    lambda x: ', '.join(sorted(x[1]))],
                cutoff=0)

        elif item_str == PHONES:
            new_display = self.create_major_display_table(
                self.lexicon.phone_dict().items(),
                key=lambda x: x[1].count, reverse=True,
                headers=['Phone', 'Count', 'Frequency', 'Plog'],
                row_cell_functions=[lambda x: x[0],
                                    lambda x: x[1].count,
                                    lambda x: x[1].frequency,
                                    lambda x: x[1].plog],
                cutoff=0)

        elif item_str == BIPHONES:
            new_display = self.create_major_display_table(
                self.lexicon.biphone_dict().items(),
                key=lambda x: x[1].count, reverse=True,
                headers=['Biphone', 'Count', 'Frequency',
                         'Mutual information (MI)', 'Weighted MI'],
                row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]),
                                    lambda x: x[1].count,
                                    lambda x: x[1].frequency,
                                    lambda x: x[1].MI,
                                    lambda x: x[1].weighted_MI],
                cutoff=0)

        elif item_str == TRIPHONES:
            new_display = self.create_major_display_table(
                self.lexicon.phone_trigram_counter().items(),
                key=lambda x: x[1], reverse=True,
                headers=['Triphone', 'Count'],
                row_cell_functions=[lambda x: SEP_NGRAM.join(x[0]),
                                    lambda x: x[1]],
                cutoff=0)

        elif item_str == WORD_NEIGHBORS:
            if self.lexicon.file_is_wordlist:
                self.unavailable_for_wordlist()
                return
            word_to_freq = self.lexicon.word_unigram_counter()
            new_display = self.create_major_display_table(
                self.lexicon.words_to_neighbors().items(),
                key=lambda x: word_to_freq[x[0]], reverse=True,
                headers=['Word', 'Word count', 'Neighbors'],
                row_cell_functions=[lambda x: x[0],
                                    lambda x: word_to_freq[x[0]],
                                    lambda x: ' '.join(x[1])],
                cutoff=0)

        elif item_str == VISUALIZED_GRAPH:
            if self.lexicon.file_is_wordlist:
                self.unavailable_for_wordlist()
                return

            graph_width = self.screen_width - TREEWIDGET_WIDTH_MAX - 50
            graph_height = self.screen_height - 70
            html_name = 'show_manifold.html'

            manifold_name = '{}_manifold.json'.format(self.corpus_stem_name)
            manifold_filename = os.path.join(CONFIG_DIR, manifold_name)
            print('manifold_filename', manifold_filename)

            manifold_json_data = json_graph.node_link_data(
                self.lexicon.neighbor_graph())
            json.dump(manifold_json_data, open(manifold_filename, 'w'))

            viz_html = os.path.join(CONFIG_DIR, html_name)
            print('viz_html', viz_html)

            # write the show_manifold html file
            with open(viz_html, 'w') as f:
                print(SHOW_MANIFOLD_HTML.format(os.path.dirname(__file__),
                                                graph_width, graph_height,
                                                manifold_filename), file=f)

            url = Path(viz_html).as_uri()
            print('url:', url)

            new_display = QWebView()
            new_display.setUrl(QUrl(url))

        self.load_main_window(major_display=new_display,
                              parameter_window=new_parameter_window)

        self.status.clearMessage()
        self.status.showMessage('{} selected'.format(item_str))

    @staticmethod
    def create_major_display_table(input_iterable,
                                   key=lambda x: x, reverse=False,
                                   headers=None, row_cell_functions=None,
                                   cutoff=0,
                                   set_text_alignment=None):
        """
        This is a general function for creating a tabular display for the
        major display.
        """

        if not input_iterable:
            print('Warning: input is empty', flush=True)
            return

        if not hasattr(input_iterable, '__iter__'):
            print('Warning: input is not an iterable', flush=True)
            return

        number_of_headers = len(headers)
        number_of_columns = len(row_cell_functions)

        if number_of_headers != number_of_columns:
            print('headers and cell functions don\'t match', flush=True)
            return

        len_input = len(input_iterable)

        table_widget = QTableWidget()
        table_widget.clear()
        table_widget.setSortingEnabled(False)

        # set up row count
        if cutoff and cutoff < len_input:
            actual_cutoff = cutoff
        else:
            actual_cutoff = len_input

        table_widget.setRowCount(actual_cutoff)

        # set up column count and table headers
        table_widget.setColumnCount(number_of_headers)
        table_widget.setHorizontalHeaderLabels(headers)

        # fill in the table
        for row, x in enumerate(double_sorted(input_iterable, key=key,
                                              reverse=reverse)):
            for col, fn in enumerate(row_cell_functions):
                cell = fn(x)

                if isinstance(cell, (int, float)):
                    # cell is numeric
                    item = QTableWidgetItem()
                    item.setData(Qt.EditRole, cell)
                else:
                    # cell is not numeric
                    item = QTableWidgetItem(cell)

                if set_text_alignment:
                    for align_col, alignment in set_text_alignment:
                        if col == align_col:
                            item.setTextAlignment(alignment)

                table_widget.setItem(row, col, item)

            if not row < actual_cutoff:
                break

        table_widget.setSortingEnabled(True)
        table_widget.resizeColumnsToContents()

        return table_widget
Ejemplo n.º 20
0
class Sidebar(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle = "Longway Sidebar"
        self.setGeometry(1920 - 180, 0, 180, 1080 - 32)

        self.color_gradient_left = QColor(100, 100, 100, 50)
        self.color_gradient_right = QColor(0, 0, 0, 255)

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

        # self.layout.addWidget(QWidget())
        self.setLayout(self.layout)

        # Splitter for storing tiles
        self.tiles = []

        self.splitter = QSplitter(Qt.Vertical)
        self.splitter.setChildrenCollapsible(False)
        self.splitter.setMinimumHeight(20)
        self.splitter.setStyleSheet('''
                                    QSplitter::handle:vertical {
                                        height: 1px;
                                        background: black
                                    }''')
        self.layout.addWidget(self.splitter)

        self.setWindowFlags(Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground)

        # Starting the show!
        self.addTile(TileSlideshow())
        self.addTile(TileTest())
        self.addTile(Tile())
        self.addTile(Tile())
        self.addTile(QWidget())
        self.show()

    def paintEvent(self, event):
        # Initializing QPainter
        qp = QPainter()
        qp.begin(self)
        qp.setRenderHint(QPainter.Antialiasing)
        sidebar_rect = self.geometry()
        # Gradient
        gradient = QLinearGradient(0, 0, sidebar_rect.width(), 0)
        gradient.setColorAt(0.0, self.color_gradient_left)
        gradient.setColorAt(1.0, self.color_gradient_right)

        qp.setBrush(QBrush(gradient))
        # qp.setPen(Qt.white)
        qp.drawRect(0, 0, sidebar_rect.width(), sidebar_rect.height())

        # Glass highlight
        qp.setBrush(QBrush(Qt.white))
        qp.setPen(QPen(QBrush(Qt.white), 0.01))
        qp.setOpacity(0.1)

        qppath = QPainterPath()
        qppath.moveTo(sidebar_rect.width() * 0.2, 0)
        qppath.quadTo(sidebar_rect.width() * 0.3, sidebar_rect.height()
                      * 0.5, sidebar_rect.width() * 0.2, sidebar_rect.height() - 1)
        qppath.lineTo(0, sidebar_rect.height())
        qppath.lineTo(0, 0)
        qp.setClipPath(qppath)
        qp.drawRect(1, 1, sidebar_rect.width() - 1, sidebar.height() - 1)

        # Left border highlight
        qp.setOpacity(1.0)
        gradient = QLinearGradient(0, 0, 2, 0)
        gradient.setColorAt(0.0, QColor(255, 255, 255, 80))
        gradient.setColorAt(1.0, QColor(0, 0, 0, 0))

        qp.setBrush(QBrush(gradient))
        # qp.setPen(Qt.transparent)
        qp.drawRect(0, 0, 8, sidebar_rect.height())

        qp.end()

    def addTile(self, tile):
        self.tiles.append(tile)
        self.splitter.addWidget(self.tiles[-1])
Ejemplo n.º 21
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     
     sp = QSizePolicy()
     sp.setHorizontalPolicy(QSizePolicy.Expanding)
     sp.setVerticalPolicy(QSizePolicy.Expanding)
     sp.setVerticalStretch(1)
     
     wb_city_data = load_workbook(filename=r'DSTU-H_B_V.1.1-27_2010.xlsx', 
                                  read_only=True, data_only=True)
     self.__ws_city_I_ser_m_sj = wb_city_data['Лист1']
     self.__ws_city_t_z = wb_city_data['Т2. Температура зовн. пов.']
     
     self.__cityChoice = QComboBox()
     self.__cityChoice.addItems(c[0].value for c in 
                                self.__ws_city_I_ser_m_sj['A6':'A605'] if c[0].value)
     
     self.__q_0Edit = KwNumberEdit(.5)
     self.__V_budEdit = KwNumberEdit(480)
     
     self.__NEdit = QSpinBox()
     self.__NEdit.setRange(1, 999)
     self.__NEdit.setSuffix(' чол')
     self.__NEdit.setValue(6)
     
     self.__t_gvEdit = QSpinBox()
     self.__t_gvEdit.setRange(25, 70)
     self.__t_gvEdit.setSingleStep(5)
     self.__t_gvEdit.setSuffix(' \N{DEGREE CELSIUS}')
     self.__t_gvEdit.setValue(45)
     
     self.__a_gvEdit = QSpinBox()
     self.__a_gvEdit.setRange(15, 99)
     self.__a_gvEdit.setSuffix(' л/добу')
     self.__a_gvEdit.setValue(35)
     
     self.__f_zEdit = KwNumberEdit(.8)
     self.__varthetaEdit = KwNumberEdit(1.7)
     self.__S_0Edit = KwNumberEdit(1.723)
     self.__F_rEdit = KwNumberEdit(1)
     self.__eta_0Edit = KwNumberEdit(0.813)
     self.__ULEdit = KwNumberEdit(4.6)
     self.__aEdit = KwNumberEdit(.007)
     self.__bEdit = KwNumberEdit(1.27E-5)
     self.__c_paEdit = KwNumberEdit(3.16)
     
     self.__P_tpEdit = KwNumberEdit(14.1)
     self.__epsilon_tpEdit = KwNumberEdit(5.5)
     self.__epsilon_elEdit = KwNumberEdit(.88)
     self.__P_elEdit = KwNumberEdit(2.6)
     
     self.__t_co_1Edit = QSpinBox()
     self.__t_co_1Edit.setRange(25, 70)
     self.__t_co_1Edit.setSingleStep(5)
     self.__t_co_1Edit.setSuffix(' \N{DEGREE CELSIUS}')
     self.__t_co_1Edit.setValue(35)
     
     self.__t_co_2Edit = QSpinBox()
     self.__t_co_2Edit.setRange(20, 60)
     self.__t_co_2Edit.setSingleStep(5)
     self.__t_co_2Edit.setSuffix(' \N{DEGREE CELSIUS}')
     self.__t_co_2Edit.setValue(30)
     
     self.__eta_KEdit = KwNumberEdit(.93)
     self.__Q_n_rEdit = KwNumberEdit(35600)
     self.__c_gazEdit = KwNumberEdit(.55)
     self.__c_elEdit = KwNumberEdit(.25)
     
     self.__q_gruEdit = KwNumberEdit(21)
     
     self.__d_gruEdit = QSpinBox()
     self.__d_gruEdit.setRange(5, 99)
     self.__d_gruEdit.setSuffix('  мм')
     self.__d_gruEdit.setValue(25)
     
     self.__l_0_gruEdit = KwNumberEdit(1.7)
     
     calcButton = QPushButton('Визначити')
     calcButton.setObjectName('calcButton')
     
     self.__outputConsoleBrowser = KwConsoleBrowser(setup={'font': {'name': 
           QFont('Hack').family(), 'size': 8}, 'color': 'rgb(255, 255, 255)', 
           'background_color': 'rgba(0, 0, 0, 218)', 'line_wrap': False})
     self.__outputConsoleBrowser.setSizePolicy(sp)
     
     graphWidget = QMainWindow()
     graphWidget.setMinimumWidth(380)
     
     self.__graphCanvas = self.__createFigureCanvas()
     self.__graphNavigationToolbar = NavigationToolbar(self.__graphCanvas, self)
     graphWidget.addToolBar(Qt.TopToolBarArea, self.__graphNavigationToolbar)
     
     graphWidget.setCentralWidget(self.__graphCanvas)
     
     cityLayout = QFormLayout()
     cityLayout.addRow('Місто:', self.__cityChoice)
     
     soGroup = QGroupBox('Для системи опалення (СО):')
     soInputLayout = QFormLayout(soGroup)
     
     soInputLayout.addRow('Питома потужність тепловтрат, q<sub>0</sub>, '
                          'Вт/(м<sup>3</sup>\N{MIDDLE DOT}\N{DEGREE CELSIUS}):', 
                          self.__q_0Edit)
     soInputLayout.addRow("Об'єм будинку по зовнішніх обмірах, V<sub>буд</sub>, "
                          "м<sup>3</sup>:", self.__V_budEdit)
     
     sgvGroup = QGroupBox(u'Для системи гарячого водопостачання (СГК):')
     sgvInputLayout = QFormLayout(sgvGroup)
     sgvInputLayout.addRow('Кількість мешканців у будинку, N:', self.__NEdit)
     sgvInputLayout.addRow('Температура гарячої води, t<sub>гв</sub>:', self.__t_gvEdit)
     sgvInputLayout.addRow('Добова витрата гарячої води на 1 особу, a<sub>гв</sub>:',
                           self.__a_gvEdit)
     
     sgkGroup = QGroupBox('Для системи геліоколекторів (СГК):')
     sgkInputLayout = QFormLayout(sgkGroup)
     sgkInputLayout.addRow('Ступінь заміщення тепловтрат СГВ, f<sub>з</sub>:', self.__f_zEdit)
     sgkInputLayout.addRow('Параметр, \u03D1:', self.__varthetaEdit)
     sgkInputLayout.addRow('Площа 1-го геліоколектора, S<sub>0</sub>, м<sup>2</sup>:',
                           self.__S_0Edit)
     sgkInputLayout.addRow('F<sub>r</sub>:', self.__F_rEdit)
     sgkInputLayout.addRow('Оптичний ККД, \N{GREEK SMALL LETTER ETA}:', self.__eta_0Edit)
     sgkInputLayout.addRow('Коефіцієнт тепловтрат, UL, Вт/(м<sup>2</sup>)'
                           '\N{MIDDLE DOT}\N{DEGREE CELSIUS}):', self.__ULEdit)
     sgkInputLayout.addRow('a:', self.__aEdit)
     sgkInputLayout.addRow('b:', self.__bEdit)
     sgkInputLayout.addRow('c<sub>pa</sub>, кДж/(кг\N{MIDDLE DOT}\N{DEGREE CELSIUS}):',
                           self.__c_paEdit)
     
     tpGroup = QGroupBox('Для теплової помпи (ТП):')
     tpInputLayout = QFormLayout(tpGroup)
     tpInputLayout.addRow('Теплова потужність, P<sub>тп</sub>, кВт:', self.__P_tpEdit)
     tpInputLayout.addRow('Тепловий к.к.д, \N{GREEK SMALL LETTER EPSILON}'
                          '<sub>тп</sub>', self.__epsilon_tpEdit)
     tpInputLayout.addRow('Електричний к.к.д., \N{GREEK SMALL LETTER EPSILON}'
                          '<sub>ел</sub>:', self.__epsilon_elEdit)
     tpInputLayout.addRow('Електрична потужність, P<sub>ел</sub>, кВт:', self.__P_elEdit)
     tpInputLayout.addRow('Т-ра нагрітої води для СО підлоги, t<sub>co 1</sub>:',
                          self.__t_co_1Edit)
     tpInputLayout.addRow('Т-ра охолодженої води для СО підлоги, t<sub>co 2</sub>:',
                          self.__t_co_2Edit)
     tpInputLayout.addRow('К.к.д. згоряння палива, eta_K:', self.__eta_KEdit)
     tpInputLayout.addRow('Нижча теплота згоряння палива, Q<sub>n r</sub>, кДж/м<sup>3</sup>:',
                          self.__Q_n_rEdit)
     tpInputLayout.addRow('Вартість 1 м<sup>3</sup> газу, c<sub>газ</sub>, грн/м<sup>3</sup>:',
                          self.__c_gazEdit)
     tpInputLayout.addRow('Вартість 1 кВт\N{MIDDLE DOT}год, c<sub>ел</sub>, '
                          'грн/м<sup>3</sup>:', self.__c_elEdit)
     
     gruGroup = QGroupBox('Для ґрунту і контуру СО підлоги:')
     gruInputEdit = QFormLayout(gruGroup)
     gruInputEdit.addRow('Питома тепловіддача ґрунту, q<sub>ґр</sub>, '
                         'Вт/м<sup>2</sup>:', self.__q_gruEdit)
     gruInputEdit.addRow('Внутрішній діаметр, d, мм:', self.__d_gruEdit)
     gruInputEdit.addRow('Питома довжина тепловідбору, l<sub>0</sub>, '
                         'м/м<sup>2</sup>:', self.__l_0_gruEdit)
     
     inputScrollArea = QScrollArea()
     inputScrollArea.setWidgetResizable(True)
     inputScrollArea.setSizePolicy(sp)
     
     inputDataPanel = QWidget()
     inputDataLayout = QVBoxLayout(inputDataPanel)
     
     inputDataLayout.addLayout(cityLayout)
     inputDataLayout.addWidget(soGroup)
     inputDataLayout.addWidget(sgvGroup)
     inputDataLayout.addWidget(sgkGroup)
     inputDataLayout.addWidget(tpGroup)
     inputDataLayout.addWidget(gruGroup)
     
     inputScrollArea.setWidget(inputDataPanel)
     
     inputWidget = QWidget()
     inputLayout = QFormLayout(inputWidget)
     #inputLayout.setContentsMargins(0, 0, 0, 0)
     inputLayout.setRowWrapPolicy(QFormLayout.WrapAllRows)
     inputLayout.addRow('Вхідні дані:', inputScrollArea)
     inputLayout.addWidget(calcButton)
     
     consoleViewWidget = QWidget()
     consoleViewLayout = QFormLayout(consoleViewWidget)
     consoleViewLayout.setRowWrapPolicy(QFormLayout.WrapAllRows)
     consoleViewLayout.addRow(u'Результати:', self.__outputConsoleBrowser)
     
     contentSplitter = QSplitter(Qt.Horizontal)
     contentSplitter.setStretchFactor(0, 1)
     contentSplitter.setStretchFactor(1, 0)
     contentSplitter.setSizes([350, 380])
     contentSplitter.setChildrenCollapsible(False)
     
     ioSplitter = QSplitter(Qt.Vertical)
     ioSplitter.setStretchFactor(0, 1)
     ioSplitter.setStretchFactor(1, 0)
     ioSplitter.setSizes([200, 320])
     ioSplitter.setChildrenCollapsible(False)
     ioSplitter.setMinimumWidth(380)
     
     ioSplitter.addWidget(inputWidget)
     ioSplitter.addWidget(consoleViewWidget)
     
     contentSplitter.addWidget(ioSplitter)
     contentSplitter.addWidget(graphWidget)
     
     self.setCentralWidget(contentSplitter)
     
     self.resize(1200, 640)
     
     # <<<
     for inputValueWidget in inputWidget.findChildren((QAbstractSpinBox,
                                                       KwNumberEdit)):
         inputValueWidget.valueChanged.connect(self._reset_output_data)
     
     for inputValueWidget in inputWidget.findChildren(QComboBox):
         inputValueWidget.activated.connect(self._reset_output_data)
     
     calcButton.clicked.connect(self.calc_script)
Ejemplo n.º 22
0
class BlockEditor(QWidget, MooseWidget):
    """
    The complete editing widget for a Block.
    The input file will only change when "Apply changes" has been clicked.
    Until then all changes only live in the widgets.
    The exceptions to this are the "Clone" and "Remove" buttons which just sends out signals to let
    others do work.
    Signals:
        needBlockList(list): When the type of a parameter references blocks (for example it is a VariableName),
            this is used to update the available options since they can change based on what the user does.
        blockRenamed(object, str): A block has been renamed. This is emitted so that the BlockTree can update the name of the block.
        blockChanged(object): Apply has been clicked for this block.
        cloneBlock(object): The user wants to clone the block we are currently editing.
        removeBlock(object): The user wants to remove the block we are currently editing.
        editingFinished(object): The user is done editing this block. Typically done by closing the window.
    """
    needBlockList = pyqtSignal(list) # list of paths that we need children for
    blockRenamed = pyqtSignal(object, str) # block with changes, old path
    blockChanged = pyqtSignal(object) # block that has changed

    cloneBlock = pyqtSignal(object) # block to clone
    removeBlock = pyqtSignal(object) # block to remove
    editingFinished = pyqtSignal()

    def __init__(self, block, type_to_block_map, **kwds):
        """
        Sets up an editor for a block.
        Input:
            block[BlockInfo]: Block to be edited.
        """
        super(BlockEditor, self).__init__(**kwds)
        self.block = block
        self.comment_edit = CommentEditor()
        self.comment_edit.textChanged.connect(self._blockChanged)
        self.splitter = None
        self.clone_button = None
        self.clone_shortcut = None
        self.remove_button = None
        self.apply_button = None
        self.reset_button = None
        self.new_parameter_button = None
        self.param_editor = None
        self.setWindowTitle(block.path)

        if block.types:
            self.param_editor = ParamsByType(block, type_to_block_map)
        elif block.parameters:
            self.param_editor = ParamsByGroup(block, block.orderedParameters(), type_to_block_map)
        else:
            self.param_editor = ParamsTable(block, block.orderedParameters(), type_to_block_map)

        self.param_editor.needBlockList.connect(self.needBlockList)
        self.param_editor.changed.connect(self._blockChanged)
        self.param_editor.blockRenamed.connect(self.blockRenamed)

        self._createButtons()
        self.applyChanges()
        self._current_commands = []
        self._command_index = 0
        self.user_params = []

        self.splitter = QSplitter(self)
        self.splitter.setOrientation(Qt.Vertical)
        self.splitter.setChildrenCollapsible(False)
        self.splitter.addWidget(self.param_editor)
        self.splitter.addWidget(self.comment_edit)
        self.splitter.setStretchFactor(0,2)
        self.splitter.setStretchFactor(1,1)
        self.top_layout = WidgetUtils.addLayout(vertical=True)
        self.top_layout.addWidget(self.splitter)
        self.top_layout.addLayout(self.button_layout)
        self.setLayout(self.top_layout)

        self.setup()

    def _blockChanged(self, enabled=True):
        """
        Sets the Apply and Reset buttons based on enabled.
        Input:
            enabled[bool]: Whether to set the buttons to enabled
        """
        self.apply_button.setEnabled(enabled)
        self.reset_button.setEnabled(enabled)
        self.setWindowTitle(self.block.path)

    def setWatchedBlockList(self, path, children):
        self.param_editor.setWatchedBlockList(path, children)

    def _createButtons(self):
        """
        Create allowable buttons for this Block.
        This will depend on whether this is a user added block.
        """
        self.button_layout = WidgetUtils.addLayout()

        self.close_button = WidgetUtils.addButton(self.button_layout, self, "Apply && Close", self._applyAndClose)
        self.close_button.setToolTip("Apply any changes and close the window")

        self.apply_button = WidgetUtils.addButton(self.button_layout, self, "Apply", self.applyChanges)
        self.apply_button.setEnabled(False)
        self.apply_button.setToolTip("Apply changes made")

        self.reset_button = WidgetUtils.addButton(self.button_layout, self, "Reset", self.resetChanges)
        self.reset_button.setEnabled(False)
        self.reset_button.setToolTip("Reset changes to when this window was opened")

        self.new_parameter_button = WidgetUtils.addButton(self.button_layout, self, "Add parameter", self.addUserParamPressed)
        self.new_parameter_button.setToolTip("Add a non standard parameter")

        if self.block.user_added:
            self.clone_button = WidgetUtils.addButton(self.button_layout, self, "Clone Block", self._cloneBlock)
            self.clone_shortcut = WidgetUtils.addShortcut(self, "Ctrl+N", self._cloneBlock, shortcut_with_children=True)
            self.clone_button.setToolTip("Clone this block with the same parameters")

            self.remove_button = WidgetUtils.addButton(self.button_layout, self, "Remove Block", self._removeBlock)
            self.remove_button.setToolTip("Remove this block")

    def _findFreeParamName(self, max_params=1000):
        """
        Find a free parameter name that can be safely added.
        Input:
            max_params[int]: Maximum number of tries before giving up.
        """
        base = "NewParam"
        for i in range(max_params):
            param = '%s%s' % (base, i)
            if self.param_editor.paramValue(param) == None:
                return param

    def addUserParamPressed(self):
        """
        The user wants to add a new user parameter to this block.
        """
        new_name = self._findFreeParamName()
        self._blockChanged()
        self.param_editor.addUserParam(new_name)

    def _cloneBlock(self):
        """
        The user wants to clone this block
        """
        self.cloneBlock.emit(self.block)

    def _removeBlock(self):
        """
        The user wants to remove this block.
        We ask to make sure they want to do this.
        """
        button = QMessageBox.question(self, "Confirm remove", "Are you sure you want to delete %s" % self.block.path, QMessageBox.Yes, QMessageBox.No)
        if button == QMessageBox.Yes:
            self.removeBlock.emit(self.block)
            self.hide()
            self.editingFinished.emit()

    def applyChanges(self):
        """
        Apply any changes the user has made.
        """
        self.block.comments = self.comment_edit.getComments()
        self.param_editor.save()
        self.block.changed_by_user = True
        self._blockChanged(enabled=False)
        self.blockChanged.emit(self.block)

    def _applyAndClose(self):
        """
        Apply any changes the user has made then close the window
        """
        if self.apply_button.isEnabled():
            self.applyChanges()
        self.close()

    def resetChanges(self):
        """
        Reset any changes the user has made.
        """
        self.comment_edit.setComments(self.block.comments)
        self.param_editor.reset()
        self._blockChanged(enabled=False)

    def updateWatchers(self):
        """
        This should be called after creating a BlockEditor.
        This isn't called in the constructor because the caller
        will typically need to hook up the needBlockList signal first.
        """
        self.param_editor.updateWatchers()

    def closeEvent(self, event):
        """
        The user is done editing.
        """
        self.editingFinished.emit()
Ejemplo n.º 23
0
class Listspace(QSplitter, ViewManager):
    """
    Class implementing the listspace viewmanager class.
    
    @signal changeCaption(str) emitted if a change of the caption is necessary
    @signal editorChanged(str) emitted when the current editor has changed
    @signal editorChangedEd(Editor) emitted when the current editor has changed
    @signal lastEditorClosed() emitted after the last editor window was closed
    @signal editorOpened(str) emitted after an editor window was opened
    @signal editorOpenedEd(Editor) emitted after an editor window was opened
    @signal editorClosed(str) emitted just before an editor window gets closed
    @signal editorClosedEd(Editor) emitted just before an editor window gets
        closed
    @signal editorRenamed(str) emitted after an editor was renamed
    @signal editorRenamedEd(Editor) emitted after an editor was renamed
    @signal editorSaved(str) emitted after an editor window was saved
    @signal editorSavedEd(Editor) emitted after an editor window was saved
    @signal checkActions(Editor) emitted when some actions should be checked
        for their status
    @signal cursorChanged(Editor) emitted after the cursor position of the
        active window has changed
    @signal breakpointToggled(Editor) emitted when a breakpoint is toggled.
    @signal bookmarkToggled(Editor) emitted when a bookmark is toggled.
    @signal syntaxerrorToggled(Editor) emitted when a syntax error is toggled.
    @signal previewStateChanged(bool) emitted to signal a change in the
        preview state
    @signal editorLanguageChanged(Editor) emitted to signal a change of an
        editors language
    @signal editorTextChanged(Editor) emitted to signal a change of an
        editor's text
    @signal editorLineChanged(str,int) emitted to signal a change of an
        editor's current line (line is given one based)
    """
    changeCaption = pyqtSignal(str)
    editorChanged = pyqtSignal(str)
    editorChangedEd = pyqtSignal(Editor)
    lastEditorClosed = pyqtSignal()
    editorOpened = pyqtSignal(str)
    editorOpenedEd = pyqtSignal(Editor)
    editorClosed = pyqtSignal(str)
    editorClosedEd = pyqtSignal(Editor)
    editorRenamed = pyqtSignal(str)
    editorRenamedEd = pyqtSignal(Editor)
    editorSaved = pyqtSignal(str)
    editorSavedEd = pyqtSignal(Editor)
    checkActions = pyqtSignal(Editor)
    cursorChanged = pyqtSignal(Editor)
    breakpointToggled = pyqtSignal(Editor)
    bookmarkToggled = pyqtSignal(Editor)
    syntaxerrorToggled = pyqtSignal(Editor)
    previewStateChanged = pyqtSignal(bool)
    editorLanguageChanged = pyqtSignal(Editor)
    editorTextChanged = pyqtSignal(Editor)
    editorLineChanged = pyqtSignal(str, int)
    
    def __init__(self, parent):
        """
        Constructor
        
        @param parent parent widget (QWidget)
        """
        self.stacks = []
        
        QSplitter.__init__(self, parent)
        ViewManager.__init__(self)
        self.setChildrenCollapsible(False)
        
        self.viewlist = QListWidget(self)
        policy = self.viewlist.sizePolicy()
        policy.setHorizontalPolicy(QSizePolicy.Ignored)
        self.viewlist.setSizePolicy(policy)
        self.addWidget(self.viewlist)
        self.viewlist.setContextMenuPolicy(Qt.CustomContextMenu)
        self.viewlist.currentRowChanged.connect(self.__showSelectedView)
        self.viewlist.customContextMenuRequested.connect(self.__showMenu)
        
        self.stackArea = QSplitter(self)
        self.stackArea.setChildrenCollapsible(False)
        self.addWidget(self.stackArea)
        self.stackArea.setOrientation(Qt.Vertical)
        stack = StackedWidget(self.stackArea)
        self.stackArea.addWidget(stack)
        self.stacks.append(stack)
        self.currentStack = stack
        stack.currentChanged.connect(self.__currentChanged)
        stack.installEventFilter(self)
        self.setSizes([int(self.width() * 0.2), int(self.width() * 0.8)])
        # 20% for viewlist, 80% for the editors
        self.__inRemoveView = False
        
        self.__initMenu()
        self.contextMenuEditor = None
        self.contextMenuIndex = -1
        
    def __initMenu(self):
        """
        Private method to initialize the viewlist context menu.
        """
        self.__menu = QMenu(self)
        self.__menu.addAction(
            UI.PixmapCache.getIcon("tabClose.png"),
            self.tr('Close'), self.__contextMenuClose)
        self.closeOthersMenuAct = self.__menu.addAction(
            UI.PixmapCache.getIcon("tabCloseOther.png"),
            self.tr("Close Others"),
            self.__contextMenuCloseOthers)
        self.__menu.addAction(
            self.tr('Close All'), self.__contextMenuCloseAll)
        self.__menu.addSeparator()
        self.saveMenuAct = self.__menu.addAction(
            UI.PixmapCache.getIcon("fileSave.png"),
            self.tr('Save'), self.__contextMenuSave)
        self.__menu.addAction(
            UI.PixmapCache.getIcon("fileSaveAs.png"),
            self.tr('Save As...'), self.__contextMenuSaveAs)
        self.__menu.addAction(
            UI.PixmapCache.getIcon("fileSaveAll.png"),
            self.tr('Save All'), self.__contextMenuSaveAll)
        self.__menu.addSeparator()
        self.openRejectionsMenuAct = self.__menu.addAction(
            self.tr("Open 'rejection' file"),
            self.__contextMenuOpenRejections)
        self.__menu.addSeparator()
        self.__menu.addAction(
            UI.PixmapCache.getIcon("print.png"),
            self.tr('Print'), self.__contextMenuPrintFile)
        self.__menu.addSeparator()
        self.copyPathAct = self.__menu.addAction(
            self.tr("Copy Path to Clipboard"),
            self.__contextMenuCopyPathToClipboard)
        
    def __showMenu(self, point):
        """
        Private slot to handle the customContextMenuRequested signal of
        the viewlist.
        
        @param point position to open the menu at (QPoint)
        """
        if self.editors:
            itm = self.viewlist.itemAt(point)
            if itm is not None:
                row = self.viewlist.row(itm)
                self.contextMenuEditor = self.editors[row]
                self.contextMenuIndex = row
                if self.contextMenuEditor:
                    self.saveMenuAct.setEnabled(
                        self.contextMenuEditor.isModified())
                    fileName = self.contextMenuEditor.getFileName()
                    self.copyPathAct.setEnabled(bool(fileName))
                    if fileName:
                        rej = "{0}.rej".format(fileName)
                        self.openRejectionsMenuAct.setEnabled(
                            os.path.exists(rej))
                    else:
                        self.openRejectionsMenuAct.setEnabled(False)
                    
                    self.closeOthersMenuAct.setEnabled(
                        self.viewlist.count() > 1)
                    
                    self.__menu.popup(self.viewlist.mapToGlobal(point))
        
    def canCascade(self):
        """
        Public method to signal if cascading of managed windows is available.
        
        @return flag indicating cascading of windows is available
        """
        return False
        
    def canTile(self):
        """
        Public method to signal if tiling of managed windows is available.
        
        @return flag indicating tiling of windows is available
        """
        return False
    
    def canSplit(self):
        """
        public method to signal if splitting of the view is available.
        
        @return flag indicating splitting of the view is available.
        """
        return True
        
    def tile(self):
        """
        Public method to tile the managed windows.
        """
        pass
        
    def cascade(self):
        """
        Public method to cascade the managed windows.
        """
        pass
        
    def _removeAllViews(self):
        """
        Protected method to remove all views (i.e. windows).
        """
        self.viewlist.clear()
        for win in self.editors:
            for stack in self.stacks:
                if stack.hasEditor(win):
                    stack.removeWidget(win)
                    break
            win.closeIt()
        
    def _removeView(self, win):
        """
        Protected method to remove a view (i.e. window).
        
        @param win editor window to be removed
        """
        self.__inRemoveView = True
        ind = self.editors.index(win)
        itm = self.viewlist.takeItem(ind)
        if itm:
            del itm
        for stack in self.stacks:
            if stack.hasEditor(win):
                stack.removeWidget(win)
                break
        win.closeIt()
        self.__inRemoveView = False
        if ind > 0:
            ind -= 1
        else:
            if len(self.editors) > 1:
                ind = 1
            else:
                return
        stack.setCurrentWidget(stack.firstEditor())
        self._showView(self.editors[ind].parent())
        
        aw = self.activeWindow()
        fn = aw and aw.getFileName() or None
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
            self.editorLineChanged.emit(fn, aw.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(aw)
        
    def _addView(self, win, fn=None, noName="", next=False):
        """
        Protected method to add a view (i.e. window).
        
        @param win editor assembly to be added
        @param fn filename of this editor (string)
        @param noName name to be used for an unnamed editor (string)
        @param next flag indicating to add the view next to the current
            view (bool)
        """
        editor = win.getEditor()
        if fn is None:
            if not noName:
                self.untitledCount += 1
                noName = self.tr("Untitled {0}").format(self.untitledCount)
            self.viewlist.addItem(noName)
            editor.setNoName(noName)
        else:
            txt = os.path.basename(fn)
            if not QFileInfo(fn).isWritable():
                txt = self.tr("{0} (ro)").format(txt)
            itm = QListWidgetItem(txt)
            itm.setToolTip(fn)
            self.viewlist.addItem(itm)
        self.currentStack.addWidget(win)
        self.currentStack.setCurrentWidget(win)
        editor.captionChanged.connect(self.__captionChange)
        editor.cursorLineChanged.connect(self.__cursorLineChanged)
        
        index = self.editors.index(editor)
        self.viewlist.setCurrentRow(index)
        editor.setFocus()
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
            self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(editor)
        
    def __captionChange(self, cap, editor):
        """
        Private method to handle caption change signals from the editor.
        
        Updates the listwidget text to reflect the new caption information.
        
        @param cap Caption for the editor (string)
        @param editor Editor to update the caption for
        """
        fn = editor.getFileName()
        if fn:
            self.setEditorName(editor, fn)
        
    def __cursorLineChanged(self, lineno):
        """
        Private slot to handle a change of the current editor's cursor line.
        
        @param lineno line number of the current editor's cursor (zero based)
        """
        editor = self.sender()
        if editor:
            fn = editor.getFileName()
            if fn:
                self.editorLineChanged.emit(fn, lineno + 1)
        
    def _showView(self, win, fn=None):
        """
        Protected method to show a view (i.e. window).
        
        @param win editor assembly to be shown
        @param fn filename of this editor (string)
        """
        editor = win.getEditor()
        for stack in self.stacks:
            if stack.hasEditor(editor):
                stack.setCurrentWidget(win)
                self.currentStack = stack
                break
        index = self.editors.index(editor)
        self.viewlist.setCurrentRow(index)
        editor.setFocus()
        fn = editor.getFileName()
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
            self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(editor)
        
    def __showSelectedView(self, row):
        """
        Private slot called to show a view selected in the list.
        
        @param row row number of the item clicked on (integer)
        """
        if row != -1:
            self._showView(self.editors[row].parent())
            self._checkActions(self.editors[row])
        
    def activeWindow(self):
        """
        Public method to return the active (i.e. current) window.
        
        @return reference to the active editor
        """
        return self.currentStack.currentWidget()
        
    def showWindowMenu(self, windowMenu):
        """
        Public method to set up the viewmanager part of the Window menu.
        
        @param windowMenu reference to the window menu
        """
        pass
        
    def _initWindowActions(self):
        """
        Protected method to define the user interface actions for window
        handling.
        """
        pass
        
    def setEditorName(self, editor, newName):
        """
        Public method to change the displayed name of the editor.
        
        @param editor editor window to be changed
        @param newName new name to be shown (string)
        """
        if newName:
            currentRow = self.viewlist.currentRow()
            index = self.editors.index(editor)
            txt = os.path.basename(newName)
            if not QFileInfo(newName).isWritable():
                txt = self.tr("{0} (ro)").format(txt)
            itm = self.viewlist.item(index)
            itm.setText(txt)
            itm.setToolTip(newName)
            self.viewlist.setCurrentRow(currentRow)
            self.changeCaption.emit(newName)
            
    def _modificationStatusChanged(self, m, editor):
        """
        Protected slot to handle the modificationStatusChanged signal.
        
        @param m flag indicating the modification status (boolean)
        @param editor editor window changed
        """
        currentRow = self.viewlist.currentRow()
        index = self.editors.index(editor)
        keys = []
        if m:
            keys.append("fileModified.png")
        if editor.hasSyntaxErrors():
            keys.append("syntaxError22.png")
        elif editor.hasWarnings():
            keys.append("warning22.png")
        if not keys:
            keys.append("empty.png")
        self.viewlist.item(index).setIcon(
            UI.PixmapCache.getCombinedIcon(keys))
        self.viewlist.setCurrentRow(currentRow)
        self._checkActions(editor)
        
    def _syntaxErrorToggled(self, editor):
        """
        Protected slot to handle the syntaxerrorToggled signal.
        
        @param editor editor that sent the signal
        """
        currentRow = self.viewlist.currentRow()
        index = self.editors.index(editor)
        keys = []
        if editor.isModified():
            keys.append("fileModified.png")
        if editor.hasSyntaxErrors():
            keys.append("syntaxError22.png")
        elif editor.hasWarnings():
            keys.append("warning22.png")
        if not keys:
            keys.append("empty.png")
        self.viewlist.item(index).setIcon(
            UI.PixmapCache.getCombinedIcon(keys))
        self.viewlist.setCurrentRow(currentRow)
        
        ViewManager._syntaxErrorToggled(self, editor)
        
    def addSplit(self):
        """
        Public method used to split the current view.
        """
        stack = StackedWidget(self.stackArea)
        stack.show()
        self.stackArea.addWidget(stack)
        self.stacks.append(stack)
        self.currentStack = stack
        stack.currentChanged.connect(self.__currentChanged)
        stack.installEventFilter(self)
        if self.stackArea.orientation() == Qt.Horizontal:
            size = self.stackArea.width()
        else:
            size = self.stackArea.height()
        self.stackArea.setSizes(
            [int(size / len(self.stacks))] * len(self.stacks))
        self.splitRemoveAct.setEnabled(True)
        self.nextSplitAct.setEnabled(True)
        self.prevSplitAct.setEnabled(True)
        
    def removeSplit(self):
        """
        Public method used to remove the current split view.
        
        @return flag indicating successfull removal
        """
        if len(self.stacks) > 1:
            stack = self.currentStack
            res = True
            savedEditors = stack.editors[:]
            for editor in savedEditors:
                res &= self.closeEditor(editor)
            if res:
                try:
                    i = self.stacks.index(stack)
                except ValueError:
                    return True
                if i == len(self.stacks) - 1:
                    i -= 1
                self.stacks.remove(stack)
                stack.close()
                self.currentStack = self.stacks[i]
                if len(self.stacks) == 1:
                    self.splitRemoveAct.setEnabled(False)
                    self.nextSplitAct.setEnabled(False)
                    self.prevSplitAct.setEnabled(False)
                return True
        
        return False
        
    def getSplitOrientation(self):
        """
        Public method to get the orientation of the split view.
        
        @return orientation of the split (Qt.Horizontal or Qt.Vertical)
        """
        return self.stackArea.orientation()
        
    def setSplitOrientation(self, orientation):
        """
        Public method used to set the orientation of the split view.
        
        @param orientation orientation of the split
                (Qt.Horizontal or Qt.Vertical)
        """
        self.stackArea.setOrientation(orientation)
        
    def nextSplit(self):
        """
        Public slot used to move to the next split.
        """
        aw = self.activeWindow()
        _hasFocus = aw and aw.hasFocus()
        ind = self.stacks.index(self.currentStack) + 1
        if ind == len(self.stacks):
            ind = 0
        
        self.currentStack = self.stacks[ind]
        if _hasFocus:
            aw = self.activeWindow()
            if aw:
                aw.setFocus()
        
        index = self.editors.index(self.currentStack.currentWidget())
        self.viewlist.setCurrentRow(index)
        
    def prevSplit(self):
        """
        Public slot used to move to the previous split.
        """
        aw = self.activeWindow()
        _hasFocus = aw and aw.hasFocus()
        ind = self.stacks.index(self.currentStack) - 1
        if ind == -1:
            ind = len(self.stacks) - 1
        
        self.currentStack = self.stacks[ind]
        if _hasFocus:
            aw = self.activeWindow()
            if aw:
                aw.setFocus()
        index = self.editors.index(self.currentStack.currentWidget())
        self.viewlist.setCurrentRow(index)
        
    def __contextMenuClose(self):
        """
        Private method to close the selected editor.
        """
        if self.contextMenuEditor:
            self.closeEditorWindow(self.contextMenuEditor)
        
    def __contextMenuCloseOthers(self):
        """
        Private method to close the other editors.
        """
        index = self.contextMenuIndex
        for i in list(range(self.viewlist.count() - 1, index, -1)) + \
                list(range(index - 1, -1, -1)):
            editor = self.editors[i]
            self.closeEditorWindow(editor)
        
    def __contextMenuCloseAll(self):
        """
        Private method to close all editors.
        """
        savedEditors = self.editors[:]
        for editor in savedEditors:
            self.closeEditorWindow(editor)
        
    def __contextMenuSave(self):
        """
        Private method to save the selected editor.
        """
        if self.contextMenuEditor:
            self.saveEditorEd(self.contextMenuEditor)
        
    def __contextMenuSaveAs(self):
        """
        Private method to save the selected editor to a new file.
        """
        if self.contextMenuEditor:
            self.saveAsEditorEd(self.contextMenuEditor)
        
    def __contextMenuSaveAll(self):
        """
        Private method to save all editors.
        """
        self.saveEditorsList(self.editors)
        
    def __contextMenuOpenRejections(self):
        """
        Private slot to open a rejections file associated with the selected
        editor.
        """
        if self.contextMenuEditor:
            fileName = self.contextMenuEditor.getFileName()
            if fileName:
                rej = "{0}.rej".format(fileName)
                if os.path.exists(rej):
                    self.openSourceFile(rej)
        
    def __contextMenuPrintFile(self):
        """
        Private method to print the selected editor.
        """
        if self.contextMenuEditor:
            self.printEditor(self.contextMenuEditor)
        
    def __contextMenuCopyPathToClipboard(self):
        """
        Private method to copy the file name of the selected editor to the
        clipboard.
        """
        if self.contextMenuEditor:
            fn = self.contextMenuEditor.getFileName()
            if fn:
                cb = QApplication.clipboard()
                cb.setText(fn)
        
    def __currentChanged(self, index):
        """
        Private slot to handle the currentChanged signal.
        
        @param index index of the current editor
        """
        if index == -1 or not self.editors:
            return
        
        editor = self.activeWindow()
        if editor is None:
            return
        
        self._checkActions(editor)
        editor.setFocus()
        fn = editor.getFileName()
        if fn:
            self.changeCaption.emit(fn)
            if not self.__inRemoveView:
                self.editorChanged.emit(fn)
                self.editorLineChanged.emit(
                    fn, editor.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(editor)
        
        cindex = self.editors.index(editor)
        self.viewlist.setCurrentRow(cindex)
        
    def eventFilter(self, watched, event):
        """
        Public method called to filter the event queue.
        
        @param watched the QObject being watched
        @param event the event that occurred
        @return flag indicating, if we handled the event
        """
        if event.type() == QEvent.MouseButtonPress and \
           not event.button() == Qt.RightButton:
            switched = True
            if isinstance(watched, QStackedWidget):
                switched = watched is not self.currentStack
                self.currentStack = watched
            elif isinstance(watched, QScintilla.Editor.Editor):
                for stack in self.stacks:
                    if stack.hasEditor(watched):
                        switched = stack is not self.currentStack
                        self.currentStack = stack
                        break
            currentWidget = self.currentStack.currentWidget()
            if currentWidget:
                index = self.editors.index(currentWidget)
                self.viewlist.setCurrentRow(index)
            
            aw = self.activeWindow()
            if aw is not None:
                self._checkActions(aw)
                aw.setFocus()
                fn = aw.getFileName()
                if fn:
                    self.changeCaption.emit(fn)
                    if switched:
                        self.editorChanged.emit(fn)
                        self.editorLineChanged.emit(
                            fn, aw.getCursorPosition()[0] + 1)
                else:
                    self.changeCaption.emit("")
                self.editorChangedEd.emit(aw)
        
        return False
Ejemplo n.º 24
0
class BlockEditor(QWidget, MooseWidget):
    """
    The complete editing widget for a Block.
    The input file will only change when "Apply changes" has been clicked.
    Until then all changes only live in the widgets.
    The exceptions to this are the "Clone" and "Remove" buttons which just sends out signals to let
    others do work.
    Signals:
        needBlockList(list): When the type of a parameter references blocks (for example it is a VariableName),
            this is used to update the available options since they can change based on what the user does.
        blockRenamed(object, str): A block has been renamed. This is emitted so that the BlockTree can update the name of the block.
        blockChanged(object): Apply has been clicked for this block.
        cloneBlock(object): The user wants to clone the block we are currently editing.
        removeBlock(object): The user wants to remove the block we are currently editing.
        editingFinished(object): The user is done editing this block. Typically done by closing the window.
    """
    needBlockList = pyqtSignal(list) # list of paths that we need children for
    blockRenamed = pyqtSignal(object, str) # block with changes, old path
    blockChanged = pyqtSignal(object) # block that has changed

    cloneBlock = pyqtSignal(object) # block to clone
    removeBlock = pyqtSignal(object) # block to remove
    editingFinished = pyqtSignal()

    def __init__(self, block, type_to_block_map, **kwds):
        """
        Sets up an editor for a block.
        Input:
            block[BlockInfo]: Block to be edited.
        """
        super(BlockEditor, self).__init__(**kwds)
        self.block = block
        self.comment_edit = CommentEditor()
        self.comment_edit.textChanged.connect(self._blockChanged)
        self.splitter = None
        self.clone_button = None
        self.clone_shortcut = None
        self.remove_button = None
        self.apply_button = None
        self.reset_button = None
        self.new_parameter_button = None
        self.param_editor = None
        self.setWindowTitle(block.path)

        if block.types:
            self.param_editor = ParamsByType(block, type_to_block_map)
        elif block.parameters:
            self.param_editor = ParamsByGroup(block, block.orderedParameters(), type_to_block_map)
        else:
            self.param_editor = ParamsTable(block, block.orderedParameters(), type_to_block_map)

        self.param_editor.needBlockList.connect(self.needBlockList)
        self.param_editor.changed.connect(self._blockChanged)
        self.param_editor.blockRenamed.connect(self.blockRenamed)

        self._createButtons()
        self.applyChanges()
        self._current_commands = []
        self._command_index = 0
        self.user_params = []

        self.splitter = QSplitter(self)
        self.splitter.setOrientation(Qt.Vertical)
        self.splitter.setChildrenCollapsible(False)
        self.splitter.addWidget(self.param_editor)
        self.splitter.addWidget(self.comment_edit)
        self.splitter.setStretchFactor(0,2)
        self.splitter.setStretchFactor(1,1)
        self.top_layout = WidgetUtils.addLayout(vertical=True)
        self.top_layout.addWidget(self.splitter)
        self.top_layout.addLayout(self.button_layout)
        self.setLayout(self.top_layout)

        self.setup()

    def _blockChanged(self, enabled=True):
        """
        Sets the Apply and Reset buttons based on enabled.
        Input:
            enabled[bool]: Whether to set the buttons to enabled
        """
        self.apply_button.setEnabled(enabled)
        self.reset_button.setEnabled(enabled)
        self.setWindowTitle(self.block.path)

    def setWatchedBlockList(self, path, children):
        self.param_editor.setWatchedBlockList(path, children)

    def _createButtons(self):
        """
        Create allowable buttons for this Block.
        This will depend on whether this is a user added block.
        """
        self.button_layout = WidgetUtils.addLayout()

        if self.block.user_added:
            self.clone_button = WidgetUtils.addButton(self.button_layout, self, "Clone Block", self._cloneBlock)
            self.clone_shortcut = WidgetUtils.addShortcut(self, "Ctrl+N", self._cloneBlock, shortcut_with_children=True)
            self.clone_button.setToolTip("Clone this block with the same parameters")

            self.remove_button = WidgetUtils.addButton(self.button_layout, self, "Remove Block", self._removeBlock)
            self.remove_button.setToolTip("Remove this block")

        self.apply_button = WidgetUtils.addButton(self.button_layout, self, "Apply", self.applyChanges)
        self.apply_button.setEnabled(False)
        self.apply_button.setToolTip("Apply changes made")

        self.reset_button = WidgetUtils.addButton(self.button_layout, self, "Reset", self.resetChanges)
        self.reset_button.setEnabled(False)
        self.reset_button.setToolTip("Reset changes to when this window was opened")

        self.new_parameter_button = WidgetUtils.addButton(self.button_layout, self, "Add parameter", self.addUserParamPressed)
        self.new_parameter_button.setToolTip("Add a non standard parameter")

        self.close_button = WidgetUtils.addButton(self.button_layout, self, "Close", self._applyAndClose)
        self.close_button.setToolTip("Apply any changes and close the window")

    def _findFreeParamName(self, max_params=1000):
        """
        Find a free parameter name that can be safely added.
        Input:
            max_params[int]: Maximum number of tries before giving up.
        """
        base = "NewParam"
        for i in range(max_params):
            param = '%s%s' % (base, i)
            if self.param_editor.paramValue(param) == None:
                return param

    def addUserParamPressed(self):
        """
        The user wants to add a new user parameter to this block.
        """
        new_name = self._findFreeParamName()
        self._blockChanged()
        self.param_editor.addUserParam(new_name)

    def _cloneBlock(self):
        """
        The user wants to clone this block
        """
        self.cloneBlock.emit(self.block)

    def _removeBlock(self):
        """
        The user wants to remove this block.
        We ask to make sure they want to do this.
        """
        button = QMessageBox.question(self, "Confirm remove", "Are you sure you want to delete %s" % self.block.path, QMessageBox.Yes, QMessageBox.No)
        if button == QMessageBox.Yes:
            self.removeBlock.emit(self.block)
            self.hide()
            self.editingFinished.emit()

    def applyChanges(self):
        """
        Apply any changes the user has made.
        """
        self.block.comments = self.comment_edit.getComments()
        self.param_editor.save()
        self._blockChanged(enabled=False)
        self.blockChanged.emit(self.block)

    def _applyAndClose(self):
        """
        Apply any changes the user has made then close the window
        """
        if self.apply_button.isEnabled():
            self.applyChanges()
        self.close()

    def resetChanges(self):
        """
        Reset any changes the user has made.
        """
        self.comment_edit.setComments(self.block.comments)
        self.param_editor.reset()
        self._blockChanged(enabled=False)

    def updateWatchers(self):
        """
        This should be called after creating a BlockEditor.
        This isn't called in the constructor because the caller
        will typically need to hook up the needBlockList signal first.
        """
        self.param_editor.updateWatchers()

    def closeEvent(self, event):
        """
        The user is done editing.
        """
        self.editingFinished.emit()
Ejemplo n.º 25
0
    def __init__(self, parent=None):
        super().__init__(parent)

        self.record = -1
        self.inspected = None
        self.oob_update = False
        prefs = QSettings()
        prefs.beginGroup("/General")
        timeout = prefs.value("/Timeout")
        dark_mode = prefs.value("/DarkMode")
        prefs.endGroup()

        # Instantiate core objects
        self.timeout_timer = QTimer()
        self.timeout_timer.setTimerType(Qt.VeryCoarseTimer)
        self.timeout_timer.setInterval(timeout * 1000)
        self.timeout_timer.setSingleShot(True)
        self.timeout_timer.timeout.connect(self.update_temp_log)
        self.systems = ActionsWidget(LogSource.SYSTEM)
        self.systems.acted.connect(self.log_item)
        self.events = ActionsWidget(LogSource.EVENT)
        self.events.acted.connect(self.log_item)

        self.compass = Compass()
        self.compass_widget = QWidget()
        compass_layout = QHBoxLayout()
        self.compass_widget.setLayout(compass_layout)
        compass_layout.addWidget(self.compass)
        self.compass.angle_event.connect(self.log_item)

        self.exact_angle = ExactAngle()
        self.exact_angle_widget = QWidget()
        exact_angle_layout = QHBoxLayout()
        self.exact_angle_widget.setLayout(exact_angle_layout)
        exact_angle_layout.addWidget(self.exact_angle)
        self.exact_angle.btn_event.connect(self.reset_timer)
        self.exact_angle.angle_event.connect(self.log_item)

        tab_widget = QTabWidget()
        tab_bar = tab_widget.tabBar()
        tab_bar.setFont(QFont('Consolas', 12, 3))
        tab_widget.addTab(self.compass_widget, "Compass")
        tab_widget.addTab(self.exact_angle_widget, "Precise Angle")
        tab_widget.setStyleSheet("""
                QTabWidget::pane {
                    border-top: 2px solid #C2C7CB;
                }
                /* Style the tab using the tab sub-control. Note that
                    it reads QTabBar _not_ QTabWidget */
                QTabBar::tab {
                    background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                stop: 0 #E1E1E1, stop: 0.4 #DDDDDD,
                                stop: 0.5 #D8D8D8, stop: 1.0 #D3D3D3);
                    border: 2px solid #C4C4C3;
                    border-bottom-color: #C2C7CB; /* same as the pane color */
                    border-top-left-radius: 4px;
                    border-top-right-radius: 4px;
                    min-width: 8ex;
                    padding: 2px;
                    color: black;
                }

                QTabBar::tab:selected, QTabBar::tab:hover {
                    background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
                                stop: 0 #fafafa, stop: 0.4 #f4f4f4,
                                stop: 0.5 #e7e7e7, stop: 1.0 #fafafa);
                }

                QTabBar::tab:selected {
                    border-color: #ff0000;
                    border-bottom-color: #C2C7CB; /* same as pane color */
                }

                QTabBar::tab:!selected {
                    margin-top: 2px; /* make non-selected tabs look smaller */
                }
            """)

        header_layout = QHBoxLayout()
        self.zulu_time_label = QLabel()
        self.assessor_label = QLabel()
        self.date_label = QLabel()
        self.dl_label = QLabel()
        self.mnemonic_label = QLabel()
        header_layout.addWidget(self.zulu_time_label)
        header_layout.addWidget(self.assessor_label)
        header_layout.addWidget(self.date_label)
        header_layout.addWidget(self.dl_label)
        header_layout.addWidget(self.mnemonic_label)
        res = QApplication.primaryScreen().size()
        w, h = res.width(), res.height()
        if w > 1920 or h > 1080:
            hdr_font = QFont("Consolas", 16, 2)
            end_font = QFont("Consolas", 32, 5)
        else:
            hdr_font = QFont("Consolas", 14, 2)
            end_font = QFont("Consolas", 28, 5)
        for index in range(header_layout.count()):
            widget = header_layout.itemAt(index).widget()
            widget.setSizePolicy(
                QSizePolicy.Preferred, QSizePolicy.Maximum
            )
            widget.setFont(hdr_font)
            widget.setAlignment(Qt.AlignCenter)

        # Setup logging state machine
        self.init_log_sm()

        # Setup splitters
        actions_splitter = QSplitter(
            Qt.Horizontal,
            frameShape=QFrame.StyledPanel,
            frameShadow=QFrame.Plain
        )
        actions_splitter.addWidget(self.systems)
        actions_splitter.addWidget(self.events)
        actions_splitter.addWidget(tab_widget)
        actions_splitter.setChildrenCollapsible(False)
        main_splitter = QSplitter(
            Qt.Vertical,
            frameShape=QFrame.StyledPanel,
            frameShadow=QFrame.Plain
        )
        self.log_area = QTableWidget(0, 3)
        self.log_area.cellDoubleClicked.connect(self.entry_inspected)
        self.log_area.cellChanged.connect(self.entry_changed)
        self.log_area.setHorizontalHeaderLabels(
            ["Time", "System", "Events"]
        )
        self.log_area.horizontalHeader().setStretchLastSection(True)
        self.set_dark_mode(dark_mode)
        end_msn_btn = QPushButton("END\r\nMISSION")
        end_msn_btn.clicked.connect(self.end_mission)
        end_msn_btn.setFont(end_font)
        end_msn_btn.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        end_msn_btn.setStyleSheet("background-color: red; color: white")
        bottom_layout = QGridLayout()
        bottom_widget = QWidget()
        bottom_widget.setLayout(bottom_layout)
        bottom_layout.addWidget(self.log_area, 0, 0, 1, 7)
        bottom_layout.addWidget(end_msn_btn, 0, 8, 1, 1)
        main_splitter.addWidget(actions_splitter)
        main_splitter.addWidget(bottom_widget)
        main_splitter.setChildrenCollapsible(False)
        handle_css = """
            QSplitter::handle {
                background-image: url(:/imgs/dot_pattern.png);
                background-repeat: repeat-xy;
                background-color: none;
                border: 1px solid gray;
            }
            QSplitter::handle:pressed {
                background-image: url(:/imgs/pressed.png);
            }
        """
        actions_splitter.setStyleSheet(handle_css)
        main_splitter.setStyleSheet(handle_css)

        # Finalize layout
        main_layout = QVBoxLayout()
        main_layout.addLayout(header_layout)
        main_layout.addWidget(main_splitter)
        self.setLayout(main_layout)
Ejemplo n.º 26
0
class POSM(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setLocale(QLocale(QLocale.English))
        self.initUI()
        self.setAttribute(Qt.WA_AlwaysShowToolTips)
        sizegrip = QtWidgets.QSizeGrip(self)
        self.layout.addWidget(sizegrip, 0,
                              QtCore.Qt.AlignBottom | QtCore.Qt.AlignRight)

        self.record = [None]
        recordAction = QAction(datetime.datetime.now().strftime('%H:%M:%S'),
                               self)
        recordAction.triggered.connect(lambda: self.changeMap(0))
        self.recordMenu.addAction(recordAction)

    def initUI(self):
        self.layout = QHBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)

        self.console = InformationalConsole(app)

        self.horSplitter = QSplitter(Qt.Horizontal)
        self.horSplitter.setChildrenCollapsible(False)
        self.editionSplitter = QSplitter(Qt.Vertical)
        self.editionSplitter.setChildrenCollapsible(False)

        self.queryUI = QueryUI()
        self.queryUI.setOnRequestChanged(self.changeCurrentMap)
        self.editionSplitter.addWidget(self.queryUI)

        self.queryWidget = QWidget()
        self.queryWidget.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Maximum)
        self.queryWidget.setLayout(QVBoxLayout())
        self.queryWidget.layout().setContentsMargins(0, 0, 0, 0)
        self.queryWidget.layout().setSpacing(0)

        self.queryHeader = QLabel("Query")
        self.queryHeader.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Fixed)
        self.queryHeader.setFixedHeight(self.queryHeader.sizeHint().height() +
                                        10)
        self.queryHeader.setContentsMargins(5, 5, 0, 5)
        self.queryWidget.layout().addWidget(self.queryHeader)

        self.queryText = CodeEditor()
        self.qlHighlighter = OverpassQLHighlighter(self.queryText.document())
        self.queryText.setReadOnly(True)
        self.queryWidget.layout().addWidget(self.queryText)

        self.editionSplitter.addWidget(self.queryWidget)

        self.horSplitter.addWidget(self.editionSplitter)

        self.emptyMapPage = QWebEnginePage()
        self.emptyMapPage.setHtml(EMPTY_HTML)

        self.manualModePage = QWebEnginePage()
        soup = bs4.BeautifulSoup(EMPTY_HTML, features="html.parser")
        js = soup.new_tag("script")
        js.string = (MANUAL_MODE_JS_SCRIPT % (str([])))
        soup.append(js)

        self.manualModePage.setHtml(str(soup))

        self.mapRenderer = QWebEngineView()
        self.mapRenderer.setMinimumWidth(500)
        self.mapRenderer.setPage(self.emptyMapPage)

        self.consoleSplitter = QSplitter(Qt.Vertical)
        self.consoleSplitter.setChildrenCollapsible(False)
        self.consoleSplitter.addWidget(self.mapRenderer)

        self.consoleWidget = QWidget()
        self.consoleWidget.setLayout(QVBoxLayout())
        self.consoleWidget.layout().setContentsMargins(0, 0, 0, 0)
        self.consoleWidget.layout().setSpacing(0)

        self.consoleHeader = QLabel("Console")
        self.consoleHeader.setSizePolicy(QSizePolicy.Expanding,
                                         QSizePolicy.Fixed)
        self.consoleHeader.setContentsMargins(5, 5, 0, 5)
        self.consoleWidget.layout().addWidget(self.consoleHeader)
        self.consoleWidget.layout().addWidget(self.console)

        self.consoleSplitter.addWidget(self.consoleWidget)

        self.horSplitter.addWidget(self.consoleSplitter)

        self.layout.addWidget(self.horSplitter)

        self.initMenuBar()

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

        self.setWindowTitle('Python Open Street Map')

    def initMenuBar(self):
        menubar = self.menuBar()

        fileMenu = menubar.addMenu('File')

        openAct = QAction('Open netedit', self)
        openAct.triggered.connect(self.openNet)
        fileMenu.addAction(openAct)

        saveMenu = fileMenu.addMenu("Save")

        saveOutputAct = QAction('output', self)
        saveOutputAct.triggered.connect(self.saveNet)
        saveOutputAct.setShortcut('Ctrl+S')
        saveMenu.addAction(saveOutputAct)

        saveQueryAct = QAction('query', self)
        saveQueryAct.triggered.connect(self.saveQuery)
        saveQueryAct.setShortcut('Ctrl+Shift+S')
        saveMenu.addAction(saveQueryAct)

        saveInteractiveModeAct = QAction('interactive mode', self)
        saveInteractiveModeAct.triggered.connect(self.saveInteractiveQuery)
        saveMenu.addAction(saveInteractiveModeAct)

        openMenu = fileMenu.addMenu("Open")

        openQuery = QAction('query', self)
        openQuery.triggered.connect(self.openQuery)
        openQuery.setShortcut('Ctrl+O')
        openMenu.addAction(openQuery)

        openInteractiveMode = QAction('interactive mode', self)
        openInteractiveMode.triggered.connect(self.openInteractiveQuery)
        openMenu.addAction(openInteractiveMode)

        self.recordMenu = openMenu.addMenu("record")

        runMenu = menubar.addMenu('Run')

        playAct = QAction('Play', self)
        playAct.triggered.connect(self.playQuery)
        playAct.setShortcut('Ctrl+P')
        runMenu.addAction(playAct)

        playTableRowAct = QAction('Play row selection', self)
        playTableRowAct.triggered.connect(self.playTableRow)
        playTableRowAct.setShortcut('Ctrl+T')
        runMenu.addAction(playTableRowAct)

        self.requestMenu = menubar.addMenu('Request')

        addRequestAct = QAction('Add request', self)
        addRequestAct.triggered.connect(lambda b: self.addRequest())
        addRequestAct.setShortcut('Ctrl+A')
        self.requestMenu.addAction(addRequestAct)

        templatesMenu = self.requestMenu.addMenu("Add template")

        addRoadAct = QAction('Roads', self)
        addRoadAct.triggered.connect(lambda: self.addTemplate([
            OverpassFilter("highway", TagComparison.EQUAL, "", False, True),
            OverpassFilter("name", TagComparison.EQUAL, "", False, True),
            OverpassFilter("ref", TagComparison.EQUAL, "", False, True),
            OverpassFilter("maxspeed", TagComparison.AT_MOST, "120", False,
                           False),
            OverpassFilter("lanes", TagComparison.EQUAL, "", False, True),
            OverpassFilter("oneway", TagComparison.EQUAL, "", False, True)
        ]))
        templatesMenu.addAction(addRoadAct)

        addMainRoadAct = QAction('Main roads', self)
        mainHighways = "^(motorway|trunk|primary|secondary|residential)(_link)?$"
        everythingButYes = "^(y(e([^s]|$|s.)|[^e]|$)|[^y]|$).*"
        addMainRoadAct.triggered.connect(lambda: self.addTemplate([
            OverpassFilter("highway", TagComparison.EQUAL, mainHighways, False,
                           False),
            OverpassFilter("construction", TagComparison.HAS_NOT_KEY, "",
                           False, False),
            OverpassFilter("noexit", TagComparison.EQUAL, "yes", True, True),
            OverpassFilter("access", TagComparison.EQUAL, everythingButYes,
                           True, False)
        ]))
        templatesMenu.addAction(addMainRoadAct)

        addParkingAct = QAction('Parking', self)
        addParkingAct.triggered.connect(lambda: self.addTemplate([
            OverpassFilter("service", TagComparison.EQUAL, "parking", False,
                           False),
            OverpassFilter("highway", TagComparison.HAS_KEY, "", False, True)
        ]))
        templatesMenu.addAction(addParkingAct)

        addPedestriansAct = QAction('Pedestrians', self)
        pedestrianHighway = [
            "pedestrian", "footway", "path", "cycleway", "bridleway", "steps",
            "crossing"
        ]
        addPedestriansAct.triggered.connect(lambda: self.addTemplate([
            OverpassFilter("highway", TagComparison.IS_ONE_OF,
                           pedestrianHighway, False, True)
        ]))
        templatesMenu.addAction(addPedestriansAct)

        removeRequestAct = QAction('Remove current request', self)
        removeRequestAct.triggered.connect(self.removeRequest)
        removeRequestAct.setShortcut('Ctrl+R')
        self.requestMenu.addAction(removeRequestAct)

        self.manualModeAct = QAction(
            'Switch between interactive and manual mode', self)
        self.manualModeAct.triggered.connect(self.switchManualMode)
        self.requestMenu.addAction(self.manualModeAct)

        self.manualModeMenu = menubar.addMenu('Manual mode')
        self.manualModeMenu.setEnabled(False)

        manualModeCleanPolygonAct = QAction('Clean polygon', self)
        manualModeCleanPolygonAct.triggered.connect(
            self.cleanManualModePolygon)
        self.manualModeMenu.addAction(manualModeCleanPolygonAct)

        manualModeGetPolygonAct = QAction('Polygon coordinates', self)
        manualModeGetPolygonAct.triggered.connect(
            lambda: self.manualModePage.runJavaScript(
                "getPolygons();", self.logManualModePolygonCoords))
        self.manualModeMenu.addAction(manualModeGetPolygonAct)

        windowsMenu = menubar.addMenu('Windows')

        cleanMapAct = QAction('Clean map', self)
        cleanMapAct.triggered.connect(self.cleanMap)
        windowsMenu.addAction(cleanMapAct)

        self.showHideInteractiveModeAct = QAction('Interactive mode', self)
        self.showHideInteractiveModeAct.triggered.connect(
            self.showHideInteractiveMode)
        windowsMenu.addAction(self.showHideInteractiveModeAct)

        showHideConsole = QAction('Console', self)
        showHideConsole.triggered.connect(self.showHideConsole)
        windowsMenu.addAction(showHideConsole)

        showHideQuery = QAction('Query', self)
        showHideQuery.triggered.connect(self.showHideQuery)
        windowsMenu.addAction(showHideQuery)

    # ACTIONS
    def cleanMap(self):
        if self.queryText.isReadOnly():
            if self.queryUI.getCurrentMap() is not None:
                self.mapRenderer.setPage(self.queryUI.updateMaps(EMPTY_HTML))
        else:
            soup = bs4.BeautifulSoup(EMPTY_HTML, features="html.parser")
            js = soup.new_tag("script")
            js.string = (MANUAL_MODE_JS_SCRIPT % (str([])))
            soup.append(js)
            self.manualModePage.setHtml(str(soup))

        logging.info("Cleaning map")

    def changeMap(self, i):
        if i == 0:
            if not self.queryText.isReadOnly():
                self.switchManualMode()
            if self.queryText.isReadOnly():
                self.queryUI.reset()
                self.queryText.clear()
                self.mapRenderer.setPage(self.emptyMapPage)
                self.queryUI.updateMaps(EMPTY_HTML)
        elif self.record[i]["interactiveMode"]:
            if not self.queryText.isReadOnly():
                self.switchManualMode()
            if self.queryText.isReadOnly():
                self.queryUI.setQuery(self.record[i]["query"])
                self.queryText.setPlainText(self.record[i]["query"].getQL())
                self.mapRenderer.setPage(
                    self.queryUI.updateMaps(self.record[i]["html"]))
        else:
            if self.queryText.isReadOnly():
                self.switchManualMode()
            if not self.queryText.isReadOnly():
                self.queryUI.reset()
                self.queryText.setPlainText(self.record[i]["query"])
                self.manualModePage.setHtml(self.record[i]["html"])
                self.mapRenderer.setPage(self.manualModePage)

    def logManualModePolygonCoords(self, coords):
        coordsString = " ".join([str(c) for point in coords for c in point])
        logging.info("Polygon coordinates:\"{}\"".format(coordsString))
        pyperclip.copy(coordsString)
        logging.debug("LINE")

    def cleanManualModePolygon(self):
        logging.info("Cleaning polygon.")
        self.manualModePage.runJavaScript(
            "cleanPolygon();", lambda returnValue: logging.debug("LINE"))

    def showHideInteractiveMode(self):
        if self.queryUI.isHidden():
            if self.editionSplitter.isHidden():
                self.editionSplitter.show()
                self.queryText.hide()
            self.queryUI.show()
            logging.info("Showing 'Interactive mode' window.")
        else:
            if self.queryText.isHidden():
                self.editionSplitter.hide()
            self.queryUI.hide()
            logging.info("Hiding 'Interactive mode' window.")
        logging.debug("LINE")

    def showHideConsole(self):
        if self.console.isHidden():
            self.console.show()
            logging.info("Showing 'Console' window.")
            self.consoleWidget.setMaximumHeight(QWIDGETSIZE_MAX)
        else:
            self.console.hide()
            self.consoleWidget.setMaximumHeight(
                self.queryHeader.sizeHint().height())
            logging.info("Hiding 'Console' window.")
        logging.debug("LINE")

    def showHideQuery(self):
        if self.queryText.isHidden():
            if self.editionSplitter.isHidden():
                self.editionSplitter.show()
                self.queryUI.hide()
            self.queryText.show()
            logging.info("Showing 'Query' window.")
            self.queryWidget.setMaximumHeight(QWIDGETSIZE_MAX)
        else:
            if self.queryUI.isHidden():
                self.editionSplitter.hide()
            self.queryText.hide()
            self.queryWidget.setMaximumHeight(
                self.queryHeader.sizeHint().height())
            logging.info("Hiding 'Query' window.")
        logging.debug("LINE")

    def switchManualMode(self):
        if self.queryText.isReadOnly():
            reply = QMessageBox.question(
                self, "Manual mode",
                "Are you sure?\nThe interactive mode will remain as it is now."
            )

            if reply == QMessageBox.Yes:
                self.queryText.setReadOnly(False)

                self.queryUI.hide()
                for action in self.requestMenu.actions():
                    action.setEnabled(False)
                self.manualModeAct.setEnabled(True)
                self.manualModeMenu.setEnabled(True)
                self.showHideInteractiveModeAct.setEnabled(False)
                self.mapRenderer.setPage(self.manualModePage)

                logging.info("Switching to manual mode.")
            else:
                logging.info(
                    "'Switch between interactive and manual mode' cancelled.")
        else:
            reply = QMessageBox.question(
                self, "Interactive mode",
                "Are you sure?\nThe current query will be removed.")

            if reply == QMessageBox.Yes:
                try:
                    self.queryText.clear()
                    self.queryText.setPlainText(
                        self.queryUI.getQuery().getQL())
                except BadFilterAttributes as e:
                    logging.error(str(e))
                except RuntimeError:
                    logging.warning("Failed to write query.")
                    self.queryText.clear()
                    self.queryText.setPlainText("")

                self.queryText.setReadOnly(True)

                self.queryUI.show()
                for action in self.requestMenu.actions():
                    action.setEnabled(True)
                self.manualModeMenu.setEnabled(False)
                self.showHideInteractiveModeAct.setEnabled(True)
                self.changeCurrentMap(0)

                logging.info("Switching to interactive mode.")
            else:
                logging.info(
                    "'Switch between interactive and manual mode' cancelled.")

        logging.info("Showing 'manual mode' polygon.")

    def addRequest(self, filters=None):
        self.queryUI.addRequestByFilters(filters)
        logging.info("Request added.")
        logging.debug("LINE")

    def addTemplate(self, filters):
        logging.info("Template applied.")
        self.queryUI.addRequestByFilters(filters)

    def removeRequest(self):
        reply = QMessageBox.question(
            self, "Remove current request",
            "Are you sure? This option is not undoable.")

        if reply == QMessageBox.Yes:
            self.queryUI.removeRequest()
            logging.info("'Remove request' successfully executed.")
        else:
            logging.info("'Remove request' cancelled.")
        logging.debug("LINE")

    def saveQuery(self):
        filename, selectedFilter = QFileDialog.getSaveFileName(
            self, 'Save query', expanduser("~/filename.txt"),
            "Text files (*.txt)")

        if filename != "":
            if self.queryText.isReadOnly():
                try:
                    query = self.queryUI.getQuery().getQL()
                    f = open(filename, "w+")
                    f.seek(0)
                    f.truncate()
                    f.write(query)
                    f.close()

                    logging.info("Query saved successfully.")
                except (RuntimeError, BadFilterAttributes) as e:
                    logging.error(str(e))
                except OSError:
                    logging.error(
                        "There was a problem creating the file with the query."
                    )
            else:
                try:
                    f = open(filename, "w+")
                    f.seek(0)
                    f.truncate()
                    f.write(self.queryText.toPlainText())
                    f.close()

                    logging.info("Query saved successfully.")
                except OSError:
                    logging.error(
                        "There was a problem creating the file with the query."
                    )
        else:
            logging.info("\"Save query\" canceled.")

        logging.debug("LINE")

    def openQuery(self):
        filename, selectedFilter = QFileDialog.getOpenFileName(
            self, 'Open query', expanduser("~/filename.txt"))

        if filename != "":
            try:
                if self.queryText.isReadOnly():
                    self.switchManualMode()

                f = open(filename, "r")
                self.queryText.clear()
                self.queryText.setPlainText(f.read())
                f.close()

                logging.info("File read successfully.")
            except UnicodeDecodeError:
                logging.error("The given file is not readable as text.")
            except OSError:
                logging.error("There was a problem opening the query file.")
        else:
            logging.info("\"Open query\" canceled.")

        logging.debug("LINE")

    def saveInteractiveQuery(self):
        filename, selectedFilter = QFileDialog.getSaveFileName(
            self, 'Save query', expanduser("~/filename.json"),
            "JSON files (*.json)")

        if filename != "":
            try:
                query = self.queryUI.getQuery()
                query.saveToFile(filename)
                logging.info("Query saved successfully.")
            except (RuntimeError, BadFilterAttributes) as e:
                logging.error(str(e))
            except OSError:
                logging.error(
                    "There was a problem creating the file with the query.")
        else:
            logging.info("\"Save query\" canceled.")

        logging.debug("LINE")

    def openInteractiveQuery(self):
        filename, selectedFilter = QFileDialog.getOpenFileName(
            self, 'Open query', expanduser("~/filename.json"))

        if filename != "":
            try:
                self.queryUI.setQuery(OverpassQuery.getFromFile(filename))

                if not self.queryText.isReadOnly():
                    self.switchManualMode()
            except json.decoder.JSONDecodeError:
                logging.error(
                    "The given file has not the right format (json). The file could not be opened."
                )
            except UnicodeDecodeError:
                logging.error(
                    "The given file is not readable as text. The file could not be opened."
                )
            except (TypeError, KeyError):
                logging.error(
                    "Fields are missing from the file or there are fields with the wrong data type. "
                    "The file could not be opened.")
            except OSError:
                logging.error(
                    "There was a problem opening the query file. The file could not be opened."
                )
        else:
            logging.info("\"Open query\" canceled.")

        logging.debug("LINE")

    def saveNet(self):
        filename, selectedFilter = QFileDialog.getSaveFileName(
            self, 'Save File', expanduser("~/filenameWithoutExtension"))
        if filename != "":
            buildNet(filename)
        else:
            logging.info("\"Save File\" canceled.")
        logging.debug("LINE")
        return filename

    def openNet(self):
        try:
            filename = self.saveNet()
            if filename == "":
                logging.error("Can't open NETEDIT without a file.")
            else:
                openNetedit(filename + ".net.xml")
                logging.info("Opening NETEDIT.")
                logging.warning(
                    "If NETEDIT is not open in ten seconds, there was an unhandled problem."
                )
                logging.debug("LINE")
        except OSError:
            logging.error("Can't find NETEDIT.")
        except Exception:
            logging.error(traceback.format_exc())

    # POLYGONS
    def changeCurrentMap(self, i):
        if self.queryUI.getCurrentMap() is None:
            self.mapRenderer.setPage(self.emptyMapPage)
        else:
            self.mapRenderer.setPage(self.queryUI.getCurrentMap())

    def playQuery(self):
        newRecord = {
            "interactiveMode": self.queryText.isReadOnly(),
            "query": self.queryText.toPlainText(),
            "html": ""
        }

        if self.queryText.isReadOnly():
            try:
                query = self.queryUI.getQuery()
                newRecord["query"] = query
                self.queryText.setPlainText(query.getQL())
            except (RuntimeError, BadFilterAttributes) as e:
                logging.error(str(e))
                return
        try:
            html = buildHTMLWithQuery(self.queryText.toPlainText())
            if self.queryText.isReadOnly():
                self.mapRenderer.setPage(self.queryUI.updateMaps(html))
                newRecord["html"] = html
            else:
                soup = bs4.BeautifulSoup(html, features="html.parser")
                js = soup.new_tag("script")
                js.string = (MANUAL_MODE_JS_SCRIPT % (str([])))
                soup.append(js)

                self.manualModePage.setHtml(str(soup))
                newRecord["html"] = str(soup)
                self.mapRenderer.setPage(self.manualModePage)
            logging.info("Query drawn.")
            logging.debug("LINE")
            self.addRecord(newRecord)
        except (OverpassRequestException, OsmnxException) as e:
            logging.error(str(e))
        except ox.EmptyOverpassResponse:
            logging.error("There are no elements with the given query.")
        except OSError:
            logging.error(
                "There was a problem creating the file with the request response."
            )
        except Exception:
            logging.error(traceback.format_exc())

    def addRecord(self, newRecord):
        self.record.append(newRecord)
        index = len(self.record) - 1

        recordAction = QAction(datetime.datetime.now().strftime('%H:%M:%S'),
                               self)
        recordAction.triggered.connect(lambda: self.changeMap(index))
        self.recordMenu.addAction(recordAction)

    def playTableRow(self):
        try:
            self.mapRenderer.setPage(self.queryUI.updateMapFromRow())
        except (OverpassRequestException, OsmnxException) as e:
            logging.error(str(e))
            logging.warning(
                "Before open NETEDIT you must run a query with the row filters applied."
            )
        except ox.EmptyOverpassResponse:
            logging.error("There are no elements with the given row.")
        except OSError:
            logging.error(
                "There was a problem creating the file with the row selection."
            )
        except RuntimeError as e:
            logging.error(str(e))
        except Exception:
            logging.error(traceback.format_exc())
        logging.debug("LINE")

    # EVENTS
    def closeEvent(self, event):
        for f in os.listdir(tempDir):
            os.remove(os.path.join(tempDir, f))
        QMainWindow.closeEvent(self, event)
Ejemplo n.º 27
0
class Tabview(ViewManager):
    """
    Class implementing a tabbed viewmanager class embedded in a splitter.
    
    @signal changeCaption(str) emitted if a change of the caption is necessary
    @signal editorChanged(str) emitted when the current editor has changed
    @signal editorChangedEd(Editor) emitted when the current editor has changed
    @signal lastEditorClosed() emitted after the last editor window was closed
    @signal editorOpened(str) emitted after an editor window was opened
    @signal editorOpenedEd(Editor) emitted after an editor window was opened
    @signal editorClosed(str) emitted just before an editor window gets closed
    @signal editorClosedEd(Editor) emitted just before an editor window gets
        closed
    @signal editorRenamed(str) emitted after an editor was renamed
    @signal editorRenamedEd(Editor) emitted after an editor was renamed
    @signal editorSaved(str) emitted after an editor window was saved
    @signal editorSavedEd(Editor) emitted after an editor window was saved
    @signal checkActions(Editor) emitted when some actions should be checked
        for their status
    @signal cursorChanged(Editor) emitted after the cursor position of the
        active window has changed
    @signal breakpointToggled(Editor) emitted when a breakpoint is toggled.
    @signal bookmarkToggled(Editor) emitted when a bookmark is toggled.
    @signal syntaxerrorToggled(Editor) emitted when a syntax error is toggled.
    @signal previewStateChanged(bool) emitted to signal a change in the
        preview state
    @signal editorLanguageChanged(Editor) emitted to signal a change of an
        editors language
    @signal editorTextChanged(Editor) emitted to signal a change of an
        editor's text
    @signal editorLineChanged(str,int) emitted to signal a change of an
        editor's current line (line is given one based)
    """
    changeCaption = pyqtSignal(str)
    editorChanged = pyqtSignal(str)
    editorChangedEd = pyqtSignal(Editor)
    lastEditorClosed = pyqtSignal()
    editorOpened = pyqtSignal(str)
    editorOpenedEd = pyqtSignal(Editor)
    editorClosed = pyqtSignal(str)
    editorClosedEd = pyqtSignal(Editor)
    editorRenamed = pyqtSignal(str)
    editorRenamedEd = pyqtSignal(Editor)
    editorSaved = pyqtSignal(str)
    editorSavedEd = pyqtSignal(Editor)
    checkActions = pyqtSignal(Editor)
    cursorChanged = pyqtSignal(Editor)
    breakpointToggled = pyqtSignal(Editor)
    bookmarkToggled = pyqtSignal(Editor)
    syntaxerrorToggled = pyqtSignal(Editor)
    previewStateChanged = pyqtSignal(bool)
    editorLanguageChanged = pyqtSignal(Editor)
    editorTextChanged = pyqtSignal(Editor)
    editorLineChanged = pyqtSignal(str, int)

    def __init__(self, parent):
        """
        Constructor
        
        @param parent parent widget (QWidget)
        """
        self.tabWidgets = []

        self.__splitter = QSplitter(parent)
        ViewManager.__init__(self)
        self.__splitter.setChildrenCollapsible(False)

        tw = TabWidget(self)
        self.__splitter.addWidget(tw)
        self.tabWidgets.append(tw)
        self.currentTabWidget = tw
        self.currentTabWidget.showIndicator(True)
        tw.currentChanged.connect(self.__currentChanged)
        tw.installEventFilter(self)
        tw.tabBar().installEventFilter(self)
        self.__splitter.setOrientation(Qt.Vertical)
        self.__inRemoveView = False

        self.maxFileNameChars = Preferences.getUI(
            "TabViewManagerFilenameLength")
        self.filenameOnly = Preferences.getUI("TabViewManagerFilenameOnly")

    def mainWidget(self):
        """
        Public method to return a reference to the main Widget of a
        specific view manager subclass.
        
        @return reference to the main widget
        @rtype QWidget
        """
        return self.__splitter

    def canCascade(self):
        """
        Public method to signal if cascading of managed windows is available.
        
        @return flag indicating cascading of windows is available
        """
        return False

    def canTile(self):
        """
        Public method to signal if tiling of managed windows is available.
        
        @return flag indicating tiling of windows is available
        """
        return False

    def canSplit(self):
        """
        public method to signal if splitting of the view is available.
        
        @return flag indicating splitting of the view is available.
        """
        return True

    def tile(self):
        """
        Public method to tile the managed windows.
        """
        pass

    def cascade(self):
        """
        Public method to cascade the managed windows.
        """
        pass

    def _removeAllViews(self):
        """
        Protected method to remove all views (i.e. windows).
        """
        for win in self.editors:
            self._removeView(win)

    def _removeView(self, win):
        """
        Protected method to remove a view (i.e. window).
        
        @param win editor window to be removed
        """
        self.__inRemoveView = True
        for tw in self.tabWidgets:
            if tw.hasEditor(win):
                tw.removeWidget(win)
                break
        win.closeIt()
        self.__inRemoveView = False

        # if this was the last editor in this view, switch to the next, that
        # still has open editors
        for i in list(range(self.tabWidgets.index(tw), -1, -1)) + \
            list(range(self.tabWidgets.index(tw) + 1,
                 len(self.tabWidgets))):
            if self.tabWidgets[i].hasEditors():
                self.currentTabWidget.showIndicator(False)
                self.currentTabWidget = self.tabWidgets[i]
                self.currentTabWidget.showIndicator(True)
                self.activeWindow().setFocus()
                break

        aw = self.activeWindow()
        fn = aw and aw.getFileName() or None
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
            self.editorLineChanged.emit(fn, aw.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(aw)

    def _addView(self, win, fn=None, noName="", next=False):
        """
        Protected method to add a view (i.e. window).
        
        @param win editor assembly to be added
        @param fn filename of this editor (string)
        @param noName name to be used for an unnamed editor (string)
        @param next flag indicating to add the view next to the current
            view (bool)
        """
        editor = win.getEditor()
        if fn is None:
            if not noName:
                self.untitledCount += 1
                noName = self.tr("Untitled {0}").format(self.untitledCount)
            if next:
                index = self.currentTabWidget.currentIndex() + 1
                self.currentTabWidget.insertWidget(index, win, noName)
            else:
                self.currentTabWidget.addTab(win, noName)
            editor.setNoName(noName)
        else:
            if self.filenameOnly:
                txt = os.path.basename(fn)
            else:
                txt = e5App().getObject("Project").getRelativePath(fn)
            if len(txt) > self.maxFileNameChars:
                txt = "...{0}".format(txt[-self.maxFileNameChars:])
            if not QFileInfo(fn).isWritable():
                txt = self.tr("{0} (ro)").format(txt)
            if next:
                index = self.currentTabWidget.currentIndex() + 1
                self.currentTabWidget.insertWidget(index, win, txt)
            else:
                self.currentTabWidget.addTab(win, txt)
            index = self.currentTabWidget.indexOf(win)
            self.currentTabWidget.setTabToolTip(index, fn)
        self.currentTabWidget.setCurrentWidget(win)
        win.show()
        editor.setFocus()
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
            self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(editor)

    def insertView(self, win, tabWidget, index, fn=None, noName=""):
        """
        Public method to add a view (i.e. window).
        
        @param win editor assembly to be inserted
        @param tabWidget reference to the tab widget to insert the editor into
            (TabWidget)
        @param index index position to insert at (integer)
        @param fn filename of this editor (string)
        @param noName name to be used for an unnamed editor (string)
        """
        editor = win.getEditor()
        if fn is None:
            if not noName:
                self.untitledCount += 1
                noName = self.tr("Untitled {0}").format(self.untitledCount)
            tabWidget.insertWidget(index, win, noName)
            editor.setNoName(noName)
        else:
            if self.filenameOnly:
                txt = os.path.basename(fn)
            else:
                txt = e5App().getObject("Project").getRelativePath(fn)
            if len(txt) > self.maxFileNameChars:
                txt = "...{0}".format(txt[-self.maxFileNameChars:])
            if not QFileInfo(fn).isWritable():
                txt = self.tr("{0} (ro)").format(txt)
            nindex = tabWidget.insertWidget(index, win, txt)
            tabWidget.setTabToolTip(nindex, fn)
        tabWidget.setCurrentWidget(win)
        win.show()
        editor.setFocus()
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
            self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(editor)

        self._modificationStatusChanged(editor.isModified(), editor)
        self._checkActions(editor)

    def _showView(self, win, fn=None):
        """
        Protected method to show a view (i.e. window).
        
        @param win editor assembly to be shown
        @param fn filename of this editor (string)
        """
        win.show()
        editor = win.getEditor()
        for tw in self.tabWidgets:
            if tw.hasEditor(editor):
                tw.setCurrentWidget(win)
                self.currentTabWidget.showIndicator(False)
                self.currentTabWidget = tw
                self.currentTabWidget.showIndicator(True)
                break
        editor.setFocus()

    def activeWindow(self):
        """
        Public method to return the active (i.e. current) window.
        
        @return reference to the active editor
        """
        cw = self.currentTabWidget.currentWidget()
        if cw:
            return cw.getEditor()
        else:
            return None

    def showWindowMenu(self, windowMenu):
        """
        Public method to set up the viewmanager part of the Window menu.
        
        @param windowMenu reference to the window menu
        """
        pass

    def _initWindowActions(self):
        """
        Protected method to define the user interface actions for window
        handling.
        """
        pass

    def setEditorName(self, editor, newName):
        """
        Public method to change the displayed name of the editor.
        
        @param editor editor window to be changed
        @param newName new name to be shown (string)
        """
        if newName:
            if self.filenameOnly:
                tabName = os.path.basename(newName)
            else:
                tabName = e5App().getObject("Project").getRelativePath(newName)
            if len(tabName) > self.maxFileNameChars:
                tabName = "...{0}".format(tabName[-self.maxFileNameChars:])
            index = self.currentTabWidget.indexOf(editor)
            self.currentTabWidget.setTabText(index, tabName)
            self.currentTabWidget.setTabToolTip(index, newName)
            self.changeCaption.emit(newName)

    def _modificationStatusChanged(self, m, editor):
        """
        Protected slot to handle the modificationStatusChanged signal.
        
        @param m flag indicating the modification status (boolean)
        @param editor editor window changed
        """
        for tw in self.tabWidgets:
            if tw.hasEditor(editor):
                break
        index = tw.indexOf(editor)
        keys = []
        if m:
            keys.append("fileModified.png")
        if editor.hasSyntaxErrors():
            keys.append("syntaxError22.png")
        elif editor.hasWarnings():
            keys.append("warning22.png")
        if not keys:
            keys.append("empty.png")
        tw.setTabIcon(index, UI.PixmapCache.getCombinedIcon(keys))
        self._checkActions(editor)

    def _syntaxErrorToggled(self, editor):
        """
        Protected slot to handle the syntaxerrorToggled signal.
        
        @param editor editor that sent the signal
        """
        for tw in self.tabWidgets:
            if tw.hasEditor(editor):
                break
        index = tw.indexOf(editor)
        keys = []
        if editor.isModified():
            keys.append("fileModified.png")
        if editor.hasSyntaxErrors():
            keys.append("syntaxError22.png")
        elif editor.hasWarnings():
            keys.append("warning22.png")
        if not keys:
            keys.append("empty.png")
        tw.setTabIcon(index, UI.PixmapCache.getCombinedIcon(keys))

        ViewManager._syntaxErrorToggled(self, editor)

    def addSplit(self):
        """
        Public method used to split the current view.
        """
        tw = TabWidget(self)
        tw.show()
        self.__splitter.addWidget(tw)
        self.tabWidgets.append(tw)
        self.currentTabWidget.showIndicator(False)
        self.currentTabWidget = self.tabWidgets[-1]
        self.currentTabWidget.showIndicator(True)
        tw.currentChanged.connect(self.__currentChanged)
        tw.installEventFilter(self)
        tw.tabBar().installEventFilter(self)
        if self.__splitter.orientation() == Qt.Horizontal:
            size = self.width()
        else:
            size = self.height()
        self.__splitter.setSizes([int(size / len(self.tabWidgets))] *
                                 len(self.tabWidgets))
        self.splitRemoveAct.setEnabled(True)
        self.nextSplitAct.setEnabled(True)
        self.prevSplitAct.setEnabled(True)

    def removeSplit(self):
        """
        Public method used to remove the current split view.
        
        @return flag indicating successfull removal
        """
        if len(self.tabWidgets) > 1:
            tw = self.currentTabWidget
            res = True
            savedEditors = tw.editors[:]
            for editor in savedEditors:
                res &= self.closeEditor(editor)
            if res:
                try:
                    i = self.tabWidgets.index(tw)
                except ValueError:
                    return True
                if i == len(self.tabWidgets) - 1:
                    i -= 1
                self.tabWidgets.remove(tw)
                tw.close()
                self.currentTabWidget = self.tabWidgets[i]
                for tw in self.tabWidgets:
                    tw.showIndicator(tw == self.currentTabWidget)
                if self.currentTabWidget is not None:
                    assembly = self.currentTabWidget.currentWidget()
                    if assembly is not None:
                        editor = assembly.getEditor()
                        if editor is not None:
                            editor.setFocus(Qt.OtherFocusReason)
                if len(self.tabWidgets) == 1:
                    self.splitRemoveAct.setEnabled(False)
                    self.nextSplitAct.setEnabled(False)
                    self.prevSplitAct.setEnabled(False)
                return True

        return False

    def getSplitOrientation(self):
        """
        Public method to get the orientation of the split view.
        
        @return orientation of the split (Qt.Horizontal or Qt.Vertical)
        """
        return self.__splitter.orientation()

    def setSplitOrientation(self, orientation):
        """
        Public method used to set the orientation of the split view.
        
        @param orientation orientation of the split
                (Qt.Horizontal or Qt.Vertical)
        """
        self.__splitter.setOrientation(orientation)

    def nextSplit(self):
        """
        Public slot used to move to the next split.
        """
        aw = self.activeWindow()
        _hasFocus = aw and aw.hasFocus()
        ind = self.tabWidgets.index(self.currentTabWidget) + 1
        if ind == len(self.tabWidgets):
            ind = 0

        self.currentTabWidget.showIndicator(False)
        self.currentTabWidget = self.tabWidgets[ind]
        self.currentTabWidget.showIndicator(True)
        if _hasFocus:
            aw = self.activeWindow()
            if aw:
                aw.setFocus()

    def prevSplit(self):
        """
        Public slot used to move to the previous split.
        """
        aw = self.activeWindow()
        _hasFocus = aw and aw.hasFocus()
        ind = self.tabWidgets.index(self.currentTabWidget) - 1
        if ind == -1:
            ind = len(self.tabWidgets) - 1

        self.currentTabWidget.showIndicator(False)
        self.currentTabWidget = self.tabWidgets[ind]
        self.currentTabWidget.showIndicator(True)
        if _hasFocus:
            aw = self.activeWindow()
            if aw:
                aw.setFocus()

    def __currentChanged(self, index):
        """
        Private slot to handle the currentChanged signal.
        
        @param index index of the current tab (integer)
        """
        if index == -1 or not self.editors:
            return

        editor = self.activeWindow()
        if editor is None:
            return

        self._checkActions(editor)
        editor.setFocus()
        fn = editor.getFileName()
        if fn:
            self.changeCaption.emit(fn)
            if not self.__inRemoveView:
                self.editorChanged.emit(fn)
                self.editorLineChanged.emit(fn,
                                            editor.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(editor)

    def eventFilter(self, watched, event):
        """
        Public method called to filter the event queue.
        
        @param watched the QObject being watched (QObject)
        @param event the event that occurred (QEvent)
        @return always False
        """
        if event.type() == QEvent.MouseButtonPress and \
           not event.button() == Qt.RightButton:
            switched = True
            self.currentTabWidget.showIndicator(False)
            if isinstance(watched, E5TabWidget):
                switched = watched is not self.currentTabWidget
                self.currentTabWidget = watched
            elif isinstance(watched, QTabBar):
                switched = watched.parent() is not self.currentTabWidget
                self.currentTabWidget = watched.parent()
                if switched:
                    index = self.currentTabWidget.selectTab(event.pos())
                    switched = self.currentTabWidget.widget(index) is \
                        self.activeWindow()
            elif isinstance(watched, QScintilla.Editor.Editor):
                for tw in self.tabWidgets:
                    if tw.hasEditor(watched):
                        switched = tw is not self.currentTabWidget
                        self.currentTabWidget = tw
                        break
            self.currentTabWidget.showIndicator(True)

            aw = self.activeWindow()
            if aw is not None:
                self._checkActions(aw)
                aw.setFocus()
                fn = aw.getFileName()
                if fn:
                    self.changeCaption.emit(fn)
                    if switched:
                        self.editorChanged.emit(fn)
                        self.editorLineChanged.emit(
                            fn,
                            aw.getCursorPosition()[0] + 1)
                else:
                    self.changeCaption.emit("")
                self.editorChangedEd.emit(aw)

        return False

    def preferencesChanged(self):
        """
        Public slot to handle the preferencesChanged signal.
        """
        ViewManager.preferencesChanged(self)

        self.maxFileNameChars = Preferences.getUI(
            "TabViewManagerFilenameLength")
        self.filenameOnly = Preferences.getUI("TabViewManagerFilenameOnly")

        for tabWidget in self.tabWidgets:
            for index in range(tabWidget.count()):
                editor = tabWidget.widget(index)
                if isinstance(editor, QScintilla.Editor.Editor):
                    fn = editor.getFileName()
                    if fn:
                        if self.filenameOnly:
                            txt = os.path.basename(fn)
                        else:
                            txt = e5App().getObject("Project")\
                                .getRelativePath(fn)
                        if len(txt) > self.maxFileNameChars:
                            txt = "...{0}".format(txt[-self.maxFileNameChars:])
                        if not QFileInfo(fn).isWritable():
                            txt = self.tr("{0} (ro)").format(txt)
                        tabWidget.setTabText(index, txt)

    def getTabWidgetById(self, id_):
        """
        Public method to get a reference to a tab widget knowing its ID.
        
        @param id_ id of the tab widget (long)
        @return reference to the tab widget (TabWidget)
        """
        for tw in self.tabWidgets:
            if id(tw) == id_:
                return tw
        return None
Ejemplo n.º 28
0
class SignalTabController(QWidget):
    frame_closed = pyqtSignal(SignalFrameController)
    not_show_again_changed = pyqtSignal()
    signal_created = pyqtSignal(Signal)
    files_dropped = pyqtSignal(list)
    frame_was_dropped = pyqtSignal(int, int)

    @property
    def num_frames(self):
        return self.splitter.count() - 1

    @property
    def signal_frames(self):
        """

        :rtype: list of SignalFrameController
        """
        return [self.splitter.widget(i) for i in range(self.num_frames)]

    @property
    def signal_undo_stack(self):
        return self.undo_stack

    def __init__(self, project_manager, parent=None):
        super().__init__(parent)
        self.ui = Ui_Interpretation()
        self.ui.setupUi(self)
        self.splitter = QSplitter()
        self.splitter.setStyleSheet("QSplitter::handle:vertical {\nmargin: 4px 0px; background-color: qlineargradient(x1:0, y1:0, x2:1, y2:0, \nstop:0 rgba(255, 255, 255, 0), \nstop:0.5 rgba(100, 100, 100, 100), \nstop:1 rgba(255, 255, 255, 0));\n	image: url(:/icons/data/icons/splitter_handle_horizontal.svg);\n}")
        self.splitter.setOrientation(Qt.Vertical)
        self.splitter.setChildrenCollapsible(True)
        self.splitter.setHandleWidth(6)

        placeholder_widget = QWidget()
        placeholder_widget.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
        self.undo_stack = QUndoStack()
        self.project_manager = project_manager

        self.splitter.addWidget(placeholder_widget)
        self.signal_vlay = QVBoxLayout()
        self.signal_vlay.setContentsMargins(0,0,0,0)
        self.signal_vlay.addWidget(self.splitter)
        self.ui.scrlAreaSignals.setLayout(self.signal_vlay)

        self.drag_pos = None

    def on_files_dropped(self, files):
        self.files_dropped.emit(files)

    def close_frame(self, frame:SignalFrameController):
        self.frame_closed.emit(frame)

    def add_signal_frame(self, proto_analyzer):
        sig_frame = SignalFrameController(proto_analyzer, self.undo_stack, self.project_manager, parent=self)
        sframes = self.signal_frames


        if len(proto_analyzer.signal.filename) == 0:
            # new signal from "create signal from selection"
            sig_frame.ui.btnSaveSignal.show()

        self.__create_connects_for_signal_frame(signal_frame=sig_frame)
        sig_frame.signal_created.connect(self.signal_created.emit)
        sig_frame.not_show_again_changed.connect(self.not_show_again_changed.emit)
        sig_frame.ui.lineEditSignalName.setToolTip(self.tr("Sourcefile: ") + proto_analyzer.signal.filename)
        sig_frame.apply_to_all_clicked.connect(self.on_apply_to_all_clicked)

        prev_signal_frame = sframes[-1] if len(sframes) > 0 else None
        if prev_signal_frame is not None and hasattr(prev_signal_frame, "ui"):
            sig_frame.ui.cbProtoView.setCurrentIndex(prev_signal_frame.ui.cbProtoView.currentIndex())

        sig_frame.blockSignals(True)

        if proto_analyzer.signal.qad_demod_file_loaded:
            sig_frame.ui.cbSignalView.setCurrentIndex(1)
            sig_frame.ui.cbSignalView.setDisabled(True)

        self.splitter.insertWidget(self.num_frames, sig_frame)
        sig_frame.blockSignals(False)

        default_view = constants.SETTINGS.value('default_view', 0, int)
        sig_frame.ui.cbProtoView.setCurrentIndex(default_view)

        return sig_frame

    def __create_connects_for_signal_frame(self, signal_frame: SignalFrameController):
        signal_frame.hold_shift = constants.SETTINGS.value('hold_shift_to_drag', False, type=bool)
        signal_frame.drag_started.connect(self.frame_dragged)
        signal_frame.frame_dropped.connect(self.frame_dropped)
        signal_frame.files_dropped.connect(self.on_files_dropped)
        signal_frame.closed.connect(self.close_frame)

    def add_empty_frame(self, filename: str, proto):
        sig_frame = SignalFrameController(proto_analyzer=proto, undo_stack=self.undo_stack,
                                          project_manager=self.project_manager, proto_bits=proto.decoded_proto_bits_str,
                                          parent=self)

        sig_frame.ui.lineEditSignalName.setText(filename)
        sig_frame.setMinimumHeight(sig_frame.height())
        sig_frame.set_empty_frame_visibilities()
        self.__create_connects_for_signal_frame(signal_frame=sig_frame)

        self.splitter.insertWidget(self.num_frames, sig_frame)

        return sig_frame

    def set_frame_numbers(self):
        for i, f in enumerate(self.signal_frames):
            f.ui.lSignalNr.setText("{0:d}:".format(i + 1))

    @pyqtSlot()
    def save_all(self):
        if self.num_frames == 0:
            return

        settings = constants.SETTINGS
        try:
            not_show = settings.value('not_show_save_dialog', type=bool, defaultValue=False)
        except TypeError:
            not_show = False

        if not not_show:
            cb = QCheckBox("Don't ask me again.")
            msg_box = QMessageBox(QMessageBox.Question, self.tr("Confirm saving all signals"),
                                  self.tr("All changed signal files will be overwritten. OK?"))
            msg_box.addButton(QMessageBox.Yes)
            msg_box.addButton(QMessageBox.No)
            msg_box.setCheckBox(cb)

            reply = msg_box.exec()
            not_show_again = cb.isChecked()
            settings.setValue("not_show_save_dialog", not_show_again)
            self.not_show_again_changed.emit()

            if reply != QMessageBox.Yes:
                return

        for f in self.signal_frames:
            if f.signal is None or f.signal.filename == "":
                continue
            f.signal.save()

    @pyqtSlot()
    def close_all(self):
        for f in self.signal_frames:
            f.my_close()

    @pyqtSlot(Signal)
    def on_apply_to_all_clicked(self, signal: Signal):
        for frame in self.signal_frames:
            if frame.signal is not None:
                frame.signal.noise_min_plot = signal.noise_min_plot
                frame.signal.noise_max_plot = signal.noise_max_plot

                frame.signal.block_protocol_update = True
                proto_needs_update = False

                if frame.signal.modulation_type != signal.modulation_type:
                    frame.signal.modulation_type = signal.modulation_type
                    proto_needs_update = True

                if frame.signal.qad_center != signal.qad_center:
                    frame.signal.qad_center = signal.qad_center
                    proto_needs_update = True

                if frame.signal.tolerance != signal.tolerance:
                    frame.signal.tolerance = signal.tolerance
                    proto_needs_update = True

                if frame.signal.noise_threshold != signal.noise_threshold:
                    frame.signal.noise_threshold = signal.noise_threshold
                    proto_needs_update = True

                if frame.signal.bit_len != signal.bit_len:
                    frame.signal.bit_len = signal.bit_len
                    proto_needs_update = True

                frame.signal.block_protocol_update = False

                if proto_needs_update:
                    frame.signal.protocol_needs_update.emit()

    @pyqtSlot(QPoint)
    def frame_dragged(self, pos: QPoint):
        self.drag_pos = pos

    @pyqtSlot(QPoint)
    def frame_dropped(self, pos: QPoint):
        start = self.drag_pos
        if start is None:
            return

        end = pos
        start_index = -1
        end_index = -1
        if self.num_frames > 1:
            for i, w in enumerate(self.signal_frames):
                if w.geometry().contains(start):
                    start_index = i

                if w.geometry().contains(end):
                    end_index = i

        self.swap_frames(start_index, end_index)
        self.frame_was_dropped.emit(start_index, end_index)

    @pyqtSlot(int, int)
    def swap_frames(self, from_index: int, to_index: int):
        if from_index != to_index:
            start_sig_widget = self.splitter.widget(from_index)
            self.splitter.insertWidget(to_index, start_sig_widget)

    @pyqtSlot()
    def on_participant_changed(self):
        for sframe in self.signal_frames:
            sframe.on_participant_changed()
Ejemplo n.º 29
0
class EntryView(BaseTransactionView):
    def _setup(self):
        self._setupUi()
        self.etable = EntryTable(self.model.etable, view=self.tableView)
        self.efbar = EntryFilterBar(model=self.model.filter_bar, view=self.filterBar)
        self.bgraph = Chart(self.model.bargraph, view=self.barGraphView)
        self.lgraph = Chart(self.model.balgraph, view=self.lineGraphView)
        self._setupColumns() # Can only be done after the model has been connected

        self.reconciliationButton.clicked.connect(self.model.toggle_reconciliation_mode)

    def _setupUi(self):
        self.resize(483, 423)
        self.verticalLayout = QVBoxLayout(self)
        self.verticalLayout.setSpacing(0)
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.horizontalLayout = QHBoxLayout()
        self.horizontalLayout.setSpacing(0)
        self.filterBar = RadioBox(self)
        sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.filterBar.sizePolicy().hasHeightForWidth())
        self.filterBar.setSizePolicy(sizePolicy)
        self.horizontalLayout.addWidget(self.filterBar)
        self.horizontalLayout.addItem(horizontalSpacer())
        self.reconciliationButton = QPushButton(tr("Reconciliation"))
        self.reconciliationButton.setCheckable(True)
        self.horizontalLayout.addWidget(self.reconciliationButton)
        self.verticalLayout.addLayout(self.horizontalLayout)
        self.splitterView = QSplitter()
        self.splitterView.setOrientation(Qt.Vertical)
        self.splitterView.setChildrenCollapsible(False)
        self.tableView = TableView(self)
        self.tableView.setAcceptDrops(True)
        self.tableView.setEditTriggers(QAbstractItemView.DoubleClicked|QAbstractItemView.EditKeyPressed)
        self.tableView.setDragEnabled(True)
        self.tableView.setDragDropMode(QAbstractItemView.InternalMove)
        self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.tableView.setSortingEnabled(True)
        self.tableView.horizontalHeader().setHighlightSections(False)
        self.tableView.horizontalHeader().setMinimumSectionSize(18)
        self.tableView.verticalHeader().setVisible(False)
        self.tableView.verticalHeader().setDefaultSectionSize(18)
        self.splitterView.addWidget(self.tableView)
        self.graphView = QStackedWidget(self)
        sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.graphView.sizePolicy().hasHeightForWidth())
        self.graphView.setSizePolicy(sizePolicy)
        self.graphView.setMinimumSize(0, 200)
        self.lineGraphView = LineGraphView()
        self.graphView.addWidget(self.lineGraphView)
        self.barGraphView = BarGraphView()
        self.graphView.addWidget(self.barGraphView)
        self.splitterView.addWidget(self.graphView)
        self.graphView.setCurrentIndex(1)
        self.splitterView.setStretchFactor(0, 1)
        self.splitterView.setStretchFactor(1, 0)
        self.verticalLayout.addWidget(self.splitterView)

    def _setupColumns(self):
        h = self.tableView.horizontalHeader()
        h.setSectionsMovable(True) # column drag & drop reorder

    # --- QWidget override
    def setFocus(self):
        self.etable.view.setFocus()

    # --- Public
    def fitViewsForPrint(self, viewPrinter):
        hidden = self.model.mainwindow.hidden_areas
        viewPrinter.fitTable(self.etable)
        if PaneArea.BottomGraph not in hidden:
            viewPrinter.fit(self.graphView.currentWidget(), 300, 150, expandH=True, expandV=True)

    def restoreSubviewsSize(self):
        graphHeight = self.model.graph_height_to_restore
        if graphHeight:
            splitterHeight = self.splitterView.height()
            sizes = [splitterHeight-graphHeight, graphHeight]
            self.splitterView.setSizes(sizes)

    # --- model --> view
    def refresh_reconciliation_button(self):
        if self.model.can_toggle_reconciliation_mode:
            self.reconciliationButton.setEnabled(True)
            self.reconciliationButton.setChecked(self.model.reconciliation_mode)
        else:
            self.reconciliationButton.setEnabled(False)
            self.reconciliationButton.setChecked(False)

    def show_bar_graph(self):
        self.graphView.setCurrentIndex(1)

    def show_line_graph(self):
        self.graphView.setCurrentIndex(0)

    def update_visibility(self):
        hidden = self.model.mainwindow.hidden_areas
        self.graphView.setHidden(PaneArea.BottomGraph in hidden)
Ejemplo n.º 30
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()
Ejemplo n.º 31
0
class ChartWidget(QWidget):
    def __init__(self, *args, **kwargs):
        super(ChartWidget, self).__init__(*args, **kwargs)
        self.resize(800, 600)
        layout = QVBoxLayout(self)
        layout.setContentsMargins(0, 0, 0, 0)
        self.splitter = QSplitter(self)
        layout.addWidget(self.splitter)
        self.splitter.setOrientation(Qt.Horizontal)
        self.splitter.setStretchFactor(0, 2)
        self.splitter.setStretchFactor(1, 3)
        self.splitter.setStretchFactor(2, 5)
        self.splitter.setAutoFillBackground(True)
        self.splitter.setChildrenCollapsible(False)
        self.splitter.setHandleWidth(2)
        # 分类
        self.classifyWidget = ClassifyWidget(self)
        self.splitter.addWidget(self.classifyWidget)
        # 代码
        self.codeWidget = CodeWidget(self)
        self.splitter.addWidget(self.codeWidget)

        # 等待界面
        self.loadingWidget = LoadingWidget(self, visible=False)
        self.loadingWidget.resize(self.size())

        # 绑定信号槽
        self.classifyWidget.fileSelected.connect(self.codeWidget.openFile)
        self.codeWidget.runSignal.connect(self.onRun)

    def onRun(self, text):
        self.setEnabled(False)
        self.loadingWidget.show()
        # 解析json生成view并添加
        if hasattr(self, "previewView"):
            # 删除旧view
            self.previewView.setParent(None)
            self.previewView.setVisible(False)
            self.previewView.hide()
            self.previewView.deleteLater()
            del self.previewView
        self.previewView = self.getChartView(text)
        if isinstance(self.previewView, QChartView):
            self.splitter.addWidget(self.previewView)
        else:
            QMessageBox.warning(self, "提示", self.previewView)
            del self.previewView
        self.setEnabled(True)
        self.loadingWidget.close()

    def getChartView(self, text):
        try:
            return ChartView(text)
        except Exception as e:
            return str(e)

    def resizeEvent(self, event):
        super(ChartWidget, self).resizeEvent(event)
        self.loadingWidget.resize(self.size())

    def closeEvent(self, event):
        self.loadingWidget.close()
        self.loadingWidget.deleteLater()
        del self.loadingWidget
        super(ChartWidget, self).closeEvent(event)
Ejemplo n.º 32
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()
Ejemplo n.º 33
0
class MainGUI(QMainWindow):
    """The main GUI for azimuthal integration."""

    _root_dir = osp.dirname(osp.abspath(__file__))

    start_sgn = pyqtSignal()
    stop_sgn = pyqtSignal()
    quit_sgn = pyqtSignal()

    _db = RedisConnection()

    _WIDTH, _HEIGHT = config['GUI_MAIN_GUI_SIZE']

    def __init__(self, pause_ev, close_ev):
        """Initialization."""
        super().__init__()

        self._pause_ev = pause_ev
        self._close_ev = close_ev
        self._input_update_ev = Event()
        self._input = MpInQueue(self._input_update_ev, pause_ev, close_ev)

        self._pulse_resolved = config["PULSE_RESOLVED"]
        self._require_geometry = config["REQUIRE_GEOMETRY"]
        self._queue = deque(maxlen=1)

        self.setAttribute(Qt.WA_DeleteOnClose)

        self.title = f"EXtra-foam {__version__} ({config['DETECTOR']})"
        self.setWindowTitle(self.title + " - main GUI")

        # *************************************************************
        # Central widget
        # *************************************************************

        self._ctrl_widgets = []  # book-keeping control widgets

        self._cw = QSplitter()
        self._cw.setChildrenCollapsible(False)
        self.setCentralWidget(self._cw)

        self._left_cw_container = QScrollArea()
        self._left_cw_container.setFrameShape(QFrame.NoFrame)
        self._left_cw = QTabWidget()
        self._right_cw_container = QScrollArea()
        self._right_cw_container.setFrameShape(QFrame.NoFrame)
        self._right_cw = QSplitter(Qt.Vertical)
        self._right_cw.setChildrenCollapsible(False)

        self._source_cw = self.createCtrlWidget(DataSourceWidget)
        self._extension_cw = self.createCtrlWidget(ExtensionCtrlWidget)

        self._ctrl_panel_cw = QTabWidget()
        self._analysis_cw = QWidget()
        self._statistics_cw = QWidget()

        self._util_panel_container = QWidget()
        self._util_panel_cw = QTabWidget()

        # *************************************************************
        # Tool bar
        # Note: the order of '_addAction` affect the unittest!!!
        # *************************************************************
        self._tool_bar = self.addToolBar("Control")
        # make icon a bit larger
        self._tool_bar.setIconSize(2 * self._tool_bar.iconSize())

        self._start_at = self._addAction("Start bridge", "start.png")
        self._start_at.triggered.connect(self.onStart)

        self._stop_at = self._addAction("Stop bridge", "stop.png")
        self._stop_at.triggered.connect(self.onStop)
        self._stop_at.setEnabled(False)

        self._tool_bar.addSeparator()

        image_tool_at = self._addAction("Image tool", "image_tool.png")
        image_tool_at.triggered.connect(lambda: (self._image_tool.show(
        ), self._image_tool.activateWindow()))

        open_poi_window_at = self._addAction("Pulse-of-interest", "poi.png")
        open_poi_window_at.triggered.connect(
            functools.partial(self.onOpenPlotWindow, PulseOfInterestWindow))
        if not self._pulse_resolved:
            open_poi_window_at.setEnabled(False)

        pump_probe_window_at = self._addAction("Pump-probe", "pump-probe.png")
        pump_probe_window_at.triggered.connect(
            functools.partial(self.onOpenPlotWindow, PumpProbeWindow))

        open_correlation_window_at = self._addAction("Correlation",
                                                     "correlation.png")
        open_correlation_window_at.triggered.connect(
            functools.partial(self.onOpenPlotWindow, CorrelationWindow))

        open_histogram_window_at = self._addAction("Histogram",
                                                   "histogram.png")
        open_histogram_window_at.triggered.connect(
            functools.partial(self.onOpenPlotWindow, HistogramWindow))

        open_bin2d_window_at = self._addAction("Binning", "binning.png")
        open_bin2d_window_at.triggered.connect(
            functools.partial(self.onOpenPlotWindow, BinningWindow))

        self._tool_bar.addSeparator()

        open_file_stream_window_at = self._addAction("File stream",
                                                     "file_stream.png")
        open_file_stream_window_at.triggered.connect(
            lambda: self.onOpenSatelliteWindow(FileStreamWindow))

        open_about_at = self._addAction("About EXtra-foam", "about.png")
        open_about_at.triggered.connect(
            lambda: self.onOpenSatelliteWindow(AboutWindow))

        # *************************************************************
        # Miscellaneous
        # *************************************************************

        # book-keeping opened windows
        self._plot_windows = WeakKeyDictionary()
        self._satellite_windows = WeakKeyDictionary()

        self._gui_logger = GuiLogger(parent=self)
        logger.addHandler(self._gui_logger)

        self._analysis_setup_manager = AnalysisSetupManager()

        self._thread_logger = ThreadLoggerBridge()
        self.quit_sgn.connect(self._thread_logger.stop)
        self._thread_logger_t = QThread()
        self._thread_logger.moveToThread(self._thread_logger_t)
        self._thread_logger_t.started.connect(self._thread_logger.recv)
        self._thread_logger.connectToMainThread(self)

        # For real time plot
        self._running = False
        self._plot_timer = QTimer()
        self._plot_timer.timeout.connect(self.updateAll)

        # For checking the connection to the Redis server
        self._redis_timer = QTimer()
        self._redis_timer.timeout.connect(self.pingRedisServer)

        self.__redis_connection_fails = 0

        self._mon_proxy = MonProxy()

        # *************************************************************
        # control widgets
        # *************************************************************

        # analysis control widgets
        self.analysis_ctrl_widget = self.createCtrlWidget(AnalysisCtrlWidget)
        self.fom_filter_ctrl_widget = self.createCtrlWidget(
            FomFilterCtrlWidget)

        # *************************************************************
        # status bar
        # *************************************************************

        # StatusBar to display topic name
        self.statusBar().showMessage(f"TOPIC: {config['TOPIC']}")
        self.statusBar().setStyleSheet("QStatusBar{font-weight:bold;}")

        # ImageToolWindow is treated differently since it is the second
        # control window.
        self._image_tool = ImageToolWindow(
            queue=self._queue,
            pulse_resolved=self._pulse_resolved,
            require_geometry=self._require_geometry,
            parent=self)

        self.initUI()
        self.initConnections()
        self.updateMetaData()
        self._analysis_setup_manager.onInit()

        self.setMinimumSize(640, 480)
        self.resize(self._WIDTH, self._HEIGHT)

        self.show()

    def createCtrlWidget(self, widget_class):
        widget = widget_class(pulse_resolved=self._pulse_resolved,
                              require_geometry=self._require_geometry,
                              parent=self)
        self._ctrl_widgets.append(widget)
        return widget

    def initUI(self):
        self.initLeftUI()
        self.initRightUI()

        self._cw.addWidget(self._left_cw_container)
        self._cw.addWidget(self._right_cw_container)
        self._cw.setSizes([self._WIDTH * 0.6, self._WIDTH * 0.4])

    def initLeftUI(self):
        self._left_cw.setTabPosition(QTabWidget.TabPosition.West)

        self._left_cw.addTab(self._source_cw, "Data source")
        self._left_cw_container.setWidget(self._left_cw)
        self._left_cw_container.setWidgetResizable(True)

        self._left_cw.addTab(self._extension_cw, "Extension")

    def initRightUI(self):
        self.initCtrlUI()
        self.initUtilUI()

        self._right_cw.addWidget(self._ctrl_panel_cw)
        self._right_cw.addWidget(self._util_panel_container)

        self._right_cw_container.setWidget(self._right_cw)
        self._right_cw_container.setWidgetResizable(True)

    def initCtrlUI(self):
        self.initGeneralAnalysisUI()

        self._ctrl_panel_cw.addTab(self._analysis_cw, "General analysis setup")

    def initGeneralAnalysisUI(self):
        layout = QVBoxLayout()
        layout.addWidget(self.analysis_ctrl_widget)
        layout.addWidget(self.fom_filter_ctrl_widget)
        self._analysis_cw.setLayout(layout)

    def initUtilUI(self):
        self._util_panel_cw.addTab(self._gui_logger.widget, "Logger")
        self._util_panel_cw.addTab(self._analysis_setup_manager,
                                   "Analysis Setup Manager")
        self._util_panel_cw.setTabPosition(QTabWidget.TabPosition.South)

        layout = QVBoxLayout()
        layout.addWidget(self._util_panel_cw)
        self._util_panel_container.setLayout(layout)

    def initConnections(self):
        self._analysis_setup_manager.load_metadata_sgn.connect(
            self.loadMetaData)

    def connect_input_to_output(self, output):
        self._input.connect(output)

    @profiler("Update Plots", process_time=True)
    def updateAll(self):
        """Update all the plots in the main and child windows."""
        if not self._running:
            return

        try:
            processed = self._input.get()
            self._queue.append(processed)
        except Empty:
            return

        # clear the previous plots no matter what comes next
        # for w in self._plot_windows.keys():
        #     w.reset()

        data = self._queue[0]

        self._image_tool.updateWidgetsF()
        for w in itertools.chain(self._plot_windows):
            try:
                w.updateWidgetsF()
            except Exception as e:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                logger.debug(
                    repr(traceback.format_tb(exc_traceback)) + repr(e))
                logger.error(f"[Update plots] {repr(e)}")

        logger.debug(f"Plot train with ID: {data.tid}")

    def pingRedisServer(self):
        try:
            self._db.ping()
            if self.__redis_connection_fails > 0:
                # Note: Indeed, we do not have mechanism to recover from
                #       a Redis server crash. It is recommended to restart
                #       Extra-foam if you encounter this situation.
                logger.info("Reconnect to the Redis server!")
                self.__redis_connection_fails = 0
        except ConnectionError:
            self.__redis_connection_fails += 1
            rest_attempts = config["REDIS_MAX_PING_ATTEMPTS"] - \
                self.__redis_connection_fails

            if rest_attempts > 0:
                logger.warning(f"No response from the Redis server! Shut "
                               f"down after {rest_attempts} attempts ...")
            else:
                logger.warning(f"No response from the Redis server! "
                               f"Shutting down!")
                self.close()

    def _addAction(self, description, filename):
        icon = QIcon(osp.join(self._root_dir, "icons/" + filename))
        action = QAction(icon, description, self)
        self._tool_bar.addAction(action)
        return action

    def onOpenPlotWindow(self, instance_type):
        """Open a plot window if it does not exist.

        Otherwise bring the opened window to the table top.
        """
        if self.checkWindowExistence(instance_type, self._plot_windows):
            return

        return instance_type(self._queue,
                             pulse_resolved=self._pulse_resolved,
                             require_geometry=self._require_geometry,
                             parent=self)

    def onOpenSatelliteWindow(self, instance_type):
        """Open a satellite window if it does not exist.

        Otherwise bring the opened window to the table top.
        """
        if self.checkWindowExistence(instance_type, self._satellite_windows):
            return
        return instance_type(parent=self)

    def checkWindowExistence(self, instance_type, windows):
        for key in windows:
            if isinstance(key, instance_type):
                key.activateWindow()
                return True
        return False

    def registerWindow(self, instance):
        self._plot_windows[instance] = 1

    def unregisterWindow(self, instance):
        del self._plot_windows[instance]

    def registerSatelliteWindow(self, instance):
        self._satellite_windows[instance] = 1

    def unregisterSatelliteWindow(self, instance):
        del self._satellite_windows[instance]

    @property
    def input(self):
        return self._input

    def start(self):
        """Start running.

        ProcessWorker interface.
        """
        self._thread_logger_t.start()
        self._plot_timer.start(config["GUI_PLOT_UPDATE_TIMER"])
        self._redis_timer.start(config["REDIS_PING_ATTEMPT_INTERVAL"])
        self._input.start()

    def onStart(self):
        if not self.updateMetaData():
            return

        self.start_sgn.emit()

        self._start_at.setEnabled(False)
        self._stop_at.setEnabled(True)

        for widget in self._ctrl_widgets:
            widget.onStart()
        for win in self._plot_windows:
            win.onStart()
        self._image_tool.onStart()
        self._analysis_setup_manager.onStart()

        self._running = True  # starting to update plots
        self._input_update_ev.set()  # notify update

    def onStop(self):
        """Actions taken before the end of a 'run'."""
        self._running = False

        self.stop_sgn.emit()

        # TODO: wait for some signal

        self._start_at.setEnabled(True)
        self._stop_at.setEnabled(False)

        for widget in self._ctrl_widgets:
            widget.onStop()
        for win in self._plot_windows:
            win.onStop()
        self._image_tool.onStop()
        self._analysis_setup_manager.onStop()

    def updateMetaData(self):
        """Update metadata from all the ctrl widgets.

        :returns bool: True if all metadata successfully parsed
            and emitted, otherwise False.
        """
        for widget in self._ctrl_widgets:
            if not widget.updateMetaData():
                return False

        for win in self._plot_windows:
            if not win.updateMetaData():
                return False

        return self._image_tool.updateMetaData()

    def loadMetaData(self):
        """Load metadata from Redis and set child control widgets."""
        for widget in self._ctrl_widgets:
            widget.loadMetaData()

        for win in self._plot_windows:
            win.loadMetaData()

        self._image_tool.loadMetaData()

    @pyqtSlot(str, str)
    def onLogMsgReceived(self, ch, msg):
        if ch == 'log:debug':
            logger.debug(msg)
        elif ch == 'log:info':
            logger.info(msg)
        elif ch == 'log:warning':
            logger.warning(msg)
        elif ch == 'log:error':
            logger.error(msg)

    def closeEvent(self, QCloseEvent):
        # prevent from logging in the GUI when it has been closed
        logger.removeHandler(self._gui_logger)

        # tell all processes to close
        self._close_ev.set()

        # clean up the logger thread
        self.quit_sgn.emit()
        self._thread_logger_t.quit()
        self._thread_logger_t.wait()

        # shutdown pipeline workers and Redis server
        shutdown_all()

        self._image_tool.close()
        for window in list(
                itertools.chain(self._plot_windows, self._satellite_windows)):
            # Close all open child windows to make sure their resources
            # (any running process etc.) are released gracefully. This
            # is especially necessary for the case when file stream was
            # still ongoing when the main GUI was closed.
            window.close()

        super().closeEvent(QCloseEvent)
Ejemplo n.º 34
0
class Listspace(QSplitter, ViewManager):
    """
    Class implementing the listspace viewmanager class.
    
    @signal changeCaption(str) emitted if a change of the caption is necessary
    @signal editorChanged(str) emitted when the current editor has changed
    @signal editorChangedEd(Editor) emitted when the current editor has changed
    @signal lastEditorClosed() emitted after the last editor window was closed
    @signal editorOpened(str) emitted after an editor window was opened
    @signal editorOpenedEd(Editor) emitted after an editor window was opened
    @signal editorClosed(str) emitted just before an editor window gets closed
    @signal editorClosedEd(Editor) emitted just before an editor window gets
        closed
    @signal editorRenamed(str) emitted after an editor was renamed
    @signal editorRenamedEd(Editor) emitted after an editor was renamed
    @signal editorSaved(str) emitted after an editor window was saved
    @signal editorSavedEd(Editor) emitted after an editor window was saved
    @signal checkActions(Editor) emitted when some actions should be checked
        for their status
    @signal cursorChanged(Editor) emitted after the cursor position of the
        active window has changed
    @signal breakpointToggled(Editor) emitted when a breakpoint is toggled.
    @signal bookmarkToggled(Editor) emitted when a bookmark is toggled.
    @signal syntaxerrorToggled(Editor) emitted when a syntax error is toggled.
    @signal previewStateChanged(bool) emitted to signal a change in the
        preview state
    @signal editorLanguageChanged(Editor) emitted to signal a change of an
        editors language
    @signal editorTextChanged(Editor) emitted to signal a change of an
        editor's text
    @signal editorLineChanged(str,int) emitted to signal a change of an
        editor's current line (line is given one based)
    """
    changeCaption = pyqtSignal(str)
    editorChanged = pyqtSignal(str)
    editorChangedEd = pyqtSignal(Editor)
    lastEditorClosed = pyqtSignal()
    editorOpened = pyqtSignal(str)
    editorOpenedEd = pyqtSignal(Editor)
    editorClosed = pyqtSignal(str)
    editorClosedEd = pyqtSignal(Editor)
    editorRenamed = pyqtSignal(str)
    editorRenamedEd = pyqtSignal(Editor)
    editorSaved = pyqtSignal(str)
    editorSavedEd = pyqtSignal(Editor)
    checkActions = pyqtSignal(Editor)
    cursorChanged = pyqtSignal(Editor)
    breakpointToggled = pyqtSignal(Editor)
    bookmarkToggled = pyqtSignal(Editor)
    syntaxerrorToggled = pyqtSignal(Editor)
    previewStateChanged = pyqtSignal(bool)
    editorLanguageChanged = pyqtSignal(Editor)
    editorTextChanged = pyqtSignal(Editor)
    editorLineChanged = pyqtSignal(str, int)

    def __init__(self, parent):
        """
        Constructor
        
        @param parent parent widget (QWidget)
        """
        self.stacks = []

        QSplitter.__init__(self, parent)
        ViewManager.__init__(self)
        self.setChildrenCollapsible(False)

        self.viewlist = QListWidget(self)
        policy = self.viewlist.sizePolicy()
        policy.setHorizontalPolicy(QSizePolicy.Ignored)
        self.viewlist.setSizePolicy(policy)
        self.addWidget(self.viewlist)
        self.viewlist.setContextMenuPolicy(Qt.CustomContextMenu)
        self.viewlist.currentRowChanged.connect(self.__showSelectedView)
        self.viewlist.customContextMenuRequested.connect(self.__showMenu)

        self.stackArea = QSplitter(self)
        self.stackArea.setChildrenCollapsible(False)
        self.addWidget(self.stackArea)
        self.stackArea.setOrientation(Qt.Vertical)
        stack = StackedWidget(self.stackArea)
        self.stackArea.addWidget(stack)
        self.stacks.append(stack)
        self.currentStack = stack
        stack.currentChanged.connect(self.__currentChanged)
        stack.installEventFilter(self)
        self.setSizes([int(self.width() * 0.2), int(self.width() * 0.8)])
        # 20% for viewlist, 80% for the editors
        self.__inRemoveView = False

        self.__initMenu()
        self.contextMenuEditor = None
        self.contextMenuIndex = -1

    def __initMenu(self):
        """
        Private method to initialize the viewlist context menu.
        """
        self.__menu = QMenu(self)
        self.__menu.addAction(UI.PixmapCache.getIcon("tabClose.png"),
                              self.tr('Close'), self.__contextMenuClose)
        self.closeOthersMenuAct = self.__menu.addAction(
            UI.PixmapCache.getIcon("tabCloseOther.png"),
            self.tr("Close Others"), self.__contextMenuCloseOthers)
        self.__menu.addAction(self.tr('Close All'), self.__contextMenuCloseAll)
        self.__menu.addSeparator()
        self.saveMenuAct = self.__menu.addAction(
            UI.PixmapCache.getIcon("fileSave.png"), self.tr('Save'),
            self.__contextMenuSave)
        self.__menu.addAction(UI.PixmapCache.getIcon("fileSaveAs.png"),
                              self.tr('Save As...'), self.__contextMenuSaveAs)
        self.__menu.addAction(UI.PixmapCache.getIcon("fileSaveAll.png"),
                              self.tr('Save All'), self.__contextMenuSaveAll)
        self.__menu.addSeparator()
        self.openRejectionsMenuAct = self.__menu.addAction(
            self.tr("Open 'rejection' file"), self.__contextMenuOpenRejections)
        self.__menu.addSeparator()
        self.__menu.addAction(UI.PixmapCache.getIcon("print.png"),
                              self.tr('Print'), self.__contextMenuPrintFile)
        self.__menu.addSeparator()
        self.copyPathAct = self.__menu.addAction(
            self.tr("Copy Path to Clipboard"),
            self.__contextMenuCopyPathToClipboard)

    def __showMenu(self, point):
        """
        Private slot to handle the customContextMenuRequested signal of
        the viewlist.
        
        @param point position to open the menu at (QPoint)
        """
        if self.editors:
            itm = self.viewlist.itemAt(point)
            if itm is not None:
                row = self.viewlist.row(itm)
                self.contextMenuEditor = self.editors[row]
                self.contextMenuIndex = row
                if self.contextMenuEditor:
                    self.saveMenuAct.setEnabled(
                        self.contextMenuEditor.isModified())
                    fileName = self.contextMenuEditor.getFileName()
                    self.copyPathAct.setEnabled(bool(fileName))
                    if fileName:
                        rej = "{0}.rej".format(fileName)
                        self.openRejectionsMenuAct.setEnabled(
                            os.path.exists(rej))
                    else:
                        self.openRejectionsMenuAct.setEnabled(False)

                    self.closeOthersMenuAct.setEnabled(
                        self.viewlist.count() > 1)

                    self.__menu.popup(self.viewlist.mapToGlobal(point))

    def canCascade(self):
        """
        Public method to signal if cascading of managed windows is available.
        
        @return flag indicating cascading of windows is available
        """
        return False

    def canTile(self):
        """
        Public method to signal if tiling of managed windows is available.
        
        @return flag indicating tiling of windows is available
        """
        return False

    def canSplit(self):
        """
        public method to signal if splitting of the view is available.
        
        @return flag indicating splitting of the view is available.
        """
        return True

    def tile(self):
        """
        Public method to tile the managed windows.
        """
        pass

    def cascade(self):
        """
        Public method to cascade the managed windows.
        """
        pass

    def _removeAllViews(self):
        """
        Protected method to remove all views (i.e. windows).
        """
        self.viewlist.clear()
        for win in self.editors:
            for stack in self.stacks:
                if stack.hasEditor(win):
                    stack.removeWidget(win)
                    break
            win.closeIt()

    def _removeView(self, win):
        """
        Protected method to remove a view (i.e. window).
        
        @param win editor window to be removed
        """
        self.__inRemoveView = True
        ind = self.editors.index(win)
        itm = self.viewlist.takeItem(ind)
        if itm:
            del itm
        for stack in self.stacks:
            if stack.hasEditor(win):
                stack.removeWidget(win)
                break
        win.closeIt()
        self.__inRemoveView = False
        if ind > 0:
            ind -= 1
        else:
            if len(self.editors) > 1:
                ind = 1
            else:
                return
        stack.setCurrentWidget(stack.firstEditor())
        self._showView(self.editors[ind].parent())

        aw = self.activeWindow()
        fn = aw and aw.getFileName() or None
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
            self.editorLineChanged.emit(fn, aw.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(aw)

    def _addView(self, win, fn=None, noName="", next=False):
        """
        Protected method to add a view (i.e. window).
        
        @param win editor assembly to be added
        @param fn filename of this editor (string)
        @param noName name to be used for an unnamed editor (string)
        @param next flag indicating to add the view next to the current
            view (bool)
        """
        editor = win.getEditor()
        if fn is None:
            if not noName:
                self.untitledCount += 1
                noName = self.tr("Untitled {0}").format(self.untitledCount)
            self.viewlist.addItem(noName)
            editor.setNoName(noName)
        else:
            txt = os.path.basename(fn)
            if not QFileInfo(fn).isWritable():
                txt = self.tr("{0} (ro)").format(txt)
            itm = QListWidgetItem(txt)
            itm.setToolTip(fn)
            self.viewlist.addItem(itm)
        self.currentStack.addWidget(win)
        self.currentStack.setCurrentWidget(win)
        editor.captionChanged.connect(self.__captionChange)
        editor.cursorLineChanged.connect(self.__cursorLineChanged)

        index = self.editors.index(editor)
        self.viewlist.setCurrentRow(index)
        editor.setFocus()
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
            self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(editor)

    def __captionChange(self, cap, editor):
        """
        Private method to handle caption change signals from the editor.
        
        Updates the listwidget text to reflect the new caption information.
        
        @param cap Caption for the editor (string)
        @param editor Editor to update the caption for
        """
        fn = editor.getFileName()
        if fn:
            self.setEditorName(editor, fn)

    def __cursorLineChanged(self, lineno):
        """
        Private slot to handle a change of the current editor's cursor line.
        
        @param lineno line number of the current editor's cursor (zero based)
        """
        editor = self.sender()
        if editor:
            fn = editor.getFileName()
            if fn:
                self.editorLineChanged.emit(fn, lineno + 1)

    def _showView(self, win, fn=None):
        """
        Protected method to show a view (i.e. window).
        
        @param win editor assembly to be shown
        @param fn filename of this editor (string)
        """
        editor = win.getEditor()
        for stack in self.stacks:
            if stack.hasEditor(editor):
                stack.setCurrentWidget(win)
                self.currentStack = stack
                break
        index = self.editors.index(editor)
        self.viewlist.setCurrentRow(index)
        editor.setFocus()
        fn = editor.getFileName()
        if fn:
            self.changeCaption.emit(fn)
            self.editorChanged.emit(fn)
            self.editorLineChanged.emit(fn, editor.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(editor)

    def __showSelectedView(self, row):
        """
        Private slot called to show a view selected in the list.
        
        @param row row number of the item clicked on (integer)
        """
        if row != -1:
            self._showView(self.editors[row].parent())
            self._checkActions(self.editors[row])

    def activeWindow(self):
        """
        Public method to return the active (i.e. current) window.
        
        @return reference to the active editor
        """
        return self.currentStack.currentWidget()

    def showWindowMenu(self, windowMenu):
        """
        Public method to set up the viewmanager part of the Window menu.
        
        @param windowMenu reference to the window menu
        """
        pass

    def _initWindowActions(self):
        """
        Protected method to define the user interface actions for window
        handling.
        """
        pass

    def setEditorName(self, editor, newName):
        """
        Public method to change the displayed name of the editor.
        
        @param editor editor window to be changed
        @param newName new name to be shown (string)
        """
        if newName:
            currentRow = self.viewlist.currentRow()
            index = self.editors.index(editor)
            txt = os.path.basename(newName)
            if not QFileInfo(newName).isWritable():
                txt = self.tr("{0} (ro)").format(txt)
            itm = self.viewlist.item(index)
            itm.setText(txt)
            itm.setToolTip(newName)
            self.viewlist.setCurrentRow(currentRow)
            self.changeCaption.emit(newName)

    def _modificationStatusChanged(self, m, editor):
        """
        Protected slot to handle the modificationStatusChanged signal.
        
        @param m flag indicating the modification status (boolean)
        @param editor editor window changed
        """
        currentRow = self.viewlist.currentRow()
        index = self.editors.index(editor)
        keys = []
        if m:
            keys.append("fileModified.png")
        if editor.hasSyntaxErrors():
            keys.append("syntaxError22.png")
        elif editor.hasWarnings():
            keys.append("warning22.png")
        if not keys:
            keys.append("empty.png")
        self.viewlist.item(index).setIcon(UI.PixmapCache.getCombinedIcon(keys))
        self.viewlist.setCurrentRow(currentRow)
        self._checkActions(editor)

    def _syntaxErrorToggled(self, editor):
        """
        Protected slot to handle the syntaxerrorToggled signal.
        
        @param editor editor that sent the signal
        """
        currentRow = self.viewlist.currentRow()
        index = self.editors.index(editor)
        keys = []
        if editor.isModified():
            keys.append("fileModified.png")
        if editor.hasSyntaxErrors():
            keys.append("syntaxError22.png")
        elif editor.hasWarnings():
            keys.append("warning22.png")
        if not keys:
            keys.append("empty.png")
        self.viewlist.item(index).setIcon(UI.PixmapCache.getCombinedIcon(keys))
        self.viewlist.setCurrentRow(currentRow)

        ViewManager._syntaxErrorToggled(self, editor)

    def addSplit(self):
        """
        Public method used to split the current view.
        """
        stack = StackedWidget(self.stackArea)
        stack.show()
        self.stackArea.addWidget(stack)
        self.stacks.append(stack)
        self.currentStack = stack
        stack.currentChanged.connect(self.__currentChanged)
        stack.installEventFilter(self)
        if self.stackArea.orientation() == Qt.Horizontal:
            size = self.stackArea.width()
        else:
            size = self.stackArea.height()
        self.stackArea.setSizes([int(size / len(self.stacks))] *
                                len(self.stacks))
        self.splitRemoveAct.setEnabled(True)
        self.nextSplitAct.setEnabled(True)
        self.prevSplitAct.setEnabled(True)

    def removeSplit(self):
        """
        Public method used to remove the current split view.
        
        @return flag indicating successfull removal
        """
        if len(self.stacks) > 1:
            stack = self.currentStack
            res = True
            savedEditors = stack.editors[:]
            for editor in savedEditors:
                res &= self.closeEditor(editor)
            if res:
                try:
                    i = self.stacks.index(stack)
                except ValueError:
                    return True
                if i == len(self.stacks) - 1:
                    i -= 1
                self.stacks.remove(stack)
                stack.close()
                self.currentStack = self.stacks[i]
                if len(self.stacks) == 1:
                    self.splitRemoveAct.setEnabled(False)
                    self.nextSplitAct.setEnabled(False)
                    self.prevSplitAct.setEnabled(False)
                return True

        return False

    def getSplitOrientation(self):
        """
        Public method to get the orientation of the split view.
        
        @return orientation of the split (Qt.Horizontal or Qt.Vertical)
        """
        return self.stackArea.orientation()

    def setSplitOrientation(self, orientation):
        """
        Public method used to set the orientation of the split view.
        
        @param orientation orientation of the split
                (Qt.Horizontal or Qt.Vertical)
        """
        self.stackArea.setOrientation(orientation)

    def nextSplit(self):
        """
        Public slot used to move to the next split.
        """
        aw = self.activeWindow()
        _hasFocus = aw and aw.hasFocus()
        ind = self.stacks.index(self.currentStack) + 1
        if ind == len(self.stacks):
            ind = 0

        self.currentStack = self.stacks[ind]
        if _hasFocus:
            aw = self.activeWindow()
            if aw:
                aw.setFocus()

        index = self.editors.index(self.currentStack.currentWidget())
        self.viewlist.setCurrentRow(index)

    def prevSplit(self):
        """
        Public slot used to move to the previous split.
        """
        aw = self.activeWindow()
        _hasFocus = aw and aw.hasFocus()
        ind = self.stacks.index(self.currentStack) - 1
        if ind == -1:
            ind = len(self.stacks) - 1

        self.currentStack = self.stacks[ind]
        if _hasFocus:
            aw = self.activeWindow()
            if aw:
                aw.setFocus()
        index = self.editors.index(self.currentStack.currentWidget())
        self.viewlist.setCurrentRow(index)

    def __contextMenuClose(self):
        """
        Private method to close the selected editor.
        """
        if self.contextMenuEditor:
            self.closeEditorWindow(self.contextMenuEditor)

    def __contextMenuCloseOthers(self):
        """
        Private method to close the other editors.
        """
        index = self.contextMenuIndex
        for i in list(range(self.viewlist.count() - 1, index, -1)) + \
                list(range(index - 1, -1, -1)):
            editor = self.editors[i]
            self.closeEditorWindow(editor)

    def __contextMenuCloseAll(self):
        """
        Private method to close all editors.
        """
        savedEditors = self.editors[:]
        for editor in savedEditors:
            self.closeEditorWindow(editor)

    def __contextMenuSave(self):
        """
        Private method to save the selected editor.
        """
        if self.contextMenuEditor:
            self.saveEditorEd(self.contextMenuEditor)

    def __contextMenuSaveAs(self):
        """
        Private method to save the selected editor to a new file.
        """
        if self.contextMenuEditor:
            self.saveAsEditorEd(self.contextMenuEditor)

    def __contextMenuSaveAll(self):
        """
        Private method to save all editors.
        """
        self.saveEditorsList(self.editors)

    def __contextMenuOpenRejections(self):
        """
        Private slot to open a rejections file associated with the selected
        editor.
        """
        if self.contextMenuEditor:
            fileName = self.contextMenuEditor.getFileName()
            if fileName:
                rej = "{0}.rej".format(fileName)
                if os.path.exists(rej):
                    self.openSourceFile(rej)

    def __contextMenuPrintFile(self):
        """
        Private method to print the selected editor.
        """
        if self.contextMenuEditor:
            self.printEditor(self.contextMenuEditor)

    def __contextMenuCopyPathToClipboard(self):
        """
        Private method to copy the file name of the selected editor to the
        clipboard.
        """
        if self.contextMenuEditor:
            fn = self.contextMenuEditor.getFileName()
            if fn:
                cb = QApplication.clipboard()
                cb.setText(fn)

    def __currentChanged(self, index):
        """
        Private slot to handle the currentChanged signal.
        
        @param index index of the current editor
        """
        if index == -1 or not self.editors:
            return

        editor = self.activeWindow()
        if editor is None:
            return

        self._checkActions(editor)
        editor.setFocus()
        fn = editor.getFileName()
        if fn:
            self.changeCaption.emit(fn)
            if not self.__inRemoveView:
                self.editorChanged.emit(fn)
                self.editorLineChanged.emit(fn,
                                            editor.getCursorPosition()[0] + 1)
        else:
            self.changeCaption.emit("")
        self.editorChangedEd.emit(editor)

        cindex = self.editors.index(editor)
        self.viewlist.setCurrentRow(cindex)

    def eventFilter(self, watched, event):
        """
        Public method called to filter the event queue.
        
        @param watched the QObject being watched
        @param event the event that occurred
        @return flag indicating, if we handled the event
        """
        if event.type() == QEvent.MouseButtonPress and \
           not event.button() == Qt.RightButton:
            switched = True
            if isinstance(watched, QStackedWidget):
                switched = watched is not self.currentStack
                self.currentStack = watched
            elif isinstance(watched, QScintilla.Editor.Editor):
                for stack in self.stacks:
                    if stack.hasEditor(watched):
                        switched = stack is not self.currentStack
                        self.currentStack = stack
                        break
            currentWidget = self.currentStack.currentWidget()
            if currentWidget:
                index = self.editors.index(currentWidget)
                self.viewlist.setCurrentRow(index)

            aw = self.activeWindow()
            if aw is not None:
                self._checkActions(aw)
                aw.setFocus()
                fn = aw.getFileName()
                if fn:
                    self.changeCaption.emit(fn)
                    if switched:
                        self.editorChanged.emit(fn)
                        self.editorLineChanged.emit(
                            fn,
                            aw.getCursorPosition()[0] + 1)
                else:
                    self.changeCaption.emit("")
                self.editorChangedEd.emit(aw)

        return False
Ejemplo n.º 35
0
class UI_mainWindow(QMainWindow):
    __tlvInfoGB_ = None
    __tlvPkgListGB_ = None
    __videoGB_ = None
    __videoLabel_ = None
    __audioGB_ = None
    __audioLabel_ = None
    __menubar_ = None
    __winResolution_ = (1280, 720)

    def __init__(self):
        super().__init__()
        # self.setWindowFlag(Qt.FramelessWindowHint)
        self.__vsplitter_ = QSplitter(Qt.Vertical)
        self.__vsplitter_.setChildrenCollapsible(False)  # 拉动分割器至最小,被分割部分不会消失
        self.__vsplitter_.setAutoFillBackground(True)  # 分割器随主窗口大小自适应变化
        self.__hsplitter_ = QSplitter(Qt.Horizontal)
        self.__hsplitter_.setChildrenCollapsible(False)
        self.__hsplitter_.setAutoFillBackground(True)
        self.__initTlvInfoLayout()
        self.__initVideoLayout()
        self.__initAudioLayout()
        self.__initTlvPkgListLayout()
        self.__initMainWindow()

    def __initMainWindow(self):
        availGeometry = QDesktopWidget().availableGeometry()
        self.resize(availGeometry.width() * 0.7, availGeometry.height() * 0.7)
        self.__center()

        self.__menubar_ = UIMenuBar()
        self.setMenuBar(self.__menubar_)
        self.setCentralWidget(self.__hsplitter_)
        self.setWindowTitle('tlv分析工具')

    def __center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def __initTlvInfoLayout(self):
        tlvinfo = UITlvInfo('TLV INFO')
        tlvinfo.setMinimumWidth(250)
        self.__hsplitter_.addWidget(tlvinfo)

    def __initTlvPkgListLayout(self):
        self.__tlvpkglist_ = UITlvPkgList('Tlv Pkg List')
        for i in range(30):
            pkg_level = UITlvPkgList.PKG_LEVEL.NORMAL
            if i % 10 == 0:
                pkg_level = UITlvPkgList.PKG_LEVEL.LOSS
            elif i % 11 == 0:
                pkg_level = UITlvPkgList.PKG_LEVEL.ORDER
            self.__tlvpkglist_.insertTlvPkgItem(
                "H264 RTP Header #{:<10d}".format(i), '''this is an example!
        this is an apple!
        this is a banana!
        this is a pen!
        end''', pkg_level)
        self.__hsplitter_.addWidget(self.__tlvpkglist_)

    def __initVideoLayout(self):
        label = UIVideoLabel()
        label.setMinimumSize(640, 480)
        hlayout = QHBoxLayout()
        hlayout.addStretch(1)
        hlayout.addWidget(label)
        hlayout.addStretch(1)
        vlayout = QVBoxLayout()
        # vlayout.addStretch(1)
        vlayout.addLayout(hlayout)
        # vlayout.addStretch(1)
        video = QGroupBox("Video Replay")
        video.setLayout(vlayout)
        self.__vsplitter_.addWidget(video)

    def __initAudioLayout(self):
        hlayout = QHBoxLayout()
        hlayout.addStretch(1)
        hlayout.addWidget(QLabel('Audio Replay'))
        hlayout.addStretch(1)

        audio = QGroupBox("Audio Replay")
        audio.setLayout(hlayout)
        self.__vsplitter_.addWidget(audio)
        self.__vsplitter_.setSizes([420, 300])
        self.__hsplitter_.addWidget(self.__vsplitter_)
Ejemplo n.º 36
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)
Ejemplo n.º 37
0
class Ui_MainWindow(object):
    def _setupMenu(self, MainWindow):
        self.menubar = MainWindow.menuBar()
        self.menubar.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.menuFile = QtWidgets.QMenu(self.menubar)
        self.menuFile.setObjectName("menuFile")
        self.menuhelp = QtWidgets.QMenu(self.menubar)
        self.menuhelp.setObjectName("menuhelp")
        MainWindow.setMenuBar(self.menubar)
        self.actionExit = QtWidgets.QAction()
        self.actionExit.setObjectName("actionExit")
        self.actionabout_version = QtWidgets.QAction()
        self.actionabout_version.setObjectName("actionabout_version")
        self.actionmanual = QtWidgets.QAction()
        self.actionmanual.setObjectName("actionmanual")
        self.menuFile.addAction(self.actionExit)
        self.menuhelp.addAction(self.actionabout_version)
        self.menuhelp.addAction(self.actionmanual)
        self.menubar.addAction(self.menuFile.menuAction())
        self.menubar.addAction(self.menuhelp.menuAction())

    def setupUi(self, MainWindow):
        #主窗口
        MainWindow.setObjectName("MainWindow")
        MainWindow.setEnabled(True)
        #菜单设置
        self._setupMenu(MainWindow)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        MainWindow.setCentralWidget(self.centralwidget)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)
        # 左侧功能面板
        self.mainPannelTabWidget = QtWidgets.QTabWidget()
        #右侧日志命令窗口
        self.logCommandTabWidget = QtWidgets.QTabWidget()
        self.logCommandTabWidget.setTabPosition(QtWidgets.QTabWidget.South)
        # 主功能面板功能细分 mysql、es、oracle
        self.mysqlQWidget = QtWidgets.QWidget()
        self.esQWidget = QtWidgets.QWidget()
        self.oracleQWidget = QtWidgets.QWidget()
        self.mainPannelTabWidget.addTab(self.mysqlQWidget, "")
        self.mainPannelTabWidget.addTab(self.esQWidget, "")
        self.mainPannelTabWidget.addTab(self.oracleQWidget, "")
        self.mainPannelTabWidget.setCurrentIndex(0)
        # 主框架
        self.splitter = QSplitter(Qt.Horizontal)
        # 主框架组装
        self.splitter.addWidget(self.mainPannelTabWidget)
        self.splitter.addWidget(self.logCommandTabWidget)
        self.splitter.setHandleWidth(1)
        self.splitter.setStretchFactor(0, 25)
        self.splitter.setStretchFactor(1, 75)
        self.splitter.setChildrenCollapsible(False)
        self.boxlayout = QVBoxLayout()
        self.boxlayout.setContentsMargins(0, 0, 0, 0)
        self.boxlayout.addWidget(self.splitter)
        self.centralwidget.setLayout(self.boxlayout)
        #文字显示
        self.retranslateUi(MainWindow)
        mysql = MysqlWindow(self, self.mysqlQWidget, self.logCommandTabWidget)
        mysql.setupUi()
        es = ESWindow(self, self.esQWidget, self.logCommandTabWidget)
        es.setupUi()
        oracle = OracleWindow(self, self.oracleQWidget,
                              self.logCommandTabWidget)
        oracle.setupUi()

    def retranslateUi(self, MainWindow):
        self._translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(self._translate("MainWindow", "Python运维工具"))
        self.mainPannelTabWidget.setToolTip(
            self._translate("MainWindow",
                            "<html><head/><body><p>mysql</p></body></html>"))
        self.mainPannelTabWidget.setWhatsThis(
            self._translate("MainWindow",
                            "<html><head/><body><p>MySQL</p></body></html>"))
        self.mainPannelTabWidget.setTabText(
            self.mainPannelTabWidget.indexOf(self.mysqlQWidget),
            self._translate("MainWindow", "MySQL"))
        self.mainPannelTabWidget.setTabText(
            self.mainPannelTabWidget.indexOf(self.esQWidget),
            self._translate("MainWindow", "ElasticSearch"))
        self.mainPannelTabWidget.setTabText(
            self.mainPannelTabWidget.indexOf(self.oracleQWidget),
            self._translate("MainWindow", "Oracle"))
        self.menuFile.setTitle(self._translate("MainWindow", "文件"))
        self.menuhelp.setTitle(self._translate("MainWindow", "帮助"))
        self.actionExit.setText(self._translate("MainWindow", "退出"))
        self.actionabout_version.setText(self._translate("MainWindow", "关于"))
        self.actionmanual.setText(self._translate("MainWindow", "手册"))
Ejemplo n.º 38
0
class Editor(QWidget):
    """
    This class is the central widget of the MainWindow.
    It contains the items library, diagram graphics scene and graphics view, and the inspector widget

    Function of Connections:
    Logically:
    A Connection is composed of a fromPort and a toPort, which gives the direction of the pipe.
    Ports are attached to Blocks.
    Visually:
    A diagram editor has a QGraphicsLineItem (connLineItem) which is set Visible only when a connection is being created

    Function of BlockItems:
    Items can be added to the library by adding them to the model of the library broswer view.
    Then they can be dragged and dropped into the diagram view.

    Function of trnsysExport:
    When exporting the trnsys file, exportData() is called.

    Function of save and load:
    A diagram can be saved to a json file by calling encodeDiagram and can then be loaded by calling decodeDiagram wiht
    appropiate filenames.

    Attributes
    ----------
    projectFolder : str
        Path to the folder of the project
    diagramName : str
        Name used for saving the diagram
    saveAsPath : :obj:`Path`
        Default saving location is trnsysGUI/diagrams, path only set if "save as" used
    idGen : :obj:`IdGenerator`
        Is used to distribute ids (id, trnsysId(for trnsysExport), etc)
    alignMode : bool
        Enables mode in which a dragged block is aligned to y or x value of another one
        Toggled in the MainWindow class in toggleAlignMode()

    editorMode : int
        Mode 0: Pipes are PolySun-like
        Mode 1: Pipes have only 90deg angles, visio-like
    snapGrid : bool
        Enable/Disable align grid
    snapSize : int
        Size of align grid

    horizontalLayout : :obj:`QHBoxLayout`
    Contains the diagram editor and the layout containing the library browser view and the listview
    vertL : :obj:`QVBoxLayout`
    Cointains the library browser view and the listWidget

    moveDirectPorts: bool
        Enables/Disables moving direct ports of storagetank (doesn't work with HxPorts yet)
    diagramScene : :obj:`QGraphicsScene`
        Contains the "logical" part of the diagram
    diagramView : :obj:`QGraphicsView`
        Contains the visualization of the diagramScene
    _currentlyDraggedConnectionFromPort : :obj:`PortItem`
    connectionList : :obj:`List` of :obj:`Connection`
    trnsysObj : :obj:`List` of :obj:`BlockItem` and :obj:`Connection`
    graphicalObj : :obj:`List` of :obj:`GraphicalItem`
    connLine : :obj:`QLineF`
    connLineItem = :obj:`QGraphicsLineItem`

    """
    def __init__(self, parent, projectFolder, jsonPath, loadValue, logger):
        super().__init__(parent)

        self.logger = logger

        self.logger.info("Initializing the diagram editor")

        self.projectFolder = projectFolder

        self.diagramName = os.path.split(self.projectFolder)[-1] + ".json"
        self.saveAsPath = _pl.Path()
        self.idGen = IdGenerator()

        self.testEnabled = False
        self.existReference = True

        self.controlExists = 0
        self.controlDirectory = ""

        self.alignMode = False

        self.moveDirectPorts = False

        self.editorMode = 1

        # Related to the grid blocks can snap to
        self.snapGrid = False
        self.snapSize = 20

        self.trnsysPath = _pl.Path(r"C:\Trnsys17\Exe\TRNExe.exe")

        self.horizontalLayout = QHBoxLayout(self)
        self.libraryBrowserView = QListView(self)
        self.libraryModel = LibraryModel(self)

        self.libraryBrowserView.setGridSize(QSize(65, 65))
        self.libraryBrowserView.setResizeMode(QListView.Adjust)
        self.libraryModel.setColumnCount(0)

        componentNamesWithIcon = [
            ("Connector", _img.CONNECTOR_SVG.icon()),
            ("TeePiece", _img.TEE_PIECE_SVG.icon()),
            ("DPTee", _img.DP_TEE_PIECE_SVG.icon()),
            ("SPCnr", _img.SINGLE_DOUBLE_PIPE_CONNECTOR_SVG.icon()),
            ("DPCnr", _img.DOUBLE_DOUBLE_PIPE_CONNECTOR_SVG.icon()),
            ("TVentil", _img.T_VENTIL_SVG.icon()),
            ("WTap_main", _img.W_TAP_MAIN_SVG.icon()),
            ("WTap", _img.W_TAP_SVG.icon()),
            ("Pump", _img.PUMP_SVG.icon()),
            ("Collector", _img.COLLECTOR_SVG.icon()),
            ("GroundSourceHx", _img.GROUND_SOURCE_HX_SVG.icon()),
            ("PV", _img.PV_SVG.icon()),
            ("HP", _img.HP_SVG.icon()),
            ("HPTwoHx", _img.HP_TWO_HX_SVG.icon()),
            ("HPDoubleDual", _img.HP_DOUBLE_DUAL_SVG.icon()),
            ("HPDual", _img.HP_DUAL_SVG.icon()),
            ("AirSourceHP", _img.AIR_SOURCE_HP_SVG.icon()),
            ("StorageTank", _img.STORAGE_TANK_SVG.icon()),
            ("IceStorage", _img.ICE_STORAGE_SVG.icon()),
            ("PitStorage", _img.PIT_STORAGE_SVG.icon()),
            ("IceStorageTwoHx", _img.ICE_STORAGE_TWO_HX_SVG.icon()),
            ("ExternalHx", _img.EXTERNAL_HX_SVG.icon()),
            ("Radiator", _img.RADIATOR_SVG.icon()),
            ("Boiler", _img.BOILER_SVG.icon()),
            ("Sink", _img.SINK_SVG.icon()),
            ("Source", _img.SOURCE_SVG.icon()),
            ("SourceSink", _img.SOURCE_SINK_SVG.icon()),
            ("Geotherm", _img.GEOTHERM_SVG.icon()),
            ("Water", _img.WATER_SVG.icon()),
            ("Crystalizer", _img.CRYSTALIZER_SVG.icon()),
            ("GenericBlock", _img.GENERIC_BLOCK_PNG.icon()),
            ("GraphicalItem", _img.GENERIC_ITEM_PNG.icon()),
        ]

        libItems = [
            QtGui.QStandardItem(icon, name)
            for name, icon in componentNamesWithIcon
        ]

        for i in libItems:
            self.libraryModel.appendRow(i)

        self.libraryBrowserView.setModel(self.libraryModel)
        self.libraryBrowserView.setViewMode(self.libraryBrowserView.IconMode)
        self.libraryBrowserView.setDragDropMode(
            self.libraryBrowserView.DragOnly)

        self.diagramScene = Scene(self)
        self.diagramView = View(self.diagramScene, self)

        # For list view
        self.vertL = QVBoxLayout()
        self.vertL.addWidget(self.libraryBrowserView)
        self.vertL.setStretchFactor(self.libraryBrowserView, 2)
        self.listV = QListWidget()
        self.vertL.addWidget(self.listV)
        self.vertL.setStretchFactor(self.listV, 1)

        # for file browser
        self.projectPath = ""
        self.fileList = []

        if loadValue == "new" or loadValue == "json":
            self.createProjectFolder()

        self.fileBrowserLayout = QVBoxLayout()
        self.pathLayout = QHBoxLayout()
        self.projectPathLabel = QLabel("Project Path:")
        self.PPL = QLineEdit(self.projectFolder)
        self.PPL.setDisabled(True)

        self.pathLayout.addWidget(self.projectPathLabel)
        self.pathLayout.addWidget(self.PPL)
        self.scroll = QScrollArea()
        self.scroll.setWidgetResizable(True)
        self.splitter = QSplitter(Qt.Vertical, )
        self.splitter.setChildrenCollapsible(False)
        self.scroll.setWidget(self.splitter)
        self.scroll.setFixedWidth(350)
        self.fileBrowserLayout.addLayout(self.pathLayout)
        self.fileBrowserLayout.addWidget(self.scroll)
        self.createDdckTree(self.projectFolder)

        if loadValue == "new" or loadValue == "json":
            self.createConfigBrowser(self.projectFolder)
            self.copyGenericFolder(self.projectFolder)
            self.createHydraulicDir(self.projectFolder)
            self.createWeatherAndControlDirs(self.projectFolder)

        self.horizontalLayout.addLayout(self.vertL)
        self.horizontalLayout.addWidget(self.diagramView)
        self.horizontalLayout.addLayout(self.fileBrowserLayout)
        self.horizontalLayout.setStretchFactor(self.diagramView, 5)
        self.horizontalLayout.setStretchFactor(self.libraryBrowserView, 1)

        self._currentlyDraggedConnectionFromPort = None
        self.connectionList = []
        self.trnsysObj = []
        self.graphicalObj = []
        self.fluids = _hlm.Fluids([])
        self.hydraulicLoops = _hlm.HydraulicLoops([])

        self.copyGroupList = QGraphicsItemGroup()
        self.selectionGroupList = QGraphicsItemGroup()

        self.printerUnitnr = 0

        # Different colors for connLineColor
        colorsc = "red"
        linePx = 4
        if colorsc == "red":
            connLinecolor = QColor(Qt.red)
        elif colorsc == "blueish":
            connLinecolor = QColor(3, 124, 193)  # Blue
        elif colorsc == "darkgray":
            connLinecolor = QColor(140, 140, 140)  # Gray
        else:
            connLinecolor = QColor(196, 196, 196)  # Gray

        # Only for displaying on-going creation of connection
        self.connLine = QLineF()
        self.connLineItem = QGraphicsLineItem(self.connLine)
        self.connLineItem.setPen(QtGui.QPen(connLinecolor, linePx))
        self.connLineItem.setVisible(False)
        self.diagramScene.addItem(self.connLineItem)

        # For line that shows quickly up when using the align mode
        self.alignYLine = QLineF()
        self.alignYLineItem = QGraphicsLineItem(self.alignYLine)
        self.alignYLineItem.setPen(QtGui.QPen(QColor(196, 249, 252), 2))
        self.alignYLineItem.setVisible(False)
        self.diagramScene.addItem(self.alignYLineItem)

        # For line that shows quickly up when using align mode
        self.alignXLine = QLineF()
        self.alignXLineItem = QGraphicsLineItem(self.alignXLine)
        self.alignXLineItem.setPen(QtGui.QPen(QColor(196, 249, 252), 2))
        self.alignXLineItem.setVisible(False)
        self.diagramScene.addItem(self.alignXLineItem)

        if loadValue == "load" or loadValue == "copy":
            self._decodeDiagram(os.path.join(self.projectFolder,
                                             self.diagramName),
                                loadValue=loadValue)
        elif loadValue == "json":
            self._decodeDiagram(jsonPath, loadValue=loadValue)

    # Debug function
    def dumpInformation(self):
        self.logger.debug("Diagram information:")
        self.logger.debug("Mode is " + str(self.editorMode))

        self.logger.debug("Next ID is " + str(self.idGen.getID()))
        self.logger.debug("Next cID is " + str(self.idGen.getConnID()))

        self.logger.debug("TrnsysObjects are:")
        for t in self.trnsysObj:
            self.logger.debug(str(t))
        self.logger.debug("")

        self.logger.debug("Scene items are:")
        sItems = self.diagramScene.items()
        for it in sItems:
            self.logger.info(str(it))
        self.logger.debug("")

        for c in self.connectionList:
            c.printConn()
        self.logger.debug("")

    # Connections related methods
    def startConnection(self, port):
        self._currentlyDraggedConnectionFromPort = port

    def _createConnection(self, startPort, endPort) -> None:
        if startPort is not endPort:
            if (isinstance(startPort.parent, StorageTank)
                    and isinstance(endPort.parent, StorageTank)
                    and startPort.parent != endPort.parent):
                msgSTank = QMessageBox(self)
                msgSTank.setText(
                    "Storage Tank to Storage Tank connection is not working atm!"
                )
                msgSTank.exec_()

            isValidSinglePipeConnection = isinstance(
                startPort, SinglePipePortItem) and isinstance(
                    endPort, SinglePipePortItem)
            if isValidSinglePipeConnection:
                command = CreateSinglePipeConnectionCommand(
                    startPort, endPort, self)
            elif isinstance(startPort, DoublePipePortItem) and isinstance(
                    endPort, DoublePipePortItem):
                command = CreateDoublePipeConnectionCommand(
                    startPort, endPort, self)
            else:
                raise AssertionError(
                    "Can only connect port items. Also, they have to be of the same type."
                )

            self.parent().undoStack.push(command)

    def sceneMouseMoveEvent(self, event):
        """
        This function is for dragging and connecting one port to another.
        When dragging, the fromPort will remain enlarged and black in color and when the toPort is hovered over, it will be
        enlarged and turn red.
        A port's details will also be displayed at the widget when they are hovered over.
        """
        fromPort = self._currentlyDraggedConnectionFromPort
        if not fromPort:
            return

        fromX = fromPort.scenePos().x()
        fromY = fromPort.scenePos().y()

        toX = event.scenePos().x()
        toY = event.scenePos().y()

        self.connLine.setLine(fromX, fromY, toX, toY)
        self.connLineItem.setLine(self.connLine)
        self.connLineItem.setVisible(True)

        hitPortItem = self._getHitPortItemOrNone(event)

        if not hitPortItem:
            return

        mousePosition = event.scenePos()

        portItemX = hitPortItem.scenePos().x()
        portItemY = hitPortItem.scenePos().y()

        distance = _math.sqrt((mousePosition.x() - portItemX)**2 +
                              (mousePosition.y() - portItemY)**2)
        if distance <= 3.5:
            hitPortItem.enlargePortSize()
            hitPortItem.innerCircle.setBrush(hitPortItem.ashColorR)
            self.listV.clear()
            hitPortItem.debugprint()
        else:
            hitPortItem.resetPortSize()
            hitPortItem.innerCircle.setBrush(hitPortItem.visibleColor)
            self.listV.clear()
            fromPort.debugprint()

        fromPort.enlargePortSize()
        fromPort.innerCircle.setBrush(hitPortItem.visibleColor)

    def _getHitPortItemOrNone(self,
                              event: QEvent) -> _tp.Optional[PortItemBase]:
        fromPort = self._currentlyDraggedConnectionFromPort
        mousePosition = event.scenePos()

        relevantPortItems = self._getRelevantHitPortItems(
            mousePosition, fromPort)
        if not relevantPortItems:
            return None

        numberOfHitPortsItems = len(relevantPortItems)
        if numberOfHitPortsItems > 1:
            raise NotImplementedError(
                "Can't deal with overlapping port items.")

        hitPortItem = relevantPortItems[0]

        return hitPortItem

    def sceneMouseReleaseEvent(self, event):
        if not self._currentlyDraggedConnectionFromPort:
            return
        fromPort = self._currentlyDraggedConnectionFromPort

        self._currentlyDraggedConnectionFromPort = None
        self.connLineItem.setVisible(False)

        mousePosition = event.scenePos()
        relevantPortItems = self._getRelevantHitPortItems(
            mousePosition, fromPort)

        numberOfHitPortsItems = len(relevantPortItems)

        if numberOfHitPortsItems > 1:
            raise NotImplementedError(
                "Can't deal with overlapping port items.")

        if numberOfHitPortsItems == 1:
            toPort = relevantPortItems[0]

            if toPort != fromPort:
                self._createConnection(fromPort, toPort)

    def _getRelevantHitPortItems(
            self, mousePosition: QPointF,
            fromPort: PortItemBase) -> _tp.Sequence[PortItemBase]:
        hitItems = self.diagramScene.items(mousePosition)
        relevantPortItems = [
            i for i in hitItems if isinstance(i, PortItemBase)
            and type(i) == type(fromPort) and not i.connectionList
        ]
        return relevantPortItems

    def cleanUpConnections(self):
        for c in self.connectionList:
            c.niceConn()

    def exportHydraulics(self, exportTo=_tp.Literal["ddck", "mfs"]):
        assert exportTo in ["ddck", "mfs"]

        if not self._isHydraulicConnected():
            messageBox = QMessageBox()
            messageBox.setWindowTitle("Hydraulic not connected")
            messageBox.setText(
                "You need to connect all port items before you can export the hydraulics."
            )
            messageBox.setStandardButtons(QMessageBox.Ok)
            messageBox.exec()
            return

        self.logger.info(
            "------------------------> START OF EXPORT <------------------------"
        )

        self.sortTrnsysObj()

        fullExportText = ""

        ddckFolder = os.path.join(self.projectFolder, "ddck")

        if exportTo == "mfs":
            mfsFileName = self.diagramName.split(".")[0] + "_mfs.dck"
            exportPath = os.path.join(self.projectFolder, mfsFileName)
        elif exportTo == "ddck":
            exportPath = os.path.join(ddckFolder, "hydraulic\\hydraulic.ddck")

        if self._doesFileExistAndDontOverwrite(exportPath):
            return None

        self.logger.info("Printing the TRNSYS file...")

        if exportTo == "mfs":
            header = open(os.path.join(ddckFolder, "generic\\head.ddck"), "r")
            headerLines = header.readlines()
            for line in headerLines:
                if line[:4] == "STOP":
                    fullExportText += "STOP = 1 \n"
                else:
                    fullExportText += line
            header.close()
        elif exportTo == "ddck":
            fullExportText += "*************************************\n"
            fullExportText += "** BEGIN hydraulic.ddck\n"
            fullExportText += "*************************************\n\n"
            fullExportText += "*************************************\n"
            fullExportText += "** Outputs to energy balance in kWh\n"
            fullExportText += (
                "** Following this naming standard : qSysIn_name, qSysOut_name, elSysIn_name, elSysOut_name\n"
            )
            fullExportText += "*************************************\n"
            fullExportText += "EQUATIONS 1\n"
            fullExportText += "qSysOut_PipeLoss = PipeLossTot\n"

        simulationUnit = 450
        simulationType = 935
        descConnLength = 20

        exporter = self._createExporter()

        blackBoxProblem, blackBoxText = exporter.exportBlackBox(
            exportTo=exportTo)
        if blackBoxProblem:
            return None

        fullExportText += blackBoxText
        if exportTo == "mfs":
            fullExportText += exporter.exportMassFlows()
            fullExportText += exporter.exportPumpOutlets()
            fullExportText += exporter.exportDivSetting(simulationUnit - 10)

        fullExportText += exporter.exportDoublePipeParameters(
            exportTo=exportTo)

        fullExportText += exporter.exportParametersFlowSolver(
            simulationUnit, simulationType, descConnLength)

        fullExportText += exporter.exportInputsFlowSolver()
        fullExportText += exporter.exportOutputsFlowSolver(simulationUnit)
        fullExportText += exporter.exportFluids() + "\n"
        fullExportText += exporter.exportHydraulicLoops() + "\n"
        fullExportText += exporter.exportPipeAndTeeTypesForTemp(
            simulationUnit + 1)  # DC-ERROR
        fullExportText += exporter.exportPrintPipeLosses()

        fullExportText += exporter.exportMassFlowPrinter(
            self.printerUnitnr, 15)
        fullExportText += exporter.exportTempPrinter(self.printerUnitnr + 1,
                                                     15)

        if exportTo == "mfs":
            fullExportText += "CONSTANTS 1\nTRoomStore=1\n"
            fullExportText += "ENDS"

        self.logger.info(
            "------------------------> END OF EXPORT <------------------------"
        )

        if exportTo == "mfs":
            f = open(exportPath, "w")
            f.truncate(0)
            f.write(fullExportText)
            f.close()
        elif exportTo == "ddck":
            if fullExportText[:1] == "\n":
                fullExportText = fullExportText[1:]
            hydraulicFolder = os.path.split(exportPath)[0]
            if not (os.path.isdir(hydraulicFolder)):
                os.makedirs(hydraulicFolder)
            f = open(exportPath, "w")
            f.truncate(0)
            f.write(fullExportText)
            f.close()

        try:
            lines = _du.loadDeck(exportPath,
                                 eraseBeginComment=True,
                                 eliminateComments=True)
            _du.checkEquationsAndConstants(lines, exportPath)
        except Exception as error:
            errorMessage = f"An error occurred while exporting the system hydraulics: {error}"
            _errs.showErrorMessageBox(errorMessage)
            return None

        return exportPath

    def _createExporter(self) -> Export:
        massFlowContributors = self._getMassFlowContributors()
        exporter = Export(massFlowContributors, self)
        return exporter

    def _getMassFlowContributors(
            self) -> _tp.Sequence[_mfs.MassFlowNetworkContributorMixin]:
        massFlowContributors = [
            o for o in self.trnsysObj
            if isinstance(o, _mfs.MassFlowNetworkContributorMixin)
        ]
        return massFlowContributors

    def _isHydraulicConnected(self) -> bool:
        for obj in self.trnsysObj:
            if not isinstance(obj, _mfs.MassFlowNetworkContributorMixin):
                continue

            internalPiping = obj.getInternalPiping()

            for portItem in internalPiping.modelPortItemsToGraphicalPortItem.values(
            ):
                if not portItem.connectionList:
                    return False

        return True

    def _doesFileExistAndDontOverwrite(self, folderPath):
        if not _pl.Path(folderPath).exists():
            return False

        qmb = QMessageBox(self)
        qmb.setText(
            f"Warning: {folderPath} already exists. Do you want to overwrite it or cancel?"
        )
        qmb.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel)
        qmb.setDefaultButton(QMessageBox.Cancel)
        ret = qmb.exec()

        if ret == QMessageBox.Cancel:
            self.canceled = True
            self.logger.info("Canceling")
            return True

        self.canceled = False
        self.logger.info("Overwriting")
        return False

    def exportHydraulicControl(self):
        self.logger.info(
            "------------------------> START OF EXPORT <------------------------"
        )

        self.sortTrnsysObj()

        fullExportText = ""

        ddckFolder = os.path.join(self.projectFolder, "ddck")

        hydCtrlPath = os.path.join(ddckFolder,
                                   "control\\hydraulic_control.ddck")
        if _pl.Path(hydCtrlPath).exists():
            qmb = QMessageBox(self)
            qmb.setText(
                "Warning: " +
                "The file hydraulic_control.ddck already exists in the control folder. Do you want to overwrite it or cancel?"
            )
            qmb.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel)
            qmb.setDefaultButton(QMessageBox.Cancel)
            ret = qmb.exec()
            if ret == QMessageBox.Save:
                self.canceled = False
                self.logger.info("Overwriting")
            else:
                self.canceled = True
                self.logger.info("Canceling")
                return

        fullExportText += "*************************************\n"
        fullExportText += "**BEGIN hydraulic_control.ddck\n"
        fullExportText += "*************************************\n"

        simulationUnit = 450

        exporter = self._createExporter()

        fullExportText += exporter.exportPumpOutlets()
        fullExportText += exporter.exportMassFlows()
        fullExportText += exporter.exportDivSetting(simulationUnit - 10)

        self.logger.info(
            "------------------------> END OF EXPORT <------------------------"
        )

        if fullExportText[:1] == "\n":
            fullExportText = fullExportText[1:]
        controlFolder = os.path.split(hydCtrlPath)[0]
        if not (os.path.isdir(controlFolder)):
            os.makedirs(controlFolder)
        f = open(str(hydCtrlPath), "w")
        f.truncate(0)
        f.write(fullExportText)
        f.close()

        return hydCtrlPath

    def sortTrnsysObj(self):
        res = self.trnsysObj.sort(key=self.sortId)
        for s in self.trnsysObj:
            self.logger.debug("s has tr id " + str(s.trnsysId) +
                              " has dname " + s.displayName)

    def sortId(self, l1):
        """
        Sort function returning a sortable key
        Parameters
        ----------
        l1 : Block/Connection

        Returns
        -------

        """
        return l1.trnsysId

    def setName(self, newName):
        self.diagramName = newName

    def delBlocks(self):
        """
        Deletes the whole diagram

        Returns
        -------

        """
        self.hydraulicLoops.clear()

        while len(self.trnsysObj) > 0:
            self.logger.info("In deleting...")
            self.trnsysObj[0].deleteBlock()

        while len(self.graphicalObj) > 0:
            self.graphicalObj[0].deleteBlock()

    # Encoding / decoding
    def encodeDiagram(self, filename):
        """
        Encodes the diagram to a json file.

        Parameters
        ----------
        filename : str

        Returns
        -------

        """
        self.logger.info("filename is at encoder " + str(filename))
        # if filename != "":
        with open(filename, "w") as jsonfile:
            json.dump(self, jsonfile, indent=4, sort_keys=True, cls=Encoder)

    def _decodeDiagram(self, filename, loadValue="load"):
        self.logger.info("Decoding " + filename)
        with open(filename, "r") as jsonfile:
            blocklist = json.load(jsonfile, cls=Decoder, editor=self)

        blockFolderNames = []

        for j in blocklist["Blocks"]:
            for k in j:
                if isinstance(k, BlockItem):
                    k.setParent(self.diagramView)
                    k.changeSize()
                    self.diagramScene.addItem(k)
                    blockFolderNames.append(k.displayName)

                if isinstance(k, StorageTank):
                    k.updateImage()

                if isinstance(k, GraphicalItem):
                    k.setParent(self.diagramView)
                    self.diagramScene.addItem(k)

                if isinstance(k, dict):
                    if "__idDct__" in k:
                        # here we don't set the ids because the copyGroup would need access to idGen
                        self.logger.debug(
                            "Found the id dict while loading, not setting the ids"
                        )

                        self.idGen.setID(k["GlobalId"])
                        self.idGen.setTrnsysID(k["trnsysID"])
                        self.idGen.setConnID(k["globalConnID"])

                    if "__nameDct__" in k:
                        self.logger.debug("Found the name dict while loading")
                        if loadValue == "load":
                            self.diagramName = k["DiagramName"]

        blockFolderNames.append("generic")
        blockFolderNames.append("hydraulic")
        blockFolderNames.append("weather")
        blockFolderNames.append("control")

        ddckFolder = os.path.join(self.projectFolder, "ddck")
        ddckFolders = os.listdir(ddckFolder)
        additionalFolders = []

        for folder in ddckFolders:
            if folder not in blockFolderNames and "StorageTank" not in folder:
                additionalFolders.append(folder)

        if len(additionalFolders) > 0:
            warnBox = QMessageBox()
            warnBox.setWindowTitle("Additional ddck-folders")

            if len(additionalFolders) == 1:
                text = "The following ddck-folder does not have a corresponding component in the diagram:"
            else:
                text = "The following ddck-folders do not have a corresponding component in the diagram:"

            for folder in additionalFolders:
                text += "\n\t" + folder

            warnBox.setText(text)
            warnBox.setStandardButtons(QMessageBox.Ok)
            warnBox.setDefaultButton(QMessageBox.Ok)
            warnBox.exec()

        for t in self.trnsysObj:
            t.assignIDsToUninitializedValuesAfterJsonFormatMigration(
                self.idGen)

            self.logger.debug("Tr obj is" + str(t) + " " + str(t.trnsysId))
            if hasattr(t, "isTempering"):
                self.logger.debug("tv has " + str(t.isTempering))

        self._decodeHydraulicLoops(blocklist)

    def _decodeHydraulicLoops(self, blocklist):
        singlePipeConnections = [
            c for c in self.connectionList
            if isinstance(c, SinglePipeConnection)
        ]
        if "hydraulicLoops" not in blocklist:
            hydraulicLoops = _hlmig.createLoops(singlePipeConnections,
                                                self.fluids.WATER)
        else:
            serializedHydraulicLoops = blocklist["hydraulicLoops"]
            hydraulicLoops = _hlm.HydraulicLoops.createFromJson(
                serializedHydraulicLoops, singlePipeConnections, self.fluids)

        self.hydraulicLoops = hydraulicLoops

    def exportSvg(self):
        """
        For exporting a svg file (text is still too large)
        Returns
        -------

        """
        generator = QSvgGenerator()
        generator.setResolution(300)
        generator.setSize(
            QSize(self.diagramScene.width(), self.diagramScene.height()))
        # generator.setViewBox(QRect(0, 0, 800, 800))
        generator.setViewBox(self.diagramScene.sceneRect())
        generator.setFileName("VectorGraphicsExport.svg")

        painter = QPainter()
        painter.begin(generator)
        painter.setRenderHint(QPainter.Antialiasing)
        self.diagramScene.render(painter)
        painter.end()

    # Saving related
    def save(self, showWarning=True):
        """
        If saveas has not been used, diagram will be saved in "/diagrams"
        If saveas has been used, diagram will be saved in self.saveAsPath
        Returns
        -------

        """
        self.diagramName = os.path.split(self.projectFolder)[-1] + ".json"
        diagramPath = os.path.join(self.projectFolder, self.diagramName)

        if os.path.isfile(diagramPath) and showWarning:
            qmb = QMessageBox(self)
            qmb.setText(
                "Warning: " +
                "This diagram name exists already. Do you want to overwrite or cancel?"
            )
            qmb.setStandardButtons(QMessageBox.Save | QMessageBox.Cancel)
            qmb.setDefaultButton(QMessageBox.Cancel)
            ret = qmb.exec()

            if ret != QMessageBox.Save:
                self.logger.info("Canceling")
                return

            self.logger.info("Overwriting")
            self.encodeDiagram(diagramPath)

        self.encodeDiagram(diagramPath)
        msgb = QMessageBox(self)
        msgb.setText("Saved diagram at " + diagramPath)
        msgb.exec()

    def saveToProject(self):
        projectPath = self.projectPath

    def renameDiagram(self, newName):
        """

        Parameters
        ----------
        newName

        Returns
        -------

        """

        if self.saveAsPath.name != "":
            # print("Path name is " + self.saveAsPath.name)
            if newName + ".json" in self.saveAsPath.glob("*"):
                QMessageBox(
                    self, "Warning",
                    "This diagram name exists already in the directory."
                    " Please rename this diagram")
            else:
                self.saveAsPath = _pl.Path(
                    self.saveAsPath.stem[0:self.saveAsPath.name.
                                         index(self.diagramName)] + newName)

        self.diagramName = newName
        self.parent().currentFile = newName
        # fromPath = self.projectFolder
        # destPath = os.path.dirname(__file__)
        # destPath = os.path.join(destPath, 'default')
        # destPath = os.path.join(destPath, newName)
        # os.rename(fromPath, destPath)

        # print("Path is now: " + str(self.saveAsPath))
        # print("Diagram name is: " + self.diagramName)

    def saveAtClose(self):
        self.logger.info("saveaspath is " + str(self.saveAsPath))

        # closeDialog = closeDlg()
        # if closeDialog.closeBool:
        filepath = _pl.Path(
            _pl.Path(__file__).resolve().parent.joinpath("recent"))
        self.encodeDiagram(str(filepath.joinpath(self.diagramName + ".json")))

    # Mode related
    def setAlignMode(self, b):
        self.alignMode = True

    def setEditorMode(self, b):
        self.editorMode = b

    def setMoveDirectPorts(self, b):
        """
        Sets the bool moveDirectPorts. When mouse released in diagramScene, moveDirectPorts is set to False again
        Parameters
        ----------
        b : bool

        Returns
        -------

        """
        self.moveDirectPorts = b

    def setSnapGrid(self, b):
        self.snapGrid = b

    def setSnapSize(self, s):
        self.snapSize = s

    def setConnLabelVis(self, isVisible: bool) -> None:
        for c in self.trnsysObj:
            if isinstance(c, ConnectionBase):
                c.setLabelVisible(isVisible)
            if isinstance(c, BlockItem):
                c.label.setVisible(isVisible)
            if isinstance(c, TVentil):
                c.posLabel.setVisible(isVisible)

    def updateConnGrads(self):
        for t in self.trnsysObj:
            if isinstance(t, ConnectionBase):
                t.updateSegGrads()

    # Dialog calls
    def showBlockDlg(self, bl):
        c = BlockDlg(bl, self)

    def showDoublePipeBlockDlg(self, bl):
        c = DoublePipeBlockDlg(bl, self)

    def showPumpDlg(self, bl):
        c = PumpDlg(bl, self)

    def showDiagramDlg(self):
        c = diagramDlg(self)

    def showGenericPortPairDlg(self, bl):
        c = GenericPortPairDlg(bl, self)

    def showHxDlg(self, hx):
        c = hxDlg(hx, self)

    def showSegmentDlg(self, seg):
        c = segmentDlg(seg, self)

    def showTVentilDlg(self, bl):
        c = TVentilDlg(bl, self)

    def showConfigStorageDlg(self, bl):
        c = ConfigureStorageDialog(bl, self)

    def getConnection(self, n):
        return self.connectionList[int(n)]

    # Unused
    def create_icon(self, map_icon):
        map_icon.fill()
        painter = QPainter(map_icon)
        painter.fillRect(10, 10, 40, 40, QColor(88, 233, 252))
        # painter.setBrush(Qt.red)
        painter.setBrush(QColor(252, 136, 98))
        painter.drawEllipse(36, 2, 15, 15)
        painter.setBrush(Qt.yellow)
        painter.drawEllipse(20, 20, 20, 20)
        painter.end()

    def setTrnsysIdBack(self):
        self.idGen.trnsysID = max(t.trnsysId for t in self.trnsysObj)

    def findStorageCorrespPorts1(self, portList):
        """
        This function gets the ports on the other side of pipes connected to a port of the StorageTank. Unused

        Parameters
        ----------
        portList : :obj:`List` of :obj:`PortItems`

        Returns
        -------

        """

        res = []
        # print("Finding c ports")
        for p in portList:
            if len(p.connectionList) > 0:  # check if not >1 needed
                # connectionList[0] is the hidden connection created when the portPair is
                i = 0
                # while type(p.connectionList[i].fromPort.parent) is StorageTank and type(p.connectionList[i].toPort.parent) is StorageTank:
                while (p.connectionList[i].fromPort.parent) == (
                        p.connectionList[i].toPort.parent):
                    i += 1
                if len(p.connectionList) >= i + 1:
                    if p.connectionList[i].fromPort is p:
                        res.append(p.connectionList[i].toPort)
                    elif p.connectionList[i].toPort is p:
                        res.append(p.connectionList[i].fromPort)
                    else:
                        self.logger.debug("Port is not fromPort nor toPort")

        # [print(p.parent.displayName) for p in res]
        return res

    def printPDF(self):
        """
        ---------------------------------------------
        Export diagram as pdf onto specified folder
        fn = user input directory
        ---------------------------------------------
        """
        fn, _ = QFileDialog.getSaveFileName(self, "Export PDF", None,
                                            "PDF files (.pdf);;All Files()")
        if fn != "":
            if QFileInfo(fn).suffix() == "":
                fn += ".pdf"
            printer = QPrinter(QPrinter.HighResolution)
            printer.setOrientation(QPrinter.Landscape)
            printer.setOutputFormat(QPrinter.PdfFormat)
            printer.setOutputFileName(fn)
            painter = QPainter(printer)
            self.diagramScene.render(painter)
            painter.end()
            self.logger.info("File exported to %s" % fn)

    def openProject(self):
        self.projectPath = str(
            QFileDialog.getExistingDirectory(self, "Select Project Path"))
        if self.projectPath != "":
            test = self.parent()

            self.parent().newDia()
            self.PPL.setText(self.projectPath)
            loadPath = os.path.join(self.projectPath, "ddck")

            self.createConfigBrowser(self.projectPath)
            self.copyGenericFolder(self.projectPath)
            self.createHydraulicDir(self.projectPath)
            self.createWeatherAndControlDirs(self.projectPath)
            self.createDdckTree(loadPath)
            # todo : open diagram
            # todo : add files into list

    def createDdckTree(self, loadPath):
        treeToRemove = self.findChild(QTreeView, "ddck")
        try:
            # treeToRemove.hide()
            treeToRemove.deleteLater()
        except AttributeError:
            self.logger.debug("Widget doesnt exist!")
        else:
            self.logger.debug("Deleted widget")
        if self.projectPath == "":
            loadPath = os.path.join(loadPath, "ddck")
        if not os.path.exists(loadPath):
            os.makedirs(loadPath)
        self.model = MyQFileSystemModel()
        self.model.setRootPath(loadPath)
        self.model.setName("ddck")
        self.tree = MyQTreeView(self.model, self)
        self.tree.setModel(self.model)
        self.tree.setRootIndex(self.model.index(loadPath))
        self.tree.setObjectName("ddck")
        self.tree.setMinimumHeight(600)
        self.tree.setSortingEnabled(True)
        self.splitter.insertWidget(0, self.tree)

    def createConfigBrowser(self, loadPath):
        self.layoutToRemove = self.findChild(QHBoxLayout, "Config_Layout")
        try:
            # treeToRemove.hide()
            self.layoutToRemove.deleteLater()
        except AttributeError:
            self.logger.debug("Widget doesnt exist!")
        else:
            self.logger.debug("Deleted widget")

        runConfigData = self._getPackageResourceData("templates/run.config")
        runConfigPath = _pl.Path(loadPath) / "run.config"
        runConfigPath.write_bytes(runConfigData)

        self.HBox = QHBoxLayout()
        self.refreshButton = QPushButton(self)
        self.refreshButton.setIcon(_img.ROTATE_TO_RIGHT_PNG.icon())
        self.refreshButton.clicked.connect(self.refreshConfig)
        self.model = MyQFileSystemModel()
        self.model.setRootPath(loadPath)
        self.model.setName("Config File")
        self.model.setFilter(QDir.Files)
        self.tree = MyQTreeView(self.model, self)
        self.tree.setModel(self.model)
        self.tree.setRootIndex(self.model.index(loadPath))
        self.tree.setObjectName("config")
        self.tree.setFixedHeight(60)
        self.tree.setSortingEnabled(False)
        self.HBox.addWidget(self.refreshButton)
        self.HBox.addWidget(self.tree)
        self.HBox.setObjectName("Config_Layout")
        self.fileBrowserLayout.addLayout(self.HBox)

    def createProjectFolder(self):
        if not os.path.exists(self.projectFolder):
            os.makedirs(self.projectFolder)

    def refreshConfig(self):
        # configPath = os.path.dirname(__file__)
        # configPath = os.path.join(configPath, 'project')
        # configPath = os.path.join(configPath, self.date_time)
        # emptyConfig = os.path.join(configPath, 'run.config')
        if self.projectPath == "":
            localPath = self.projectFolder
        else:
            localPath = self.projectPath

        self.configToEdit = os.path.join(localPath, "run.config")
        os.remove(self.configToEdit)
        shutil.copy(self.emptyConfig, localPath)
        self.configToEdit = os.path.join(localPath, "run.config")

        localDdckPath = os.path.join(localPath, "ddck")
        with open(self.configToEdit, "r") as file:
            lines = file.readlines()
        localPathStr = "string LOCAL$ %s" % str(localDdckPath)
        # localPathStr.replace('/', '\\')
        lines[21] = localPathStr + "\n"

        with open(self.configToEdit, "w") as file:
            file.writelines(lines)

        # print(localPathStr)
        self.userInputList()

    def userInputList(self):
        self.logger.debug(self.fileList)
        dia = FileOrderingDialog(self.fileList, self)

    def copyGenericFolder(self, loadPath):
        genericFolderPath = _pl.Path(loadPath) / "ddck" / "generic"

        if not genericFolderPath.exists():
            self.logger.info("Creating %s", genericFolderPath)
            genericFolderPath.mkdir()

        headData = self._getPackageResourceData("templates/generic/head.ddck")
        self.logger.info("Copying head.ddck")
        (genericFolderPath / "head.ddck").write_bytes(headData)

        endData = self._getPackageResourceData("templates/generic/end.ddck")
        self.logger.info("Copying end.ddck")
        (genericFolderPath / "end.ddck").write_bytes(endData)

    @staticmethod
    def _getPackageResourceData(resourcePath):
        data = _pu.get_data(_tgui.__name__, resourcePath)
        assert data, f"{resourcePath} package resource not found"
        return data

    def createHydraulicDir(self, projectPath):

        self.hydraulicFolder = os.path.join(projectPath, "ddck")
        self.hydraulicFolder = os.path.join(self.hydraulicFolder, "hydraulic")

        if not os.path.exists(self.hydraulicFolder):
            self.logger.info("Creating " + self.hydraulicFolder)
            os.makedirs(self.hydraulicFolder)

    def createWeatherAndControlDirs(self, projectPath):

        ddckFolder = os.path.join(projectPath, "ddck")
        weatherFolder = os.path.join(ddckFolder, "weather")
        controlFolder = os.path.join(ddckFolder, "control")

        if not os.path.exists(weatherFolder):
            self.logger.info("Creating " + weatherFolder)
            os.makedirs(weatherFolder)

        if not os.path.exists(controlFolder):
            self.logger.info("Creating " + controlFolder)
            os.makedirs(controlFolder)

    def editHydraulicLoop(self, singlePipeConnection: SinglePipeConnection):
        assert isinstance(singlePipeConnection.fromPort, SinglePipePortItem)

        hydraulicLoop = self.hydraulicLoops.getLoopForExistingConnection(
            singlePipeConnection)
        _hledit.edit(hydraulicLoop, self.hydraulicLoops, self.fluids)
Ejemplo n.º 39
0
class OTMainWindow(QWidget):
    def __init__(self, parent=None):
        super(OTMainWindow, self).__init__(parent, Qt.Window)
        self.setWindowTitle('OPC Python Tester')
        self.layout = QVBoxLayout()
        #
        self.tree = QTreeWidget(self)
        self.tree.setHeaderLabel('OPC server tree')
        self.tree_root = QTreeWidgetItem()
        self.tree_root.setText(0, 'not connected')
        self.tree.addTopLevelItem(self.tree_root)
        self.tree.itemDoubleClicked.connect(self.on_tree_item_double_clicked)
        #
        self.table = QTableWidget(self)
        self.table.setRowCount(0)
        self.table_column_labels = [
            'item_id', 'value', 'type', 'access', 'quality', 'timestamp']
        self.table.setColumnCount(len(self.table_column_labels))
        self.table.setHorizontalHeaderLabels(self.table_column_labels)
        self.table.horizontalHeader().setStretchLastSection(True)
        #
        self.splitter = QSplitter(Qt.Horizontal, self)
        self.splitter.setChildrenCollapsible(False)
        self.splitter.setHandleWidth(10)
        self.layout.addWidget(self.splitter)
        # final
        self.splitter.addWidget(self.tree)
        self.splitter.addWidget(self.table)
        self.splitter.setSizes([150, 300])
        self.setLayout(self.layout)

        # self.opcsrv = None
        self.cur_server_info = {}
        self.cur_comp_name = ''
        self.watched_itemids = []

        self.ssdialog = ServerSelectDialog(self)
        ssel_ret = self.ssdialog.exec_()
        if ssel_ret == QDialog.Accepted:
            self.do_connect(self.ssdialog.selected_server, self.ssdialog.selected_comp_name)
        else:
            print('Connection cancelled')

        self.timer = QTimer(self)
        self.timer.timeout.connect(self.on_timer_timeout)
        self.timer.start(1000)  # every 1 second

    def do_connect(self, srv_info: dict, comp_name: str):
        print('Connecting to "{0}" ({1}) on comp: {2}...'.format(
            srv_info['desc'], srv_info['guid'], comp_name))
        self.opcsrv = opc_helper.opc_connect(srv_info['guid'], comp_name)
        if self.opcsrv is None:
            return
        self.cur_comp_name = comp_name
        self.cur_server_info = srv_info
        print(self.opcsrv.get_status())
        self.fill_tree()

    def fill_tree(self):
        self.tree.clear()
        if self.opcsrv is None:
            return
        self.tree_root = QTreeWidgetItem(self.tree)
        self.tree_root.setChildIndicatorPolicy(QTreeWidgetItem.DontShowIndicatorWhenChildless)
        root_title = '{0}'.format(self.cur_server_info['desc'])
        if self.cur_comp_name != '':
            root_title = '{0} ({1})'.format(self.cur_server_info['desc'], self.cur_comp_name)
        self.tree_root.setText(0, root_title)
        self.tree.addTopLevelItem(self.tree_root)
        server_tree = self.opcsrv.browse(flat=False)
        #
        for oitem in server_tree:
            self.fill_item(oitem, self.tree_root)

    def fill_item(self, item: dict, parent: QTreeWidgetItem):
        tree_item = QTreeWidgetItem()
        tree_item.setChildIndicatorPolicy(QTreeWidgetItem.DontShowIndicatorWhenChildless)
        tree_item.setText(0, item['name'])
        if item['children'] is None:
            # set userdata = item_id only if this IS a LEAF node
            tree_item.setData(0, Qt.UserRole, item['item_id'])  # column, role, data
        parent.addChild(tree_item)
        # recurse into children
        if item['children'] is not None:
            for oitem in item['children']:
                self.fill_item(oitem, tree_item)

    @pyqtSlot(QTreeWidgetItem, int)
    def on_tree_item_double_clicked(self, item: QTreeWidgetItem, column: int):
        # void	itemDoubleClicked(QTreeWidgetItem * item, int column)
        # virtual QVariant	data(int column, int role) const
        item_data = item.data(0, Qt.UserRole)
        if item_data is None:
            return
        item_id = str(item_data)
        print('Double click on [{0}]'.format(item_id))
        self.opcsrv.get_item(item_id)
        if item_id not in self.watched_itemids:
            self.watched_itemids.append(item_id)

    @pyqtSlot()
    def on_timer_timeout(self):
        num_items = len(self.watched_itemids)
        self.table.setRowCount(num_items)
        i = 0
        while i < num_items:
            item_id = self.watched_itemids[i]
            item_value = self.opcsrv.get_item(item_id)
            item_info = self.opcsrv.get_item_info(item_id)
            #
            twi = QTableWidgetItem(str(item_id))
            self.table.setItem(i, 0, twi)
            #
            twi = QTableWidgetItem(str(item_value))
            self.table.setItem(i, 1, twi)
            #
            twi = QTableWidgetItem(str(item_info['type']))
            self.table.setItem(i, 2, twi)
            #
            twi = QTableWidgetItem(str(item_info['access_rights']))
            self.table.setItem(i, 3, twi)
            #
            twi = QTableWidgetItem(str(item_info['quality']))
            self.table.setItem(i, 4, twi)
            #
            ts_str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(item_info['timestamp']))
            twi = QTableWidgetItem(str(ts_str))
            self.table.setItem(i, 5, twi)
            #
            i += 1