コード例 #1
0
ファイル: memory_view.py プロジェクト: hazedic/seninja
    def on_customContextMenuRequested(self, qpoint):
        if self.current_state is None:
            return
        menu = QMenu(self)
        index = self.hexWidget.view.indexAt(qpoint)

        sel_start = self.hexWidget._hsm.start
        sel_end = self.hexWidget._hsm.end
        if sel_start is None:
            return

        expr = self.current_state.mem.load(sel_start + self.address_start,
                                           sel_end - sel_start + 1)

        if symbolic(expr):
            a = menu.addAction("Show reg expression")
            a.triggered.connect(
                MemoryView._condom(self._show_reg_expression,
                                   sel_start + self.address_start, expr))
            a = menu.addAction("Evaluate with solver")
            a.triggered.connect(
                MemoryView._condom(self._evaluate_with_solver,
                                   sel_start + self.address_start, expr))
            a = menu.addAction("Concretize")
            a.triggered.connect(
                MemoryView._condom(self._concretize,
                                   sel_start + self.address_start, expr))
        else:
            a = menu.addAction("Make symbolic")
            a.triggered.connect(
                MemoryView._condom(self._make_symbolic,
                                   sel_start + self.address_start,
                                   sel_end - sel_start + 1))
            copy_menu = menu.addMenu("Copy...")
            a = copy_menu.addAction("Copy Little Endian")
            a.triggered.connect(
                MemoryView._condom(self._copy_little_endian, expr))
            a = copy_menu.addAction("Copy Big Endian")
            a.triggered.connect(MemoryView._condom(self._copy_big_endian,
                                                   expr))
            a = copy_menu.addAction("Copy String")
            a.triggered.connect(MemoryView._condom(self._copy_string, expr))
            a = copy_menu.addAction("Copy Binary")
            a.triggered.connect(MemoryView._condom(self._copy_binary, expr))

        return menu
コード例 #2
0
ファイル: table_visualizer.py プロジェクト: IvanKosik/vision
    def _display_age_column_header_context_menu(self, point: QPoint):
        menu = QMenu(self)
        format_menu = menu.addMenu('Format')
        format_action_group = QActionGroup(self)
        for age_format in self._age_formats:
            format_action = format_menu.addAction(age_format.NAME)
            format_action.age_format = age_format
            format_action.setCheckable(True)

            if self._age_format == age_format:
                format_action.setChecked(True)

            format_action_group.addAction(format_action)

        triggered_action = menu.exec_(
            self.horizontalHeader().viewport().mapToGlobal(point))
        if triggered_action:
            self.age_format = triggered_action.age_format
コード例 #3
0
 def contextMenu(position):
     menu = QMenu()
     actionEsc.setEnabled(not isSuspended)
     action2 = QAction('Full Screen', None)
     action2.setCheckable(True)
     action2.setChecked(newWin.windowState() & Qt.WindowFullScreen)
     action3 = QAction('Resume', None)
     action3.setEnabled(isSuspended)
     for action in [actionEsc, action2, action3]:
         menu.addAction(action)
         action.triggered.connect(
             lambda checked=False, name=action: contextMenuHandler(name))  # named arg checked is sent
     subMenuRating = menu.addMenu('Rating')
     for i in range(6):
         action = QAction(str(i), None)
         subMenuRating.addAction(action)
         action.triggered.connect(
             lambda checked=False, name=action: contextMenuHandler(name))  # named arg checked is sent
     menu.exec_(position)
コード例 #4
0
 def generate_menu(self, pos):
     row_num = -1
     for i in self.tableWidget.selectionModel().selection().indexes():
         row_num = i.row()
     if row_num < 0:
         return
     name = self.tableWidget.item(row_num,
                                  market_table_column.index('name')).text()
     local_symbol = self.tableWidget.item(
         row_num, market_table_column.index('local_symbol')).text()
     menu = QMenu()
     cancel_item = menu.addAction("取消订阅")
     if local_symbol in G.config.CONTRACT:
         item = menu.addAction("取消收藏")
         cancel = True
     else:
         item = menu.addAction("加入收藏")
         cancel = False
     star_menu = menu.addMenu("我的收藏")
     for k, v in G.config.CONTRACT.items():
         star_menu.addAction(' '.join([k, v]))
     clear_item = star_menu.addAction('清空')
     action = menu.exec_(self.tableWidget.mapToGlobal(pos))
     ####
     if action == cancel_item:
         # 取消订阅
         res = current_app.market.unsubscribe(local_symbol.split('.')[0])
         # 事后处理
         if res == 0:
             G.subscribes.pop(local_symbol, None)
             self.tableWidget.removeRow(row_num)
             self.item_row -= 1
     elif action == item:
         if cancel:
             G.config.CONTRACT.pop(local_symbol)
         else:
             G.config.CONTRACT.update({local_symbol: name})
         G.config.to_file()
     elif action == clear_item:
         G.config.CONTRACT.clear()
         G.config.to_file()
コード例 #5
0
 def contextMenuEvent(self, event):
     index = self.tabAt(event.pos())
     if self._plus_button.underMouse():
         self._show_plus_button_context_menu(event.globalPos())
         return
     if self.tabButton(index, QTabBar.RightSide).underMouse():
         return
     db_editor = self._parent.tab_widget.widget(index)
     if db_editor is None:
         return
     menu = QMenu(self)
     others = self._parent.others()
     if others:
         move_tab_menu = menu.addMenu("Move tab to another window")
         move_tab_to_new_window = move_tab_menu.addAction(
             "New window",
             lambda _=False, index=index: self._parent.move_tab(
                 index, None))
         for other in others:
             move_tab_menu.addAction(
                 other.name(),
                 lambda _=False, index=index, other=other: self._parent.
                 move_tab(index, other))
     else:
         move_tab_to_new_window = menu.addAction(
             "Move tab to new window",
             lambda _=False, index=index: self._parent.move_tab(
                 index, None))
     move_tab_to_new_window.setEnabled(self.count() > 1)
     menu.addSeparator()
     menu.addAction(db_editor.url_toolbar.reload_action)
     db_url_codenames = db_editor.db_url_codenames
     menu.addAction(
         QIcon(CharIconEngine("\uf24d")),
         "Duplicate",
         lambda _=False, index=index + 1, db_url_codenames=db_url_codenames:
         self._parent.insert_new_tab(index, db_url_codenames),
     )
     menu.popup(event.globalPos())
     menu.aboutToHide.connect(menu.deleteLater)
     event.accept()
コード例 #6
0
class Window(QMainWindow):
    def __init__(self):
        super().__init__()

        self.create_menu()
        self.add_directory('&Scripts')
        self.add_exit()

    def create_menu(self):
        self.menu = QMenu()

    def add_directory(self, label):
        qtIconFolder = QIcon(QApplication.style().standardIcon(
            QStyle.SP_DirIcon))
        qtIconFile = QIcon(QApplication.style().standardIcon(
            QStyle.SP_FileIcon))

        dirMenu = self.menu.addMenu(qtIconFolder, label)
        # dirMenu.aboutToShow.connect(self.current_hover(dirMenu))
        dirMenu.aboutToShow.connect(
            lambda dirMenu=dirMenu: self.current_hover(dirMenu))

    def current_hover(self, item):
        print(item)

    def add_exit(self):
        qtIcon = QIcon(QApplication.style().standardIcon(
            QStyle.SP_DialogCloseButton))
        exit = self.menu.addAction(qtIcon, '&Quit')
        self.menu.insertSeparator(exit)
        exit.triggered.connect(self.exit_app)
        self.menu.exec_(QCursor.pos())

    def exit_app(self):
        self.close()
        sys.exit(0)
コード例 #7
0
class PivotTableView(CopyPasteTableView):
    """Custom QTableView class with pivot capabilities.
    """

    _REMOVE_OBJECT = "Remove objects"
    _REMOVE_RELATIONSHIP = "Remove relationships"
    _REMOVE_PARAMETER = "Remove parameter definitions"
    _REMOVE_ALTERNATIVE = "Remove alternatives"
    _REMOVE_SCENARIO = "Remove scenarios"

    def __init__(self, parent=None):
        """Initialize the class."""
        super().__init__(parent)
        self._spine_db_editor = None
        self._menu = QMenu(self)
        self._selected_value_indexes = list()
        self._selected_entity_indexes = list()
        self._selected_parameter_indexes = list()
        self._selected_alternative_indexes = list()
        self._selected_scenario_indexes = list()
        self._open_in_editor_action = None
        self._plot_action = None
        self._plot_in_window_menu = None
        self._remove_values_action = None
        self._remove_objects_action = None
        self._remove_relationships_action = None
        self._remove_parameters_action = None
        self._remove_alternatives_action = None
        self._remove_scenarios_action = None

    @property
    def source_model(self):
        return self.model().sourceModel()

    @property
    def db_mngr(self):
        return self.source_model.db_mngr

    def connect_spine_db_editor(self, spine_db_editor):
        self._spine_db_editor = spine_db_editor
        self.populate_context_menu()
        h_header = PivotTableHeaderView(Qt.Horizontal, "columns", self)
        h_header.setContextMenuPolicy(Qt.DefaultContextMenu)
        h_header.setResizeContentsPrecision(spine_db_editor.visible_rows)
        v_header = PivotTableHeaderView(Qt.Vertical, "rows", self)
        v_header.setContextMenuPolicy(Qt.NoContextMenu)
        v_header.setDefaultSectionSize(spine_db_editor.default_row_height)
        self.setHorizontalHeader(h_header)
        self.setVerticalHeader(v_header)

    def populate_context_menu(self):
        self._open_in_editor_action = self._menu.addAction(
            "Edit...", self.open_in_editor)
        self._menu.addSeparator()
        self._plot_action = self._menu.addAction("Plot", self.plot)
        self._plot_in_window_menu = self._menu.addMenu("Plot in window")
        self._plot_in_window_menu.triggered.connect(self._plot_in_window)
        self._menu.addSeparator()
        self._menu.addAction(self._spine_db_editor.ui.actionCopy)
        self._menu.addAction(self._spine_db_editor.ui.actionPaste)
        self._menu.addSeparator()
        self._remove_values_action = self._menu.addAction(
            "Remove parameter values", self.remove_values)
        self._remove_objects_action = self._menu.addAction(
            self._REMOVE_OBJECT, self.remove_objects)
        self._remove_relationships_action = self._menu.addAction(
            self._REMOVE_RELATIONSHIP, self.remove_relationships)
        self._remove_parameters_action = self._menu.addAction(
            self._REMOVE_PARAMETER, self.remove_parameters)
        self._remove_alternatives_action = self._menu.addAction(
            self._REMOVE_ALTERNATIVE, self.remove_alternatives)
        self._remove_scenarios_action = self._menu.addAction(
            self._REMOVE_SCENARIO, self.remove_scenarios)
        self._menu.aboutToShow.connect(
            self._spine_db_editor.refresh_copy_paste_actions)

    def remove_selected(self):
        self.remove_values()
        if self._can_remove_relationships():
            self.remove_relationships()
        self.remove_objects()
        self.remove_parameters()
        self.remove_alternatives()
        self.remove_scenarios()

    def remove_values(self):
        row_mask = set()
        column_mask = set()
        for index in self._selected_value_indexes:
            row, column = self.source_model.map_to_pivot(index)
            row_mask.add(row)
            column_mask.add(column)
        data = self.source_model.model.get_pivoted_data(row_mask, column_mask)
        items = (item for row in data for item in row)
        db_map_typed_data = {}
        for item in items:
            if item is None:
                continue
            db_map, id_ = item
            db_map_typed_data.setdefault(db_map, {}).setdefault(
                "parameter_value", set()).add(id_)
        self.db_mngr.remove_items(db_map_typed_data)

    def remove_objects(self):
        db_map_typed_data = {}
        for index in self._selected_entity_indexes:
            db_map, id_ = self.source_model._header_id(index)
            db_map_typed_data.setdefault(db_map,
                                         {}).setdefault("object",
                                                        set()).add(id_)
        self.db_mngr.remove_items(db_map_typed_data)

    def remove_relationships(self):
        db_map_relationship_lookup = {
            db_map: {rel["object_id_list"]: rel["id"]
                     for rel in rels}
            for db_map, rels in
            self._spine_db_editor._get_db_map_entities().items()
        }
        db_map_typed_data = {}
        for index in self._selected_entity_indexes:
            db_map, object_ids = self.source_model.db_map_object_ids(index)
            object_id_list = ",".join([str(id_) for id_ in object_ids])
            id_ = db_map_relationship_lookup.get(db_map,
                                                 {}).get(object_id_list)
            if id_:
                db_map_typed_data.setdefault(db_map, {}).setdefault(
                    "relationship", set()).add(id_)
        self.db_mngr.remove_items(db_map_typed_data)

    def remove_parameters(self):
        db_map_typed_data = {}
        for index in self._selected_parameter_indexes:
            db_map, id_ = self.source_model._header_id(index)
            db_map_typed_data.setdefault(db_map, {}).setdefault(
                "parameter_definition", set()).add(id_)
        self.db_mngr.remove_items(db_map_typed_data)

    def remove_alternatives(self):
        db_map_typed_data = {}
        for index in self._selected_alternative_indexes:
            db_map, id_ = self.source_model._header_id(index)
            db_map_typed_data.setdefault(db_map,
                                         {}).setdefault("alternative",
                                                        set()).add(id_)
        self.db_mngr.remove_items(db_map_typed_data)

    def remove_scenarios(self):
        db_map_typed_data = {}
        for index in self._selected_scenario_indexes:
            db_map, id_ = self.source_model._header_id(index)
            db_map_typed_data.setdefault(db_map,
                                         {}).setdefault("scenario",
                                                        set()).add(id_)
        self.db_mngr.remove_items(db_map_typed_data)

    def open_in_editor(self):
        """Opens the parameter_value editor for the first selected cell."""
        index = self._selected_value_indexes[0]
        self._spine_db_editor.show_parameter_value_editor(index)

    def plot(self):
        """Plots the selected cells in the pivot table."""
        selected_indexes = self.selectedIndexes()
        hints = PivotTablePlottingHints()
        try:
            plot_window = plot_selection(self.model(), selected_indexes, hints)
        except PlottingError as error:
            report_plotting_failure(error, self)
            return
        plotted_column_names = {
            hints.column_label(self.model(), index.column())
            for index in selected_indexes
            if hints.is_index_in_data(self.model(), index)
        }
        plot_window.use_as_window(self.parentWidget(),
                                  ", ".join(plotted_column_names))
        plot_window.show()

    def contextMenuEvent(self, event):
        """Shows context menu.

        Args:
            event (QContextMenuEvent)
        """
        index = self.indexAt(event.pos())
        index = self.model().mapToSource(index)
        if not index.isValid() or self.source_model.index_within_top_left(
                index):
            pivot_menu = QMenu(self)
            title = TitleWidgetAction("Pivot", self._spine_db_editor)
            pivot_menu.addAction(title)
            pivot_menu.addActions(
                self._spine_db_editor.pivot_action_group.actions())
            pivot_menu.exec_(event.globalPos())
            return
        self._refresh_selected_indexes()
        self._update_actions_availability()
        _prepare_plot_in_window_menu(self._plot_in_window_menu)
        self._menu.exec_(event.globalPos())

    def _refresh_selected_indexes(self):
        self._selected_value_indexes = list()
        self._selected_entity_indexes = list()
        self._selected_parameter_indexes = list()
        self._selected_alternative_indexes = list()
        self._selected_scenario_indexes = list()
        indexes = [
            self.model().mapToSource(ind) for ind in self.selectedIndexes()
        ]
        for index in indexes:
            if self.source_model.index_in_data(index):
                self._selected_value_indexes.append(index)
            elif self.source_model.index_in_headers(index):
                top_left_id = self.source_model.top_left_id(index)
                header_type = self.source_model.top_left_headers[
                    top_left_id].header_type
                if header_type == "parameter":
                    self._selected_parameter_indexes.append(index)
                elif header_type == "object":
                    self._selected_entity_indexes.append(index)
                elif header_type == "alternative":
                    self._selected_alternative_indexes.append(index)
                elif header_type == "scenario":
                    self._selected_scenario_indexes.append(index)

    def _update_actions_availability(self):
        self._open_in_editor_action.setEnabled(
            len(self._selected_value_indexes) == 1)
        self._plot_action.setEnabled(len(self._selected_value_indexes) > 0)
        self._remove_values_action.setEnabled(
            bool(self._selected_value_indexes))
        self._remove_objects_action.setEnabled(
            bool(self._selected_entity_indexes))
        self._remove_relationships_action.setEnabled(
            bool(self._selected_entity_indexes)
            and self._can_remove_relationships())
        self._remove_parameters_action.setEnabled(
            bool(self._selected_parameter_indexes))
        self._remove_alternatives_action.setEnabled(
            bool(self._selected_alternative_indexes))
        self._remove_scenarios_action.setEnabled(
            bool(self._selected_scenario_indexes))

    def _can_remove_relationships(self):
        return (self.model().sourceModel().item_type == "parameter_value"
                and self._spine_db_editor.current_class_type
                == "relationship_class")

    @Slot(QAction)
    def _plot_in_window(self, action):
        window_id = action.text()
        plot_window = PlotWidget.plot_windows.get(window_id)
        if plot_window is None:
            self.plot()
            return
        selected_indexes = self.selectedIndexes()
        hints = PivotTablePlottingHints()
        try:
            plot_selection(self.model(), selected_indexes, hints, plot_window)
            plot_window.raise_()
        except PlottingError as error:
            report_plotting_failure(error, self)
コード例 #8
0
ファイル: views.py プロジェクト: tornado80/tonebox
class SongsView(QTableWidget):
    def __init__(self, parent):
        super().__init__(parent)
        self.manager_model = None
        self.settings_model = None
        self.filterViews = []
        self.setup_ui()

    def setup_ui(self):
        self.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.horizontalHeader().setStretchLastSection(True)
        self.verticalHeader().setVisible(False)
        self.horizontalHeader().setSectionsMovable(True)
        self.doubleClicked.connect(self.double_click_to_play_song)
        self.setup_context_menu()

    def connect_to_models(self, manager_model, settings_model, queue_manager):
        self.manager_model = manager_model
        self.settings_model = settings_model
        self.queue_manager = queue_manager
        self.connect_to_models_signals()

    def connect_to_models_signals(self):
        self.manager_model.modelUpdated.connect(self.update_view)
        self.settings_model.settingsUpdated.connect(self.update_columns)

    def disconnect_from_models_signals(self):
        self.manager_model.modelUpdated.disconnect(self.update_view)
        self.settings_model.settingsUpdated.disconnect(self.update_columns)

    def contextMenuEvent(self, event):
        row = self.rowAt(event.y())
        if row != -1:
            if len(self.selectionModel().selectedRows()) > 1:
                self.informationAction.setVisible(False)
            self.contextMenu.exec_(event.globalPos())
            self.informationAction.setVisible(True)

    def double_click_to_play_song(self):
        self.queue_manager.clear_queue()
        self.handle_add_to_queue()
        self.queue_manager.play_queue()

    def request_playing_song(self):
        self.handle_add_to_queue()
        self.queue_manager.play_queue()

    def setup_context_menu(self):
        self.contextMenu = QMenu(self)
        self.playSongAction = self.contextMenu.addAction("Play")
        self.addToQueueAction = self.contextMenu.addAction("Add to Queue")
        self.addToPlaylistMenu = self.contextMenu.addMenu("Add to Playlist")
        self.removeSongAction = self.contextMenu.addAction("Remove song(s)")
        self.informationAction = self.contextMenu.addAction("Details")
        self.playSongAction.triggered.connect(self.request_playing_song)
        self.removeSongAction.triggered.connect(self.handle_remove_song)
        self.addToQueueAction.triggered.connect(self.handle_add_to_queue)
        self.informationAction.triggered.connect(self.handle_information)
        self.addToPlaylistMenu.aboutToShow.connect(self.setup_playlist_menu)
        self.addToPlaylistMenu.triggered.connect(self.add_to_playlist)

    def handle_information(self):
        InfoDialog(
            self, self.manager_model.songs[self.rows_data[
                self.selectionModel().currentIndex().row()]],
            self.settings_model).exec_()

    def setup_playlist_menu(self):
        self.addToPlaylistMenu.clear()
        for playlist_id, playlist in self.manager_model.playlists.items():
            action = self.addToPlaylistMenu.addAction(playlist.name)
            action.setData(playlist_id)

    def add_to_playlist(self, action):
        songs_to_be_added = [
            self.rows_data[row_idx.row()]
            for row_idx in self.selectionModel().selectedRows()
        ]
        self.manager_model.add_songs_to_playlist(action.data(),
                                                 *songs_to_be_added)

    def handle_add_to_queue(self):
        self.add_to_queue(None)

    def add_to_queue(self, playlist_id):
        songs_to_be_queued = [
            (self.rows_data[row_idx.row()], playlist_id)
            for row_idx in self.selectionModel().selectedRows()
        ]
        self.queue_manager.add_songs_to_queue(*songs_to_be_queued)

    def handle_remove_song(self):
        if QMessageBox.question(
                self, "User Consent",
                "Do you really want to remove the song from Library?",
                QMessageBox.No, QMessageBox.Yes) == QMessageBox.Yes:
            songs_to_be_deleted = [
                self.rows_data[row_idx.row()]
                for row_idx in self.selectionModel().selectedRows()
            ]
            self.manager_model.remove_songs(*songs_to_be_deleted)

    def update_columns(self):
        headers = list(
            self.settings_model.DEFAULT_JSON_FIELDS["SongsViewHeaders"].keys())
        self.setColumnCount(len(headers))
        self.setHorizontalHeaderLabels(headers)
        for i in range(len(headers)):
            if not self.settings_model.get("SongsViewHeaders")[headers[i]]:
                self.setColumnHidden(i, True)
            else:
                self.setColumnHidden(i, False)

    def connect_to_filter_view(self, fview):
        fview.childToBeUpdated.connect(self.update_view)
        self.filterViews.append(fview)

    def update_view(self):
        self.update_columns()
        self.update_rows()

    def update_rows(self):
        self.clearContents()
        self.rows_data = self.filter_view()
        self.setRowCount(len(self.rows_data))
        headers = list(
            self.settings_model.DEFAULT_JSON_FIELDS["SongsViewHeaders"].keys())
        for i in range(len(self.rows_data)):
            song = self.manager_model.songs[self.rows_data[i]]
            for j in range(len(headers)):
                self.setItem(
                    i,
                    j,
                    QTableWidgetItem(
                        str(
                            getattr(
                                song,  # duration as an integer should be an object and __str__ overloaded for it
                                self.settings_model.
                                SONGS_VIEW_HEADERS_TRANSLATIONS[headers[j]]))))

    def accumulate_filter_keywords(self):
        search = {}
        for view in self.filterViews:
            search.update(view.child_filter_keywords())
        return search

    def filter_view(self):
        search = self.accumulate_filter_keywords()
        return self.manager_model.songs_dict_filter(**search)
コード例 #9
0
class Window(QMainWindow):
    def __init__(self):
        super(Window, self).__init__()

        # Config --------------------------------------
        self.objectList = {}
        self.systemIcon = {
            'folder':
            QIcon(QApplication.style().standardIcon(QStyle.SP_DirIcon)),
            'folderLink':
            QIcon(QApplication.style().standardIcon(QStyle.SP_DirLinkIcon)),
            'file':
            QIcon(QApplication.style().standardIcon(QStyle.SP_FileIcon)),
            'quit':
            QIcon(QApplication.style().standardIcon(
                QStyle.SP_DialogCloseButton))
        }
        self.filetypeIcon = Icons.icon_filetypes_flat(self)
        self.includeExclude = IncludeExclude()
        self.includeExclude.includePaths = ['Examples']
        self.includeExclude.includeExtensions = ['md', 'py', 'png']
        self.includeExclude.excludeFilenames = ['gitignore']
        self.includeExclude.excludePaths = ['git', '__pycache__']

        # --------------------------------------------
        self.create_menu()
        self.add_directory(
            '&Scripts',
            '/media/m/Data/Development/Phyton/TreeViewUltraMenu/Python-UltraMenu'
        )
        self.add_exit()

    def create_menu(self):
        self.menu = QMenu(self)
        # Enable theme, uncomment:
        self.menu.setStyleSheet(appStyle)
        self.menu.setCursor(QtCore.Qt.PointingHandCursor)
        tt1 = self.menu.addAction('Tooltip 1')
        tt1.setToolTip('t1')
        tt2 = self.menu.addAction('Tooltip 2')
        tt2.setToolTip('t2')
        tt3 = self.menu.addAction('Tooltip 3')
        tt3.setWhatsThis('setWhatsThis')
        tt3.setIconText('q')
        tt3.iconText()

        # custom style label
        text = QLabel("Label with custom text", self)
        text.setProperty('class', 'singlestyle')
        styleItem = QWidgetAction(self)
        styleItem.setDefaultWidget(text)
        styleItem.setProperty('class', 'singlestyle')
        tt4 = self.menu.addAction(styleItem)
        # tt4.setProperty('class', 'singlestyle')

        # tt4.Priority
        # tt4.setStyleSheet("QLabel { color: rgb(50, 50, 50); font-size: 11px; background-color: rgba(188, 188, 188, 50); border: 1px solid rgba(188, 188, 188, 250); }")
        self.menu.setToolTipsVisible(True)
        # self.menu.setIcon
        # self.menu.setwha(True)
        # self.menu.hovered.connect(lambda tt1= tt1, tt1())
        # self.menu.keyPressEvent(self.keyPressEvent)
        # pos = tt3.
        self.menu.installEventFilter(self)
        # widgetRect = self.menu.mapToGlobal()
        p = (0, 0)
        # print(self.menu.mapToGlobal(0,0))
        widgetRect = self.geometry()

        print(widgetRect)
        # tt2.hovered.connect(lambda tt2=self.tt2, tt2.tooltip())
        tt3.hovered.connect(lambda pos=[self], parent=self.menu, index=2: self.
                            show_toolTip(pos, parent, index))

    # Listen for All KeyPress/Mouse events
    def eventFilter(self, widget, event):
        if (event.type() == QtCore.QEvent.KeyRelease and widget is self.menu):
            self.menu.toolTip()
            print('all')

            # self.
            key = event.key()
            if key == QtCore.Qt.Key_Escape:
                print('escape')
            else:
                if key == QtCore.Qt.Key_Return:
                    print('escape')
                elif key == QtCore.Qt.Key_Enter:
                    print('escape')
                elif key == QtCore.Qt.Key_C:
                    print('Key - c')
                return True
        # return QtGui.QWidget.eventFilter(self, widget, event)

    def show_toolTip(self, pos, parent, index):
        '''
        **Parameters**
            pos:    list
                list of all parent widget up to the upmost

            parent: PySide.QtGui.QAction
                the parent QAction

            index:  int
                place within the QMenu, beginning with zero
        '''
        position_x = 0
        position_y = 0
        for widget in pos:
            position_x += widget.pos().x()
            position_y += widget.pos().y()

        point = QtCore.QPoint()
        point.setX(position_x)
        point.setY(position_y + index * 22)  # set y Position of QToolTip
        QToolTip.showText(point, parent.toolTip())

    def add_directory(self, label, dir):

        qtParentMenuItem = self.menu.addMenu(self.systemIcon['folder'], label)
        parentMenuClass = FolderItem(qtMenuItem=qtParentMenuItem,
                                     label=label,
                                     iconPath='',
                                     globalHotkey='',
                                     path=Path(dir))
        uid = parentMenuClass.getUid()
        self.objectList[uid] = parentMenuClass
        self.add_directory_submenu(parentMenuClass)

    def add_directory_submenu(self, parentMenuClass: FolderItem):

        qtParentMenuItem = parentMenuClass.getQtMenuItem()

        # Don't add the menu items multiple times, every time you hover a submenu.
        if parentMenuClass.isSubmenuItemsAdded == True:
            return None

        parentMenuClass.isSubmenuItemsAdded = True
        dirPath: str = parentMenuClass.getFullPath()
        osPaths = sorted(Path(dirPath).glob('*'))
        osPaths.sort(key=lambda x: x.is_file())
        filesAndFoldersArr = []

        for item in osPaths:
            # path = dirPath + '/' + item.name
            if item.is_dir(
            ) == True and self.includeExclude.folderIsNotExcluded(item):

                folderIcon = self.systemIcon['folder']

                if item.is_symlink():
                    folderIcon = self.systemIcon['folderLink']

                qtFolderItem = qtParentMenuItem.addMenu(
                    folderIcon, '&' + item.name)
                folderItemClass = FolderItem(qtMenuItem=qtFolderItem,
                                             label=item.name,
                                             iconPath='',
                                             globalHotkey='',
                                             path=item)
                uid = folderItemClass.getUid()
                self.objectList[uid] = folderItemClass
                filesAndFoldersArr.append(folderItemClass)
                qtFolderItem.aboutToShow.connect(
                    lambda folderItemClass=folderItemClass: self.
                    add_directory_submenu(folderItemClass))
                # newFolder.triggered.connect(self.action_directory(item.uid))

            elif item.is_file() and self.includeExclude.fileIsNotExcluded(
                    item):

                fileExt = item.suffix

                qtIcon = self.systemIcon['file']

                # If we have a icon for the filetype, use that instead
                if fileExt in self.filetypeIcon:
                    qtIcon = QIcon(str(self.filetypeIcon[fileExt]))

                qtFileItem = qtParentMenuItem.addAction(qtIcon, item.name)
                folderItemClass = FolderItem(qtMenuItem=qtFileItem,
                                             label=item.name,
                                             iconPath='',
                                             globalHotkey='',
                                             path=item)
                uid = folderItemClass.getUid()
                self.objectList[uid] = folderItemClass
                filesAndFoldersArr.append(folderItemClass)
                # filename
                # item.name
                # extension
                # item.suffix
                # newFile.triggered.connect(self.action_directory(item.uid))
                #         # newFile.hovered.connect(self.exit_app)
                #         # newFile.hovered.connect(lambda:  item.printUid())

                #         # func = self.hover()
                #         # newFile.hovered.connect(lambda f=func,arg=newFile:f(arg))
            else:
                print("It is a special file (socket, FIFO, device file)")

    def action_directory(self, uid):
        print(uid)

    def add_exit(self):
        exit = self.menu.addAction(self.systemIcon['quit'], '&Quit')
        self.menu.insertSeparator(exit)
        exit.triggered.connect(self.exit_app)
        self.menu.exec_(QCursor.pos())

    def exit_app(self):
        self.close()
        sys.exit(0)
コード例 #10
0
class PivotTableHeaderView(QHeaderView):

    header_dropped = Signal(object, object)

    def __init__(self, orientation, area, pivot_table_view):
        super().__init__(orientation, parent=pivot_table_view)
        self._area = area
        self._proxy_model = pivot_table_view.model()
        self._model_index = None
        self._menu = QMenu(self)
        self._plot_action = self._menu.addAction("Plot single column",
                                                 self._plot_column)
        self._add_to_plot_menu = self._menu.addMenu("Plot in window")
        self._add_to_plot_menu.triggered.connect(self._add_column_to_plot)
        self._set_as_x_action = self._menu.addAction("Use as X",
                                                     self._set_x_flag)
        self._set_as_x_action.setCheckable(True)
        self.setAcceptDrops(True)
        self.setStyleSheet("QHeaderView::section {background-color: " +
                           PIVOT_TABLE_HEADER_COLOR + ";}")

    @property
    def area(self):
        return self._area

    def dragEnterEvent(self, event):
        if isinstance(event.source(), TabularViewHeaderWidget):
            event.accept()

    def dragMoveEvent(self, event):
        if isinstance(event.source(), TabularViewHeaderWidget):
            event.accept()

    def dropEvent(self, event):
        self.header_dropped.emit(event.source(), self)

    def contextMenuEvent(self, event):
        """Shows context menu.

        Args:
            event (QContextMenuEvent)
        """
        self._menu.move(event.globalPos())
        self._model_index = self.parent().indexAt(event.pos())
        source_index = self._proxy_model.mapToSource(self._model_index)
        if self._proxy_model.sourceModel().column_is_index_column(
                self._model_index.column()):
            self._plot_action.setEnabled(False)
            self._set_as_x_action.setEnabled(True)
            self._set_as_x_action.setChecked(source_index.column(
            ) == self._proxy_model.sourceModel().plot_x_column)
        elif self._model_index.column() < self._proxy_model.sourceModel(
        ).headerColumnCount():
            self._plot_action.setEnabled(False)
            self._set_as_x_action.setEnabled(False)
            self._set_as_x_action.setChecked(False)
        else:
            self._plot_action.setEnabled(True)
            self._set_as_x_action.setEnabled(True)
            self._set_as_x_action.setChecked(source_index.column(
            ) == self._proxy_model.sourceModel().plot_x_column)
        _prepare_plot_in_window_menu(self._add_to_plot_menu)
        self._menu.show()

    @Slot("QAction")
    def _add_column_to_plot(self, action):
        """Adds a single column to existing plot window."""
        window_id = action.text()
        plot_window = PlotWidget.plot_windows.get(window_id)
        if plot_window is None:
            self._plot_column()
            return
        try:
            support = PivotTablePlottingHints()
            plot_pivot_column(self._proxy_model, self._model_index.column(),
                              support, plot_window)
        except PlottingError as error:
            report_plotting_failure(error, self)

    @Slot()
    def _plot_column(self):
        """Plots a single column not the selection."""
        try:
            support = PivotTablePlottingHints()
            plot_window = plot_pivot_column(self._proxy_model,
                                            self._model_index.column(),
                                            support)
        except PlottingError as error:
            report_plotting_failure(error, self)
            return
        plot_window.use_as_window(
            self.parentWidget(),
            support.column_label(self._proxy_model,
                                 self._model_index.column()))
        plot_window.show()

    @Slot()
    def _set_x_flag(self):
        """Sets the X flag for a column."""
        index = self._proxy_model.mapToSource(self._model_index)
        self._proxy_model.sourceModel().set_plot_x_column(
            index.column(), self._set_as_x_action.isChecked())
コード例 #11
0
ファイル: QCanvas.py プロジェクト: Bernardrouhi/PuppetMaster
class CanvasGraphicsView(QGraphicsView):
    onSelection = Signal(PickNode)
    requestEditMode = Signal(bool)

    def __init__(self, parent=None):
        super(CanvasGraphicsView, self).__init__(parent)

        self.setFocusPolicy(Qt.StrongFocus)
        # Scene properties
        self.setAcceptDrops(True)
        self.setMouseTracking(True)
        self.setRenderHint(QPainter.Antialiasing)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setBackgroundBrush(QBrush(QColor(51, 51, 51)))
        self.setFrameShape(QFrame.NoFrame)
        self.setDragMode(QGraphicsView.RubberBandDrag)
        self.ViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate)

        self.init()

    def init(self):
        self.piiPath = str()
        self._model = {'background': str()}
        self._isPanning = False
        self._isZooming = False
        self._mousePressed = False
        self._scene = QGraphicsScene()
        self._scene.selectionChanged.connect(self.update_node_settings)
        self._backgroundNode = QGraphicsPixmapItem()
        self._scene.addItem(self._backgroundNode)
        self._orderSelected = list()
        self._lastPos = QPoint(0, 0)
        self.editMode = False
        self._namespace = str()
        self._dragMulti = list()

        self._defaultColor = QColor(255, 255, 255)
        self._defaultTextColor = QColor(0, 0, 0)
        self._defaultTextSize = 20
        self._defaultText = "New Node"

        self.workHight = 2160
        self.workWidth = 4096

        self.setScene(self._scene)
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.setBackgroundImage(str())

    def update_node_settings(self):
        if self._orderSelected:
            node = self._orderSelected[-1]
            self._defaultText = node.toPlainText()
            self._defaultColor = node.Background
            self._defaultTextColor = node.defaultTextColor()
            self._defaultTextSize = node.font().pointSize()

    def update_maya_selection(self):
        '''
        Update Maya Scene base on active selection.
        '''
        clearSelection()
        selection = list()
        for each in self._orderSelected:
            selection += each.Items
        if selection:
            selectObjects(selection)

    def setBackgroundImage(self, path=str()):
        '''
        Set background image

        Parameters
        ----------
        path: (str)
            Path to background image.
        '''
        self._model['background'] = path
        self.setStatusTip(self._model['background'])
        pixmap = QPixmap(self._model['background'])
        self._backgroundNode.setPixmap(pixmap)

    def getBackgroundImage(self):
        '''
        Get background image
        '''
        return self._model['background']

    BackgroundImage = property(getBackgroundImage, setBackgroundImage)

    def actionMenu(self, QPos):
        '''
        Show action menu.

        Parameters
        ----------
        QPos: (list)
            list of x and y location.
        '''
        self.mainMenu = QMenu()

        add_action = self.mainMenu.addAction('Add A Button')
        add_action.setEnabled(self.editMode)
        add_action.triggered.connect(self.add_node)

        addMany_action = self.mainMenu.addAction('Add Many Buttons')
        addMany_action.setEnabled(self.editMode)
        addMany_action.triggered.connect(self.add_multiple_nodes)

        activeNode = self.mouse_on_node()
        if activeNode:
            update_action = self.mainMenu.addAction('Update Button')
            update_action.setEnabled(self.editMode)
            update_action.triggered.connect(
                lambda: self.update_node(activeNode))

        delete_action = self.mainMenu.addAction('Delete Button')
        delete_action.setEnabled(self.editMode)
        delete_action.setShortcut('Backspace')
        delete_action.triggered.connect(self.removeSelected)

        self.mainMenu.addSeparator()

        # search for selected ButtonNode
        btnStatus = [
            isinstance(n, ButtonNode) for n in self._scene.selectedItems()
        ]
        if True in btnStatus:
            # first ButtonNode
            activeNode = self._scene.selectedItems()[btnStatus.index(True)]
            command_action = self.mainMenu.addAction('Edit Command Button...')
            command_action.setEnabled(self.editMode)
            command_action.triggered.connect(
                lambda: self.update_ButtonNode(activeNode))
        else:
            command_action = self.mainMenu.addAction('add Command Button...')
            command_action.setEnabled(self.editMode)
            command_action.triggered.connect(self.add_commands)

        self.mainMenu.addSeparator()

        reset_action = self.mainMenu.addAction('Reset View')
        reset_action.setShortcut('H')
        reset_action.triggered.connect(self.reset_view)

        frame_action = self.mainMenu.addAction('Frame View')
        frame_action.setShortcut('F')
        frame_action.triggered.connect(self.frame_view)

        self.mainMenu.addSeparator()

        alignGrp = QMenu('Align')
        self.mainMenu.addMenu(alignGrp)

        hac_action = alignGrp.addAction('Horizontal Align Center')
        hac_action.setIcon(QIconSVG('h_align-01'))
        hac_action.setEnabled(self.editMode)
        hac_action.triggered.connect(self.align_horizontal)

        vac_action = alignGrp.addAction('Vertical Align Center')
        vac_action.setIcon(QIconSVG('v_align-01'))
        vac_action.setEnabled(self.editMode)
        vac_action.triggered.connect(self.align_vertical)

        hd_action = alignGrp.addAction('Horizontal Distribute')
        hd_action.setIcon(QIconSVG('h_d_align-01'))
        hd_action.setEnabled(self.editMode)
        hd_action.triggered.connect(self.align_horizontal_distribute)

        vd_action = alignGrp.addAction('Vertical Distribute')
        vd_action.setIcon(QIconSVG('v_d_align-01'))
        vd_action.setEnabled(self.editMode)
        vd_action.triggered.connect(self.align_vertical_distribute)

        alignGrp.addSeparator()

        ins_action = alignGrp.addAction('Increase Size')
        ins_action.setShortcut('+')
        ins_action.setEnabled(self.editMode)
        ins_action.triggered.connect(self.increase_size)

        dis_action = alignGrp.addAction('Decrease Size')
        dis_action.setShortcut('-')
        dis_action.setEnabled(self.editMode)
        dis_action.triggered.connect(self.decrease_size)

        self.mainMenu.addSeparator()

        edit_mode = self.mainMenu.addAction('Edit Mode')
        edit_mode.setCheckable(True)
        edit_mode.setChecked(self.editMode)
        edit_mode.triggered.connect(
            lambda: self.request_edit(not self.editMode))

        pos = self.mapToGlobal(QPoint(0, 0))
        self.mainMenu.move(pos + QPos)
        self.mainMenu.show()

    def mouse_on_node(self):
        globPosition = self.mapFromGlobal(QCursor.pos())
        scenePosition = self.mapToScene(globPosition)
        for node in self._scene.items():
            if isinstance(node, PickNode):
                if node.mapRectToScene(
                        node.boundingRect()).contains(scenePosition):
                    return node
        return None

    def update_node(self, node=PickNode):
        '''
        Update the Node selection base on selection in maya.
        '''
        mayaScene = getActiveItems()
        # for each in self._scene.selectedItems():
        node.Items = mayaScene

    def update_ButtonNode(self, node=ButtonNode):
        '''
        Update the ButtonNode commands.

        Parameters
        ----------
        node: (ButtonNode)
            ButtonNode Node.
        '''
        self.newCommand = CommandDialog(text=node.toPlainText(),
                                        cmd=node.Command,
                                        cmdType=node.CommandsType)
        if self.newCommand.exec_() == QDialog.Accepted:
            data = self.newCommand.Raw
            node.setPlainText(data[PIIButton.TEXT])
            node.Command = data[PIIButton.COMMAND]
            node.CommandsType = data[PIIButton.COMMANDTYPE]

    def add_commands(self):
        '''
        Create a new ButtonNode with Commands.
        '''
        globPosition = self.mapFromGlobal(QCursor.pos())
        scenePosition = self.mapToScene(globPosition)
        self.newCommand = CommandDialog()
        if self.newCommand.exec_() == QDialog.Accepted:
            data = self.newCommand.Raw
            self.create_button(position=scenePosition,
                               text=data[PIIButton.TEXT],
                               size=self._defaultTextSize,
                               textColor=self._defaultTextColor,
                               bgColor=self._defaultColor,
                               cmd=data[PIIButton.COMMAND],
                               cmdType=data[PIIButton.COMMANDTYPE])

    def align_horizontal(self):
        '''
        Align the selection to center horizontally.
        '''
        selected = self._scene.selectedItems()
        if len(selected) > 1:
            minValue = selected[0].y()
            maxValue = selected[0].y()
            whole = None
            for each in selected:
                y = each.y()
                value = y + each.boundingRect().height()
                # finding lowest value
                minValue = y if y < minValue else minValue
                minValue = value if value < minValue else minValue
                # finding highest value
                maxValue = y if y > maxValue else maxValue
                maxValue = value if value > maxValue else maxValue

            total = maxValue - minValue
            if total != 0:
                middle = (maxValue + minValue) / 2
                for each in selected:
                    center = each.shape().boundingRect().center()
                    start_y = each.y()
                    offset = start_y + center.y() - middle
                    each.setY(each.y() - offset)

    def align_vertical(self):
        '''
        Align the selection to center vertically.
        '''
        selected = self._scene.selectedItems()
        if len(selected) > 1:
            # sort it based on x position + width
            selected = sorted(selected,
                              key=lambda x: x.x() + x.boundingRect().width())
            leftNode = selected[0]
            rightNode = selected[-1]
            # total length of x axis
            total = rightNode.boundingRect().width() + rightNode.x(
            ) - leftNode.x()
            if total != 0:
                middle = (total / 2) + leftNode.x()
                for each in selected:
                    center = each.shape().boundingRect().center()
                    start_x = each.x()
                    offset = start_x + center.x() - middle
                    each.setX(each.x() - offset)

    def align_horizontal_distribute(self):
        '''
        Disturbute the selected nodes evenly between first node on the left and last 
        node on the right horizontally.
        '''
        selected = self._scene.selectedItems()
        if len(selected) > 2:
            # sort it based on x position + width
            selected = sorted(selected,
                              key=lambda x: x.x() + x.boundingRect().width())
            startItem = selected.pop(0)
            endItem = selected.pop(-1)

            # total length of items
            itemsLength = int()
            for each in selected:
                itemsLength += each.boundingRect().width()

            startPoint = startItem.x() + startItem.boundingRect().width()
            total = endItem.x() - startPoint
            section_num = len(selected) + 1
            extraSpace = total - itemsLength
            # nicly divide
            if extraSpace > 0:
                gap = extraSpace / section_num
                nextPlace = startPoint
                for each in selected:
                    newLoc = nextPlace + gap
                    nextPlace += gap + each.boundingRect().width()
                    each.setX(newLoc)
            else:
                total = endItem.x() - startPoint
                gap = total / section_num
                nextPlace = startPoint
                for each in selected:
                    nextPlace += gap
                    each.setX(nextPlace)
        else:
            errorMes("PUPPETMASTER-INFO: Select more than 2 nodes.")

    def align_vertical_distribute(self):
        '''
        Disturbute the selected nodes evenly between first node on the top and last 
        node on the bottom vertically.
        '''
        selected = self._scene.selectedItems()
        if len(selected) > 2:
            # sort it based on y position + width
            selected = sorted(
                selected,
                key=lambda node: node.y() + node.boundingRect().height())
            startItem = selected.pop(0)
            endItem = selected.pop(-1)

            # total length of items
            itemsLength = int()
            for each in selected:
                itemsLength += each.boundingRect().height()

            startPoint = startItem.y() + startItem.boundingRect().height()
            total = endItem.y() - startPoint
            section_num = len(selected) + 1
            extraSpace = total - itemsLength
            # nicly divide
            if extraSpace > 0:
                gap = extraSpace / section_num
                nextPlace = startPoint
                for each in selected:
                    newLoc = nextPlace + gap
                    nextPlace += gap + each.boundingRect().height()
                    each.setY(newLoc)
            else:
                total = endItem.y() - startPoint
                gap = total / section_num
                nextPlace = startPoint
                for each in selected:
                    nextPlace += gap
                    each.setY(nextPlace)
        else:
            errorMes("PUPPETMASTER-INFO: Select more than 2 nodes.")

    def reset_view(self):
        '''
        Fit all the items to the view.
        '''
        items = self._scene.items()
        if items:
            rects = [
                item.mapToScene(item.boundingRect()).boundingRect()
                for item in items
            ]
            rect = self.min_bounding_rect(rects)
            self._scene.setSceneRect(rect)
            self.fitInView(rect, Qt.KeepAspectRatio)

    def frame_view(self):
        '''
        Fit selected items to the view.
        '''
        items = self._scene.selectedItems()
        if items:
            rects = [
                item.mapToScene(item.boundingRect()).boundingRect()
                for item in items
            ]
            rect = self.min_bounding_rect(rects)
            self.fitInView(rect, Qt.KeepAspectRatio)

    def fit_contents(self):
        '''
        Update the scene boundery.
        '''
        items = self._scene.items()
        if items:
            rects = [
                item.mapToScene(item.boundingRect()).boundingRect()
                for item in items
            ]
            rect = self.min_bounding_rect(rects)
            self._scene.setSceneRect(rect)

    def request_edit(self, value=bool):
        self.requestEditMode.emit(value)

    def min_bounding_rect(self, rectList=list()):
        '''
        Get the minimum boundry based on objects in the scene.

        Parameters
        ----------
        rectList: (list)
            List of QRectF (boundry of objects)

        Return
        ------
        out: (QRectF)
            Get the minimum boundry
        '''
        minX = rectList[0].left()
        minY = rectList[0].top()
        maxX = rectList[0].right()
        maxY = rectList[0].bottom()

        for k in range(1, len(rectList)):
            minX = min(minX, rectList[k].left())
            minY = min(minY, rectList[k].top())
            maxX = max(maxX, rectList[k].right())
            maxY = max(maxY, rectList[k].bottom())

        return QRectF(minX, minY, maxX - minX, maxY - minY)

    def increase_size(self):
        '''
        Increase the size of selected items by 1 unit.
        '''
        selected = self._scene.selectedItems()
        for each in selected:
            font = each.font()
            fontSize = font.pointSize()
            if fontSize < 99:
                fontSize += 1
                font.setPointSize(fontSize)
                each.setFont(font)

    def decrease_size(self):
        '''
        Decrease the size of selected items by 1 unit.
        '''
        selected = self._scene.selectedItems()
        for each in selected:
            font = each.font()
            fontSize = font.pointSize()
            if fontSize > 1:
                fontSize -= 1
                font.setPointSize(fontSize)
                each.setFont(font)

    def is_texture(self, path=str):
        '''
        Check if the texture path is valid.

        Return
        ------
        out: (bool)
            True if texture is valide, otherwise False.
        '''
        if path.lower().endswith(IMAGE_FORMATS):
            return True
        return False

    def _QMimeDataToFile(self, data=QMimeData):
        '''
        Get all the filepath from drag file.

        Parameters
        ----------
        data: (QMimeData)
            QMimeData of dragged file.
        '''
        files = list()
        if data.hasUrls:
            for each in data.urls():
                files.append(each.toLocalFile())
        return files

    def _is_dragValid(self, event):
        '''
        Check for draged file validation
        '''
        dragedItems = self._QMimeDataToFile(event.mimeData())
        if dragedItems:
            first_path = dragedItems[0]
            if self.is_texture(first_path) and self.editMode:
                return True
        return False

    def dragEnterEvent(self, event):
        event.accept() if self._is_dragValid(event) else event.ignore()

    def dragMoveEvent(self, event):
        event.accept() if self._is_dragValid(event) else event.ignore()

    def dropEvent(self, event):
        dragedItems = self._QMimeDataToFile(event.mimeData())
        if dragedItems:
            first_path = dragedItems[0]
            if self.is_texture(first_path):
                self.setBackgroundImage(path=first_path)
                event.accept()
        else:
            event.ignore()

    def mousePressEvent(self, event):
        self._lastPos = event.pos()
        self._lastScenePos = self.mapToScene(event.pos())
        if self._dragMulti:
            for each in self._dragMulti:
                each.setSelected(True)
            self._dragMulti = list()
        if event.button() == Qt.MiddleButton:
            self._isPanning = True
            self.setCursor(QPixmap(iconSVG('nav-pan-02')))
            self._dragPos = event.pos()
            event.accept()
        elif event.button() == Qt.RightButton:
            if event.modifiers() == Qt.AltModifier:
                self._isZooming = True
                self.setCursor(QPixmap(iconSVG('nav-zoom-02')))
                self._dragPos = event.pos()
                self._dragPos2 = self.mapToScene(event.pos())
            else:
                self.actionMenu(event.pos())
            event.accept()
        else:
            super(CanvasGraphicsView, self).mousePressEvent(event)

    def mouseMoveEvent(self, event):
        if self._dragMulti and len(self._dragMulti) > 1:
            start = self._lastScenePos
            end = self.mapToScene(event.pos())

            total = len(self._dragMulti) - 1
            xLength = start.x() - end.x()
            yLength = start.y() - end.y()
            xStep = 0 if xLength == 0 else -(xLength / total)
            yStep = 0 if yLength == 0 else -(yLength / total)
            num = 0
            for each in self._dragMulti:
                position = QPointF(start.x() + (num * xStep),
                                   start.y() + (num * yStep))
                each.setPos(position)
                num += 1

        if self._isPanning:
            newPos = event.pos()
            diff = newPos - self._dragPos
            self._dragPos = newPos
            self.horizontalScrollBar().setValue(
                self.horizontalScrollBar().value() - diff.x())
            self.verticalScrollBar().setValue(
                self.verticalScrollBar().value() - diff.y())
            event.accept()
        elif self._isZooming:
            newPos = event.pos()
            diff = newPos - self._dragPos
            self._dragPos = newPos
            factor = 1.000
            if diff.x() < 0:
                factor = 0.98
            else:
                factor = 1.02

            self.scale(factor, factor)
            event.accept()
        else:
            if event.modifiers() == Qt.ShiftModifier:
                diff = event.pos() - self._lastPos
                x = event.x() if abs(diff.x()) > abs(
                    diff.y()) else self._lastPos.x()
                y = event.y() if abs(diff.y()) > abs(
                    diff.x()) else self._lastPos.y()
                event = QMouseEvent(QEvent.MouseMove, QPoint(x, y),
                                    self.mapToGlobal(QPoint(x,
                                                            y)), Qt.LeftButton,
                                    Qt.LeftButton, Qt.NoModifier)
            super(CanvasGraphicsView, self).mouseMoveEvent(event)

    def mouseReleaseEvent(self, event):
        self._isPanning = False
        self._isZooming = False
        self.setCursor(Qt.ArrowCursor)
        super(CanvasGraphicsView, self).mouseReleaseEvent(event)
        self.fit_contents()
        self.update_maya_selection()

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Backspace or event.key() == Qt.Key_Delete:
            if self.editMode:
                self.removeSelected()
        elif event.key() == Qt.Key_Plus:
            if self.editMode:
                self.increase_size()
        elif event.key() == Qt.Key_Minus:
            if self.editMode:
                self.decrease_size()
        elif event.key() == Qt.Key_H:
            self.reset_view()
        elif event.key() == Qt.Key_F:
            self.frame_view()
        else:
            super(CanvasGraphicsView, self).keyPressEvent(event)

    def removeSelected(self):
        '''
        Remove selected Items.
        '''
        for each in self._scene.selectedItems():
            self._scene.removeItem(each)
            self.remove_stack(each)

    def wheelEvent(self, event):
        factor = 1.05
        if event.delta() < 0:
            # factor = .2 / factor
            factor = 0.95
        self.scale(factor, factor)
        self.update()

    def add_node(self):
        '''
        Add a new PickNode to the scene.
        '''
        # Cursor Position on Scene
        globPosition = self.mapFromGlobal(QCursor.pos())
        scenePosition = self.mapToScene(globPosition)

        self.create_node(text=self._defaultText,
                         size=self._defaultTextSize,
                         textColor=self._defaultTextColor,
                         bgColor=self._defaultColor,
                         position=scenePosition,
                         items=getActiveItems(),
                         shape=PickShape.SQUARE)

    def add_multiple_nodes(self):
        '''
        Add group of PickNode bellow each other to the scene.
        '''
        # Cursor Position on Scene
        globPosition = self.mapFromGlobal(QCursor.pos())
        scenePosition = self.mapToScene(globPosition)

        self._dragMulti = list()
        for each in getActiveItems():
            node = self.create_node(text=self._defaultText,
                                    size=self._defaultTextSize,
                                    textColor=self._defaultTextColor,
                                    bgColor=self._defaultColor,
                                    position=scenePosition,
                                    items=[each],
                                    shape=PickShape.SQUARE)
            self._dragMulti.append(node)
            # scenePosition = QPointF(scenePosition.x(), node.y() + node.boundingRect().height() + 5)

    def create_node(self,
                    position=list,
                    text=str,
                    size=int,
                    textColor=QColor,
                    bgColor=QColor,
                    items=list,
                    shape=PickShape.SQUARE):
        '''
        Create a new PickNode.

        Parameters
        ----------
        position: (list)
            List of x and y location.
        text: (str)
            Name of the text.
        size: (int)
            Size of the text.
        textColor: (QColor)
            Color of the text.
        bgColor: (QColor)
            Background Color of the node.
        items: (list)
            List of selected Maya object.

        Return
        ------
        out: (PickNode)
            Reference of created Node.
        '''
        textNode = PickNode()
        font = QFont("SansSerif", size)
        font.setStyleHint(QFont.Helvetica)
        textNode.setFont(font)
        textNode.setDefaultTextColor(textColor)
        textNode.setFlag(QGraphicsItem.ItemIsMovable, self.editMode)
        textNode.setFlag(QGraphicsItem.ItemIsSelectable)
        # textNode.setFlag(QGraphicsItem.ItemIsFocusable, self.editMode)
        textNode.Background = bgColor
        textNode.Items = items
        textNode.Shape = shape

        textNode.onSelected.connect(lambda: self.onSelection.emit(textNode))
        textNode.onAddToStack.connect(lambda: self.add_stack(textNode))
        textNode.onRemoveFromStack.connect(lambda: self.remove_stack(textNode))

        textNode.setPos(position)
        textNode.setPlainText(text)

        self._scene.addItem(textNode)
        return textNode

    def create_button(self,
                      position=list,
                      text=str,
                      size=int,
                      textColor=QColor,
                      bgColor=QColor,
                      cmd=str,
                      cmdType=str):
        '''
        Create a new ButtonNode.

        Parameters
        ----------
        position: (list)
            List of x and y location.
        text: (str)
            Name of the text.
        size: (int)
            Size of the text.
        textColor: (QColor)
            Color of the text.
        bgColor: (QColor)
            Background Color of the node.
        cmd: (str)
            Command to run when it's pressed.
        cmdType: (str)
            Type of command.("python"/"mel")
        '''
        btnNode = ButtonNode()
        font = QFont("SansSerif", size)
        font.setStyleHint(QFont.Helvetica)
        btnNode.setFont(font)
        btnNode.setDefaultTextColor(textColor)
        btnNode.setFlag(QGraphicsItem.ItemIsMovable, self.editMode)
        btnNode.setFlag(QGraphicsItem.ItemIsSelectable)
        btnNode.Background = bgColor
        btnNode.CommandsType = cmdType
        btnNode.Command = cmd

        # btnNode.onSelected.connect(lambda: self.onSelection.emit(textNode))
        btnNode.onSelected.connect(lambda: self.onSelection.emit(btnNode))
        btnNode.onClicked.connect(self.scriptJob)

        btnNode.setPos(position)
        btnNode.setPlainText(text)

        self._scene.addItem(btnNode)

    def scriptJob(self, cmdType=str, cmd=str):
        '''
        Run a command.

        Parameters
        ----------
        cmd: (str)
            Command to run.
        cmdType: (str)
            Type of command.("python"/"mel")
        '''
        if not self.editMode:
            if cmdType == CommandType.PYTHON:
                runPython(cmd)
            elif cmdType == CommandType.MEL:
                runMel(cmd)

    def add_stack(self, node=PickNode):
        '''
        Add a node selection in right order into the stack.

        Parameters
        ----------
        node: (PickNode)
            Selected node.
        '''
        self._orderSelected.append(node)

    def remove_stack(self, node=PickNode):
        '''
        Remove a node from the stack.

        Parameters
        ----------
        node: (PickNode)
            Selected node.
        '''
        if node in self._orderSelected:
            index = self._orderSelected.index(node)
            self._orderSelected.pop(index)

    def get_edit(self):
        return self.editMode

    def set_edit(self, value=bool):
        self.editMode = value
        for each in self._scene.items():
            if type(each) == PickNode:
                each.setFlag(QGraphicsItem.ItemIsMovable, self.editMode)
            elif type(each) == ButtonNode:
                each.setFlag(QGraphicsItem.ItemIsMovable, self.editMode)

    Edit = property(get_edit, set_edit)

    def get_path(self):
        return self.piiPath

    def set_path(self, path=str):
        self.piiPath = path

    Path = property(get_path, set_path)

    def get_raw(self):
        '''
        Get the scene information. (can be be save in .pii)

        Return
        ------
        out: (dict)
            Dictionary of scene date to be save in .pii file.
        '''
        image_data = str()
        pixmap = self._backgroundNode.pixmap()
        # Extract Image Data
        if not pixmap.isNull():
            buffer = QBuffer()
            buffer.open(QIODevice.WriteOnly)
            pixmap.save(buffer, "PNG")
            # Image Data
            image_data = bytes(buffer.data().toBase64()).decode('ascii')

        nodeList = []
        for each in self._scene.items():
            if type(each) == PickNode:
                textColor = each.defaultTextColor()
                bgColor = each.Background
                item = {
                    PIIPick.TYPE:
                    PIINode.PICK,
                    PIIPick.TEXT:
                    each.toPlainText(),
                    PIIPick.SIZE:
                    each.font().pointSize(),
                    PIIPick.POSITION: (each.pos().x(), each.pos().y()),
                    PIIPick.COLOR:
                    (textColor.red(), textColor.green(), textColor.blue()),
                    PIIPick.BACKGROUND:
                    (bgColor.red(), bgColor.green(), bgColor.blue()),
                    PIIPick.SELECTION:
                    each.Items,
                    PIIPick.SHAPE:
                    each.Shape
                }
                nodeList.append(item)
            elif type(each) == ButtonNode:
                textColor = each.defaultTextColor()
                bgColor = each.Background
                item = {
                    PIIButton.TYPE:
                    PIINode.BUTTON,
                    PIIButton.TEXT:
                    each.toPlainText(),
                    PIIButton.SIZE:
                    each.font().pointSize(),
                    PIIButton.POSITION: (each.pos().x(), each.pos().y()),
                    PIIButton.COLOR:
                    (textColor.red(), textColor.green(), textColor.blue()),
                    PIIButton.BACKGROUND:
                    (bgColor.red(), bgColor.green(), bgColor.blue()),
                    PIIButton.COMMAND:
                    each.Command,
                    PIIButton.COMMANDTYPE:
                    each.CommandsType
                }
                nodeList.append(item)

        rawData = {
            PII.VERSION: "1.0.0",
            PII.BACKGROUND: image_data,
            PII.NODES: nodeList
        }
        return rawData

    def set_raw(self, data=dict):
        '''
        set the scene information. (information from .pii)

        Parameters
        ----------
        data: (dict)
            Dictionary of date from .pii file.
        '''
        if data:
            if data[PII.VERSION] == "1.0.0":
                self.load_1_0_0(data)

    Raw = property(get_raw, set_raw)

    def get_namespace(self):
        '''
        Get namespace of all PickNode.

        Return
        ------
        out: (list)
            List of namespaces.
        '''
        namespaceList = []
        for each in self._scene.items():
            if type(each) == PickNode:
                valueList = each.Items
                for sObj in valueList:
                    if ":" in sObj:
                        group = sObj.split(":")[:-1]
                        for index in range(len(group)):
                            namespaceList.append(":".join(group[:index + 1]))
        return list(set(namespaceList))

    def set_namespace(self, data=dict):
        '''
        Set namespace of all PickNode.

        Parameters
        ----------
        data: (dict)
            Dictionary of namespace with value of new namespace.
        '''
        for each in self._scene.items():
            if type(each) == PickNode:
                valueList = each.Items
                newValue = list()
                for sObj in valueList:
                    if ":" in sObj:
                        # namesapce
                        nameS = ":".join(sObj.split(":")[:-1])
                        # object name
                        object_name = sObj.split(":")[-1]
                        keys = data.keys()
                        keys.sort(reverse=True)
                        for key in keys:
                            if key in nameS:
                                nameS = nameS.replace(key, data[key], 1)
                        # making sure doesn't start with ':'
                        nameS = nameS[1:] if nameS.startswith(":") else nameS
                        # add the object to namespace
                        nameS = ":".join([nameS, object_name
                                          ]) if nameS else object_name
                        newValue.append(nameS)
                    else:
                        newValue.append(sObj)
                each.Items = newValue

    Namespace = property(get_namespace, set_namespace)

    def get_NSHistory(self):
        return self._namespace

    def set_NSHistory(self, name=str):
        self._namespace = name

    NamespaceHistory = property(get_NSHistory, set_NSHistory)

    def get_highlight(self):
        return

    def set_highlight(self, data=list):
        if data:
            for each in self._scene.items():
                # QApplication.processEvents()
                if type(each) == PickNode:
                    for item in data:
                        if item in each.Items:
                            each.Highlight = True
                            break
                        else:
                            each.Highlight = False
        else:
            for each in self._scene.items():
                if type(each) == PickNode:
                    each.Highlight = False

    Highlight = property(get_highlight, set_highlight)

    def clear_scene(self):
        '''
        Clear the scene.
        '''
        self._orderSelected = list()
        self._scene.clear()
        self._backgroundNode = QGraphicsPixmapItem()
        self._scene.addItem(self._backgroundNode)
        self.reset_view()

    def is_changed(self):
        '''
        Check for the scene changes.
        '''
        if self._backgroundNode.pixmap():
            return True
        elif len(self._scene.items()) > 1:
            return True
        return False

    def load_1_0_0(self, data=dict):
        '''
        Load v1.0.0 of .pii version file.

        Parameters
        ----------
        data: (dict)
            Dictionary of date from .pii file.
        '''
        if data[PII.BACKGROUND]:
            # Import Image Data
            newPix = QPixmap()
            newPix.loadFromData(
                QByteArray.fromBase64(data[PII.BACKGROUND].encode('ascii')),
                "PNG")
            self._backgroundNode.setPixmap(newPix)

        for each in data[PII.NODES]:
            if each["type"] == PIINode.PICK:
                self.create_node(text=each[PIIPick.TEXT],
                                 size=each[PIIPick.SIZE],
                                 textColor=QColor(*each[PIIPick.COLOR]),
                                 bgColor=QColor(*each[PIIPick.BACKGROUND]),
                                 position=QPointF(*each[PIIPick.POSITION]),
                                 items=each[PIIPick.SELECTION],
                                 shape=each[PIIPick.SHAPE])
            elif each["type"] == PIINode.BUTTON:
                self.create_button(position=QPointF(*each[PIIButton.POSITION]),
                                   text=each[PIIButton.TEXT],
                                   size=each[PIIButton.SIZE],
                                   textColor=QColor(*each[PIIButton.COLOR]),
                                   bgColor=QColor(*each[PIIButton.BACKGROUND]),
                                   cmd=each[PIIButton.COMMAND],
                                   cmdType=each[PIIButton.COMMANDTYPE])

    def set_nodes_bg_color(self, color=QColor):
        '''
        Set background color of selected nodes.

        Parameters
        ----------
        color: (QColor)
            QColor value.
        '''
        self._defaultColor = color
        for each in self._scene.selectedItems():
            each.Background = color
        self.update()

    def set_nodes_font_color(self, color=QColor):
        '''
        Set font color of selected nodes.

        Parameters
        ----------
        color: (QColor)
            QColor value.
        '''
        self._defaultTextColor = color
        for each in self._scene.selectedItems():
            each.setDefaultTextColor(color)

    def set_nodes_font_size(self, size=int):
        '''
        Set font size of selected nodes.

        Parameters
        ----------
        size: (int)
            font size.
        '''
        self._defaultTextSize = size
        for each in self._scene.selectedItems():
            font = each.font()
            font.setPointSize(size)
            each.setFont(font)

    def set_nodes_text(self, text=str):
        '''
        Set text for selected nodes.

        Parameters
        ----------
        text: (str)
            text for the node.
        '''
        self._defaultText = text
        for each in self._scene.selectedItems():
            each.setPlainText(text)

    def set_nodes_shape(self, shape=str):
        '''
        Set shape for selected nodes.

        Parameters
        ----------
        shape: (str)
            name for the shape.
        '''
        for each in self._scene.selectedItems():
            if isinstance(each, PickNode):
                each.Shape = shape
コード例 #12
0
ファイル: ContextMenu.py プロジェクト: jthirp/pivy_jtp
class ContextMenu(QObject):
    def __init__(self, quarterwidget):
        QObject.__init__(self, quarterwidget)
        #QObject.__init__(quarterwidget)

        self._quarterwidget = quarterwidget
        self._rendermanager = self._quarterwidget.getSoRenderManager()

        self.contextmenu = QMenu()
        self.functionsmenu = QMenu("Functions")
        self.rendermenu = QMenu("Render Mode")
        self.stereomenu = QMenu("Stereo Mode")
        self.transparencymenu = QMenu("Transparency Type")

        self.functionsgroup = QActionGroup(self)
        self.stereomodegroup = QActionGroup(self)
        self.rendermodegroup = QActionGroup(self)
        self.transparencytypegroup = QActionGroup(self)

        self.rendermodes = []
        self.rendermodes.append((SoRenderManager.AS_IS, "as is"))
        self.rendermodes.append((SoRenderManager.WIREFRAME, "wireframe"))
        self.rendermodes.append(
            (SoRenderManager.WIREFRAME_OVERLAY, "wireframe overlay"))
        self.rendermodes.append((SoRenderManager.POINTS, "points"))
        self.rendermodes.append((SoRenderManager.HIDDEN_LINE, "hidden line"))
        self.rendermodes.append((SoRenderManager.BOUNDING_BOX, "bounding box"))

        self.stereomodes = []
        self.stereomodes.append((SoRenderManager.MONO, "mono"))
        self.stereomodes.append((SoRenderManager.ANAGLYPH, "anaglyph"))
        self.stereomodes.append((SoRenderManager.QUAD_BUFFER, "quad buffer"))
        self.stereomodes.append(
            (SoRenderManager.INTERLEAVED_ROWS, "interleaved rows"))
        self.stereomodes.append(
            (SoRenderManager.INTERLEAVED_COLUMNS, "interleaved columns"))

        self.transparencytypes = []
        self.transparencytypes.append((SoGLRenderAction.NONE, "none"))
        self.transparencytypes.append(
            (SoGLRenderAction.SCREEN_DOOR, "screen door"))
        self.transparencytypes.append((SoGLRenderAction.ADD, "add"))
        self.transparencytypes.append(
            (SoGLRenderAction.DELAYED_ADD, "delayed add"))
        self.transparencytypes.append(
            (SoGLRenderAction.SORTED_OBJECT_ADD, "sorted object add"))
        self.transparencytypes.append((SoGLRenderAction.BLEND, "blend"))
        self.transparencytypes.append(
            (SoGLRenderAction.DELAYED_BLEND, "delayed blend"))
        self.transparencytypes.append(
            (SoGLRenderAction.SORTED_OBJECT_BLEND, "sorted object blend"))
        self.transparencytypes.append(
            (SoGLRenderAction.SORTED_OBJECT_SORTED_TRIANGLE_ADD,
             "sorted object sorted triangle add"))
        self.transparencytypes.append(
            (SoGLRenderAction.SORTED_OBJECT_SORTED_TRIANGLE_BLEND,
             "sorted object sorted triangle blend"))
        self.transparencytypes.append(
            (SoGLRenderAction.SORTED_LAYERS_BLEND, "sorted layers blend"))

        self.rendermodeactions = []
        for first, second in self.rendermodes:
            action = QAction(second, self)
            action.setCheckable(True)
            action.setChecked(self._rendermanager.getRenderMode() == first)
            action.setData(first)
            self.rendermodeactions.append(action)
            self.rendermodegroup.addAction(action)
            self.rendermenu.addAction(action)

        self.stereomodeactions = []
        for first, second in self.stereomodes:
            action = QAction(second, self)
            action.setCheckable(True)
            action.setChecked(self._rendermanager.getStereoMode() == first)
            action.setData(first)
            self.stereomodeactions.append(action)
            self.stereomodegroup.addAction(action)
            self.stereomenu.addAction(action)

        self.transparencytypeactions = []
        for first, second in self.transparencytypes:
            action = QAction(second, self)
            action.setCheckable(True)
            action.setChecked(self._rendermanager.getGLRenderAction().
                              getTransparencyType() == first)
            action.setData(first)
            self.transparencytypeactions.append(action)
            self.transparencytypegroup.addAction(action)
            self.transparencymenu.addAction(action)

        viewall = QAction("View All", self)
        seek = QAction("Seek", self)
        self.functionsmenu.addAction(viewall)
        self.functionsmenu.addAction(seek)

        self.connect(seek, QtCore.SIGNAL("triggered(bool)"), self.seek)

        self.connect(viewall, QtCore.SIGNAL("triggered(bool)"), self.viewAll)

        self.connect(self.rendermodegroup,
                     QtCore.SIGNAL("triggered(QAction *)"),
                     self.changeRenderMode)

        self.connect(self.stereomodegroup,
                     QtCore.SIGNAL("triggered(QAction *)"),
                     self.changeStereoMode)

        self.connect(self.transparencytypegroup,
                     QtCore.SIGNAL("triggered(QAction *)"),
                     self.changeTransparencyType)

        self.contextmenu.addMenu(self.functionsmenu)
        self.contextmenu.addMenu(self.rendermenu)
        self.contextmenu.addMenu(self.stereomenu)
        self.contextmenu.addMenu(self.transparencymenu)

    def __del__(self):
        del self.functionsmenu
        del self.rendermenu
        del self.stereomenu
        del self.transparencymenu
        del self.contextmenu

    def getMenu(self):
        return self.contextmenu

    def exec_(self, pos):
        self._processEvent("sim.coin3d.coin.PopupMenuOpen")
        self.contextmenu.exec_(pos)

    def seek(self, checked):
        self._processEvent("sim.coin3d.coin.navigation.Seek")

    def viewAll(self, checked):
        self._processEvent("sim.coin3d.coin.navigation.ViewAll")

    def _processEvent(self, eventname):
        eventmanager = self._quarterwidget.getSoEventManager()
        for c in range(eventmanager.getNumSoScXMLStateMachines()):
            sostatemachine = eventmanager.getSoScXMLStateMachine(c)
            sostatemachine.queueEvent(coin.SbName(eventname))
            sostatemachine.processEventQueue()

    def changeRenderMode(self, action):
        try:
            self._rendermanager.setRenderMode(action.data().toInt()[0])
        except AttributeError:
            self._rendermanager.setRenderMode(action.data())

    def changeStereoMode(self, action):
        try:
            self._rendermanager.setStereoMode(action.data().toInt()[0])
        except AttributeError:
            self._rendermanager.setStereoMode(action.data())

    def changeTransparencyType(self, action):
        try:
            self._quarterwidget.setTransparencyType(action.data().toInt()[0])
        except AttributeError:
            self._quarterwidget.setTransparencyType(action.data())
コード例 #13
0
ファイル: Main.py プロジェクト: zorowyw/VaspStudio
class Main():
    def __init__(self, debug_mode=False):

        self.debug_mode = debug_mode

        self.xsd_files_item = []

        self.main_window = QtWidgets.QMainWindow()
        self.main_window_ui = Ui_VASPStudio()
        self.main_window_ui.setupUi(self.main_window)

        self.create_project = QtWidgets.QDialog()
        self.create_project_ui = Ui_CreateProject()
        self.create_project_ui.setupUi(self.create_project)

        self.about_window = QtWidgets.QDialog()
        self.about_window_ui = Ui_about()
        self.about_window_ui.setupUi(self.about_window)

        #self.main_window.setWindowFlags()
        self.main_window.setFixedSize(self.main_window.width(),
                                      self.main_window.height())

        self.last_save_path = None
        self.vsp = None

        self.command_output = ""  # 控制台输出,等同于GUI界面的控制台输出结果,用于分析

        self.binding_main_window()
        self.binding_create_project_window()

        # 注意header要多一行
        self.xsd_file_headers = [
            "File", "Status", "Type", "Mark", "Energy", "Final RMS",
            "Work Node", "Job", "Match State", "备注"
        ]
        self.xsd_file_contents = [
            "status", "type", "mark_text", "energy", "final_RMS", "nodel",
            "submit_job", "match_state", "note"
        ]

    def main_window_error(self, string):
        QMessageBox.critical(self.main_window, "Error", string)

    def main_window_info(self, string):
        QMessageBox.information(self.main_window, "Information", string)

    def main_window_warn(self, string):
        QMessageBox.warning(self.main_window, "Warning", string)

    def generate_item_window(self):
        # item的设定,item自动与vsp同步

        self.text_item_window = Text_File_Item_Window(
            main_window=self.main_window,
            vsp=self.vsp,
            new_button=self.main_window_ui.textItemNew,
            edit_button=self.main_window_ui.textItemEdit,
            remove_button=self.main_window_ui.textItemRemove,
            check_button=self.main_window_ui.textItemCheck,
            list_widget=self.main_window_ui.textItemListWidget)
        self.text_item_window.update()

        self.file_item_window = File_Item_Window(
            main_window=self.main_window,
            vsp=self.vsp,
            new_button=self.main_window_ui.newFile,
            edit_button=self.main_window_ui.editFile,
            remove_button=self.main_window_ui.removeFile,
            check_button=self.main_window_ui.checkFile,
            list_widget=self.main_window_ui.fileWidget)
        self.file_item_window.update()

        self.tf_window = TF_Window(
            main_window=self.main_window,
            vsp=self.vsp,
            new_button=self.main_window_ui.newFunction,
            edit_button=self.main_window_ui.editFunction,
            remove_button=self.main_window_ui.removeFunction,
            check_button=self.main_window_ui.checkFunction,
            list_widget=self.main_window_ui.functionListWidget)
        self.tf_window.update()

        self.key_item_window = Key_Item_Window(
            main_window=self.main_window,
            vsp=self.vsp,
            new_button=self.main_window_ui.keyNew,
            edit_button=self.main_window_ui.keyEdit,
            remove_button=self.main_window_ui.keyRemove,
            check_button=self.main_window_ui.keyCheck,
            list_widget=self.main_window_ui.keyListWidget)
        self.key_item_window.update()

        self.job_submit_window = SubmitJob_Window(
            main_window=self.main_window,
            vsp=self.vsp,
            new_button=self.main_window_ui.submitJobNew,
            edit_button=self.main_window_ui.submitJobEdit,
            remove_button=self.main_window_ui.submitJobRemove,
            check_button=self.main_window_ui.submitJobCheck,
            list_widget=self.main_window_ui.jobSubmitListWidget)
        self.job_submit_window.update()

    def refresh(self):
        # 不只是更新,还要检测新文件
        self.check_file_change_and_update_file()
        self.update_xsd_files_information()

    def binding_main_window(self):
        # 绑定打开项目对话框
        self.main_window_ui.actionNewProject.triggered.connect(
            self.create_project.show)
        self.main_window_ui.tabProjectInformationRefreshButton.clicked.connect(
            self.update_project_information)
        self.main_window_ui.actionOpenProject.triggered.connect(
            self.load_vs_project)
        self.main_window_ui.xsdFilesRefreshButton.clicked.connect(self.refresh)
        self.main_window_ui.actionSaveProject.triggered.connect(
            self.save_project)

        # 以下代码是原来按钮控制的,现在改为了右键菜单控制
        # self.main_window_ui.xsdFileSubmitJobButton.clicked.connect(self.submit_job)
        # self.main_window_ui.xsdFileLocalLinkByName.clicked.connect(self.submit_file_link_by_name)
        # self.main_window_ui.xsdFileLocalLinkByPath.clicked.connect(self.submit_file_link_by_path)
        # self.main_window_ui.xsdFileEnergyExtract.clicked.connect(self.submit_energy_collect)
        # self.main_window_ui.about.triggered.connect(self.about_window.show)
        # self.main_window_ui.xsdFileRMSExtract.clicked.connect(self.submit_RMS_extract)
        # self.main_window_ui.xsdFileFreq.clicked.connect(self.submit_freq_extract)
        # self.main_window_ui.xsdFileGrepCommand.clicked.connect(self.submit_grep_command)
        # self.main_window_ui.xsdFileUpdate.clicked.connect(self.submit_update)
        # self.main_window_ui.taskCancel.clicked.connect(self.run_task_cancel_command)
        # self.main_window_ui.xsdFileDelete.clicked.connect(self.xsd_file_delete)
        #self.main_window_ui.xsdFileTreeWidget.doubleClicked.connect(self.open_xsd_file)
        #self.main_window_ui.xsdFileTreeWidget.doubleClicked.connect(self.show_3d_coordinate)
        # self.main_window_ui.xsdFileExportFinalStructure.clicked.connect(self.submit_structure_export)
        # self.main_window_ui.xsdFileMark.clicked.connect(self.submit_mark)
        # self.main_window_ui.xsdFileButtonChangeToNotSubmit.clicked.connect(self.change_to_not_submit)
        # self.main_window_ui.runQstatButton.clicked.connect(self.run_qstat_command)
        # 选中的item显示所有信息

        self.main_window_ui.xsdFileRunQstat.clicked.connect(
            self.run_qstat_command)
        self.main_window_ui.xsdFileTreeWidget.itemSelectionChanged.connect(
            self.update_item_information)
        self.add_right_memu_to_xsdFileTreeWidget()

    def show_xsdFile_right_menu(self):
        self.xsdFileRightMenu.exec_(QCursor.pos())

    def add_right_memu_to_xsdFileTreeWidget(self):
        self.xsdFileRightMenu = QMenu(self.main_window_ui.xsdFileTreeWidget)
        self.a_view_action = self.xsdFileRightMenu.addAction("View Molecule")
        self.a_view_action.triggered.connect(self.submit_view_molecule)
        self.xsdFileRightMenu.addSeparator()
        # mark
        self.a_mark_action = self.xsdFileRightMenu.addAction("Mark")
        self.a_mark_action.triggered.connect(self.submit_mark)
        self.xsdFileRightMenu.addSeparator()
        # submit job
        self.a_submit_job = self.xsdFileRightMenu.addAction("Submit Job")
        self.a_submit_job.triggered.connect(self.submit_job)
        self.xsdFileRightMenu.addSeparator()
        # status control
        self.m_status_control = self.xsdFileRightMenu.addMenu("Status")
        self.a_turn_not_submit = self.m_status_control.addAction(
            "Turn Not Submit")
        self.a_turn_not_submit.triggered.connect(self.change_to_not_submit)
        # task control
        # TODO:增加stop 命令
        pass

        # download TODO:
        pass
        # local link
        self.m_local_link = self.xsdFileRightMenu.addMenu("Local Link")
        self.a_by_path = self.m_local_link.addAction("By Path")
        self.a_by_name = self.m_local_link.addAction("By Name")
        self.a_by_name.triggered.connect(self.submit_file_link_by_name)
        self.a_by_path.triggered.connect(self.submit_file_link_by_path)
        # information
        self.m_information = self.xsdFileRightMenu.addMenu("Information")
        self.a_rms = self.m_information.addAction("Final RMS")
        self.a_energy = self.m_information.addAction("Final Energy")
        self.a_freq = self.m_information.addAction("Frequency")
        self.a_grep = self.m_information.addAction("grep ... OUTCAR")
        self.m_information.addSeparator()
        self.a_info_export_csv = self.m_information.addAction(
            "Export information to csv")
        self.a_info_export_csv.triggered.connect(
            self.submit_information_export)
        self.a_info_export_freq_to_dict = self.m_information.addAction(
            "Export Freq to dict file for catmap")
        self.a_info_export_freq_to_dict.triggered.connect(
            self.submit_export_freq_to_dict)
        self.a_rms.triggered.connect(self.submit_RMS_extract)
        self.a_energy.triggered.connect(self.submit_energy_collect)
        self.a_freq.triggered.connect(self.submit_freq_extract)
        self.a_grep.triggered.connect(self.submit_grep_command)
        # structure
        self.m_structure = self.xsdFileRightMenu.addMenu("Structure")
        self.a_structure_export = self.m_structure.addAction(
            "Export Final Structure")
        self.a_structure_export.triggered.connect(self.submit_structure_export)

        # file control
        self.m_file_control = self.xsdFileRightMenu.addMenu("File")
        self.a_outcar_export = self.m_file_control.addAction(
            "Export OUTCAR in dir")
        self.a_outcar_export.triggered.connect(self.submit_outcar_export)
        self.a_delete_file = self.m_file_control.addAction("Delete")
        self.a_delete_file.triggered.connect(self.xsd_file_delete)

        self.main_window_ui.xsdFileTreeWidget.setContextMenuPolicy(
            QtCore.Qt.CustomContextMenu)
        self.main_window_ui.xsdFileTreeWidget.customContextMenuRequested.connect(
            self.show_xsdFile_right_menu)

        self.main_window_ui.xsdFileTreeWidget.doubleClicked.connect(
            self.submit_view_molecule)

    def open_xsd_file(self):
        for i in self.xsd_files_item:
            if i.isSelected():
                print(i.file_path)
                try:
                    os.startfile(self.vsp.local_project_dir + "/" +
                                 i.file_path)
                except:
                    traceback.print_exc()
                break

    def binding_create_project_window(self):

        # 将文件对话框函数绑定并获得相应值到类成员变量中
        #self.create_project_ui.localBaseFilePathButton.\
        #    clicked.connect(self.file_dialog_local_base_file)

        self.create_project_ui.localProjectPathButton.clicked.connect(
            self.file_dialog_local_project_path)

        # 直接将ok按钮绑定创建project,如果不合理会弹出警告,合理会创建然后close
        self.create_project_ui.projectOkButton.clicked.connect(
            self.create_vs_project)
        pass

    def load_vs_project(self):

        vsp_file = self.file_dialog_vsp_file()[0]
        if len(vsp_file) == 0: return
        # 先尝试默认密码123
        vs = self.check(self.main_window,
                        VASPStuProject.read_existing_project(vsp_file, "123"))
        if vs == Status.FAILED:
            key = \
            QInputDialog(self.main_window).getText(self.main_window, "Please insert the project key", "Project Key")[0]
            vs = self.check(
                self.main_window,
                VASPStuProject.read_existing_project(vsp_file, key))
            self.vsp = vs

        elif isinstance(vs, VASPStuProject):
            self.vsp = vs
            #QMessageBox.information(self.main_window, "Information", "Success.")
        self.after_open_or_load()

    def create_vs_project(self):
        try:

            self.local_project_path = self.create_project_ui.localProjectPathEdit.text(
            )
        except:
            QMessageBox.critical(self.create_project, "Error", "Not complete")
            # return是必要的,否则会直接执行下一个语句,报错会直接关掉程序
            return

        self.vsp = VASPStuProject(local_project_dir=self.local_project_path,
                                  project_key="123")

        with open(self.vsp.local_project_dir + "/" + "temp", "w") as f:
            f.write("")

        # 是否覆盖已有文件,如果是就写入,否则不关闭窗口
        if self.save_project() == False: return

        # 进行检查,否则要求重新输入
        #if self.project_check() == False: return

        self.create_project.close()

        self.after_open_or_load()
        #QMessageBox.information(self.main_window,"提示","项目建立完成,请重新打开并载入\ndue to thread lock error of deepcopy(vs_dc)")
        #app.quit()
    def after_open_or_load(self):
        # 旧版本vsp文件更新
        self.vsp.update_old_version()
        self.update_molecule_view_plot_settings()
        self.check_file_change_and_update_file()
        self.update_project_information()
        self.update_xsd_files_information()
        self.generate_item_window()

    def update_molecule_view_plot_settings(self):
        try:
            self.main_window_ui.moleculeViewSettingsText.setPlainText(
                self.vsp.molecule_view_setting_text)
        except:
            self.main_window_ui.moleculeViewSettingsText.setPlainText(
                default_molecule_view_setting_text)
            traceback.print_exc()

    def save_project(self):

        if self.vsp == None: return

        if self.vsp.class_data_save_path == None:
            path = QFileDialog.getSaveFileName(
                self.main_window, "Save .vsp file", "C:/",
                "VASP Studio Project File (*.vsp)")[0]
            if path == "":
                return
            self.vsp.class_data_save_path = path
        else:
            path = self.vsp.class_data_save_path

        tw = self.main_window_ui.xsdFileTreeWidget
        n = []
        # 存储标题栏的宽度
        for i in range(tw.columnCount()):
            n.append(tw.columnWidth(i))
        expand_state_dict = {}
        # 存储是否expanded,注意需要使用node去获取信息
        # 新创建的文件会有问题,尝试这一个
        try:
            for key in self.tree_node_widget_item_info:
                expand_state_dict[key] = self.tree_node_widget_item_info[
                    key].isExpanded()
        except:
            pass
        self.vsp.xsd_tree_widget_param["expanded_status"] = expand_state_dict
        self.vsp.xsd_tree_widget_param["column_status"] = n
        self.vsp.molecule_view_setting_text = self.main_window_ui.moleculeViewSettingsText.toPlainText(
        )
        # 如果保存了一次,变为new save,因为新保存的密码固定了
        self.vsp.new_save = True
        self.vsp.save_project_info(path)
        QMessageBox.information(self.main_window, "提示", "已保存")
        return True

    def check_file_change_and_update_file(self):
        new_files, deleted_files = self.vsp.check_and_add_new_xsd_files_and_generate_XVI(
        )
        if new_files == False:  # 这个是由底层传来的,不只是文件,还有status以及错误信息
            # 这里newFiles是错误时传来的status,deletedFiles是信息
            QMessageBox.critical(self.main_window, "Error", deleted_files)
            return

        # 之所以需要self.vs.new_files,是因为希望在一开始创建项目时就保存项目
        if len(new_files) == 0 and len(deleted_files) == 0: return
        string = ""
        if len(new_files) >= 1:
            string += "检测到新增%s个以下文件:" % len(new_files)
            string += "\n".join(new_files)
        if len(deleted_files) >= 1:
            string += "检测到删除%s个以下文件" % len(deleted_files)
            string += "\n".join(deleted_files)
        QMessageBox.information(self.create_project, "File changes", string)

    def project_check(self):
        if self.check(self.create_project,
                      self.vsp.submit_job_file_check) == False:
            return False
        if self.check(self.create_project, self.vsp.base_file_check) == False:
            return False
        if self.check(self.create_project,
                      self.vsp.connect_remote_project_dir_check) == False:
            return False
        QMessageBox.information(
            self.create_project, "Information",
            "Success. 为确保顺利运行,请检查:\n服务器端是否有新创建的checkFile文件")

    def update_item_information(self):
        # 选中后增加具体的信息
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                xvis = self.vsp.get_XVI_from_relative_xsd_files(
                    [str(i.file_path)])[0]
                content = ""
                for key in xvis.__dict__:
                    if key in [
                            "local_vasp_dir", "energy", "final_RMS",
                            "relative_xsd_file", "status", "nodel", "note",
                            "type", "mark_text", "RMS_array", "submit_job",
                            "real_freq", "virtual_freq", "match_state"
                    ]:

                        content += "<b><font size=4 >%s</font><br></b>" % key
                        content += "<font size=4>%s</font><br>" % getattr(
                            xvis, key, "")

                        self.main_window_ui.xsdFileInformation.setText(content)

    def update_xsd_files_information(self):
        '''
        update 函数直接与vs类关联,得到所有的文件类,这样只用update就能更新最新
        :return:
        '''

        if self.vsp == None:
            QMessageBox.information(self.main_window, "提示", "没有加载项目,无法获得xsd文件")
            return
        self.xsd_files_item = []

        try:
            filenames = list(self.vsp.relative_path_XVI_dict.keys())
            filenames = sorted(filenames)
            tw = self.main_window_ui.xsdFileTreeWidget

            index = 0
            tw.clear()
            # TODO :这里让用户自选显示的顺序和内容
            tw.setHeaderLabels(self.xsd_file_headers)
            tw.setColumnCount(len(self.xsd_file_headers))
            file_name_item_dict = {}
            root = QTreeWidgetItem(tw)
            root.setText(0, self.vsp.local_project_dir)

            for file in filenames:

                l = [root]
                trees = file.split("/")[1:]  # 第一个元素是“”,是root

                # 按照文件目录树进行,如果能够获取到子集就开始增加内容,否则增加child
                for i in range(len(trees) + 1):  # 对于剩下的child
                    node_name = "/".join(trees[:i])
                    try:  # 尝试找到这个node,如果没有就创建
                        node = file_name_item_dict[node_name]

                    except:
                        # 创建child node,加入到字典中
                        node = QTreeWidgetItem(l[-1])
                        index += 1
                        node.setText(0, node_name.split("/")[-1])
                        file_name_item_dict[node_name] = node
                        try:
                            # 这里面的每个显示都是直接从xvi信息中获取,然后设置
                            # 这里如果node能够获取到文件,就开始加上文件信息,否则就是空的只作为有内容的node的parent
                            xvi_item = self.vsp.relative_path_XVI_dict[
                                "/" + node_name]
                            for i in range(len(self.xsd_file_contents)):
                                #if getattr(xvi_item,"type","") == "":xvi_item.type = Type.Origin
                                node.setText(
                                    i + 1,
                                    getattr(xvi_item,
                                            self.xsd_file_contents[i], "None"))
                                if self.xsd_file_contents[i] == "status":
                                    # 这些set使用index进行的,所以先判断是不是在相应的列,也可改成名称为key,value为index
                                    node.setBackground(
                                        i + 1,
                                        QBrush(STATUS_COLOR[xvi_item.status]))
                                if self.xsd_file_contents[i] == "type":
                                    try:
                                        node.setBackground(
                                            i + 1,
                                            QBrush(Type_Color[xvi_item.type]))
                                    except:  # 这个是应对之前没有这个attr的项目,之后可以删除此
                                        node.setBackground(
                                            i + 1,
                                            QBrush(Type_Color[Type.Origin]))
                                if self.xsd_file_contents[i] == "mark_text":
                                    node.setText(
                                        i + 1,
                                        getattr(xvi_item,
                                                self.xsd_file_contents[i],
                                                "None"))
                                    try:
                                        node.setBackground(
                                            i + 1,
                                            QBrush(QColor(
                                                xvi_item.mark_color)))

                                    except:
                                        traceback.print_exc()
                                        node.setBackground(
                                            i + 1,
                                            QBrush(QColor(255, 255, 255)))

                            node.file_path = "/" + node_name  # 这里强行给这个实例增加了属性,之后直接调用,这里相当于继承
                            self.xsd_files_item.append(node)
                        except KeyError:
                            pass
                        except:
                            traceback.print_exc()
                    l.append(node)

                    l[-2].addChild(l[-1])

            def load_column_status():
                # 这里设置列的长度,首先用一个变量去存储这些列,存储在save project中
                try:
                    info = self.vsp.xsd_tree_widget_param["column_status"]
                    for i in range(tw.columnCount()):
                        tw.setColumnWidth(i, info[i])
                except:
                    return

            load_column_status()
            # 把node的信息存储一下!用于接下来搞node的扩展
            self.tree_node_widget_item_info = file_name_item_dict

            # 这里我们用一个与tree node widget 。。key一模一样的字典去存储node的expand信息,之后
            # 用这个key去获取node以及node的expand信息然后修改!
            def expand():  # 存储各个列的expand情况
                try:
                    info = self.vsp.xsd_tree_widget_param["expanded_status"]
                    tw.expandAll()
                    for key in self.tree_node_widget_item_info.keys():
                        try:
                            self.tree_node_widget_item_info[key].setExpanded(
                                info[key])
                        except:
                            continue
                except:
                    pass

            expand()
            print(tw.rootIndex())
            return

        except:
            QMessageBox.information(self.main_window, "提示", "No xsd files.")
            traceback.print_exc()
            return

    def update_project_information(self):

        try:

            tw = self.main_window_ui.tableWidgetProjectInformation
            tw.setHorizontalHeaderLabels(["Property", "Value"])
            info_x = [
                "Local Project Path",
            ]
            info_y = [
                self.vsp.local_project_dir,
            ]
            tw.setRowCount(len(info_x))
            tw.setColumnCount(2)
            for i in range(len(info_x)):
                newItem = QTableWidgetItem(info_x[i])
                tw.setItem(i, 0, newItem)
                newItem = QTableWidgetItem(info_y[i])
                tw.setItem(i, 1, newItem)
        except:
            QMessageBox.information(
                self.main_window, "提示",
                "No project information, please create New or Open one.")

    def check(self, window, checker):
        # 专门用于相应check函数的
        # 如果为False,需要有error msg
        # 如果单为True,pass
        # 如果为True且有附加信息,返回它们
        assert isinstance(checker, Checker)
        if checker.window_status == Status.INFO:
            QMessageBox.information(window, "Information",
                                    checker.window_string)
        elif checker.window_status == Status.WARN:
            QMessageBox.warning(window, "Warning", checker.window_string)
        elif checker.window_status == Status.ERROR:
            QMessageBox.critical(window, "Error", checker.window_string)
        if checker.status == Status.FAILED:
            return Status.FAILED
        elif checker.status == Status.PASS:
            return checker.output_

    def file_dialog_vsp_file(self):
        return QFileDialog.getOpenFileName(self.main_window, "Open .vsp file",
                                           "C:/",
                                           "VASP Studio Project File (*.vsp)")

    def file_dialog_local_project_path(self):
        self.local_project_path = QFileDialog.getExistingDirectory()
        self.create_project_ui.localProjectPathEdit.setText(
            self.local_project_path)

    # TODO :下面是根据选定的item进行提交job的操作,代码有很大冗余,可以精简
    #  思路:按钮联系,提供文件,然后更新VASP Item的属性

    def submit_update(self):
        raise NotImplementedError
        self.run_command(["qstat -a"])
        if self.command_output is None or len(self.command_output) == 0:
            return

    def submit_grep_command(self):
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
                xvis = self.vsp.get_XVI_from_relative_xsd_files(
                    self.selected_items)[0]
                a = getattr(xvis, "local_vasp_dir", "")
                if a == "" or a == None: return
                a += "/OUTCAR"
                a = a.replace("/", "\\", 99)
                command = str(
                    QInputDialog.getText(self.main_window, "输入grep内容",
                                         "grep")[0])

                if command == "": return
                try:
                    command_ = "findstr %s %s" % (command, a)
                    print(command_)
                    return_ = os.popen(command_).read()
                    print(return_)
                    self.main_window_ui.commandOutput.setText(str(return_))
                    return
                except:
                    traceback.print_exc()

    def run_task_cancel_command(self):
        # TODO: 完成cancel需要重新多线程,建议将多线程写为一个类
        pass
        # if self.xsd_files_item == []: return
        # self.selected_items = []
        # for i in self.xsd_files_item:
        #     if i.isSelected():
        #         self.selected_items.append(str(i.file_path))
        # xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)
        # try:
        #     command = []
        #     for i in xvis:
        #         if i.status != XVI_Status.Submitted:
        #             QMessageBox.information(self.main_window,"提示","包含没有运行的任务,请重新提交")
        #             return
        #         if i.nodel is not None and len(i.nodel) > 0:
        #             command.append("qdel %s" % i.nodel)
        #     self.run_command(command)
        #     for i in xvis:
        #         i.status = XVI_Status.Canceled
        #         i.nodel = ""
        #     self.update_xsd_files_information()
        # except:
        #     traceback.print_exc()

    def run_qstat_command(self):
        self.run_command(["qstat -a"])

    def run_command(self, command_list):
        try:
            if len(self.vsp.job_submit_items) == 0:
                QMessageBox.information(self.main_window, "提示",
                                        "没有可用的Job Submit配置")
                return

            def ok():
                job_submit = ui.chooseJobSubmit.currentText()
                if job_submit == "":
                    QMessageBox.information(a, "提示", "选择一个Job Submit配置")
                    return
                else:
                    job_submit = self.vsp.job_submit_items[job_submit]
                    a.close()

                    def command_run(*args):

                        sf = SFTP_SSH_Utils(host=job_submit.host,
                                            port=job_submit.port,
                                            username=job_submit.username,
                                            password=job_submit.password)
                        a, b, c = sf.ssh_run_command(args)
                        print(a)
                        print(b)
                        print(c)
                        return a, b, c, 1

                    class SJT(QThread):
                        def __init__(self, job_submit_confg, *args):
                            super().__init__()
                            self.status = None
                            self.jc = job_submit_confg
                            self.args = args

                        def run(self):
                            print(self.args)
                            self.output = command_run(self.args[0])
                            self.status = self.output[3]
                            self.output = self.output[:3]

                        def get_output(self):
                            return self.output

                    try:
                        thread = SJT(job_submit, command_list)
                        thread.start()

                        while True:

                            q = thread.status
                            if q == None:
                                QCoreApplication.processEvents()
                                continue

                            elif q == 1:
                                try:
                                    string = ""
                                    for i in thread.output[1]:
                                        string += str(i,
                                                      encoding="utf-8") + "\n"
                                    # 提交成功才更新节点信息
                                    self.main_window_ui.commandOutput.setText(
                                        string)
                                    self.command_output = string
                                    return

                                except:
                                    traceback.print_exc()

                                    QMessageBox.information(
                                        self.main_window, '提示', "command运行失败")
                                    return

                    except:
                        traceback.print_exc()

            a = QDialog()
            ui = Ui_submitJob()
            ui.setupUi(a)
            for i in self.vsp.job_submit_items:
                ui.chooseJobSubmit.addItem(i)
            a.show()
            ui.submitJobOK.clicked.connect(ok)

        except:
            traceback.print_exc()

    # TODO: 编写逻辑架构,对于所有选中item,设定allow status和allow type,设定之后进行的参数,设定是否多线程等等

    def submit_freq_extract(self):
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)
        freq = QInputDialog(self.main_window)
        freq = freq.getText(self.main_window, "输入虚频允许的数目", "虚频数目")[0]
        VASPFreqExtract().freq_extract(xvis, int(freq))
        self.update_xsd_files_information()

    def submit_view_molecule(self):
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                # 只显示选中的第一个的结构
                self.selected_items.append(str(i.file_path))

                xvis = self.vsp.get_XVI_from_relative_xsd_files(
                    self.selected_items)[0]

                # 不管多不多线程,现在的问题是,显示之后会退出
                #s = threading.Thread(target=thread_submit_plot)
                #s.start()
                #thread_submit_plot()
                try:
                    #s = threading.Thread(target=thread_submit_plot)
                    #s.start()

                    submit_plot(xvis,
                                config_string=self.main_window_ui.
                                moleculeViewSettingsText.toPlainText())
                except:
                    traceback.print_exc()
                return

    def submit_mark(self):
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)
        # TODO: 一定要记住,Dialog返回的是一个元组!!!
        col = QColorDialog.getColor(parent=self.main_window,
                                    title="选择一个mark颜色")

        text = QInputDialog.getText(self.main_window, "输入mark内容", "标注")[0]
        if text == "": return
        print(123123)
        print(col.value())

        print(text)

        for i in xvis:
            i.mark_text = text
            i.mark_color = (col.rgb())
        self.update_xsd_files_information()

    def xsd_file_delete(self):
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)

        reply = QMessageBox.information(
            self.main_window, "提示",
            "将删除(移动到项目文件下trash文件夹)以下文件,请确认\n" + "\n".join(self.selected_items),
            QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.No: return
        for i in xvis:
            try:
                if not os.path.exists(self.vsp.local_project_dir + "/" +
                                      "trash"):
                    os.mkdir(self.vsp.local_project_dir + "/" + "trash")
                shutil.move(
                    i.local_xsd_path, self.vsp.local_project_dir + "/" +
                    "trash" + "/" + i.local_xsd_path.split("/")[-1])
            except:
                traceback.print_exc()
        self.check_file_change_and_update_file()
        self.update_xsd_files_information()

    def change_to_not_submit(self):
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)

        for i in xvis:
            assert isinstance(i, XSD_VASP_item)
            i.status = XVI_Status.NotSubmitted
            i.type = Type.Origin
            i.energy = ""
            i.final_RMS = ""
            i.nodel = ""
            i.local_vasp_dir = ""
        self.update_xsd_files_information()

    def submit_outcar_export(self):
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)
        directory = QFileDialog.getExistingDirectory(
            self.main_window, "Choose a dir to store OUTCAR")
        fail = []
        for i in xvis:
            try:
                _from = i.local_vasp_dir + "/OUTCAR"
                _to = directory + "/" + i.item_key.split("\\")[-1].split(
                    "/")[-1] + "_OUTCAR"
                print("Copy from %s to %s" % (_from, _to))
                shutil.copyfile(_from, _to)
            except:
                traceback.print_exc()
                fail.append(i.item_key)
        if len(fail) > 0:
            string = "Failed to export following OUTCAR: \n"
            string += "\n".join(fail)
            self.main_window_info(string)

    def submit_export_freq_to_dict(self):
        # 这个是专用于catmap对接的,之后根据catmap对接情况修改

        string = "frequency_dict={"
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)
        for xvi in xvis:
            string += '"' + str(xvi.relative_xsd_file_name) + '"' + ":" + "["
            for i in xvi.real_freq:
                string += str(i) + ","
            string += "]"
            string += ",\n"
        string += "}"

        path = QFileDialog.getSaveFileName(self.main_window, "Export", "C:/",
                                           "*.txt")[0]

        with open(path, "w") as f:
            f.write(string)

    # 不建议导出csv然后excel打开,因为软件本身定位就是取代这个功能
    def submit_information_export(self):
        string = ""
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)
        path = QFileDialog.getSaveFileName(self.main_window,
                                           "Export to csv file", "C:/",
                                           "*.csv")[0]

        # 导出的信息
        attr_list = [
            "relative_xsd_file_name", "energy", "final_RMS", "real_freq",
            "virtual_freq"
        ]
        string = ",".join(attr_list) + "\n"
        for xvi in xvis:

            for attr in attr_list:
                string += str(getattr(xvi, attr, "")).replace(
                    ",", " ", 999) + ","  # 避免数据的空格导致划分错误
            string += "\n"

        with open(path, "w") as f:
            f.write(string)

    def submit_structure_export(self):
        # TODO: 没有收敛状态的,不允许导出
        QMessageBox.information(
            self.main_window, "提示",
            "提取时间可能较长,程序可能未响应。匹配完成后请检查Match State,全部匹配才有效")  # TODO:使用多线程解决这个问题
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)
        VASPStuStructureManager().final_outani_structure_export(xvi_items=xvis)
        self.check_file_change_and_update_file()
        self.update_xsd_files_information()

    def submit_RMS_extract(self):

        thushold = QInputDialog(self.main_window)
        thushold = thushold.getText(self.main_window, "输入RMS收敛阈值",
                                    "RMS收敛阈值")[0]
        try:
            thushold = float(thushold)
        except:
            self.main_window_info("Invalid Input")
            return
        #QMessageBox.information(self.main_window,"提示","提取时间可能较长,程序可能未响应。") # TODO:使用多线程解决这个问题,
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)

        VASP_RMS_Extract().final_RMS_extract(xvis, thushold)
        self.update_xsd_files_information()
        self.save_project()

    def submit_energy_collect(self):

        #QMessageBox.information(self.main_window,"提示","提取时间可能较长,程序可能未响应。")

        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)
        VASPEnergyExtract().energy_extract(xvis)
        self.update_xsd_files_information()
        self.save_project()

    def submit_file_link_by_path(self):
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)

        directory = QFileDialog.getExistingDirectory(self.main_window,
                                                     "选择一个与当前项目嵌套结构相同的顶端文件夹")
        try:
            VASPStuFileLinker().link_file_and_vsp_dir_by_path(xvis, directory)
        except:
            traceback.print_exc()
        self.update_xsd_files_information()
        self.save_project()

    def submit_file_link_by_name(self):
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))
        xvis = self.vsp.get_XVI_from_relative_xsd_files(self.selected_items)

        directory = QFileDialog.getExistingDirectory(
            self.main_window, "选择一个可能与当前所选xsd文件名相同的VASP文件夹")
        try:
            VASPStuFileLinker().link_file_and_vsp_dir_by_name(xvis, directory)
        except:
            traceback.print_exc()
        self.update_xsd_files_information()
        self.save_project()

    def submit_job(self):
        if self.xsd_files_item == []: return
        self.selected_items = []
        for i in self.xsd_files_item:
            if i.isSelected():
                self.selected_items.append(str(i.file_path))

        self.tmp_xvis = xvis = self.vsp.get_XVI_from_relative_xsd_files(
            self.selected_items)
        new_items = []

        for i in xvis:
            if i.status != XVI_Status.NotSubmitted:
                reply = QMessageBox.information(
                    self.main_window,  # 使用information信息框
                    "提示",
                    "包含已经提交的项目,确定再次提交?",
                    QMessageBox.Yes | QMessageBox.No)
                if reply == QMessageBox.Yes:
                    new_items = self.selected_items
                    break
                else:
                    return
            elif i.status == XVI_Status.NotSubmitted:
                new_items.append(i.relative_xsd_file_name)

        self.selected_items = new_items

        reply = QMessageBox.information(
            self.main_window, "提示",
            "将提交以下项目,请确认\n" + "\n".join(self.selected_items),
            QMessageBox.Yes | QMessageBox.No)
        if reply == QMessageBox.No: return

        if len(self.vsp.job_submit_items) == 0:
            QMessageBox.information(self.main_window, "提示",
                                    "没有可用的Job Submit配置")
            return

        def ok():
            job_submit = ui.chooseJobSubmit.currentText()
            if job_submit == "":
                QMessageBox.information(a, "提示", "选择一个Job Submit配置")
                return
            else:
                job_submit = self.vsp.job_submit_items[job_submit]
                self.tmp_note = \
                    QInputDialog(self.main_window).getText(self.main_window, "给这批任务添加一个备注", "任务备注")[0]

                self.submit_job_run(job_submit)
                a.close()

        a = QDialog()
        ui = Ui_submitJob()
        ui.setupUi(a)
        for i in self.vsp.job_submit_items:
            ui.chooseJobSubmit.addItem(i)
        a.show()
        ui.submitJobOK.clicked.connect(ok)

    def submit_job_run(self, job_submit_confg):
        # job submit confg 实际上就是Job Submit Item,里面自带投job的方法
        self.job_config = job_submit_confg

        # 多线程: 主线程是UI
        def multiThreadUIMain():
            # 这里采用了一个deepcopy的对象去做另一线程,该线程不涉及任何类的修改操作
            # 这样做很不值得,但是如果在另一线程中使用self.vs会因为线程锁定而无法被pickle存储,即使线程已经关闭
            jc = copy.deepcopy(self.job_config)

            class SJT(QThread):
                def __init__(self, job_submit_confg, *args):
                    super().__init__()
                    self.status = None
                    self.jc = job_submit_confg
                    self.args = args

                def run(self):
                    self.status = self.jc.submit_job(self.args)

            thread = SJT(jc, self.selected_items)
            thread.start()
            #QMessageBox.information(self.main_window, "提示", "任务提交中......")
            error_msg = "任务提交失败,请检查\n1:服务器路径设置是否正确\n2:各文件路径是否有效\n3:Key Library是否按照说明书要求设置"
            while True:
                q = thread.status
                if q == None:
                    QCoreApplication.processEvents()
                    continue
                elif q == 1:
                    try:
                        # 提交成功才更新节点信息
                        thread.quit()
                        thread.wait()
                        del thread

                        info = jc.update_XVI_nodel_info()
                        if info == False:
                            QMessageBox.information(self.main_window, "提示",
                                                    "节点信息获取失败")
                        self.vsp.update_xvi_item_info()
                        self.update_xsd_files_information()

                        QMessageBox.information(self.main_window, '提示',
                                                "任务已提交,若获取到任务节点信息即为提交成功")
                        return
                    except:
                        traceback.print_exc()
                        QMessageBox.critical(self.main_window, "错误", error_msg)
                        return

                elif q == 0:
                    QMessageBox.critical(self.main_window, "错误", error_msg)
                    return

        multiThreadUIMain()

        for i in self.tmp_xvis:
            i.note = self.tmp_note

        self.save_project()
コード例 #14
0
class SystemTray:
    tray_icon: QSystemTrayIcon
    tray_menu: QMenu

    def set_use_systemtray(self):
        use_systemtray = not self.switcher.settings.get("use_systemtray")
        self.switcher.settings["use_systemtray"] = use_systemtray
        self.switcher.settings_write()
        if use_systemtray:
            self.tray_icon.show()
        else:
            self.tray_icon.hide()

    def systemtray(self, parent=None):
        self.tray_menu = QMenu(parent)
        self.tray_icon = QSystemTrayIcon(QIcon("logo.png"))
        self.tray_icon.setToolTip(
            _("Program to quickly switch between steam accounts"))

        login_menu = QMenu(_("Login with"))
        self.tray_menu.addMenu(login_menu)

        def populate_login_menu():
            login_menu.clear()
            menu_accounts = []
            accounts, avatars = self.load_accounts(no_populate=True)
            if not accounts:
                login_menu.setEnabled(False)
            else:
                login_menu.setEnabled(True)
                for login_name, user in accounts:
                    menu_accounts.append(
                        QAction(user.get("steam_name", login_name),
                                self,
                                data=str(login_name)))
                    menu_accounts[-1].setToolTip(
                        "Login with {0}".format(login_name))
                    if self.switcher.settings["show_avatars"]:
                        menu_accounts[-1].setIcon(
                            QIcon(
                                avatars.get(login_name,
                                            self.switcher.default_avatar)))
                    menu_accounts[-1].triggered.connect(
                        lambda: self.steam_login(str(menu_accounts[-1].data()),
                                                 True))
                login_menu.addActions(menu_accounts)

        def activated(reason):
            self.tray_menu.addMenu(TopBar.settings_menu)
            self.tray_menu.addSeparator()
            self.tray_menu.addAction(_("Exit"), self.exit_app)
            self.tray_icon.setContextMenu(self.tray_menu)
            if reason == QSystemTrayIcon.Trigger:
                if self.isVisible():
                    self.hide()
                else:
                    self.show()
            else:
                populate_login_menu()

        self.tray_icon.activated.connect(activated)
コード例 #15
0
class EntityQGraphicsView(CustomQGraphicsView):
    """QGraphicsView for the Entity Graph View."""

    graph_selection_changed = Signal(object)

    def __init__(self, parent):
        """

        Args:
            parent (QWidget): Graph View Form's (QMainWindow) central widget (self.centralwidget)
        """
        super().__init__(
            parent=parent)  # Parent is passed to QWidget's constructor
        self._spine_db_editor = None
        self._menu = QMenu(self)
        self.pos_x_parameter = "x"
        self.pos_y_parameter = "y"
        self.selected_items = list()
        self.removed_items = list()
        self.hidden_items = list()
        self.prunned_entity_ids = dict()
        self.heat_map_items = list()
        self._point_value_tuples_per_parameter_name = dict(
        )  # Used in the heat map menu
        self._hovered_obj_item = None
        self.relationship_class = None
        self.cross_hairs_items = []
        self.auto_expand_objects = None
        self._auto_expand_objects_action = None
        self._add_objects_action = None
        self._save_pos_action = None
        self._clear_pos_action = None
        self._hide_action = None
        self._show_hidden_action = None
        self._prune_entities_action = None
        self._prune_classes_action = None
        self._restore_all_pruned_action = None
        self._rebuild_action = None
        self._export_as_pdf_action = None
        self._zoom_action = None
        self._rotate_action = None
        self._arc_length_action = None
        self._restore_pruned_menu = None
        self._parameter_heat_map_menu = None
        self._previous_mouse_pos = None
        self._context_menu_pos = None

    @property
    def entity_items(self):
        return [
            x for x in self.scene().items()
            if isinstance(x, EntityItem) and x not in self.removed_items
        ]

    def setScene(self, scene):
        super().setScene(scene)
        scene.selectionChanged.connect(self._handle_scene_selection_changed)

    @Slot()
    def _handle_scene_selection_changed(self):
        """Filters parameters by selected objects in the graph."""
        if self.scene() is None:
            return
        selected_items = self.scene().selectedItems()
        selected_objs = [
            x for x in selected_items if isinstance(x, ObjectItem)
        ]
        selected_rels = [
            x for x in selected_items if isinstance(x, RelationshipItem)
        ]
        self.selected_items = selected_objs + selected_rels
        self.graph_selection_changed.emit({
            "object": selected_objs,
            "relationship": selected_rels
        })

    def connect_spine_db_editor(self, spine_db_editor):
        self._spine_db_editor = spine_db_editor
        self.populate_context_menu()

    def populate_context_menu(self):
        self._auto_expand_objects_action = self._menu.addAction(
            "Auto-expand objects")
        self._auto_expand_objects_action.setCheckable(True)
        self.auto_expand_objects = (self._spine_db_editor.qsettings.value(
            "appSettings/autoExpandObjects", defaultValue="false") == "true")
        self._auto_expand_objects_action.setChecked(self.auto_expand_objects)
        self._auto_expand_objects_action.toggled.connect(
            self.set_auto_expand_objects)
        self._menu.addSeparator()
        self._add_objects_action = self._menu.addAction(
            "Add objects", self.add_objects_at_position)
        self._menu.addSeparator()
        self._save_pos_action = self._menu.addAction("Save positions",
                                                     self.save_positions)
        self._clear_pos_action = self._menu.addAction(
            "Clear saved positions", self.clear_saved_positions)
        self._menu.addSeparator()
        self._hide_action = self._menu.addAction("Hide",
                                                 self.hide_selected_items)
        self._show_hidden_action = self._menu.addAction(
            "Show hidden", self.show_hidden_items)
        self._menu.addSeparator()
        self._prune_entities_action = self._menu.addAction(
            "Prune entities", self.prune_selected_entities)
        self._prune_classes_action = self._menu.addAction(
            "Prune classes", self.prune_selected_classes)
        self._restore_pruned_menu = self._menu.addMenu("Restore")
        self._restore_pruned_menu.triggered.connect(self.restore_pruned_items)
        self._restore_all_pruned_action = self._menu.addAction(
            "Restore all", self.restore_all_pruned_items)
        self._menu.addSeparator()
        # FIXME: The heap map doesn't seem to be working nicely
        # self._parameter_heat_map_menu = self._menu.addMenu("Add heat map")
        # self._parameter_heat_map_menu.triggered.connect(self.add_heat_map)
        self._menu.addSeparator()
        self._rebuild_action = self._menu.addAction(
            "Rebuild", self._spine_db_editor.build_graph)
        self._export_as_pdf_action = self._menu.addAction(
            "Export as PDF", self.export_as_pdf)
        self._menu.addSeparator()
        self._zoom_action = ToolBarWidgetAction("Zoom",
                                                self._menu,
                                                compact=True)
        self._zoom_action.tool_bar.addAction(
            "-", self.zoom_out).setToolTip("Zoom out")
        self._zoom_action.tool_bar.addAction(
            "Reset", self.reset_zoom).setToolTip("Reset zoom")
        self._zoom_action.tool_bar.addAction(
            "+", self.zoom_in).setToolTip("Zoom in")
        self._rotate_action = ToolBarWidgetAction("Rotate",
                                                  self._menu,
                                                  compact=True)
        self._rotate_action.tool_bar.addAction(
            "\u2b6f",
            self.rotate_anticlockwise).setToolTip("Rotate counter-clockwise")
        self._rotate_action.tool_bar.addAction(
            "\u2b6e", self.rotate_clockwise).setToolTip("Rotate clockwise")
        self._arc_length_action = ToolBarWidgetAction("Arc length",
                                                      self._menu,
                                                      compact=True)
        self._arc_length_action.tool_bar.addAction(
            QIcon(CharIconEngine("\uf422")), "",
            self.decrease_arc_length).setToolTip("Decrease arc length")
        self._arc_length_action.tool_bar.addAction(
            QIcon(CharIconEngine("\uf424")), "",
            self.increase_arc_length).setToolTip("Increase arc length")
        self._menu.addSeparator()
        self._menu.addAction(self._zoom_action)
        self._menu.addAction(self._arc_length_action)
        self._menu.addAction(self._rotate_action)
        self._menu.aboutToShow.connect(self._update_actions_visibility)

    def increase_arc_length(self):
        for item in self.entity_items:
            item.setPos(1.1 * item.pos())
            item.update_arcs_line()

    def decrease_arc_length(self):
        for item in self.entity_items:
            item.setPos(item.pos() / 1.1)
            item.update_arcs_line()

    @Slot()
    def _update_actions_visibility(self):
        """Enables or disables actions according to current selection in the graph."""
        self._save_pos_action.setEnabled(bool(self.selected_items))
        self._clear_pos_action.setEnabled(bool(self.selected_items))
        self._hide_action.setEnabled(bool(self.selected_items))
        self._show_hidden_action.setEnabled(bool(self.hidden_items))
        self._prune_entities_action.setEnabled(bool(self.selected_items))
        self._prune_classes_action.setEnabled(bool(self.selected_items))
        self._restore_pruned_menu.setEnabled(
            any(self.prunned_entity_ids.values()))
        self._restore_all_pruned_action.setEnabled(
            any(self.prunned_entity_ids.values()))
        self._prune_entities_action.setText(
            f"Prune {self._get_selected_entity_names()}")
        self._prune_classes_action.setText(
            f"Prune {self._get_selected_class_names()}")
        has_graph = bool(self.items())
        self._rebuild_action.setEnabled(has_graph)
        self._zoom_action.setEnabled(has_graph)
        self._rotate_action.setEnabled(has_graph)
        self._export_as_pdf_action.setEnabled(has_graph)
        # FIXME: The heap map doesn't seem to be working nicely
        # self._parameter_heat_map_menu.setEnabled(has_graph)
        # if has_graph:
        #    self._populate_add_heat_map_menu()

    def make_items_menu(self):
        menu = QMenu(self)
        menu.addAction(self._save_pos_action)
        menu.addAction(self._clear_pos_action)
        menu.addSeparator()
        menu.addAction(self._hide_action)
        menu.addAction(self._prune_entities_action)
        menu.addAction(self._prune_classes_action)
        menu.addSeparator()
        menu.addAction("Edit", self.edit_selected)
        menu.addAction("Remove", self.remove_selected)
        menu.aboutToShow.connect(self._update_actions_visibility)
        return menu

    @Slot(bool)
    def set_auto_expand_objects(self, checked=False):
        self.auto_expand_objects = checked
        self._auto_expand_objects_action.setChecked(checked)
        self._spine_db_editor.build_graph()

    @Slot(bool)
    def add_objects_at_position(self, checked=False):
        self._spine_db_editor.add_objects_at_position(self._context_menu_pos)

    @Slot(bool)
    def edit_selected(self, _=False):
        """Edits selected items."""
        obj_items = [
            item for item in self.selected_items
            if isinstance(item, ObjectItem)
        ]
        rel_items = [
            item for item in self.selected_items
            if isinstance(item, RelationshipItem)
        ]
        self._spine_db_editor.show_edit_objects_form(obj_items)
        self._spine_db_editor.show_edit_relationships_form(rel_items)

    @Slot(bool)
    def remove_selected(self, _=False):
        """Removes selected items."""
        if not self.selected_items:
            return
        db_map_typed_data = {}
        for item in self.selected_items:
            db_map, entity_id = item.db_map_entity_id
            db_map_typed_data.setdefault(db_map, {}).setdefault(
                item.entity_type, set()).add(entity_id)
        self._spine_db_editor.db_mngr.remove_items(db_map_typed_data)

    @Slot(bool)
    def hide_selected_items(self, checked=False):
        """Hides selected items."""
        self.hidden_items.extend(self.selected_items)
        for item in self.selected_items:
            item.set_all_visible(False)

    @Slot(bool)
    def show_hidden_items(self, checked=False):
        """Shows hidden items."""
        if not self.scene():
            return
        for item in self.hidden_items:
            item.set_all_visible(True)
        self.hidden_items.clear()

    def _get_selected_entity_names(self):
        if not self.selected_items:
            return ""
        names = "'" + self.selected_items[0].entity_name + "'"
        if len(self.selected_items) > 1:
            names += f" and {len(self.selected_items) - 1} other entities"
        return names

    def _get_selected_class_names(self):
        if not self.selected_items:
            return ""
        entity_class_names = list(
            set(item.entity_class_name for item in self.selected_items))
        names = "'" + entity_class_names[0] + "'"
        if len(entity_class_names) > 1:
            names += f" and {len(entity_class_names) - 1} other classes"
        return names

    @Slot(bool)
    def prune_selected_entities(self, checked=False):
        """Prunes selected items."""
        entity_ids = {x.db_map_entity_id for x in self.selected_items}
        key = self._get_selected_entity_names()
        self.prunned_entity_ids[key] = entity_ids
        self._restore_pruned_menu.addAction(key)
        self._spine_db_editor.build_graph()

    @Slot(bool)
    def prune_selected_classes(self, checked=False):
        """Prunes selected items."""
        db_map_class_ids = {}
        for x in self.selected_items:
            db_map_class_ids.setdefault(x.db_map, set()).add(x.entity_class_id)
        entity_ids = {
            (db_map, x["id"])
            for db_map, class_ids in db_map_class_ids.items()
            for x in self._spine_db_editor.db_mngr.get_items(db_map, "object")
            if x["class_id"] in class_ids
        }
        entity_ids |= {(db_map, x["id"])
                       for db_map, class_ids in db_map_class_ids.items()
                       for x in self._spine_db_editor.db_mngr.get_items(
                           db_map, "relationship")
                       if x["class_id"] in class_ids}
        key = self._get_selected_class_names()
        self.prunned_entity_ids[key] = entity_ids
        self._restore_pruned_menu.addAction(key)
        self._spine_db_editor.build_graph()

    @Slot(bool)
    def restore_all_pruned_items(self, checked=False):
        """Reinstates all pruned items."""
        self.prunned_entity_ids.clear()
        self._spine_db_editor.build_graph()

    @Slot("QAction")
    def restore_pruned_items(self, action):
        """Reinstates last pruned items."""
        key = action.text()
        if self.prunned_entity_ids.pop(key, None) is not None:
            action = next(
                iter(a for a in self._restore_pruned_menu.actions()
                     if a.text() == key))
            self._restore_pruned_menu.removeAction(action)
            self._spine_db_editor.build_graph()

    @Slot(bool)
    def select_position_parameters(self, checked=False):
        dialog = SelectPositionParametersDialog(self._spine_db_editor)
        dialog.show()
        dialog.selection_made.connect(self._set_position_parameters)

    @Slot(str, str)
    def _set_position_parameters(self, parameter_pos_x, parameter_pos_y):
        self.pos_x_parameter = parameter_pos_x
        self.pos_y_parameter = parameter_pos_y

    @Slot(bool)
    def save_positions(self, checked=False):
        if not self.pos_x_parameter or not self.pos_y_parameter:
            msg = "You haven't selected the position parameters. Please go to Graph -> Select position parameters"
            self._spine_db_editor.msg.emit(msg)
            return
        obj_items = [
            item for item in self.selected_items
            if isinstance(item, ObjectItem)
        ]
        rel_items = [
            item for item in self.selected_items
            if isinstance(item, RelationshipItem)
        ]
        db_map_class_obj_items = {}
        db_map_class_rel_items = {}
        for item in obj_items:
            db_map_class_obj_items.setdefault(item.db_map, {}).setdefault(
                item.entity_class_name, []).append(item)
        for item in rel_items:
            db_map_class_rel_items.setdefault(item.db_map, {}).setdefault(
                item.entity_class_name, []).append(item)
        db_map_data = {}
        for db_map, class_obj_items in db_map_class_obj_items.items():
            data = db_map_data.setdefault(db_map, {})
            for class_name, obj_items in class_obj_items.items():
                data["object_parameters"] = [
                    (class_name, self.pos_x_parameter),
                    (class_name, self.pos_y_parameter)
                ]
                data["object_parameter_values"] = [
                    (class_name, item.entity_name, self.pos_x_parameter,
                     item.pos().x()) for item in obj_items
                ] + [(class_name, item.entity_name, self.pos_y_parameter,
                      item.pos().y()) for item in obj_items]
        for db_map, class_rel_items in db_map_class_rel_items.items():
            data = db_map_data.setdefault(db_map, {})
            for class_name, rel_items in class_rel_items.items():
                data["relationship_parameters"] = [
                    (class_name, self.pos_x_parameter),
                    (class_name, self.pos_y_parameter),
                ]
                data["relationship_parameter_values"] = [
                    (class_name, item.object_name_list.split(","),
                     self.pos_x_parameter, item.pos().x())
                    for item in rel_items
                ] + [(class_name, item.object_name_list.split(","),
                      self.pos_y_parameter, item.pos().y())
                     for item in rel_items]
        self._spine_db_editor.db_mngr.import_data(db_map_data)

    @Slot(bool)
    def clear_saved_positions(self, checked=False):
        if not self.selected_items:
            return
        db_map_ids = {}
        for item in self.selected_items:
            db_map_ids.setdefault(item.db_map, set()).add(item.entity_id)
        db_map_typed_data = {}
        for db_map, ids in db_map_ids.items():
            db_map_typed_data[db_map] = {
                "parameter_value":
                set(pv["id"] for parameter_name in (self.pos_x_parameter,
                                                    self.pos_y_parameter)
                    for pv in self._spine_db_editor.db_mngr.get_items_by_field(
                        db_map, "parameter_value", "parameter_name",
                        parameter_name) if pv["entity_id"] in ids)
            }
        self._spine_db_editor.db_mngr.remove_items(db_map_typed_data)
        self._spine_db_editor.build_graph()

    @Slot(bool)
    def export_as_pdf(self, checked=False):
        file_path = self._spine_db_editor.get_pdf_file_path()
        if not file_path:
            return
        source = self._get_viewport_scene_rect()
        current_zoom_factor = self.zoom_factor
        self._zoom(1.0 / current_zoom_factor)
        self.scene().clearSelection()
        printer = QPrinter()
        printer.setPaperSize(source.size(), QPrinter.Point)
        printer.setOutputFileName(file_path)
        painter = QPainter(printer)
        self.scene().render(painter, QRectF(), source)
        painter.end()
        self._zoom(current_zoom_factor)
        self._spine_db_editor.file_exported.emit(file_path)

    def _populate_add_heat_map_menu(self):
        """Populates the menu 'Add heat map' with parameters for currently shown items in the graph."""
        db_map_class_ids = {}
        for item in self.entity_items:
            db_map_class_ids.setdefault(item.db_map,
                                        set()).add(item.entity_class_id)
        db_map_parameters = self._spine_db_editor.db_mngr.find_cascading_parameter_data(
            db_map_class_ids, "parameter_definition")
        db_map_class_parameters = {}
        parameter_value_ids = {}
        for db_map, parameters in db_map_parameters.items():
            for p in parameters:
                db_map_class_parameters.setdefault(
                    (db_map, p["entity_class_id"]), []).append(p)
            parameter_value_ids = {
                (db_map, pv["parameter_id"], pv["entity_id"]): pv["id"]
                for pv in self._spine_db_editor.db_mngr.
                find_cascading_parameter_values_by_definition(
                    {db_map: {x["id"]
                              for x in parameters}})[db_map]
            }
        self._point_value_tuples_per_parameter_name.clear()
        for item in self.entity_items:
            for parameter in db_map_class_parameters.get(
                (item.db_map, item.entity_class_id), ()):
                pv_id = parameter_value_ids.get(
                    (item.db_map, parameter["id"], item.entity_id))
                try:
                    value = float(
                        self._spine_db_editor.db_mngr.get_value(
                            item.db_map, "parameter_value", pv_id))
                    pos = item.pos()
                    self._point_value_tuples_per_parameter_name.setdefault(
                        parameter["parameter_name"], []).append(
                            (pos.x(), -pos.y(), value))
                except (TypeError, ValueError):
                    pass
        self._parameter_heat_map_menu.clear()
        for name, point_value_tuples in self._point_value_tuples_per_parameter_name.items(
        ):
            if len(point_value_tuples) > 1:
                self._parameter_heat_map_menu.addAction(name)
        self._parameter_heat_map_menu.setDisabled(
            self._parameter_heat_map_menu.isEmpty())

    @Slot("QAction")
    def add_heat_map(self, action):
        """Adds heat map for the parameter in the action text.
        """
        self._clean_up_heat_map_items()
        point_value_tuples = self._point_value_tuples_per_parameter_name[
            action.text()]
        x, y, values = zip(*point_value_tuples)
        heat_map, xv, yv, min_x, min_y, max_x, max_y = make_heat_map(
            x, y, values)
        heat_map_item, hm_figure = make_figure_graphics_item(self.scene(),
                                                             z=-3,
                                                             static=True)
        colorbar_item, cb_figure = make_figure_graphics_item(self.scene(),
                                                             z=3,
                                                             static=False)
        colormesh = hm_figure.gca().pcolormesh(xv, yv, heat_map)
        cb_figure.colorbar(colormesh, fraction=1)
        cb_figure.gca().set_visible(False)
        width = max_x - min_x
        height = max_y - min_y
        heat_map_item.widget().setGeometry(min_x, min_y, width, height)
        extent = self._spine_db_editor.VERTEX_EXTENT
        colorbar_item.widget().setGeometry(max_x + extent, min_y, 2 * extent,
                                           height)
        self.heat_map_items += [heat_map_item, colorbar_item]

    def _clean_up_heat_map_items(self):
        for item in self.heat_map_items:
            item.hide()
            self.scene().removeItem(item)
        self.heat_map_items.clear()

    def set_cross_hairs_items(self, relationship_class, cross_hairs_items):
        """Sets 'cross_hairs' items for relationship creation.

        Args:
            relationship_class (dict)
            cross_hairs_items (list(QGraphicsItems))
        """
        self.relationship_class = relationship_class
        self.cross_hairs_items = cross_hairs_items
        for item in cross_hairs_items:
            self.scene().addItem(item)
            item.apply_zoom(self.zoom_factor)
        cursor_pos = self.mapFromGlobal(QCursor.pos())
        self._update_cross_hairs_pos(cursor_pos)
        self.viewport().setCursor(Qt.BlankCursor)

    def clear_cross_hairs_items(self):
        self.relationship_class = None
        for item in self.cross_hairs_items:
            item.hide()
            item.scene().removeItem(item)
        self.cross_hairs_items.clear()
        self.viewport().unsetCursor()

    def _cross_hairs_has_valid_taget(self):
        return (self._hovered_obj_item.db_map
                == self.cross_hairs_items[0].db_map
                and self._hovered_obj_item.entity_class_id
                in self.relationship_class["object_class_ids_to_go"])

    def mousePressEvent(self, event):
        """Handles relationship creation if one it's in process."""
        if not self.cross_hairs_items:
            super().mousePressEvent(event)
            return
        if event.buttons() & Qt.RightButton or not self._hovered_obj_item:
            self.clear_cross_hairs_items()
            return
        if self._cross_hairs_has_valid_taget():
            self.relationship_class["object_class_ids_to_go"].remove(
                self._hovered_obj_item.entity_class_id)
            if self.relationship_class["object_class_ids_to_go"]:
                # Add hovered as member and keep going, we're not done yet
                ch_rel_item = self.cross_hairs_items[1]
                ch_arc_item = CrossHairsArcItem(
                    ch_rel_item, self._hovered_obj_item,
                    self._spine_db_editor._ARC_WIDTH)
                ch_rel_item.refresh_icon()
                self.scene().addItem(ch_arc_item)
                ch_arc_item.apply_zoom(self.zoom_factor)
                self.cross_hairs_items.append(ch_arc_item)
                return
            # Here we're done, add the relationships between the hovered and the members
            ch_item, _, *ch_arc_items = self.cross_hairs_items
            obj_items = [arc_item.obj_item for arc_item in ch_arc_items]
            obj_items.remove(ch_item)
            self._spine_db_editor.finalize_relationship(
                self.relationship_class, self._hovered_obj_item, *obj_items)
            self.clear_cross_hairs_items()

    def mouseMoveEvent(self, event):
        """Updates the hovered object item if we're in relationship creation mode."""
        if self.cross_hairs_items:
            self._update_cross_hairs_pos(event.pos())
            return
        super().mouseMoveEvent(event)
        if not self.itemAt(
                event.pos()) and (event.buttons() & Qt.LeftButton != 0):
            if self._previous_mouse_pos is not None:
                delta = event.pos() - self._previous_mouse_pos
                self._scroll_scene_by(delta.x(), delta.y())
            self._previous_mouse_pos = event.pos()

    def _update_cross_hairs_pos(self, pos):
        """Updates the hovered object item and sets the 'cross_hairs' icon accordingly.

        Args:
            pos (QPoint): the desired position in view coordinates
        """
        cross_hairs_item = self.cross_hairs_items[0]
        scene_pos = self.mapToScene(pos)
        delta = scene_pos - cross_hairs_item.scenePos()
        cross_hairs_item.block_move_by(delta.x(), delta.y())
        self._hovered_obj_item = None
        obj_items = [
            item for item in self.items(pos) if isinstance(item, ObjectItem)
        ]
        self._hovered_obj_item = next(iter(obj_items), None)
        if self._hovered_obj_item is not None:
            if self._cross_hairs_has_valid_taget():
                if len(self.relationship_class["object_class_ids_to_go"]) == 1:
                    self.cross_hairs_items[0].set_check_icon()
                else:
                    self.cross_hairs_items[0].set_plus_icon()
                return
            self.cross_hairs_items[0].set_ban_icon()
            return
        self.cross_hairs_items[0].set_normal_icon()

    def mouseReleaseEvent(self, event):
        if not self.cross_hairs_items:
            super().mouseReleaseEvent(event)

    def _scroll_scene_by(self, dx, dy):
        if dx == dy == 0:
            return
        scene_rect = self.sceneRect()
        view_scene_rect = self.mapFromScene(scene_rect).boundingRect()
        view_rect = self.viewport().rect()
        scene_dx = abs((self.mapToScene(0, 0) - self.mapToScene(dx, 0)).x())
        scene_dy = abs((self.mapToScene(0, 0) - self.mapToScene(0, dy)).y())
        if dx < 0 and view_rect.right() - dx >= view_scene_rect.right():
            scene_rect.adjust(0, 0, scene_dx, 0)
        elif dx > 0 and view_rect.left() - dx <= view_scene_rect.left():
            scene_rect.adjust(-scene_dx, 0, 0, 0)
        if dy < 0 and view_rect.bottom() - dy >= view_scene_rect.bottom():
            scene_rect.adjust(0, 0, 0, scene_dy)
        elif dy > 0 and view_rect.top() - dy <= view_scene_rect.top():
            scene_rect.adjust(0, -scene_dy, 0, 0)
        self.scene().setSceneRect(scene_rect)

    def keyPressEvent(self, event):
        """Aborts relationship creation if user presses ESC."""
        super().keyPressEvent(event)
        if event.key() == Qt.Key_Escape and self.cross_hairs_items:
            self._spine_db_editor.msg.emit("Relationship creation aborted.")
            self.clear_cross_hairs_items()

    def contextMenuEvent(self, e):
        """Shows context menu.

        Args:
            e (QContextMenuEvent): Context menu event
        """
        super().contextMenuEvent(e)
        if e.isAccepted():
            return
        e.accept()
        self._context_menu_pos = self.mapToScene(e.pos())
        self._menu.exec_(e.globalPos())

    def _compute_max_zoom(self):
        return sys.maxsize

    def _use_smooth_zoom(self):
        return self._qsettings.value("appSettings/smoothEntityGraphZoom",
                                     defaultValue="false") == "true"

    def _zoom(self, factor):
        self.scale(factor, factor)
        self.apply_zoom()

    def apply_zoom(self):
        for item in self.items():
            if hasattr(item, "apply_zoom"):
                item.apply_zoom(self.zoom_factor)

    def wheelEvent(self, event):
        """Zooms in/out. If user has pressed the shift key, rotates instead.

        Args:
            event (QWheelEvent): Mouse wheel event
        """
        if event.modifiers() != Qt.ShiftModifier:
            super().wheelEvent(event)
            return
        if event.orientation() != Qt.Vertical:
            event.ignore()
            return
        event.accept()
        smooth_rotation = self._qsettings.value(
            "appSettings/smoothEntityGraphRotation", defaultValue="false")
        if smooth_rotation == "true":
            num_degrees = event.delta() / 8
            num_steps = num_degrees / 15
            self._scheduled_transformations += num_steps
            if self._scheduled_transformations * num_steps < 0:
                self._scheduled_transformations = num_steps
            if self.time_line:
                self.time_line.deleteLater()
            self.time_line = QTimeLine(200, self)
            self.time_line.setUpdateInterval(20)
            self.time_line.valueChanged.connect(
                self._handle_rotation_time_line_advanced)
            self.time_line.finished.connect(
                self._handle_transformation_time_line_finished)
            self.time_line.start()
        else:
            angle = event.angleDelta().y() / 8
            self._rotate(angle)
            self._set_preferred_scene_rect()

    def _handle_rotation_time_line_advanced(self, pos):
        """Performs rotation whenever the smooth rotation time line advances."""
        angle = self._scheduled_transformations / 2.0
        self._rotate(angle)

    def _rotate(self, angle):
        center = self._get_viewport_scene_rect().center()
        for item in self.items():
            if hasattr(item, "apply_rotation"):
                item.apply_rotation(angle, center)

    def rotate_clockwise(self):
        """Performs a rotate clockwise with fixed angle."""
        self._rotate(-self._angle / 8)
        self._set_preferred_scene_rect()

    def rotate_anticlockwise(self):
        """Performs a rotate anticlockwise with fixed angle."""
        self._rotate(self._angle / 8)
        self._set_preferred_scene_rect()
コード例 #16
0
class PlotImage(FigureCanvas):
    def __init__(self, model, parent, main):

        super(FigureCanvas, self).__init__(Figure())

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)

        FigureCanvas.updateGeometry(self)

        self.model = model
        self.mw = main
        self.parent = parent

        self.rubber_band = QRubberBand(QRubberBand.Rectangle, self)
        self.band_origin = QtCore.QPoint()
        self.x_plot_origin = None
        self.y_plot_origin = None

        self.menu = QMenu(self)

    def enterEvent(self, event):
        self.setCursor(QtCore.Qt.CrossCursor)
        self.mw.coord_label.show()

    def leaveEvent(self, event):
        self.mw.coord_label.hide()
        self.mw.statusBar().showMessage("")

    def mousePressEvent(self, event):
        self.mw.coord_label.hide()

        # Set rubber band absolute and relative position
        self.band_origin = event.pos()
        self.x_plot_origin, self.y_plot_origin = self.getPlotCoords(
            event.pos())

        # Create rubber band
        self.rubber_band.setGeometry(
            QtCore.QRect(self.band_origin, QtCore.QSize()))

        FigureCanvas.mousePressEvent(self, event)

    def getPlotCoords(self, pos):

        cv = self.model.currentView

        # get the normalized axis coordinates from the event display units
        xPlotCoord, yPlotCoord = self.ax.transAxes.inverted().transform(
            (pos.x(), pos.y()))
        # flip the y-axis (its zero is in the upper left)
        yPlotCoord = 1 - yPlotCoord

        # scale axes using the plot extents
        xPlotCoord = self.ax.dataLim.x0 + xPlotCoord * self.ax.dataLim.width
        yPlotCoord = self.ax.dataLim.y0 + yPlotCoord * self.ax.dataLim.height

        # set coordinate label if pointer is in the axes
        if self.ax.contains_point((pos.x(), pos.y())):
            self.mw.coord_label.show()
            self.mw.showCoords(xPlotCoord, yPlotCoord)
        else:
            self.mw.coord_label.hide()

        return (xPlotCoord, yPlotCoord)

    def getIDinfo(self, event):

        cv = self.model.currentView

        # get origin in axes coordinates
        x0, y0 = self.ax.transAxes.transform((0., 0.))

        # get the extents of the axes box in axes coordinates
        bbox = self.ax.get_window_extent().transformed(
            self.figure.dpi_scale_trans.inverted())
        # get dimensions and scale using dpi
        width, height = bbox.width, bbox.height
        width *= self.figure.dpi
        height *= self.figure.dpi

        # use factor to get proper x,y position in pixels
        factor = (width / cv.h_res, height / cv.v_res)
        xPos = int((event.pos().x() - x0 + 0.05) / factor[0])
        yPos = int((event.pos().y() - y0 + 0.05) / factor[1])

        # check that the position is in the axes view
        if yPos < self.model.currentView.v_res \
            and xPos < self.model.currentView.h_res:
            id = f"{self.model.ids[yPos][xPos]}"
            temp = f"{self.model.props[yPos][xPos][0]:g}"
            density = f"{self.model.props[yPos][xPos][1]:g}"
        else:
            id = '-1'
            density = '-1'
            temp = '-1'

        if self.model.currentView.colorby == 'cell':
            domain = self.model.activeView.cells
            domain_kind = 'Cell'
        else:
            domain = self.model.activeView.materials
            domain_kind = 'Material'

        properties = {'density': density, 'temperature': temp}

        return id, properties, domain, domain_kind

    def mouseDoubleClickEvent(self, event):

        xCenter, yCenter = self.getPlotCoords(event.pos())
        self.mw.editPlotOrigin(xCenter, yCenter, apply=True)

        FigureCanvas.mouseDoubleClickEvent(self, event)

    def mouseMoveEvent(self, event):

        # Show Cursor position relative to plot in status bar
        xPlotPos, yPlotPos = self.getPlotCoords(event.pos())

        # Show Cell/Material ID, Name in status bar
        id, properties, domain, domain_kind = self.getIDinfo(event)
        if self.ax.contains_point((event.pos().x(), event.pos().y())):

            if id != str(_NOT_FOUND_) and domain[id].name:
                domainInfo = (f"{domain_kind} {id}: \"{domain[id].name}\"\t "
                              f"Density: {properties['density']} g/cm3\t"
                              f"Temperature: {properties['temperature']} K")
            elif id != str(_NOT_FOUND_):
                domainInfo = (f"{domain_kind} {id}\t"
                              f"Density: {properties['density']} g/cm3\t"
                              f"Temperature: {properties['temperature']} K")

            else:
                domainInfo = ""
        else:
            domainInfo = ""

        self.mw.statusBar().showMessage(f" {domainInfo}")

        # Update rubber band and values if mouse button held down
        if event.buttons() == QtCore.Qt.LeftButton:
            self.rubber_band.setGeometry(
                QtCore.QRect(self.band_origin, event.pos()).normalized())

            # Show rubber band if both dimensions > 10 pixels
            if self.rubber_band.width() > 10 and self.rubber_band.height(
            ) > 10:
                self.rubber_band.show()
            else:
                self.rubber_band.hide()

            # Update plot X Origin
            xCenter = (self.x_plot_origin + xPlotPos) / 2
            yCenter = (self.y_plot_origin + yPlotPos) / 2
            self.mw.editPlotOrigin(xCenter, yCenter)

            modifiers = event.modifiers()

            # Zoom out if Shift held
            if modifiers == QtCore.Qt.ShiftModifier:
                cv = self.model.currentView
                bandwidth = abs(self.band_origin.x() - event.pos().x())
                width = cv.width * (cv.h_res / max(bandwidth, .001))
                bandheight = abs(self.band_origin.y() - event.pos().y())
                height = cv.height * (cv.v_res / max(bandheight, .001))
            else:  # Zoom in
                width = max(abs(self.x_plot_origin - xPlotPos), 0.1)
                height = max(abs(self.y_plot_origin - yPlotPos), 0.1)

            self.mw.editWidth(width)
            self.mw.editHeight(height)

    def mouseReleaseEvent(self, event):

        if self.rubber_band.isVisible():
            self.rubber_band.hide()
            self.mw.applyChanges()
        else:
            self.mw.revertDockControls()

    def wheelEvent(self, event):

        if event.delta() and event.modifiers() == QtCore.Qt.ShiftModifier:
            numDegrees = event.delta() / 8

            if 24 < self.mw.zoom + numDegrees < 5001:
                self.mw.editZoom(self.mw.zoom + numDegrees)

    def contextMenuEvent(self, event):

        self.menu.clear()

        self.mw.undoAction.setText(f'&Undo ({len(self.model.previousViews)})')
        self.mw.redoAction.setText(
            f'&Redo ({len(self.model.subsequentViews)})')

        id, properties, domain, domain_kind = self.getIDinfo(event)

        if id != '-1':

            # Domain ID
            domainID = self.menu.addAction(f"{domain_kind} {id}")
            domainID.setDisabled(True)

            # Domain Name (if any)
            if domain[id].name:
                domainName = self.menu.addAction(domain[id].name)
                domainName.setDisabled(True)

            self.menu.addSeparator()
            self.menu.addAction(self.mw.undoAction)
            self.menu.addAction(self.mw.redoAction)
            self.menu.addSeparator()

            colorAction = self.menu.addAction(f'Edit {domain_kind} Color...')
            colorAction.setDisabled(self.model.currentView.highlighting)
            colorAction.setToolTip(f'Edit {domain_kind} color')
            colorAction.setStatusTip(f'Edit {domain_kind} color')
            colorAction.triggered.connect(
                lambda: self.mw.editDomainColor(domain_kind, id))

            maskAction = self.menu.addAction(f'Mask {domain_kind}')
            maskAction.setCheckable(True)
            maskAction.setChecked(domain[id].masked)
            maskAction.setDisabled(not self.model.currentView.masking)
            maskAction.setToolTip(f'Toggle {domain_kind} mask')
            maskAction.setStatusTip(f'Toggle {domain_kind} mask')
            maskAction.triggered[bool].connect(
                lambda bool=bool: self.mw.toggleDomainMask(
                    bool, domain_kind, id))

            highlightAction = self.menu.addAction(f'Highlight {domain_kind}')
            highlightAction.setCheckable(True)
            highlightAction.setChecked(domain[id].highlighted)
            highlightAction.setDisabled(
                not self.model.currentView.highlighting)
            highlightAction.setToolTip(f'Toggle {domain_kind} highlight')
            highlightAction.setStatusTip(f'Toggle {domain_kind} highlight')
            highlightAction.triggered[bool].connect(
                lambda bool=bool: self.mw.toggleDomainHighlight(
                    bool, domain_kind, id))

        else:
            self.menu.addAction(self.mw.undoAction)
            self.menu.addAction(self.mw.redoAction)
            self.menu.addSeparator()
            bgColorAction = self.menu.addAction('Edit Background Color...')
            bgColorAction.setToolTip('Edit background color')
            bgColorAction.setStatusTip('Edit plot background color')
            bgColorAction.triggered.connect(
                lambda: self.mw.editBackgroundColor(apply=True))

        self.menu.addSeparator()
        self.menu.addAction(self.mw.saveImageAction)
        self.menu.addAction(self.mw.saveViewAction)
        self.menu.addAction(self.mw.openAction)
        self.menu.addSeparator()
        self.menu.addMenu(self.mw.basisMenu)
        self.menu.addMenu(self.mw.colorbyMenu)
        self.menu.addSeparator()
        self.menu.addAction(self.mw.maskingAction)
        self.menu.addAction(self.mw.highlightingAct)
        self.menu.addSeparator()
        self.menu.addAction(self.mw.dockAction)

        self.mw.maskingAction.setChecked(self.model.currentView.masking)
        self.mw.highlightingAct.setChecked(self.model.currentView.highlighting)

        if self.mw.dock.isVisible():
            self.mw.dockAction.setText('Hide &Dock')
        else:
            self.mw.dockAction.setText('Show &Dock')

        self.menu.exec_(event.globalPos())

    def setPixmap(self, w, h):

        # clear out figure
        self.figure.clear()

        cv = self.model.currentView
        # set figure bg color to match window
        window_background = self.parent.palette().color(
            QtGui.QPalette.Background)
        self.figure.patch.set_facecolor(
            rgb_normalize(window_background.getRgb()))
        # set figure width
        self.figure.set_figwidth(0.99 * w / self.figure.get_dpi())
        self.figure.set_figheight(0.99 * h / self.figure.get_dpi())
        # set data extents for automatic reporting of pointer location
        data_bounds = [
            cv.origin[self.mw.xBasis] - cv.width / 2.,
            cv.origin[self.mw.xBasis] + cv.width / 2.,
            cv.origin[self.mw.yBasis] - cv.height / 2.,
            cv.origin[self.mw.yBasis] + cv.height / 2.
        ]

        # make sure we have an image to load
        if not hasattr(self.model, 'image'):
            self.model.generatePlot()
        c = self.figure.subplots().imshow(self.model.image,
                                          extent=data_bounds,
                                          alpha=cv.plotAlpha)
        self.ax = self.figure.axes[0]
        self.ax.margins(0.0, 0.0)
        self.figure.set_tight_layout({'pad': 1.0})
        # set axis labels
        axis_label_str = "{} (cm)"
        self.ax.set_xlabel(axis_label_str.format(cv.basis[0]))
        self.ax.set_ylabel(axis_label_str.format(cv.basis[1]))
        self.draw()
コード例 #17
0
class FLines(QOpenGLWidget):
    def __init__(self, parent, *args, **kwargs):
        super().__init__(parent, *args, **kwargs)
        self.num = 3  # 顶点数
        self.dia = 100  # 直径
        self.linewidth = 2  # 线宽
        self.r = 255
        self.g = 255
        self.b = 0
        self.xRot = 0.0
        self.yRot = 0.0
        self.zRot = 0.0
        self.show_coor = True  # 是否显示模型坐标轴
        # 右键菜单
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.rightMenu)
        self.contextMenu = QMenu(self)
        self.DD = self.contextMenu.addMenu('顶点个数')
        self.D4 = self.DD.addAction('4')
        self.D10 = self.DD.addAction('10')
        self.D20 = self.DD.addAction('20')
        self.ZJ = self.contextMenu.addMenu('直径')
        self.ZJ50 = self.ZJ.addAction('50')
        self.ZJ150 = self.ZJ.addAction('150')
        self.ZJ200 = self.ZJ.addAction('200')
        self.YS = self.contextMenu.addMenu('颜色')
        self.YS1 = self.YS.addAction('绿色')
        self.YS2 = self.YS.addAction('黑色')
        self.YS3 = self.YS.addAction('白色')
        self.D4.triggered.connect(lambda: self.changeNum(4))
        self.D10.triggered.connect(lambda: self.changeNum(10))
        self.D20.triggered.connect(lambda: self.changeNum(20))
        self.ZJ50.triggered.connect(lambda: self.changeDia(50))
        self.ZJ150.triggered.connect(lambda: self.changeDia(150))
        self.ZJ200.triggered.connect(lambda: self.changeDia(200))
        self.YS1.triggered.connect(lambda: self.changeRGB([0, 255, 0]))
        self.YS2.triggered.connect(lambda: self.changeRGB([0, 0, 0]))
        self.YS3.triggered.connect(lambda: self.changeRGB([255, 255, 255]))
        # 快捷键
        QShortcut(QKeySequence(Qt.Key_F1), self, lambda: self.RotF('F1'))
        QShortcut(QKeySequence(Qt.Key_F2), self, lambda: self.RotF('F2'))
        QShortcut(QKeySequence(Qt.Key_F3), self, lambda: self.RotF('F3'))
        QShortcut(QKeySequence(Qt.Key_F4), self, lambda: self.RotF('F4'))

    def rightMenu(self):
        self.contextMenu.popup(QCursor.pos(), None)  # 菜单显示的位置

    def initializeGL(self):
        glClearColor(0.3, 0.3, 0.6, 1)  # 背景色

    def paintGL(self):
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)  # 用当前背景色清屏
        glPushMatrix()
        glRotatef(self.xRot, 1.0, 0.0, 0.0)
        glRotatef(self.yRot, 0.0, 1.0, 0.0)
        glRotatef(self.zRot, 0.0, 0.0, 1.0)

        glLineWidth(self.linewidth)  # 设置线宽

        # 圆弧等分点
        theta = 2 * pi / self.num
        angle = 0
        z = 0
        Point = namedtuple('Point', ['x', 'y', 'z'])
        points_list = []
        for i in range(self.num):
            x = self.dia / 2 * cos(angle)
            y = self.dia / 2 * sin(angle)
            angle += theta
            p = Point(x, y, z)
            points_list.append(p)

        glColor3ub(self.r, self.g, self.b)  # 画笔色
        glBegin(GL_LINES)
        points_list2 = points_list[:]
        for i1 in points_list:
            points_list2.pop(0)
            for i in points_list2:
                glVertex3f(i1.x, i1.y, i1.z)
                glVertex3f(i.x, i.y, i.z)
        glEnd()

        if self.show_coor:  # 绘制模型坐标系
            glBegin(GL_LINES)
            # 以红色绘制x轴
            glColor4f(1.0, 0.0, 0.0, 1.0)  # 设置当前颜色为红色不透明
            glVertex3f(0.0, 0.0, 0.0)  # 设置x轴顶点(x轴负方向)
            glVertex3f(20, 0.0, 0.0)  # 设置x轴顶点(x轴正方向)
            # 以绿色绘制y轴
            glColor4f(0.0, 1.0, 0.0, 1.0)  # 设置当前颜色为绿色不透明
            glVertex3f(0.0, 0.0, 0.0)  # 设置y轴顶点(y轴负方向)
            glVertex3f(0.0, 20, 0.0)  # 设置y轴顶点(y轴正方向)
            # 以蓝色绘制z轴
            glColor4f(0.0, 0.0, 1.0, 1.0)  # 设置当前颜色为蓝色不透明
            glVertex3f(0.0, 0.0, 0.0)  # 设置z轴顶点(z轴负方向)
            glVertex3f(0.0, 0.0, 20)  # 设置z轴顶点(z轴正方向)
            glEnd()  # 结束绘制线段

            glPointSize(6)  # 设置点大小
            glBegin(GL_POINTS)  # 画点表示坐标轴正方向
            glColor4f(1.0, 0.0, 0.0, 1.0)
            glVertex3f(20, 0, 0)
            glColor4f(0.0, 1.0, 0.0, 1.0)
            glVertex3f(0, 20, 0)
            glColor4f(0.0, 0.0, 1.0, 1.0)
            glVertex3f(0, 0, 20)
            glEnd()

        glPopMatrix()  # 弹出矩阵

    def resizeGL(self, w: int, h: int):
        nRange = 100.0
        glViewport(0, 0, w, h)  # 设置视区和窗口等大
        glMatrixMode(GL_PROJECTION)  # 投影坐标系
        glLoadIdentity()  # 单位矩阵
        if w <= h:  # 平行投影
            glOrtho(-nRange, nRange, -nRange * h / w, nRange * h / w, -nRange,
                    nRange)
        else:
            glOrtho(-nRange * w / h, nRange * w / h, -nRange, nRange, -nRange,
                    nRange)
        glMatrixMode(GL_MODELVIEW)  # 模型坐标系
        glLoadIdentity()

    def changeNum(self, val):
        self.num = val
        self.update()

    def changeDia(self, val):
        self.dia = val
        self.update()

    def changeWidth(self, val):
        self.linewidth = val
        self.update()

    def changeR(self, val):
        self.r = val
        self.update()

    def changeG(self, val):
        self.g = val
        self.update()

    def changeB(self, val):
        self.b = val
        self.update()

    def changeRGB(self, RGB):
        self.r = RGB[0]
        self.g = RGB[1]
        self.b = RGB[2]
        self.update()

    def RotX(self, val):
        self.xRot = val
        self.update()

    def RotY(self, val):
        self.yRot = val
        self.update()

    def RotZ(self, val):
        self.zRot = val
        self.update()

    def RotF(self, F):
        if F == 'F1':
            self.xRot += 5
        elif F == 'F2':
            self.xRot -= 5
        elif F == 'F3':
            self.yRot += 5
        elif F == 'F4':
            self.yRot -= 5
        self.update()

    def showcoor(self, val):
        self.show_coor = val
        self.update()

    def wheelEvent(self, event):
        angle = event.angleDelta().y() / 8
        self.dia += angle
        if self.dia <= 50:
            self.dia = 50
        self.update()
コード例 #18
0
class TreeTaskList_supervisor(TreeTaskList_coordinator):
    def __init__(self, parent):
        super(TreeTaskList_supervisor, self).__init__(parent)

        self.setColumnCount(3)
        self.setColumnHidden(2, False)

        self.appendSupervisorConntectMenu()

    def appendSupervisorConntectMenu(self):
        self.processMenu = QMenu("Processes", self.menu)
        self.proceesAddSubMenu = QMenu("Add", self.processMenu)
        self.processMenu.addMenu(self.proceesAddSubMenu)
        self.processMenu.addAction("Remove", self.removeProcess)
        self.menu.addMenu(self.processMenu)

    def callContextMenu(self, pos):
        self.proceesAddSubMenu.clear()
        self.createProcessAddMenu(self.proceesAddSubMenu)
        self.menu.exec_(QCursor.pos())

    def createProcessAddMenu(self, processMenu):
        items = self.itemUtils.getSelected_shotItems()
        processesList = self.getProcessList(items)
        sKeysList = [it.data(0, Qt.UserRole) for it in items]

        server = self.taskManagerWdg.userServerCore.server
        server.start()
        for process in processesList:
            processMenu.addAction(
                "{}".format(process),
                lambda x=process: self.addTaskAction(server, sKeysList, x))
        server.finish()

    def getProcessList(self, items):
        def getItemType(sKey):
            idx0 = sKey.find('/')
            idx1 = sKey.find('?')
            return sKey[idx0:idx1]

        def compareItemTypes(code, types):
            idx = code.find('/')
            processType = code[idx:]
            return processType in types

        itemTypes = []
        for item in items:
            sKey = item.data(0, Qt.UserRole)
            itemTypes.append(getItemType(sKey))

        itemTypes = list(set(itemTypes))

        processesData = self.taskManagerWdg.getProcessesData()
        filteredData = list(
            filter(lambda x: compareItemTypes(x['code'], itemTypes),
                   processesData))

        processesList = []
        for data in filteredData:
            processesList += data.get("processes")
        return processesList

    def addTaskAction(self, server, sKeysList, process):
        for sKey in sKeysList:
            taskData = self.taskManagerWdg.userServerCore.taskData
            itemData = tacticDataProcess.getTaskElementBySearchField(
                taskData, "__search_key__", sKey)
            existingProcesses = tacticDataProcess.getActiveProcessesList(
                [itemData])

            if process in existingProcesses:
                continue
            task = tacticPostUtils.createTask(server, sKey, process)
            tacticPostUtils.updateSobject(
                server, task.get('__search_key__'),
                {"status": configUtils.tctStatusElements.get('assignment')})

    def removeProcess(self):
        items = self.itemUtils.getSelected_ProcessItems(getMultiple=True)
        if not isinstance(items, list):
            items = [items]

        server = self.taskManagerWdg.userServerCore.server
        for item in items:
            sKey = item.data(0, Qt.UserRole)
            if sKey.find("task") < 0:
                continue
            print(sKey)
            tacticPostUtils.deleteSObject(server, sKey, True)
コード例 #19
0
class FittingResultViewer(QDialog):
    PAGE_ROWS = 20
    logger = logging.getLogger("root.QGrain.ui.FittingResultViewer")
    result_marked = Signal(SSUResult)

    def __init__(self, reference_viewer: ReferenceResultViewer, parent=None):
        super().__init__(parent=parent, f=Qt.Window)
        self.setWindowTitle(self.tr("SSU Fitting Result Viewer"))
        self.__fitting_results = []  # type: list[SSUResult]
        self.retry_tasks = {}  # type: dict[UUID, SSUTask]
        self.__reference_viewer = reference_viewer
        self.init_ui()
        self.boxplot_chart = BoxplotChart(parent=self, toolbar=True)
        self.typical_chart = SSUTypicalComponentChart(parent=self,
                                                      toolbar=True)
        self.distance_chart = DistanceCurveChart(parent=self, toolbar=True)
        self.mixed_distribution_chart = MixedDistributionChart(
            parent=self, toolbar=True, use_animation=True)
        self.file_dialog = QFileDialog(parent=self)
        self.async_worker = AsyncWorker()
        self.async_worker.background_worker.task_succeeded.connect(
            self.on_fitting_succeeded)
        self.async_worker.background_worker.task_failed.connect(
            self.on_fitting_failed)
        self.update_page_list()
        self.update_page(self.page_index)

        self.normal_msg = QMessageBox(self)
        self.remove_warning_msg = QMessageBox(self)
        self.remove_warning_msg.setStandardButtons(QMessageBox.No
                                                   | QMessageBox.Yes)
        self.remove_warning_msg.setDefaultButton(QMessageBox.No)
        self.remove_warning_msg.setWindowTitle(self.tr("Warning"))
        self.remove_warning_msg.setText(
            self.tr("Are you sure to remove all SSU results?"))
        self.outlier_msg = QMessageBox(self)
        self.outlier_msg.setStandardButtons(QMessageBox.Discard
                                            | QMessageBox.Retry
                                            | QMessageBox.Ignore)
        self.outlier_msg.setDefaultButton(QMessageBox.Ignore)
        self.retry_progress_msg = QMessageBox()
        self.retry_progress_msg.addButton(QMessageBox.Ok)
        self.retry_progress_msg.button(QMessageBox.Ok).hide()
        self.retry_progress_msg.setWindowTitle(self.tr("Progress"))
        self.retry_timer = QTimer(self)
        self.retry_timer.setSingleShot(True)
        self.retry_timer.timeout.connect(
            lambda: self.retry_progress_msg.exec_())

    def init_ui(self):
        self.data_table = QTableWidget(100, 100)
        self.data_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.data_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.data_table.setAlternatingRowColors(True)
        self.data_table.setContextMenuPolicy(Qt.CustomContextMenu)
        self.main_layout = QGridLayout(self)
        self.main_layout.addWidget(self.data_table, 0, 0, 1, 3)

        self.previous_button = QPushButton(
            qta.icon("mdi.skip-previous-circle"), self.tr("Previous"))
        self.previous_button.setToolTip(
            self.tr("Click to back to the previous page."))
        self.previous_button.clicked.connect(self.on_previous_button_clicked)
        self.current_page_combo_box = QComboBox()
        self.current_page_combo_box.addItem(self.tr("Page {0}").format(1))
        self.current_page_combo_box.currentIndexChanged.connect(
            self.update_page)
        self.next_button = QPushButton(qta.icon("mdi.skip-next-circle"),
                                       self.tr("Next"))
        self.next_button.setToolTip(self.tr("Click to jump to the next page."))
        self.next_button.clicked.connect(self.on_next_button_clicked)
        self.main_layout.addWidget(self.previous_button, 1, 0)
        self.main_layout.addWidget(self.current_page_combo_box, 1, 1)
        self.main_layout.addWidget(self.next_button, 1, 2)

        self.distance_label = QLabel(self.tr("Distance"))
        self.distance_label.setToolTip(
            self.
            tr("It's the function to calculate the difference (on the contrary, similarity) between two samples."
               ))
        self.distance_combo_box = QComboBox()
        self.distance_combo_box.addItems(built_in_distances)
        self.distance_combo_box.setCurrentText("log10MSE")
        self.distance_combo_box.currentTextChanged.connect(
            lambda: self.update_page(self.page_index))
        self.main_layout.addWidget(self.distance_label, 2, 0)
        self.main_layout.addWidget(self.distance_combo_box, 2, 1, 1, 2)
        self.menu = QMenu(self.data_table)
        self.menu.setShortcutAutoRepeat(True)
        self.mark_action = self.menu.addAction(
            qta.icon("mdi.marker-check"),
            self.tr("Mark Selection(s) as Reference"))
        self.mark_action.triggered.connect(self.mark_selections)
        self.remove_selection_action = self.menu.addAction(
            qta.icon("fa.remove"), self.tr("Remove Selection(s)"))
        self.remove_selection_action.triggered.connect(self.remove_selections)
        self.remove_all_action = self.menu.addAction(qta.icon("fa.remove"),
                                                     self.tr("Remove All"))
        self.remove_all_action.triggered.connect(self.remove_all_results)
        self.plot_loss_chart_action = self.menu.addAction(
            qta.icon("mdi.chart-timeline-variant"), self.tr("Plot Loss Chart"))
        self.plot_loss_chart_action.triggered.connect(self.show_distance)
        self.plot_distribution_chart_action = self.menu.addAction(
            qta.icon("fa5s.chart-area"), self.tr("Plot Distribution Chart"))
        self.plot_distribution_chart_action.triggered.connect(
            self.show_distribution)
        self.plot_distribution_animation_action = self.menu.addAction(
            qta.icon("fa5s.chart-area"),
            self.tr("Plot Distribution Chart (Animation)"))
        self.plot_distribution_animation_action.triggered.connect(
            self.show_history_distribution)

        self.detect_outliers_menu = self.menu.addMenu(
            qta.icon("mdi.magnify"), self.tr("Detect Outliers"))
        self.check_nan_and_inf_action = self.detect_outliers_menu.addAction(
            self.tr("Check NaN and Inf"))
        self.check_nan_and_inf_action.triggered.connect(self.check_nan_and_inf)
        self.check_final_distances_action = self.detect_outliers_menu.addAction(
            self.tr("Check Final Distances"))
        self.check_final_distances_action.triggered.connect(
            self.check_final_distances)
        self.check_component_mean_action = self.detect_outliers_menu.addAction(
            self.tr("Check Component Mean"))
        self.check_component_mean_action.triggered.connect(
            lambda: self.check_component_moments("mean"))
        self.check_component_std_action = self.detect_outliers_menu.addAction(
            self.tr("Check Component STD"))
        self.check_component_std_action.triggered.connect(
            lambda: self.check_component_moments("std"))
        self.check_component_skewness_action = self.detect_outliers_menu.addAction(
            self.tr("Check Component Skewness"))
        self.check_component_skewness_action.triggered.connect(
            lambda: self.check_component_moments("skewness"))
        self.check_component_kurtosis_action = self.detect_outliers_menu.addAction(
            self.tr("Check Component Kurtosis"))
        self.check_component_kurtosis_action.triggered.connect(
            lambda: self.check_component_moments("kurtosis"))
        self.check_component_fractions_action = self.detect_outliers_menu.addAction(
            self.tr("Check Component Fractions"))
        self.check_component_fractions_action.triggered.connect(
            self.check_component_fractions)
        self.degrade_results_action = self.detect_outliers_menu.addAction(
            self.tr("Degrade Results"))
        self.degrade_results_action.triggered.connect(self.degrade_results)
        self.try_align_components_action = self.detect_outliers_menu.addAction(
            self.tr("Try Align Components"))
        self.try_align_components_action.triggered.connect(
            self.try_align_components)

        self.analyse_typical_components_action = self.menu.addAction(
            qta.icon("ei.tags"), self.tr("Analyse Typical Components"))
        self.analyse_typical_components_action.triggered.connect(
            self.analyse_typical_components)
        self.load_dump_action = self.menu.addAction(
            qta.icon("fa.database"), self.tr("Load Binary Dump"))
        self.load_dump_action.triggered.connect(self.load_dump)
        self.save_dump_action = self.menu.addAction(
            qta.icon("fa.save"), self.tr("Save Binary Dump"))
        self.save_dump_action.triggered.connect(self.save_dump)
        self.save_excel_action = self.menu.addAction(
            qta.icon("mdi.microsoft-excel"), self.tr("Save Excel"))
        self.save_excel_action.triggered.connect(
            lambda: self.on_save_excel_clicked(align_components=False))
        self.save_excel_align_action = self.menu.addAction(
            qta.icon("mdi.microsoft-excel"),
            self.tr("Save Excel (Force Alignment)"))
        self.save_excel_align_action.triggered.connect(
            lambda: self.on_save_excel_clicked(align_components=True))
        self.data_table.customContextMenuRequested.connect(self.show_menu)
        # necessary to add actions of menu to this widget itself,
        # otherwise, the shortcuts will not be triggered
        self.addActions(self.menu.actions())

    def show_menu(self, pos: QPoint):
        self.menu.popup(QCursor.pos())

    def show_message(self, title: str, message: str):
        self.normal_msg.setWindowTitle(title)
        self.normal_msg.setText(message)
        self.normal_msg.exec_()

    def show_info(self, message: str):
        self.show_message(self.tr("Info"), message)

    def show_warning(self, message: str):
        self.show_message(self.tr("Warning"), message)

    def show_error(self, message: str):
        self.show_message(self.tr("Error"), message)

    @property
    def distance_name(self) -> str:
        return self.distance_combo_box.currentText()

    @property
    def distance_func(self) -> typing.Callable:
        return get_distance_func_by_name(self.distance_combo_box.currentText())

    @property
    def page_index(self) -> int:
        return self.current_page_combo_box.currentIndex()

    @property
    def n_pages(self) -> int:
        return self.current_page_combo_box.count()

    @property
    def n_results(self) -> int:
        return len(self.__fitting_results)

    @property
    def selections(self):
        start = self.page_index * self.PAGE_ROWS
        temp = set()
        for item in self.data_table.selectedRanges():
            for i in range(item.topRow(),
                           min(self.PAGE_ROWS + 1,
                               item.bottomRow() + 1)):
                temp.add(i + start)
        indexes = list(temp)
        indexes.sort()
        return indexes

    def update_page_list(self):
        last_page_index = self.page_index
        if self.n_results == 0:
            n_pages = 1
        else:
            n_pages, left = divmod(self.n_results, self.PAGE_ROWS)
            if left != 0:
                n_pages += 1
        self.current_page_combo_box.blockSignals(True)
        self.current_page_combo_box.clear()
        self.current_page_combo_box.addItems(
            [self.tr("Page {0}").format(i + 1) for i in range(n_pages)])
        if last_page_index >= n_pages:
            self.current_page_combo_box.setCurrentIndex(n_pages - 1)
        else:
            self.current_page_combo_box.setCurrentIndex(last_page_index)
        self.current_page_combo_box.blockSignals(False)

    def update_page(self, page_index: int):
        def write(row: int, col: int, value: str):
            if isinstance(value, str):
                pass
            elif isinstance(value, int):
                value = str(value)
            elif isinstance(value, float):
                value = f"{value: 0.4f}"
            else:
                value = value.__str__()
            item = QTableWidgetItem(value)
            item.setTextAlignment(Qt.AlignCenter)
            self.data_table.setItem(row, col, item)

        # necessary to clear
        self.data_table.clear()
        if page_index == self.n_pages - 1:
            start = page_index * self.PAGE_ROWS
            end = self.n_results
        else:
            start, end = page_index * self.PAGE_ROWS, (page_index +
                                                       1) * self.PAGE_ROWS
        self.data_table.setRowCount(end - start)
        self.data_table.setColumnCount(7)
        self.data_table.setHorizontalHeaderLabels([
            self.tr("Resolver"),
            self.tr("Distribution Type"),
            self.tr("N_components"),
            self.tr("N_iterations"),
            self.tr("Spent Time [s]"),
            self.tr("Final Distance"),
            self.tr("Has Reference")
        ])
        sample_names = [
            result.sample.name for result in self.__fitting_results[start:end]
        ]
        self.data_table.setVerticalHeaderLabels(sample_names)
        for row, result in enumerate(self.__fitting_results[start:end]):
            write(row, 0, result.task.resolver)
            write(row, 1,
                  self.get_distribution_name(result.task.distribution_type))
            write(row, 2, result.task.n_components)
            write(row, 3, result.n_iterations)
            write(row, 4, result.time_spent)
            write(
                row, 5,
                self.distance_func(result.sample.distribution,
                                   result.distribution))
            has_ref = result.task.initial_guess is not None or result.task.reference is not None
            write(row, 6, self.tr("Yes") if has_ref else self.tr("No"))

        self.data_table.resizeColumnsToContents()

    def on_previous_button_clicked(self):
        if self.page_index > 0:
            self.current_page_combo_box.setCurrentIndex(self.page_index - 1)

    def on_next_button_clicked(self):
        if self.page_index < self.n_pages - 1:
            self.current_page_combo_box.setCurrentIndex(self.page_index + 1)

    def get_distribution_name(self, distribution_type: DistributionType):
        if distribution_type == DistributionType.Normal:
            return self.tr("Normal")
        elif distribution_type == DistributionType.Weibull:
            return self.tr("Weibull")
        elif distribution_type == DistributionType.SkewNormal:
            return self.tr("Skew Normal")
        else:
            raise NotImplementedError(distribution_type)

    def add_result(self, result: SSUResult):
        if self.n_results == 0 or \
            (self.page_index == self.n_pages - 1 and \
            divmod(self.n_results, self.PAGE_ROWS)[-1] != 0):
            need_update = True
        else:
            need_update = False
        self.__fitting_results.append(result)
        self.update_page_list()
        if need_update:
            self.update_page(self.page_index)

    def add_results(self, results: typing.List[SSUResult]):
        if self.n_results == 0 or \
            (self.page_index == self.n_pages - 1 and \
            divmod(self.n_results, self.PAGE_ROWS)[-1] != 0):
            need_update = True
        else:
            need_update = False
        self.__fitting_results.extend(results)
        self.update_page_list()
        if need_update:
            self.update_page(self.page_index)

    def mark_selections(self):
        for index in self.selections:
            self.result_marked.emit(self.__fitting_results[index])

    def remove_results(self, indexes):
        results = []
        for i in reversed(indexes):
            res = self.__fitting_results.pop(i)
            results.append(res)
        self.update_page_list()
        self.update_page(self.page_index)

    def remove_selections(self):
        indexes = self.selections
        self.remove_results(indexes)

    def remove_all_results(self):
        res = self.remove_warning_msg.exec_()
        if res == QMessageBox.Yes:
            self.__fitting_results.clear()
            self.update_page_list()
            self.update_page(0)

    def show_distance(self):
        results = [self.__fitting_results[i] for i in self.selections]
        if results is None or len(results) == 0:
            return
        result = results[0]
        self.distance_chart.show_distance_series(result.get_distance_series(
            self.distance_name),
                                                 title=result.sample.name)
        self.distance_chart.show()

    def show_distribution(self):
        results = [self.__fitting_results[i] for i in self.selections]
        if results is None or len(results) == 0:
            return
        result = results[0]
        self.mixed_distribution_chart.show_model(result.view_model)
        self.mixed_distribution_chart.show()

    def show_history_distribution(self):
        results = [self.__fitting_results[i] for i in self.selections]
        if results is None or len(results) == 0:
            return
        result = results[0]
        self.mixed_distribution_chart.show_result(result)
        self.mixed_distribution_chart.show()

    def load_dump(self):
        filename, _ = self.file_dialog.getOpenFileName(
            self, self.tr("Select a binary dump file of SSU results"), None,
            self.tr("Binary dump (*.dump)"))
        if filename is None or filename == "":
            return
        with open(filename, "rb") as f:
            results = pickle.load(f)  # type: list[SSUResult]
            valid = True
            if isinstance(results, list):
                for result in results:
                    if not isinstance(result, SSUResult):
                        valid = False
                        break
            else:
                valid = False

            if valid:
                if self.n_results != 0 and len(results) != 0:
                    old_classes = self.__fitting_results[0].classes_φ
                    new_classes = results[0].classes_φ
                    classes_inconsistent = False
                    if len(old_classes) != len(new_classes):
                        classes_inconsistent = True
                    else:
                        classes_error = np.abs(old_classes - new_classes)
                        if not np.all(np.less_equal(classes_error, 1e-8)):
                            classes_inconsistent = True
                    if classes_inconsistent:
                        self.show_error(
                            self.
                            tr("The results in the dump file has inconsistent grain-size classes with that in your list."
                               ))
                        return
                self.add_results(results)
            else:
                self.show_error(self.tr("The binary dump file is invalid."))

    def save_dump(self):
        if self.n_results == 0:
            self.show_warning(self.tr("There is not any result in the list."))
            return
        filename, _ = self.file_dialog.getSaveFileName(
            self, self.tr("Save the SSU results to binary dump file"), None,
            self.tr("Binary dump (*.dump)"))
        if filename is None or filename == "":
            return
        with open(filename, "wb") as f:
            pickle.dump(self.__fitting_results, f)

    def save_excel(self, filename, align_components=False):
        if self.n_results == 0:
            return

        results = self.__fitting_results.copy()
        classes_μm = results[0].classes_μm
        n_components_list = [
            result.n_components for result in self.__fitting_results
        ]
        count_dict = Counter(n_components_list)
        max_n_components = max(count_dict.keys())
        self.logger.debug(
            f"N_components: {count_dict}, Max N_components: {max_n_components}"
        )

        flags = []
        if not align_components:
            for result in results:
                flags.extend(range(result.n_components))
        else:
            n_components_desc = "\n".join([
                self.tr("{0} Component(s): {1}").format(n_components, count)
                for n_components, count in count_dict.items()
            ])
            self.show_info(
                self.tr("N_components distribution of Results:\n{0}").format(
                    n_components_desc))
            stacked_components = []
            for result in self.__fitting_results:
                for component in result.components:
                    stacked_components.append(component.distribution)
            stacked_components = np.array(stacked_components)
            cluser = KMeans(n_clusters=max_n_components)
            flags = cluser.fit_predict(stacked_components)
            # check flags to make it unique
            flag_index = 0
            for i, result in enumerate(self.__fitting_results):
                result_flags = set()
                for component in result.components:
                    if flags[flag_index] in result_flags:
                        if flags[flag_index] == max_n_components:
                            flags[flag_index] = max_n_components - 1
                        else:
                            flag_index[flag_index] += 1
                        result_flags.add(flags[flag_index])
                    flag_index += 1

            flag_set = set(flags)
            picked = []
            for target_flag in flag_set:
                for i, flag in enumerate(flags):
                    if flag == target_flag:
                        picked.append(
                            (target_flag,
                             logarithmic(classes_μm,
                                         stacked_components[i])["mean"]))
                        break
            picked.sort(key=lambda x: x[1])
            flag_map = {flag: index for index, (flag, _) in enumerate(picked)}
            flags = np.array([flag_map[flag] for flag in flags])

        wb = openpyxl.Workbook()
        prepare_styles(wb)
        ws = wb.active
        ws.title = self.tr("README")
        description = \
            """
            This Excel file was generated by QGrain ({0}).

            Please cite:
            Liu, Y., Liu, X., Sun, Y., 2021. QGrain: An open-source and easy-to-use software for the comprehensive analysis of grain size distributions. Sedimentary Geology 423, 105980. https://doi.org/10.1016/j.sedgeo.2021.105980

            It contanins 4 + max(N_components) sheets:
            1. The first sheet is the sample distributions of SSU results.
            2. The second sheet is used to put the infomation of fitting.
            3. The third sheet is the statistic parameters calculated by statistic moment method.
            4. The fouth sheet is the distributions of unmixed components and their sum of each sample.
            5. Other sheets are the unmixed end-member distributions which were discretely stored.

            The SSU algorithm is implemented by QGrain.

            """.format(QGRAIN_VERSION)

        def write(row, col, value, style="normal_light"):
            cell = ws.cell(row + 1, col + 1, value=value)
            cell.style = style

        lines_of_desc = description.split("\n")
        for row, line in enumerate(lines_of_desc):
            write(row, 0, line, style="description")
        ws.column_dimensions[column_to_char(0)].width = 200

        ws = wb.create_sheet(self.tr("Sample Distributions"))
        write(0, 0, self.tr("Sample Name"), style="header")
        ws.column_dimensions[column_to_char(0)].width = 16
        for col, value in enumerate(classes_μm, 1):
            write(0, col, value, style="header")
            ws.column_dimensions[column_to_char(col)].width = 10
        for row, result in enumerate(results, 1):
            if row % 2 == 0:
                style = "normal_dark"
            else:
                style = "normal_light"
            write(row, 0, result.sample.name, style=style)
            for col, value in enumerate(result.sample.distribution, 1):
                write(row, col, value, style=style)
            QCoreApplication.processEvents()

        ws = wb.create_sheet(self.tr("Information of Fitting"))
        write(0, 0, self.tr("Sample Name"), style="header")
        ws.column_dimensions[column_to_char(0)].width = 16
        headers = [
            self.tr("Distribution Type"),
            self.tr("N_components"),
            self.tr("Resolver"),
            self.tr("Resolver Settings"),
            self.tr("Initial Guess"),
            self.tr("Reference"),
            self.tr("Spent Time [s]"),
            self.tr("N_iterations"),
            self.tr("Final Distance [log10MSE]")
        ]
        for col, value in enumerate(headers, 1):
            write(0, col, value, style="header")
            if col in (4, 5, 6):
                ws.column_dimensions[column_to_char(col)].width = 10
            else:
                ws.column_dimensions[column_to_char(col)].width = 10
        for row, result in enumerate(results, 1):
            if row % 2 == 0:
                style = "normal_dark"
            else:
                style = "normal_light"
            write(row, 0, result.sample.name, style=style)
            write(row, 1, result.distribution_type.name, style=style)
            write(row, 2, result.n_components, style=style)
            write(row, 3, result.task.resolver, style=style)
            write(row,
                  4,
                  self.tr("Default") if result.task.resolver_setting is None
                  else result.task.resolver_setting.__str__(),
                  style=style)
            write(row,
                  5,
                  self.tr("None") if result.task.initial_guess is None else
                  result.task.initial_guess.__str__(),
                  style=style)
            write(row,
                  6,
                  self.tr("None") if result.task.reference is None else
                  result.task.reference.__str__(),
                  style=style)
            write(row, 7, result.time_spent, style=style)
            write(row, 8, result.n_iterations, style=style)
            write(row, 9, result.get_distance("log10MSE"), style=style)

        ws = wb.create_sheet(self.tr("Statistic Moments"))
        write(0, 0, self.tr("Sample Name"), style="header")
        ws.merge_cells(start_row=1, start_column=1, end_row=2, end_column=1)
        ws.column_dimensions[column_to_char(0)].width = 16
        headers = []
        sub_headers = [
            self.tr("Proportion"),
            self.tr("Mean [φ]"),
            self.tr("Mean [μm]"),
            self.tr("STD [φ]"),
            self.tr("STD [μm]"),
            self.tr("Skewness"),
            self.tr("Kurtosis")
        ]
        for i in range(max_n_components):
            write(0,
                  i * len(sub_headers) + 1,
                  self.tr("C{0}").format(i + 1),
                  style="header")
            ws.merge_cells(start_row=1,
                           start_column=i * len(sub_headers) + 2,
                           end_row=1,
                           end_column=(i + 1) * len(sub_headers) + 1)
            headers.extend(sub_headers)
        for col, value in enumerate(headers, 1):
            write(1, col, value, style="header")
            ws.column_dimensions[column_to_char(col)].width = 10
        flag_index = 0
        for row, result in enumerate(results, 2):
            if row % 2 == 0:
                style = "normal_light"
            else:
                style = "normal_dark"
            write(row, 0, result.sample.name, style=style)
            for component in result.components:
                index = flags[flag_index]
                write(row,
                      index * len(sub_headers) + 1,
                      component.fraction,
                      style=style)
                write(row,
                      index * len(sub_headers) + 2,
                      component.logarithmic_moments["mean"],
                      style=style)
                write(row,
                      index * len(sub_headers) + 3,
                      component.geometric_moments["mean"],
                      style=style)
                write(row,
                      index * len(sub_headers) + 4,
                      component.logarithmic_moments["std"],
                      style=style)
                write(row,
                      index * len(sub_headers) + 5,
                      component.geometric_moments["std"],
                      style=style)
                write(row,
                      index * len(sub_headers) + 6,
                      component.logarithmic_moments["skewness"],
                      style=style)
                write(row,
                      index * len(sub_headers) + 7,
                      component.logarithmic_moments["kurtosis"],
                      style=style)
                flag_index += 1

        ws = wb.create_sheet(self.tr("Unmixed Components"))
        ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=2)
        write(0, 0, self.tr("Sample Name"), style="header")
        ws.column_dimensions[column_to_char(0)].width = 16
        for col, value in enumerate(classes_μm, 2):
            write(0, col, value, style="header")
            ws.column_dimensions[column_to_char(col)].width = 10
        row = 1
        for result_index, result in enumerate(results, 1):
            if result_index % 2 == 0:
                style = "normal_dark"
            else:
                style = "normal_light"
            write(row, 0, result.sample.name, style=style)
            ws.merge_cells(start_row=row + 1,
                           start_column=1,
                           end_row=row + result.n_components + 1,
                           end_column=1)
            for component_i, component in enumerate(result.components, 1):
                write(row, 1, self.tr("C{0}").format(component_i), style=style)
                for col, value in enumerate(
                        component.distribution * component.fraction, 2):
                    write(row, col, value, style=style)
                row += 1
            write(row, 1, self.tr("Sum"), style=style)
            for col, value in enumerate(result.distribution, 2):
                write(row, col, value, style=style)
            row += 1

        ws_dict = {}
        flag_set = set(flags)
        for flag in flag_set:
            ws = wb.create_sheet(self.tr("Unmixed EM{0}").format(flag + 1))
            write(0, 0, self.tr("Sample Name"), style="header")
            ws.column_dimensions[column_to_char(0)].width = 16
            for col, value in enumerate(classes_μm, 1):
                write(0, col, value, style="header")
                ws.column_dimensions[column_to_char(col)].width = 10
            ws_dict[flag] = ws

        flag_index = 0
        for row, result in enumerate(results, 1):
            if row % 2 == 0:
                style = "normal_dark"
            else:
                style = "normal_light"

            for component in result.components:
                flag = flags[flag_index]
                ws = ws_dict[flag]
                write(row, 0, result.sample.name, style=style)
                for col, value in enumerate(component.distribution, 1):
                    write(row, col, value, style=style)
                flag_index += 1

        wb.save(filename)
        wb.close()

    def on_save_excel_clicked(self, align_components=False):
        if self.n_results == 0:
            self.show_warning(self.tr("There is not any SSU result."))
            return
        filename, _ = self.file_dialog.getSaveFileName(
            None, self.tr("Choose a filename to save SSU Results"), None,
            "Microsoft Excel (*.xlsx)")
        if filename is None or filename == "":
            return
        try:
            self.save_excel(filename, align_components)
            self.show_info(
                self.tr("SSU results have been saved to:\n    {0}").format(
                    filename))
        except Exception as e:
            self.show_error(
                self.
                tr("Error raised while save SSU results to Excel file.\n    {0}"
                   ).format(e.__str__()))

    def on_fitting_succeeded(self, result: SSUResult):
        result_replace_index = self.retry_tasks[result.task.uuid]
        self.__fitting_results[result_replace_index] = result
        self.retry_tasks.pop(result.task.uuid)
        self.retry_progress_msg.setText(
            self.tr("Tasks to be retried: {0}").format(len(self.retry_tasks)))
        if len(self.retry_tasks) == 0:
            self.retry_progress_msg.close()

        self.logger.debug(
            f"Retried task succeeded, sample name={result.task.sample.name}, distribution_type={result.task.distribution_type.name}, n_components={result.task.n_components}"
        )
        self.update_page(self.page_index)

    def on_fitting_failed(self, failed_info: str, task: SSUTask):
        # necessary to remove it from the dict
        self.retry_tasks.pop(task.uuid)
        if len(self.retry_tasks) == 0:
            self.retry_progress_msg.close()
        self.show_error(
            self.tr("Failed to retry task, sample name={0}.\n{1}").format(
                task.sample.name, failed_info))
        self.logger.warning(
            f"Failed to retry task, sample name={task.sample.name}, distribution_type={task.distribution_type.name}, n_components={task.n_components}"
        )

    def retry_results(self, indexes, results):
        assert len(indexes) == len(results)
        if len(results) == 0:
            return
        self.retry_progress_msg.setText(
            self.tr("Tasks to be retried: {0}").format(len(results)))
        self.retry_timer.start(1)
        for index, result in zip(indexes, results):
            query = self.__reference_viewer.query_reference(result.sample)
            ref_result = None
            if query is None:
                nearby_results = self.__fitting_results[
                    index - 5:index] + self.__fitting_results[index + 1:index +
                                                              6]
                ref_result = self.__reference_viewer.find_similar(
                    result.sample, nearby_results)
            else:
                ref_result = query
            keys = ["mean", "std", "skewness"]
            # reference = [{key: comp.logarithmic_moments[key] for key in keys} for comp in ref_result.components]
            task = SSUTask(
                result.sample,
                ref_result.distribution_type,
                ref_result.n_components,
                resolver=ref_result.task.resolver,
                resolver_setting=ref_result.task.resolver_setting,
                #    reference=reference)
                initial_guess=ref_result.last_func_args)

            self.logger.debug(
                f"Retry task: sample name={task.sample.name}, distribution_type={task.distribution_type.name}, n_components={task.n_components}"
            )
            self.retry_tasks[task.uuid] = index
            self.async_worker.execute_task(task)

    def degrade_results(self):
        degrade_results = []  # type: list[SSUResult]
        degrade_indexes = []  # type: list[int]
        for i, result in enumerate(self.__fitting_results):
            for component in result.components:
                if component.fraction < 1e-3:
                    degrade_results.append(result)
                    degrade_indexes.append(i)
                    break
        self.logger.debug(
            f"Results should be degrade (have a redundant component): {[result.sample.name for result in degrade_results]}"
        )
        if len(degrade_results) == 0:
            self.show_info(
                self.tr("No fitting result was evaluated as an outlier."))
            return
        self.show_info(
            self.
            tr("The results below should be degrade (have a redundant component:\n    {0}"
               ).format(", ".join(
                   [result.sample.name for result in degrade_results])))

        self.retry_progress_msg.setText(
            self.tr("Tasks to be retried: {0}").format(len(degrade_results)))
        self.retry_timer.start(1)
        for index, result in zip(degrade_indexes, degrade_results):
            reference = []
            n_redundant = 0
            for component in result.components:
                if component.fraction < 1e-3:
                    n_redundant += 1
                else:
                    reference.append(
                        dict(mean=component.logarithmic_moments["mean"],
                             std=component.logarithmic_moments["std"],
                             skewness=component.logarithmic_moments["skewness"]
                             ))
            task = SSUTask(
                result.sample,
                result.distribution_type,
                result.n_components -
                n_redundant if result.n_components > n_redundant else 1,
                resolver=result.task.resolver,
                resolver_setting=result.task.resolver_setting,
                reference=reference)
            self.logger.debug(
                f"Retry task: sample name={task.sample.name}, distribution_type={task.distribution_type.name}, n_components={task.n_components}"
            )
            self.retry_tasks[task.uuid] = index
            self.async_worker.execute_task(task)

    def ask_deal_outliers(self, outlier_results: typing.List[SSUResult],
                          outlier_indexes: typing.List[int]):
        assert len(outlier_indexes) == len(outlier_results)
        if len(outlier_results) == 0:
            self.show_info(
                self.tr("No fitting result was evaluated as an outlier."))
        else:
            if len(outlier_results) > 100:
                self.outlier_msg.setText(
                    self.
                    tr("The fitting results have the component that its fraction is near zero:\n    {0}...(total {1} outliers)\nHow to deal with them?"
                       ).format(
                           ", ".join([
                               result.sample.name
                               for result in outlier_results[:100]
                           ]), len(outlier_results)))
            else:
                self.outlier_msg.setText(
                    self.
                    tr("The fitting results have the component that its fraction is near zero:\n    {0}\nHow to deal with them?"
                       ).format(", ".join([
                           result.sample.name for result in outlier_results
                       ])))
            res = self.outlier_msg.exec_()
            if res == QMessageBox.Discard:
                self.remove_results(outlier_indexes)
            elif res == QMessageBox.Retry:
                self.retry_results(outlier_indexes, outlier_results)
            else:
                pass

    def check_nan_and_inf(self):
        if self.n_results == 0:
            self.show_warning(self.tr("There is not any result in the list."))
            return
        outlier_results = []
        outlier_indexes = []
        for i, result in enumerate(self.__fitting_results):
            if not result.is_valid:
                outlier_results.append(result)
                outlier_indexes.append(i)
        self.logger.debug(
            f"Outlier results with the nan or inf value(s): {[result.sample.name for result in outlier_results]}"
        )
        self.ask_deal_outliers(outlier_results, outlier_indexes)

    def check_final_distances(self):
        if self.n_results == 0:
            self.show_warning(self.tr("There is not any result in the list."))
            return
        elif self.n_results < 10:
            self.show_warning(self.tr("The results in list are too less."))
            return
        distances = []
        for result in self.__fitting_results:
            distances.append(result.get_distance(self.distance_name))
        distances = np.array(distances)
        self.boxplot_chart.show_dataset([distances],
                                        xlabels=[self.distance_name],
                                        ylabel=self.tr("Distance"))
        self.boxplot_chart.show()

        # calculate the 1/4, 1/2, and 3/4 postion value to judge which result is invalid
        # 1. the mean squared errors are much higher in the results which are lack of components
        # 2. with the component number getting higher, the mean squared error will get lower and finally reach the minimum
        median = np.median(distances)
        upper_group = distances[np.greater(distances, median)]
        lower_group = distances[np.less(distances, median)]
        value_1_4 = np.median(lower_group)
        value_3_4 = np.median(upper_group)
        distance_QR = value_3_4 - value_1_4
        outlier_results = []
        outlier_indexes = []
        for i, (result,
                distance) in enumerate(zip(self.__fitting_results, distances)):
            if distance > value_3_4 + distance_QR * 1.5:
                # which error too small is not outlier
                # if distance > value_3_4 + distance_QR * 1.5 or distance < value_1_4 - distance_QR * 1.5:
                outlier_results.append(result)
                outlier_indexes.append(i)
        self.logger.debug(
            f"Outlier results with too greater distances: {[result.sample.name for result in outlier_results]}"
        )
        self.ask_deal_outliers(outlier_results, outlier_indexes)

    def check_component_moments(self, key: str):
        if self.n_results == 0:
            self.show_warning(self.tr("There is not any result in the list."))
            return
        elif self.n_results < 10:
            self.show_warning(self.tr("The results in list are too less."))
            return
        max_n_components = 0
        for result in self.__fitting_results:
            if result.n_components > max_n_components:
                max_n_components = result.n_components
        moments = []
        for i in range(max_n_components):
            moments.append([])

        for result in self.__fitting_results:
            for i, component in enumerate(result.components):
                if np.isnan(component.logarithmic_moments[key]) or np.isinf(
                        component.logarithmic_moments[key]):
                    pass
                else:
                    moments[i].append(component.logarithmic_moments[key])

        # key_trans = {"mean": self.tr("Mean"), "std": self.tr("STD"), "skewness": self.tr("Skewness"), "kurtosis": self.tr("Kurtosis")}
        key_label_trans = {
            "mean": self.tr("Mean [φ]"),
            "std": self.tr("STD [φ]"),
            "skewness": self.tr("Skewness"),
            "kurtosis": self.tr("Kurtosis")
        }
        self.boxplot_chart.show_dataset(
            moments,
            xlabels=[f"C{i+1}" for i in range(max_n_components)],
            ylabel=key_label_trans[key])
        self.boxplot_chart.show()

        outlier_dict = {}

        for i in range(max_n_components):
            stacked_moments = np.array(moments[i])
            # calculate the 1/4, 1/2, and 3/4 postion value to judge which result is invalid
            # 1. the mean squared errors are much higher in the results which are lack of components
            # 2. with the component number getting higher, the mean squared error will get lower and finally reach the minimum
            median = np.median(stacked_moments)
            upper_group = stacked_moments[np.greater(stacked_moments, median)]
            lower_group = stacked_moments[np.less(stacked_moments, median)]
            value_1_4 = np.median(lower_group)
            value_3_4 = np.median(upper_group)
            distance_QR = value_3_4 - value_1_4

            for j, result in enumerate(self.__fitting_results):
                if result.n_components > i:
                    distance = result.components[i].logarithmic_moments[key]
                    if distance > value_3_4 + distance_QR * 1.5 or distance < value_1_4 - distance_QR * 1.5:
                        outlier_dict[j] = result

        outlier_results = []
        outlier_indexes = []
        for index, result in sorted(outlier_dict.items(), key=lambda x: x[0]):
            outlier_indexes.append(index)
            outlier_results.append(result)
        self.logger.debug(
            f"Outlier results with abnormal {key} values of their components: {[result.sample.name for result in outlier_results]}"
        )
        self.ask_deal_outliers(outlier_results, outlier_indexes)

    def check_component_fractions(self):
        outlier_results = []
        outlier_indexes = []
        for i, result in enumerate(self.__fitting_results):
            for component in result.components:
                if component.fraction < 1e-3:
                    outlier_results.append(result)
                    outlier_indexes.append(i)
                    break
        self.logger.debug(
            f"Outlier results with the component that its fraction is near zero: {[result.sample.name for result in outlier_results]}"
        )
        self.ask_deal_outliers(outlier_results, outlier_indexes)

    def try_align_components(self):
        if self.n_results == 0:
            self.show_warning(self.tr("There is not any result in the list."))
            return
        elif self.n_results < 10:
            self.show_warning(self.tr("The results in list are too less."))
            return
        import matplotlib.pyplot as plt
        n_components_list = [
            result.n_components for result in self.__fitting_results
        ]
        count_dict = Counter(n_components_list)
        max_n_components = max(count_dict.keys())
        self.logger.debug(
            f"N_components: {count_dict}, Max N_components: {max_n_components}"
        )
        n_components_desc = "\n".join([
            self.tr("{0} Component(s): {1}").format(n_components, count)
            for n_components, count in count_dict.items()
        ])
        self.show_info(
            self.tr("N_components distribution of Results:\n{0}").format(
                n_components_desc))

        x = self.__fitting_results[0].classes_μm
        stacked_components = []
        for result in self.__fitting_results:
            for component in result.components:
                stacked_components.append(component.distribution)
        stacked_components = np.array(stacked_components)

        cluser = KMeans(n_clusters=max_n_components)
        flags = cluser.fit_predict(stacked_components)

        figure = plt.figure(figsize=(6, 4))
        cmap = plt.get_cmap("tab10")
        axes = figure.add_subplot(1, 1, 1)
        for flag, distribution in zip(flags, stacked_components):
            plt.plot(x, distribution, c=cmap(flag), zorder=flag)
        axes.set_xscale("log")
        axes.set_xlabel(self.tr("Grain-size [μm]"))
        axes.set_ylabel(self.tr("Frequency"))
        figure.tight_layout()
        figure.show()

        outlier_results = []
        outlier_indexes = []
        flag_index = 0
        for i, result in enumerate(self.__fitting_results):
            result_flags = set()
            for component in result.components:
                if flags[flag_index] in result_flags:
                    outlier_results.append(result)
                    outlier_indexes.append(i)
                    break
                else:
                    result_flags.add(flags[flag_index])
                flag_index += 1
        self.logger.debug(
            f"Outlier results that have two components in the same cluster: {[result.sample.name for result in outlier_results]}"
        )
        self.ask_deal_outliers(outlier_results, outlier_indexes)

    def analyse_typical_components(self):
        if self.n_results == 0:
            self.show_warning(self.tr("There is not any result in the list."))
            return
        elif self.n_results < 10:
            self.show_warning(self.tr("The results in list are too less."))
            return

        self.typical_chart.show_typical(self.__fitting_results)
        self.typical_chart.show()
コード例 #20
0
class PlotImage(QLabel):
    def __init__(self, model, FM, parent=None):
        super(PlotImage, self).__init__(parent)

        self.model = model
        self.FM = FM
        self.mw = parent

        self.setAlignment(QtCore.Qt.AlignCenter)
        self.setMouseTracking(True)

        self.rubberBand = QRubberBand(QRubberBand.Rectangle, self)
        self.bandOrigin = QtCore.QPoint()
        self.xPlotOrigin = None
        self.yPlotOrigin = None

        self.menu = QMenu(self)

    def enterEvent(self, event):
        self.setCursor(QtCore.Qt.CrossCursor)
        self.mw.coordLabel.show()

    def leaveEvent(self, event):
        self.mw.coordLabel.hide()
        self.mw.statusBar().showMessage('')

    def mousePressEvent(self, event):

        # Set rubber band absolute and relative position
        self.bandOrigin = event.pos()
        self.xPlotOrigin, self.yPlotOrigin = self.getPlotCoords(event.pos())

        # Create rubber band
        self.rubberBand.setGeometry(
            QtCore.QRect(self.bandOrigin, QtCore.QSize()))

        QLabel.mousePressEvent(self, event)

    def mouseDoubleClickEvent(self, event):

        xCenter, yCenter = self.getPlotCoords(event.pos())
        self.mw.editPlotOrigin(xCenter, yCenter, apply=True)

        QLabel.mouseDoubleClickEvent(self, event)

    def mouseMoveEvent(self, event):

        # Show Cursor position relative to plot in status bar
        xPlotPos, yPlotPos = self.getPlotCoords(event.pos())

        # Show Cell/Material ID, Name in status bar
        id, domain, domain_kind = self.getIDinfo(event)
        if id != '-1' and domain[id].name:
            domainInfo = f"{domain_kind} {id}: {domain[id].name}"
        elif id != '-1':
            domainInfo = f"{domain_kind} {id}"
        else:
            domainInfo = ""
        self.mw.statusBar().showMessage(f" {domainInfo}")

        # Update rubber band and values if mouse button held down
        if event.buttons() == QtCore.Qt.LeftButton:
            self.rubberBand.setGeometry(
                QtCore.QRect(self.bandOrigin, event.pos()).normalized())

            # Show rubber band if both dimensions > 10 pixels
            if self.rubberBand.width() > 10 and self.rubberBand.height() > 10:
                self.rubberBand.show()
            else:
                self.rubberBand.hide()

            # Update plot X Origin
            xCenter = (self.xPlotOrigin + xPlotPos) / 2
            yCenter = (self.yPlotOrigin + yPlotPos) / 2
            self.mw.editPlotOrigin(xCenter, yCenter)

            modifiers = event.modifiers()

            # Zoom out if Shift held
            if modifiers == QtCore.Qt.ShiftModifier:
                cv = self.model.currentView
                bandwidth = abs(self.bandOrigin.x() - event.pos().x())
                width = cv.width * (cv.hRes / max(bandwidth, .001))
                bandheight = abs(self.bandOrigin.y() - event.pos().y())
                height = cv.height * (cv.vRes / max(bandheight, .001))
            else:  # Zoom in
                width = max(abs(self.xPlotOrigin - xPlotPos), 0.1)
                height = max(abs(self.yPlotOrigin - yPlotPos), 0.1)

            self.mw.editWidth(width)
            self.mw.editHeight(height)

    def mouseReleaseEvent(self, event):

        if self.rubberBand.isVisible():
            self.rubberBand.hide()
            self.mw.applyChanges()
        else:
            self.mw.revertDockControls()

    def wheelEvent(self, event):

        if event.delta() and event.modifiers() == QtCore.Qt.ShiftModifier:
            numDegrees = event.delta() / 8

            if 24 < self.mw.zoom + numDegrees < 5001:
                self.mw.editZoom(self.mw.zoom + numDegrees)

    def contextMenuEvent(self, event):

        self.menu.clear()

        self.mw.undoAction.setText(f'&Undo ({len(self.model.previousViews)})')
        self.mw.redoAction.setText(
            f'&Redo ({len(self.model.subsequentViews)})')

        id, domain, domain_kind = self.getIDinfo(event)

        if id != '-1':

            # Domain ID
            domainID = self.menu.addAction(f"{domain_kind} {id}")
            domainID.setDisabled(True)

            # Domain Name (if any)
            if domain[id].name:
                domainName = self.menu.addAction(domain[id].name)
                domainName.setDisabled(True)

            self.menu.addSeparator()
            self.menu.addAction(self.mw.undoAction)
            self.menu.addAction(self.mw.redoAction)
            self.menu.addSeparator()

            colorAction = self.menu.addAction(f'Edit {domain_kind} Color...')
            colorAction.setDisabled(self.model.currentView.highlighting)
            colorAction.setToolTip(f'Edit {domain_kind} color')
            colorAction.setStatusTip(f'Edit {domain_kind} color')
            colorAction.triggered.connect(
                lambda: self.mw.editDomainColor(domain_kind, id))

            maskAction = self.menu.addAction(f'Mask {domain_kind}')
            maskAction.setCheckable(True)
            maskAction.setChecked(domain[id].masked)
            maskAction.setDisabled(not self.model.currentView.masking)
            maskAction.setToolTip(f'Toggle {domain_kind} mask')
            maskAction.setStatusTip(f'Toggle {domain_kind} mask')
            maskAction.triggered[bool].connect(
                lambda bool=bool: self.mw.toggleDomainMask(
                    bool, domain_kind, id))

            highlightAction = self.menu.addAction(f'Highlight {domain_kind}')
            highlightAction.setCheckable(True)
            highlightAction.setChecked(domain[id].highlighted)
            highlightAction.setDisabled(
                not self.model.currentView.highlighting)
            highlightAction.setToolTip(f'Toggle {domain_kind} highlight')
            highlightAction.setStatusTip(f'Toggle {domain_kind} highlight')
            highlightAction.triggered[bool].connect(
                lambda bool=bool: self.mw.toggleDomainHighlight(
                    bool, domain_kind, id))

        else:
            self.menu.addAction(self.mw.undoAction)
            self.menu.addAction(self.mw.redoAction)
            self.menu.addSeparator()
            bgColorAction = self.menu.addAction('Edit Background Color...')
            bgColorAction.setToolTip('Edit background color')
            bgColorAction.setStatusTip('Edit plot background color')
            bgColorAction.triggered.connect(
                lambda: self.mw.editBackgroundColor(apply=True))

        self.menu.addSeparator()
        self.menu.addAction(self.mw.saveImageAction)
        self.menu.addAction(self.mw.saveViewAction)
        self.menu.addAction(self.mw.openAction)
        self.menu.addSeparator()
        self.menu.addMenu(self.mw.basisMenu)
        self.menu.addMenu(self.mw.colorbyMenu)
        self.menu.addSeparator()
        self.menu.addAction(self.mw.maskingAction)
        self.menu.addAction(self.mw.highlightingAct)
        self.menu.addSeparator()
        self.menu.addAction(self.mw.dockAction)

        self.mw.maskingAction.setChecked(self.model.currentView.masking)
        self.mw.highlightingAct.setChecked(self.model.currentView.highlighting)

        if self.mw.dock.isVisible():
            self.mw.dockAction.setText('Hide &Dock')
        else:
            self.mw.dockAction.setText('Show &Dock')

        self.menu.exec_(event.globalPos())

    def getPlotCoords(self, pos):

        cv = self.model.currentView

        factor = (self.width() / cv.hRes, self.height() / cv.vRes)

        # Cursor position in pixels relative to center of plot image
        xPos = (pos.x() + 0.5) / factor[0] - cv.hRes / 2
        yPos = (-pos.y() - 0.5) / factor[1] + cv.vRes / 2

        # Curson position in plot coordinates
        xPlotCoord = (xPos / self.mw.scale[0]) + cv.origin[self.mw.xBasis]
        yPlotCoord = (yPos / self.mw.scale[1]) + cv.origin[self.mw.yBasis]

        self.mw.showCoords(xPlotCoord, yPlotCoord)

        return (xPlotCoord, yPlotCoord)

    def getIDinfo(self, event):

        cv = self.model.currentView
        factor = (self.width() / cv.hRes, self.height() / cv.vRes)
        xPos = int((event.pos().x() + .05) / factor[0])
        yPos = int((event.pos().y() + .05) / factor[1])

        if yPos < self.model.currentView.vRes \
            and xPos < self.model.currentView.hRes:

            id = f"{self.model.ids[yPos][xPos]}"
        else:
            id = '-1'

        if self.model.currentView.colorby == 'cell':
            domain = self.model.activeView.cells
            domain_kind = 'Cell'
        else:
            domain = self.model.activeView.materials
            domain_kind = 'Material'

        return id, domain, domain_kind
コード例 #21
0
    def initContextMenu(self):
        """
        return the context menu
        @return:
        @rtype: QMenu
        """
        menu = QMenu()
        menu.actionReset = QAction('Reset To Default', None)
        menu.actionLoadImage = QAction('Load New Image', None)
        menu.actionGroupSelection = QAction('Group Selection', None)
        menu.actionAdd2Group = QAction('Add to Group', None)
        # Active layer is not in a group or right clicked layer is in a group

        menu.actionUnGroup = QAction('Ungroup', None)

        # multiple selections
        menu.actionMerge = QAction('Merge Lower', None)
        # merge only adjustment layer with image layer

        # don't dup adjustment layers
        menu.actionUnselect = QAction('Unselect All', None)

        menu.actionRepositionLayer = QAction('Reposition Layer(s)', None)
        menu.actionColorMaskEnable = QAction('Color Mask', None)
        menu.actionOpacityMaskEnable = QAction('Opacity Mask', None)
        menu.actionClippingMaskEnable = QAction('Clipping Mask', None)
        menu.actionMaskDisable = QAction('Disable Mask', None)
        menu.actionMaskInvert = QAction('Invert Mask', None)
        menu.actionMaskReset = QAction('Clear Mask', None)
        menu.actionMaskCopy = QAction('Copy Mask to Clipboard', None)
        menu.actionImageCopy = QAction('Copy Image to Clipboard', None)
        menu.actionMaskPaste = QAction('Paste Mask', None)
        menu.actionImagePaste = QAction('Paste Image', None)
        menu.actionMaskDilate = QAction('Dilate Mask', None)
        menu.actionMaskErode = QAction('Erode Mask', None)
        menu.actionColorMaskEnable.setCheckable(True)
        menu.actionOpacityMaskEnable.setCheckable(True)
        menu.actionClippingMaskEnable.setCheckable(True)
        menu.actionMaskDisable.setCheckable(True)
        ####################
        # Build menu
        ###################
        # group/ungroup
        menu.addAction(menu.actionAdd2Group)
        menu.addAction(menu.actionGroupSelection)
        menu.addAction(menu.actionUnGroup)
        menu.addSeparator()
        menu.addAction(menu.actionUnselect)
        menu.addSeparator()
        menu.addAction(menu.actionRepositionLayer)
        menu.addSeparator()
        # layer
        menu.addAction(menu.actionImageCopy)
        menu.addAction(menu.actionImagePaste)
        menu.addSeparator()
        # mask
        menu.subMenuEnable = menu.addMenu('Mask...')
        menu.subMenuEnable.addAction(menu.actionColorMaskEnable)
        menu.subMenuEnable.addAction(menu.actionOpacityMaskEnable)
        menu.subMenuEnable.addAction(menu.actionClippingMaskEnable)
        menu.subMenuEnable.addAction(menu.actionMaskDisable)
        menu.addAction(menu.actionMaskInvert)
        menu.addAction(menu.actionMaskReset)
        menu.addAction(menu.actionMaskCopy)
        menu.addAction(menu.actionMaskPaste)
        menu.addAction(menu.actionMaskDilate)
        menu.addAction(menu.actionMaskErode)
        menu.addSeparator()
        # miscellaneous
        menu.addAction(menu.actionLoadImage)
        # to link actionDup with a shortcut,
        # it must be set in __init__
        menu.addAction(self.actionDup)
        menu.addAction(menu.actionMerge)
        menu.addAction(menu.actionReset)
        return menu
コード例 #22
0
 def initContextMenu(self):
     """
     Context menu initialization
     @return:
     @rtype: QMenu
     """
     menu = QMenu()
     # menu.actionReset = QAction('Reset To Default', None)
     # menu.actionLoadImage = QAction('Load New Image', None)
     # multiple selections
     menu.actionMerge = QAction('Merge Lower', None)
     # merge only adjustment layer with image layer
     menu.actionRepositionLayer = QAction('Reposition Layer(s)', None)
     menu.actionColorMaskEnable = QAction('Color', None)
     menu.actionOpacityMaskEnable = QAction('Opacity', None)
     menu.actionClippingMaskEnable = QAction('Clipping', None)
     menu.actionMaskDisable = QAction('Disabled', None)
     menu.actionMaskUndo = QAction('Undo Mask', None)
     menu.actionMaskRedo = QAction('Redo Mask', None)
     menu.actionMaskInvert = QAction('Invert Mask', None)
     menu.actionMaskReset_UM = QAction('Unmask All', None)
     menu.actionMaskReset_M = QAction('Mask All', None)
     menu.actionMaskCopy = QAction('Copy Mask to Clipboard', None)
     menu.actionImageCopy = QAction('Copy Image to Clipboard', None)
     menu.actionMaskPaste = QAction('Paste Mask', None)
     menu.actionImagePaste = QAction('Paste Image', None)
     menu.actionMaskDilate = QAction('Dilate Mask', None)
     menu.actionMaskErode = QAction('Erode Mask', None)
     menu.actionMaskSmooth = QAction('Smooth Mask', None)
     menu.actionMaskBright1 = QAction('Bright 1 Mask', None)
     menu.actionMaskBright2 = QAction('Bright 2 Mask', None)
     menu.actionMaskBright3 = QAction('Bright 3 Mask', None)
     menu.actionMaskDark1 = QAction('Dark 1 Mask', None)
     menu.actionMaskDark2 = QAction('Dark 2 Mask', None)
     menu.actionMaskDark3 = QAction('Dark 3 Mask', None)
     menu.actionMaskMid1 = QAction('Mid 1 Mask', None)
     menu.actionMaskMid2 = QAction('Mid 2 Mask', None)
     menu.actionMaskMid3 = QAction('Mid 3 Mask', None)
     menu.actionMergingFlag = QAction('Merged Layer', None)
     menu.actionMergingFlag.setCheckable(True)
     menu.actionColorMaskEnable.setCheckable(True)
     menu.actionOpacityMaskEnable.setCheckable(True)
     menu.actionClippingMaskEnable.setCheckable(True)
     menu.actionMaskDisable.setCheckable(True)
     ####################
     # Build menu
     ###################
     menu.addAction(menu.actionRepositionLayer)
     menu.addSeparator()
     # layer
     menu.addAction(menu.actionImageCopy)
     menu.addAction(menu.actionImagePaste)
     menu.addAction(menu.actionMergingFlag)
     menu.addSeparator()
     # mask
     menu.subMenuEnable = menu.addMenu('Mask...')
     menu.subMenuEnable.addAction(menu.actionColorMaskEnable)
     menu.subMenuEnable.addAction(menu.actionOpacityMaskEnable)
     menu.subMenuEnable.addAction(menu.actionClippingMaskEnable)
     menu.subMenuEnable.addAction(menu.actionMaskDisable)
     menu.addAction(menu.actionMaskUndo)
     menu.addAction(menu.actionMaskRedo)
     menu.addAction(menu.actionMaskInvert)
     menu.subMenuLum = menu.addMenu('Luminosity Mask...')
     for a in [
             menu.actionMaskBright1, menu.actionMaskBright2,
             menu.actionMaskBright3, menu.actionMaskDark1,
             menu.actionMaskDark2, menu.actionMaskDark3,
             menu.actionMaskMid1, menu.actionMaskMid2, menu.actionMaskMid3
     ]:
         menu.subMenuLum.addAction(a)
     menu.addAction(menu.actionMaskReset_UM)
     menu.addAction(menu.actionMaskReset_M)
     menu.addAction(menu.actionMaskCopy)
     menu.addAction(menu.actionMaskPaste)
     menu.addAction(menu.actionMaskDilate)
     menu.addAction(menu.actionMaskErode)
     menu.addAction(menu.actionMaskSmooth)
     menu.addSeparator()
     # miscellaneous
     # menu.addAction(menu.actionLoadImage)
     # to link actionDup with a shortcut,
     # it must be set in __init__
     menu.addAction(self.actionDup)
     menu.addAction(menu.actionMerge)
     # menu.addAction(menu.actionReset)
     return menu
コード例 #23
0
    def __init__(
        self,
        document: Optional[vp.Document] = None,
        view_mode: ViewMode = ViewMode.PREVIEW,
        show_pen_up: bool = False,
        show_points: bool = False,
        parent=None,
    ):
        super().__init__(parent)

        self._settings = QSettings()
        self._settings.beginGroup("viewer")

        self.setWindowTitle("vpype viewer")
        self.setStyleSheet("""
        QToolButton:pressed {
            background-color: rgba(0, 0, 0, 0.2);
        }
        """)

        self._viewer_widget = QtViewerWidget(parent=self)

        # setup toolbar
        self._toolbar = QToolBar()
        self._icon_size = QSize(32, 32)
        self._toolbar.setIconSize(self._icon_size)

        view_mode_grp = QActionGroup(self._toolbar)
        if _DEBUG_ENABLED:
            act = view_mode_grp.addAction("None")
            act.setCheckable(True)
            act.setChecked(view_mode == ViewMode.NONE)
            act.triggered.connect(
                functools.partial(self.set_view_mode, ViewMode.NONE))
        act = view_mode_grp.addAction("Outline Mode")
        act.setCheckable(True)
        act.setChecked(view_mode == ViewMode.OUTLINE)
        act.triggered.connect(
            functools.partial(self.set_view_mode, ViewMode.OUTLINE))
        act = view_mode_grp.addAction("Outline Mode (Colorful)")
        act.setCheckable(True)
        act.setChecked(view_mode == ViewMode.OUTLINE_COLORFUL)
        act.triggered.connect(
            functools.partial(self.set_view_mode, ViewMode.OUTLINE_COLORFUL))
        act = view_mode_grp.addAction("Preview Mode")
        act.setCheckable(True)
        act.setChecked(view_mode == ViewMode.PREVIEW)
        act.triggered.connect(
            functools.partial(self.set_view_mode, ViewMode.PREVIEW))
        self.set_view_mode(view_mode)

        # VIEW MODE
        # view modes
        view_mode_btn = QToolButton()
        view_mode_menu = QMenu(view_mode_btn)
        act = view_mode_menu.addAction("View Mode:")
        act.setEnabled(False)
        view_mode_menu.addActions(view_mode_grp.actions())
        view_mode_menu.addSeparator()
        # show pen up
        act = view_mode_menu.addAction("Show Pen-Up Trajectories")
        act.setCheckable(True)
        act.setChecked(show_pen_up)
        act.toggled.connect(self.set_show_pen_up)
        self._viewer_widget.engine.show_pen_up = show_pen_up
        # show points
        act = view_mode_menu.addAction("Show Points")
        act.setCheckable(True)
        act.setChecked(show_points)
        act.toggled.connect(self.set_show_points)
        self._viewer_widget.engine.show_points = show_points
        # preview mode options
        view_mode_menu.addSeparator()
        act = view_mode_menu.addAction("Preview Mode Options:")
        act.setEnabled(False)
        # pen width
        pen_width_menu = view_mode_menu.addMenu("Pen Width")
        act_grp = PenWidthActionGroup(0.3, parent=pen_width_menu)
        act_grp.triggered.connect(self.set_pen_width_mm)
        pen_width_menu.addActions(act_grp.actions())
        self.set_pen_width_mm(0.3)
        # pen opacity
        pen_opacity_menu = view_mode_menu.addMenu("Pen Opacity")
        act_grp = PenOpacityActionGroup(0.8, parent=pen_opacity_menu)
        act_grp.triggered.connect(self.set_pen_opacity)
        pen_opacity_menu.addActions(act_grp.actions())
        self.set_pen_opacity(0.8)
        # debug view
        if _DEBUG_ENABLED:
            act = view_mode_menu.addAction("Debug View")
            act.setCheckable(True)
            act.toggled.connect(self.set_debug)
        # rulers
        view_mode_menu.addSeparator()
        act = view_mode_menu.addAction("Show Rulers")
        act.setCheckable(True)
        val = bool(self._settings.value("show_rulers", True))
        act.setChecked(val)
        act.toggled.connect(self.set_show_rulers)
        self._viewer_widget.engine.show_rulers = val
        # units
        units_menu = view_mode_menu.addMenu("Units")
        unit_action_grp = QActionGroup(units_menu)
        unit_type = UnitType(self._settings.value("unit_type",
                                                  UnitType.METRIC))
        act = unit_action_grp.addAction("Metric")
        act.setCheckable(True)
        act.setChecked(unit_type == UnitType.METRIC)
        act.setData(UnitType.METRIC)
        act = unit_action_grp.addAction("Imperial")
        act.setCheckable(True)
        act.setChecked(unit_type == UnitType.IMPERIAL)
        act.setData(UnitType.IMPERIAL)
        act = unit_action_grp.addAction("Pixel")
        act.setCheckable(True)
        act.setChecked(unit_type == UnitType.PIXELS)
        act.setData(UnitType.PIXELS)
        unit_action_grp.triggered.connect(self.set_unit_type)
        units_menu.addActions(unit_action_grp.actions())
        self._viewer_widget.engine.unit_type = unit_type

        view_mode_btn.setMenu(view_mode_menu)
        view_mode_btn.setIcon(load_icon("eye-outline.svg"))
        view_mode_btn.setText("View")
        view_mode_btn.setPopupMode(QToolButton.InstantPopup)
        view_mode_btn.setStyleSheet(
            "QToolButton::menu-indicator { image: none; }")
        self._toolbar.addWidget(view_mode_btn)

        # LAYER VISIBILITY
        self._layer_visibility_btn = QToolButton()
        self._layer_visibility_btn.setIcon(
            load_icon("layers-triple-outline.svg"))
        self._layer_visibility_btn.setText("Layer")
        self._layer_visibility_btn.setMenu(QMenu(self._layer_visibility_btn))
        self._layer_visibility_btn.setPopupMode(QToolButton.InstantPopup)
        self._layer_visibility_btn.setStyleSheet(
            "QToolButton::menu-indicator { image: none; }")
        self._toolbar.addWidget(self._layer_visibility_btn)

        # FIT TO PAGE
        fit_act = self._toolbar.addAction(load_icon("fit-to-page-outline.svg"),
                                          "Fit")
        fit_act.triggered.connect(self._viewer_widget.engine.fit_to_viewport)

        # RULER
        # TODO: not implemented yet
        # self._toolbar.addAction(load_icon("ruler-square.svg"), "Units")

        # MOUSE COORDINATES>
        self._mouse_coord_lbl = QLabel("")
        self._mouse_coord_lbl.setMargin(6)
        self._mouse_coord_lbl.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
        self._mouse_coord_lbl.setSizePolicy(QSizePolicy.Expanding,
                                            QSizePolicy.Minimum)
        self._toolbar.addWidget(self._mouse_coord_lbl)
        # noinspection PyUnresolvedReferences
        self._viewer_widget.mouse_coords.connect(
            self.set_mouse_coords)  # type: ignore

        # setup horizontal layout for optional side widgets
        self._hlayout = QHBoxLayout()
        self._hlayout.setSpacing(0)
        self._hlayout.setMargin(0)
        self._hlayout.addWidget(self._viewer_widget)
        widget = QWidget()
        widget.setLayout(self._hlayout)

        # setup global vertical layout
        layout = QVBoxLayout()
        layout.setSpacing(0)
        layout.setMargin(0)
        layout.addWidget(self._toolbar)
        layout.addWidget(widget)
        self.setLayout(layout)

        if document is not None:
            self.set_document(document)
コード例 #24
0
    def __init__(self, timeline, event, menu_topic):
        super(TimelinePopupMenu, self).__init__()

        self.parent = timeline
        self.timeline = timeline

        if menu_topic is not None:
            self.setTitle(menu_topic)
            self._menu_topic = menu_topic
        else:
            self._menu_topic = None

        self._reset_timeline = self.addAction('Reset Timeline')

        self._play_all = self.addAction('Play All Messages')
        self._play_all.setCheckable(True)
        self._play_all.setChecked(self.timeline.play_all)

        self.addSeparator()

        self._renderers = self.timeline._timeline_frame.get_renderers()
        self._thumbnail_actions = []

        # create thumbnail menu items
        if menu_topic is None:
            submenu = self.addMenu('Thumbnails...')
            self._thumbnail_show_action = submenu.addAction('Show All')
            self._thumbnail_hide_action = submenu.addAction('Hide All')
            submenu.addSeparator()

            for topic, renderer in self._renderers:
                self._thumbnail_actions.append(submenu.addAction(topic))
                self._thumbnail_actions[-1].setCheckable(True)
                self._thumbnail_actions[-1].setChecked(
                    self.timeline._timeline_frame.is_renderer_active(topic))
        else:
            self._thumbnail_show_action = None
            self._thumbnail_hide_action = None
            for topic, renderer in self._renderers:
                if menu_topic == topic:
                    self._thumbnail_actions.append(self.addAction("Thumbnail"))
                    self._thumbnail_actions[-1].setCheckable(True)
                    self._thumbnail_actions[-1].setChecked(
                        self.timeline._timeline_frame.is_renderer_active(
                            topic))

        # create view menu items
        self._topic_actions = []
        self._type_actions = []
        if menu_topic is None:
            self._topics = self.timeline._timeline_frame.topics
            view_topics_menu = self.addMenu('View (by Topic)')
            for topic in self._topics:
                datatype = self.timeline.get_datatype(topic)

                # View... / topic
                topic_menu = QMenu(topic, self)
                viewer_types = self.timeline._timeline_frame.get_viewer_types(
                    datatype)

                # View... / topic / Viewer
                for viewer_type in viewer_types:
                    tempaction = topic_menu.addAction(viewer_type.name)
                    tempaction.setData(viewer_type)
                    self._topic_actions.append(tempaction)
                view_topics_menu.addMenu(topic_menu)

            view_type_menu = self.addMenu('View (by Type)')
            self._topics_by_type = self.timeline._timeline_frame._topics_by_datatype
            for datatype in self._topics_by_type:
                # View... / datatype
                datatype_menu = QMenu(datatype, self)
                datatype_topics = self._topics_by_type[datatype]
                viewer_types = self.timeline._timeline_frame.get_viewer_types(
                    datatype)
                for topic in [t for t in self._topics if t in datatype_topics
                              ]:  # use timeline ordering
                    topic_menu = QMenu(topic, datatype_menu)
                    # View... / datatype / topic / Viewer
                    for viewer_type in viewer_types:
                        tempaction = topic_menu.addAction(viewer_type.name)
                        tempaction.setData(viewer_type)
                        self._topic_actions.append(tempaction)
                    datatype_menu.addMenu(topic_menu)
                view_type_menu.addMenu(datatype_menu)
        else:
            view_menu = self.addMenu("View")
            datatype = self.timeline.get_datatype(menu_topic)

            viewer_types = self.timeline._timeline_frame.get_viewer_types(
                datatype)
            for viewer_type in viewer_types:
                tempaction = view_menu.addAction(viewer_type.name)
                tempaction.setData(viewer_type)
                self._topic_actions.append(tempaction)

        self.addSeparator()

        # create publish menu items
        self._publish_actions = []
        if menu_topic is None:
            submenu = self.addMenu('Publish...')

            self._publish_all = submenu.addAction('Publish All')
            self._publish_none = submenu.addAction('Publish None')

            submenu.addSeparator()

            for topic in self._topics:
                self._publish_actions.append(submenu.addAction(topic))
                self._publish_actions[-1].setCheckable(True)
                self._publish_actions[-1].setChecked(
                    self.timeline.is_publishing(topic))
        else:
            self._publish_actions.append(self.addAction("Publish"))
            self._publish_actions[-1].setCheckable(True)
            self._publish_actions[-1].setChecked(
                self.timeline.is_publishing(menu_topic))
            self._publish_all = None
            self._publish_none = None

        action = self.exec_(event.globalPos())
        if action is not None and action != 0:
            self.process(action)
コード例 #25
0
class PlotImage(FigureCanvas):
    def __init__(self, model, parent, main_window):

        self.figure = Figure(dpi=main_window.logicalDpiX())
        super().__init__(self.figure)

        FigureCanvas.setSizePolicy(self, QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)

        FigureCanvas.updateGeometry(self)
        self.model = model
        self.main_window = main_window
        self.parent = parent

        self.rubber_band = QRubberBand(QRubberBand.Rectangle, self)
        self.band_origin = QtCore.QPoint()
        self.x_plot_origin = None
        self.y_plot_origin = None

        self.colorbar = None
        self.data_indicator = None
        self.tally_data_indicator = None
        self.image = None

        self.menu = QMenu(self)

    def enterEvent(self, event):
        self.setCursor(QtCore.Qt.CrossCursor)
        self.main_window.coord_label.show()

    def leaveEvent(self, event):
        self.main_window.coord_label.hide()
        self.main_window.statusBar().showMessage("")

    def mousePressEvent(self, event):
        self.main_window.coord_label.hide()
        position = event.pos()
        # Set rubber band absolute and relative position
        self.band_origin = position
        self.x_plot_origin, self.y_plot_origin = self.getPlotCoords(position)

        # Create rubber band
        self.rubber_band.setGeometry(
            QtCore.QRect(self.band_origin, QtCore.QSize()))

    def getPlotCoords(self, pos):
        x, y = self.mouseEventCoords(pos)

        # get the normalized axis coordinates from the event display units
        transform = self.ax.transAxes.inverted()
        xPlotCoord, yPlotCoord = transform.transform((x, y))
        # flip the y-axis (its zero is in the upper left)

        # scale axes using the plot extents
        xPlotCoord = self.ax.dataLim.x0 + xPlotCoord * self.ax.dataLim.width
        yPlotCoord = self.ax.dataLim.y0 + yPlotCoord * self.ax.dataLim.height

        # set coordinate label if pointer is in the axes
        if self.parent.underMouse():
            self.main_window.coord_label.show()
            self.main_window.showCoords(xPlotCoord, yPlotCoord)
        else:
            self.main_window.coord_label.hide()

        return (xPlotCoord, yPlotCoord)

    def _resize(self):
        z = self.main_window.zoom / 100.0
        # manage scroll bars
        if z <= 1.0:
            self.parent.verticalScrollBar().hide()
            self.parent.horizontalScrollBar().hide()
            self.parent.cornerWidget().hide()
            self.parent.verticalScrollBar().setEnabled(False)
            self.parent.horizontalScrollBar().setEnabled(False)
        else:
            self.parent.verticalScrollBar().show()
            self.parent.horizontalScrollBar().show()
            self.parent.cornerWidget().show()
            self.parent.verticalScrollBar().setEnabled(True)
            self.parent.horizontalScrollBar().setEnabled(True)

        # resize plot
        self.resize(self.parent.width() * z, self.parent.height() * z)

    def getDataIndices(self, event):
        cv = self.model.currentView

        x, y = self.mouseEventCoords(event.pos())

        # get origin in axes coordinates
        x0, y0 = self.ax.transAxes.transform((0.0, 0.0))

        # get the extents of the axes box in axes coordinates
        bbox = self.ax.get_window_extent().transformed(
            self.figure.dpi_scale_trans.inverted())
        # get dimensions and scale using dpi
        width, height = bbox.width, bbox.height
        width *= self.figure.dpi
        height *= self.figure.dpi

        # use factor to get proper x,y position in pixels
        factor = (width / cv.h_res, height / cv.v_res)
        xPos = int((x - x0 + 0.01) / factor[0])
        # flip y-axis
        yPos = cv.v_res - int((y - y0 + 0.01) / factor[1])

        return xPos, yPos

    def getTallyIndices(self, event):

        xPos, yPos = self.getPlotCoords(event.pos())

        ext = self.model.tally_extents

        x0 = ext[0]
        y0 = ext[2]

        v_res, h_res = self.model.tally_data.shape

        dx = (ext[1] - ext[0]) / h_res
        dy = (ext[3] - ext[2]) / v_res

        i = int((xPos - x0) // dx)
        j = v_res - int((yPos - y0) // dy) - 1

        return i, j

    def getTallyInfo(self, event):
        cv = self.model.currentView

        xPos, yPos = self.getTallyIndices(event)

        if self.model.tally_data is None:
            return -1, None

        if not cv.selectedTally or not cv.tallyDataVisible:
            return -1, None

        # don't look up mesh filter data (for now)
        tally = self.model.statepoint.tallies[cv.selectedTally]

        # check that the position is in the axes view
        v_res, h_res = self.model.tally_data.shape
        if 0 <= yPos < v_res and 0 <= xPos < h_res:
            value = self.model.tally_data[yPos][xPos]
        else:
            value = None

        return cv.selectedTally, value

    def getIDinfo(self, event):

        xPos, yPos = self.getDataIndices(event)

        # check that the position is in the axes view
        if 0 <= yPos < self.model.currentView.v_res \
           and 0 <= xPos and xPos < self.model.currentView.h_res:
            id = self.model.ids[yPos][xPos]
            temp = "{:g}".format(self.model.properties[yPos][xPos][0])
            density = "{:g}".format(self.model.properties[yPos][xPos][1])
        else:
            id = _NOT_FOUND
            density = str(_NOT_FOUND)
            temp = str(_NOT_FOUND)

        if self.model.currentView.colorby == 'cell':
            domain = self.model.activeView.cells
            domain_kind = 'Cell'
        elif self.model.currentView.colorby == 'temperature':
            domain = self.model.activeView.materials
            domain_kind = 'Temperature'
        elif self.model.currentView.colorby == 'density':
            domain = self.model.activeView.materials
            domain_kind = 'Density'
        else:
            domain = self.model.activeView.materials
            domain_kind = 'Material'

        properties = {'density': density, 'temperature': temp}

        return id, properties, domain, domain_kind

    def mouseDoubleClickEvent(self, event):
        xCenter, yCenter = self.getPlotCoords(event.pos())
        self.main_window.editPlotOrigin(xCenter, yCenter, apply=True)

    def mouseMoveEvent(self, event):
        cv = self.model.currentView
        # Show Cursor position relative to plot in status bar
        xPlotPos, yPlotPos = self.getPlotCoords(event.pos())

        # Show Cell/Material ID, Name in status bar
        id, properties, domain, domain_kind = self.getIDinfo(event)

        domainInfo = ""
        tallyInfo = ""

        if self.parent.underMouse():

            if domain_kind.lower() in _MODEL_PROPERTIES:
                line_val = float(properties[domain_kind.lower()])
                line_val = max(line_val, 0.0)
                self.updateDataIndicatorValue(line_val)
                domain_kind = 'Material'

            temperature = properties['temperature']
            density = properties['density']

            if id == _VOID_REGION:
                domainInfo = ("VOID")
            elif id == _OVERLAP:
                domainInfo = ("OVERLAP")
            elif id != _NOT_FOUND and domain[id].name:
                domainInfo = ("{} {}: \"{}\"\t Density: {} g/cc\t"
                              "Temperature: {} K".format(
                                  domain_kind, id, domain[id].name, density,
                                  temperature))
            elif id != _NOT_FOUND:
                domainInfo = ("{} {}\t Density: {} g/cc\t"
                              "Temperature: {} K".format(
                                  domain_kind, id, density, temperature))
            else:
                domainInfo = ""

            if self.model.tally_data is not None:
                tid, value = self.getTallyInfo(event)
                if value is not None and value != np.nan:
                    self.updateTallyDataIndicatorValue(value)
                    tallyInfo = "Tally {} {}: {:.5E}".format(
                        tid, cv.tallyValue, value)
                else:
                    self.updateTallyDataIndicatorValue(0.0)
        else:
            self.updateTallyDataIndicatorValue(0.0)
            self.updateDataIndicatorValue(0.0)

        if domainInfo:
            self.main_window.statusBar().showMessage(" " + domainInfo +
                                                     "      " + tallyInfo)
        else:
            self.main_window.statusBar().showMessage(" " + tallyInfo)

        # Update rubber band and values if mouse button held down
        if event.buttons() == QtCore.Qt.LeftButton:
            self.rubber_band.setGeometry(
                QtCore.QRect(self.band_origin, event.pos()).normalized())

            # Show rubber band if both dimensions > 10 pixels
            if self.rubber_band.width() > 10 and self.rubber_band.height(
            ) > 10:
                self.rubber_band.show()
            else:
                self.rubber_band.hide()

            # Update plot X Origin
            xCenter = (self.x_plot_origin + xPlotPos) / 2
            yCenter = (self.y_plot_origin + yPlotPos) / 2
            self.main_window.editPlotOrigin(xCenter, yCenter)

            modifiers = event.modifiers()

            # Zoom out if Shift held
            if modifiers == QtCore.Qt.ShiftModifier:
                cv = self.model.currentView
                bandwidth = abs(self.band_origin.x() - event.pos().x())
                width = cv.width * (cv.h_res / max(bandwidth, .001))
                bandheight = abs(self.band_origin.y() - event.pos().y())
                height = cv.height * (cv.v_res / max(bandheight, .001))
            # Zoom in
            else:
                width = max(abs(self.x_plot_origin - xPlotPos), 0.1)
                height = max(abs(self.y_plot_origin - yPlotPos), 0.1)

            self.main_window.editWidth(width)
            self.main_window.editHeight(height)

    def mouseReleaseEvent(self, event):

        if self.rubber_band.isVisible():
            self.rubber_band.hide()
            self.main_window.applyChanges()
        else:
            self.main_window.revertDockControls()

    def wheelEvent(self, event):

        if event.delta() and event.modifiers() == QtCore.Qt.ShiftModifier:
            numDegrees = event.delta() / 8

            if 24 < self.main_window.zoom + numDegrees < 5001:
                self.main_window.editZoom(self.main_window.zoom + numDegrees)

    def contextMenuEvent(self, event):

        self.menu.clear()

        self.main_window.undoAction.setText('&Undo ({})'.format(
            len(self.model.previousViews)))
        self.main_window.redoAction.setText('&Redo ({})'.format(
            len(self.model.subsequentViews)))

        id, properties, domain, domain_kind = self.getIDinfo(event)

        cv = self.model.currentView

        # always provide undo option
        self.menu.addSeparator()
        self.menu.addAction(self.main_window.undoAction)
        self.menu.addAction(self.main_window.redoAction)
        self.menu.addSeparator()

        if int(id) not in (_NOT_FOUND, _OVERLAP) and \
           cv.colorby not in _MODEL_PROPERTIES:

            # Domain ID
            if domain[id].name:
                domainID = self.menu.addAction("{} {}: \"{}\"".format(
                    domain_kind, id, domain[id].name))
            else:
                domainID = self.menu.addAction("{} {}".format(domain_kind, id))

            self.menu.addSeparator()

            colorAction = self.menu.addAction(
                'Edit {} Color...'.format(domain_kind))
            colorAction.setDisabled(cv.highlighting)
            colorAction.setToolTip('Edit {} color'.format(domain_kind))
            colorAction.setStatusTip('Edit {} color'.format(domain_kind))
            domain_color_connector = partial(self.main_window.editDomainColor,
                                             domain_kind, id)
            colorAction.triggered.connect(domain_color_connector)

            maskAction = self.menu.addAction('Mask {}'.format(domain_kind))
            maskAction.setCheckable(True)
            maskAction.setChecked(domain[id].masked)
            maskAction.setDisabled(not cv.masking)
            maskAction.setToolTip('Toggle {} mask'.format(domain_kind))
            maskAction.setStatusTip('Toggle {} mask'.format(domain_kind))
            mask_connector = partial(self.main_window.toggleDomainMask,
                                     kind=domain_kind,
                                     id=id)
            maskAction.toggled.connect(mask_connector)

            highlightAction = self.menu.addAction(
                'Highlight {}'.format(domain_kind))
            highlightAction.setCheckable(True)
            highlightAction.setChecked(domain[id].highlight)
            highlightAction.setDisabled(not cv.highlighting)
            highlightAction.setToolTip(
                'Toggle {} highlight'.format(domain_kind))
            highlightAction.setStatusTip(
                'Toggle {} highlight'.format(domain_kind))
            highlight_connector = partial(
                self.main_window.toggleDomainHighlight,
                kind=domain_kind,
                id=id)
            highlightAction.toggled.connect(highlight_connector)

        else:
            self.menu.addAction(self.main_window.undoAction)
            self.menu.addAction(self.main_window.redoAction)

            if cv.colorby not in _MODEL_PROPERTIES:
                self.menu.addSeparator()
                if int(id) == _NOT_FOUND:
                    bgColorAction = self.menu.addAction(
                        'Edit Background Color...')
                    bgColorAction.setToolTip('Edit background color')
                    bgColorAction.setStatusTip('Edit plot background color')
                    connector = partial(self.main_window.editBackgroundColor,
                                        apply=True)
                    bgColorAction.triggered.connect(connector)
                elif int(id) == _OVERLAP:
                    olapColorAction = self.menu.addAction(
                        'Edit Overlap Color...')
                    olapColorAction.setToolTip('Edit overlap color')
                    olapColorAction.setStatusTip('Edit plot overlap color')
                    connector = partial(self.main_window.editOverlapColor,
                                        apply=True)
                    olapColorAction.triggered.connect(connector)

        self.menu.addSeparator()
        self.menu.addAction(self.main_window.saveImageAction)
        self.menu.addAction(self.main_window.saveViewAction)
        self.menu.addAction(self.main_window.openAction)
        self.menu.addSeparator()
        self.menu.addMenu(self.main_window.basisMenu)
        self.menu.addMenu(self.main_window.colorbyMenu)
        self.menu.addSeparator()
        if domain_kind.lower() not in ('density', 'temperature'):
            self.menu.addAction(self.main_window.maskingAction)
            self.menu.addAction(self.main_window.highlightingAct)
            self.menu.addAction(self.main_window.overlapAct)
            self.menu.addSeparator()
        self.menu.addAction(self.main_window.dockAction)

        self.main_window.maskingAction.setChecked(cv.masking)
        self.main_window.highlightingAct.setChecked(cv.highlighting)
        self.main_window.overlapAct.setChecked(cv.color_overlaps)

        if self.main_window.dock.isVisible():
            self.main_window.dockAction.setText('Hide &Dock')
        else:
            self.main_window.dockAction.setText('Show &Dock')

        self.menu.exec_(event.globalPos())

    def generatePixmap(self, update=False):
        self.model.generatePlot()
        if update:
            self.updatePixmap()

    def updatePixmap(self):

        # clear out figure
        self.figure.clear()

        cv = self.model.currentView
        # set figure bg color to match window
        window_bg = self.parent.palette().color(QtGui.QPalette.Background)
        self.figure.patch.set_facecolor(rgb_normalize(window_bg.getRgb()))

        # set data extents for automatic reporting of pointer location
        # in model units
        data_bounds = [
            cv.origin[self.main_window.xBasis] - cv.width / 2.,
            cv.origin[self.main_window.xBasis] + cv.width / 2.,
            cv.origin[self.main_window.yBasis] - cv.height / 2.,
            cv.origin[self.main_window.yBasis] + cv.height / 2.
        ]

        # make sure we have a domain image to load
        if not hasattr(self.model, 'image'):
            self.model.generatePlot()

        ### DRAW DOMAIN IMAGE ###

        # still generate the domain image if the geometric
        # plot isn't visible so mouse-over info can still
        # be shown
        alpha = cv.domainAlpha if cv.domainVisible else 0.0
        if cv.colorby in ('material', 'cell'):
            self.image = self.figure.subplots().imshow(self.model.image,
                                                       extent=data_bounds,
                                                       alpha=alpha)
        else:
            cmap = cv.colormaps[cv.colorby]
            if cv.colorby == 'temperature':
                idx = 0
                cmap_label = "Temperature (K)"
            else:
                idx = 1
                cmap_label = "Density (g/cc)"

            norm = SymLogNorm(1E-10) if cv.color_scale_log[
                cv.colorby] else None

            data = self.model.properties[:, :, idx]
            self.image = self.figure.subplots().imshow(data,
                                                       cmap=cmap,
                                                       norm=norm,
                                                       extent=data_bounds,
                                                       alpha=cv.domainAlpha)

            # add colorbar
            self.colorbar = self.figure.colorbar(self.image, anchor=(1.0, 0.0))
            self.colorbar.set_label(cmap_label, rotation=-90, labelpad=15)
            # draw line on colorbar
            dl = self.colorbar.ax.dataLim.get_points()
            self.data_indicator = mlines.Line2D(dl[:][0], [0.0, 0.0],
                                                linewidth=3.,
                                                color='blue',
                                                clip_on=True)
            self.colorbar.ax.add_line(self.data_indicator)
            self.colorbar.ax.margins(0.0, 0.0)
            self.updateDataIndicatorVisibility()
            self.updateColorMinMax(cv.colorby)

        self.ax = self.figure.axes[0]
        self.ax.margins(0.0, 0.0)

        # set axis labels
        axis_label_str = "{} (cm)"
        self.ax.set_xlabel(axis_label_str.format(cv.basis[0]))
        self.ax.set_ylabel(axis_label_str.format(cv.basis[1]))

        # generate tally image
        image_data, extents, data_min, data_max, units = self.model.create_tally_image(
        )

        ### DRAW TALLY IMAGE ###

        # draw tally image
        if image_data is not None:

            if not cv.tallyDataUserMinMax:
                cv.tallyDataMin = data_min
                cv.tallyDataMax = data_max
            else:
                data_min = cv.tallyDataMin
                data_max = cv.tallyDataMax

            # always mask out negative values
            image_mask = image_data < 0.0

            if cv.clipTallyData:
                image_mask |= image_data < data_min
                image_mask |= image_data > data_max

            if cv.tallyMaskZeroValues:
                image_mask |= image_data == 0.0

            # mask out invalid values
            image_data = np.ma.masked_where(image_mask, image_data)

            if extents is None:
                extents = data_bounds

            self.model.tally_data = image_data
            self.model.tally_extents = extents if extents is not None else data_bounds

            norm = SymLogNorm(1E-30) if cv.tallyDataLogScale else None

            if cv.tallyContours:
                # parse the levels line
                levels = self.parseContoursLine(cv.tallyContourLevels)
                self.tally_image = self.ax.contour(image_data,
                                                   origin='image',
                                                   levels=levels,
                                                   alpha=cv.tallyDataAlpha,
                                                   cmap=cv.tallyDataColormap,
                                                   norm=norm,
                                                   extent=extents)

            else:
                self.tally_image = self.ax.imshow(image_data,
                                                  alpha=cv.tallyDataAlpha,
                                                  cmap=cv.tallyDataColormap,
                                                  norm=norm,
                                                  extent=extents)
            # add colorbar
            self.tally_colorbar = self.figure.colorbar(self.tally_image,
                                                       anchor=(1.0, 0.0))

            if cv.tallyContours:
                fmt = "%.2E"
                self.ax.clabel(self.tally_image,
                               self.tally_image.levels,
                               inline=True,
                               fmt=fmt)

            # draw line on colorbar
            self.tally_data_indicator = mlines.Line2D([0.0, 1.0], [0.0, 0.0],
                                                      linewidth=3.,
                                                      color='blue',
                                                      clip_on=True)
            self.tally_colorbar.ax.add_line(self.tally_data_indicator)
            self.tally_colorbar.ax.margins(0.0, 0.0)

            self.tally_data_indicator.set_visible(cv.tallyDataIndicator)

            self.main_window.updateTallyMinMax()

            self.tally_colorbar.mappable.set_clim(data_min, data_max)
            self.tally_colorbar.set_label(units, rotation=-90, labelpad=15)

        # annotate outlines
        self.add_outlines()

        # always make sure the data bounds are set correctly
        self.ax.set_xbound(data_bounds[0], data_bounds[1])
        self.ax.set_ybound(data_bounds[2], data_bounds[3])
        self.ax.dataLim.x0 = data_bounds[0]
        self.ax.dataLim.x1 = data_bounds[1]
        self.ax.dataLim.y0 = data_bounds[2]
        self.ax.dataLim.y1 = data_bounds[3]

        self.draw()
        return "Done"

    def add_outlines(self):
        cv = self.model.currentView
        # draw outlines as isocontours
        if cv.outlines:
            # set data extents for automatic reporting of pointer location
            data_bounds = [
                cv.origin[self.main_window.xBasis] - cv.width / 2.,
                cv.origin[self.main_window.xBasis] + cv.width / 2.,
                cv.origin[self.main_window.yBasis] - cv.height / 2.,
                cv.origin[self.main_window.yBasis] + cv.height / 2.
            ]
            levels = np.unique(self.model.ids)
            self.contours = self.ax.contour(self.model.ids,
                                            origin='upper',
                                            colors='k',
                                            linestyles='solid',
                                            levels=levels,
                                            extent=data_bounds)

    @staticmethod
    def parseContoursLine(line):
        # if there are any commas in the line, treat as level values
        line = line.strip()
        if ',' in line:
            return [float(val) for val in line.split(",") if val != '']
        else:
            return int(line)

    def updateColorbarScale(self):
        self.updatePixmap()

    def updateTallyDataIndicatorValue(self, y_val):
        cv = self.model.currentView

        if not cv.tallyDataVisible or not cv.tallyDataIndicator:
            return

        if self.tally_data_indicator is not None:
            data = self.tally_data_indicator.get_data()
            # use norm to get axis value if log scale
            if cv.tallyDataLogScale:
                y_val = self.tally_image.norm(y_val)
            self.tally_data_indicator.set_data([data[0], [y_val, y_val]])
            dl_color = invert_rgb(self.tally_image.get_cmap()(y_val), True)
            self.tally_data_indicator.set_c(dl_color)
            self.draw()

    def updateDataIndicatorValue(self, y_val):
        cv = self.model.currentView

        if cv.colorby not in _MODEL_PROPERTIES or \
           not cv.data_indicator_enabled[cv.colorby]:
            return

        if self.data_indicator:
            data = self.data_indicator.get_data()
            # use norm to get axis value if log scale
            if cv.color_scale_log[cv.colorby]:
                y_val = self.image.norm(y_val)
            self.data_indicator.set_data([data[0], [y_val, y_val]])
            dl_color = invert_rgb(self.image.get_cmap()(y_val), True)
            self.data_indicator.set_c(dl_color)
            self.draw()

    def updateDataIndicatorVisibility(self):
        cv = self.model.currentView
        if self.data_indicator and cv.colorby in _MODEL_PROPERTIES:
            val = cv.data_indicator_enabled[cv.colorby]
            self.data_indicator.set_visible(val)
            self.draw()

    def updateColorMap(self, colormap_name, property_type):
        if self.colorbar and property_type == self.model.activeView.colorby:
            self.image.set_cmap(colormap_name)
            self.colorbar.draw_all()
            self.draw()

    def updateColorMinMax(self, property_type):
        av = self.model.activeView
        if self.colorbar and property_type == av.colorby:
            clim = av.getColorLimits(property_type)
            self.colorbar.mappable.set_clim(*clim)
            self.data_indicator.set_data(clim[:2], (0.0, 0.0))
            self.colorbar.draw_all()
            self.draw()
コード例 #26
0
class PivotTableView(CopyPasteTableView):
    """Custom QTableView class with pivot capabilities.
    """

    _REMOVE_OBJECT = "Remove selected objects"
    _REMOVE_RELATIONSHIP = "Remove selected relationships"
    _REMOVE_PARAMETER = "Remove selected parameter definitions"

    def __init__(self, parent=None):
        """Initialize the class."""
        super().__init__(parent)
        self._data_store_form = None
        self._menu = QMenu(self)
        self._selected_value_indexes = list()
        self._selected_entity_indexes = list()
        self._selected_parameter_indexes = list()
        self.open_in_editor_action = None
        self.plot_action = None
        self._plot_in_window_menu = None
        self.remove_values_action = None
        self.remove_objects_action = None
        self.remove_relationships_action = None
        self.remove_parameters_action = None

    @property
    def source_model(self):
        return self.model().sourceModel()

    @property
    def db_mngr(self):
        return self.source_model.db_mngr

    @property
    def db_map(self):
        return self._data_store_form.db_map

    def connect_data_store_form(self, data_store_form):
        self._data_store_form = data_store_form
        self.create_context_menu()
        h_header = PivotTableHeaderView(Qt.Horizontal, "columns", self)
        h_header.setContextMenuPolicy(Qt.DefaultContextMenu)
        h_header.setResizeContentsPrecision(data_store_form.visible_rows)
        v_header = PivotTableHeaderView(Qt.Vertical, "rows", self)
        v_header.setContextMenuPolicy(Qt.NoContextMenu)
        v_header.setDefaultSectionSize(data_store_form.default_row_height)
        self.setHorizontalHeader(h_header)
        self.setVerticalHeader(v_header)

    def create_context_menu(self):
        self.open_in_editor_action = self._menu.addAction(
            "Open in editor...", self.open_in_editor)
        self._menu.addSeparator()
        self.plot_action = self._menu.addAction("Plot", self.plot)
        self._plot_in_window_menu = self._menu.addMenu("Plot in window")
        self._plot_in_window_menu.triggered.connect(self._plot_in_window)
        self._menu.addSeparator()
        self.remove_values_action = self._menu.addAction(
            "Remove selected parameter values", self.remove_values)
        self.remove_objects_action = self._menu.addAction(
            self._REMOVE_OBJECT, self.remove_objects)
        self.remove_relationships_action = self._menu.addAction(
            self._REMOVE_RELATIONSHIP, self.remove_relationships)
        self.remove_parameters_action = self._menu.addAction(
            self._REMOVE_PARAMETER, self.remove_parameters)

    def remove_selected(self):
        self._find_selected_indexes()
        self.remove_values()
        self.remove_relationships()
        self.remove_objects()
        self.remove_parameters()

    def remove_values(self):
        row_mask = set()
        column_mask = set()
        for index in self._selected_value_indexes:
            row, column = self.source_model.map_to_pivot(index)
            row_mask.add(row)
            column_mask.add(column)
        data = self.source_model.model.get_pivoted_data(row_mask, column_mask)
        ids = {item for row in data for item in row if item is not None}
        parameter_values = [
            self.db_mngr.get_item(self.db_map, "parameter value", id_)
            for id_ in ids
        ]
        db_map_typed_data = {
            self.db_map: {
                "parameter value": parameter_values
            }
        }
        self.db_mngr.remove_items(db_map_typed_data)

    def remove_objects(self):
        ids = {
            self.source_model._header_id(index)
            for index in self._selected_entity_indexes
        }
        objects = [
            self.db_mngr.get_item(self.db_map, "object", id_) for id_ in ids
        ]
        db_map_typed_data = {self.db_map: {"object": objects}}
        self.db_mngr.remove_items(db_map_typed_data)

    def remove_relationships(self):
        if self.model().sourceModel().item_type != "relationship":
            return
        rels_by_object_ids = {
            rel["object_id_list"]: rel
            for rel in self._data_store_form._get_entities()
        }
        relationships = []
        for index in self._selected_entity_indexes:
            object_ids, _ = self.source_model.object_and_parameter_ids(index)
            object_ids = ",".join([str(id_) for id_ in object_ids])
            relationships.append(rels_by_object_ids[object_ids])
        db_map_typed_data = {self.db_map: {"relationship": relationships}}
        self.db_mngr.remove_items(db_map_typed_data)

    def remove_parameters(self):
        ids = {
            self.source_model._header_id(index)
            for index in self._selected_parameter_indexes
        }
        parameters = [
            self.db_mngr.get_item(self.db_map, "parameter definition", id_)
            for id_ in ids
        ]
        db_map_typed_data = {self.db_map: {"parameter definition": parameters}}
        self.db_mngr.remove_items(db_map_typed_data)

    def open_in_editor(self):
        """Opens the parameter value editor for the first selected cell."""
        index = self._selected_value_indexes[0]
        self._data_store_form.show_parameter_value_editor(index)

    def plot(self):
        """Plots the selected cells in the pivot table."""
        selected_indexes = self.selectedIndexes()
        hints = PivotTablePlottingHints()
        try:
            plot_window = plot_selection(self.model(), selected_indexes, hints)
        except PlottingError as error:
            report_plotting_failure(error, self)
            return
        plotted_column_names = {
            hints.column_label(self.model(), index.column())
            for index in selected_indexes
            if hints.is_index_in_data(self.model(), index)
        }
        plot_window.use_as_window(self.parentWidget(),
                                  ", ".join(plotted_column_names))
        plot_window.show()

    def contextMenuEvent(self, event):
        """Shows context menu.

        Args:
            event (QContextMenuEvent)
        """
        self._find_selected_indexes()
        self._update_actions_availability()
        self._update_actions_text()
        pos = event.globalPos()
        self._menu.move(pos)
        _prepare_plot_in_window_menu(self._plot_in_window_menu)
        self._menu.show()

    def _find_selected_indexes(self):
        indexes = [
            self.model().mapToSource(ind) for ind in self.selectedIndexes()
        ]
        self._selected_value_indexes = list()
        self._selected_entity_indexes = list()
        self._selected_parameter_indexes = list()
        for index in indexes:
            if self.source_model.index_in_data(index):
                self._selected_value_indexes.append(index)
            elif self.source_model.index_in_headers(index):
                top_left_id = self.source_model._top_left_id(index)
                header_type = self.source_model.top_left_headers[
                    top_left_id].header_type
                if header_type == "parameter":
                    self._selected_parameter_indexes.append(index)
                elif header_type == "object":
                    self._selected_entity_indexes.append(index)

    def _update_actions_availability(self):
        self.open_in_editor_action.setEnabled(
            len(self._selected_value_indexes) == 1)
        self.plot_action.setEnabled(len(self._selected_value_indexes) > 0)
        self.remove_values_action.setEnabled(bool(
            self._selected_value_indexes))
        self.remove_objects_action.setEnabled(
            bool(self._selected_entity_indexes))
        self.remove_relationships_action.setEnabled(
            bool(self._selected_entity_indexes)
            and self.model().sourceModel().item_type != "relationship")
        self.remove_parameters_action.setEnabled(
            bool(self._selected_parameter_indexes))

    def _update_actions_text(self):
        self.remove_objects_action.setText(self._REMOVE_OBJECT)
        self.remove_relationships_action.setText(self._REMOVE_RELATIONSHIP)
        self.remove_parameters_action.setText(self._REMOVE_PARAMETER)
        if len(self._selected_entity_indexes) == 1:
            index = self._selected_entity_indexes[0]
            object_name = self.source_model.header_name(index)
            self.remove_objects_action.setText(
                "Remove object: {}".format(object_name))
            if self.remove_relationships_action.isEnabled():
                object_names, _ = self.source_model.object_and_parameter_names(
                    index)
                relationship_name = self.db_mngr._GROUP_SEP.join(object_names)
                self.remove_relationships_action.setText(
                    "Remove relationship: {}".format(relationship_name))
        if len(self._selected_parameter_indexes) == 1:
            index = self._selected_parameter_indexes[0]
            parameter_name = self.source_model.header_name(index)
            self.remove_parameters_action.setText(
                "Remove parameter definition: {}".format(parameter_name))

    @Slot("QAction")
    def _plot_in_window(self, action):
        window_id = action.text()
        plot_window = PlotWidget.plot_windows.get(window_id)
        if plot_window is None:
            self.plot()
            return
        selected_indexes = self.selectedIndexes()
        hints = PivotTablePlottingHints()
        try:
            plot_selection(self.model(), selected_indexes, hints, plot_window)
            plot_window.raise_()
        except PlottingError as error:
            report_plotting_failure(error, self)
コード例 #27
0
class AppMenuBar(QMenuBar):
    """ This code is for the menu bar at the top of the main window. File, help, etc. """
    def __init__(self,
                 parent=None,
                 lang: LangEnum = LangEnum.ENG,
                 log_handlers: [StreamHandler] = None):
        self._logger = getLogger(__name__)
        if log_handlers:
            for h in log_handlers:
                self._logger.addHandler(h)
        self._logger.debug("Initializing")
        super().__init__(parent)
        self.setGeometry(QRect(0, 0, 840, 22))

        self._file_menu = QMenu(self)

        self._open_last_save_dir_action = QAction(self)

        self._exit_action = QAction(self)

        self._settings_menu = QMenu(self)

        self._cam_list_menu = QMenu(self)
        self._settings_menu.addMenu(self._cam_list_menu)
        self._use_cams_action = QAction(self)
        self._use_cams_action.setCheckable(True)
        self._cam_list_menu.addAction(self._use_cams_action)
        """ Display layout options """
        self._subwindow_layout_actions = []
        self._subwindow_layout_menu = QMenu(self)

        self._horiz_action = QAction(self)
        self._subwindow_layout_actions.append(self._horiz_action)
        self._horiz_action.triggered.connect(self._horiz_clicked)

        self._vert_action = QAction(self)
        self._subwindow_layout_actions.append(self._vert_action)
        self._vert_action.triggered.connect(self._vert_clicked)

        self._tiled_action = QAction(self)
        self._subwindow_layout_actions.append(self._tiled_action)
        self._tiled_action.triggered.connect(self._tiled_clicked)

        self._cascade_action = QAction(self)
        self._subwindow_layout_actions.append(self._cascade_action)
        self._cascade_action.triggered.connect(self._cascade_clicked)
        """ Language options """
        self._lang_actions = []
        self._language_menu = QMenu(self)

        # English
        self._english_action = QAction(self)
        self._lang_actions.append(self._english_action)
        self._english_action.setCheckable(True)
        self._english_action.triggered.connect(self._eng_clicked)

        # Dutch
        self._dutch_action = QAction(self)
        self._lang_actions.append(self._dutch_action)
        self._dutch_action.setCheckable(True)
        self._dutch_action.triggered.connect(self._dut_clicked)

        # French
        self._french_action = QAction(self)
        self._lang_actions.append(self._french_action)
        self._french_action.setCheckable(True)
        self._french_action.triggered.connect(self._fre_clicked)

        # German
        self._german_action = QAction(self)
        self._lang_actions.append(self._german_action)
        self._german_action.setCheckable(True)
        self._german_action.triggered.connect(self._ger_clicked)

        # Spanish
        self._spanish_action = QAction(self)
        self._lang_actions.append(self._spanish_action)
        self._spanish_action.setCheckable(True)
        self._spanish_action.triggered.connect(self._spa_clicked)

        # TODO: issue with Chinese, Japanese, and Russian characters.
        #       seems to be an issue with matplotlib
        # Chinese
        # self._chinese_action = QAction(self)
        # self._lang_actions.append(self._chinese_action)
        # self._chinese_action.setCheckable(True)
        # self._chinese_action.triggered.connect(self._chi_clicked)

        # Japanese
        # self._japanese_action = QAction(self)
        # self._lang_actions.append(self._japanese_action)
        # self._japanese_action.setCheckable(True)
        # self._japanese_action.triggered.connect(self._jpn_clicked)

        # Russian
        # self._russian_action = QAction(self)
        # self._lang_actions.append(self._russian_action)
        # self._russian_action.setCheckable(True)
        # self._russian_action.triggered.connect(self._rus_clicked)
        """ Debug options """
        self._debug_actions = []
        self._debug_menu = QMenu(self)

        self._debug_action = QAction(self)
        self._debug_action.setCheckable(True)
        self._debug_action.triggered.connect(self._debug_clicked)
        self._debug_actions.append(self._debug_action)

        self._warning_action = QAction(self)
        self._warning_action.setCheckable(True)
        self._warning_action.triggered.connect(self._warning_clicked)
        self._debug_actions.append(self._warning_action)
        """ Help options """
        self._help_menu = QMenu(self)

        self._about_app_action = QAction(self)

        self._about_company_action = QAction(self)

        self._update_action = QAction(self)

        self._log_window_action = QAction(self)

        self._cam_actions = {}
        """ Menus order """
        # Menu bar options
        self.addAction(self._file_menu.menuAction())
        self.addAction(self._settings_menu.menuAction())
        self.addAction(self._help_menu.menuAction())

        # Menu bar -> File menu options
        self._file_menu.addAction(self._open_last_save_dir_action)
        self._file_menu.addSeparator()
        self._file_menu.addAction(self._exit_action)

        # Menu bar -> Settings menu options
        self._settings_menu.addMenu(self._language_menu)
        self._settings_menu.addMenu(self._debug_menu)

        # Menu bar -> Settings -> Language menu options
        self._language_menu.addAction(self._english_action)
        self._language_menu.addAction(self._dutch_action)
        self._language_menu.addAction(self._french_action)
        self._language_menu.addAction(self._german_action)
        self._language_menu.addAction(self._spanish_action)
        # self._language_menu.addAction(self._russian_action)
        # self._language_menu.addAction(self._chinese_action)
        # self._language_menu.addAction(self._japanese_action)

        # Menu bar -> Settings -> Debug menu options
        self._debug_menu.addAction(self._debug_action)
        self._debug_menu.addAction(self._warning_action)

        # Menu bar -> Help menu options
        self._help_menu.addAction(self._about_app_action)
        self._help_menu.addAction(self._about_company_action)
        self._help_menu.addAction(self._update_action)
        self._help_menu.addAction(self._log_window_action)

        self._debug_callback = None
        self._lang_callback = None
        self._layout_callback = None
        self._strings = dict()
        self.set_lang(lang)
        self._logger.debug("Initialized")

    def set_lang(self, lang: LangEnum) -> None:
        """
        Set the language of this view item.
        :param lang: The language enum to use.
        :return None:
        """
        self._strings = strings[lang]
        self._set_texts()
        if lang == LangEnum.ENG:
            self._reset_lang_actions(self._english_action)
        elif lang == LangEnum.DUT:
            self._reset_lang_actions(self._dutch_action)
        elif lang == LangEnum.FRE:
            self._reset_lang_actions(self._french_action)
        elif lang == LangEnum.GER:
            self._reset_lang_actions(self._german_action)
        elif lang == LangEnum.SPA:
            self._reset_lang_actions(self._spanish_action)
        # elif lang == LangEnum.RUS:
        #     self._reset_lang_actions(self._russian_action)
        # elif lang == LangEnum.CHI:
        #     self._reset_lang_actions(self._chinese_action)
        # elif lang == LangEnum.JPN:
        #     self._reset_lang_actions(self._japanese_action)

    def set_use_cams(self, usable: bool) -> None:
        self._use_cams_action.setEnabled(usable)

    def add_window_layout_handler(self, func: classmethod) -> None:
        """
        Add handler for window layout.
        :param func: The handler function.
        :return None:
        """
        self._layout_callback = func

    def add_lang_select_handler(self, func: classmethod) -> None:
        """
        Add handler for these selectable. Handler must take a LangEnum
        :param func: The handler.
        :return None:
        """
        self._lang_callback = func

    def set_debug_action(self, level) -> None:
        """
        Set which debug action is checked.
        :param level: The debug level.
        :return None:
        """
        if level == DEBUG:
            self._reset_debug_actions(self._debug_action)
        elif level == WARNING:
            self._reset_debug_actions(self._warning_action)

    def add_debug_select_handler(self, func: classmethod) -> None:
        """
        Add handler for these selectables. Handler must take a string.
        :param func: The handler.
        :return None:
        """
        self._debug_callback = func

    def set_cam_action_enabled(self, is_active: bool) -> None:
        """
        Set whether or not the camera actions can be used.
        :param is_active: can be used.
        :return None:
        """

        self._use_cams_action.setEnabled(is_active)
        for action in self._cam_actions.values():
            action.setEnabled(is_active)

    def set_cam_bool_checked(self, is_active: bool) -> None:
        """
        Set whether or not this action is checked.
        :param is_active: whether or not this action should be checked.
        :return None:
        """
        self._use_cams_action.setChecked(is_active)
        self.empty_cam_actions()

    def add_cam_bool_handler(self, func: classmethod) -> None:
        """
        Add handler to this selectable.
        :param func: The handler.
        :return None:
        """
        self._logger.debug("running")
        self._use_cams_action.triggered.connect(func)
        self._logger.debug("done")

    def set_use_cams_action_active(self, is_active: bool) -> None:
        """
        Set whether or not this action is usable.
        :param is_active: whether or not to let this action be usable.
        :return None:
        """

        self._use_cams_action.setEnabled(is_active)

    def add_exit_handler(self, func: classmethod) -> None:
        """
        Add handler to this selectable.
        :param func: The handler.
        :return None:
        """
        self._logger.debug("running")
        self._exit_action.triggered.connect(func)
        self._logger.debug("done")

    def add_open_last_save_dir_handler(self, func: classmethod) -> None:
        """
        Add handler to this selectable.
        :param func: The handler.
        :return None:
        """
        self._logger.debug("running")
        self._open_last_save_dir_action.triggered.connect(func)
        self._logger.debug("done")

    def add_about_app_handler(self, func: classmethod) -> None:
        """
        Add handler to this selectable.
        :param func: The handler.
        :return None:
        """
        self._logger.debug("running")
        self._about_app_action.triggered.connect(func)
        self._logger.debug("done")

    def add_about_company_handler(self, func: classmethod) -> None:
        """
        Add handler to this selectable.
        :param func: The handler.
        :return None:
        """
        self._logger.debug("running")
        self._about_company_action.triggered.connect(func)
        self._logger.debug("done")

    def add_update_handler(self, func: classmethod) -> None:
        """
        Add handler to this selectable.
        :param func: The handler.
        :return None:
        """
        self._logger.debug("running")
        self._update_action.triggered.connect(func)
        self._logger.debug("done")

    def add_log_window_handler(self, func: classmethod) -> None:
        """
        Add handler to this selectable.
        :param func: The handler.
        :return None:
        """
        self._logger.debug("running")
        self._log_window_action.triggered.connect(func)
        self._logger.debug("done")

    def add_cam_action(self,
                       name: str,
                       handler: classmethod,
                       is_active: bool = True) -> None:
        """
        Add a new action in the camera menu by name.
        :param name: The name of the camera being added.
        :param handler: The button handler.
        :param is_active: Whether or not this camera is considered active.
        :return None:
        """

        new_cam_action = QAction(self)
        new_cam_action.setText(name)
        new_cam_action.setCheckable(True)
        new_cam_action.setChecked(is_active)
        new_cam_action.toggled.connect(handler)
        self._cam_actions[name] = new_cam_action
        self._cam_list_menu.addAction(new_cam_action)

    def remove_cam_action(self, name: str) -> None:
        """
        Remove a cam action by name.
        :param name: The name of the camera being removed.
        :return None:
        """

        if name in self._cam_actions.keys():
            self._cam_list_menu.removeAction(self._cam_actions[name])
            del self._cam_actions[name]

    def _horiz_clicked(self) -> None:
        """
        Private handler for self._horiz_action
        :return None:
        """
        self._logger.debug("running")
        self._layout_callback("horizontal")
        self._logger.debug("done")

    def _vert_clicked(self) -> None:
        """
        Private handler for self._vert_action
        :return None:
        """
        self._logger.debug("running")
        self._layout_callback("vertical")
        self._logger.debug("done")

    def _tiled_clicked(self) -> None:
        """
        Private handler for self._tiled_action
        :return None:
        """
        self._logger.debug("running")
        self._layout_callback("tiled")
        self._logger.debug("done")

    def _cascade_clicked(self) -> None:
        """
        Private handler for self._cascade_action
        :return None:
        """
        self._logger.debug("running")
        self._layout_callback("cascade")
        self._logger.debug("done")

    def _eng_clicked(self) -> None:
        """
        Private handler for self._english_action
        :return None:
        """
        if self._lang_callback:
            self._lang_callback(LangEnum.ENG)

    def _dut_clicked(self) -> None:
        """
        Private handler for self._english_action
        :return None:
        """
        if self._lang_callback:
            self._lang_callback(LangEnum.DUT)

    def _fre_clicked(self) -> None:
        """
        Private handler for self._french_action
        :return None:
        """
        if self._lang_callback:
            self._lang_callback(LangEnum.FRE)

    def _ger_clicked(self) -> None:
        """
        Private handler for self._german_action
        :return None:
        """
        if self._lang_callback:
            self._lang_callback(LangEnum.GER)

    def _spa_clicked(self) -> None:
        """
        Private handler for self._spanish_action
        :return None:
        """
        if self._lang_callback:
            self._lang_callback(LangEnum.SPA)

    def _rus_clicked(self) -> None:
        """
        Private handler for self._russian_action
        :return None:
        """
        if self._lang_callback:
            self._lang_callback(LangEnum.RUS)

    def _chi_clicked(self) -> None:
        """
        Private handler for self._chinese_action
        :return None:
        """
        if self._lang_callback:
            self._lang_callback(LangEnum.CHI)

    def _jpn_clicked(self) -> None:
        """
        Private handler for self._japanese_action
        :return None:
        """
        if self._lang_callback:
            self._lang_callback(LangEnum.JPN)

    def _debug_clicked(self) -> None:
        """
        Private handler for self._french_action
        :return None:
        """
        if self._debug_callback:
            self._debug_callback(DEBUG)
        self._reset_debug_actions(self._debug_action)

    def _warning_clicked(self) -> None:
        """
        Private handler for self._french_action
        :return None:
        """
        if self._debug_callback:
            self._debug_callback(WARNING)
        self._reset_debug_actions(self._warning_action)

    def _reset_debug_actions(self, keep_checked: QAction) -> None:
        """
        Unset all except for keep_checked QAction.
        :param keep_checked: The QAction to keep checked.
        :return None:
        """
        for action in self._debug_actions:
            action.setChecked(False)
        keep_checked.setChecked(True)

    def _reset_lang_actions(self, keep_checked: QAction) -> None:
        """
        Unset all except for keep_checked QAction.
        :param keep_checked: The QAction to keep checked.
        :return None:
        """
        for action in self._lang_actions:
            action.setChecked(False)
        keep_checked.setChecked(True)

    def _set_texts(self) -> None:
        """
        Set the texts of this view object.
        :return None:
        """
        self._logger.debug("running")
        self._file_menu.setTitle(self._strings[StringsEnum.FILE])
        self._open_last_save_dir_action.setText(
            self._strings[StringsEnum.LAST_DIR])
        self._exit_action.setText(self._strings[StringsEnum.EXIT])
        self._settings_menu.setTitle(self._strings[StringsEnum.SETTINGS])
        self._debug_menu.setTitle(self._strings[StringsEnum.DEBUG_MENU])
        self._debug_action.setText(self._strings[StringsEnum.DEBUG])
        self._warning_action.setText(self._strings[StringsEnum.WARNING])
        self._cam_list_menu.setTitle(self._strings[StringsEnum.ATTACHED_CAMS])
        self._use_cams_action.setText(self._strings[StringsEnum.USE_CAMS])
        self._language_menu.setTitle(self._strings[StringsEnum.LANG])
        self._english_action.setText(self._strings[StringsEnum.ENG])
        self._dutch_action.setText(self._strings[StringsEnum.DUT])
        self._french_action.setText(self._strings[StringsEnum.FRE])
        self._german_action.setText(self._strings[StringsEnum.GER])
        self._spanish_action.setText(self._strings[StringsEnum.SPA])
        # self._russian_action.setText(self._strings[StringsEnum.RUS])
        # self._chinese_action.setText(self._strings[StringsEnum.CHI])
        # self._japanese_action.setText(self._strings[StringsEnum.JPN])
        self._help_menu.setTitle(self._strings[StringsEnum.HELP])
        self._about_app_action.setText(self._strings[StringsEnum.ABOUT_APP])
        self._about_company_action.setText(
            self._strings[StringsEnum.ABOUT_COMPANY])
        self._update_action.setText(self._strings[StringsEnum.UPDATE_CHECK])
        self._log_window_action.setText(
            self._strings[StringsEnum.SHOW_LOG_WINDOW])
        self._logger.debug("done")

    def empty_cam_actions(self) -> None:
        """
        :return None:
        """
        for name in self._cam_actions.keys():
            self._cam_list_menu.removeAction(self._cam_actions[name])
        self._cam_actions = {}
コード例 #28
0
ファイル: table.py プロジェクト: rpanerai/Eddy
    def _ContextMenu(self, rows):
        menu = QMenu()

        if len(rows) == 0:
            action_new = menu.addAction(QIcon(icons.ADD), "New item")
            action_new.triggered.connect(self._AddRow)
            return menu

        if len(rows) > 1:
            action_delete = menu.addAction(
                QIcon(icons.DELETE), "Remove " + str(len(rows)) + " items")
            action_delete.triggered.connect(
                partial(self.model().DeleteRows, rows))
            return menu

        row = rows[0]

        action_inspire_page = menu.addAction(QIcon(icons.INSPIRE),
                                             "Open INSPIRE page")
        action_arxiv_page = menu.addAction(QIcon(icons.ARXIV),
                                           "Open arXiv page")
        action_arxiv_pdf = menu.addAction(QIcon(icons.PDF), "Open arXiv PDF")
        action_doi_link = menu.addAction(QIcon(icons.DOI), "Open DOI links")
        menu.addSeparator()
        action_references = menu.addAction(QIcon(icons.SEARCH),
                                           "Find references")
        action_citations = menu.addAction(QIcon(icons.SEARCH),
                                          "Find citations")
        menu.addSeparator()
        if self.model().source is not None:
            files_menu = self._FilesMenu(row)
            if files_menu is None:
                action_files = menu.addAction(QIcon(icons.FILES), "Open files")
                action_files.setEnabled(False)
            else:
                action_files = menu.addMenu(files_menu)
                action_files.setIcon(QIcon(icons.FILES))
                action_files.setText("Open files")
            menu.addSeparator()
            tags_menu = self._TagsMenu(row)
            if tags_menu is None:
                action_tags = menu.addAction(QIcon(icons.STOP), "Drop tags")
                action_tags.setEnabled(False)
            else:
                action_tags = menu.addMenu(tags_menu)
                action_tags.setIcon(QIcon(icons.STOP))
                action_tags.setText("Drop tags")
            menu.addSeparator()
            action_new = menu.addAction(QIcon(icons.ADD), "New item")
            action_new.triggered.connect(self._AddRow)
            menu.addSeparator()
        action_delete = menu.addAction(QIcon(icons.DELETE), "Remove")

        arxiv_id = self.model().GetArXivId(row)
        if arxiv_id is None:
            action_arxiv_page.setEnabled(False)
            action_arxiv_pdf.setEnabled(False)
        else:
            arxiv_url = "https://arxiv.org/abs/" + arxiv_id
            action_arxiv_page.triggered.connect(partial(OpenWebURL, arxiv_url))
            pdf_url = "https://arxiv.org/pdf/" + arxiv_id + ".pdf"
            action_arxiv_pdf.triggered.connect(
                partial(OpenOnlineDocument, pdf_url))

        inspire_id = self.model().GetInspireId(row)
        if inspire_id is None:
            action_inspire_page.setEnabled(False)
            action_references.setEnabled(False)
            action_citations.setEnabled(False)
        else:
            inspire_id = str(inspire_id)

            inspire_url = "https://labs.inspirehep.net/literature/" + inspire_id
            action_inspire_page.triggered.connect(
                partial(OpenWebURL, inspire_url))

            ref_search = INSPIRE_SOURCE.CreateSearch("citedby:recid:" +
                                                     inspire_id)
            action_references.triggered.connect(
                partial(self.NewTabRequested.emit, ref_search))

            cit_search = INSPIRE_SOURCE.CreateSearch("refersto:recid:" +
                                                     inspire_id)
            action_citations.triggered.connect(
                partial(self.NewTabRequested.emit, cit_search))

        dois = self.model().GetDOIs(row)
        if dois == []:
            action_doi_link.setEnabled(False)
        else:
            doi_urls = ("https://doi.org/" + s for s in dois)
            for u in doi_urls:
                action_doi_link.triggered.connect(partial(OpenWebURL, u))

        action_delete.triggered.connect(
            partial(self.model().DeleteRows, (row, )))

        return menu
コード例 #29
0
class GrainSizeDatasetViewer(QDialog):
    PAGE_ROWS = 20
    logger = logging.getLogger("root.ui.GrainSizeDatasetView")
    gui_logger = logging.getLogger("GUI")

    def __init__(self, parent=None):
        super().__init__(parent=parent, f=Qt.Window)
        self.setWindowTitle(self.tr("Grain-size Dataset Viewer"))
        self.__dataset = GrainSizeDataset()  # type: GrainSizeDataset
        self.init_ui()
        self.data_table.setRowCount(0)
        self.frequency_curve_chart = FrequencyCurveChart(parent=self,
                                                         toolbar=True)
        self.frequency_curve_3D_chart = FrequencyCurve3DChart(parent=self,
                                                              toolbar=True)
        self.cumulative_curve_chart = CumulativeCurveChart(parent=self,
                                                           toolbar=True)
        self.folk54_GSM_diagram_chart = Folk54GSMDiagramChart(parent=self,
                                                              toolbar=True)
        self.folk54_SSC_diagram_chart = Folk54SSCDiagramChart(parent=self,
                                                              toolbar=True)
        self.BP12_GSM_diagram_chart = BP12GSMDiagramChart(parent=self,
                                                          toolbar=True)
        self.BP12_SSC_diagram_chart = BP12SSCDiagramChart(parent=self,
                                                          toolbar=True)
        self.load_dataset_dialog = LoadDatasetDialog(parent=self)
        self.load_dataset_dialog.dataset_loaded.connect(self.on_data_loaded)
        self.file_dialog = QFileDialog(parent=self)
        self.normal_msg = QMessageBox(self)

    def init_ui(self):
        self.setWindowTitle(self.tr("Dataset Viewer"))
        self.data_table = QTableWidget(100, 100)
        self.data_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
        self.data_table.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.data_table.setAlternatingRowColors(True)
        self.data_table.setContextMenuPolicy(Qt.CustomContextMenu)
        # self.data_table.hideColumn(0)
        self.main_layout = QGridLayout(self)
        self.main_layout.addWidget(self.data_table, 0, 0, 1, 3)

        self.previous_button = QPushButton(self.tr("Previous"))
        self.previous_button.setToolTip(
            self.tr("Click to back to the previous page."))
        self.previous_button.clicked.connect(self.on_previous_button_clicked)
        self.current_page_combo_box = QComboBox()
        self.current_page_combo_box.currentIndexChanged.connect(
            self.update_page)
        self.next_button = QPushButton(self.tr("Next"))
        self.next_button.setToolTip(self.tr("Click to jump to the next page."))
        self.next_button.clicked.connect(self.on_next_button_clicked)
        self.main_layout.addWidget(self.previous_button, 1, 0)
        self.main_layout.addWidget(self.current_page_combo_box, 1, 1)
        self.main_layout.addWidget(self.next_button, 1, 2)

        self.geometric_checkbox = QCheckBox(self.tr("Geometric"))
        self.geometric_checkbox.setChecked(True)
        self.geometric_checkbox.stateChanged.connect(
            self.on_is_geometric_changed)
        self.main_layout.addWidget(self.geometric_checkbox, 2, 0)
        self.FW57_checkbox = QCheckBox(self.tr("Method of statistic moments"))
        self.FW57_checkbox.setChecked(False)
        self.FW57_checkbox.stateChanged.connect(self.on_is_FW57_changed)
        self.main_layout.addWidget(self.FW57_checkbox, 2, 1)
        self.proportion_combo_box = QComboBox()
        self.supported_proportions = [
            ("GSM_proportion", self.tr("Gravel, Sand, Mud")),
            ("SSC_proportion", self.tr("Sand, Silt, Clay")),
            ("BGSSC_proportion", self.tr("Boulder, Gravel, Sand, Silt, Clay"))
        ]
        self.proportion_combo_box.addItems(
            [description for _, description in self.supported_proportions])
        self.proportion_combo_box.currentIndexChanged.connect(
            lambda: self.update_page(self.page_index))
        self.main_layout.addWidget(self.proportion_combo_box, 2, 2)

        self.menu = QMenu(self.data_table)
        self.load_dataset_action = self.menu.addAction(qta.icon("fa.database"),
                                                       self.tr("Load Dataset"))
        self.load_dataset_action.triggered.connect(self.load_dataset)
        self.plot_cumulative_curve_menu = self.menu.addMenu(
            qta.icon("mdi.chart-bell-curve-cumulative"),
            self.tr("Plot Cumlulative Curve Chart"))
        self.cumulative_plot_selected_action = self.plot_cumulative_curve_menu.addAction(
            self.tr("Plot Selected Samples"))
        self.cumulative_plot_selected_action.triggered.connect(
            lambda: self.plot_chart(self.cumulative_curve_chart, self.
                                    selections, False))
        self.cumulative_append_selected_action = self.plot_cumulative_curve_menu.addAction(
            self.tr("Append Selected Samples"))
        self.cumulative_append_selected_action.triggered.connect(
            lambda: self.plot_chart(self.cumulative_curve_chart, self.
                                    selections, True))
        self.cumulative_plot_all_action = self.plot_cumulative_curve_menu.addAction(
            self.tr("Plot All Samples"))
        self.cumulative_plot_all_action.triggered.connect(
            lambda: self.plot_chart(self.cumulative_curve_chart, self.__dataset
                                    .samples, False))
        self.cumulative_append_all_action = self.plot_cumulative_curve_menu.addAction(
            self.tr("Append All Samples"))
        self.cumulative_append_all_action.triggered.connect(
            lambda: self.plot_chart(self.cumulative_curve_chart, self.__dataset
                                    .samples, True))

        self.plot_frequency_curve_menu = self.menu.addMenu(
            qta.icon("mdi.chart-bell-curve"),
            self.tr("Plot Frequency Curve Chart"))
        self.frequency_plot_selected_action = self.plot_frequency_curve_menu.addAction(
            self.tr("Plot Selected Samples"))
        self.frequency_plot_selected_action.triggered.connect(
            lambda: self.plot_chart(self.frequency_curve_chart, self.
                                    selections, False))
        self.frequency_append_selected_action = self.plot_frequency_curve_menu.addAction(
            self.tr("Append Selected Samples"))
        self.frequency_append_selected_action.triggered.connect(
            lambda: self.plot_chart(self.frequency_curve_chart, self.
                                    selections, True))
        self.frequency_plot_all_action = self.plot_frequency_curve_menu.addAction(
            self.tr("Plot All Samples"))
        self.frequency_plot_all_action.triggered.connect(
            lambda: self.plot_chart(self.frequency_curve_chart, self.__dataset.
                                    samples, False))
        self.frequency_append_all_action = self.plot_frequency_curve_menu.addAction(
            self.tr("Append All Samples"))
        self.frequency_append_all_action.triggered.connect(
            lambda: self.plot_chart(self.frequency_curve_chart, self.__dataset.
                                    samples, True))

        self.plot_frequency_curve_3D_menu = self.menu.addMenu(
            qta.icon("mdi.video-3d"), self.tr("Plot Frequency Curve 3D Chart"))
        self.frequency_3D_plot_selected_action = self.plot_frequency_curve_3D_menu.addAction(
            self.tr("Plot Selected Samples"))
        self.frequency_3D_plot_selected_action.triggered.connect(
            lambda: self.plot_chart(self.frequency_curve_3D_chart, self.
                                    selections, False))
        self.frequency_3D_append_selected_action = self.plot_frequency_curve_3D_menu.addAction(
            self.tr("Append Selected Samples"))
        self.frequency_3D_append_selected_action.triggered.connect(
            lambda: self.plot_chart(self.frequency_curve_3D_chart, self.
                                    selections, True))
        self.frequency_3D_plot_all_action = self.plot_frequency_curve_3D_menu.addAction(
            self.tr("Plot All Samples"))
        self.frequency_3D_plot_all_action.triggered.connect(
            lambda: self.plot_chart(self.frequency_curve_3D_chart, self.
                                    __dataset.samples, False))
        self.frequency_3D_append_all_action = self.plot_frequency_curve_3D_menu.addAction(
            self.tr("Append All Samples"))
        self.frequency_3D_append_all_action.triggered.connect(
            lambda: self.plot_chart(self.frequency_curve_3D_chart, self.
                                    __dataset.samples, True))

        self.folk54_GSM_diagram_menu = self.menu.addMenu(
            qta.icon("mdi.triangle-outline"),
            self.tr("Plot GSM Diagram (Folk, 1954)"))
        self.folk54_GSM_plot_selected_action = self.folk54_GSM_diagram_menu.addAction(
            self.tr("Plot Selected Samples"))
        self.folk54_GSM_plot_selected_action.triggered.connect(
            lambda: self.plot_chart(self.folk54_GSM_diagram_chart, self.
                                    selections, False))
        self.folk54_GSM_append_selected_action = self.folk54_GSM_diagram_menu.addAction(
            self.tr("Append Selected Samples"))
        self.folk54_GSM_append_selected_action.triggered.connect(
            lambda: self.plot_chart(self.folk54_GSM_diagram_chart, self.
                                    selections, True))
        self.folk54_GSM_plot_all_action = self.folk54_GSM_diagram_menu.addAction(
            self.tr("Plot All Samples"))
        self.folk54_GSM_plot_all_action.triggered.connect(
            lambda: self.plot_chart(self.folk54_GSM_diagram_chart, self.
                                    __dataset.samples, False))
        self.folk54_GSM_append_all_action = self.folk54_GSM_diagram_menu.addAction(
            self.tr("Append All Samples"))
        self.folk54_GSM_append_all_action.triggered.connect(
            lambda: self.plot_chart(self.folk54_GSM_diagram_chart, self.
                                    __dataset.samples, True))

        self.folk54_SSC_diagram_menu = self.menu.addMenu(
            qta.icon("mdi.triangle-outline"),
            self.tr("Plot SSC Diagram (Folk, 1954)"))
        self.folk54_SSC_plot_selected_action = self.folk54_SSC_diagram_menu.addAction(
            self.tr("Plot Selected Samples"))
        self.folk54_SSC_plot_selected_action.triggered.connect(
            lambda: self.plot_chart(self.folk54_SSC_diagram_chart, self.
                                    selections, False))
        self.folk54_SSC_append_selected_action = self.folk54_SSC_diagram_menu.addAction(
            self.tr("Append Selected Samples"))
        self.folk54_SSC_append_selected_action.triggered.connect(
            lambda: self.plot_chart(self.folk54_SSC_diagram_chart, self.
                                    selections, True))
        self.folk54_SSC_plot_all_action = self.folk54_SSC_diagram_menu.addAction(
            self.tr("Plot All Samples"))
        self.folk54_SSC_plot_all_action.triggered.connect(
            lambda: self.plot_chart(self.folk54_SSC_diagram_chart, self.
                                    __dataset.samples, False))
        self.folk54_SSC_append_all_action = self.folk54_SSC_diagram_menu.addAction(
            self.tr("Append All Samples"))
        self.folk54_SSC_append_all_action.triggered.connect(
            lambda: self.plot_chart(self.folk54_SSC_diagram_chart, self.
                                    __dataset.samples, True))

        self.BP12_GSM_diagram_menu = self.menu.addMenu(
            qta.icon("mdi.triangle-outline"),
            self.tr("Plot GSM Diagram (Blott && Pye, 2012)"))
        self.BP12_GSM_plot_selected_action = self.BP12_GSM_diagram_menu.addAction(
            self.tr("Plot Selected Samples"))
        self.BP12_GSM_plot_selected_action.triggered.connect(
            lambda: self.plot_chart(self.BP12_GSM_diagram_chart, self.
                                    selections, False))
        self.BP12_GSM_append_selected_action = self.BP12_GSM_diagram_menu.addAction(
            self.tr("Append Selected Samples"))
        self.BP12_GSM_append_selected_action.triggered.connect(
            lambda: self.plot_chart(self.BP12_GSM_diagram_chart, self.
                                    selections, True))
        self.BP12_GSM_plot_all_action = self.BP12_GSM_diagram_menu.addAction(
            self.tr("Plot All Samples"))
        self.BP12_GSM_plot_all_action.triggered.connect(
            lambda: self.plot_chart(self.BP12_GSM_diagram_chart, self.__dataset
                                    .samples, False))
        self.BP12_GSM_append_all_action = self.BP12_GSM_diagram_menu.addAction(
            self.tr("Append All Samples"))
        self.BP12_GSM_append_all_action.triggered.connect(
            lambda: self.plot_chart(self.BP12_GSM_diagram_chart, self.__dataset
                                    .samples, True))

        self.BP12_SSC_diagram_menu = self.menu.addMenu(
            qta.icon("mdi.triangle-outline"),
            self.tr("Plot SSC Diagram (Blott && Pye, 2012)"))
        self.BP12_SSC_plot_selected_action = self.BP12_SSC_diagram_menu.addAction(
            self.tr("Plot Selected Samples"))
        self.BP12_SSC_plot_selected_action.triggered.connect(
            lambda: self.plot_chart(self.BP12_SSC_diagram_chart, self.
                                    selections, False))
        self.BP12_SSC_append_selected_action = self.BP12_SSC_diagram_menu.addAction(
            self.tr("Append Selected Samples"))
        self.BP12_SSC_append_selected_action.triggered.connect(
            lambda: self.plot_chart(self.BP12_SSC_diagram_chart, self.
                                    selections, True))
        self.BP12_SSC_plot_all_action = self.BP12_SSC_diagram_menu.addAction(
            self.tr("Plot All Samples"))
        self.BP12_SSC_plot_all_action.triggered.connect(
            lambda: self.plot_chart(self.BP12_SSC_diagram_chart, self.__dataset
                                    .samples, False))
        self.BP12_SSC_append_all_action = self.BP12_SSC_diagram_menu.addAction(
            self.tr("Append All Samples"))
        self.BP12_SSC_append_all_action.triggered.connect(
            lambda: self.plot_chart(self.BP12_SSC_diagram_chart, self.__dataset
                                    .samples, True))

        self.save_action = self.menu.addAction(qta.icon("mdi.microsoft-excel"),
                                               self.tr("Save Summary"))
        self.save_action.triggered.connect(self.on_save_clicked)
        self.data_table.customContextMenuRequested.connect(self.show_menu)

    def show_menu(self, pos):
        self.menu.popup(QCursor.pos())

    def show_message(self, title: str, message: str):
        self.normal_msg.setWindowTitle(title)
        self.normal_msg.setText(message)
        self.normal_msg.exec_()

    def show_info(self, message: str):
        self.show_message(self.tr("Info"), message)

    def show_warning(self, message: str):
        self.show_message(self.tr("Warning"), message)

    def show_error(self, message: str):
        self.show_message(self.tr("Error"), message)

    def load_dataset(self):
        self.load_dataset_dialog.show()

    def on_data_loaded(self, dataset: GrainSizeDataset):
        self.__dataset = dataset
        self.current_page_combo_box.clear()
        page_count, left = divmod(self.__dataset.n_samples, self.PAGE_ROWS)
        if left != 0:
            page_count += 1
        self.current_page_combo_box.addItems(
            [f"{self.tr('Page')} {i+1}" for i in range(page_count)])
        self.update_page(0)

    @property
    def is_geometric(self) -> bool:
        return self.geometric_checkbox.isChecked()

    def on_is_geometric_changed(self, state):
        if state == Qt.Checked:
            self.geometric_checkbox.setText(self.tr("Geometric"))
        else:
            self.geometric_checkbox.setText(self.tr("Logarithmic"))
        self.update_page(self.page_index)

    @property
    def is_FW57(self) -> bool:
        return self.FW57_checkbox.isChecked()

    def on_is_FW57_changed(self, state):
        if state == Qt.Checked:
            self.FW57_checkbox.setText(self.tr("Folk and Ward (1957) method"))
        else:
            self.FW57_checkbox.setText(self.tr("Method of statistic moments"))
        self.update_page(self.page_index)

    @property
    def proportion(self) -> str:
        index = self.proportion_combo_box.currentIndex()
        key, description = self.supported_proportions[index]
        return key, description

    @property
    def page_index(self) -> int:
        return self.current_page_combo_box.currentIndex()

    @property
    def n_pages(self) -> int:
        return self.current_page_combo_box.count()

    @property
    def unit(self) -> str:
        return "μm" if self.is_geometric else "φ"

    def update_page(self, page_index: int):
        if self.__dataset is None:
            return

        def write(row: int, col: int, value: str):
            if isinstance(value, str):
                pass
            elif isinstance(value, int):
                value = str(value)
            elif isinstance(value, float):
                value = f"{value: 0.2f}"
            else:
                value = value.__str__()
            item = QTableWidgetItem(value)
            item.setTextAlignment(Qt.AlignCenter)
            self.data_table.setItem(row, col, item)

        # necessary to clear
        self.data_table.clear()
        if page_index == self.n_pages - 1:
            start = page_index * self.PAGE_ROWS
            end = self.__dataset.n_samples
        else:
            start, end = page_index * self.PAGE_ROWS, (page_index +
                                                       1) * self.PAGE_ROWS
        proportion_key, proportion_desciption = self.proportion
        col_names = [
            f"{self.tr('Mean')}[{self.unit}]",
            self.tr("Mean Desc."), f"{self.tr('Median')} [{self.unit}]",
            f"{self.tr('Modes')} [{self.unit}]",
            self.tr("STD (Sorting)"),
            self.tr("Sorting Desc."),
            self.tr("Skewness"),
            self.tr("Skew. Desc."),
            self.tr("Kurtosis"),
            self.tr("Kurt. Desc."),
            f"({proportion_desciption})\n{self.tr('Proportion')} [%]",
            self.tr("Group\n(Folk, 1954)"),
            self.tr("Group\nSymbol (Blott & Pye, 2012)"),
            self.tr("Group\n(Blott & Pye, 2012)")
        ]
        col_keys = [(True, "mean"), (True, "mean_description"),
                    (True, "median"), (True, "modes"), (True, "std"),
                    (True, "std_description"), (True, "skewness"),
                    (True, "skewness_description"), (True, "kurtosis"),
                    (True, "kurtosis_description"), (False, proportion_key),
                    (False, "group_Folk54"), (False, "group_BP12_symbol"),
                    (False, "group_BP12")]
        self.data_table.setRowCount(end - start)
        self.data_table.setColumnCount(len(col_names))
        self.data_table.setHorizontalHeaderLabels(col_names)
        self.data_table.setVerticalHeaderLabels(
            [sample.name for sample in self.__dataset.samples[start:end]])
        for row, sample in enumerate(self.__dataset.samples[start:end]):
            statistic = get_all_statistic(sample.classes_μm, sample.classes_φ,
                                          sample.distribution)
            if self.is_geometric:
                if self.is_FW57:
                    sub_key = "geometric_FW57"
                else:
                    sub_key = "geometric"
            else:
                if self.is_FW57:
                    sub_key = "logarithmic_FW57"
                else:
                    sub_key = "logarithmic"
            for col, (in_sub, key) in enumerate(col_keys):
                value = statistic[sub_key][key] if in_sub else statistic[key]
                if key == "modes":
                    write(row, col, ", ".join([f"{m:0.2f}" for m in value]))
                elif key[-11:] == "_proportion":
                    write(row, col,
                          ", ".join([f"{p*100:0.2f}" for p in value]))
                else:
                    write(row, col, value)

        self.data_table.resizeColumnsToContents()

    @property
    def selections(self):
        if self.__dataset.n_samples == 0:
            self.show_warning(self.tr("Dataset has not been loaded."))
            return []

        start = self.page_index * self.PAGE_ROWS
        temp = set()
        for item in self.data_table.selectedRanges():
            for i in range(item.topRow(),
                           min(self.PAGE_ROWS + 1,
                               item.bottomRow() + 1)):
                temp.add(i + start)
        indexes = list(temp)
        indexes.sort()
        samples = [self.__dataset.samples[i] for i in indexes]
        return samples

    def on_previous_button_clicked(self):
        if self.page_index > 0:
            self.current_page_combo_box.setCurrentIndex(self.page_index - 1)

    def on_next_button_clicked(self):
        if self.page_index < self.n_pages - 1:
            self.current_page_combo_box.setCurrentIndex(self.page_index + 1)

    def plot_chart(self, chart, samples, append):
        if len(samples) == 0:
            return
        chart.show_samples(samples, append=append)
        chart.show()

    def save_file(self, filename: str):
        wb = openpyxl.Workbook()
        prepare_styles(wb)

        ws = wb.active
        ws.title = self.tr("README")
        description = \
            """
            This Excel file was generated by QGrain ({0}).

            Please cite:
            Liu, Y., Liu, X., Sun, Y., 2021. QGrain: An open-source and easy-to-use software for the comprehensive analysis of grain size distributions. Sedimentary Geology 423, 105980. https://doi.org/10.1016/j.sedgeo.2021.105980

            It contanins one sheet:
            1. The sheet puts the statistic parameters and the classification groups of the samples.

            The statistic formulas are referred to Blott & Pye (2001)'s work.
            The classification of GSDs is referred to Folk (1957)'s and Blott & Pye (2012)'s scheme.

            References:
                1.Blott, S. J. & Pye, K. Particle size scales and classification of sediment types based on particle size distributions: Review and recommended procedures. Sedimentology 59, 2071–2096 (2012).
                2.Blott, S. J. & Pye, K. GRADISTAT: a grain-size distribution and statistics package for the analysis of unconsolidated sediments. Earth Surf. Process. Landforms 26, 1237–1248 (2001).
                3.Folk, R. L. The Distinction between Grain Size and Mineral Composition in Sedimentary-Rock Nomenclature. The Journal of Geology 62, 344–359 (1954).

            """.format(QGRAIN_VERSION)

        def write(row, col, value, style="normal_light"):
            cell = ws.cell(row + 1, col + 1, value=value)
            cell.style = style

        lines_of_desc = description.split("\n")
        for row, line in enumerate(lines_of_desc):
            write(row, 0, line, style="description")
        ws.column_dimensions[column_to_char(0)].width = 200

        ws = wb.create_sheet(self.tr("Parameters and Groups"))
        proportion_key, proportion_desciption = self.proportion
        col_names = [
            f"{self.tr('Mean')}[{self.unit}]",
            self.tr("Mean Desc."), f"{self.tr('Median')} [{self.unit}]",
            f"{self.tr('Modes')} [{self.unit}]",
            self.tr("STD (Sorting)"),
            self.tr("Sorting Desc."),
            self.tr("Skewness"),
            self.tr("Skew. Desc."),
            self.tr("Kurtosis"),
            self.tr("Kurt. Desc."),
            f"({proportion_desciption})\n{self.tr('Proportion')} [%]",
            self.tr("Group\n(Folk, 1954)"),
            self.tr("Group\nSymbol (Blott & Pye, 2012)"),
            self.tr("Group\n(Blott & Pye, 2012)")
        ]
        col_keys = [(True, "mean"), (True, "mean_description"),
                    (True, "median"), (True, "modes"), (True, "std"),
                    (True, "std_description"), (True, "skewness"),
                    (True, "skewness_description"), (True, "kurtosis"),
                    (True, "kurtosis_description"), (False, proportion_key),
                    (False, "group_Folk54"), (False, "group_BP12_symbol"),
                    (False, "group_BP12")]
        write(0, 0, self.tr("Sample Name"), style="header")
        ws.column_dimensions[column_to_char(0)].width = 16
        for col, moment_name in enumerate(col_names, 1):
            write(0, col, moment_name, style="header")
            if col in (2, 4, 6, 8, 10, 11, 12, 14):
                ws.column_dimensions[column_to_char(col)].width = 30
            else:
                ws.column_dimensions[column_to_char(col)].width = 16
        ws.column_dimensions[column_to_char(len(col_names))].width = 40
        for row, sample in enumerate(self.__dataset.samples, 1):
            if row % 2 == 0:
                style = "normal_dark"
            else:
                style = "normal_light"
            write(row, 0, sample.name, style=style)
            statistic = get_all_statistic(sample.classes_μm, sample.classes_φ,
                                          sample.distribution)
            if self.is_geometric:
                if self.is_FW57:
                    sub_key = "geometric_FW57"
                else:
                    sub_key = "geometric"
            else:
                if self.is_FW57:
                    sub_key = "logarithmic_FW57"
                else:
                    sub_key = "logarithmic"
            for col, (in_sub, key) in enumerate(col_keys, 1):
                value = statistic[sub_key][key] if in_sub else statistic[key]
                if key == "modes":
                    write(row,
                          col,
                          ", ".join([f"{m:0.4f}" for m in value]),
                          style=style)
                elif key[-11:] == "_proportion":
                    write(row,
                          col,
                          ", ".join([f"{p*100:0.4f}" for p in value]),
                          style=style)
                else:
                    write(row, col, value, style=style)

        wb.save(filename)
        wb.close()

    def on_save_clicked(self):
        if self.__dataset is None or self.__dataset.n_samples == 0:
            self.show_warning(self.tr("Dataset has not been loaded."))
            return

        filename, _ = self.file_dialog.getSaveFileName(
            self, self.tr("Select Filename"), None, "Excel (*.xlsx)")
        if filename is None or filename == "":
            return

        try:
            self.save_file(filename)
            self.show_info(
                self.tr(
                    "The summary of this dataset has been saved to:\n    {0}").
                format(filename))
        except Exception as e:
            self.show_error(
                self.tr(
                    "Error raised while save summary to Excel file.\n    {0}").
                format(e.__str__()))
コード例 #30
0
class MangaTrackerGUI(QMainWindow, ui_mainwindow.Ui_MainWindow):
    """Main window for the MangaTracker GUI interface.

    Contains a list of all series in the database on the left, and a
    table on the right to show the currently selected series. A user
    can filter the list using a search bar directly above the list. A
    user can add a series, remove a series, change settings, edit a
    series, add the next volume for a series, or mark a series as
    completed using buttons at the bottom of the window.

    """
    def __init__(self, parent=None):
        """Creates main window, links buttons and filter bar to functions."""
        super(MangaTrackerGUI, self).__init__(parent)
        self.setupUi(self)
        self.set_styles()

        self.create_filter_menu()

        self.list_series.currentItemChanged.connect(self.display_series)
        self.filter_series.textChanged.connect(self.filter_series_list)

        self.settings_button.clicked.connect(self.open_config_window)

        self.edit_series_button.clicked.connect(self.open_edit_window)
        self.add_series_button.clicked.connect(self.open_add_window)
        self.remove_series_button.clicked.connect(self.remove_series)
        self.mark_as_completed_button.clicked.connect(self.toggle_is_completed)
        self.add_next_volume_button.clicked.connect(self.add_next_volume)

        self.get_list_items()

    def set_styles(self):
        """Sets styling for list items."""
        self.list_series.setStyleSheet(
            "QListWidget::item {padding-top:8px;"
            "padding-bottom:8px; border:1px solid #5DA9F6;}"
            "QListWidget::item:selected{background:#5DA9F6;}")

    def create_filter_menu(self):
        """Creates and populates the filter button menu.

        The menu contains two subgroups: options for filtering and
        options for ordering.

        """
        # Create menus and action groups
        self.filter_button_menu = QMenu()
        self.filter_button_group = QActionGroup(self.filter_button_menu)

        self.sort_button_menu = self.filter_button_menu.addMenu("Sort by...")
        self.sort_button_group = QActionGroup(self.sort_button_menu)
        self.filter_button_menu.addSeparator()

        # Create filter actions
        self.no_filter_action = self.filter_button_menu.addAction("No filter")
        self.gaps_action = self.filter_button_menu.addAction(
            "Show series with gaps")
        self.completed_action = self.filter_button_menu.addAction(
            "Show completed series")
        self.incomplete_action = self.filter_button_menu.addAction(
            "Show incomplete series")
        self.wishlist_action = self.filter_button_menu.addAction(
            "Show wishlisted series")

        # Create sort actions
        self.sort_name_action = self.sort_button_menu.addAction(
            "Name")
        self.sort_author_action = self.sort_button_menu.addAction(
            "Author")
        self.sort_publisher_action = self.sort_button_menu.addAction(
            "Publisher")
        self.sort_alt_names_action = self.sort_button_menu.addAction(
            "Alternate Names")

        # Add actions to action groups
        self.filter_button_group.addAction(self.no_filter_action)
        self.filter_button_group.addAction(self.gaps_action)
        self.filter_button_group.addAction(self.completed_action)
        self.filter_button_group.addAction(self.incomplete_action)
        self.filter_button_group.addAction(self.wishlist_action)

        self.sort_button_group.addAction(self.sort_name_action)
        self.sort_button_group.addAction(self.sort_author_action)
        self.sort_button_group.addAction(self.sort_publisher_action)
        self.sort_button_group.addAction(self.sort_alt_names_action)

        # Set actions checkable
        self.no_filter_action.setCheckable(True)
        self.gaps_action.setCheckable(True)
        self.completed_action.setCheckable(True)
        self.incomplete_action.setCheckable(True)
        self.wishlist_action.setCheckable(True)

        self.sort_name_action.setCheckable(True)
        self.sort_author_action.setCheckable(True)
        self.sort_publisher_action.setCheckable(True)
        self.sort_alt_names_action.setCheckable(True)

        self.no_filter_action.setChecked(True)
        self.sort_name_action.setChecked(True)

        # Refresh series list when options changed
        self.no_filter_action.toggled.connect(self.get_list_items)
        self.gaps_action.toggled.connect(self.get_list_items)
        self.completed_action.toggled.connect(self.get_list_items)
        self.incomplete_action.toggled.connect(self.get_list_items)
        self.wishlist_action.toggled.connect(self.get_list_items)

        self.sort_name_action.toggled.connect(self.get_list_items)
        self.sort_author_action.toggled.connect(self.get_list_items)
        self.sort_publisher_action.toggled.connect(self.get_list_items)
        self.sort_alt_names_action.toggled.connect(self.get_list_items)

        # Add menu to filter button
        self.filter_button.setMenu(self.filter_button_menu)
        self.filter_button.setPopupMode(self.filter_button.InstantPopup)

        # Open edit window when a series property is double-clicked
        self.series_info_display.cellDoubleClicked.connect(
            self.open_edit_window_from_table)

    def get_list_order(self):
        """Returns currently selected list order"""
        if self.sort_name_action.isChecked():
            return "name"
        elif self.sort_author_action.isChecked():
            return "author"
        elif self.sort_publisher_action.isChecked():
            return "publisher"
        elif self.sort_alt_names_action.isChecked():
            return "alt_names"

        # Fallback
        return "name"

    def toggle_is_completed(self):
        """Toggles completion status of selected series."""
        data_mgr = DatabaseManager(Config().database_name, None)
        if self.list_series.currentItem():
            series_rowid = self.list_series.currentItem().data(Qt.UserRole)
            cur = data_mgr.query("SELECT rowid, * FROM Series WHERE rowid = %d"
                                 % series_rowid)
            series = entry_to_series(cur.fetchone())
            series.is_completed ^= 1
            series.update_database_entry(data_mgr)
            self.get_list_items()

    def add_next_volume(self):
        """Adds next volume to selected series."""
        data_mgr = DatabaseManager(Config().database_name, None)
        if self.list_series.currentItem():
            series_rowid = self.list_series.currentItem().data(Qt.UserRole)
            cur = data_mgr.query("SELECT rowid, * FROM Series WHERE rowid = %d"
                                 % series_rowid)
            series = entry_to_series(cur.fetchone())
            if not series.is_completed:
                series.add_volumes(str(series.next_volume))
                series.update_database_entry(data_mgr)
                self.get_list_items()

    def remove_series(self):
        """Remove selected series from database

        Removes the currently selected series from the database, after
        prompting the user for confirmation.

        """
        data_mgr = DatabaseManager(Config().database_name, None)
        if self.list_series.currentItem():
            series_rowid = self.list_series.currentItem().data(Qt.UserRole)
            cur = data_mgr.query("SELECT rowid, * FROM Series WHERE rowid = %d"
                                 % series_rowid)
            series = entry_to_series(cur.fetchone())
            confirm_dialog = QMessageBox.question(
                self, "Remove %s" % series.name,
                "Are you sure you want to remove this series?\n"
                "This can't be undone.",
                QMessageBox.Discard | QMessageBox.Cancel,
                QMessageBox.Cancel)
            if confirm_dialog == QMessageBox.Discard:
                remove_series_from_database(data_mgr, series)
                self.list_series.takeItem(self.list_series.currentRow())
                self.list_series.setFocus()

    def open_config_window(self):
        self.config_window = MangaTrackerConfigWindow()
        self.config_window.setWindowModality(Qt.ApplicationModal)
        self.config_window.finished.connect(self.get_list_items)
        self.config_window.show()

    def open_add_window(self):
        """Opens window to add a new series

        Initializes the MangaTrackerAddWindow class. Triggers
        get_list_items when the add series window is closed

        """
        self.add_window = MangaTrackerAddWindow()
        self.add_window.setWindowModality(Qt.ApplicationModal)
        self.add_window.finished.connect(self.get_list_items)
        self.add_window.show()

    def open_edit_window(self):
        """Opens edit window for selected series.

        Retrieves the unique rowid for the selected series, and
        initializes the MangaTrackerEditWindow() class. Triggers
        display_series() when edit window is closed.

        """
        series_rowid = self.list_series.currentItem().data(Qt.UserRole)
        self.edit_window = MangaTrackerEditWindow(series_rowid)
        self.edit_window.setWindowModality(Qt.ApplicationModal)
        self.edit_window.finished.connect(self.display_series)
        self.edit_window.show()

    def open_edit_window_from_table(self, row, column):
        """Opens edit window with selected property in edit mode."""
        series_rowid = self.list_series.currentItem().data(Qt.UserRole)
        self.edit_window = MangaTrackerEditWindow(
            series_rowid, item=self.series_info_display.item(row, 0).text())
        self.edit_window.setWindowModality(Qt.ApplicationModal)
        self.edit_window.finished.connect(self.display_series)
        self.edit_window.show()

    def table_setup(self, series):
        """Generates table elements based on series.

        Clears any existing elements in the table, then uses series to
        generate a two-column table, with headings in the first column
        and data in the second.

        """
        headings = ["Name", "Alt. Names", "Author", "Volumes Owned",
                    "Next Volume", "Publisher", "Completed"]
        data = [series.name, series.alt_names, series.author,
                series.volumes_owned_readable, series.next_volume,
                series.publisher, "Yes" if series.is_completed else "No"]

        # Prepare table
        self.series_info_display.clear()
        self.series_info_display.setRowCount(len(headings))
        self.series_info_display.setColumnCount(2)
        header = self.series_info_display.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.ResizeToContents)
        header.setSectionResizeMode(1, QHeaderView.Stretch)

        # Populate table
        for i in range(len(headings)):
            headerItem = QTableWidgetItem(headings[i])
            dataItem = QTableWidgetItem(str(data[i]))
            self.series_info_display.setItem(i, 0, headerItem)
            self.series_info_display.setItem(i, 1, dataItem)

    def display_series(self):
        """Retrieves and displays info for selected series.

        This function retrieves the unique rowid for the selected
        series and retrieves the series from the database. It then
        updates all main window elements which show series info to
        show up-to-date properties. Once all series information is
        properly displayed, buttons which can change the selected
        series's properties are enabled.

        """
        data_mgr = DatabaseManager(Config().database_name, None)
        if self.list_series.currentItem():
            series_rowid = self.list_series.currentItem().data(Qt.UserRole)
            cur = data_mgr.query("SELECT rowid, * FROM Series WHERE rowid = %d"
                                 % series_rowid)
            series = entry_to_series(cur.fetchone())

            if series:
                self.list_series.currentItem().setText(series.compact_string())
                self.table_setup(series)
                self.edit_series_button.setEnabled(True)
                self.remove_series_button.setEnabled(True)
                self.add_next_volume_button.setEnabled(True)
                self.mark_as_completed_button.setEnabled(True)

    def filter_series_list(self):
        """Hides list elements whose text does not contain a user-provided string.

        When the text in the filter bar above the list is edited, this
        function takes the updated text and compares it to each
        element of the list. If the series string in the list element
        does not contain the updated text, the element will be
        hidden. As a result, the filter only works on name, author,
        and 'Completed'; functionality for filtering by other
        properties is planned for future updates.

        """
        filter_text = self.filter_series.text()
        # If empty string, make sure all items are visible
        if not filter_text:
            for i in range(self.list_series.count()):
                self.list_series.item(i).setHidden(False)
            return

        matches = self.list_series.findItems(filter_text, Qt.MatchContains)

        # # Can't use this because 'if item in matches' throws
        # # 'Operator not implemented' error
        # for i in range(self.list_series.count()):
        #     item = self.list_series.item(i)
        #     print(item)
        #     if item in matches:
        #         item.setHidden(False)
        #     else:
        #         item.setHidden(True)

        # Hide all items, then show items which match filter
        for i in range(self.list_series.count()):
            self.list_series.item(i).setHidden(True)
        for i in matches:
            i.setHidden(False)

    def check_filters(self, series):
        """Compare series to any filters set in filter_button

        If any of the filters in the filter_button_menu are selected,
        this function returns True if the series matches the selected
        filter and False if it does not. If no filter is selected,
        this function always returns True.

        """
        if (not Config().show_empty_series
                and not self.wishlist_action.isChecked()):
            if series.volumes_owned == "0,0,0,0":
                return False

        if self.gaps_action.isChecked():
            binary_str = series.get_volumes_owned_binary()
            if regexp("1*0+1", binary_str):
                return True
            return False

        elif self.completed_action.isChecked():
            return series.is_completed

        elif self.incomplete_action.isChecked():
            return not series.is_completed

        elif self.wishlist_action.isChecked():
            if series.volumes_owned == "0,0,0,0":
                return True
            return False

        else:
            return True

    def clear_table(self):
        """Clear series info from display table and disable buttons"""
        self.series_info_display.clear()
        self.series_info_display.setRowCount(0)
        self.series_info_display.setColumnCount(0)
        self.edit_series_button.setEnabled(False)
        self.remove_series_button.setEnabled(False)
        self.add_next_volume_button.setEnabled(False)
        self.mark_as_completed_button.setEnabled(False)

    def get_list_items(self):
        """Retrieves all series from database and populates list in main window.

        Populates the list in the main window with the compact_string()
        representations of all the series in the database, sorting by the
        given property (default "name") and placing any series with an
        unknown value for that property at the end of the list

        """
        order = self.get_list_order()
        data_mgr = DatabaseManager(Config().database_name, None)
        cur = data_mgr.query("SELECT rowid, * FROM Series ORDER BY %s "
                             "COLLATE NOCASE ASC, name ASC;" % order)
        entries = cur.fetchall()
        unknown_entries = []
        selected_series = None
        selected_series_found = False

        if self.list_series.currentItem():
            selected_series = self.list_series.currentItem().data(Qt.UserRole)
        if hasattr(self, "add_window") and self.add_window.added > -1:
            selected_series = self.add_window.added
            self.add_window.added = -1

        self.list_series.clear()
        for entry in entries:
            if entry[SI[order.upper()]] in ["Unknown", ""]:
                unknown_entries.append(entry)
                continue
            series = entry_to_series(entry)

            # Check if any filters are selected.
            if not self.check_filters(series):
                continue

            series_item = QListWidgetItem(series.compact_string())
            series_item.setData(Qt.UserRole, series.rowid)
            self.list_series.addItem(series_item)
            if selected_series and selected_series == series.rowid:
                self.list_series.setCurrentItem(series_item)
                selected_series_found = True

        for entry in unknown_entries:
            series = entry_to_series(entry)
            series_item = QListWidgetItem(series.compact_string())
            series_item.setData(Qt.UserRole, series.rowid)
            self.list_series.addItem(series_item)
            if selected_series and selected_series == series.rowid:
                self.list_series.setCurrentItem(series_item)
                selected_series_found = True

        # If previous series item no longer exists, select first entry in list
        if selected_series and not selected_series_found:
            self.list_series.setCurrentRow(0)

        if not self.list_series.currentItem():
            self.clear_table()

        self.filter_series_list()