def __init__(self, plotted_lines, *args, **kwargs): super().__init__(None, *args, **kwargs) self.plotted_lines = plotted_lines layout = QVBoxLayout() layout.setSizeConstraint(QLayout.SetMaximumSize) self.setLayout(layout) table_model = LineListTableModel(plotted_lines) if table_model.rowCount() > 0: table_view = QTableView() # disabling sorting will significantly speed up the # plot. This is because the table view must be re-built # every time a new set of markers is drawn on the plot # surface. Alternate approaches are worth examining. It # remains to be seen what would be the approach users # will favor. table_view.setSortingEnabled(False) proxy = SortModel(table_model.getName()) proxy.setSourceModel(table_model) table_view.setModel(proxy) table_view.setSortingEnabled(True) table_view.setSelectionMode(QAbstractItemView.NoSelection) table_view.horizontalHeader().setStretchLastSection(True) table_view.resizeColumnsToContents() layout.addWidget(table_view)
class ExampleCheckBoxItemDelegateWindow(QMainWindow): def __init__(self, parent=None): super().__init__(parent) self.resize(300, 300) self.setCentralWidget(QWidget(self)) self.centralWidget().setLayout(QVBoxLayout(self.centralWidget())) self.example_table_view = QTableView(self.centralWidget()) self.example_table_model = ExampleTableModel(self.example_table_view) self.checkbox_item_delegate = CheckBoxItemDelegate( self.example_table_view, self.example_table_model) self.example_table_view.setSelectionMode( QAbstractItemView.ExtendedSelection) self.example_table_view.setSelectionBehavior( QAbstractItemView.SelectRows) self.example_table_view.verticalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) self.example_table_view.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.example_table_view.setItemDelegateForColumn( 1, self.checkbox_item_delegate) self.example_table_view.setModel(self.example_table_model) self.centralWidget().layout().addWidget(self.example_table_view)
def __init__(self, plotted_lines, *args, **kwargs): super().__init__(None, *args, **kwargs) self.plotted_lines = plotted_lines layout = QVBoxLayout() layout.setSizeConstraint(QLayout.SetMaximumSize) self.setLayout(layout) table_model = LineListTableModel(plotted_lines) if table_model.rowCount() > 0: table_view = QTableView() # disabling sorting will significantly speed up theWidget # plot. This is because the table view must be re-built # every time a new set of markers is drawn on the plot # surface. Alternate approaches are worth examining. It # remains to be seen what would be the approach users # will favor. table_view.setSortingEnabled(False) proxy = SortModel(table_model.get_name()) proxy.setSourceModel(table_model) table_view.setModel(proxy) table_view.setSortingEnabled(True) table_view.setSelectionMode(QAbstractItemView.NoSelection) table_view.horizontalHeader().setStretchLastSection(True) table_view.resizeColumnsToContents() layout.addWidget(table_view)
class ChannelPropertiesDialog(QDialog): def __init__(self, parent, info, title="Channel Properties"): super().__init__(parent) self.setWindowTitle(title) self.model = QStandardItemModel(info["nchan"], 4) self.model.setHorizontalHeaderLabels(["#", "Label", "Type", "Bad"]) for index, ch in enumerate(info["chs"]): item = QStandardItem() item.setData(index, Qt.DisplayRole) item.setFlags(item.flags() & ~Qt.ItemIsEditable) self.model.setItem(index, 0, item) self.model.setItem(index, 1, QStandardItem(ch["ch_name"])) kind = channel_type(info, index).upper() self.model.setItem(index, 2, QStandardItem(str(kind))) bad = QStandardItem() bad.setData(ch["ch_name"] in info["bads"], Qt.UserRole) bad.setCheckable(True) bad.setEditable(False) checked = ch["ch_name"] in info["bads"] bad.setCheckState(Qt.Checked if checked else Qt.Unchecked) self.model.setItem(index, 3, bad) self.model.itemChanged.connect(bad_changed) self.proxymodel = MySortFilterProxyModel() self.proxymodel.setDynamicSortFilter(False) self.proxymodel.setSourceModel(self.model) self.view = QTableView() self.view.setModel(self.proxymodel) self.view.setItemDelegateForColumn(2, ComboBoxDelegate(self.view)) self.view.setEditTriggers(QAbstractItemView.AllEditTriggers) self.view.verticalHeader().setVisible(False) self.view.horizontalHeader().setStretchLastSection(True) self.view.setShowGrid(False) self.view.setSelectionMode(QAbstractItemView.NoSelection) self.view.setSortingEnabled(True) self.view.sortByColumn(0, Qt.AscendingOrder) vbox = QVBoxLayout(self) vbox.addWidget(self.view) self.buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) vbox.addWidget(self.buttonbox) self.buttonbox.accepted.connect(self.accept) self.buttonbox.rejected.connect(self.reject) self.resize(475, 650) self.view.setColumnWidth(0, 70) self.view.setColumnWidth(1, 155) self.view.setColumnWidth(2, 90)
def create_controls(self): table = QTableView(self) self.model = PluginsModel(self.plugin_manager) table.setModel(self.model) h = table.horizontalHeader() h.setSectionResizeMode(QHeaderView.ResizeToContents) table.setHorizontalHeader(h) h = table.verticalHeader() h.setSectionResizeMode(QHeaderView.ResizeToContents) table.setVerticalHeader(h) self.table = table width = 80 for i in range(3): width += table.columnWidth(i) btns = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal) self.edit_btn = QPushButton("Edit") btns.addButton(self.edit_btn, QDialogButtonBox.ActionRole) self.edit_btn.clicked.connect(self.edit_plugin) btns.accepted.connect(self.accept) btns.rejected.connect(self.reject) vbox = QVBoxLayout() vbox.addWidget(table) vbox.addWidget(btns) self.setLayout(vbox) s = self.size() s.setHeight(table.rowHeight(0) * 10) s.setWidth(width) self.resize(s)
def create_controls(self): table = QTableView(self) self.model = PluginsModel(self.plugin_manager) table.setModel(self.model) h = table.horizontalHeader() h.setSectionResizeMode(QHeaderView.ResizeToContents) table.setHorizontalHeader(h) h = table.verticalHeader() h.setSectionResizeMode(QHeaderView.ResizeToContents) table.setVerticalHeader(h) self.table = table width = 80 for i in range(3): width += table.columnWidth(i) btns = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal) self.edit_btn = QPushButton("Edit") btns.addButton(self.edit_btn, QDialogButtonBox.ActionRole) self.edit_btn.clicked.connect(self.edit_plugin) btns.accepted.connect(self.accept) btns.rejected.connect(self.reject) vbox = QVBoxLayout() vbox.addWidget(table) vbox.addWidget(btns) self.setLayout(vbox) s = self.size() s.setHeight(table.rowHeight(0) * 10) s.setWidth(width) self.resize(s)
class _DatumTableWidget(_DatumWidget): def __init__(self, clasz, controller, datum=None, parent=None): _DatumWidget.__init__(self, clasz, controller, datum, parent) def _init_ui(self): # Widgets self._table = QTableView() header = self._table.horizontalHeader() mode = QHeaderView.Stretch if os.environ[qtpy.QT_API] in qtpy.PYQT5_API: header.setSectionResizeMode(mode) else: header.setResizeMode(mode) # Layouts layout = _DatumWidget._init_ui(self) layout.addWidget(self._table) return layout def _create_model(self, datum): raise NotImplementedError def setDatum(self, datum): _DatumWidget.setDatum(self, datum) if datum is not None: model = self._create_model(datum) else: model = None self._table.setModel(model)
class XDFStreamsDialog(QDialog): def __init__(self, parent, rows, selected=None, disabled=None): super().__init__(parent) self.setWindowTitle("Select XDF Stream") self.model = QStandardItemModel() self.model.setHorizontalHeaderLabels( ["ID", "Name", "Type", "Channels", "Format", "Sampling Rate"]) for index, stream in enumerate(rows): items = [] for item in stream: tmp = QStandardItem() tmp.setData(item, Qt.DisplayRole) items.append(tmp) for item in items: item.setEditable(False) if disabled is not None and index in disabled: item.setFlags(Qt.NoItemFlags) self.model.appendRow(items) self.view = QTableView() self.view.setModel(self.model) self.view.verticalHeader().setVisible(False) self.view.horizontalHeader().setStretchLastSection(True) self.view.setShowGrid(False) self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) if selected is not None: self.view.selectRow(selected) self.view.setSortingEnabled(True) self.view.sortByColumn(0, Qt.AscendingOrder) vbox = QVBoxLayout(self) vbox.addWidget(self.view) self.buttonbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) vbox.addWidget(self.buttonbox) self.buttonbox.accepted.connect(self.accept) self.buttonbox.rejected.connect(self.reject) self.resize(775, 650) self.view.setColumnWidth(0, 100) self.view.setColumnWidth(1, 200) self.view.setColumnWidth(2, 120)
def buildViews(self, plot_window): # Table views must be preserved in the instance so they can be # passed to whoever is going to do the actual line list plotting. # The plotting code must know which lines (table rows) are selected # in each line list. self._table_views = [] for linelist in plot_window.linelists: table_model = LineListTableModel(linelist) proxy = SortModel(table_model.getName()) proxy.setSourceModel(table_model) if table_model.rowCount() > 0: table_view = QTableView() table_view.setModel(proxy) # setting this to False will significantly speed up # the loading of very large line lists. However, these # lists are often jumbled in wavelength, and consequently # difficult to read and use. It remains to be seen what # would be the approach users will favor. table_view.setSortingEnabled(True) table_view.setSelectionBehavior(QAbstractItemView.SelectRows) table_view.horizontalHeader().setStretchLastSection(True) table_view.resizeColumnsToContents() comments = linelist.meta['comments'] # this preserves the original sorting state # of the list. Use zero to sort by wavelength # on load. Doesn't seem to affect performance # by much tough. proxy.sort(-1, Qt.AscendingOrder) pane = self._buildLinelistPane(table_view, comments) self.tabWidget.addTab(pane, table_model.getName()) self._table_views.append(table_view)
def _createLineListPane(linelist, table_model, caller): table_view = QTableView() # disabling sorting will significantly speed up the rendering, # in particular of large line lists. These lists are often jumbled # in wavelength, and consequently difficult to read and use, so # having a sorting option is useful indeed. It remains to be seen # what would be the approach users will favor. We might add a toggle # that users can set/reset depending on their preferences. table_view.setSortingEnabled(False) sort_proxy = SortModel(table_model.getName()) sort_proxy.setSourceModel(table_model) table_view.setModel(sort_proxy) table_view.setSortingEnabled(True) table_view.horizontalHeader().setStretchLastSection(True) # playing with these doesn't speed up the sorting, regardless of whatever # you may read on the net. # # table_view.horizontalHeader().setResizeMode(QHeaderView.Fixed) # table_view.verticalHeader().setResizeMode(QHeaderView.Fixed) # table_view.horizontalHeader().setStretchLastSection(False) # table_view.verticalHeader().setStretchLastSection(False) table_view.setSelectionMode(QAbstractItemView.ExtendedSelection) table_view.setSelectionBehavior(QAbstractItemView.SelectRows) table_view.resizeColumnsToContents() # this preserves the original sorting state of the list. Use zero # to sort by wavelength on load. Doesn't seem to affect performance # by much tough. sort_proxy.sort(-1, Qt.AscendingOrder) # table selections will change the total count of lines selected. pane = LineListPane(table_view, linelist, sort_proxy, caller) return pane, table_view
def _create_line_list_pane(linelist, table_model, caller): table_view = QTableView() # disabling sorting will significantly speed up the rendering, # in particular of large line lists. These lists are often jumbled # in wavelength, and consequently difficult to read and use, so # having a sorting option is useful indeed. It remains to be seen # what would be the approach users will favor. We might add a toggle # that users can set/reset depending on their preferences. table_view.setSortingEnabled(False) sort_proxy = SortModel(table_model.get_name()) sort_proxy.setSourceModel(table_model) table_view.setModel(sort_proxy) table_view.setSortingEnabled(True) table_view.horizontalHeader().setStretchLastSection(True) # playing with these doesn't speed up the sorting, regardless of whatever # you may read on the net. # # table_view.horizontalHeader().setResizeMode(QHeaderView.Fixed) # table_view.verticalHeader().setResizeMode(QHeaderView.Fixed) # table_view.horizontalHeader().setStretchLastSection(False) # table_view.verticalHeader().setStretchLastSection(False) table_view.setSelectionMode(QAbstractItemView.ExtendedSelection) table_view.setSelectionBehavior(QAbstractItemView.SelectRows) table_view.resizeColumnsToContents() # this preserves the original sorting state of the list. Use zero # to sort by wavelength on load. Doesn't seem to affect performance # by much tough. sort_proxy.sort(-1, Qt.AscendingOrder) # table selections will change the total count of lines selected. pane = LineListPane(table_view, linelist, sort_proxy, caller) return pane, table_view
class LayoutSettingsDialog(QDialog): """Layout settings dialog""" def __init__(self, parent, names, order, active): super(LayoutSettingsDialog, self).__init__(parent) # variables self._parent = parent self._selection_model = None self.names = names self.order = order self.active = active # widgets self.button_move_up = QPushButton(_('Move Up')) self.button_move_down = QPushButton(_('Move Down')) self.button_delete = QPushButton(_('Delete Layout')) self.button_box = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.group_box = QGroupBox(_("Layout Display and Order")) self.table = QTableView(self) self.ok_button = self.button_box.button(QDialogButtonBox.Ok) self.cancel_button = self.button_box.button(QDialogButtonBox.Cancel) self.cancel_button.setDefault(True) self.cancel_button.setAutoDefault(True) # widget setup self.dialog_size = QSize(300, 200) self.setMinimumSize(self.dialog_size) self.setFixedSize(self.dialog_size) self.setWindowTitle('Layout Settings') self.table.setModel(LayoutModel(self.table, order, active)) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setSelectionMode(QAbstractItemView.SingleSelection) self.table.verticalHeader().hide() self.table.horizontalHeader().hide() self.table.setAlternatingRowColors(True) self.table.setShowGrid(False) self.table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table.horizontalHeader().setStretchLastSection(True) self.table.setColumnHidden(1, True) # need to keep a reference for pyside not to segfault! self._selection_model = self.table.selectionModel() # layout buttons_layout = QVBoxLayout() buttons_layout.addWidget(self.button_move_up) buttons_layout.addWidget(self.button_move_down) buttons_layout.addStretch() buttons_layout.addWidget(self.button_delete) group_layout = QHBoxLayout() group_layout.addWidget(self.table) group_layout.addLayout(buttons_layout) self.group_box.setLayout(group_layout) layout = QVBoxLayout() layout.addWidget(self.group_box) layout.addWidget(self.button_box) self.setLayout(layout) # signals and slots self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.close) self.button_delete.clicked.connect(self.delete_layout) self.button_move_up.clicked.connect(lambda: self.move_layout(True)) self.button_move_down.clicked.connect(lambda: self.move_layout(False)) self.table.model().dataChanged.connect( lambda: self.selection_changed(None, None)) self._selection_model.selectionChanged.connect( lambda: self.selection_changed(None, None)) # focus table index = self.table.model().index(0, 0) self.table.setCurrentIndex(index) self.table.setFocus() def delete_layout(self): """ """ names, order, active = self.names, self.order, self.order name = from_qvariant(self.table.selectionModel().currentIndex().data(), to_text_string) if name in names: index = names.index(name) # In case nothing has focus in the table if index != -1: order.remove(name) names[index] = None if name in active: active.remove(name) self.names, self.order, self.active = names, order, active self.table.model().set_data(order, active) index = self.table.model().index(0, 0) self.table.setCurrentIndex(index) self.table.setFocus() self.selection_changed(None, None) if len(order) == 0: self.button_move_up.setDisabled(True) self.button_move_down.setDisabled(True) self.button_delete.setDisabled(True) def move_layout(self, up=True): """ """ names, order, active = self.names, self.order, self.active row = self.table.selectionModel().currentIndex().row() row_new = row if up: row_new -= 1 else: row_new += 1 order[row], order[row_new] = order[row_new], order[row] self.order = order self.table.model().set_data(order, active) index = self.table.model().index(row_new, 0) self.table.setCurrentIndex(index) self.table.setFocus() self.selection_changed(None, None) def selection_changed(self, selection, deselection): """ """ model = self.table.model() index = self.table.currentIndex() row = index.row() order, names, active = self.order, self.names, self.active state = model.row(row)[1] name = model.row(row)[0] # Check if name changed if name not in names: # Did changed if row != -1: # row == -1, means no items left to delete old_name = order[row] order[row] = name names[names.index(old_name)] = name if old_name in active: active[active.index(old_name)] = name # Check if checbox clicked if state: if name not in active: active.append(name) else: if name in active: active.remove(name) self.active = active self.button_move_up.setDisabled(False) self.button_move_down.setDisabled(False) if row == 0: self.button_move_up.setDisabled(True) if row == len(names) - 1: self.button_move_down.setDisabled(True) if len(names) == 0: self.button_move_up.setDisabled(True) self.button_move_down.setDisabled(True)
class MxDataWidget(QWidget): """ Dialog for displaying and editing DataFrame and related objects. Based on the gtabview project (ExtTableView). For more information please see: https://github.com/wavexx/gtabview/blob/master/gtabview/viewer.py Signals ------- sig_option_changed(str, object): Raised if an option is changed. Arguments are name of option and its new value. """ sig_option_changed = Signal(str, object) def __init__(self, parent=None, data=DataFrame()): QWidget.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) self.is_series = False self.layout = None self.setup_and_check(data) def setup_and_check(self, data, title=''): """ Setup DataFrameEditor: return False if data is not supported, True otherwise. Supported types for data are DataFrame, Series and Index. """ self._selection_rec = False self._model = None self.layout = QGridLayout() self.layout.setSpacing(0) self.layout.setContentsMargins(0, 0, 0, 0) self.setLayout(self.layout) self.setWindowIcon(ima.icon('arredit')) if title: title = to_text_string(title) + " - %s" % data.__class__.__name__ else: title = _("%s editor") % data.__class__.__name__ if isinstance(data, Series): self.is_series = True data = data.to_frame() elif isinstance(data, Index): data = DataFrame(data) self.setWindowTitle(title) # self.resize(600, 500) self.hscroll = QScrollBar(Qt.Horizontal) self.vscroll = QScrollBar(Qt.Vertical) # Create the view for the level self.create_table_level() # Create the view for the horizontal header self.create_table_header() # Create the view for the vertical index self.create_table_index() # Create the model and view of the data self.dataModel = MxDataModel(data, parent=self) # self.dataModel.dataChanged.connect(self.save_and_close_enable) self.create_data_table() self.layout.addWidget(self.hscroll, 2, 0, 1, 2) self.layout.addWidget(self.vscroll, 0, 2, 2, 1) # autosize columns on-demand self._autosized_cols = set() self._max_autosize_ms = None self.dataTable.installEventFilter(self) avg_width = self.fontMetrics().averageCharWidth() self.min_trunc = avg_width * 8 # Minimum size for columns self.max_width = avg_width * 64 # Maximum size for columns self.setLayout(self.layout) # Make the dialog act as a window # self.setWindowFlags(Qt.Window) self.setModel(self.dataModel) self.resizeColumnsToContents() return True def create_table_level(self): """Create the QTableView that will hold the level model.""" self.table_level = QTableView() self.table_level.setEditTriggers(QTableWidget.NoEditTriggers) self.table_level.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_level.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_level.setFrameStyle(QFrame.Plain) self.table_level.horizontalHeader().sectionResized.connect( self._index_resized) self.table_level.verticalHeader().sectionResized.connect( self._header_resized) # self.table_level.setItemDelegate(QItemDelegate()) self.layout.addWidget(self.table_level, 0, 0) self.table_level.setContentsMargins(0, 0, 0, 0) self.table_level.horizontalHeader().sectionClicked.connect( self.sortByIndex) def create_table_header(self): """Create the QTableView that will hold the header model.""" self.table_header = QTableView() self.table_header.verticalHeader().hide() self.table_header.setEditTriggers(QTableWidget.NoEditTriggers) self.table_header.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_header.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_header.setHorizontalScrollMode(QTableView.ScrollPerPixel) self.table_header.setHorizontalScrollBar(self.hscroll) self.table_header.setFrameStyle(QFrame.Plain) self.table_header.horizontalHeader().sectionResized.connect( self._column_resized) # self.table_header.setItemDelegate(QItemDelegate()) self.layout.addWidget(self.table_header, 0, 1) def create_table_index(self): """Create the QTableView that will hold the index model.""" self.table_index = QTableView() self.table_index.horizontalHeader().hide() self.table_index.setEditTriggers(QTableWidget.NoEditTriggers) self.table_index.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_index.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table_index.setVerticalScrollMode(QTableView.ScrollPerPixel) self.table_index.setVerticalScrollBar(self.vscroll) self.table_index.setFrameStyle(QFrame.Plain) self.table_index.verticalHeader().sectionResized.connect( self._row_resized) # self.table_index.setItemDelegate(QItemDelegate()) self.layout.addWidget(self.table_index, 1, 0) self.table_index.setContentsMargins(0, 0, 0, 0) def create_data_table(self): """Create the QTableView that will hold the data model.""" self.dataTable = MxDataTable(self, self.dataModel, self.table_header.horizontalHeader(), self.hscroll, self.vscroll) self.dataTable.verticalHeader().hide() self.dataTable.horizontalHeader().hide() self.dataTable.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.dataTable.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.dataTable.setHorizontalScrollMode(QTableView.ScrollPerPixel) self.dataTable.setVerticalScrollMode(QTableView.ScrollPerPixel) self.dataTable.setFrameStyle(QFrame.Plain) # self.dataTable.setItemDelegate(QItemDelegate()) self.layout.addWidget(self.dataTable, 1, 1) self.setFocusProxy(self.dataTable) self.dataTable.sig_sort_by_column.connect(self._sort_update) self.dataTable.sig_fetch_more_columns.connect(self._fetch_more_columns) self.dataTable.sig_fetch_more_rows.connect(self._fetch_more_rows) def sortByIndex(self, index): """Implement a Index sort.""" self.table_level.horizontalHeader().setSortIndicatorShown(True) sort_order = self.table_level.horizontalHeader().sortIndicatorOrder() self.table_index.model().sort(index, sort_order) self._sort_update() def model(self): """Get the model of the dataframe.""" return self._model def _column_resized(self, col, old_width, new_width): """Update the column width.""" self.dataTable.setColumnWidth(col, new_width) self._update_layout() def _row_resized(self, row, old_height, new_height): """Update the row height.""" self.dataTable.setRowHeight(row, new_height) self._update_layout() def _index_resized(self, col, old_width, new_width): """Resize the corresponding column of the index section selected.""" self.table_index.setColumnWidth(col, new_width) self._update_layout() def _header_resized(self, row, old_height, new_height): """Resize the corresponding row of the header section selected.""" self.table_header.setRowHeight(row, new_height) self._update_layout() def _update_layout(self): """Set the width and height of the QTableViews and hide rows.""" h_width = max(self.table_level.verticalHeader().sizeHint().width(), self.table_index.verticalHeader().sizeHint().width()) self.table_level.verticalHeader().setFixedWidth(h_width) self.table_index.verticalHeader().setFixedWidth(h_width) last_row = self._model.header_shape[0] - 1 if last_row < 0: hdr_height = self.table_level.horizontalHeader().height() else: # Check if the header shape has only one row (which display the # same info than the horizontal header). if last_row == 0: self.table_level.setRowHidden(0, True) self.table_header.setRowHidden(0, True) else: self.table_level.setRowHidden(0, False) self.table_header.setRowHidden(0, False) hdr_height = self.table_level.rowViewportPosition(last_row) + \ self.table_level.rowHeight(last_row) + \ self.table_level.horizontalHeader().height() self.table_header.setFixedHeight(hdr_height) self.table_level.setFixedHeight(hdr_height) last_col = self._model.header_shape[1] - 1 if last_col < 0: idx_width = self.table_level.verticalHeader().width() else: idx_width = self.table_level.columnViewportPosition(last_col) + \ self.table_level.columnWidth(last_col) + \ self.table_level.verticalHeader().width() self.table_index.setFixedWidth(idx_width) self.table_level.setFixedWidth(idx_width) self._resizeVisibleColumnsToContents() def _reset_model(self, table, model): """Set the model in the given table.""" old_sel_model = table.selectionModel() table.setModel(model) if old_sel_model: del old_sel_model def setAutosizeLimit(self, limit_ms): """Set maximum size for columns.""" self._max_autosize_ms = limit_ms def setModel(self, model, relayout=True): """Set the model for the data, header/index and level views.""" self._model = model # sel_model = self.dataTable.selectionModel() # sel_model.currentColumnChanged.connect( # self._resizeCurrentColumnToContents) self._reset_model(self.dataTable, model) # Asociate the models (level, vertical index and horizontal header) # with its corresponding view. self._reset_model( self.table_level, DataFrameLevelModel(model, self.palette(), self.font())) self._reset_model(self.table_header, DataFrameHeaderModel(model, 0, self.palette())) self._reset_model(self.table_index, DataFrameHeaderModel(model, 1, self.palette())) # Needs to be called after setting all table models if relayout: self._update_layout() def setCurrentIndex(self, y, x): """Set current selection.""" self.dataTable.selectionModel().setCurrentIndex( self.dataTable.model().index(y, x), QItemSelectionModel.ClearAndSelect) def _sizeHintForColumn(self, table, col, limit_ms=None): """Get the size hint for a given column in a table.""" max_row = table.model().rowCount() lm_start = time.perf_counter() lm_row = 64 if limit_ms else max_row max_width = 0 for row in range(max_row): v = table.sizeHintForIndex(table.model().index(row, col)) max_width = max(max_width, v.width()) if row > lm_row: lm_now = time.perf_counter() lm_elapsed = (lm_now - lm_start) * 1000 if lm_elapsed >= limit_ms: break lm_row = int((row / lm_elapsed) * limit_ms) return max_width def _resizeColumnToContents(self, header, data, col, limit_ms): """Resize a column by its contents.""" hdr_width = self._sizeHintForColumn(header, col, limit_ms) data_width = self._sizeHintForColumn(data, col, limit_ms) if data_width > hdr_width: width = min(self.max_width, data_width) elif hdr_width > data_width * 2: width = max(min(hdr_width, self.min_trunc), min(self.max_width, data_width)) else: width = min(self.max_width, hdr_width) header.setColumnWidth(col, width) def _resizeColumnsToContents(self, header, data, limit_ms): """Resize all the colummns to its contents.""" max_col = data.model().columnCount() if limit_ms is None: max_col_ms = None else: max_col_ms = limit_ms / max(1, max_col) for col in range(max_col): self._resizeColumnToContents(header, data, col, max_col_ms) def eventFilter(self, obj, event): """Override eventFilter to catch resize event.""" if obj == self.dataTable and event.type() == QEvent.Resize: self._resizeVisibleColumnsToContents() return False def _resizeVisibleColumnsToContents(self): """Resize the columns that are in the view.""" index_column = self.dataTable.rect().topLeft().x() start = col = self.dataTable.columnAt(index_column) width = self._model.shape[1] end = self.dataTable.columnAt(self.dataTable.rect().bottomRight().x()) end = width if end == -1 else end + 1 if self._max_autosize_ms is None: max_col_ms = None else: max_col_ms = self._max_autosize_ms / max(1, end - start) while col < end: resized = False if col not in self._autosized_cols: self._autosized_cols.add(col) resized = True self._resizeColumnToContents(self.table_header, self.dataTable, col, max_col_ms) col += 1 if resized: # As we resize columns, the boundary will change index_column = self.dataTable.rect().bottomRight().x() end = self.dataTable.columnAt(index_column) end = width if end == -1 else end + 1 if max_col_ms is not None: max_col_ms = self._max_autosize_ms / max(1, end - start) def _resizeCurrentColumnToContents(self, new_index, old_index): """Resize the current column to its contents.""" if new_index.column() not in self._autosized_cols: # Ensure the requested column is fully into view after resizing self._resizeVisibleColumnsToContents() self.dataTable.scrollTo(new_index) def resizeColumnsToContents(self): """Resize the columns to its contents.""" self._autosized_cols = set() self._resizeColumnsToContents(self.table_level, self.table_index, self._max_autosize_ms) self._update_layout() self.table_level.resizeColumnsToContents() def change_format(self): """ Ask user for display format for floats and use it. This function also checks whether the format is valid and emits `sig_option_changed`. """ format, valid = QInputDialog.getText(self, _('Format'), _("Float formatting"), QLineEdit.Normal, self.dataModel.get_format()) if valid: format = str(format) try: format % 1.1 except: msg = _("Format ({}) is incorrect").format(format) QMessageBox.critical(self, _("Error"), msg) return if not format.startswith('%'): msg = _("Format ({}) should start with '%'").format(format) QMessageBox.critical(self, _("Error"), msg) return self.dataModel.set_format(format) self.sig_option_changed.emit('dataframe_format', format) def get_value(self): """Return modified Dataframe -- this is *not* a copy""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute df = self.dataModel.get_data() if self.is_series: return df.iloc[:, 0] else: return df def _update_header_size(self): """Update the column width of the header.""" column_count = self.table_header.model().columnCount() for index in range(0, column_count): if index < column_count: column_width = self.dataTable.columnWidth(index) self.table_header.setColumnWidth(index, column_width) else: break def _sort_update(self): """ Update the model for all the QTableView objects. Uses the model of the dataTable as the base. """ self.setModel(self.dataTable.model()) def _fetch_more_columns(self): """Fetch more data for the header (columns).""" self.table_header.model().fetch_more() def _fetch_more_rows(self): """Fetch more data for the index (rows).""" self.table_index.model().fetch_more() def resize_to_contents(self): QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self.dataTable.resizeColumnsToContents() self.dataModel.fetch_more(columns=True) self.dataTable.resizeColumnsToContents() self._update_header_size() QApplication.restoreOverrideCursor() # --- mx specific --- def process_remote_view(self, data): if data is None: data = DataFrame() # Empty DataFrame self.setModel(MxDataModel(data, parent=self))
class BasePlotCurveEditorDialog(QDialog): """QDialog that is used in Qt Designer to edit the properties of the curves in a waveform plot. This dialog is shown when you double-click the plot, or when you right click it and choose 'edit curves'. This thing is mostly just a wrapper for a table view, with a couple buttons to add and remove curves, and a button to save the changes.""" TABLE_MODEL_CLASS = BasePlotCurvesModel def __init__(self, plot, parent=None): super(BasePlotCurveEditorDialog, self).__init__(parent) self.plot = plot self.setup_ui() self.table_model = self.TABLE_MODEL_CLASS(self.plot) self.table_view.setModel(self.table_model) self.table_model.plot = plot # self.table_view.resizeColumnsToContents() self.add_button.clicked.connect(self.addCurve) self.remove_button.clicked.connect(self.removeSelectedCurve) self.remove_button.setEnabled(False) self.table_view.selectionModel().selectionChanged.connect( self.handleSelectionChange) self.table_view.doubleClicked.connect(self.handleDoubleClick) self.resize(800, 300) def setup_ui(self): self.vertical_layout = QVBoxLayout(self) self.table_view = QTableView(self) self.table_view.setEditTriggers(QAbstractItemView.DoubleClicked) self.table_view.setProperty("showDropIndicator", False) self.table_view.setDragDropOverwriteMode(False) self.table_view.setSelectionMode(QAbstractItemView.SingleSelection) self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.table_view.setSortingEnabled(False) self.table_view.horizontalHeader().setStretchLastSection(True) self.table_view.verticalHeader().setVisible(False) self.table_view.setColumnWidth(0, 160) self.table_view.setColumnWidth(1, 160) self.table_view.setColumnWidth(2, 160) self.vertical_layout.addWidget(self.table_view) self.add_remove_layout = QHBoxLayout() spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.add_remove_layout.addItem(spacer) self.add_button = QPushButton("Add Curve", self) self.add_remove_layout.addWidget(self.add_button) self.remove_button = QPushButton("Remove Curve", self) self.add_remove_layout.addWidget(self.remove_button) self.vertical_layout.addLayout(self.add_remove_layout) self.button_box = QDialogButtonBox(self) self.button_box.setOrientation(Qt.Horizontal) self.button_box.addButton("Done", QDialogButtonBox.AcceptRole) self.vertical_layout.addWidget(self.button_box) self.button_box.accepted.connect(self.saveChanges) self.button_box.rejected.connect(self.reject) self.setWindowTitle("Waveform Curve Editor") def setup_delegate_columns(self, index=2): symbol_delegate = SymbolColumnDelegate(self) self.table_view.setItemDelegateForColumn(index+3, symbol_delegate) line_delegate = LineColumnDelegate(self) self.table_view.setItemDelegateForColumn(index+1, line_delegate) color_delegate = ColorColumnDelegate(self) self.table_view.setItemDelegateForColumn(index, color_delegate) @Slot() def addCurve(self): self.table_model.append() @Slot() def removeSelectedCurve(self): self.table_model.removeAtIndex(self.table_view.currentIndex()) @Slot(QItemSelection, QItemSelection) def handleSelectionChange(self, selected, deselected): self.remove_button.setEnabled( self.table_view.selectionModel().hasSelection()) @Slot(QModelIndex) def handleDoubleClick(self, index): if self.table_model.needsColorDialog(index): # The table model returns a QBrush for BackgroundRole, not a QColor init_color = self.table_model.data(index, Qt.BackgroundRole).color() color = QColorDialog.getColor(init_color, self) if color.isValid(): self.table_model.setData(index, color, role=Qt.EditRole) @Slot() def saveChanges(self): formWindow = QDesignerFormWindowInterface.findFormWindow(self.plot) if formWindow: formWindow.cursor().setProperty("curves", self.plot.curves) self.accept()
class QtTableView(QtAbstractItemView, ProxyTableView): #: Proxy widget widget = Typed(QTableView) def create_widget(self): self.widget = QTableView(self.parent_widget()) def init_widget(self): super(QtTableView, self).init_widget() d = self.declaration self.set_show_grid(d.show_grid) def init_model(self): self.set_model(QAtomTableModel(parent=self.widget)) # ------------------------------------------------------------------------- # Widget settters # ------------------------------------------------------------------------- def set_show_grid(self,show): self.widget.setShowGrid(show) def set_cell_padding(self, padding): self.widget.setStyleSheet( "QTableView::item { padding: %ipx }" % padding) def set_vertical_minimum_section_size(self, size): self.widget.verticalHeader().setMinimumSectionSize(size) def set_horizontal_minimum_section_size(self, size): self.widget.horizontalHeader().setMinimumSectionSize(size) def set_horizontal_stretch(self, stretch): self.widget.horizontalHeader().setStretchLastSection(stretch) def set_vertical_stretch(self, stretch): self.widget.verticalHeader().setStretchLastSection(stretch) def set_resize_mode(self, mode): header = self.widget.horizontalHeader() if IS_QT4: header.setResizeMode(RESIZE_MODES[mode]) else: # Custom is obsolete, use fixed instead. mode = 'fixed' if mode == 'custom' else mode header.setSectionResizeMode(RESIZE_MODES[mode]) def set_show_horizontal_header(self, show): header = self.widget.horizontalHeader() header.show() if show else header.hide() def set_show_vertical_header(self, show): header = self.widget.verticalHeader() header.show() if show else header.hide() # ------------------------------------------------------------------------- # View refresh handlers # ------------------------------------------------------------------------- def _refresh_visible_column(self, value): self._pending_column_refreshes -= 1 if self._pending_column_refreshes == 0: d = self.declaration cols = self.model.columnCount()-d.visible_columns d.visible_column = max(0, min(value, cols)) def _refresh_visible_row(self, value): self._pending_row_refreshes -= 1 if self._pending_row_refreshes == 0 and (self.declaration is not None): d = self.declaration rows = self.model.rowCount()-d.visible_rows d.visible_row = max(0, min(value, rows)) def _refresh_visible_rows(self): return top = self.widget.rowAt(self.widget.rect().top()) bottom = self.widget.rowAt(self.widget.rect().bottom()) self.declaration.visible_rows = max(1, (bottom-top))*2 # 2x for safety def _refresh_visible_columns(self): return left = self.widget.rowAt(self.widget.rect().left()) right = self.widget.rowAt(self.widget.rect().right()) self.declaration.visible_columns = max(1, (right-left))*2
class RunDialog(QDialog): simulation_done = Signal(bool, str) simulation_termination_request = Signal() def __init__(self, config_file, run_model, parent=None): QDialog.__init__(self, parent) self.setWindowFlags(Qt.Window) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.setModal(True) self.setWindowModality(Qt.WindowModal) self.setWindowTitle(f"Simulations - {config_file}") self._snapshot_model = SnapshotModel(self) self._run_model = run_model self._isDetailedDialog = False self._minimum_width = 1200 ert = None if isinstance(run_model, BaseRunModel): ert = run_model.ert() self._ticker = QTimer(self) self._ticker.timeout.connect(self._on_ticker) progress_proxy_model = ProgressProxyModel(self._snapshot_model, parent=self) self._total_progress_label = QLabel( _TOTAL_PROGRESS_TEMPLATE.format( total_progress=0, phase_name=run_model.getPhaseName()), self, ) self._total_progress_bar = QProgressBar(self) self._total_progress_bar.setRange(0, 100) self._total_progress_bar.setTextVisible(False) self._iteration_progress_label = QLabel(self) self._progress_view = ProgressView(self) self._progress_view.setModel(progress_proxy_model) self._progress_view.setIndeterminate(True) legend_view = LegendView(self) legend_view.setModel(progress_proxy_model) self._tab_widget = QTabWidget(self) self._tab_widget.currentChanged.connect(self._current_tab_changed) self._snapshot_model.rowsInserted.connect(self.on_new_iteration) self._job_label = QLabel(self) self._job_model = JobListProxyModel(self, 0, 0, 0, 0) self._job_model.setSourceModel(self._snapshot_model) self._job_view = QTableView(self) self._job_view.setVerticalScrollMode(QAbstractItemView.ScrollPerItem) self._job_view.setSelectionBehavior(QAbstractItemView.SelectRows) self._job_view.setSelectionMode(QAbstractItemView.SingleSelection) self._job_view.clicked.connect(self._job_clicked) self._open_files = {} self._job_view.setModel(self._job_model) self.running_time = QLabel("") self.plot_tool = PlotTool(ert, config_file) self.plot_tool.setParent(self) self.plot_button = QPushButton(self.plot_tool.getName()) self.plot_button.clicked.connect(self.plot_tool.trigger) self.plot_button.setEnabled(ert is not None) self.kill_button = QPushButton("Kill simulations") self.done_button = QPushButton("Done") self.done_button.setHidden(True) self.restart_button = QPushButton("Restart") self.restart_button.setHidden(True) self.show_details_button = QPushButton("Show details") self.show_details_button.setCheckable(True) size = 20 spin_movie = resourceMovie("loading.gif") spin_movie.setSpeed(60) spin_movie.setScaledSize(QSize(size, size)) spin_movie.start() self.processing_animation = QLabel() self.processing_animation.setMaximumSize(QSize(size, size)) self.processing_animation.setMinimumSize(QSize(size, size)) self.processing_animation.setMovie(spin_movie) button_layout = QHBoxLayout() button_layout.addWidget(self.processing_animation) button_layout.addWidget(self.running_time) button_layout.addStretch() button_layout.addWidget(self.show_details_button) button_layout.addWidget(self.plot_button) button_layout.addWidget(self.kill_button) button_layout.addWidget(self.done_button) button_layout.addWidget(self.restart_button) button_widget_container = QWidget() button_widget_container.setLayout(button_layout) layout = QVBoxLayout() layout.addWidget(self._total_progress_label) layout.addWidget(self._total_progress_bar) layout.addWidget(self._iteration_progress_label) layout.addWidget(self._progress_view) layout.addWidget(legend_view) layout.addWidget(self._tab_widget) layout.addWidget(self._job_label) layout.addWidget(self._job_view) layout.addWidget(button_widget_container) self.setLayout(layout) self.kill_button.clicked.connect(self.killJobs) self.done_button.clicked.connect(self.accept) self.restart_button.clicked.connect(self.restart_failed_realizations) self.show_details_button.clicked.connect(self.toggle_detailed_progress) self.simulation_done.connect(self._on_simulation_done) self.setMinimumWidth(self._minimum_width) self._setSimpleDialog() def _current_tab_changed(self, index: int): # Clear the selection in the other tabs for i in range(0, self._tab_widget.count()): if i != index: self._tab_widget.widget(i).clearSelection() def _setSimpleDialog(self) -> None: self._isDetailedDialog = False self._tab_widget.setVisible(False) self._job_label.setVisible(False) self._job_view.setVisible(False) self.show_details_button.setText("Show details") def _setDetailedDialog(self) -> None: self._isDetailedDialog = True self._tab_widget.setVisible(True) self._job_label.setVisible(True) self._job_view.setVisible(True) self.show_details_button.setText("Hide details") @Slot(QModelIndex, int, int) def on_new_iteration(self, parent: QModelIndex, start: int, end: int) -> None: if not parent.isValid(): index = self._snapshot_model.index(start, 0, parent) iter_row = start self._iteration_progress_label.setText( f"Progress for iteration {index.internalPointer().id}") widget = RealizationWidget(iter_row) widget.setSnapshotModel(self._snapshot_model) widget.currentChanged.connect(self._select_real) self._tab_widget.addTab( widget, f"Realizations for iteration {index.internalPointer().id}") @Slot(QModelIndex) def _job_clicked(self, index): if not index.isValid(): return selected_file = index.data(FileRole) if selected_file and selected_file not in self._open_files: job_name = index.siblingAtColumn(0).data() viewer = FileDialog( selected_file, job_name, index.row(), index.model().get_real(), index.model().get_iter(), self, ) self._open_files[selected_file] = viewer def remove_file(): """ We have sometimes seen this fail because the selected file is not in open file, without being able to reproduce the exception. """ try: self._open_files.pop(selected_file) except KeyError: logger = logging.getLogger(__name__) logger.exception( f"Failed to pop: {selected_file} from {self._open_files}" ) viewer.finished.connect(remove_file) elif selected_file in self._open_files: self._open_files[selected_file].raise_() @Slot(QModelIndex) def _select_real(self, index): step = 0 stage = 0 real = index.row() iter_ = index.model().get_iter() self._job_model.set_step(iter_, real, stage, step) self._job_label.setText( f"Realization id {index.data(RealIens)} in iteration {iter_}") self._job_view.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) def reject(self): return def closeEvent(self, QCloseEvent): if self._run_model.isFinished(): self.simulation_done.emit(self._run_model.hasRunFailed(), self._run_model.getFailMessage()) else: # Kill jobs if dialog is closed if self.killJobs() != QMessageBox.Yes: QCloseEvent.ignore() def startSimulation(self): self._run_model.reset() self._snapshot_model.reset() self._tab_widget.clear() evaluator_server_config = EvaluatorServerConfig() def run(): asyncio.set_event_loop(asyncio.new_event_loop()) self._run_model.startSimulations( evaluator_server_config=evaluator_server_config, ) simulation_thread = Thread(name="ert_gui_simulation_thread") simulation_thread.setDaemon(True) simulation_thread.run = run simulation_thread.start() self._ticker.start(1000) tracker = EvaluatorTracker( self._run_model, ee_con_info=evaluator_server_config.get_connection_info(), ) worker = TrackerWorker(tracker) worker_thread = QThread() worker.done.connect(worker_thread.quit) worker.consumed_event.connect(self._on_tracker_event) worker.moveToThread(worker_thread) self.simulation_done.connect(worker.stop) self._worker = worker self._worker_thread = worker_thread worker_thread.started.connect(worker.consume_and_emit) self._worker_thread.start() def killJobs(self): msg = "Are you sure you want to kill the currently running simulations?" kill_job = QMessageBox.question(self, "Kill simulations?", msg, QMessageBox.Yes | QMessageBox.No) if kill_job == QMessageBox.Yes: # Normally this slot would be invoked by the signal/slot system, # but the worker is busy tracking the evaluation. self._worker.request_termination() self.reject() return kill_job @Slot(bool, str) def _on_simulation_done(self, failed, failed_msg): self.processing_animation.hide() self.kill_button.setHidden(True) self.done_button.setHidden(False) self.restart_button.setVisible( self._run_model.has_failed_realizations()) self.restart_button.setEnabled(self._run_model.support_restart) self._total_progress_bar.setValue(100) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format( total_progress=100, phase_name=self._run_model.getPhaseName())) if failed: msg = QMessageBox() msg.setIcon(QMessageBox.Critical) msg.setText("Simulations failed!".center(100)) msg.setDetailedText(failed_msg) msg.exec_() @Slot() def _on_ticker(self): runtime = self._run_model.get_runtime() self.running_time.setText(format_running_time(runtime)) @Slot(object) def _on_tracker_event(self, event): if isinstance(event, EndEvent): self.simulation_done.emit(event.failed, event.failed_msg) self._worker.stop() self._ticker.stop() elif isinstance(event, FullSnapshotEvent): if event.snapshot is not None: self._snapshot_model._add_snapshot(event.snapshot, event.iteration) self._progress_view.setIndeterminate(event.indeterminate) progress = int(event.progress * 100) self._total_progress_bar.setValue(progress) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format(total_progress=progress, phase_name=event.phase_name)) elif isinstance(event, SnapshotUpdateEvent): if event.partial_snapshot is not None: self._snapshot_model._add_partial_snapshot( event.partial_snapshot, event.iteration) self._progress_view.setIndeterminate(event.indeterminate) progress = int(event.progress * 100) self._total_progress_bar.setValue(progress) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format(total_progress=progress, phase_name=event.phase_name)) def restart_failed_realizations(self): msg = QMessageBox(self) msg.setIcon(QMessageBox.Information) msg.setText( "Note that workflows will only be executed on the restarted " "realizations and that this might have unexpected consequences.") msg.setWindowTitle("Restart failed realizations") msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) result = msg.exec_() if result == QMessageBox.Ok: self.restart_button.setVisible(False) self.kill_button.setVisible(True) self.done_button.setVisible(False) self._run_model.restart() self.startSimulation() @Slot() def toggle_detailed_progress(self): if self._isDetailedDialog: self._setSimpleDialog() else: self._setDetailedDialog() self.adjustSize()
class RunDialog(QDialog): simulation_done = Signal(bool, str) simulation_termination_request = Signal() def __init__(self, config_file, run_model, simulation_arguments, parent=None): QDialog.__init__(self, parent) self.setWindowFlags(Qt.Window) self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.setModal(True) self.setWindowModality(Qt.WindowModal) self.setWindowTitle("Simulations - {}".format(config_file)) self._snapshot_model = SnapshotModel(self) self._run_model = run_model self._isDetailedDialog = False self._minimum_width = 1200 ert = None if isinstance(run_model, BaseRunModel): ert = run_model.ert() self._simulations_argments = simulation_arguments self._ticker = QTimer(self) self._ticker.timeout.connect(self._on_ticker) progress_proxy_model = ProgressProxyModel(self._snapshot_model, parent=self) self._total_progress_label = QLabel(_TOTAL_PROGRESS_TEMPLATE.format(0), self) self._total_progress_bar = QProgressBar(self) self._total_progress_bar.setRange(0, 100) self._total_progress_bar.setTextVisible(False) self._iteration_progress_label = QLabel(self) self._progress_view = ProgressView(self) self._progress_view.setModel(progress_proxy_model) self._progress_view.setIndeterminate(True) legend_view = LegendView(self) legend_view.setModel(progress_proxy_model) self._tab_widget = QTabWidget(self) self._snapshot_model.rowsInserted.connect(self.on_new_iteration) self._job_label = QLabel(self) self._job_model = JobListProxyModel(self, 0, 0, 0, 0) self._job_model.setSourceModel(self._snapshot_model) self._job_view = QTableView(self) self._job_view.clicked.connect(self._job_clicked) self._open_files = {} self._job_view.setModel(self._job_model) self.running_time = QLabel("") self.plot_tool = PlotTool(config_file) self.plot_tool.setParent(self) self.plot_button = QPushButton(self.plot_tool.getName()) self.plot_button.clicked.connect(self.plot_tool.trigger) self.plot_button.setEnabled(ert is not None) self.kill_button = QPushButton("Kill Simulations") self.done_button = QPushButton("Done") self.done_button.setHidden(True) self.restart_button = QPushButton("Restart") self.restart_button.setHidden(True) self.show_details_button = QPushButton("Show Details") self.show_details_button.setCheckable(True) size = 20 spin_movie = resourceMovie("ide/loading.gif") spin_movie.setSpeed(60) spin_movie.setScaledSize(QSize(size, size)) spin_movie.start() self.processing_animation = QLabel() self.processing_animation.setMaximumSize(QSize(size, size)) self.processing_animation.setMinimumSize(QSize(size, size)) self.processing_animation.setMovie(spin_movie) button_layout = QHBoxLayout() button_layout.addWidget(self.processing_animation) button_layout.addWidget(self.running_time) button_layout.addStretch() button_layout.addWidget(self.show_details_button) button_layout.addWidget(self.plot_button) button_layout.addWidget(self.kill_button) button_layout.addWidget(self.done_button) button_layout.addWidget(self.restart_button) button_widget_container = QWidget() button_widget_container.setLayout(button_layout) layout = QVBoxLayout() layout.addWidget(self._total_progress_label) layout.addWidget(self._total_progress_bar) layout.addWidget(self._iteration_progress_label) layout.addWidget(self._progress_view) layout.addWidget(legend_view) layout.addWidget(self._tab_widget) layout.addWidget(self._job_label) layout.addWidget(self._job_view) layout.addWidget(button_widget_container) self.setLayout(layout) self.kill_button.clicked.connect(self.killJobs) self.done_button.clicked.connect(self.accept) self.restart_button.clicked.connect(self.restart_failed_realizations) self.show_details_button.clicked.connect(self.toggle_detailed_progress) self.simulation_done.connect(self._on_simulation_done) self.setMinimumWidth(self._minimum_width) self._setSimpleDialog() def _setSimpleDialog(self) -> None: self._isDetailedDialog = False self._tab_widget.setVisible(False) self._job_label.setVisible(False) self._job_view.setVisible(False) self.show_details_button.setText("Show Details") def _setDetailedDialog(self) -> None: self._isDetailedDialog = True self._tab_widget.setVisible(True) self._job_label.setVisible(True) self._job_view.setVisible(True) self.show_details_button.setText("Hide Details") @Slot(QModelIndex, int, int) def on_new_iteration(self, parent: QModelIndex, start: int, end: int) -> None: if not parent.isValid(): iter = start self._iteration_progress_label.setText( f"Progress for iteration {iter}") widget = RealizationWidget(iter) widget.setSnapshotModel(self._snapshot_model) widget.currentChanged.connect(self._select_real) self._tab_widget.addTab(widget, f"Realizations for iteration {iter}") @Slot(QModelIndex) def _job_clicked(self, index): if not index.isValid(): return selected_file = index.data(FileRole) if selected_file and selected_file not in self._open_files: job_name = index.siblingAtColumn(0).data() viewer = FileDialog( selected_file, job_name, index.row(), index.model().get_real(), index.model().get_iter(), self, ) self._open_files[selected_file] = viewer viewer.finished.connect( lambda _, f=selected_file: self._open_files.pop(f)) elif selected_file in self._open_files: self._open_files[selected_file].raise_() @Slot(QModelIndex) def _select_real(self, index): step = 0 stage = 0 real = index.row() iter_ = index.model().get_iter() self._job_model.set_step(iter_, real, stage, step) self._job_label.setText( f"Realization id {index.data(RealIens)} in iteration {iter_}") self._job_view.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) # Clear the selection in the other tabs for i in range(0, self._tab_widget.count()): if i != self._tab_widget.currentIndex(): self._tab_widget.widget(i).clearSelection() def reject(self): return def closeEvent(self, QCloseEvent): if self._run_model.isFinished(): self.simulation_done.emit(self._run_model.hasRunFailed(), self._run_model.getFailMessage()) else: # Kill jobs if dialog is closed if self.killJobs() != QMessageBox.Yes: QCloseEvent.ignore() def startSimulation(self): self._run_model.reset() self._snapshot_model.reset() self._tab_widget.clear() def run(): asyncio.set_event_loop(asyncio.new_event_loop()) self._run_model.startSimulations(self._simulations_argments) simulation_thread = Thread(name="ert_gui_simulation_thread") simulation_thread.setDaemon(True) simulation_thread.run = run simulation_thread.start() self._ticker.start(1000) tracker = create_tracker( self._run_model, num_realizations=self._simulations_argments["active_realizations"]. count(), ee_config=self._simulations_argments.get("ee_config", None), ) worker = TrackerWorker(tracker) worker_thread = QThread() worker.done.connect(worker_thread.quit) worker.consumed_event.connect(self._on_tracker_event) worker.moveToThread(worker_thread) self.simulation_done.connect(worker.stop) self._worker = worker self._worker_thread = worker_thread worker_thread.started.connect(worker.consume_and_emit) self._worker_thread.start() def killJobs(self): msg = "Are you sure you want to kill the currently running simulations?" if self._run_model.getQueueStatus().get( JobStatusType.JOB_QUEUE_UNKNOWN, 0) > 0: msg += "\n\nKilling a simulation with unknown status will not kill the realizations already submitted!" kill_job = QMessageBox.question(self, "Kill simulations?", msg, QMessageBox.Yes | QMessageBox.No) if kill_job == QMessageBox.Yes: # Normally this slot would be invoked by the signal/slot system, # but the worker is busy tracking the evaluation. self._worker.request_termination() self.reject() return kill_job @Slot(bool, str) def _on_simulation_done(self, failed, failed_msg): self.processing_animation.hide() self.kill_button.setHidden(True) self.done_button.setHidden(False) self.restart_button.setVisible(self.has_failed_realizations()) self.restart_button.setEnabled(self._run_model.support_restart) self._total_progress_bar.setValue(100) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format(100)) if failed: QMessageBox.critical( self, "Simulations failed!", f"The simulation failed with the following error:\n\n{failed_msg}", ) @Slot() def _on_ticker(self): runtime = self._run_model.get_runtime() self.running_time.setText(format_running_time(runtime)) @Slot(object) def _on_tracker_event(self, event): if isinstance(event, EndEvent): self.simulation_done.emit(event.failed, event.failed_msg) self._worker.stop() self._ticker.stop() elif isinstance(event, FullSnapshotEvent): if event.snapshot is not None: self._snapshot_model._add_snapshot(event.snapshot, event.iteration) self._progress_view.setIndeterminate(event.indeterminate) progress = int(event.progress * 100) self._total_progress_bar.setValue(progress) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format(progress)) elif isinstance(event, SnapshotUpdateEvent): if event.partial_snapshot is not None: self._snapshot_model._add_partial_snapshot( event.partial_snapshot, event.iteration) self._progress_view.setIndeterminate(event.indeterminate) progress = int(event.progress * 100) self._total_progress_bar.setValue(progress) self._total_progress_label.setText( _TOTAL_PROGRESS_TEMPLATE.format(progress)) def has_failed_realizations(self): completed = self._run_model.completed_realizations_mask initial = self._run_model.initial_realizations_mask for (index, successful) in enumerate(completed): if initial[index] and not successful: return True return False def count_successful_realizations(self): """ Counts the realizations completed in the prevoius ensemble run :return: """ completed = self._run_model.completed_realizations_mask return completed.count(True) def create_mask_from_failed_realizations(self): """ Creates a BoolVector mask representing the failed realizations :return: Type BoolVector """ completed = self._run_model.completed_realizations_mask initial = self._run_model.initial_realizations_mask inverted_mask = BoolVector(default_value=False) for (index, successful) in enumerate(completed): inverted_mask[index] = initial[index] and not successful return inverted_mask def restart_failed_realizations(self): msg = QMessageBox(self) msg.setIcon(QMessageBox.Information) msg.setText( "Note that workflows will only be executed on the restarted realizations and that this might have unexpected consequences." ) msg.setWindowTitle("Restart Failed Realizations") msg.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel) result = msg.exec_() if result == QMessageBox.Ok: self.restart_button.setVisible(False) self.kill_button.setVisible(True) self.done_button.setVisible(False) active_realizations = self.create_mask_from_failed_realizations() self._simulations_argments[ "active_realizations"] = active_realizations self._simulations_argments[ "prev_successful_realizations"] = self._simulations_argments.get( "prev_successful_realizations", 0) self._simulations_argments[ "prev_successful_realizations"] += self.count_successful_realizations( ) self.startSimulation() @Slot() def toggle_detailed_progress(self): if self._isDetailedDialog: self._setSimpleDialog() else: self._setDetailedDialog() self.adjustSize()
elif section == 4: return "Нулевой уровень" elif section == 5: return "Интервал варьирования" def rowCount(self, *args, **kwargs): return len(self._factors) def add_factor(self, factor): rows = self.rowCount() position = rows - 1 if self.rowCount() > 0 else rows self.beginInsertRows(QModelIndex(), position, position) self._factors.append(factor) self.endInsertRows() if __name__ == '__main__': import sys from qtpy.QtWidgets import QApplication, QTableView, QHeaderView factors = [Factor("die", 1, 2, 3432, 4)] app = QApplication([]) model = FactorsTableModel() tv = QTableView() tv.setModel(model) for f in factors: model.add_factor(f) tv.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) tv.show() sys.exit(app.exec_())
def __init__(self, parent, env, name, action, version, versions): super(CondaPackageActionDialog, self).__init__(parent) self._parent = parent self._env = env self._version_text = None self._name = name self._dependencies_dic = {} self._conda_process = \ conda_api_q.CondaProcess(self, self._on_process_finished) # widgets self.label = QLabel(self) self.combobox_version = QComboBox() self.label_version = QLabel(self) self.widget_version = None self.table_dependencies = None self.checkbox = QCheckBox(_('Install dependencies (recommended)')) self.bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.button_ok = self.bbox.button(QDialogButtonBox.Ok) self.button_cancel = self.bbox.button(QDialogButtonBox.Cancel) self.button_cancel.setDefault(True) self.button_cancel.setAutoDefault(True) dialog_size = QSize(300, 90) # helper variable values action_title = {const.UPGRADE: _("Upgrade package"), const.DOWNGRADE: _("Downgrade package"), const.REMOVE: _("Remove package"), const.INSTALL: _("Install package")} # Versions might have duplicates from different builds versions = sort_versions(list(set(versions)), reverse=True) # FIXME: There is a bug, a package installed by anaconda has version # astropy 0.4 and the linked list 0.4 but the available versions # in the json file do not include 0.4 but 0.4rc1... so... # temporal fix is to check if inside list otherwise show full list if action == const.UPGRADE: if version in versions: index = versions.index(version) versions = versions[:index] else: versions = versions elif action == const.DOWNGRADE: if version in versions: index = versions.index(version) versions = versions[index+1:] else: versions = versions elif action == const.REMOVE: versions = [version] self.combobox_version.setEnabled(False) if len(versions) == 1: if action == const.REMOVE: labeltext = _('Package version to remove:') else: labeltext = _('Package version available:') self.label_version.setText(versions[0]) self.widget_version = self.label_version else: labeltext = _("Select package version:") self.combobox_version.addItems(versions) self.widget_version = self.combobox_version self.label.setText(labeltext) self.label_version.setAlignment(Qt.AlignLeft) self.table_dependencies = QWidget(self) self._layout = QGridLayout() self._layout.addWidget(self.label, 0, 0, Qt.AlignVCenter | Qt.AlignLeft) self._layout.addWidget(self.widget_version, 0, 1, Qt.AlignVCenter | Qt.AlignRight) self.widgets = [self.checkbox, self.button_ok, self.widget_version, self.table_dependencies] row_index = 1 # Create a Table if action in [const.INSTALL, const.UPGRADE, const.DOWNGRADE]: table = QTableView(self) dialog_size = QSize(dialog_size.width() + 40, 300) self.table_dependencies = table row_index = 1 self._layout.addItem(QSpacerItem(10, 5), row_index, 0) self._layout.addWidget(self.checkbox, row_index + 1, 0, 1, 2) self.checkbox.setChecked(True) self._changed_version(versions[0]) table.setSelectionBehavior(QAbstractItemView.SelectRows) table.verticalHeader().hide() table.horizontalHeader().hide() table.setAlternatingRowColors(True) table.setShowGrid(False) table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) table.horizontalHeader().setStretchLastSection(True) self._layout.addWidget(self.table_dependencies, row_index + 2, 0, 1, 2, Qt.AlignHCenter) self._layout.addItem(QSpacerItem(10, 5), row_index + 3, 0) self._layout.addWidget(self.bbox, row_index + 6, 0, 1, 2, Qt.AlignHCenter) title = "{0}: {1}".format(action_title[action], name) self.setLayout(self._layout) self.setMinimumSize(dialog_size) self.setFixedSize(dialog_size) self.setWindowTitle(title) self.setModal(True) # signals and slots self.bbox.accepted.connect(self.accept) self.bbox.rejected.connect(self.close) self.combobox_version.currentIndexChanged.connect( self._changed_version) self.checkbox.stateChanged.connect(self._changed_checkbox)
class MainWidget(QWidget): bot_double_clicked = Signal(Bot) task_button_clicked = Signal() payload_button_clicked = Signal() bot_clicked = Signal(int) load_finished = Signal() def __init__(self, parent): super(MainWidget, self).__init__(parent) self.main_layout = QHBoxLayout(self) self.main_layout.setSpacing(11) self.setLayout(self.main_layout) def setupUi(self, config): self.map = GoogleMapsView(self, config.get("gmaps_key")) self.map.getHandler().markerDoubleClicked.connect( lambda bot_id, lat, lng: self.bot_double_clicked.emit( self.table_model.getDeviceById(int(bot_id)))) self.map.getHandler().markerClicked.connect(self.bot_clicked.emit) self.map.setObjectName("mapWidget") self.map.enableMarkersDragging(False) self.map.loadFinished.connect(self.load_finished.emit) self.bots_table = QTableView(self) self.bots_table.doubleClicked.connect(self.on_botTable_doubleClicked) self.bots_table.setObjectName("bots_table") self.bots_table.setAutoFillBackground(True) self.bots_table.setFrameShape(QFrame.StyledPanel) self.bots_table.setFrameShadow(QFrame.Sunken) self.bots_table.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.bots_table.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.bots_table.setSizeAdjustPolicy( QAbstractScrollArea.AdjustToContents) self.bots_table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.bots_table.setHorizontalScrollMode( QAbstractItemView.ScrollPerPixel) self.bots_table.setGridStyle(Qt.SolidLine) self.bots_table.horizontalHeader().setStretchLastSection(True) self.bots_table.setContextMenuPolicy(Qt.CustomContextMenu) hheader = self.bots_table.horizontalHeader() hheader.setDefaultSectionSize(150) hheader.setMinimumSectionSize(150) hheader.setMouseTracking(True) vheader = self.bots_table.verticalHeader() vheader.setCascadingSectionResizes(True) vheader.setDefaultSectionSize(35) vheader.setSortIndicatorShown(False) vheader.setStretchLastSection(False) vheader.setVisible(False) self.bots_table.setEditTriggers(QTableWidget.NoEditTriggers) self.bots_table.setSelectionMode(QAbstractItemView.ExtendedSelection) self.bots_table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table_model = BotsTableModel(self) self.table_model.setObjectName("table_model") self.table_model.removed.connect(self.map.deleteMarker) self.bots_table.setModel(self.table_model) for i in range(self.table_model.columnCount()): self.bots_table.horizontalHeader().setSectionResizeMode( i, QHeaderView.Stretch) self.splitter = QSplitter(self) self.splitter.addWidget(self.map) self.splitter.addWidget(self.bots_table) self.splitter.setOrientation(Qt.Vertical) self.main_layout.addWidget(self.splitter) sizepolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) sizepolicy.setHorizontalStretch(0) sizepolicy.setVerticalStretch(0) self.buttons_widget = QWidget(self) self.buttons_widget.setContentsMargins(0, 0, 0, 0) self.buttons_widgetLayout = QVBoxLayout(self.buttons_widget) self.buttons_widgetLayout.setContentsMargins(0, 0, 0, 0) self.buttons_widgetLayout.setAlignment(Qt.AlignTop) self.buttons_widget.setLayout(self.buttons_widgetLayout) self.payload_button = MainWindowButton(self.buttons_widget) self.payload_button.setToolTip("Create payload") self.payload_button.clicked.connect(self.payload_button_clicked.emit) self.payload_button.setIcon( QIcon( os.path.join(os.getcwd(), "resources/icons/3d-cube-sphere.svg"))) self.task_button = MainWindowButton(self.buttons_widget) self.task_button.setToolTip("Create tasks") self.task_button.clicked.connect(self.task_button_clicked.emit) self.task_button.setIcon( QIcon(os.path.join(os.getcwd(), "resources/icons/list-check.svg"))) self.disconnect_button = MainWindowButton(self.buttons_widget) self.disconnect_button.setToolTip("Kick") self.disconnect_button.setIcon( QIcon(os.path.join(os.getcwd(), "resources/icons/wifi-off.svg"))) self.terminate_button = MainWindowButton(self.buttons_widget) self.terminate_button.setToolTip("Terminate") self.terminate_button.setIcon( QIcon(os.path.join(os.getcwd(), "resources/icons/user-off.svg"))) self.close_button = MainWindowButton(self.buttons_widget) self.close_button.setToolTip("Close") self.close_button.clicked.connect(self.window().closeClicked.emit) self.close_button.setIcon( QIcon(os.path.join(os.getcwd(), "resources/icons/x.svg"))) self.buttons_widgetLayout.addWidget(self.payload_button) self.buttons_widgetLayout.addWidget(self.task_button) self.buttons_widgetLayout.addWidget(self.terminate_button) self.buttons_widgetLayout.addWidget(self.disconnect_button) self.buttons_widgetLayout.addStretch(1) self.buttons_widgetLayout.addWidget(self.close_button) self.main_layout.addWidget(self.buttons_widget) @Slot(QModelIndex) def on_botTable_doubleClicked(self, index: QModelIndex): self.bot_double_clicked.emit( self.table_model.getDeviceById( self.table_model.index(index.row(), 0).data())) def add_bot(self, bot_id, ip, port, kwargs={}): bot = Bot(None, bot_id, ip, port, **kwargs) bot.update_map.connect(lambda marker_id, loc: self.map.addMarker( marker_id, loc[0], loc[1])) self.table_model.appendDevice(bot) def update_bot(self, bot_id, kwargs): self.table_model.updateDevice(bot_id, kwargs) def remove_bot(self, bot_id): self.table_model.removeDevice(bot_id) @Slot(Log) def on_bot_log(self, log): bot = self.table_model.getDeviceById(log.device_id) if bot: bot.logs.append(log) bot.updated.emit() @Slot(int) def get_bot_by_id(self, bot_id): return self.table_model.getDeviceById(bot_id)
def __init__(self, parent, prefix, name, action, version, versions, packages_sizes, active_channels): super(CondaPackageActionDialog, self).__init__(parent) self._parent = parent self._prefix = prefix self._version_text = None self._name = name self._dependencies_dic = {} self._active_channels = active_channels self._packages_sizes = packages_sizes self.api = ManagerAPI() # Widgets self.label = QLabel(self) self.combobox_version = QComboBox() self.label_version = QLabel(self) self.widget_version = None self.table_dependencies = None self.checkbox = QCheckBox(_("Install dependencies (recommended)")) self.bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.button_ok = self.bbox.button(QDialogButtonBox.Ok) self.button_cancel = self.bbox.button(QDialogButtonBox.Cancel) self.button_cancel.setDefault(True) self.button_cancel.setAutoDefault(True) dialog_size = QSize(300, 90) # Helper variable values action_title = { C.ACTION_UPGRADE: _("Upgrade package"), C.ACTION_DOWNGRADE: _("Downgrade package"), C.ACTION_REMOVE: _("Remove package"), C.ACTION_INSTALL: _("Install package"), } # FIXME: There is a bug, a package installed by anaconda has version # astropy 0.4 and the linked list 0.4 but the available versions # in the json file do not include 0.4 but 0.4rc1... so... # temporal fix is to check if inside list otherwise show full list if action == C.ACTION_UPGRADE: if version in versions: index = versions.index(version) combo_versions = versions[index + 1 :] else: versions = versions elif action == C.ACTION_DOWNGRADE: if version in versions: index = versions.index(version) combo_versions = versions[:index] else: versions = versions elif action == C.ACTION_REMOVE: combo_versions = [version] self.combobox_version.setEnabled(False) elif action == C.ACTION_INSTALL: combo_versions = versions # Reverse order for combobox combo_versions = list(reversed(combo_versions)) if len(versions) == 1: if action == C.ACTION_REMOVE: labeltext = _("Package version to remove:") else: labeltext = _("Package version available:") self.label_version.setText(combo_versions[0]) self.widget_version = self.label_version else: labeltext = _("Select package version:") self.combobox_version.addItems(combo_versions) self.widget_version = self.combobox_version self.label.setText(labeltext) self.label_version.setAlignment(Qt.AlignLeft) self.table_dependencies = QWidget(self) layout = QVBoxLayout() version_layout = QHBoxLayout() version_layout.addWidget(self.label) version_layout.addStretch() version_layout.addWidget(self.widget_version) layout.addLayout(version_layout) self.widgets = [self.checkbox, self.button_ok, self.widget_version, self.table_dependencies] # Create a Table if action in [C.ACTION_INSTALL, C.ACTION_UPGRADE, C.ACTION_DOWNGRADE]: table = QTableView(self) dialog_size = QSize(dialog_size.width() + 40, 300) self.table_dependencies = table layout.addWidget(self.checkbox) self.checkbox.setChecked(True) self._changed_version(versions[0]) table.setSelectionBehavior(QAbstractItemView.SelectRows) table.verticalHeader().hide() table.horizontalHeader().hide() table.setAlternatingRowColors(True) table.setShowGrid(False) table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) table.horizontalHeader().setStretchLastSection(True) layout.addWidget(self.table_dependencies) layout.addWidget(self.bbox) title = "{0}: {1}".format(action_title[action], name) self.setLayout(layout) self.setMinimumSize(dialog_size) self.setFixedSize(dialog_size) self.setWindowTitle(title) self.setModal(True) # Signals and slots self.bbox.accepted.connect(self.accept) self.bbox.rejected.connect(self.close) self.combobox_version.currentIndexChanged.connect(self._changed_version) self.checkbox.stateChanged.connect(self._changed_checkbox)
class ConfigManagerWindow(SiriusMainWindow): """Window to manage offline configuration of BO and SI devices. This window allows the user to create new configurations as well as interpolate or apply a tune or chromaticity delta to a configuration. """ NEW_CONFIGURATION = 0 def __init__(self, config_type, parent=None): """Init UI.""" super(ConfigManagerWindow, self).__init__(parent) self._config_type = config_type self._model = ConfigModel(self._config_type) self._delegate = ConfigDelegate() self._setup_ui() self.ld_cur_state_btn.clicked.connect(self._loadCurrentConfiguration) self.ld_config_btn.clicked.connect(self._addConfiguration) self.delete_config_btn.clicked.connect(self._removeConfiguration) self.setGeometry(100, 100, 1600, 900) self.setWindowTitle("Configuration Manager") self.show() def _setup_ui(self): self.central_widget = QWidget() self.central_widget.layout = QHBoxLayout() self.button_box = QVBoxLayout() self.ld_cur_state_btn = QPushButton("Load Current State") self.ld_config_btn = QPushButton("Load Configuration") self.ld_config_btn.setShortcut(QKeySequence.New) self.delete_config_btn = QPushButton("Delete Configuration") self.button_box.addWidget(self.delete_config_btn) self.button_box.addWidget(self.ld_config_btn) self.button_box.addWidget(self.ld_cur_state_btn) self.button_box.addStretch() # TableView self.table = QTableView(self) self.table.setModel(self._model) self.table.setItemDelegate(self._delegate) # self.table.setSelectionBehavior(QAbstractItemView.SelectColumns) self.table.setContextMenuPolicy(Qt.CustomContextMenu) self.table.customContextMenuRequested.connect(self._showHeaderMenu) self.table.resizeColumnsToContents() self.table.resizeRowsToContents() # TableView Headers self.headers = self.table.horizontalHeader() self.headers.setContextMenuPolicy(Qt.CustomContextMenu) self.headers.customContextMenuRequested.connect(self._showHeaderMenu) self.central_widget.layout.addLayout(self.button_box) self.central_widget.layout.addWidget(self.table) self.central_widget.setLayout(self.central_widget.layout) # Set widget self.setCentralWidget(self.central_widget) def closeEvent(self, event): """Close window. The user is warned if there are any unsaved changes. """ columns = list(range(len(self._model.configurations))) columns.sort(reverse=True) if not self._closeConfigurations(columns): event.ignore() def keyPressEvent(self, event): """Override keyPressEvent. Ctrl+S - Save changes Ctrl+W - Close configuration on focus F2 - Rename configuration on focus Ctrl+Z - Undo Ctrl+R - Redo """ if event.key() == Qt.Key_S: self._saveChanges() return if event.key() == Qt.Key_W: self._closeConfigurationOnFocus() return if event.key() == Qt.Key_F2: self._renameOnFocus() return if event.key() == Qt.Key_Z: print(self._model._undo) if len(self._model._undo) > 0: self._model._undo.pop()[1]() return if event.key() == Qt.Key_R: if len(self._model._redo) > 0: self._model._redo.pop()[1]() return @Slot(QPoint) def _showHeaderMenu(self, point): column = self.headers.logicalIndexAt(point.x()) if column == -1: return menu = QMenu(self) # Actions cols = self.table.selectionModel().selectedColumns() if len(cols) != 2 or column not in [col.column() for col in cols]: self.table.selectColumn(column) menu.aboutToHide.connect(lambda: self.table.clearSelection()) save = QAction("Save", menu) save.triggered.connect(lambda: self._saveConfiguration(column)) save_all = QAction("Save all", menu) save_all.triggered.connect(lambda: self._saveChanges()) save_all.setShortcut(QKeySequence.Save) rename = QAction("Rename", menu) rename.triggered.connect(lambda: self._renameConfiguration(column)) close = QAction("Close", menu) close.triggered.connect(lambda: self._closeConfiguration(column)) close.setShortcut(QKeySequence.Close) close_right = QAction("Close to the right", menu) close_right.triggered.connect( lambda: self._closeConfigurationsToTheRight(column)) close_others = QAction("Close other", menu) close_others.triggered.connect( lambda: self._closeOtherConfigurations(column)) close_all = QAction("Close all", menu) close_all.triggered.connect(lambda: self._closeAllConfigurations()) tune = QAction("Tune", menu) tune.triggered.connect(lambda: self._tuneConfiguration(column)) menu.addActions([save, save_all]) menu.addSeparator() menu.addActions([rename]) menu.addSeparator() menu.addActions([close, close_right, close_others, close_all]) menu.addSeparator() menu.addActions([tune]) else: bar = QAction("Interpolate", menu) bar.triggered.connect(lambda: self._barConfiguration(cols)) menu.addAction(bar) vheader_offset = self.table.verticalHeader().width() point.setX(point.x() + vheader_offset) menu.popup(self.mapToGlobal(point)) # ContextMenu Actions @Slot(int) def _saveConfiguration(self, column): try: self._model.saveConfiguration(column) return True except Exception as e: QMessageBox(QMessageBox.Warning, "Failed to save data", "{}, {}".format(e, type(e))).exec_() return False @Slot(int) def _renameConfiguration(self, column): new_name, ok = QInputDialog.getText(self, "New name", "Rename to:") if ok and new_name: return self._model.renameConfiguration(column, new_name) @Slot(int) def _closeConfiguration(self, column): self._closeConfigurations([column]) @Slot(int) def _closeConfigurationsToTheRight(self, column): columns = list() i = len(self._model.configurations) - 1 while i > column: columns.append(i) i -= 1 self._closeConfigurations(columns) @Slot(int) def _closeOtherConfigurations(self, column): columns = list() i = len(self._model.configurations) - 1 while i >= 0: if i == column: i -= 1 continue columns.append(i) i -= 1 self._closeConfigurations(columns) @Slot() def _closeAllConfigurations(self): columns = list() i = len(self._model.configurations) - 1 while i >= 0: columns.append(i) i -= 1 self._closeConfigurations(columns) def _closeConfigurations(self, columns): save = self._maybeSaveChanges(columns) if save == QMessageBox.Discard: for column in columns: self._model.cleanUndo(column) self._model.closeConfiguration(column) return True elif save == QMessageBox.Save: for column in columns: if self._saveConfiguration(column): self._model.cleanUndo(column) self._model.closeConfiguration(column) else: return False return True else: return False @Slot(int) def _tuneConfiguration(self, column): dlg = TuneDlg(self) ok1 = dlg.exec_() if ok1: # Get Matrix Calculate deltaK and show to user tune = [dlg.tune_x.value(), dlg.tune_y.value()] try: inv_matrix = self._model.getTuneMatrix() except Exception as e: self._showWarningBox("{}".format(e), "Failed to retrieve tune matrix") else: delta_f = tune[0] * inv_matrix[0][0] + tune[1] * inv_matrix[0][ 1] delta_d = tune[0] * inv_matrix[1][0] + tune[1] * inv_matrix[1][ 1] # config_name, ok2 = QInputDialog.getText( # self, "Select value", "New Configuration Name:") # if ok2: # if not config_name: proceed = QMessageBox(QMessageBox.Question, "Delta K", ("\u0394K<sub>d</sub> = {:1.3f}<br>" "\u0394K<sub>f</sub> = {:1.3f}<br>" "Proceed?").format(delta_d, delta_f), QMessageBox.Ok | QMessageBox.Cancel, self).exec_() if proceed == QMessageBox.Ok: config_name = self._getNextName() self._model.deriveConfiguration(config_name, column, ConfigModel.TUNE, [delta_d, delta_f]) @Slot(int) def _barConfiguration(self, cols): if len(cols) != 2: raise SystemError("Must interpolate 2 columns") new_name, ok = QInputDialog.getText(self, "New name", "Rename to:") if ok: if not new_name: new_name = self._getNextName() self._model.interpolateConfiguration(new_name, cols[0].column(), cols[1].column()) # Window menu slots @Slot() def _addConfiguration(self): try: configs = self._model.getConfigurations() except Exception as e: self._showWarningBox(e, "Failed to retrieve configurations") else: if configs: options = [item["name"] for item in configs] config_name, ok = QInputDialog.getItem( self, "Available Configurations", "Select a configuration:", options, 0, False) if ok and config_name: if not self._isConfigurationLoaded(config_name): self._model.loadConfiguration(name=config_name) else: QMessageBox( QMessageBox.Information, "Configuration already loaded", "Configuration is already loaded.").exec_() # Highlight new column; or the one that is already loaded col = self._model.getConfigurationColumn(config_name) self.table.selectColumn(col) else: self._showMessageBox("No configuration found") return @Slot() def _removeConfiguration(self): try: configs = self._model.getConfigurations() except Exception as e: self._showWarningBox(e, "Failed to retrieve configurations") else: if configs: # Show configs available options = [item["name"] for item in configs] config, ok = QInputDialog.getItem(self, "Available Configurations", "Select a configuration:", options, 0, False) if ok and config: # Ask for confirmation if self._isConfigurationLoaded(config): msg = ("Configuration is currenty loaded." "Delete it anyway?") else: msg = ("This will permanently delete configuration {}." "Proceed?").format(config) if self._showDialogBox(msg) == QMessageBox.Cancel: return # Delete configuration config = configs[options.index(config)] try: self._model.deleteConfiguration(config) except Exception as e: self._showWarningBox(e) else: self._showMessageBox( "Configuration {} was deleted.".format( config['name'])) else: self._showMessageBox("No configuration found") return @Slot() def _loadCurrentConfiguration(self): try: t = LoadingThread(self._getNextName(), self._model._vertical_header, self) dlg = \ LoadingDialog("Loading", len(self._model._vertical_header), self) t.taskUpdated.connect(dlg.update) t.taskFinished.connect(dlg.done) t.start() dlg.exec_() except Exception as e: self._showWarningBox("{}".format(e)) # Actions binded with keys def _saveChanges(self): for column in range(len(self._model.configurations)): self._saveConfiguration(column) def _closeConfigurationOnFocus(self): cols = self.table.selectionModel().selectedColumns() columns = list() for col in cols: columns.append(col.column()) columns.sort(reverse=True) self._closeConfigurations(columns) def _renameOnFocus(self): cols = self.table.selectionModel().selectedColumns() if len(cols) == 1: self._renameConfiguration(cols[0].column()) # Helpers def _isConfigurationLoaded(self, config_name): ret = self._model.getConfigurationColumn(config_name) if ret == -1: return False return True def _getNextName(self): # Treat if there already exist saved configuration with this name configs = self._model.getConfigurations(deleted=None) configs = [item["name"] for item in configs] configs.extend([item.name for item in self._model.configurations]) new_name = 'config-{}'.format(self.NEW_CONFIGURATION) while new_name in configs: self.NEW_CONFIGURATION += 1 new_name = 'config-{}'.format(self.NEW_CONFIGURATION) return new_name def _maybeSaveChanges(self, columns): ask_to_save = False for column in columns: if self._model.configurations[column].dirty: ask_to_save = True break # If nothing to save, will close all columns if not ask_to_save: return QMessageBox.Discard # Ask if user wants to save changes msg_box = QMessageBox( QMessageBox.Question, "There are unsaved changes", "Keep changes?", QMessageBox.Save | QMessageBox.Cancel | QMessageBox.Discard, self) return msg_box.exec_() def _showWarningBox(self, message, title="Warning"): QMessageBox(QMessageBox.Warning, title, '{}'.format(message)).exec_() def _showMessageBox(self, message, title="Message"): return QMessageBox(QMessageBox.Information, title, message).exec_() def _showDialogBox(self, message, title="Dialog"): return QMessageBox(QMessageBox.Information, title, message, QMessageBox.Ok | QMessageBox.Cancel).exec_()
def __init__(self, parent, prefix, name, action, version, versions, packages_sizes, active_channels): super(CondaPackageActionDialog, self).__init__(parent) self._parent = parent self._prefix = prefix self._version_text = None self._name = name self._dependencies_dic = {} self._active_channels = active_channels self._packages_sizes = packages_sizes self.api = ManagerAPI() # Widgets self.label = QLabel(self) self.combobox_version = QComboBox() self.label_version = QLabel(self) self.widget_version = None self.table_dependencies = None self.checkbox = QCheckBox(_('Install dependencies (recommended)')) self.bbox = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.button_ok = self.bbox.button(QDialogButtonBox.Ok) self.button_cancel = self.bbox.button(QDialogButtonBox.Cancel) self.button_cancel.setDefault(True) self.button_cancel.setAutoDefault(True) dialog_size = QSize(300, 90) # Helper variable values action_title = { C.ACTION_UPGRADE: _("Upgrade package"), C.ACTION_DOWNGRADE: _("Downgrade package"), C.ACTION_REMOVE: _("Remove package"), C.ACTION_INSTALL: _("Install package") } # FIXME: There is a bug, a package installed by anaconda has version # astropy 0.4 and the linked list 0.4 but the available versions # in the json file do not include 0.4 but 0.4rc1... so... # temporal fix is to check if inside list otherwise show full list if action == C.ACTION_UPGRADE: if version in versions: index = versions.index(version) combo_versions = versions[index + 1:] else: versions = versions elif action == C.ACTION_DOWNGRADE: if version in versions: index = versions.index(version) combo_versions = versions[:index] else: versions = versions elif action == C.ACTION_REMOVE: combo_versions = [version] self.combobox_version.setEnabled(False) elif action == C.ACTION_INSTALL: combo_versions = versions # Reverse order for combobox combo_versions = list(reversed(combo_versions)) if len(versions) == 1: if action == C.ACTION_REMOVE: labeltext = _('Package version to remove:') else: labeltext = _('Package version available:') self.label_version.setText(combo_versions[0]) self.widget_version = self.label_version else: labeltext = _("Select package version:") self.combobox_version.addItems(combo_versions) self.widget_version = self.combobox_version self.label.setText(labeltext) self.label_version.setAlignment(Qt.AlignLeft) self.table_dependencies = QWidget(self) layout = QVBoxLayout() version_layout = QHBoxLayout() version_layout.addWidget(self.label) version_layout.addStretch() version_layout.addWidget(self.widget_version) layout.addLayout(version_layout) self.widgets = [ self.checkbox, self.button_ok, self.widget_version, self.table_dependencies ] # Create a Table if action in [C.ACTION_INSTALL, C.ACTION_UPGRADE, C.ACTION_DOWNGRADE]: table = QTableView(self) dialog_size = QSize(dialog_size.width() + 40, 300) self.table_dependencies = table layout.addWidget(self.checkbox) self.checkbox.setChecked(True) self._changed_version(versions[0]) table.setSelectionBehavior(QAbstractItemView.SelectRows) table.verticalHeader().hide() table.horizontalHeader().hide() table.setAlternatingRowColors(True) table.setShowGrid(False) table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) table.horizontalHeader().setStretchLastSection(True) layout.addWidget(self.table_dependencies) layout.addWidget(self.bbox) title = "{0}: {1}".format(action_title[action], name) self.setLayout(layout) self.setMinimumSize(dialog_size) self.setFixedSize(dialog_size) self.setWindowTitle(title) self.setModal(True) # Signals and slots self.bbox.accepted.connect(self.accept) self.bbox.rejected.connect(self.close) self.combobox_version.currentIndexChanged.connect( self._changed_version) self.checkbox.stateChanged.connect(self._changed_checkbox)
class CompositionElementalWidget(_CompositionWidget): class _CompositionModel(QAbstractTableModel): def __init__(self): QAbstractTableModel.__init__(self) self.composition = OrderedDict() def rowCount(self, *args, **kwargs): return len(self.composition) def columnCount(self, *args, **kwargs): return 2 def data(self, index, role): if not index.isValid() or \ not (0 <= index.row() < len(self.composition)): return None if role == Qt.TextAlignmentRole: return Qt.AlignCenter if role != Qt.DisplayRole: return None z, fraction = list(self.composition.items())[index.row()] column = index.column() if column == 0: if z is None: return 'none' else: return str(get_symbol(z)) elif column == 1: return str(fraction) def headerData(self, section , orientation, role): if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: if section == 0: return 'Element' elif section == 1: return 'Fraction' elif orientation == Qt.Vertical: return str(section + 1) def flags(self, index): if not index.isValid(): return Qt.ItemIsEnabled return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable) def setData(self, index, value, role=Qt.EditRole): if not index.isValid() or \ not (0 <= index.row() < len(self.composition)): return False z = list(self.composition.keys())[index.row()] column = index.column() if column == 0: if value in self.composition: return False fraction = self.composition.pop(z) self.composition[value] = fraction elif column == 1: self.composition[z] = float(value) self.dataChanged.emit(index, index) return True def insertRows(self, row, count=1, parent=None): if count == 0: return False if parent is None: parent = QModelIndex() self.beginInsertRows(parent, row, row + count - 1) if None in self.composition: return False self.composition[None] = 0.0 self.endInsertRows() return True def removeRows(self, row, count=1, parent=None): if count == 0: return False if parent is None: parent = QModelIndex() self.beginRemoveRows(parent, row, count + row - 1) keys = list(self.composition.keys()) for key in keys[:row] + keys[row + count:]: self.composition.pop(key) self.endRemoveRows() return True class _CompositionDelegate(QItemDelegate): def __init__(self, parent=None): QItemDelegate.__init__(self, parent) def createEditor(self, parent, option, index): column = index.column() if column == 0: editor = PeriodicTableDialog(parent) editor.setMultipleSelection(False) editor.setRequiresSelection(True) return editor elif column == 1: editor = QLineEdit(parent) editor.setValidator(QDoubleValidator()) return editor else: return QItemDelegate.createEditor(self, parent, option, index) def setEditorData(self, editor, index): text = index.model().data(index, Qt.DisplayRole) column = index.column() if column == 0: if text != 'none': editor.setSelection(text) elif column == 1: editor.setText(text) else: QItemDelegate.setEditorData(self, editor, index) def setModelData(self, editor, model, index): column = index.column() if column == 0: model.setData(index, editor.selection()) elif column == 1: model.setData(index, editor.text()) else: return QItemDelegate.setModelData(self, editor, model, index) def __init__(self, parent=None): _CompositionWidget.__init__(self, CompositionElemental, parent) def _init_ui(self): # Widgets model = self._CompositionModel() self._table = QTableView() self._table.setModel(model) self._table.setItemDelegate(self._CompositionDelegate(self)) self._table.horizontalHeader().setStretchLastSection(True) self._toolbar = QToolBar() action_add = self._toolbar.addAction(getIcon("list-add"), "Add layer") action_remove = self._toolbar.addAction(getIcon("list-remove"), "Remove layer") # Layouts layout = _CompositionWidget._init_ui(self) layout.addRow(self._table) layout.addRow(self._toolbar) # Signals action_add.triggered.connect(self._on_add) action_remove.triggered.connect(self._on_remove) model.dataChanged.connect(self.edited) model.rowsInserted.connect(self.edited) model.rowsRemoved.connect(self.edited) return layout def _on_add(self): index = self._table.selectionModel().currentIndex() model = self._table.model() model.insertRows(index.row() + 1) def _on_remove(self): selection = self._table.selectionModel().selection().indexes() if len(selection) == 0: QMessageBox.warning(self, "Window layer", "Select a layer") return model = self._table.model() for row in sorted(map(methodcaller('row'), selection), reverse=True): model.removeRow(row) def _create_parameter(self): return self.CLASS('wt%') def parameter(self, parameter=None): parameter = _CompositionWidget.parameter(self, parameter) parameter.update(self._table.model().composition) return parameter def setParameter(self, condition): _CompositionWidget.setParameter(self, condition) self._table.model().composition.update(condition) self._table.model().reset() def setReadOnly(self, state): _CompositionWidget.setReadOnly(self, state) if state: trigger = QTableView.EditTrigger.NoEditTriggers else: trigger = QTableView.EditTrigger.AllEditTriggers self._table.setEditTriggers(trigger) self._toolbar.setEnabled(not state) def isReadOnly(self): return _CompositionWidget.isReadOnly(self) and \ self._table.editTriggers() == QTableView.EditTrigger.NoEditTriggers and \ not self._toolbar.isEnabled()
class ConfigurationManager(SiriusMainWindow): """.""" NAME_COL = None CONFIG_TYPE_COL = None def __init__(self, model, parent=None): """Constructor.""" super().__init__(parent) self._model = model self._logger = logging.getLogger(__name__) self._logger.setLevel(logging.INFO) self._setup_ui() self.setWindowTitle("Configuration Manager") def _setup_ui(self): # self.setGeometry(0, 0, 1600, 900) self.main_widget = QFrame() self.main_widget.setObjectName('ServConf') self.setCentralWidget(self.main_widget) self.layout = QGridLayout() self.main_widget.setLayout(self.layout) # Basic widgets self.editor = QTableView() self.delete_button = QPushButton('Delete', self) self.delete_button.setObjectName('DeleteButton') self.rename_button = QPushButton('Rename', self) self.rename_button.setObjectName('RenameButton') self.d_editor = QTableView() self.retrieve_button = QPushButton('Retrieve', self) self.retrieve_button.setObjectName('RetrieveButton') self.tree = QTreeView(self) self.config_type = QComboBox(parent=self) self.config_type.setModel( ConfigTypeModel(self._model, self.config_type)) # Tab widgets self.tab1 = QWidget() self.tab1.layout = QVBoxLayout(self.tab1) self.tab2 = QWidget() self.tab2.layout = QVBoxLayout(self.tab2) self.tab1.layout.addWidget(self.editor) hlay = QHBoxLayout() hlay.addWidget(self.rename_button) hlay.addWidget(self.delete_button) self.tab1.layout.addLayout(hlay) self.tab2.layout.addWidget(self.d_editor) self.tab2.layout.addWidget(self.retrieve_button) self.editor_tab = QTabWidget(self) self.editor_tab.addTab(self.tab1, 'Configurations') self.editor_tab.addTab(self.tab2, 'Discarded Configurations') self.config_viewer = QWidget(self) self.config_viewer.layout = QVBoxLayout(self.config_viewer) self.config_viewer.layout.addWidget(self.editor_tab) self.config_viewer.layout.addWidget(self.tree) # Header widget self.header = QFrame(self) self.header.setObjectName('Header') self.header.layout = QHBoxLayout(self.header) self.header.layout.addStretch() self.header.layout.addWidget( QLabel('Configuration Database Manager', self.header)) self.header.layout.addStretch() # Sub header with database genral information self.sub_header = QFrame(self) self.sub_header.setObjectName('SubHeader') self.sub_header.layout = QVBoxLayout(self.sub_header) self.server_layout = QHBoxLayout() self.server_layout.addWidget(QLabel('<b>Server:</b>', self.sub_header)) self.server_layout.addWidget(QLabel(self._model.url, self.sub_header)) self.server_layout.addStretch() self.size_layout = QHBoxLayout() self.size_layout.addWidget(QLabel('<b>DB Size:</b>', self.sub_header)) try: dbsize = self._model.get_dbsize() dbsize = '{:.2f} MB'.format(dbsize/(1024*1024)) except ConfigDBException: dbsize = 'Failed to retrieve information' self.size_layout.addWidget(QLabel(dbsize, self.sub_header)) self.size_layout.addStretch() self.sub_header.layout.addLayout(self.server_layout) self.sub_header.layout.addLayout(self.size_layout) # Query form self.query_form = QFrame() self.query_form.setObjectName("QueryForm") self.query_form.layout = QVBoxLayout() self.query_form.setLayout(self.query_form.layout) self.configs_layout = QGridLayout() self.configs_layout.addWidget(QLabel('Configurations:', self), 0, 0) self.nr_configs = QLabel(self) self.configs_layout.addWidget(self.nr_configs, 0, 1) self.configs_layout.addWidget(QLabel('Discarded:', self), 0, 2) self.nr_discarded = QLabel(self) self.configs_layout.addWidget(self.nr_discarded, 0, 3) self.query_form.layout.addWidget(self.config_type) self.query_form.layout.addLayout(self.configs_layout) # Main widget layout setup self.layout.addWidget(self.header, 0, 0, 1, 3) self.layout.addWidget(self.sub_header, 1, 0, 1, 2) self.layout.addWidget(self.query_form, 2, 0, 1, 2) self.layout.addWidget(self.config_viewer, 3, 0, 1, 2) self.layout.addWidget(self.tree, 1, 2, 4, 1) # self.layout.addWidget(self.delete_button, 4, 0, 1, 2) self.layout.setColumnStretch(0, 1) self.layout.setColumnStretch(1, 2) self.layout.setColumnStretch(2, 2) # Set table models and options self.editor_model = ConfigDbTableModel('notexist', self._model) self.d_editor_model = ConfigDbTableModel('notexist', self._model, True) self.editor.setModel(self.editor_model) self.editor.setSelectionBehavior(self.editor.SelectRows) self.editor.setSortingEnabled(True) self.editor.horizontalHeader().setResizeMode(QHeaderView.Stretch) self.d_editor.setModel(self.d_editor_model) self.d_editor.setSelectionBehavior(self.editor.SelectRows) self.d_editor.setSortingEnabled(True) self.d_editor.horizontalHeader().setResizeMode(QHeaderView.Stretch) self.d_editor.setSelectionMode(self.d_editor.SingleSelection) # Set tree model and options self.tree_model = JsonTreeModel(None, None, self._model) self.tree.setModel(self.tree_model) # Delete button self.delete_button.setEnabled(False) self.rename_button.setEnabled(True) self.retrieve_button.setEnabled(False) # Signals and slots # Tab self.editor_tab.currentChanged.connect(self._tab_changed) # Fill tables when configuration is selected self.config_type.currentTextChanged.connect(self._fill_table) # Fill tree when a configuration is selected self.editor.selectionModel().selectionChanged.connect( lambda x, y: self._fill_tree()) self.d_editor.selectionModel().selectionChanged.connect( lambda x, y: self._fill_tree()) # Connect database error to slot that show messages self.editor_model.connectionError.connect(self._database_error) self.d_editor_model.connectionError.connect(self._database_error) # Makes tree column extend automatically to show content self.tree.expanded.connect( lambda idx: self.tree.resizeColumnToContents(idx.column())) # Button action self.delete_button.pressed.connect(self._remove_configuration) self.rename_button.pressed.connect(self._rename_configuration) self.retrieve_button.pressed.connect(self._retrieve_configuration) # Set constants ConfigurationManager.NAME_COL = \ self.editor_model.horizontalHeader.index('name') ConfigurationManager.CONFIG_TYPE_COL = \ self.editor_model.horizontalHeader.index('config_type') self.editor.resizeColumnsToContents() self.d_editor.resizeColumnsToContents() @Slot(str) def _fill_table(self, config_type): """Fill table with configuration of `config_type`.""" leng = len(self._model.find_configs( config_type=config_type, discarded=False)) self.nr_configs.setText(str(leng)) leng = len(self._model.find_configs( config_type=config_type, discarded=True)) self.nr_discarded.setText(str(leng)) self.editor_model.setupModelData(config_type) self.d_editor_model.setupModelData(config_type) self.editor.resizeColumnsToContents() self.d_editor.resizeColumnsToContents() self.editor_model.sort(2, Qt.DescendingOrder) self.d_editor_model.sort(2, Qt.DescendingOrder) @Slot() def _fill_tree(self): if self.editor_tab.currentIndex() == 0: configs = list() rows = self._get_selected_rows(self.editor) # Get selected rows for row in rows: # Get name and configuration type configs.append(self._type_name(row, self.editor_model)) # Set tree data self.tree_model.setupModelData(configs) if len(configs) == 1: self.delete_button.setEnabled(True) self.delete_button.setText( 'Delete {} ({})'.format(configs[0][1], configs[0][0])) self.rename_button.setEnabled(True) self.rename_button.setText( 'Rename {} ({})'.format(configs[0][1], configs[0][0])) elif len(configs) > 1: self.rename_button.setEnabled(False) self.rename_button.setText('Rename') self.delete_button.setEnabled(True) self.delete_button.setText( 'Delete {} configurations'.format(len(configs))) else: self.rename_button.setEnabled(False) self.rename_button.setText('Rename') self.delete_button.setEnabled(False) self.delete_button.style().polish(self.delete_button) self.rename_button.style().polish(self.rename_button) else: try: row = self._get_selected_rows(self.d_editor).pop() except KeyError: self.retrieve_button.setEnabled(False) self.retrieve_button.style().polish(self.retrieve_button) else: config_type, name = self._type_name(row, self.d_editor_model) self.tree_model.setupModelData([(config_type, name)]) self.retrieve_button.setEnabled(True) self.retrieve_button.style().polish(self.retrieve_button) # self.tree.resizeColumnsToContents() @Slot() def _remove_configuration(self): type = QMessageBox.Question title = 'Remove configuration?' buttons = QMessageBox.Ok | QMessageBox.Cancel # self.editor.selectRow(index.row()) rows = list(self._get_selected_rows(self.editor)) message = 'Remove configurations:\n' for row in rows: config_type = self.editor_model.createIndex(row, 0).data() name = self.editor_model.createIndex(row, 1).data() message += '- {} ({})\n'.format(name, config_type) msg = QMessageBox(type, title, message, buttons).exec_() if msg == QMessageBox.Ok: rows.sort(reverse=True) for row in rows: self.editor_model.removeRows(row) self.editor.selectionModel().clearSelection() self._fill_table(self.config_type.currentText()) @Slot() def _rename_configuration(self): # self.editor.selectRow(index.row()) rows = list(self._get_selected_rows(self.editor)) if not rows: return config_type = self.editor_model.createIndex(rows[0], 0).data() name = self.editor_model.createIndex(rows[0], 1).data() wid = RenameConfigDialog(config_type, self) wid.setWindowTitle('Rename: {}'.format(name)) wid.search_le.setText(name) newname, status = wid.exec_() if not newname or not status: return self._model.rename_config( name, newname, config_type=config_type) self.editor.selectionModel().clearSelection() self._fill_table(self.config_type.currentText()) @Slot() def _retrieve_configuration(self): type = QMessageBox.Question title = 'Retrieve configuration?' buttons = QMessageBox.Ok | QMessageBox.Cancel try: row = self._get_selected_rows(self.d_editor).pop() except KeyError: pass else: config_type, name = self._type_name(row, self.d_editor_model) name = name[:-37] message = \ 'Retrieve configuration {} ({})?'.format(config_type, name) msg = QMessageBox(type, title, message, buttons).exec_() if msg == QMessageBox.Ok: try: self.d_editor_model.removeRows(row) except TypeError: self._database_error( 'Exception', 'Configuration no longer is in the correct format', 'retrieve configuration') self.editor.selectionModel().clearSelection() self._fill_table(self.config_type.currentText()) @Slot(int) def _tab_changed(self, index): if index == 0: self.editor.selectionModel().clearSelection() self.delete_button.setText('Delete') self.delete_button.setEnabled(False) self.delete_button.style().polish(self.delete_button) self.rename_button.setText('Rename') self.rename_button.setEnabled(False) self.rename_button.style().polish(self.rename_button) else: self.d_editor.selectionModel().clearSelection() self.retrieve_button.setEnabled(False) self.retrieve_button.style().polish(self.retrieve_button) self.tree_model.setupModelData([]) @Slot(int, str, str) def _database_error(self, code, message, operation): type = QMessageBox.Warning title = 'Something went wrong' msg = '{}: {}, while trying to {}'.format(code, message, operation) QMessageBox(type, title, msg).exec_() def _get_selected_rows(self, table): index_list = table.selectionModel().selectedIndexes() return {idx.row() for idx in index_list} def _type_name(self, row, model): # Return config_type and name given a row and a table model return (model.createIndex(row, self.CONFIG_TYPE_COL).data(), model.createIndex(row, self.NAME_COL).data())
class BasePlotCurveEditorDialog(QDialog): """QDialog that is used in Qt Designer to edit the properties of the curves in a waveform plot. This dialog is shown when you double-click the plot, or when you right click it and choose 'edit curves'. This thing is mostly just a wrapper for a table view, with a couple buttons to add and remove curves, and a button to save the changes.""" TABLE_MODEL_CLASS = BasePlotCurvesModel def __init__(self, plot, parent=None): super(BasePlotCurveEditorDialog, self).__init__(parent) self.plot = plot self.setup_ui() self.table_model = self.TABLE_MODEL_CLASS(self.plot) self.table_view.setModel(self.table_model) self.table_model.plot = plot # self.table_view.resizeColumnsToContents() self.add_button.clicked.connect(self.addCurve) self.remove_button.clicked.connect(self.removeSelectedCurve) self.remove_button.setEnabled(False) self.table_view.selectionModel().selectionChanged.connect( self.handleSelectionChange) self.table_view.doubleClicked.connect(self.handleDoubleClick) self.resize(800, 300) def setup_ui(self): self.vertical_layout = QVBoxLayout(self) self.table_view = QTableView(self) self.table_view.setEditTriggers(QAbstractItemView.DoubleClicked) self.table_view.setProperty("showDropIndicator", False) self.table_view.setDragDropOverwriteMode(False) self.table_view.setSelectionMode(QAbstractItemView.SingleSelection) self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.table_view.setSortingEnabled(False) self.table_view.horizontalHeader().setStretchLastSection(True) self.table_view.verticalHeader().setVisible(False) self.table_view.setColumnWidth(0, 160) self.table_view.setColumnWidth(1, 160) self.table_view.setColumnWidth(2, 160) self.vertical_layout.addWidget(self.table_view) self.add_remove_layout = QHBoxLayout() spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.add_remove_layout.addItem(spacer) self.add_button = QPushButton("Add Curve", self) self.add_remove_layout.addWidget(self.add_button) self.remove_button = QPushButton("Remove Curve", self) self.add_remove_layout.addWidget(self.remove_button) self.vertical_layout.addLayout(self.add_remove_layout) self.button_box = QDialogButtonBox(self) self.button_box.setOrientation(Qt.Horizontal) self.button_box.addButton("Done", QDialogButtonBox.AcceptRole) self.vertical_layout.addWidget(self.button_box) self.button_box.accepted.connect(self.saveChanges) self.button_box.rejected.connect(self.reject) self.setWindowTitle("Waveform Curve Editor") def setup_delegate_columns(self, index=2): symbol_delegate = SymbolColumnDelegate(self) self.table_view.setItemDelegateForColumn(index + 3, symbol_delegate) line_delegate = LineColumnDelegate(self) self.table_view.setItemDelegateForColumn(index + 1, line_delegate) color_delegate = ColorColumnDelegate(self) self.table_view.setItemDelegateForColumn(index, color_delegate) @Slot() def addCurve(self): self.table_model.append() @Slot() def removeSelectedCurve(self): self.table_model.removeAtIndex(self.table_view.currentIndex()) @Slot(QItemSelection, QItemSelection) def handleSelectionChange(self, selected, deselected): self.remove_button.setEnabled( self.table_view.selectionModel().hasSelection()) @Slot(QModelIndex) def handleDoubleClick(self, index): if self.table_model.needsColorDialog(index): # The table model returns a QBrush for BackgroundRole, not a QColor init_color = self.table_model.data(index, Qt.BackgroundRole).color() color = QColorDialog.getColor(init_color, self) if color.isValid(): self.table_model.setData(index, color, role=Qt.EditRole) @Slot() def saveChanges(self): formWindow = QDesignerFormWindowInterface.findFormWindow(self.plot) if formWindow: formWindow.cursor().setProperty("curves", self.plot.curves) self.accept()
class SampleLogsView(QSplitter): """Sample Logs View This contains a table of the logs, a plot of the currently selected logs, and the statistics of the selected log. """ def __init__(self, presenter, parent = None, name = '', isMD=False, noExp = 0): super(SampleLogsView, self).__init__(parent) self.presenter = presenter self.setWindowTitle("{} sample logs".format(name)) self.setWindowFlags(Qt.Window) # Create sample log table self.table = QTableView() self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.clicked.connect(self.presenter.clicked) self.table.doubleClicked.connect(self.presenter.doubleClicked) self.table.contextMenuEvent = self.tableMenu self.addWidget(self.table) frame_right = QFrame() layout_right = QVBoxLayout() #Add full_time and experimentinfo options layout_options = QHBoxLayout() if isMD: layout_options.addWidget(QLabel("Experiment Info #")) self.experimentInfo = QSpinBox() self.experimentInfo.setMaximum(noExp-1) self.experimentInfo.valueChanged.connect(self.presenter.changeExpInfo) layout_options.addWidget(self.experimentInfo) self.full_time = QCheckBox("Relative Time") self.full_time.setChecked(True) self.full_time.stateChanged.connect(self.presenter.plot_logs) layout_options.addWidget(self.full_time) layout_right.addLayout(layout_options) # Sample log plot self.fig = Figure() self.canvas = FigureCanvas(self.fig) self.canvas.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding) self.canvas.mpl_connect('button_press_event', self.presenter.plot_clicked) self.ax = self.fig.add_subplot(111, projection='mantid') layout_right.addWidget(self.canvas) # Sample stats self.create_stats_widgets() layout_stats = QFormLayout() layout_stats.addRow('', QLabel("Log Statistics")) layout_stats.addRow('Min:', self.stats_widgets["minimum"]) layout_stats.addRow('Max:', self.stats_widgets["maximum"]) layout_stats.addRow('Mean:', self.stats_widgets["mean"]) layout_stats.addRow('Median:', self.stats_widgets["median"]) layout_stats.addRow('Std Dev:', self.stats_widgets["standard_deviation"]) layout_stats.addRow('Time Avg:', self.stats_widgets["time_mean"]) layout_stats.addRow('Time Std Dev:', self.stats_widgets["time_standard_deviation"]) layout_stats.addRow('Duration:', self.stats_widgets["duration"]) layout_right.addLayout(layout_stats) frame_right.setLayout(layout_right) self.addWidget(frame_right) self.setStretchFactor(0,1) self.resize(1200,800) self.show() def tableMenu(self, event): """Right click menu for table, can plot or print selected logs""" menu = QMenu(self) plotAction = menu.addAction("Plot selected") plotAction.triggered.connect(self.presenter.new_plot_logs) plotAction = menu.addAction("Print selected") plotAction.triggered.connect(self.presenter.print_selected_logs) menu.exec_(event.globalPos()) def set_model(self, model): """Set the model onto the table""" self.model = model self.table.setModel(self.model) self.table.resizeColumnsToContents() self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) def plot_selected_logs(self, ws, exp, rows): """Update the plot with the selected rows""" self.ax.clear() self.create_ax_by_rows(self.ax, ws, exp, rows) self.fig.canvas.draw() def new_plot_selected_logs(self, ws, exp, rows): """Create a new plot, in a separate window for selected rows""" fig, ax = plt.subplots(subplot_kw={'projection': 'mantid'}) self.create_ax_by_rows(ax, ws, exp, rows) fig.show() def create_ax_by_rows(self, ax, ws, exp, rows): """Creates the plots for given rows onto axis ax""" for row in rows: log_text = self.get_row_log_name(row) ax.plot(ws, LogName=log_text, label=log_text, marker='.', FullTime=not self.full_time.isChecked(), ExperimentInfo=exp) ax.set_ylabel('') if ax.get_legend_handles_labels()[0]: ax.legend() def get_row_log_name(self, i): """Returns the log name of particular row""" return str(self.model.item(i, 0).text()) def get_exp(self): """Get set experiment info number""" return self.experimentInfo.value() def get_selected_row_indexes(self): """Return a list of selected row from table""" return [row.row() for row in self.table.selectionModel().selectedRows()] def set_selected_rows(self, rows): """Set seleceted rows in table""" mode = QItemSelectionModel.Select | QItemSelectionModel.Rows for row in rows: self.table.selectionModel().select(self.model.index(row, 0), mode) def create_stats_widgets(self): """Creates the statistics widgets""" self.stats_widgets = {"minimum": QLineEdit(), "maximum": QLineEdit(), "mean": QLineEdit(), "median": QLineEdit(), "standard_deviation": QLineEdit(), "time_mean": QLineEdit(), "time_standard_deviation": QLineEdit(), "duration": QLineEdit()} for widget in self.stats_widgets.values(): widget.setReadOnly(True) def set_statistics(self, stats): """Updates the statistics widgets from stats dictionary""" for param in self.stats_widgets.keys(): self.stats_widgets[param].setText('{:.6}'.format(getattr(stats, param))) def clear_statistics(self): """Clears the values in statistics widgets""" for widget in self.stats_widgets.values(): widget.clear()
class SampleLogsView(QSplitter): """Sample Logs View This contains a table of the logs, a plot of the currently selected logs, and the statistics of the selected log. """ def __init__(self, presenter, parent = None, name = '', isMD=False, noExp = 0): super(SampleLogsView, self).__init__(parent) self.presenter = presenter self.setWindowTitle("{} sample logs".format(name)) self.setWindowFlags(Qt.Window) self.setAttribute(Qt.WA_DeleteOnClose, True) # left hand side self.frame_left = QFrame() layout_left = QVBoxLayout() # add a spin box for MD workspaces if isMD: layout_mult_expt_info = QHBoxLayout() layout_mult_expt_info.addWidget(QLabel("Experiment Info #")) self.experimentInfo = QSpinBox() self.experimentInfo.setMaximum(noExp-1) self.experimentInfo.valueChanged.connect(self.presenter.changeExpInfo) layout_mult_expt_info.addWidget(self.experimentInfo) layout_mult_expt_info.addSpacerItem(QSpacerItem(10, 10, QSizePolicy.Expanding)) layout_left.addLayout(layout_mult_expt_info) # Create sample log table self.table = QTableView() self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.doubleClicked.connect(self.presenter.doubleClicked) self.table.contextMenuEvent = self.tableMenu layout_left.addWidget(self.table) self.frame_left.setLayout(layout_left) self.addWidget(self.frame_left) #right hand side self.frame_right = QFrame() layout_right = QVBoxLayout() #Add full_time and experimentinfo options layout_options = QHBoxLayout() if isMD: layout_options.addWidget(QLabel("Experiment Info #")) self.experimentInfo = QSpinBox() self.experimentInfo.setMaximum(noExp-1) self.experimentInfo.valueChanged.connect(self.presenter.changeExpInfo) layout_options.addWidget(self.experimentInfo) #check boxes self.full_time = QCheckBox("Relative Time") self.full_time.setToolTip( "Shows relative time in seconds from the start of the run.") self.full_time.setChecked(True) self.full_time.stateChanged.connect(self.presenter.plot_logs) layout_options.addWidget(self.full_time) self.show_filtered = QCheckBox("Filtered Data") self.show_filtered.setToolTip( "Filtered data only shows data while running and in this period.\nInvalid values are also filtered.") self.show_filtered.setChecked(True) self.show_filtered.stateChanged.connect(self.presenter.filtered_changed) layout_options.addWidget(self.show_filtered) self.spaceItem = QSpacerItem(10, 10, QSizePolicy.Expanding) layout_options.addSpacerItem(self.spaceItem) layout_right.addLayout(layout_options) # Sample log plot self.fig = Figure() self.canvas = FigureCanvas(self.fig) self.canvas.setSizePolicy(QSizePolicy.Expanding,QSizePolicy.Expanding) self.canvas.mpl_connect('button_press_event', self.presenter.plot_clicked) self.ax = self.fig.add_subplot(111, projection='mantid') layout_right.addWidget(self.canvas) # Sample stats self.create_stats_widgets() layout_stats = QFormLayout() layout_stats.addRow('', QLabel("Log Statistics")) layout_stats.addRow('Min:', self.stats_widgets["minimum"]) layout_stats.addRow('Max:', self.stats_widgets["maximum"]) layout_stats.addRow('Time Avg:', self.stats_widgets["time_mean"]) layout_stats.addRow('Time Std Dev:', self.stats_widgets["time_standard_deviation"]) layout_stats.addRow('Mean (unweighted):', self.stats_widgets["mean"]) layout_stats.addRow('Median (unweighted):', self.stats_widgets["median"]) layout_stats.addRow('Std Dev:', self.stats_widgets["standard_deviation"]) layout_stats.addRow('Duration:', self.stats_widgets["duration"]) layout_right.addLayout(layout_stats) self.frame_right.setLayout(layout_right) self.addWidget(self.frame_right) self.setStretchFactor(0,1) self.resize(1200,800) self.show() def closeEvent(self, event): self.deleteLater() super(SampleLogsView, self).closeEvent(event) def tableMenu(self, event): """Right click menu for table, can plot or print selected logs""" menu = QMenu(self) plotAction = menu.addAction("Plot selected") plotAction.triggered.connect(self.presenter.new_plot_logs) plotAction = menu.addAction("Print selected") plotAction.triggered.connect(self.presenter.print_selected_logs) menu.exec_(event.globalPos()) def set_model(self, model): """Set the model onto the table""" self.model = model self.table.setModel(self.model) self.table.resizeColumnsToContents() self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) self.table.selectionModel().selectionChanged.connect(self.presenter.update) def show_plot_and_stats(self, show_plot_and_stats): """sets wether the plot and stats section should be visible""" if self.frame_right.isVisible() != show_plot_and_stats: # the desired state is nor the current state self.setUpdatesEnabled(False) current_width = self.frame_right.width() if current_width: self.last_width = current_width else: current_width = self.last_width if show_plot_and_stats: self.resize(self.width() + current_width, self.height()) else: self.resize(self.width() - current_width, self.height()) self.frame_right.setVisible(show_plot_and_stats) self.setUpdatesEnabled(True) def plot_selected_logs(self, ws, exp, rows): """Update the plot with the selected rows""" if self.frame_right.isVisible(): self.ax.clear() self.create_ax_by_rows(self.ax, ws, exp, rows) try: self.fig.canvas.draw() except ValueError as ve: #this can throw an error if the plot has recently been hidden, but the error does not matter if not str(ve).startswith("Image size of"): raise def new_plot_selected_logs(self, ws, exp, rows): """Create a new plot, in a separate window for selected rows""" fig, ax = plt.subplots(subplot_kw={'projection': 'mantid'}) self.create_ax_by_rows(ax, ws, exp, rows) fig.show() def create_ax_by_rows(self, ax, ws, exp, rows): """Creates the plots for given rows onto axis ax""" for row in rows: log_text = self.get_row_log_name(row) ax.plot(ws, LogName=log_text, label=log_text, FullTime=not self.full_time.isChecked(), Filtered=self.show_filtered.isChecked(), ExperimentInfo=exp) ax.set_ylabel('') if ax.get_legend_handles_labels()[0]: ax.legend() def set_log_controls(self,are_logs_filtered): """Sets log specific settings based on the log clicked on""" self.show_filtered.setEnabled(are_logs_filtered) def get_row_log_name(self, i): """Returns the log name of particular row""" return str(self.model.item(i, 0).text()) def get_exp(self): """Get set experiment info number""" return self.experimentInfo.value() def get_selected_row_indexes(self): """Return a list of selected row from table""" return [row.row() for row in self.table.selectionModel().selectedRows()] def set_selected_rows(self, rows): """Set seleceted rows in table""" mode = QItemSelectionModel.Select | QItemSelectionModel.Rows for row in rows: self.table.selectionModel().select(self.model.index(row, 0), mode) def create_stats_widgets(self): """Creates the statistics widgets""" self.stats_widgets = {"minimum": QLineEdit(), "maximum": QLineEdit(), "mean": QLineEdit(), "median": QLineEdit(), "standard_deviation": QLineEdit(), "time_mean": QLineEdit(), "time_standard_deviation": QLineEdit(), "duration": QLineEdit()} for widget in self.stats_widgets.values(): widget.setReadOnly(True) def set_statistics(self, stats): """Updates the statistics widgets from stats dictionary""" for param in self.stats_widgets.keys(): self.stats_widgets[param].setText('{:.6}'.format(getattr(stats, param))) def clear_statistics(self): """Clears the values in statistics widgets""" for widget in self.stats_widgets.values(): widget.clear()
class LayoutSettingsDialog(QDialog): """Layout settings dialog""" def __init__(self, parent, names, order, active): super(LayoutSettingsDialog, self).__init__(parent) # variables self._parent = parent self._selection_model = None self.names = names self.order = order self.active = active # widgets self.button_move_up = QPushButton(_('Move Up')) self.button_move_down = QPushButton(_('Move Down')) self.button_delete = QPushButton(_('Delete Layout')) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.group_box = QGroupBox(_("Layout Display and Order")) self.table = QTableView(self) self.ok_button = self.button_box.button(QDialogButtonBox.Ok) self.cancel_button = self.button_box.button(QDialogButtonBox.Cancel) self.cancel_button.setDefault(True) self.cancel_button.setAutoDefault(True) # widget setup self.dialog_size = QSize(300, 200) self.setMinimumSize(self.dialog_size) self.setFixedSize(self.dialog_size) self.setWindowTitle('Layout Settings') self.table.setModel(LayoutModel(self.table, order, active)) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setSelectionMode(QAbstractItemView.SingleSelection) self.table.verticalHeader().hide() self.table.horizontalHeader().hide() self.table.setAlternatingRowColors(True) self.table.setShowGrid(False) self.table.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.table.horizontalHeader().setStretchLastSection(True) self.table.setColumnHidden(1, True) # need to keep a reference for pyside not to segfault! self._selection_model = self.table.selectionModel() # layout buttons_layout = QVBoxLayout() buttons_layout.addWidget(self.button_move_up) buttons_layout.addWidget(self.button_move_down) buttons_layout.addStretch() buttons_layout.addWidget(self.button_delete) group_layout = QHBoxLayout() group_layout.addWidget(self.table) group_layout.addLayout(buttons_layout) self.group_box.setLayout(group_layout) layout = QVBoxLayout() layout.addWidget(self.group_box) layout.addWidget(self.button_box) self.setLayout(layout) # signals and slots self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.close) self.button_delete.clicked.connect(self.delete_layout) self.button_move_up.clicked.connect(lambda: self.move_layout(True)) self.button_move_down.clicked.connect(lambda: self.move_layout(False)) self.table.model().dataChanged.connect( lambda: self.selection_changed(None, None)) self._selection_model.selectionChanged.connect( lambda: self.selection_changed(None, None)) # focus table index = self.table.model().index(0, 0) self.table.setCurrentIndex(index) self.table.setFocus() def delete_layout(self): """ """ names, order, active = self.names, self.order, self.order name = from_qvariant(self.table.selectionModel().currentIndex().data(), to_text_string) if name in names: index = names.index(name) # In case nothing has focus in the table if index != -1: order.remove(name) names[index] = None if name in active: active.remove(name) self.names, self.order, self.active = names, order, active self.table.model().set_data(order, active) index = self.table.model().index(0, 0) self.table.setCurrentIndex(index) self.table.setFocus() self.selection_changed(None, None) if len(order) == 0: self.button_move_up.setDisabled(True) self.button_move_down.setDisabled(True) self.button_delete.setDisabled(True) def move_layout(self, up=True): """ """ names, order, active = self.names, self.order, self.active row = self.table.selectionModel().currentIndex().row() row_new = row if up: row_new -= 1 else: row_new += 1 order[row], order[row_new] = order[row_new], order[row] self.order = order self.table.model().set_data(order, active) index = self.table.model().index(row_new, 0) self.table.setCurrentIndex(index) self.table.setFocus() self.selection_changed(None, None) def selection_changed(self, selection, deselection): """ """ model = self.table.model() index = self.table.currentIndex() row = index.row() order, names, active = self.order, self.names, self.active state = model.row(row)[1] name = model.row(row)[0] # Check if name changed if name not in names: # Did changed if row != -1: # row == -1, means no items left to delete old_name = order[row] order[row] = name names[names.index(old_name)] = name if old_name in active: active[active.index(old_name)] = name # Check if checbox clicked if state: if name not in active: active.append(name) else: if name in active: active.remove(name) self.active = active self.button_move_up.setDisabled(False) self.button_move_down.setDisabled(False) if row == 0: self.button_move_up.setDisabled(True) if row == len(names) - 1: self.button_move_down.setDisabled(True) if len(names) == 0: self.button_move_up.setDisabled(True) self.button_move_down.setDisabled(True)
class SpecimenPositionListWidget(ParameterWidget): class _SpecimenPositionModel(QAbstractTableModel): def __init__(self): QAbstractTableModel.__init__(self) self.positions = [] def rowCount(self, *args, **kwargs): return len(self.positions) def columnCount(self, *args, **kwargs): return 5 def data(self, index, role): if not index.isValid() or not (0 <= index.row() < len(self.positions)): return None if role != Qt.DisplayRole: return None position = self.positions[index.row()] column = index.column() if column == 0: return str(position.x) if position.x is not None else '' elif column == 1: return str(position.y) if position.y is not None else '' elif column == 2: return str(position.z) if position.z is not None else '' elif column == 3: return str(position.r) if position.r is not None else '' elif column == 4: return str(position.t) if position.t is not None else '' def headerData(self, section , orientation, role): if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: if section == 0: return 'X' elif section == 1: return 'Y' elif section == 2: return 'Z' elif section == 3: return 'R' elif section == 4: return 'T' elif orientation == Qt.Vertical: return str(section + 1) def flags(self, index): if not index.isValid(): return Qt.ItemIsEnabled return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable) def setData(self, index, value, role=Qt.EditRole): if not index.isValid() or \ not (0 <= index.row() < len(self.positions)): return False position = self.positions[index.row()] column = index.column() if column == 0: position.x = value elif column == 1: position.y = value elif column == 2: position.z = value elif column == 3: position.r = value elif column == 4: position.t = value return True def insertRows(self, row, count=1, parent=None): if count == 0: return False if parent is None: parent = QModelIndex() self.beginInsertRows(parent, row, row + count - 1) for i in range(count): self.positions.insert(row + i, SpecimenPosition()) self.endInsertRows() return True def removeRows(self, row, count=1, parent=None): if count == 0: return False if parent is None: parent = QModelIndex() self.beginRemoveRows(parent, row, row + count - 1) self.positions = self.positions[:row] + self.positions[row + count:] self.endRemoveRows() return True class _SpecimenPositionDelegate(QItemDelegate): def __init__(self, parent=None): QItemDelegate.__init__(self, parent) def createEditor(self, parent, option, index): column = index.column() if column == 0: return NumericalAttributeLineEdit(SpecimenPosition.x, parent) elif column == 1: return NumericalAttributeLineEdit(SpecimenPosition.y, parent) elif column == 2: return NumericalAttributeLineEdit(SpecimenPosition.y, parent) elif column == 3: return NumericalAttributeLineEdit(SpecimenPosition.y, parent) elif column == 4: return NumericalAttributeLineEdit(SpecimenPosition.y, parent) else: return QItemDelegate.createEditor(self, parent, option, index) def setEditorData(self, editor, index): text = index.model().data(index, Qt.DisplayRole) column = index.column() if column == 0: editor.setText(text) elif column == 1: editor.setText(text) elif column == 2: editor.setText(text) elif column == 3: editor.setText(text) elif column == 4: editor.setText(text) else: QItemDelegate.setEditorData(self, editor, index) def setModelData(self, editor, model, index): column = index.column() if column == 0: model.setData(index, editor.text()) elif column == 1: model.setData(index, editor.text()) elif column == 2: model.setData(index, editor.text()) elif column == 3: model.setData(index, editor.text()) elif column == 4: model.setData(index, editor.text()) else: return QItemDelegate.setModelData(self, editor, model, index) def __init__(self, parent=None): ParameterWidget.__init__(self, object, parent) def _init_ui(self): # Widgets self._table = QTableView() self._table.setModel(self._SpecimenPositionModel()) self._table.setItemDelegate(self._SpecimenPositionDelegate(self)) self._table.horizontalHeader().setStretchLastSection(True) self._toolbar = QToolBar() action_add = self._toolbar.addAction(getIcon("list-add"), "Add layer") action_remove = self._toolbar.addAction(getIcon("list-remove"), "Remove layer") # Layouts layout = ParameterWidget._init_ui(self) layout.addRow(self._table) layout.addRow(self._toolbar) # Signals action_add.triggered.connect(self._on_add) action_remove.triggered.connect(self._on_remove) return layout def _on_add(self): index = self._table.selectionModel().currentIndex() model = self._table.model() model.insertRows(index.row() + 1) def _on_remove(self): selection = self._table.selectionModel().selection().indexes() if len(selection) == 0: QMessageBox.warning(self, "Specimen position", "Select a position") return model = self._table.model() for row in sorted(map(methodcaller('row'), selection), reverse=True): model.removeRow(row) def parameter(self): positions = [] for position in self._table.model().positions: positions.append(SpecimenPosition(position.x, position.y, position.z, position.r, position.t)) return positions def setParameter(self, positions): model = self._table.model() model.positions = positions model.reset() def positions(self): return self.parameter() def setPositions(self, positions): self.setParameter(positions) def setReadOnly(self, state): ParameterWidget.setReadOnly(self, state) if state: trigger = QTableView.EditTrigger.NoEditTriggers else: trigger = QTableView.EditTrigger.AllEditTriggers self._table.setEditTriggers(trigger) self._toolbar.setEnabled(not state) def isReadOnly(self): return ParameterWidget.isReadOnly(self) and \ self._table.editTriggers() == QTableView.EditTrigger.NoEditTriggers and \ not self._toolbar.isEnabled()
class WindowWidget(ParameterWidget): class _WindowModel(QAbstractTableModel): def __init__(self): QAbstractTableModel.__init__(self) self.layers = [] def rowCount(self, *args, **kwargs): return len(self.layers) def columnCount(self, *args, **kwargs): return 2 def data(self, index, role): if not index.isValid() or not (0 <= index.row() < len(self.layers)): return None if role != Qt.DisplayRole: return None layer = self.layers[index.row()] column = index.column() if column == 0: return layer.material elif column == 1: return '%s' % layer.thickness def headerData(self, section , orientation, role): if role != Qt.DisplayRole: return None if orientation == Qt.Horizontal: if section == 0: return 'Material' elif section == 1: return 'Thickness' elif orientation == Qt.Vertical: return str(section + 1) def flags(self, index): if not index.isValid(): return Qt.ItemIsEnabled return Qt.ItemFlags(QAbstractTableModel.flags(self, index) | Qt.ItemIsEditable) def setData(self, index, value, role=Qt.EditRole): if not index.isValid() or \ not (0 <= index.row() < len(self.layers)): return False layer = self.layers[index.row()] column = index.column() if column == 0: layer.material = value elif column == 1: layer.thickness = value self.dataChanged.emit(index, index) return True def insertRows(self, row, count=1, parent=None): if count == 0: return False if parent is None: parent = QModelIndex() self.beginInsertRows(parent, row, row + count - 1) for i in range(count): self.layers.insert(row + i, WindowLayer("unknown", 0.0)) self.endInsertRows() return True def removeRows(self, row, count=1, parent=None): if count == 0: return False if parent is None: parent = QModelIndex() self.beginRemoveRows(parent, row, row + count - 1) self.layers = self.layers[:row] + self.layers[row + count:] self.endRemoveRows() return True class _WindowDelegate(QItemDelegate): def __init__(self, parent=None): QItemDelegate.__init__(self, parent) def createEditor(self, parent, option, index): column = index.column() if column == 0: return TextAttributeLineEdit(WindowLayer.material, parent) elif column == 1: return NumericalAttributeLineEdit(WindowLayer.thickness, parent) else: return QItemDelegate.createEditor(self, parent, option, index) def setEditorData(self, editor, index): text = index.model().data(index, Qt.DisplayRole) column = index.column() if column == 0: editor.setText(text) elif column == 1: editor.setText(text) else: QItemDelegate.setEditorData(self, editor, index) def setModelData(self, editor, model, index): column = index.column() if column == 0: model.setData(index, editor.text()) elif column == 1: model.setData(index, editor.text()) else: return QItemDelegate.setModelData(self, editor, model, index) def __init__(self, parent=None): ParameterWidget.__init__(self, Window, parent) def _init_ui(self): # Widgets model = self._WindowModel() self._table = QTableView() self._table.setModel(model) self._table.setItemDelegate(self._WindowDelegate(self)) self._table.horizontalHeader().setStretchLastSection(True) self._toolbar = QToolBar() action_add = self._toolbar.addAction(getIcon("list-add"), "Add layer") action_remove = self._toolbar.addAction(getIcon("list-remove"), "Remove layer") # Layouts layout = ParameterWidget._init_ui(self) layout.addRow(self._table) layout.addRow(self._toolbar) # Signals action_add.triggered.connect(self._on_add) action_remove.triggered.connect(self._on_remove) model.dataChanged.connect(self.edited) model.rowsInserted.connect(self.edited) model.rowsRemoved.connect(self.edited) return layout def _on_add(self): index = self._table.selectionModel().currentIndex() model = self._table.model() model.insertRows(index.row() + 1) def _on_remove(self): selection = self._table.selectionModel().selection().indexes() if len(selection) == 0: QMessageBox.warning(self, "Window layer", "Select a layer") return model = self._table.model() for row in sorted(map(methodcaller('row'), selection), reverse=True): model.removeRow(row) def parameter(self, parameter=None): parameter = ParameterWidget.parameter(self, parameter) parameter.layers.clear() for layer in self._table.model().layers: parameter.append_layer(layer.material, layer.thickness) # copy return parameter def setParameter(self, window): model = self._table.model() model.layers = window.layers model.reset() def window(self): return self.parameter() def setWindow(self, window): self.setParameter(window) def setReadOnly(self, state): ParameterWidget.setReadOnly(self, state) if state: trigger = QTableView.EditTrigger.NoEditTriggers else: trigger = QTableView.EditTrigger.AllEditTriggers self._table.setEditTriggers(trigger) self._toolbar.setEnabled(not state) def isReadOnly(self): return ParameterWidget.isReadOnly(self) and \ self._table.editTriggers() == QTableView.EditTrigger.NoEditTriggers and \ not self._toolbar.isEnabled()