def __init__(self, parent=None, *, file_path="", calib_preview=""): super().__init__(parent) self.setWindowTitle("View Calibration Standard") self.setMinimumSize(300, 400) self.resize(700, 700) # Displayed data (must be set before the dialog is shown self.file_path = file_path self.calib_preview = calib_preview vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel("<b>Source file:</b> "), 0, Qt.AlignTop) file_path = textwrap.fill(self.file_path, width=80) hbox.addWidget(QLabel(file_path), 0, Qt.AlignTop) hbox.addStretch(1) vbox.addLayout(hbox) te = QTextEdit() te.setReadOnly(True) te.setText(self.calib_preview) vbox.addWidget(te) button_box = QDialogButtonBox(QDialogButtonBox.Close) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) vbox.addWidget(button_box) self.setLayout(vbox)
def _credits_tab(): #scroll = QScrollArea() #scroll.setWidget(self) #scroll.setWidgetResizable(True) ##scroll.setFixedHeight(400) #layout.addWidget(scroll) #vbox = QVBoxLayout() #vbox.addLayout(layout) #vbox.addStretch() scrollArea = QScrollArea() scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) scrollArea.setWidgetResizable(True) #scrollArea->setGeometry( 10, 10, 200, 200 ); package_tab = QWidget() scrollArea.setWidget(package_tab) widget = QWidget(scrollArea) vbox = QVBoxLayout(widget) text = QTextEdit(CREDITS) text.setReadOnly(True) vbox.addWidget(text) #vbox.addLayout(scrollArea) package_tab = QWidget() package_tab.setLayout(vbox) return package_tab
class Console(FramelessWindow): message = Signal(dict) def __init__(self, parent=None): super(Console, self).__init__(parent) self.json_decode_warning = None self.widget = QWidget(self) self.console_layout = QVBoxLayout(self.widget) self.widget.setLayout(self.console_layout) self.output_label = QLabel(self.widget) self.output_label.setText("Output") self.output_edit = QTextEdit(self.widget) self.output_edit.setReadOnly(True) self.highlighter = Highlighter(self.output_edit.document()) self.output_edit.setStyleSheet("background-color: rgb(0, 0, 0);") self.output_edit.setTextColor(QColor(0, 255, 0)) self.output_edit.setFont(QFont(self.output_edit.currentFont().family(), 10)) self.input_label = QLabel(self.widget) self.input_label.setText("Command") self.input_edit = QLineEdit(self.widget) self.input_edit.setStyleSheet("background-color: rgb(0, 0, 0); color: rgb(0, 255, 0)") self.input_edit.setFont(QFont(self.output_edit.currentFont().family(), 10)) self.send_button = QPushButton(self.widget) self.send_button.setObjectName("send_button") self.send_button.setText("Send command") self.console_layout.addWidget(self.output_label) self.console_layout.addWidget(self.output_edit) self.console_layout.addWidget(self.input_label) self.console_layout.addWidget(self.input_edit) self.console_layout.addWidget(self.send_button) self.addContentWidget(self.widget) QMetaObject.connectSlotsByName(self) def on_send_button_clicked(self): mess = self.input_edit.text() try: mess = json.loads(mess) self.message.emit(mess) self.input_edit.clear() self.output_edit.append("$: {}".format(mess)) except json.JSONDecodeError as e: self.json_decode_warning = FramelessCriticalMessageBox(self) self.json_decode_warning.setText("Failed to convert string to JSON: {}".format(e)) self.json_decode_warning.setStandardButtons(QDialogButtonBox.Ok) self.json_decode_warning.button(QDialogButtonBox.Ok).clicked.connect(self.json_decode_warning.close) self.json_decode_warning.show() def write(self, resp: dict): if resp.get("event_type"): resp = "[{}]: {}".format(str(resp.get("event_type")).upper(), resp) else: resp = str(resp) self.output_edit.append(resp)
def show_tb(parent): tbdialog = QDialog(parent=parent.parent()) tbdialog.setModal(True) # this is about the minimum width to not get rewrap # and the minimum height to not have scrollbar tbdialog.resize(650, 270) tbdialog.setLayout(QVBoxLayout()) text = QTextEdit() text.setHtml(notification.as_html()) text.setReadOnly(True) btn = QPushButton('Enter Debugger') def _enter_debug_mode(): btn.setText( 'Now Debugging. Please quit debugger in console ' 'to continue' ) _debug_tb(notification.exception.__traceback__) btn.setText('Enter Debugger') btn.clicked.connect(_enter_debug_mode) tbdialog.layout().addWidget(text) tbdialog.layout().addWidget(btn, 0, Qt.AlignRight) tbdialog.show()
class TermsFrame(ConfigBaseFrame): def __init__(self, parent=None): super(TermsFrame, self).__init__(parent) self.setObjectName("botnet_termsframe_start") self.disable_next_on_enter = True self.widget_layout = QVBoxLayout(self) self.setLayout(self.widget_layout) self.terms = QTextEdit(self) self.terms.setReadOnly(True) self.terms.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.terms.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.terms.setLineWrapMode(QTextEdit.WidgetWidth) self.terms.setWordWrapMode(QTextOption.WrapAnywhere) with open(os.path.join(sys.path[0], "../LICENSE")) as f: self.terms.setText(f.read()) self.widget_layout.addWidget(self.terms) self.accept = QCheckBox(self) self.accept.setChecked(False) self.accept.setText("Accept license") self.accept.setObjectName("license_checkbox") self.widget_layout.addWidget(self.accept) QMetaObject.connectSlotsByName(self) @Slot(bool) def on_license_checkbox_clicked(self, checked): self.set_next_enabled.emit(checked) self.disable_next_on_enter = not checked @Slot() def collect_info(self): return {"terms_accepted": self.accept.isChecked()}
class FileViewer(QDialog): def __init__(self, parent, file_name, job_name, job_number, realization, iteration): super(FileViewer, self).__init__(parent) self.setWindowTitle("{} # {} Realization: {} Iteration: {}" \ .format(job_name, job_number, realization, iteration)) self.text_cont = QTextEdit() self.text_cont.setReadOnly(True) close_button = QPushButton("Close") close_button.clicked.connect(self.accept) layout = QGridLayout(self) layout.addWidget(self.text_cont) layout.addWidget(close_button) self.setMinimumWidth(400) self.setMinimumHeight(200) self.reload(file_name) def reload(self, file_name): with open(file_name) as f: text = f.read() self.text_cont.setText(text) self.show()
def show_tb(parent): tbdialog = QDialog(parent=parent.parent()) tbdialog.setModal(True) # this is about the minimum width to not get rewrap # and the minimum height to not have scrollbar tbdialog.resize(650, 270) tbdialog.setLayout(QVBoxLayout()) text = QTextEdit() theme = get_theme( get_settings().appearance.theme, as_dict=False ) _highlight = Pylighter( # noqa: F841 text.document(), "python", theme.syntax_style ) text.setText(notification.as_text()) text.setReadOnly(True) btn = QPushButton(trans._('Enter Debugger')) def _enter_debug_mode(): btn.setText( trans._( 'Now Debugging. Please quit debugger in console to continue' ) ) _debug_tb(notification.exception.__traceback__) btn.setText(trans._('Enter Debugger')) btn.clicked.connect(_enter_debug_mode) tbdialog.layout().addWidget(text) tbdialog.layout().addWidget(btn, 0, Qt.AlignRight) tbdialog.show()
class ServerInfoDialog(BaseDialog): """Dialog window showing information about notebook servers.""" def __init__(self, server_info, parent=None): """ Construct a RecoveryDialog. Parameters ---------- servers : list of ServerProcess Information to be displayed. This parameter is read only. parent : QWidget, optional Parent of the dialog window. The default is None. """ super().__init__(parent) self.servers = server_info self.setWindowTitle(_('Notebook server info')) self.layout = QVBoxLayout(self) self.formlayout = QFormLayout() self.layout.addLayout(self.formlayout) self.process_combo = QComboBox(self) self.process_combo.currentIndexChanged.connect(self.select_process) self.formlayout.addRow(_('Process ID:'), self.process_combo) self.dir_lineedit = QLineEdit(self) self.dir_lineedit.setReadOnly(True) self.formlayout.addRow(_('Notebook dir:'), self.dir_lineedit) self.state_lineedit = QLineEdit(self) self.state_lineedit.setReadOnly(True) self.formlayout.addRow(_('State:'), self.state_lineedit) self.log_textedit = QTextEdit(self) self.log_textedit.setReadOnly(True) self.layout.addWidget(self.log_textedit) self.buttonbox = QDialogButtonBox(QDialogButtonBox.Ok, self) self.buttonbox.accepted.connect(self.accept) self.refresh_button = QPushButton(_('Refresh'), self) self.refresh_button.clicked.connect(self.refresh_data) self.buttonbox.addButton(self.refresh_button, QDialogButtonBox.ActionRole) self.layout.addWidget(self.buttonbox) self.refresh_data() def refresh_data(self): self.process_combo.clear() for server in self.servers: self.process_combo.addItem(str(server.process.processId())) self.select_process(0) def select_process(self, index): self.dir_lineedit.setText(self.servers[index].notebook_dir) self.state_lineedit.setText( SERVER_STATE_DESCRIPTIONS[self.servers[index].state]) self.log_textedit.setPlainText(self.servers[index].output)
def show_tb(parent): tbdialog = QDialog(parent=parent.parent()) tbdialog.setModal(True) # this is about the minimum width to not get rewrap # and the minimum height to not have scrollbar tbdialog.resize(650, 270) tbdialog.setLayout(QVBoxLayout()) text = QTextEdit() text.setHtml(notification.as_html()) text.setReadOnly(True) tbdialog.layout().addWidget(text) tbdialog.show()
class TextView(View): title = "Text" supported_types = ("text",) def __init__(self, data_object): super(TextView, self).__init__(data_object) def create_widget(self, parent=None): self.text_widget = QTextEdit(parent) self.text_widget.setReadOnly(True) do = self.data_object.convert("text") self.text_widget.setText(do.inner_data) return self.text_widget
class ModuleDetailsWindow(FramelessWindow): def __init__(self, parent): super(ModuleDetailsWindow, self).__init__(parent) self.content_widget = QWidget(self) self.widget_layout = QFormLayout(self.content_widget) self.content_widget.setLayout(self.widget_layout) self.addContentWidget(self.content_widget) self.module_code = QTextEdit(self.content_widget) self.module_code.setReadOnly(True) self.widget_layout.addWidget(self.module_code) def set_module(self, module: Module): try: self.module_code.setText(base64.b64decode(module.code).decode()) except Exception as e: self.module_code.setText( "Exception while loading module's code: {}".format(e))
def _credits_tab(): """creates the credits tab""" scroll_area = QScrollArea() scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) scroll_area.setWidgetResizable(True) scroll_widget = QWidget(scroll_area) widget = QWidget() scroll_area.setWidget(widget) vbox = QVBoxLayout(scroll_widget) text = QTextEdit(CREDITS) text.setReadOnly(True) vbox.addWidget(text) package_tab = QWidget() package_tab.setLayout(vbox) return package_tab
def __init__(self, run_button=None, qwidgets_to_disable=None, text_edit=None, busy=None, timer_label=None, parent=None, setup_layout=True, **kwargs): super(JobRunner, self).__init__(parent, **kwargs) self.is_passed = False self._iteration_edit = QLineEdit(self, placeholderText="Iteration (n)") self._iteration_edit.setText('5') if run_button is None: run_button = QPushButton("Calculate Prime", self) run_button.clicked.connect(self.prime_requested) if text_edit is None: text_edit = QTextEdit("Result:<br>", self) text_edit.setReadOnly(True) if busy is None: busy = QProgressBar(self) self._busy = busy if timer_label is None: timer_label = QLabel('0:00') self._timer_label = timer_label self._busy.setVisible(False) self._timer_label.setVisible(False) self._run_button = run_button self._text_edit = text_edit self._time0 = None self.process = None if qwidgets_to_disable is None: qwidgets_to_disable = [self._run_button, self._iteration_edit] self.qwidgets_to_disable = qwidgets_to_disable if setup_layout: self.setup_layout()
def __init__(self, parent): super(EditSrcCodeInfoDialog, self).__init__(parent) self.setLayout(QGridLayout()) # info text edit info_text_edit = QTextEdit() info_text_edit.setHtml(''' <h2 style="font-family: Poppins; font-size: xx-large; color: #a9d5ef;">Some info before you delete the universe</h2> <div style="font-family: Corbel; font-size: x-large;"> <p> Yes, you can change method implementations of objects. This can be quite useful but since changing an instance's implementation at runtime is kinda sketchy, you should be a bit careful, it's not exactly bulletproof, and doesnt <i>always</i> work. When you override a method implementation, a new function object will be created using python's ast module, which then gets bound to the object as method, which essentially shadows the old implementation. Therefore, you might need to add imports etc. you node uses in the original nodes package. All changes are temporary and only apply on a single object. </p> <p> Have fun. </p> </div> ''') info_text_edit.setReadOnly(True) self.layout().addWidget(info_text_edit, 0, 0, 1, 2) dont_show_again_button = QPushButton('Stop being annoying') dont_show_again_button.clicked.connect(self.close_and_dont_show_again) self.layout().addWidget(dont_show_again_button, 1, 0) ok_button = QPushButton('Got it') ok_button.clicked.connect(self.accept) self.layout().addWidget(ok_button, 1, 1) ok_button.setFocus() self.setWindowTitle('Editing Source Code Info') self.resize(560, 366)
class DialogViewMetadata(QDialog): def __init__(self): super().__init__() self.resize(500, 500) self.setWindowTitle("Run Metadata") self.te_meta = QTextEdit() self.te_meta.setReadOnly(True) # 'Close' button box button_box = QDialogButtonBox(QDialogButtonBox.Close) button_box.accepted.connect(self.accept) button_box.rejected.connect(self.reject) vbox = QVBoxLayout() vbox.addWidget(self.te_meta) vbox.addWidget(button_box) self.setLayout(vbox) def setText(self, text): self.te_meta.setText(text)
class NodeDetailsWidget(QGroupBox): def __init__(self, main_window, nodes_tree_widget): super().__init__('details') self.main_window = main_window self.nodes_tree_widget = nodes_tree_widget self.nodes_tree_widget.details_widget = self self.setLayout(QVBoxLayout()) self.doc_text_edit = QTextEdit() self.doc_text_edit.setFont(QFont('Consolas', 9)) self.doc_text_edit.setReadOnly(True) self.layout().addWidget(self.doc_text_edit) self.node = None def set_node(self, node): self.node = node self.update_details() def update_details(self): self.doc_text_edit.setText(self.node.doc)
class MessageBox(DialogBase): """Base message box dialog.""" QUESTION_BOX = 100 INFORMATION_BOX = 101 ERROR_BOX = 102 REMOVE_BOX = 103 sig_url_clicked = Signal(object) def __init__(self, type_, error='', title='', text='', learn_more=None): """Base message box dialog.""" super(MessageBox, self).__init__() # self.tracker = GATracker() self.label_text = QLabel(to_text_string(text)) self.textbox_error = QTextEdit() self.button_ok = ButtonPrimary('Ok') self.button_yes = ButtonPrimary('Yes') self.button_no = ButtonNormal('No') self.button_copy = ButtonNormal('Copy text') self.button_learn = ButtonNormal('Learn more') self.button_remove = ButtonDanger('Remove') self.button_cancel = ButtonNormal('Cancel') self.bbox = QDialogButtonBox(Qt.Horizontal) self.label_text.setOpenExternalLinks(False) self.label_text.linkActivated.connect(self.url_clicked) self.textbox_error.setReadOnly(True) self.textbox_error.setFrameStyle(QTextEdit.Plain) self.textbox_error.setFrameShape(QTextEdit.NoFrame) self.setMinimumWidth(260) self.textbox_error.verticalScrollBar().show() self.setWindowTitle(to_text_string(title)) error = to_text_string(error).split('\n') error = '<br>'.join(error) self.textbox_error.setText(error) # Layouts layout = QVBoxLayout() layout.addWidget(self.label_text) layout.addWidget(SpacerVertical()) if error: layout.addWidget(self.textbox_error) layout.addWidget(SpacerVertical()) layout.addWidget(self.button_copy) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout.addLayout(layout_buttons) self.layout = layout self.setLayout(layout) # Signals self.button_copy.clicked.connect(self.copy_text) self.button_ok.clicked.connect(self.accept) self.button_yes.clicked.connect(self.accept) self.button_no.clicked.connect(self.reject) self.button_remove.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) # Setup self.button_learn.setVisible(bool(learn_more)) if bool(learn_more): layout_buttons.addWidget(self.button_learn) layout_buttons.addWidget(SpacerHorizontal()) self.button_learn.clicked.connect( lambda: self.show_url(learn_more) ) if type_ == self.ERROR_BOX: layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.INFORMATION_BOX: layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.textbox_error.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.QUESTION_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_no) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_yes) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.REMOVE_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_remove) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_yes.setVisible(False) self.button_no.setVisible(False) def url_clicked(self, url): """Emit url interaction.""" self.sig_url_clicked.emit(url) def copy_text(self): """Copy all the content of the displayed error message.""" self.textbox_error.selectAll() self.textbox_error.copy() def show_url(self, url=None): """Open url in default browser.""" if url: qurl = QUrl(url) QDesktopServices.openUrl(qurl) self.tracker.track_event('help', 'documentation', url)
class CreatePlan(QWidget): plan_created = Signal() plan_node_changed = Signal() def __init__(self, settings: PartSettings): super().__init__() self.settings = settings self.save_translate_dict: typing.Dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()} self.plan = PlanPreview(self) self.save_plan_btn = QPushButton("Save") self.clean_plan_btn = QPushButton("Remove all") self.remove_btn = QPushButton("Remove") self.update_element_chk = QCheckBox("Update element") self.change_root = EnumComboBox(RootType) self.save_choose = QComboBox() self.save_choose.addItem("<none>") self.save_choose.addItems(list(self.save_translate_dict.keys())) self.save_btn = QPushButton("Save") self.segment_profile = SearchableListWidget() self.pipeline_profile = SearchableListWidget() self.segment_stack = QTabWidget() self.segment_stack.addTab(self.segment_profile, "Profile") self.segment_stack.addTab(self.pipeline_profile, "Pipeline") self.generate_mask_btn = QPushButton("Add mask") self.generate_mask_btn.setToolTip("Mask need to have unique name") self.mask_name = QLineEdit() self.mask_operation = EnumComboBox(MaskOperation) self.chanel_num = QSpinBox() self.choose_channel_for_measurements = QComboBox() self.choose_channel_for_measurements.addItems( ["Same as segmentation"] + [str(x + 1) for x in range(MAX_CHANNEL_NUM)] ) self.units_choose = EnumComboBox(Units) self.units_choose.set_value(self.settings.get("units_value", Units.nm)) self.chanel_num.setRange(0, 10) self.expected_node_type = None self.save_constructor = None self.chose_profile_btn = QPushButton("Add Profile") self.get_big_btn = QPushButton("Leave the biggest") self.get_big_btn.hide() self.get_big_btn.setDisabled(True) self.measurements_list = SearchableListWidget(self) self.measurement_name_prefix = QLineEdit(self) self.add_calculation_btn = QPushButton("Add measurement calculation") self.information = QTextEdit() self.information.setReadOnly(True) self.protect = False self.mask_set = set() self.calculation_plan = CalculationPlan() self.plan.set_plan(self.calculation_plan) self.segmentation_mask = MaskWidget(settings) self.file_mask = FileMask() self.change_root.currentIndexChanged.connect(self.change_root_type) self.save_choose.currentTextChanged.connect(self.save_changed) self.measurements_list.currentTextChanged.connect(self.show_measurement) self.segment_profile.currentTextChanged.connect(self.show_segment) self.measurements_list.currentTextChanged.connect(self.show_measurement_info) self.segment_profile.currentTextChanged.connect(self.show_segment_info) self.pipeline_profile.currentTextChanged.connect(self.show_segment_info) self.pipeline_profile.currentTextChanged.connect(self.show_segment) self.mask_name.textChanged.connect(self.mask_name_changed) self.generate_mask_btn.clicked.connect(self.create_mask) self.clean_plan_btn.clicked.connect(self.clean_plan) self.remove_btn.clicked.connect(self.remove_element) self.mask_name.textChanged.connect(self.mask_text_changed) self.chose_profile_btn.clicked.connect(self.add_segmentation) self.get_big_btn.clicked.connect(self.add_leave_biggest) self.add_calculation_btn.clicked.connect(self.add_measurement) self.save_plan_btn.clicked.connect(self.add_calculation_plan) # self.forgot_mask_btn.clicked.connect(self.forgot_mask) # self.cmap_save_btn.clicked.connect(self.save_to_cmap) self.save_btn.clicked.connect(self.add_save_to_project) self.update_element_chk.stateChanged.connect(self.mask_text_changed) self.update_element_chk.stateChanged.connect(self.show_measurement) self.update_element_chk.stateChanged.connect(self.show_segment) self.update_element_chk.stateChanged.connect(self.update_names) self.segment_stack.currentChanged.connect(self.change_segmentation_table) plan_box = QGroupBox("Prepare workflow:") lay = QVBoxLayout() lay.addWidget(self.plan) bt_lay = QGridLayout() bt_lay.setSpacing(1) bt_lay.addWidget(self.save_plan_btn, 0, 0) bt_lay.addWidget(self.clean_plan_btn, 0, 1) bt_lay.addWidget(self.remove_btn, 1, 0) bt_lay.addWidget(self.update_element_chk, 1, 1) lay.addLayout(bt_lay) plan_box.setLayout(lay) plan_box.setStyleSheet(group_sheet) other_box = QGroupBox("Other operations:") other_box.setContentsMargins(0, 0, 0, 0) bt_lay = QVBoxLayout() bt_lay.setSpacing(0) bt_lay.addWidget(QLabel("Root type:")) bt_lay.addWidget(self.change_root) bt_lay.addStretch(1) bt_lay.addWidget(QLabel("Saving:")) bt_lay.addWidget(self.save_choose) bt_lay.addWidget(self.save_btn) other_box.setLayout(bt_lay) other_box.setStyleSheet(group_sheet) mask_box = QGroupBox("Use mask from:") mask_box.setStyleSheet(group_sheet) self.mask_stack = QTabWidget() self.mask_stack.addTab(stretch_widget(self.file_mask), "File") self.mask_stack.addTab(stretch_widget(self.segmentation_mask), "Current ROI") self.mask_stack.addTab(stretch_widget(self.mask_operation), "Operations on masks") self.mask_stack.setTabToolTip(2, "Allows to create mask which is based on masks previously added to plan.") lay = QGridLayout() lay.setSpacing(0) lay.addWidget(self.mask_stack, 0, 0, 1, 2) label = QLabel("Mask name:") label.setToolTip("Needed if you would like to reuse this mask in tab 'Operations on masks'") self.mask_name.setToolTip("Needed if you would like to reuse this mask in tab 'Operations on masks'") lay.addWidget(label, 1, 0) lay.addWidget(self.mask_name, 1, 1) lay.addWidget(self.generate_mask_btn, 2, 0, 1, 2) mask_box.setLayout(lay) segment_box = QGroupBox("ROI extraction:") segment_box.setStyleSheet(group_sheet) lay = QVBoxLayout() lay.setSpacing(0) lay.addWidget(self.segment_stack) lay.addWidget(self.chose_profile_btn) lay.addWidget(self.get_big_btn) segment_box.setLayout(lay) measurement_box = QGroupBox("Set of measurements:") measurement_box.setStyleSheet(group_sheet) lay = QGridLayout() lay.setSpacing(0) lay.addWidget(self.measurements_list, 0, 0, 1, 2) lab = QLabel("Name prefix:") lab.setToolTip("Prefix added before each column name") lay.addWidget(lab, 1, 0) lay.addWidget(self.measurement_name_prefix, 1, 1) lay.addWidget(QLabel("Channel:"), 2, 0) lay.addWidget(self.choose_channel_for_measurements, 2, 1) lay.addWidget(QLabel("Units:"), 3, 0) lay.addWidget(self.units_choose, 3, 1) lay.addWidget(self.add_calculation_btn, 4, 0, 1, 2) measurement_box.setLayout(lay) info_box = QGroupBox("Information") info_box.setStyleSheet(group_sheet) lay = QVBoxLayout() lay.addWidget(self.information) info_box.setLayout(lay) layout = QGridLayout() fst_col = QVBoxLayout() fst_col.addWidget(plan_box, 1) fst_col.addWidget(mask_box) layout.addWidget(plan_box, 0, 0, 5, 1) # layout.addWidget(plan_box, 0, 0, 3, 1) # layout.addWidget(mask_box, 3, 0, 2, 1) # layout.addWidget(segmentation_mask_box, 1, 1) layout.addWidget(mask_box, 0, 2, 1, 2) layout.addWidget(other_box, 0, 1) layout.addWidget(segment_box, 1, 1, 1, 2) layout.addWidget(measurement_box, 1, 3) layout.addWidget(info_box, 3, 1, 1, 3) self.setLayout(layout) self.generate_mask_btn.setDisabled(True) self.chose_profile_btn.setDisabled(True) self.add_calculation_btn.setDisabled(True) self.mask_allow = False self.segment_allow = False self.file_mask_allow = False self.node_type = NodeType.root self.node_name = "" self.plan_node_changed.connect(self.mask_text_changed) self.plan.changed_node.connect(self.node_type_changed) self.plan_node_changed.connect(self.show_segment) self.plan_node_changed.connect(self.show_measurement) self.plan_node_changed.connect(self.mask_stack_change) self.mask_stack.currentChanged.connect(self.mask_stack_change) self.file_mask.value_changed.connect(self.mask_stack_change) self.mask_name.textChanged.connect(self.mask_stack_change) self.node_type_changed() def change_root_type(self): value: RootType = self.change_root.get_value() self.calculation_plan.set_root_type(value) self.plan.update_view() def change_segmentation_table(self): index = self.segment_stack.currentIndex() text = self.segment_stack.tabText(index) if self.update_element_chk.isChecked(): self.chose_profile_btn.setText("Replace " + text) else: self.chose_profile_btn.setText("Add " + text) self.segment_profile.setCurrentItem(None) self.pipeline_profile.setCurrentItem(None) def save_changed(self, text): text = str(text) if text == "<none>": self.save_btn.setText("Save") self.save_btn.setToolTip("Choose file type") self.expected_node_type = None self.save_constructor = None else: save_class = self.save_translate_dict.get(text, None) if save_class is None: self.save_choose.setCurrentText("<none>") return self.save_btn.setText(f"Save to {save_class.get_short_name()}") self.save_btn.setToolTip("Choose mask create in plan view") if save_class.need_mask(): self.expected_node_type = NodeType.mask elif save_class.need_segmentation(): self.expected_node_type = NodeType.segment else: self.expected_node_type = NodeType.root self.save_constructor = Save self.save_activate() def save_activate(self): self.save_btn.setDisabled(True) if self.node_type == self.expected_node_type: self.save_btn.setEnabled(True) return def segmentation_from_project(self): self.calculation_plan.add_step(Operations.reset_to_base) self.plan.update_view() def update_names(self): if self.update_element_chk.isChecked(): self.chose_profile_btn.setText("Replace Profile") self.add_calculation_btn.setText("Replace set of measurements") self.generate_mask_btn.setText("Replace mask") else: self.chose_profile_btn.setText("Add Profile") self.add_calculation_btn.setText("Add set of measurements") self.generate_mask_btn.setText("Generate mask") def node_type_changed(self): # self.cmap_save_btn.setDisabled(True) self.save_btn.setDisabled(True) self.node_name = "" if self.plan.currentItem() is None: self.mask_allow = False self.file_mask_allow = False self.segment_allow = False self.remove_btn.setDisabled(True) self.plan_node_changed.emit() logging.debug("[node_type_changed] return") return node_type = self.calculation_plan.get_node_type() self.node_type = node_type if node_type in [NodeType.file_mask, NodeType.mask, NodeType.segment, NodeType.measurement, NodeType.save]: self.remove_btn.setEnabled(True) else: self.remove_btn.setEnabled(False) if node_type in (NodeType.mask, NodeType.file_mask): self.mask_allow = False self.segment_allow = True self.file_mask_allow = False self.node_name = self.calculation_plan.get_node().operation.name elif node_type == NodeType.segment: self.mask_allow = True self.segment_allow = False self.file_mask_allow = False self.save_btn.setEnabled(True) # self.cmap_save_btn.setEnabled(True) elif node_type == NodeType.root: self.mask_allow = False self.segment_allow = True self.file_mask_allow = True elif node_type in (NodeType.none, NodeType.measurement, NodeType.save): self.mask_allow = False self.segment_allow = False self.file_mask_allow = False self.save_activate() self.plan_node_changed.emit() def add_save_to_project(self): save_class = self.save_translate_dict.get(self.save_choose.currentText(), None) if save_class is None: QMessageBox.warning(self, "Save problem", "Not found save class") dial = FormDialog( [AlgorithmProperty("suffix", "File suffix", ""), AlgorithmProperty("directory", "Sub directory", "")] + save_class.get_fields() ) if dial.exec(): values = dial.get_values() suffix = values["suffix"] directory = values["directory"] del values["suffix"] del values["directory"] save_elem = Save(suffix, directory, save_class.get_name(), save_class.get_short_name(), values) if self.update_element_chk.isChecked(): self.calculation_plan.replace_step(save_elem) else: self.calculation_plan.add_step(save_elem) self.plan.update_view() def create_mask(self): text = str(self.mask_name.text()).strip() if text != "" and text in self.mask_set: QMessageBox.warning(self, "Already exists", "Mask with this name already exists", QMessageBox.Ok) return if _check_widget(self.mask_stack, EnumComboBox): # existing mask mask_dialog = TwoMaskDialog if self.mask_operation.get_value() == MaskOperation.mask_intersection: # Mask intersection MaskConstruct = MaskIntersection else: MaskConstruct = MaskSum dial = mask_dialog(self.mask_set) if not dial.exec(): return names = dial.get_result() mask_ob = MaskConstruct(text, *names) elif _check_widget(self.mask_stack, MaskWidget): mask_ob = MaskCreate(text, self.segmentation_mask.get_mask_property()) elif _check_widget(self.mask_stack, FileMask): mask_ob = self.file_mask.get_value(text) else: raise ValueError("Unknowsn widget") if self.update_element_chk.isChecked(): node = self.calculation_plan.get_node() name = node.operation.name if name in self.calculation_plan.get_reused_mask() and name != text: QMessageBox.warning( self, "Cannot remove", f"Cannot remove mask '{name}' from plan because it is used in other elements" ) return self.mask_set.remove(name) self.mask_set.add(mask_ob.name) self.calculation_plan.replace_step(mask_ob) else: self.mask_set.add(mask_ob.name) self.calculation_plan.add_step(mask_ob) self.plan.update_view() self.mask_text_changed() def mask_stack_change(self): node_type = self.calculation_plan.get_node_type() if self.update_element_chk.isChecked() and node_type not in [NodeType.mask, NodeType.file_mask]: self.generate_mask_btn.setDisabled(True) text = self.mask_name.text() update = self.update_element_chk.isChecked() if self.node_type == NodeType.none: self.generate_mask_btn.setDisabled(True) return operation = self.calculation_plan.get_node().operation if ( not update and isinstance(operation, (MaskMapper, MaskBase)) and self.calculation_plan.get_node().operation.name == text ): self.generate_mask_btn.setDisabled(True) return if _check_widget(self.mask_stack, EnumComboBox): # reuse mask if len(self.mask_set) > 1 and ( (not update and node_type == NodeType.root) or (update and node_type == NodeType.file_mask) ): self.generate_mask_btn.setEnabled(True) else: self.generate_mask_btn.setEnabled(False) self.generate_mask_btn.setToolTip("Need at least two named mask and root selected") elif _check_widget(self.mask_stack, MaskWidget): # mask from segmentation if (not update and node_type == NodeType.segment) or (update and node_type == NodeType.mask): self.generate_mask_btn.setEnabled(True) else: self.generate_mask_btn.setEnabled(False) self.generate_mask_btn.setToolTip("Select segmentation") else: if (not update and node_type == NodeType.root) or (update and node_type == NodeType.file_mask): self.generate_mask_btn.setEnabled(self.file_mask.is_valid()) else: self.generate_mask_btn.setEnabled(False) self.generate_mask_btn.setToolTip("Need root selected") def mask_name_changed(self, text): if str(text) in self.mask_set: self.generate_mask_btn.setDisabled(True) else: self.generate_mask_btn.setDisabled(False) def add_leave_biggest(self): profile = self.calculation_plan.get_node().operation profile.leave_biggest_swap() self.calculation_plan.replace_step(profile) self.plan.update_view() def add_segmentation(self): if self.segment_stack.currentIndex() == 0: text = str(self.segment_profile.currentItem().text()) if text not in self.settings.segmentation_profiles: self.refresh_all_profiles() return profile = self.settings.segmentation_profiles[text] if self.update_element_chk.isChecked(): self.calculation_plan.replace_step(profile) else: self.calculation_plan.add_step(profile) self.plan.update_view() else: # self.segment_stack.currentIndex() == 1 text = self.pipeline_profile.currentItem().text() segmentation_pipeline = self.settings.segmentation_pipelines[text] pos = self.calculation_plan.current_pos[:] old_pos = self.calculation_plan.current_pos[:] for el in segmentation_pipeline.mask_history: self.calculation_plan.add_step(el.segmentation) self.plan.update_view() node = self.calculation_plan.get_node(pos) pos.append(len(node.children) - 1) self.calculation_plan.set_position(pos) self.calculation_plan.add_step(MaskCreate("", el.mask_property)) self.plan.update_view() pos.append(0) self.calculation_plan.set_position(pos) self.calculation_plan.add_step(segmentation_pipeline.segmentation) self.calculation_plan.set_position(old_pos) self.plan.update_view() def add_measurement(self): text = str(self.measurements_list.currentItem().text()) measurement_copy = deepcopy(self.settings.measurement_profiles[text]) prefix = str(self.measurement_name_prefix.text()).strip() channel = self.choose_channel_for_measurements.currentIndex() - 1 measurement_copy.name_prefix = prefix # noinspection PyTypeChecker measurement_calculate = MeasurementCalculate( channel=channel, measurement_profile=measurement_copy, name_prefix=prefix, units=self.units_choose.get_value(), ) if self.update_element_chk.isChecked(): self.calculation_plan.replace_step(measurement_calculate) else: self.calculation_plan.add_step(measurement_calculate) self.plan.update_view() def remove_element(self): conflict_mask, used_mask = self.calculation_plan.get_file_mask_names() if len(conflict_mask) > 0: logging.info("Mask in use") QMessageBox.warning(self, "In use", "Masks {} are used in other places".format(", ".join(conflict_mask))) return self.mask_set -= used_mask self.calculation_plan.remove_step() self.plan.update_view() def clean_plan(self): self.calculation_plan = CalculationPlan() self.plan.set_plan(self.calculation_plan) self.node_type_changed() self.mask_set = set() def mask_text_changed(self): name = str(self.mask_name.text()).strip() self.generate_mask_btn.setDisabled(True) # load mask from file if not self.update_element_chk.isChecked(): # generate mask from segmentation if self.mask_allow and (name == "" or name not in self.mask_set): self.generate_mask_btn.setEnabled(True) else: if self.node_type != NodeType.file_mask and self.node_type != NodeType.mask: return # generate mask from segmentation if self.node_type == NodeType.mask and (name == "" or name == self.node_name or name not in self.mask_set): self.generate_mask_btn.setEnabled(True) def add_calculation_plan(self, text=None): if text is None or isinstance(text, bool): text, ok = QInputDialog.getText(self, "Plan title", "Set plan title") else: text, ok = QInputDialog.getText( self, "Plan title", f"Set plan title. Previous ({text}) is already in use", text=text ) text = text.strip() if ok: if text == "": QMessageBox.information( self, "Name cannot be empty", "Name cannot be empty, Please set correct name", QMessageBox.Ok ) self.add_calculation_plan() return if text in self.settings.batch_plans: res = QMessageBox.information( self, "Name already in use", "Name already in use. Would like to overwrite?", QMessageBox.Yes | QMessageBox.No, ) if res == QMessageBox.No: self.add_calculation_plan(text) return plan = copy(self.calculation_plan) plan.set_name(text) self.settings.batch_plans[text] = plan self.settings.dump() self.plan_created.emit() @staticmethod def get_index(item: QListWidgetItem, new_values: typing.List[str]) -> int: if item is None: return -1 text = item.text() try: return new_values.index(text) except ValueError: return -1 @staticmethod def refresh_profiles(list_widget: QListWidget, new_values: typing.List[str], index: int): list_widget.clear() list_widget.addItems(new_values) if index != -1: list_widget.setCurrentRow(index) def showEvent(self, _event): self.refresh_all_profiles() def refresh_all_profiles(self): new_measurements = list(sorted(self.settings.measurement_profiles.keys())) new_segment = list(sorted(self.settings.segmentation_profiles.keys())) new_pipelines = list(sorted(self.settings.segmentation_pipelines.keys())) measurement_index = self.get_index(self.measurements_list.currentItem(), new_measurements) segment_index = self.get_index(self.segment_profile.currentItem(), new_segment) pipeline_index = self.get_index(self.pipeline_profile.currentItem(), new_pipelines) self.protect = True self.refresh_profiles(self.measurements_list, new_measurements, measurement_index) self.refresh_profiles(self.segment_profile, new_segment, segment_index) self.refresh_profiles(self.pipeline_profile, new_pipelines, pipeline_index) self.protect = False def show_measurement_info(self, text=None): if self.protect: return if text is None: if self.measurements_list.currentItem() is not None: text = str(self.measurements_list.currentItem().text()) else: return profile = self.settings.measurement_profiles[text] self.information.setText(str(profile)) def show_measurement(self): if self.update_element_chk.isChecked(): if self.node_type == NodeType.measurement: self.add_calculation_btn.setEnabled(True) else: self.add_calculation_btn.setDisabled(True) else: if self.measurements_list.currentItem() is not None: self.add_calculation_btn.setEnabled(self.mask_allow) else: self.add_calculation_btn.setDisabled(True) def show_segment_info(self, text=None): if self.protect: return if text == "": return if self.segment_stack.currentIndex() == 0: if text is None: if self.segment_profile.currentItem() is not None: text = str(self.segment_profile.currentItem().text()) else: return profile = self.settings.segmentation_profiles[text] else: if text is None: if self.pipeline_profile.currentItem() is not None: text = str(self.pipeline_profile.currentItem().text()) else: return profile = self.settings.segmentation_pipelines[text] self.information.setText(profile.pretty_print(analysis_algorithm_dict)) def show_segment(self): if self.update_element_chk.isChecked() and self.segment_stack.currentIndex() == 0: self.get_big_btn.setDisabled(True) if self.node_type == NodeType.segment: self.chose_profile_btn.setEnabled(True) else: self.chose_profile_btn.setDisabled(True) else: if self.node_type == NodeType.segment: self.get_big_btn.setEnabled(True) else: self.get_big_btn.setDisabled(True) if ( self.segment_stack.currentIndex() == 0 and self.segment_profile.currentItem() ) or self.pipeline_profile.currentItem() is not None: self.chose_profile_btn.setEnabled(self.segment_allow) else: self.chose_profile_btn.setDisabled(True) def edit_plan(self): plan = self.sender().plan_to_edit # type: CalculationPlan self.calculation_plan = copy(plan) self.plan.set_plan(self.calculation_plan) self.mask_set.clear() self.calculation_plan.set_position([]) self.mask_set.update(self.calculation_plan.get_mask_names())
class QtPluginDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.installer = Installer() self.setup_ui() self.installer.set_output_widget(self.stdout_text) self.installer.process.started.connect(self._on_installer_start) self.installer.process.finished.connect(self._on_installer_done) self.refresh() def _on_installer_start(self): self.show_status_btn.setChecked(True) self.working_indicator.show() self.process_error_indicator.hide() def _on_installer_done(self, exit_code, exit_status): self.working_indicator.hide() if exit_code: self.process_error_indicator.show() else: self.show_status_btn.setChecked(False) self.refresh() self.plugin_sorter.refresh() def refresh(self): self.installed_list.clear() self.available_list.clear() # fetch installed from ...plugins import plugin_manager plugin_manager.discover() # since they might not be loaded yet already_installed = set() for plugin_name, mod_name, distname in plugin_manager.iter_available(): # not showing these in the plugin dialog if plugin_name in ('napari_plugin_engine', ): continue if distname: already_installed.add(distname) meta = standard_metadata(distname) else: meta = {} self.installed_list.addItem( ProjectInfo( normalized_name(distname or ''), meta.get('version', ''), meta.get('url', ''), meta.get('summary', ''), meta.get('author', ''), meta.get('license', ''), ), plugin_name=plugin_name, enabled=plugin_name in plugin_manager.plugins, ) # self.v_splitter.setSizes([70 * self.installed_list.count(), 10, 10]) # fetch available plugins self.worker = create_worker(iter_napari_plugin_info) def _handle_yield(project_info): if project_info.name in already_installed: self.installed_list.tag_outdated(project_info) else: self.available_list.addItem(project_info) self.worker.yielded.connect(_handle_yield) self.worker.finished.connect(self.working_indicator.hide) self.worker.start() def setup_ui(self): self.resize(1080, 640) vlay_1 = QVBoxLayout(self) self.h_splitter = QSplitter(self) vlay_1.addWidget(self.h_splitter) self.h_splitter.setOrientation(Qt.Horizontal) self.v_splitter = QSplitter(self.h_splitter) self.v_splitter.setOrientation(Qt.Vertical) self.v_splitter.setMinimumWidth(500) self.plugin_sorter = QtPluginSorter(parent=self.h_splitter) self.plugin_sorter.layout().setContentsMargins(2, 0, 0, 0) self.plugin_sorter.hide() installed = QWidget(self.v_splitter) lay = QVBoxLayout(installed) lay.setContentsMargins(0, 2, 0, 2) lay.addWidget(QLabel("Installed Plugins")) self.installed_list = QPluginList(installed, self.installer) lay.addWidget(self.installed_list) uninstalled = QWidget(self.v_splitter) lay = QVBoxLayout(uninstalled) lay.setContentsMargins(0, 2, 0, 2) lay.addWidget(QLabel("Available Plugin Packages")) self.available_list = QPluginList(uninstalled, self.installer) lay.addWidget(self.available_list) self.stdout_text = QTextEdit(self.v_splitter) self.stdout_text.setReadOnly(True) self.stdout_text.setObjectName("pip_install_status") self.stdout_text.hide() buttonBox = QHBoxLayout() self.working_indicator = QLabel("loading ...", self) self.process_error_indicator = QLabel(self) self.process_error_indicator.setObjectName("error_label") self.process_error_indicator.hide() load_gif = str(Path(napari.resources.__file__).parent / "loading.gif") mov = QMovie(load_gif) mov.setScaledSize(QSize(18, 18)) self.working_indicator.setMovie(mov) mov.start() self.show_status_btn = QPushButton("Show Status", self) self.show_status_btn.setFixedWidth(100) self.show_sorter_btn = QPushButton("<< Show Sorter", self) self.close_btn = QPushButton("Close", self) self.close_btn.clicked.connect(self.reject) buttonBox.addWidget(self.show_status_btn) buttonBox.addWidget(self.working_indicator) buttonBox.addWidget(self.process_error_indicator) buttonBox.addStretch() buttonBox.addWidget(self.show_sorter_btn) buttonBox.addWidget(self.close_btn) buttonBox.setContentsMargins(0, 0, 4, 0) vlay_1.addLayout(buttonBox) self.show_status_btn.setCheckable(True) self.show_status_btn.setChecked(False) self.show_status_btn.toggled.connect(self._toggle_status) self.show_sorter_btn.setCheckable(True) self.show_sorter_btn.setChecked(False) self.show_sorter_btn.toggled.connect(self._toggle_sorter) self.v_splitter.setStretchFactor(1, 2) self.h_splitter.setStretchFactor(0, 2) def _toggle_sorter(self, show): if show: self.show_sorter_btn.setText(">> Hide Sorter") self.plugin_sorter.show() else: self.show_sorter_btn.setText("<< Show Sorter") self.plugin_sorter.hide() def _toggle_status(self, show): if show: self.show_status_btn.setText("Hide Status") self.stdout_text.show() else: self.show_status_btn.setText("Show Status") self.stdout_text.hide()
class PyChopGui(QMainWindow): """ GUI Class using PyQT for PyChop to help users plan inelastic neutron experiments at spallation sources by calculating the resolution and flux at a given neutron energies. """ instruments = {} choppers = {} minE = {} maxE = {} def __init__(self): super(PyChopGui, self).__init__() self.folder = os.path.dirname(sys.modules[self.__module__].__file__) for fname in os.listdir(self.folder): if fname.endswith('.yaml'): instobj = Instrument(os.path.join(self.folder, fname)) self.instruments[instobj.name] = instobj self.choppers[instobj.name] = instobj.getChopperNames() self.minE[instobj.name] = max([instobj.emin, 0.01]) self.maxE[instobj.name] = instobj.emax self.drawLayout() self.setInstrument(list(self.instruments.keys())[0]) self.resaxes_xlim = 0 self.qeaxes_xlim = 0 self.isFramePlotted = 0 def setInstrument(self, instname): """ Defines the instrument parameters by the name of the instrument. """ self.engine = self.instruments[str(instname)] self.tabs.setTabEnabled(self.tdtabID, False) self.widgets['ChopperCombo']['Combo'].clear() self.widgets['FrequencyCombo']['Combo'].clear() self.widgets['FrequencyCombo']['Label'].setText('Frequency') self.widgets['PulseRemoverCombo']['Combo'].clear() for item in self.choppers[str(instname)]: self.widgets['ChopperCombo']['Combo'].addItem(item) rep = self.engine.moderator.source_rep maxfreq = self.engine.chopper_system.max_frequencies # At the moment, the GUI only supports up to two independent frequencies if not hasattr(maxfreq, '__len__') or len(maxfreq) == 1: self.widgets['PulseRemoverCombo']['Combo'].hide() self.widgets['PulseRemoverCombo']['Label'].hide() for fq in range( rep, (maxfreq[0] if hasattr(maxfreq, '__len__') else maxfreq) + 1, rep): self.widgets['FrequencyCombo']['Combo'].addItem(str(fq)) if hasattr(self.engine.chopper_system, 'frequency_names'): self.widgets['FrequencyCombo']['Label'].setText( self.engine.chopper_system.frequency_names[0]) else: self.widgets['PulseRemoverCombo']['Combo'].show() self.widgets['PulseRemoverCombo']['Label'].show() if hasattr(self.engine.chopper_system, 'frequency_names'): for idx, chp in enumerate([ self.widgets['FrequencyCombo']['Label'], self.widgets['PulseRemoverCombo']['Label'] ]): chp.setText( self.engine.chopper_system.frequency_names[idx]) for fq in range(rep, maxfreq[0] + 1, rep): self.widgets['FrequencyCombo']['Combo'].addItem(str(fq)) for fq in range(rep, maxfreq[1] + 1, rep): self.widgets['PulseRemoverCombo']['Combo'].addItem(str(fq)) if len(self.engine.chopper_system.choppers) > 1: self.widgets['MultiRepCheck'].setEnabled(True) self.tabs.setTabEnabled(self.tdtabID, True) else: self.widgets['MultiRepCheck'].setEnabled(False) self.widgets['MultiRepCheck'].setChecked(False) self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() if self.engine.chopper_system.isPhaseIndependent: self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() self.widgets['Chopper2Phase']['Edit'].setText( str(self.engine.chopper_system.defaultPhase[0])) self.widgets['Chopper2Phase']['Label'].setText( self.engine.chopper_system.phaseNames[0]) # Special case for MERLIN - hide phase control from normal users if 'MERLIN' in str(instname) and not self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() self.engine.setChopper( str(self.widgets['ChopperCombo']['Combo'].currentText())) self.engine.setFrequency( float(self.widgets['FrequencyCombo']['Combo'].currentText())) val = self.flxslder.val * self.maxE[self.engine.instname] / 100 self.flxedt.setText('%3.2f' % (val)) nframe = self.engine.moderator.n_frame if hasattr( self.engine.moderator, 'n_frame') else 1 self.repfig_nframe_edit.setText(str(nframe)) if hasattr(self.engine.chopper_system, 'default_frequencies'): cb = [ self.widgets['FrequencyCombo']['Combo'], self.widgets['PulseRemoverCombo']['Combo'] ] for idx, freq in enumerate( self.engine.chopper_system.default_frequencies): cb[idx].setCurrentIndex([ i for i in range(cb[idx].count()) if str(freq) in cb[idx].itemText(i) ][0]) if idx > 1: break self.tabs.setTabEnabled(self.qetabID, False) if self.engine.has_detector and hasattr(self.engine.detector, 'tthlims'): self.tabs.setTabEnabled(self.qetabID, True) def setChopper(self, choppername): """ Defines the Fermi chopper slit package type by name, or the disk chopper arrangement variant. """ self.engine.setChopper(str(choppername)) self.engine.setFrequency( float(self.widgets['FrequencyCombo']['Combo'].currentText())) # Special case for MERLIN - only enable multirep for 'G' chopper if 'MERLIN' in self.engine.instname: if 'G' in str(choppername): self.widgets['MultiRepCheck'].setEnabled(True) self.tabs.setTabEnabled(self.tdtabID, True) self.widgets['Chopper2Phase']['Edit'].setText('1500') self.widgets['Chopper2Phase']['Label'].setText( 'Disk chopper phase delay time') if self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() else: self.widgets['MultiRepCheck'].setEnabled(False) self.widgets['MultiRepCheck'].setChecked(False) self.tabs.setTabEnabled(self.tdtabID, False) self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() def setFreq(self, freqtext=None, **kwargs): """ Sets the chopper frequency(ies), in Hz. """ freq_gui = float(self.widgets['FrequencyCombo']['Combo'].currentText()) freq_in = kwargs['manual_freq'] if ('manual_freq' in kwargs.keys()) else freq_gui if len(self.engine.getFrequency()) > 1 and ( not hasattr(freq_in, '__len__') or len(freq_in) == 1): freqpr = float( self.widgets['PulseRemoverCombo']['Combo'].currentText()) freq_in = [freq_in, freqpr] if not self.widgets['Chopper2Phase']['Label'].isHidden(): chop2phase = self.widgets['Chopper2Phase']['Edit'].text() if isinstance(self.engine.chopper_system.defaultPhase[0], string_types): chop2phase = str(chop2phase) else: chop2phase = float(chop2phase) % ( 1e6 / self.engine.moderator.source_rep) self.engine.setFrequency(freq_in, phase=chop2phase) else: self.engine.setFrequency(freq_in) def setEi(self): """ Sets the incident energy (or focused incident energy for multi-rep case). """ try: eitxt = float(self.widgets['EiEdit']['Edit'].text()) self.engine.setEi(eitxt) if self.eiPlots.isChecked(): self.calc_callback() except ValueError: raise ValueError('No Ei specified, or Ei string not understood') def calc_callback(self): """ Calls routines to calculate the resolution / flux and to update the Matplotlib graphs. """ try: if self.engine.getChopper() is None: self.setChopper( self.widgets['ChopperCombo']['Combo'].currentText()) self.setEi() self.setFreq() self.calculate() if self.errormess: idx = [ i for i, ei in enumerate(self.eis) if np.abs(ei - self.engine.getEi()) < 1.e-4 ] if idx and self.flux[idx[0]] == 0: raise ValueError(self.errormess) self.errormessage(self.errormess) self.plot_res() self.plot_frame() if self.instSciAct.isChecked(): self.update_script() except ValueError as err: self.errormessage(err) self.plot_flux_ei() self.plot_flux_hz() def calculate(self): """ Performs the resolution and flux calculations. """ self.errormess = None if self.engine.getEi() is None: self.setEi() if self.widgets['MultiRepCheck'].isChecked(): en = np.linspace(0, 0.95, 200) self.eis = self.engine.getAllowedEi() with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) self.res = self.engine.getMultiRepResolution(en) self.flux = self.engine.getMultiRepFlux() if len(w) > 0: mess = [str(w[i].message) for i in range(len(w))] self.errormess = '\n'.join( [m for m in mess if 'tchop' in m]) else: en = np.linspace(0, 0.95 * self.engine.getEi(), 200) with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) self.res = self.engine.getResolution(en) self.flux = self.engine.getFlux() if len(w) > 0: raise ValueError(w[0].message) def _set_overplot(self, overplot, axisname): axis = getattr(self, axisname) if overplot: if matplotlib.compare_versions('2.1.0', matplotlib.__version__): axis.hold(True) else: setattr(self, axisname + '_xlim', 0) axis.clear() axis.axhline(color='k') def plot_res(self): """ Plots the resolution in the resolution tab """ overplot = self.widgets['HoldCheck'].isChecked() multiplot = self.widgets['MultiRepCheck'].isChecked() self._set_overplot(overplot, 'resaxes') self._set_overplot(overplot, 'qeaxes') inst = self.engine.instname freq = self.engine.getFrequency() if hasattr(freq, '__len__'): freq = freq[0] if multiplot: if matplotlib.compare_versions('2.1.0', matplotlib.__version__): self.resaxes.hold(True) for ie, Ei in enumerate(self.eis): en = np.linspace(0, 0.95 * Ei, 200) if any(self.res[ie]): if not self.flux[ie]: continue line, = self.resaxes.plot(en, self.res[ie]) label_text = '%s_%3.2fmeV_%dHz_Flux=%fn/cm2/s' % ( inst, Ei, freq, self.flux[ie]) line.set_label(label_text) if self.tabs.isTabEnabled(self.qetabID): self.plot_qe(Ei, label_text, hold=True) self.resaxes_xlim = max(Ei, self.resaxes_xlim) if matplotlib.compare_versions('2.1.0', matplotlib.__version__): self.resaxes.hold(False) else: ei = self.engine.getEi() en = np.linspace(0, 0.95 * ei, 200) line, = self.resaxes.plot(en, self.res) chopper = self.engine.getChopper() label_text = '%s_%s_%3.2fmeV_%dHz_Flux=%fn/cm2/s' % ( inst, chopper, ei, freq, self.flux) line.set_label(label_text) if self.tabs.isTabEnabled(self.qetabID): self.plot_qe(ei, label_text, overplot) self.resaxes_xlim = max(ei, self.resaxes_xlim) self.resaxes.set_xlim([0, self.resaxes_xlim]) self.resaxes.legend().draggable() self.resaxes.set_xlabel('Energy Transfer (meV)') self.resaxes.set_ylabel(r'$\Delta$E (meV FWHM)') self.rescanvas.draw() def plot_qe(self, Ei, label_text, hold=False): """ Plots the Q-E diagram """ from scipy import constants E2q, meV2J = (2. * constants.m_n / (constants.hbar**2), constants.e / 1000.) en = np.linspace(-Ei / 5., Ei, 100) q2 = [] for tth in self.engine.detector.tthlims: q = np.sqrt(E2q * (2 * Ei - en - 2 * np.sqrt(Ei * (Ei - en)) * np.cos(np.deg2rad(tth))) * meV2J) / 1e10 q2.append(np.concatenate((np.flipud(q), q))) self._set_overplot(hold, 'qeaxes') self.qeaxes_xlim = max(np.max(q2), self.qeaxes_xlim) line, = self.qeaxes.plot( np.hstack(q2), np.concatenate((np.flipud(en), en)).tolist() * len(self.engine.detector.tthlims)) line.set_label(label_text) self.qeaxes.set_xlim([0, self.qeaxes_xlim]) self.qeaxes.legend().draggable() self.qecanvas.draw() def plot_flux_ei(self, **kwargs): """ Plots the flux vs Ei in the middle tab """ inst = self.engine.instname chop = self.engine.getChopper() freq = self.engine.getFrequency() overplot = self.widgets['HoldCheck'].isChecked() if hasattr(freq, '__len__'): freq = freq[0] update = kwargs['update'] if 'update' in kwargs.keys() else False # Do not recalculate if all relevant parameters still the same. _, labels = self.flxaxes2.get_legend_handles_labels() searchStr = '([A-Z]+) "(.+)" ([0-9]+) Hz' tmpinst = [] if (labels and (overplot or len(labels) == 1)) or update: for prevtitle in labels: prevInst, prevChop, prevFreq = re.search(searchStr, prevtitle).groups() if update: tmpinst.append( copy.deepcopy( Instrument(self.instruments[prevInst], prevChop, float(prevFreq)))) else: if inst == prevInst and chop == prevChop and freq == float( prevFreq): return ne = 25 mn = self.minE[inst] mx = (self.flxslder.val / 100) * self.maxE[inst] eis = np.linspace(mn, mx, ne) flux = eis * 0 elres = eis * 0 if update: self.flxaxes1.clear() self.flxaxes2.clear() if matplotlib.compare_versions('2.1.0', matplotlib.__version__): self.flxaxes1.hold(True) self.flxaxes2.hold(True) for ii, instrument in enumerate(tmpinst): for ie, ei in enumerate(eis): with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = instrument.getFlux(ei) elres[ie] = instrument.getResolution(0., ei)[0] self.flxaxes1.plot(eis, flux) line, = self.flxaxes2.plot(eis, elres) line.set_label(labels[ii]) else: for ie, ei in enumerate(eis): with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: if matplotlib.compare_versions('2.1.0', matplotlib.__version__): self.flxaxes1.hold(True) self.flxaxes2.hold(True) else: self.flxaxes1.clear() self.flxaxes2.clear() self.flxaxes1.plot(eis, flux) line, = self.flxaxes2.plot(eis, elres) line.set_label('%s "%s" %d Hz' % (inst, chop, freq)) self.flxaxes1.set_xlim([mn, mx]) self.flxaxes2.set_xlim([mn, mx]) self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes2.set_ylabel('Elastic Resolution FWHM (meV)') lg = self.flxaxes2.legend() lg.draggable() self.flxcanvas.draw() def update_slider(self, val=None): """ Callback function for the x-axis slider of the flux tab """ if val is None: val = float( self.flxedt.text()) / self.maxE[self.engine.instname] * 100 if val < self.minE[self.engine.instname]: self.errormessage("Max Ei must be greater than %2.1f" % (self.minE[self.engine.instname])) val = (self.minE[self.engine.instname] + 0.1) / self.maxE[self.engine.instname] * 100 self.flxslder.set_val(val) else: val = self.flxslder.val * self.maxE[self.engine.instname] / 100 self.flxedt.setText('%3.2f' % (val)) self.plot_flux_ei(update=True) self.flxcanvas.draw() def plot_flux_hz(self): """ Plots the flux vs freq in the middle tab """ inst = self.engine.instname chop = self.engine.getChopper() ei = float(self.widgets['EiEdit']['Edit'].text()) overplot = self.widgets['HoldCheck'].isChecked() # Do not recalculate if one of the plots has the same parametersc _, labels = self.frqaxes2.get_legend_handles_labels() searchStr = '([A-Z]+) "(.+)" Ei = ([0-9.-]+) meV' if labels and (overplot or len(labels) == 1): for prevtitle in labels: prevInst, prevChop, prevEi = re.search(searchStr, prevtitle).groups() if inst == prevInst and chop == prevChop and abs( ei - float(prevEi)) < 0.01: return freq0 = self.engine.getFrequency() rep = self.engine.moderator.source_rep maxfreq = self.engine.chopper_system.max_frequencies freqs = range( rep, (maxfreq[0] if hasattr(maxfreq, '__len__') else maxfreq) + 1, rep) flux = np.zeros(len(freqs)) elres = np.zeros(len(freqs)) for ie, freq in enumerate(freqs): if hasattr(freq0, '__len__'): self.setFreq(manual_freq=[freq] + freq0[1:]) else: self.setFreq(manual_freq=freq) with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: if matplotlib.compare_versions('2.1.0', matplotlib.__version__): self.frqaxes1.hold(True) self.frqaxes2.hold(True) else: self.frqaxes1.clear() self.frqaxes2.clear() self.setFreq(manual_freq=freq0) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes1.set_ylabel('Flux (n/cm$^2$/s)') line, = self.frqaxes1.plot(freqs, flux, 'o-') self.frqaxes1.set_xlim([0, np.max(freqs)]) self.frqaxes2.set_xlabel('Chopper Frequency (Hz)') self.frqaxes2.set_ylabel('Elastic Resolution FWHM (meV)') line, = self.frqaxes2.plot(freqs, elres, 'o-') line.set_label('%s "%s" Ei = %5.3f meV' % (inst, chop, ei)) lg = self.frqaxes2.legend() lg.draggable() self.frqaxes2.set_xlim([0, np.max(freqs)]) self.frqcanvas.draw() def instSciCB(self): """ Callback function for the "Instrument Scientist Mode" menu option """ # MERLIN is a special case - want to hide ability to change phase from users if 'MERLIN' in self.engine.instname and 'G' in self.engine.getChopper( ): if self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() self.widgets['Chopper2Phase']['Edit'].setText('1500') self.widgets['Chopper2Phase']['Label'].setText( 'Disk chopper phase delay time') else: self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() if self.instSciAct.isChecked(): self.tabs.insertTab(self.scrtabID, self.scrtab, 'ScriptOutput') self.scrtab.show() else: self.tabs.removeTab(self.scrtabID) self.scrtab.hide() def errormessage(self, message): msg = QMessageBox() msg.setText(str(message)) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def loadYaml(self): yaml_file = QFileDialog().getOpenFileName(self.mainWidget, 'Open Instrument YAML File', self.folder, 'Files (*.yaml)') if isinstance(yaml_file, tuple): yaml_file = yaml_file[0] yaml_file = str(yaml_file) new_folder = os.path.dirname(yaml_file) if new_folder != self.folder: self.folder = new_folder try: new_inst = Instrument(yaml_file) except (RuntimeError, AttributeError, ValueError) as err: self.errormessage(err) newname = new_inst.name if newname in self.instruments.keys( ) and not self.overwriteload.isChecked(): overwrite, newname = self._ask_overwrite() if overwrite == 1: return elif overwrite == 0: newname = new_inst.name self.instruments[newname] = new_inst self.choppers[newname] = new_inst.getChopperNames() self.minE[newname] = max([new_inst.emin, 0.01]) self.maxE[newname] = new_inst.emax self.updateInstrumentList() combo = self.widgets['InstrumentCombo']['Combo'] idx = [ i for i in range(combo.count()) if str(combo.itemText(i)) == newname ] combo.setCurrentIndex(idx[0]) self.setInstrument(newname) def _ask_overwrite(self): msg = QDialog() msg.setWindowTitle('Load overwrite') layout = QGridLayout() layout.addWidget( QLabel('Instrument %s already exists in memory. Overwrite this?'), 0, 0, 1, -1) buttons = [ QPushButton(label) for label in ['Load and overwrite', 'Cancel Load', 'Load and rename to'] ] locations = [[1, 0], [1, 1], [2, 0]] self.overwrite_flag = 1 def overwriteCB(idx): self.overwrite_flag = idx msg.accept() for idx, button in enumerate(buttons): button.clicked.connect(lambda _, idx=idx: overwriteCB(idx)) layout.addWidget(button, locations[idx][0], locations[idx][1]) newname = QLineEdit() newname.editingFinished.connect(lambda: overwriteCB(2)) layout.addWidget(newname, 2, 1) msg.setLayout(layout) msg.exec_() newname = str(newname.text()) if not newname or newname in self.instruments: self.errormessage('Invalid instrument name. Cancelling load.') self.overwrite_flag = 1 return self.overwrite_flag, newname def updateInstrumentList(self): combo = self.widgets['InstrumentCombo']['Combo'] old_instruments = [ str(combo.itemText(i)) for i in range(combo.count()) ] new_instruments = [ inst for inst in self.instruments if inst not in old_instruments ] for inst in new_instruments: combo.addItem(inst) def plot_frame(self): """ Plots the distance-time diagram in the right tab """ if len(self.engine.chopper_system.choppers) > 1: self.engine.n_frame = int(self.repfig_nframe_edit.text()) self.repaxes.clear() self.engine.plotMultiRepFrame(self.repaxes) self.repcanvas.draw() def genText(self): """ Generates text output of the resolution function versus energy transfer and other information. """ en = np.linspace(0, 0.95 * self.engine.getEi(), 10) try: flux = self.engine.getFlux() res = self.engine.getResolution(en) except ValueError as err: self.errormessage(err) raise ValueError(err) obj = self.engine instname, chtyp, freqs, ei_in = tuple( [obj.instname, obj.getChopper(), obj.getFrequency(), obj.getEi()]) ei = ei_in tsqvan, tsqdic, tsqmodchop = obj.getVanVar() v_mod, v_chop = tuple(np.sqrt(tsqmodchop[:2]) * 1e6) x0, _, x1, x2, _ = obj.chopper_system.getDistances() first_component = 'moderator' if x0 != tsqmodchop[2]: x0 = tsqmodchop[2] first_component = 'chopper 1' txt = '# ------------------------------------------------------------- #\n' txt += '# Chop calculation for instrument %s\n' % (instname) if obj.isFermi: txt += '# with chopper %s at %3i Hz\n' % (chtyp, freqs[0]) else: txt += '# in %s mode with:\n' % (chtyp) freq_names = obj.chopper_system.frequency_names for idx in range(len(freq_names)): txt += '# %s at %3i Hz\n' % (freq_names[idx], freqs[idx]) txt += '# ------------------------------------------------------------- #\n' txt += '# Flux = %8.2f n/cm2/s\n' % (flux) txt += '# Elastic resolution = %6.2f meV\n' % (res[0]) txt += '# Time width at sample = %6.2f us, of which:\n' % ( 1e6 * np.sqrt(tsqvan)) for ky, val in list(tsqdic.items()): txt += '# %20s : %6.2f us\n' % (ky, 1e6 * np.sqrt(val)) txt += '# %s distances:\n' % (instname) txt += '# x0 = %6.2f m (%s to Fermi)\n' % (x0, first_component) txt += '# x1 = %6.2f m (Fermi to sample)\n' % (x1) txt += '# x2 = %6.2f m (sample to detector)\n' % (x2) txt += '# Approximate inelastic resolution is given by:\n' txt += '# dE = 2 * E2V * sqrt(ef**3 * t_van**2) / x2\n' txt += '# where: E2V = 4.373e-4 meV/(m/us) conversion from energy to speed\n' txt += '# t_van**2 = (geom*t_mod)**2 + ((1+geom)*t_chop)**2\n' txt += '# geom = (x1 + x2*(ei/ef)**1.5) / x0\n' txt += '# and t_mod and t_chop are the moderator and chopper time widths at the\n' txt += '# moderator and chopper positions (not at the sample as listed above).\n' txt += '# Which in this case is:\n' txt += '# %.4e*sqrt(ef**3 * ( (%6.5f*(%.3f+%.3f*(ei/ef)**1.5))**2 \n' % ( 874.78672e-6 / x2, v_mod, x1 / x0, x2 / x0) txt += '# + (%6.5f*(%.3f+%.3f*(ei/ef)**1.5))**2) )\n' % ( v_chop, 1 + x1 / x0, x2 / x0) txt += '# EN (meV) Full dE (meV) Approx dE (meV)\n' for ii in range(len(res)): ef = ei - en[ii] approx = (874.78672e-6 / x2) * np.sqrt(ef**3 * ( (v_mod * ((x1 / x0) + (x2 / x0) * (ei / ef)**1.5))**2 + (v_chop * (1 + (x1 / x0) + (x2 / x0) * (ei / ef)**1.5))**2)) txt += '%12.5f %12.5f %12.5f\n' % (en[ii], res[ii], approx) return txt def showText(self): """ Creates a dialog to show the generated text output. """ try: generatedText = self.genText() except ValueError: return self.txtwin = QDialog() self.txtedt = QTextEdit() self.txtbtn = QPushButton('OK') self.txtwin.layout = QVBoxLayout(self.txtwin) self.txtwin.layout.addWidget(self.txtedt) self.txtwin.layout.addWidget(self.txtbtn) self.txtbtn.clicked.connect(self.txtwin.deleteLater) self.txtedt.setText(generatedText) self.txtedt.setReadOnly(True) self.txtwin.setWindowTitle('Resolution information') self.txtwin.setWindowModality(Qt.ApplicationModal) self.txtwin.setAttribute(Qt.WA_DeleteOnClose) self.txtwin.setMinimumSize(400, 600) self.txtwin.resize(400, 600) self.txtwin.show() self.txtloop = QEventLoop() self.txtloop.exec_() def saveText(self): """ Saves the generated text to a file (opens file dialog). """ fname = QFileDialog.getSaveFileName(self, 'Open file', '') if isinstance(fname, tuple): fname = fname[0] fid = open(fname, 'w') fid.write(self.genText()) fid.close() def update_script(self): """ Updates the text window with information about the previous calculation. """ if self.widgets['MultiRepCheck'].isChecked(): out = self.engine.getMultiWidths() new_str = '\n' for ie, ee in enumerate(out['Eis']): res = out['Energy'][ie] percent = res / ee * 100 chop_width = out['chopper'][ie] mod_width = out['moderator'][ie] new_str += 'Ei is %6.2f meV, resolution is %6.2f ueV, percentage resolution is %6.3f\n' % ( ee, res * 1000, percent) new_str += 'FWHM at sample from chopper and moderator are %6.2f us, %6.2f us\n' % ( chop_width, mod_width) else: ei = self.engine.getEi() out = self.engine.getWidths() res = out['Energy'] percent = res / ei * 100 chop_width = out['chopper'] mod_width = out['moderator'] new_str = '\nEi is %6.2f meV, resolution is %6.2f ueV, percentage resolution is %6.3f\n' % ( ei, res * 1000, percent) new_str += 'FWHM at sample from chopper and moderator are %6.2f us, %6.2f us\n' % ( chop_width, mod_width) self.scredt.append(new_str) def onHelp(self): """ Shows the help page """ try: from pymantidplot.proxies import showCustomInterfaceHelp showCustomInterfaceHelp("PyChop") except ImportError: helpTxt = "PyChop is a tool to allow direct inelastic neutron\nscattering users to estimate the inelastic resolution\n" helpTxt += "and incident flux for a given spectrometer setting.\n\nFirst select the instrument, chopper settings and\n" helpTxt += "Ei, and then click 'Calculate and Plot'. Data for all\nthe graphs will be generated (may take 1-2s) and\n" helpTxt += "all graphs will be updated. If the 'Hold current plot'\ncheck box is ticked, additional settings will be\n" helpTxt += "overplotted on the existing graphs if they are\ndifferent from previous settings.\n\nMore in-depth help " helpTxt += "can be obtained from the\nMantid help pages." self.hlpwin = QDialog() self.hlpedt = QLabel(helpTxt) self.hlpbtn = QPushButton('OK') self.hlpwin.layout = QVBoxLayout(self.hlpwin) self.hlpwin.layout.addWidget(self.hlpedt) self.hlpwin.layout.addWidget(self.hlpbtn) self.hlpbtn.clicked.connect(self.hlpwin.deleteLater) self.hlpwin.setWindowTitle('Help') self.hlpwin.setWindowModality(Qt.ApplicationModal) self.hlpwin.setAttribute(Qt.WA_DeleteOnClose) self.hlpwin.setMinimumSize(370, 300) self.hlpwin.resize(370, 300) self.hlpwin.show() self.hlploop = QEventLoop() self.hlploop.exec_() def drawLayout(self): """ Draws the GUI layout. """ self.widgetslist = [[ 'pair', 'show', 'Instrument', 'combo', self.instruments, self.setInstrument, 'InstrumentCombo' ], [ 'pair', 'show', 'Chopper', 'combo', '', self.setChopper, 'ChopperCombo' ], [ 'pair', 'show', 'Frequency', 'combo', '', self.setFreq, 'FrequencyCombo' ], [ 'pair', 'hide', 'Pulse remover chopper freq', 'combo', '', self.setFreq, 'PulseRemoverCombo' ], [ 'pair', 'show', 'Ei', 'edit', '', self.setEi, 'EiEdit' ], [ 'pair', 'hide', 'Chopper 2 phase delay time', 'edit', '5', self.setFreq, 'Chopper2Phase' ], ['spacer'], [ 'single', 'show', 'Calculate and Plot', 'button', self.calc_callback, 'CalculateButton' ], [ 'single', 'show', 'Hold current plot', 'check', lambda: None, 'HoldCheck' ], [ 'single', 'show', 'Show multi-reps', 'check', lambda: None, 'MultiRepCheck' ], ['spacer'], [ 'single', 'show', 'Show data ascii window', 'button', self.showText, 'ShowAsciiButton' ], [ 'single', 'show', 'Save data as ascii', 'button', self.saveText, 'SaveAsciiButton' ]] self.droplabels = [] self.dropboxes = [] self.singles = [] self.widgets = {} self.leftPanel = QVBoxLayout() self.rightPanel = QVBoxLayout() self.tabs = QTabWidget(self) self.fullWindow = QGridLayout() for widget in self.widgetslist: if 'pair' in widget[0]: self.droplabels.append(QLabel(widget[2])) if 'combo' in widget[3]: self.dropboxes.append(QComboBox(self)) self.dropboxes[-1].activated['QString'].connect(widget[5]) for item in widget[4]: self.dropboxes[-1].addItem(item) self.widgets[widget[-1]] = { 'Combo': self.dropboxes[-1], 'Label': self.droplabels[-1] } elif 'edit' in widget[3]: self.dropboxes.append(QLineEdit(self)) self.dropboxes[-1].returnPressed.connect(widget[5]) self.widgets[widget[-1]] = { 'Edit': self.dropboxes[-1], 'Label': self.droplabels[-1] } else: raise RuntimeError( 'Bug in code - widget %s is not recognised.' % (widget[3])) self.leftPanel.addWidget(self.droplabels[-1]) self.leftPanel.addWidget(self.dropboxes[-1]) if 'hide' in widget[1]: self.droplabels[-1].hide() self.dropboxes[-1].hide() elif 'single' in widget[0]: if 'check' in widget[3]: self.singles.append(QCheckBox(widget[2], self)) self.singles[-1].stateChanged.connect(widget[4]) elif 'button' in widget[3]: self.singles.append(QPushButton(widget[2])) self.singles[-1].clicked.connect(widget[4]) else: raise RuntimeError( 'Bug in code - widget %s is not recognised.' % (widget[3])) self.leftPanel.addWidget(self.singles[-1]) if 'hide' in widget[1]: self.singles[-1].hide() self.widgets[widget[-1]] = self.singles[-1] elif 'spacer' in widget[0]: self.leftPanel.addItem(QSpacerItem(0, 35)) else: raise RuntimeError( 'Bug in code - widget class %s is not recognised.' % (widget[0])) # Right panel, matplotlib figures self.resfig = Figure() self.resfig.patch.set_facecolor('white') self.rescanvas = FigureCanvas(self.resfig) self.resaxes = self.resfig.add_subplot(111) self.resaxes.axhline(color='k') self.resaxes.set_xlabel('Energy Transfer (meV)') self.resaxes.set_ylabel(r'$\Delta$E (meV FWHM)') self.resfig_controls = NavigationToolbar(self.rescanvas, self) self.restab = QWidget(self.tabs) self.restabbox = QVBoxLayout() self.restabbox.addWidget(self.rescanvas) self.restabbox.addWidget(self.resfig_controls) self.restab.setLayout(self.restabbox) self.flxfig = Figure() self.flxfig.patch.set_facecolor('white') self.flxcanvas = FigureCanvas(self.flxfig) self.flxaxes1 = self.flxfig.add_subplot(121) self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.flxaxes2 = self.flxfig.add_subplot(122) self.flxaxes2.set_xlabel('Incident Energy (meV)') self.flxaxes2.set_ylabel('Elastic Resolution FWHM (meV)') self.flxfig_controls = NavigationToolbar(self.flxcanvas, self) self.flxsldfg = Figure() self.flxsldfg.patch.set_facecolor('white') self.flxsldcv = FigureCanvas(self.flxsldfg) self.flxsldax = self.flxsldfg.add_subplot(111) self.flxslder = Slider(self.flxsldax, 'Ei (meV)', 0, 100, valinit=100) self.flxslder.valtext.set_visible(False) self.flxslder.on_changed(self.update_slider) self.flxedt = QLineEdit() self.flxedt.setText('1000') self.flxedt.returnPressed.connect(self.update_slider) self.flxtab = QWidget(self.tabs) self.flxsldbox = QHBoxLayout() self.flxsldbox.addWidget(self.flxsldcv) self.flxsldbox.addWidget(self.flxedt) self.flxsldwdg = QWidget() self.flxsldwdg.setLayout(self.flxsldbox) sz = self.flxsldwdg.maximumSize() sz.setHeight(50) self.flxsldwdg.setMaximumSize(sz) self.flxtabbox = QVBoxLayout() self.flxtabbox.addWidget(self.flxcanvas) self.flxtabbox.addWidget(self.flxsldwdg) self.flxtabbox.addWidget(self.flxfig_controls) self.flxtab.setLayout(self.flxtabbox) self.frqfig = Figure() self.frqfig.patch.set_facecolor('white') self.frqcanvas = FigureCanvas(self.frqfig) self.frqaxes1 = self.frqfig.add_subplot(121) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.frqaxes2 = self.frqfig.add_subplot(122) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes2.set_ylabel('Elastic Resolution FWHM (meV)') self.frqfig_controls = NavigationToolbar(self.frqcanvas, self) self.frqtab = QWidget(self.tabs) self.frqtabbox = QVBoxLayout() self.frqtabbox.addWidget(self.frqcanvas) self.frqtabbox.addWidget(self.frqfig_controls) self.frqtab.setLayout(self.frqtabbox) self.repfig = Figure() self.repfig.patch.set_facecolor('white') self.repcanvas = FigureCanvas(self.repfig) self.repaxes = self.repfig.add_subplot(111) self.repaxes.axhline(color='k') self.repaxes.set_xlabel(r'TOF ($\mu$sec)') self.repaxes.set_ylabel('Distance (m)') self.repfig_controls = NavigationToolbar(self.repcanvas, self) self.repfig_nframe_label = QLabel('Number of frames to plot') self.repfig_nframe_edit = QLineEdit('1') self.repfig_nframe_button = QPushButton('Replot') self.repfig_nframe_button.clicked.connect(lambda: self.plot_frame()) self.repfig_nframe_box = QHBoxLayout() self.repfig_nframe_box.addWidget(self.repfig_nframe_label) self.repfig_nframe_box.addWidget(self.repfig_nframe_edit) self.repfig_nframe_box.addWidget(self.repfig_nframe_button) self.reptab = QWidget(self.tabs) self.repfig_nframe = QWidget(self.reptab) self.repfig_nframe.setLayout(self.repfig_nframe_box) self.repfig_nframe.setSizePolicy( QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)) self.reptabbox = QVBoxLayout() self.reptabbox.addWidget(self.repcanvas) self.reptabbox.addWidget(self.repfig_nframe) self.reptabbox.addWidget(self.repfig_controls) self.reptab.setLayout(self.reptabbox) self.qefig = Figure() self.qefig.patch.set_facecolor('white') self.qecanvas = FigureCanvas(self.qefig) self.qeaxes = self.qefig.add_subplot(111) self.qeaxes.axhline(color='k') self.qeaxes.set_xlabel(r'$|Q| (\mathrm{\AA}^{-1})$') self.qeaxes.set_ylabel('Energy Transfer (meV)') self.qefig_controls = NavigationToolbar(self.qecanvas, self) self.qetabbox = QVBoxLayout() self.qetabbox.addWidget(self.qecanvas) self.qetabbox.addWidget(self.qefig_controls) self.qetab = QWidget(self.tabs) self.qetab.setLayout(self.qetabbox) self.scrtab = QWidget(self.tabs) self.scredt = QTextEdit() self.scrcls = QPushButton("Clear") self.scrcls.clicked.connect(lambda: self.scredt.clear()) self.scrbox = QVBoxLayout() self.scrbox.addWidget(self.scredt) self.scrbox.addWidget(self.scrcls) self.scrtab.setLayout(self.scrbox) self.scrtab.hide() self.tabs.addTab(self.restab, 'Resolution') self.tabs.addTab(self.flxtab, 'Flux-Ei') self.tabs.addTab(self.frqtab, 'Flux-Freq') self.tabs.addTab(self.reptab, 'Time-Distance') self.tdtabID = 3 self.tabs.setTabEnabled(self.tdtabID, False) self.tabs.addTab(self.qetab, 'Q-E') self.qetabID = 4 self.tabs.setTabEnabled(self.qetabID, False) self.scrtabID = 5 self.rightPanel.addWidget(self.tabs) self.menuLoad = QMenu('Load') self.loadAct = QAction('Load YAML', self.menuLoad) self.loadAct.triggered.connect(self.loadYaml) self.menuLoad.addAction(self.loadAct) self.menuOptions = QMenu('Options') self.instSciAct = QAction('Instrument Scientist Mode', self.menuOptions, checkable=True) self.instSciAct.triggered.connect(self.instSciCB) self.menuOptions.addAction(self.instSciAct) self.eiPlots = QAction('Press Enter in Ei box updates plots', self.menuOptions, checkable=True) self.menuOptions.addAction(self.eiPlots) self.overwriteload = QAction('Always overwrite instruments in memory', self.menuOptions, checkable=True) self.menuOptions.addAction(self.overwriteload) self.menuBar().addMenu(self.menuLoad) self.menuBar().addMenu(self.menuOptions) self.leftPanelWidget = QWidget() self.leftPanelWidget.setLayout(self.leftPanel) self.leftPanelWidget.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)) self.fullWindow.addWidget(self.leftPanelWidget, 0, 0) self.fullWindow.addLayout(self.rightPanel, 0, 1) self.helpbtn = QPushButton("?", self) self.helpbtn.setMaximumWidth(30) self.helpbtn.clicked.connect(self.onHelp) self.fullWindow.addWidget(self.helpbtn, 1, 0, 1, -1) self.mainWidget = QWidget() self.mainWidget.setLayout(self.fullWindow) self.setCentralWidget(self.mainWidget) self.setWindowTitle('PyChopGUI') self.show()
class QtPluginDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.refresh_state = RefreshState.DONE self.already_installed = set() installer_type = "mamba" if running_as_constructor_app() else "pip" self.installer = Installer(installer=installer_type) self.setup_ui() self.installer.set_output_widget(self.stdout_text) self.installer.started.connect(self._on_installer_start) self.installer.finished.connect(self._on_installer_done) self.refresh() def _on_installer_start(self): self.cancel_all_btn.setVisible(True) self.working_indicator.show() self.process_error_indicator.hide() self.close_btn.setDisabled(True) def _on_installer_done(self, exit_code): self.working_indicator.hide() if exit_code: self.process_error_indicator.show() self.cancel_all_btn.setVisible(False) self.close_btn.setDisabled(False) self.refresh() def closeEvent(self, event): if self.close_btn.isEnabled(): super().closeEvent(event) event.ignore() def refresh(self): if self.refresh_state != RefreshState.DONE: self.refresh_state = RefreshState.OUTDATED return self.refresh_state = RefreshState.REFRESHING self.installed_list.clear() self.available_list.clear() # fetch installed from npe2 import PluginManager from ...plugins import plugin_manager plugin_manager.discover() # since they might not be loaded yet self.already_installed = set() def _add_to_installed(distname, enabled, npe_version=1): norm_name = normalized_name(distname or '') if distname: try: meta = metadata(distname) except PackageNotFoundError: self.refresh_state = RefreshState.OUTDATED return # a race condition has occurred and the package is uninstalled by another thread if len(meta) == 0: # will not add builtins. return self.already_installed.add(norm_name) else: meta = {} self.installed_list.addItem( PackageMetadata( metadata_version="1.0", name=norm_name, version=meta.get('version', ''), summary=meta.get('summary', ''), home_page=meta.get('url', ''), author=meta.get('author', ''), license=meta.get('license', ''), ), installed=True, enabled=enabled, npe_version=npe_version, ) pm2 = PluginManager.instance() for manifest in pm2.iter_manifests(): distname = normalized_name(manifest.name or '') if distname in self.already_installed or distname == 'napari': continue enabled = not pm2.is_disabled(manifest.name) _add_to_installed(distname, enabled, npe_version=2) for ( plugin_name, _mod_name, distname, ) in plugin_manager.iter_available(): # not showing these in the plugin dialog if plugin_name in ('napari_plugin_engine', ): continue if distname in self.already_installed: continue _add_to_installed(distname, not plugin_manager.is_blocked(plugin_name)) self.installed_label.setText( trans._( "Installed Plugins ({amount})", amount=len(self.already_installed), )) # fetch available plugins settings = get_settings() use_hub = (running_as_bundled_app() or running_as_constructor_app() or settings.plugins.plugin_api.name == "napari_hub") if use_hub: conda_forge = running_as_constructor_app() self.worker = create_worker(iter_hub_plugin_info, conda_forge=conda_forge) else: self.worker = create_worker(iter_napari_plugin_info) self.worker.yielded.connect(self._handle_yield) self.worker.finished.connect(self.working_indicator.hide) self.worker.finished.connect(self._update_count_in_label) self.worker.finished.connect(self._end_refresh) self.worker.start() def setup_ui(self): self.resize(1080, 640) vlay_1 = QVBoxLayout(self) self.h_splitter = QSplitter(self) vlay_1.addWidget(self.h_splitter) self.h_splitter.setOrientation(Qt.Horizontal) self.v_splitter = QSplitter(self.h_splitter) self.v_splitter.setOrientation(Qt.Vertical) self.v_splitter.setMinimumWidth(500) installed = QWidget(self.v_splitter) lay = QVBoxLayout(installed) lay.setContentsMargins(0, 2, 0, 2) self.installed_label = QLabel(trans._("Installed Plugins")) self.packages_filter = QLineEdit() self.packages_filter.setPlaceholderText(trans._("filter...")) self.packages_filter.setMaximumWidth(350) self.packages_filter.setClearButtonEnabled(True) mid_layout = QVBoxLayout() mid_layout.addWidget(self.packages_filter) mid_layout.addWidget(self.installed_label) lay.addLayout(mid_layout) self.installed_list = QPluginList(installed, self.installer) self.packages_filter.textChanged.connect(self.installed_list.filter) lay.addWidget(self.installed_list) uninstalled = QWidget(self.v_splitter) lay = QVBoxLayout(uninstalled) lay.setContentsMargins(0, 2, 0, 2) self.avail_label = QLabel(trans._("Available Plugins")) mid_layout = QHBoxLayout() mid_layout.addWidget(self.avail_label) mid_layout.addStretch() lay.addLayout(mid_layout) self.available_list = QPluginList(uninstalled, self.installer) self.packages_filter.textChanged.connect(self.available_list.filter) lay.addWidget(self.available_list) self.stdout_text = QTextEdit(self.v_splitter) self.stdout_text.setReadOnly(True) self.stdout_text.setObjectName("pip_install_status") self.stdout_text.hide() buttonBox = QHBoxLayout() self.working_indicator = QLabel(trans._("loading ..."), self) sp = self.working_indicator.sizePolicy() sp.setRetainSizeWhenHidden(True) self.working_indicator.setSizePolicy(sp) self.process_error_indicator = QLabel(self) self.process_error_indicator.setObjectName("error_label") self.process_error_indicator.hide() load_gif = str(Path(napari.resources.__file__).parent / "loading.gif") mov = QMovie(load_gif) mov.setScaledSize(QSize(18, 18)) self.working_indicator.setMovie(mov) mov.start() visibility_direct_entry = not running_as_constructor_app() self.direct_entry_edit = QLineEdit(self) self.direct_entry_edit.installEventFilter(self) self.direct_entry_edit.setPlaceholderText( trans._('install by name/url, or drop file...')) self.direct_entry_edit.setVisible(visibility_direct_entry) self.direct_entry_btn = QPushButton(trans._("Install"), self) self.direct_entry_btn.setVisible(visibility_direct_entry) self.direct_entry_btn.clicked.connect(self._install_packages) self.show_status_btn = QPushButton(trans._("Show Status"), self) self.show_status_btn.setFixedWidth(100) self.cancel_all_btn = QPushButton(trans._("cancel all actions"), self) self.cancel_all_btn.setObjectName("remove_button") self.cancel_all_btn.setVisible(False) self.cancel_all_btn.clicked.connect(lambda: self.installer.cancel()) self.close_btn = QPushButton(trans._("Close"), self) self.close_btn.clicked.connect(self.accept) self.close_btn.setObjectName("close_button") buttonBox.addWidget(self.show_status_btn) buttonBox.addWidget(self.working_indicator) buttonBox.addWidget(self.direct_entry_edit) buttonBox.addWidget(self.direct_entry_btn) if not visibility_direct_entry: buttonBox.addStretch() buttonBox.addWidget(self.process_error_indicator) buttonBox.addSpacing(20) buttonBox.addWidget(self.cancel_all_btn) buttonBox.addSpacing(20) buttonBox.addWidget(self.close_btn) buttonBox.setContentsMargins(0, 0, 4, 0) vlay_1.addLayout(buttonBox) self.show_status_btn.setCheckable(True) self.show_status_btn.setChecked(False) self.show_status_btn.toggled.connect(self._toggle_status) self.v_splitter.setStretchFactor(1, 2) self.h_splitter.setStretchFactor(0, 2) self.packages_filter.setFocus() def _update_count_in_label(self): count = self.available_list.count() self.avail_label.setText( trans._("Available Plugins ({count})", count=count)) def _end_refresh(self): refresh_state = self.refresh_state self.refresh_state = RefreshState.DONE if refresh_state == RefreshState.OUTDATED: self.refresh() def eventFilter(self, watched, event): if event.type() == QEvent.DragEnter: # we need to accept this event explicitly to be able # to receive QDropEvents! event.accept() if event.type() == QEvent.Drop: md = event.mimeData() if md.hasUrls(): files = [url.toLocalFile() for url in md.urls()] self.direct_entry_edit.setText(files[0]) return True return super().eventFilter(watched, event) def _toggle_status(self, show): if show: self.show_status_btn.setText(trans._("Hide Status")) self.stdout_text.show() else: self.show_status_btn.setText(trans._("Show Status")) self.stdout_text.hide() def _install_packages(self, packages: Sequence[str] = ()): if not packages: _packages = self.direct_entry_edit.text() if os.path.exists(_packages): packages = [_packages] else: packages = _packages.split() self.direct_entry_edit.clear() if packages: self.installer.install(packages) def _handle_yield(self, data: Tuple[PackageMetadata, bool]): project_info, is_available = data if project_info.name in self.already_installed: self.installed_list.tag_outdated(project_info, is_available) else: self.available_list.addItem(project_info) if not is_available: self.available_list.tag_unavailable(project_info) self.filter() def filter(self, text: str = None) -> None: """Filter by text or set current text as filter.""" if text is None: text = self.packages_filter.text() else: self.packages_filter.setText(text) self.installed_list.filter(text) self.available_list.filter(text)
class QtPluginDialog(QDialog): def __init__(self, parent=None): super().__init__(parent) self.installer = Installer() self.setup_ui() self.installer.set_output_widget(self.stdout_text) self.installer.process.started.connect(self._on_installer_start) self.installer.process.finished.connect(self._on_installer_done) self.refresh() def _on_installer_start(self): self.show_status_btn.setChecked(True) self.working_indicator.show() self.process_error_indicator.hide() def _on_installer_done(self, exit_code, exit_status): self.working_indicator.hide() if exit_code: self.process_error_indicator.show() else: self.show_status_btn.setChecked(False) self.refresh() self.plugin_sorter.refresh() def refresh(self): self.installed_list.clear() self.available_list.clear() # fetch installed from ...plugins import plugin_manager plugin_manager.discover() # since they might not be loaded yet already_installed = set() for plugin_name, mod_name, distname in plugin_manager.iter_available(): # not showing these in the plugin dialog if plugin_name in ('napari_plugin_engine', ): continue if distname: already_installed.add(distname) meta = standard_metadata(distname) else: meta = {} self.installed_list.addItem( ProjectInfo( normalized_name(distname or ''), meta.get('version', ''), meta.get('url', ''), meta.get('summary', ''), meta.get('author', ''), meta.get('license', ''), ), plugin_name=plugin_name, enabled=plugin_name in plugin_manager.plugins, ) # self.v_splitter.setSizes([70 * self.installed_list.count(), 10, 10]) # fetch available plugins self.worker = create_worker(iter_napari_plugin_info) def _handle_yield(project_info): if project_info.name in already_installed: self.installed_list.tag_outdated(project_info) else: self.available_list.addItem(project_info) self.worker.yielded.connect(_handle_yield) self.worker.finished.connect(self.working_indicator.hide) self.worker.finished.connect(self._update_count_in_label) self.worker.start() def setup_ui(self): self.resize(1080, 640) vlay_1 = QVBoxLayout(self) self.h_splitter = QSplitter(self) vlay_1.addWidget(self.h_splitter) self.h_splitter.setOrientation(Qt.Horizontal) self.v_splitter = QSplitter(self.h_splitter) self.v_splitter.setOrientation(Qt.Vertical) self.v_splitter.setMinimumWidth(500) self.plugin_sorter = QtPluginSorter(parent=self.h_splitter) self.plugin_sorter.layout().setContentsMargins(2, 0, 0, 0) self.plugin_sorter.hide() installed = QWidget(self.v_splitter) lay = QVBoxLayout(installed) lay.setContentsMargins(0, 2, 0, 2) lay.addWidget(QLabel(trans._("Installed Plugins"))) self.installed_list = QPluginList(installed, self.installer) lay.addWidget(self.installed_list) uninstalled = QWidget(self.v_splitter) lay = QVBoxLayout(uninstalled) lay.setContentsMargins(0, 2, 0, 2) self.avail_label = QLabel(trans._("Available Plugins")) lay.addWidget(self.avail_label) self.available_list = QPluginList(uninstalled, self.installer) lay.addWidget(self.available_list) self.stdout_text = QTextEdit(self.v_splitter) self.stdout_text.setReadOnly(True) self.stdout_text.setObjectName("pip_install_status") self.stdout_text.hide() buttonBox = QHBoxLayout() self.working_indicator = QLabel(trans._("loading ..."), self) sp = self.working_indicator.sizePolicy() sp.setRetainSizeWhenHidden(True) self.working_indicator.setSizePolicy(sp) self.process_error_indicator = QLabel(self) self.process_error_indicator.setObjectName("error_label") self.process_error_indicator.hide() load_gif = str(Path(napari.resources.__file__).parent / "loading.gif") mov = QMovie(load_gif) mov.setScaledSize(QSize(18, 18)) self.working_indicator.setMovie(mov) mov.start() self.direct_entry_edit = QLineEdit(self) self.direct_entry_edit.installEventFilter(self) self.direct_entry_edit.setPlaceholderText( trans._('install by name/url, or drop file...')) self.direct_entry_btn = QPushButton(trans._("Install"), self) self.direct_entry_btn.clicked.connect(self._install_packages) self.show_status_btn = QPushButton(trans._("Show Status"), self) self.show_status_btn.setFixedWidth(100) self.show_sorter_btn = QPushButton(trans._("<< Show Sorter"), self) self.close_btn = QPushButton(trans._("Close"), self) self.close_btn.clicked.connect(self.reject) buttonBox.addWidget(self.show_status_btn) buttonBox.addWidget(self.working_indicator) buttonBox.addWidget(self.direct_entry_edit) buttonBox.addWidget(self.direct_entry_btn) buttonBox.addWidget(self.process_error_indicator) buttonBox.addSpacing(60) buttonBox.addWidget(self.show_sorter_btn) buttonBox.addWidget(self.close_btn) buttonBox.setContentsMargins(0, 0, 4, 0) vlay_1.addLayout(buttonBox) self.show_status_btn.setCheckable(True) self.show_status_btn.setChecked(False) self.show_status_btn.toggled.connect(self._toggle_status) self.show_sorter_btn.setCheckable(True) self.show_sorter_btn.setChecked(False) self.show_sorter_btn.toggled.connect(self._toggle_sorter) self.v_splitter.setStretchFactor(1, 2) self.h_splitter.setStretchFactor(0, 2) def _update_count_in_label(self): count = self.available_list.count() self.avail_label.setText( trans._("Available Plugins ({count})".format(count=count))) def eventFilter(self, watched, event): if event.type() == QEvent.DragEnter: # we need to accept this event explicitly to be able # to receive QDropEvents! event.accept() if event.type() == QEvent.Drop: md = event.mimeData() if md.hasUrls(): files = [url.toLocalFile() for url in md.urls()] self.direct_entry_edit.setText(files[0]) return True return super().eventFilter(watched, event) def _toggle_sorter(self, show): if show: self.show_sorter_btn.setText(trans._(">> Hide Sorter")) self.plugin_sorter.show() else: self.show_sorter_btn.setText(trans._("<< Show Sorter")) self.plugin_sorter.hide() def _toggle_status(self, show): if show: self.show_status_btn.setText(trans._("Hide Status")) self.stdout_text.show() else: self.show_status_btn.setText(trans._("Show Status")) self.stdout_text.hide() def _install_packages(self, packages: Sequence[str] = ()): if not packages: _packages = self.direct_entry_edit.text() if os.path.exists(_packages): packages = [_packages] else: packages = _packages.split() self.direct_entry_edit.clear() if packages: self.installer.install(packages)
class PyChopGui(QMainWindow): """ GUI Class using PyQT for PyChop to help users plan inelastic neutron experiments at spallation sources by calculating the resolution and flux at a given neutron energies. """ instruments = {} choppers = {} minE = {} maxE = {} def __init__(self): super(PyChopGui, self).__init__() self.folder = os.path.dirname(sys.modules[self.__module__].__file__) for fname in os.listdir(self.folder): if fname.endswith('.yaml'): instobj = Instrument(os.path.join(self.folder, fname)) self.instruments[instobj.name] = instobj self.choppers[instobj.name] = instobj.getChopperNames() self.minE[instobj.name] = max([instobj.emin, 0.01]) self.maxE[instobj.name] = instobj.emax self.drawLayout() self.setInstrument(list(self.instruments.keys())[0]) self.resaxes_xlim = 0 self.qeaxes_xlim = 0 self.isFramePlotted = 0 def setInstrument(self, instname): """ Defines the instrument parameters by the name of the instrument. """ self.engine = self.instruments[str(instname)] self.tabs.setTabEnabled(self.tdtabID, False) self.widgets['ChopperCombo']['Combo'].clear() self.widgets['FrequencyCombo']['Combo'].clear() self.widgets['FrequencyCombo']['Label'].setText('Frequency') self.widgets['PulseRemoverCombo']['Combo'].clear() for item in self.choppers[str(instname)]: self.widgets['ChopperCombo']['Combo'].addItem(item) rep = self.engine.moderator.source_rep maxfreq = self.engine.chopper_system.max_frequencies # At the moment, the GUI only supports up to two independent frequencies if not hasattr(maxfreq, '__len__') or len(maxfreq) == 1: self.widgets['PulseRemoverCombo']['Combo'].hide() self.widgets['PulseRemoverCombo']['Label'].hide() for fq in range(rep, (maxfreq[0] if hasattr(maxfreq, '__len__') else maxfreq) + 1, rep): self.widgets['FrequencyCombo']['Combo'].addItem(str(fq)) if hasattr(self.engine.chopper_system, 'frequency_names'): self.widgets['FrequencyCombo']['Label'].setText(self.engine.chopper_system.frequency_names[0]) else: self.widgets['PulseRemoverCombo']['Combo'].show() self.widgets['PulseRemoverCombo']['Label'].show() if hasattr(self.engine.chopper_system, 'frequency_names'): for idx, chp in enumerate([self.widgets['FrequencyCombo']['Label'], self.widgets['PulseRemoverCombo']['Label']]): chp.setText(self.engine.chopper_system.frequency_names[idx]) for fq in range(rep, maxfreq[0] + 1, rep): self.widgets['FrequencyCombo']['Combo'].addItem(str(fq)) for fq in range(rep, maxfreq[1] + 1, rep): self.widgets['PulseRemoverCombo']['Combo'].addItem(str(fq)) if len(self.engine.chopper_system.choppers) > 1: self.widgets['MultiRepCheck'].setEnabled(True) self.tabs.setTabEnabled(self.tdtabID, True) else: self.widgets['MultiRepCheck'].setEnabled(False) self.widgets['MultiRepCheck'].setChecked(False) self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() if self.engine.chopper_system.isPhaseIndependent: self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() self.widgets['Chopper2Phase']['Edit'].setText(str(self.engine.chopper_system.defaultPhase[0])) self.widgets['Chopper2Phase']['Label'].setText(self.engine.chopper_system.phaseNames[0]) # Special case for MERLIN - hide phase control from normal users if 'MERLIN' in str(instname) and not self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() self.engine.setChopper(str(self.widgets['ChopperCombo']['Combo'].currentText())) self.engine.setFrequency(float(self.widgets['FrequencyCombo']['Combo'].currentText())) val = self.flxslder.val * self.maxE[self.engine.instname] / 100 self.flxedt.setText('%3.2f' % (val)) nframe = self.engine.moderator.n_frame if hasattr(self.engine.moderator, 'n_frame') else 1 self.repfig_nframe_edit.setText(str(nframe)) self.repfig_nframe_rep1only.setChecked(False) if hasattr(self.engine.chopper_system, 'default_frequencies'): cb = [self.widgets['FrequencyCombo']['Combo'], self.widgets['PulseRemoverCombo']['Combo']] for idx, freq in enumerate(self.engine.chopper_system.default_frequencies): cb[idx].setCurrentIndex([i for i in range(cb[idx].count()) if str(freq) in cb[idx].itemText(i)][0]) if idx > 1: break self.tabs.setTabEnabled(self.qetabID, False) if self.engine.has_detector and hasattr(self.engine.detector, 'tthlims'): self.tabs.setTabEnabled(self.qetabID, True) def setChopper(self, choppername): """ Defines the Fermi chopper slit package type by name, or the disk chopper arrangement variant. """ self.engine.setChopper(str(choppername)) self.engine.setFrequency(float(self.widgets['FrequencyCombo']['Combo'].currentText())) # Special case for MERLIN - only enable multirep for 'G' chopper if 'MERLIN' in self.engine.instname: if 'G' in str(choppername): self.widgets['MultiRepCheck'].setEnabled(True) self.tabs.setTabEnabled(self.tdtabID, True) self.widgets['Chopper2Phase']['Edit'].setText('1500') self.widgets['Chopper2Phase']['Label'].setText('Disk chopper phase delay time') if self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() else: self.widgets['MultiRepCheck'].setEnabled(False) self.widgets['MultiRepCheck'].setChecked(False) self.tabs.setTabEnabled(self.tdtabID, False) self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() def setFreq(self, freqtext=None, **kwargs): """ Sets the chopper frequency(ies), in Hz. """ freq_gui = float(self.widgets['FrequencyCombo']['Combo'].currentText()) freq_in = kwargs['manual_freq'] if ('manual_freq' in kwargs.keys()) else freq_gui if len(self.engine.getFrequency()) > 1 and (not hasattr(freq_in, '__len__') or len(freq_in)==1): freqpr = float(self.widgets['PulseRemoverCombo']['Combo'].currentText()) freq_in = [freq_in, freqpr] if not self.widgets['Chopper2Phase']['Label'].isHidden(): chop2phase = self.widgets['Chopper2Phase']['Edit'].text() if isinstance(self.engine.chopper_system.defaultPhase[0], string_types): chop2phase = str(chop2phase) else: chop2phase = float(chop2phase) % (1e6 / self.engine.moderator.source_rep) self.engine.setFrequency(freq_in, phase=chop2phase) else: self.engine.setFrequency(freq_in) def setEi(self): """ Sets the incident energy (or focused incident energy for multi-rep case). """ try: eitxt = float(self.widgets['EiEdit']['Edit'].text()) self.engine.setEi(eitxt) if self.eiPlots.isChecked(): self.calc_callback() except ValueError: raise ValueError('No Ei specified, or Ei string not understood') def calc_callback(self): """ Calls routines to calculate the resolution / flux and to update the Matplotlib graphs. """ try: if self.engine.getChopper() is None: self.setChopper(self.widgets['ChopperCombo']['Combo'].currentText()) self.setEi() self.setFreq() self.calculate() if self.errormess: idx = [i for i, ei in enumerate(self.eis) if np.abs(ei - self.engine.getEi()) < 1.e-4] if idx and self.flux[idx[0]] == 0: raise ValueError(self.errormess) self.errormessage(self.errormess) self.plot_res() self.plot_frame() if self.instSciAct.isChecked(): self.update_script() except ValueError as err: self.errormessage(err) self.plot_flux_ei() self.plot_flux_hz() def calculate(self): """ Performs the resolution and flux calculations. """ self.errormess = None if self.engine.getEi() is None: self.setEi() if self.widgets['MultiRepCheck'].isChecked(): en = np.linspace(0, 0.95, 200) self.eis = self.engine.getAllowedEi() with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) self.res = self.engine.getMultiRepResolution(en) self.flux = self.engine.getMultiRepFlux() if len(w) > 0: mess = [str(w[i].message) for i in range(len(w))] self.errormess = '\n'.join([m for m in mess if 'tchop' in m]) else: en = np.linspace(0, 0.95*self.engine.getEi(), 200) with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always', UserWarning) self.res = self.engine.getResolution(en) self.flux = self.engine.getFlux() if len(w) > 0: raise ValueError(w[0].message) def _set_overplot(self, overplot, axisname): axis = getattr(self, axisname) if overplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): axis.hold(True) else: setattr(self, axisname+'_xlim', 0) axis.clear() axis.axhline(color='k') def plot_res(self): """ Plots the resolution in the resolution tab """ overplot = self.widgets['HoldCheck'].isChecked() multiplot = self.widgets['MultiRepCheck'].isChecked() self._set_overplot(overplot, 'resaxes') self._set_overplot(overplot, 'qeaxes') inst = self.engine.instname freq = self.engine.getFrequency() if hasattr(freq, '__len__'): freq = freq[0] if multiplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.resaxes.hold(True) for ie, Ei in enumerate(self.eis): en = np.linspace(0, 0.95*Ei, 200) if any(self.res[ie]): if not self.flux[ie]: continue line, = self.resaxes.plot(en, self.res[ie]) label_text = '%s_%3.2fmeV_%dHz_Flux=%fn/cm2/s' % (inst, Ei, freq, self.flux[ie]) line.set_label(label_text) if self.tabs.isTabEnabled(self.qetabID): self.plot_qe(Ei, label_text, hold=True) self.resaxes_xlim = max(Ei, self.resaxes_xlim) if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.resaxes.hold(False) else: ei = self.engine.getEi() en = np.linspace(0, 0.95*ei, 200) line, = self.resaxes.plot(en, self.res) chopper = self.engine.getChopper() label_text = '%s_%s_%3.2fmeV_%dHz_Flux=%fn/cm2/s' % (inst, chopper, ei, freq, self.flux) line.set_label(label_text) if self.tabs.isTabEnabled(self.qetabID): self.plot_qe(ei, label_text, overplot) self.resaxes_xlim = max(ei, self.resaxes_xlim) self.resaxes.set_xlim([0, self.resaxes_xlim]) self.resaxes.legend().draggable() self.resaxes.set_xlabel('Energy Transfer (meV)') self.resaxes.set_ylabel(r'$\Delta$E (meV FWHM)') self.rescanvas.draw() def plot_qe(self, Ei, label_text, hold=False): """ Plots the Q-E diagram """ from scipy import constants E2q, meV2J = (2. * constants.m_n / (constants.hbar ** 2), constants.e / 1000.) en = np.linspace(-Ei / 5., Ei, 100) q2 = [] for tth in self.engine.detector.tthlims: q = np.sqrt(E2q * (2 * Ei - en - 2 * np.sqrt(Ei * (Ei - en)) * np.cos(np.deg2rad(tth))) * meV2J) / 1e10 q2.append(np.concatenate((np.flipud(q), q))) self._set_overplot(hold, 'qeaxes') self.qeaxes_xlim = max(np.max(q2), self.qeaxes_xlim) line, = self.qeaxes.plot(np.hstack(q2), np.concatenate((np.flipud(en), en)).tolist() * len(self.engine.detector.tthlims)) line.set_label(label_text) self.qeaxes.set_xlim([0, self.qeaxes_xlim]) self.qeaxes.legend().draggable() self.qeaxes.set_xlabel(r'$|Q| (\mathrm{\AA}^{-1})$') self.qeaxes.set_ylabel('Energy Transfer (meV)') self.qecanvas.draw() def plot_flux_ei(self, **kwargs): """ Plots the flux vs Ei in the middle tab """ inst = self.engine.instname chop = self.engine.getChopper() freq = self.engine.getFrequency() overplot = self.widgets['HoldCheck'].isChecked() if hasattr(freq, '__len__'): freq = freq[0] update = kwargs['update'] if 'update' in kwargs.keys() else False # Do not recalculate if all relevant parameters still the same. _, labels = self.flxaxes2.get_legend_handles_labels() searchStr = '([A-Z]+) "(.+)" ([0-9]+) Hz' tmpinst = [] if (labels and (overplot or len(labels) == 1)) or update: for prevtitle in labels: prevInst, prevChop, prevFreq = re.search(searchStr, prevtitle).groups() if update: tmpinst.append(copy.deepcopy(Instrument(self.instruments[prevInst], prevChop, float(prevFreq)))) else: if inst == prevInst and chop == prevChop and freq == float(prevFreq): return ne = 25 mn = self.minE[inst] mx = (self.flxslder.val/100)*self.maxE[inst] eis = np.linspace(mn, mx, ne) flux = eis*0 elres = eis*0 if update: self.flxaxes1.clear() self.flxaxes2.clear() if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.flxaxes1.hold(True) self.flxaxes2.hold(True) for ii, instrument in enumerate(tmpinst): for ie, ei in enumerate(eis): with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = instrument.getFlux(ei) elres[ie] = instrument.getResolution(0., ei)[0] self.flxaxes1.plot(eis, flux) line, = self.flxaxes2.plot(eis, elres) line.set_label(labels[ii]) else: for ie, ei in enumerate(eis): with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.flxaxes1.hold(True) self.flxaxes2.hold(True) else: self.flxaxes1.clear() self.flxaxes2.clear() self.flxaxes1.plot(eis, flux) line, = self.flxaxes2.plot(eis, elres) line.set_label('%s "%s" %d Hz' % (inst, chop, freq)) self.flxaxes1.set_xlim([mn, mx]) self.flxaxes2.set_xlim([mn, mx]) self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes2.set_ylabel('Elastic Resolution FWHM (meV)') lg = self.flxaxes2.legend() lg.draggable() self.flxcanvas.draw() def update_slider(self, val=None): """ Callback function for the x-axis slider of the flux tab """ if val is None: val = float(self.flxedt.text()) / self.maxE[self.engine.instname] * 100 if val < self.minE[self.engine.instname]: self.errormessage("Max Ei must be greater than %2.1f" % (self.minE[self.engine.instname])) val = (self.minE[self.engine.instname]+0.1) / self.maxE[self.engine.instname] * 100 self.flxslder.set_val(val) else: val = self.flxslder.val * self.maxE[self.engine.instname] / 100 self.flxedt.setText('%3.2f' % (val)) self.plot_flux_ei(update=True) self.flxcanvas.draw() def plot_flux_hz(self): """ Plots the flux vs freq in the middle tab """ inst = self.engine.instname chop = self.engine.getChopper() ei = float(self.widgets['EiEdit']['Edit'].text()) overplot = self.widgets['HoldCheck'].isChecked() # Do not recalculate if one of the plots has the same parametersc _, labels = self.frqaxes2.get_legend_handles_labels() searchStr = '([A-Z]+) "(.+)" Ei = ([0-9.-]+) meV' if labels and (overplot or len(labels) == 1): for prevtitle in labels: prevInst, prevChop, prevEi = re.search(searchStr, prevtitle).groups() if inst == prevInst and chop == prevChop and abs(ei-float(prevEi)) < 0.01: return freq0 = self.engine.getFrequency() rep = self.engine.moderator.source_rep maxfreq = self.engine.chopper_system.max_frequencies freqs = range(rep, (maxfreq[0] if hasattr(maxfreq, '__len__') else maxfreq) + 1, rep) flux = np.zeros(len(freqs)) elres = np.zeros(len(freqs)) for ie, freq in enumerate(freqs): if hasattr(freq0, '__len__'): self.setFreq(manual_freq=[freq] + freq0[1:]) else: self.setFreq(manual_freq=freq) with warnings.catch_warnings(record=True): warnings.simplefilter('always', UserWarning) flux[ie] = self.engine.getFlux(ei) elres[ie] = self.engine.getResolution(0., ei)[0] if overplot: if matplotlib.compare_versions('2.1.0',matplotlib.__version__): self.frqaxes1.hold(True) self.frqaxes2.hold(True) else: self.frqaxes1.clear() self.frqaxes2.clear() self.setFreq(manual_freq=freq0) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes1.set_ylabel('Flux (n/cm$^2$/s)') line, = self.frqaxes1.plot(freqs, flux, 'o-') self.frqaxes1.set_xlim([0, np.max(freqs)]) self.frqaxes2.set_xlabel('Chopper Frequency (Hz)') self.frqaxes2.set_ylabel('Elastic Resolution FWHM (meV)') line, = self.frqaxes2.plot(freqs, elres, 'o-') line.set_label('%s "%s" Ei = %5.3f meV' % (inst, chop, ei)) lg = self.frqaxes2.legend() lg.draggable() self.frqaxes2.set_xlim([0, np.max(freqs)]) self.frqcanvas.draw() def instSciCB(self): """ Callback function for the "Instrument Scientist Mode" menu option """ # MERLIN is a special case - want to hide ability to change phase from users if 'MERLIN' in self.engine.instname and 'G' in self.engine.getChopper(): if self.instSciAct.isChecked(): self.widgets['Chopper2Phase']['Edit'].show() self.widgets['Chopper2Phase']['Label'].show() self.widgets['Chopper2Phase']['Edit'].setText('1500') self.widgets['Chopper2Phase']['Label'].setText('Disk chopper phase delay time') else: self.widgets['Chopper2Phase']['Edit'].hide() self.widgets['Chopper2Phase']['Label'].hide() if self.instSciAct.isChecked(): self.tabs.insertTab(self.scrtabID, self.scrtab, 'ScriptOutput') self.scrtab.show() else: self.tabs.removeTab(self.scrtabID) self.scrtab.hide() def errormessage(self, message): msg = QMessageBox() msg.setText(str(message)) msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def loadYaml(self): yaml_file = QFileDialog().getOpenFileName(self.mainWidget, 'Open Instrument YAML File', self.folder, 'Files (*.yaml)') if isinstance(yaml_file, tuple): yaml_file = yaml_file[0] yaml_file = str(yaml_file) new_folder = os.path.dirname(yaml_file) if new_folder != self.folder: self.folder = new_folder try: new_inst = Instrument(yaml_file) except (RuntimeError, AttributeError, ValueError) as err: self.errormessage(err) newname = new_inst.name if newname in self.instruments.keys() and not self.overwriteload.isChecked(): overwrite, newname = self._ask_overwrite() if overwrite == 1: return elif overwrite == 0: newname = new_inst.name self.instruments[newname] = new_inst self.choppers[newname] = new_inst.getChopperNames() self.minE[newname] = max([new_inst.emin, 0.01]) self.maxE[newname] = new_inst.emax self.updateInstrumentList() combo = self.widgets['InstrumentCombo']['Combo'] idx = [i for i in range(combo.count()) if str(combo.itemText(i)) == newname] combo.setCurrentIndex(idx[0]) self.setInstrument(newname) def _ask_overwrite(self): msg = QDialog() msg.setWindowTitle('Load overwrite') layout = QGridLayout() layout.addWidget(QLabel('Instrument %s already exists in memory. Overwrite this?'), 0, 0, 1, -1) buttons = [QPushButton(label) for label in ['Load and overwrite', 'Cancel Load', 'Load and rename to']] locations = [[1, 0], [1, 1], [2, 0]] self.overwrite_flag = 1 def overwriteCB(idx): self.overwrite_flag = idx msg.accept() for idx, button in enumerate(buttons): button.clicked.connect(lambda _, idx=idx: overwriteCB(idx)) layout.addWidget(button, locations[idx][0], locations[idx][1]) newname = QLineEdit() newname.editingFinished.connect(lambda: overwriteCB(2)) layout.addWidget(newname, 2, 1) msg.setLayout(layout) msg.exec_() newname = str(newname.text()) if not newname or newname in self.instruments: self.errormessage('Invalid instrument name. Cancelling load.') self.overwrite_flag = 1 return self.overwrite_flag, newname def updateInstrumentList(self): combo = self.widgets['InstrumentCombo']['Combo'] old_instruments = [str(combo.itemText(i)) for i in range(combo.count())] new_instruments = [inst for inst in self.instruments if inst not in old_instruments] for inst in new_instruments: combo.addItem(inst) def plot_frame(self): """ Plots the distance-time diagram in the right tab """ if len(self.engine.chopper_system.choppers) > 1: self.engine.n_frame = int(self.repfig_nframe_edit.text()) self.repaxes.clear() self.engine.plotMultiRepFrame(self.repaxes, first_rep=self.repfig_nframe_rep1only.isChecked()) self.repcanvas.draw() def _gen_text_ei(self, ei, obj_in): obj = Instrument(obj_in) obj.setEi(ei) en = np.linspace(0, 0.95*ei, 10) try: flux = self.engine.getFlux() res = self.engine.getResolution(en) except ValueError as err: self.errormessage(err) raise ValueError(err) tsqvan, tsqdic, tsqmodchop = obj.getVanVar() v_mod, v_chop = tuple(np.sqrt(tsqmodchop[:2]) * 1e6) x0, _, x1, x2, _ = obj.chopper_system.getDistances() first_component = 'moderator' if x0 != tsqmodchop[2]: x0 = tsqmodchop[2] first_component = 'chopper 1' txt = '# ------------------------------------------------------------- #\n' txt += '# Ei = %8.2f meV\n' % (ei) txt += '# Flux = %8.2f n/cm2/s\n' % (flux) txt += '# Elastic resolution = %6.2f meV\n' % (res[0]) txt += '# Time width at sample = %6.2f us, of which:\n' % (1e6*np.sqrt(tsqvan)) for ky, val in list(tsqdic.items()): txt += '# %20s : %6.2f us\n' % (ky, 1e6*np.sqrt(val)) txt += '# %s distances:\n' % (obj.instname) txt += '# x0 = %6.2f m (%s to Fermi)\n' % (x0, first_component) txt += '# x1 = %6.2f m (Fermi to sample)\n' % (x1) txt += '# x2 = %6.2f m (sample to detector)\n' % (x2) txt += '# Approximate inelastic resolution is given by:\n' txt += '# dE = 2 * E2V * sqrt(ef**3 * t_van**2) / x2\n' txt += '# where: E2V = 4.373e-4 meV/(m/us) conversion from energy to speed\n' txt += '# t_van**2 = (geom*t_mod)**2 + ((1+geom)*t_chop)**2\n' txt += '# geom = (x1 + x2*(ei/ef)**1.5) / x0\n' txt += '# and t_mod and t_chop are the moderator and chopper time widths at the\n' txt += '# moderator and chopper positions (not at the sample as listed above).\n' txt += '# Which in this case is:\n' txt += '# %.4e*sqrt(ef**3 * ( (%6.5f*(%.3f+%.3f*(ei/ef)**1.5))**2 \n' % (874.78672e-6/x2, v_mod, x1/x0, x2/x0) txt += '# + (%6.5f*(%.3f+%.3f*(ei/ef)**1.5))**2) )\n' % (v_chop, 1+x1/x0, x2/x0) txt += '# EN (meV) Full dE (meV) Approx dE (meV)\n' for ii in range(len(res)): ef = ei-en[ii] approx = (874.78672e-6/x2)*np.sqrt(ef**3 * ((v_mod*((x1/x0)+(x2/x0)*(ei/ef)**1.5))**2 + (v_chop*(1+(x1/x0)+(x2/x0)*(ei/ef)**1.5))**2)) txt += '%12.5f %12.5f %12.5f\n' % (en[ii], res[ii], approx) return txt def genText(self): """ Generates text output of the resolution function versus energy transfer and other information. """ multiplot = self.widgets['MultiRepCheck'].isChecked() obj = self.engine if obj.getChopper() is None: self.setChopper(self.widgets['ChopperCombo']['Combo'].currentText()) if obj.getEi() is None: self.setEi() instname, chtyp, freqs, ei_in = tuple([obj.instname, obj.getChopper(), obj.getFrequency(), obj.getEi()]) txt = '# ------------------------------------------------------------- #\n' txt += '# Chop calculation for instrument %s\n' % (instname) if obj.isFermi: txt += '# with chopper %s at %3i Hz\n' % (chtyp, freqs[0]) else: txt += '# in %s mode with:\n' % (chtyp) freq_names = obj.chopper_system.frequency_names for idx in range(len(freq_names)): txt += '# %s at %3i Hz\n' % (freq_names[idx], freqs[idx]) txt += self._gen_text_ei(ei_in, obj) if multiplot: for ei in sorted(self.engine.getAllowedEi()): if np.abs(ei - ei_in) > 0.001: txt += self._gen_text_ei(ei, obj) return txt def showText(self): """ Creates a dialog to show the generated text output. """ try: generatedText = self.genText() except ValueError: return self.txtwin = QDialog() self.txtedt = QTextEdit() self.txtbtn = QPushButton('OK') self.txtwin.layout = QVBoxLayout(self.txtwin) self.txtwin.layout.addWidget(self.txtedt) self.txtwin.layout.addWidget(self.txtbtn) self.txtbtn.clicked.connect(self.txtwin.deleteLater) self.txtedt.setText(generatedText) self.txtedt.setReadOnly(True) self.txtwin.setWindowTitle('Resolution information') self.txtwin.setWindowModality(Qt.ApplicationModal) self.txtwin.setAttribute(Qt.WA_DeleteOnClose) self.txtwin.setMinimumSize(400, 600) self.txtwin.resize(400, 600) self.txtwin.show() self.txtloop = QEventLoop() self.txtloop.exec_() def saveText(self): """ Saves the generated text to a file (opens file dialog). """ fname = QFileDialog.getSaveFileName(self, 'Open file', '') if isinstance(fname, tuple): fname = fname[0] fid = open(fname, 'w') fid.write(self.genText()) fid.close() def update_script(self): """ Updates the text window with information about the previous calculation. """ if self.widgets['MultiRepCheck'].isChecked(): out = self.engine.getMultiWidths() new_str = '\n' for ie, ee in enumerate(out['Eis']): res = out['Energy'][ie] percent = res / ee * 100 chop_width = out['chopper'][ie] mod_width = out['moderator'][ie] new_str += 'Ei is %6.2f meV, resolution is %6.2f ueV, percentage resolution is %6.3f\n' % (ee, res * 1000, percent) new_str += 'FWHM at sample from chopper and moderator are %6.2f us, %6.2f us\n' % (chop_width, mod_width) else: ei = self.engine.getEi() out = self.engine.getWidths() res = out['Energy'] percent = res / ei * 100 chop_width = out['chopper'] mod_width = out['moderator'] new_str = '\nEi is %6.2f meV, resolution is %6.2f ueV, percentage resolution is %6.3f\n' % (ei, res * 1000, percent) new_str += 'FWHM at sample from chopper and moderator are %6.2f us, %6.2f us\n' % (chop_width, mod_width) self.scredt.append(new_str) def onHelp(self): """ Shows the help page """ try: from pymantidplot.proxies import showCustomInterfaceHelp showCustomInterfaceHelp("PyChop") except ImportError: helpTxt = "PyChop is a tool to allow direct inelastic neutron\nscattering users to estimate the inelastic resolution\n" helpTxt += "and incident flux for a given spectrometer setting.\n\nFirst select the instrument, chopper settings and\n" helpTxt += "Ei, and then click 'Calculate and Plot'. Data for all\nthe graphs will be generated (may take 1-2s) and\n" helpTxt += "all graphs will be updated. If the 'Hold current plot'\ncheck box is ticked, additional settings will be\n" helpTxt += "overplotted on the existing graphs if they are\ndifferent from previous settings.\n\nMore in-depth help " helpTxt += "can be obtained from the\nMantid help pages." self.hlpwin = QDialog() self.hlpedt = QLabel(helpTxt) self.hlpbtn = QPushButton('OK') self.hlpwin.layout = QVBoxLayout(self.hlpwin) self.hlpwin.layout.addWidget(self.hlpedt) self.hlpwin.layout.addWidget(self.hlpbtn) self.hlpbtn.clicked.connect(self.hlpwin.deleteLater) self.hlpwin.setWindowTitle('Help') self.hlpwin.setWindowModality(Qt.ApplicationModal) self.hlpwin.setAttribute(Qt.WA_DeleteOnClose) self.hlpwin.setMinimumSize(370, 300) self.hlpwin.resize(370, 300) self.hlpwin.show() self.hlploop = QEventLoop() self.hlploop.exec_() def drawLayout(self): """ Draws the GUI layout. """ self.widgetslist = [ ['pair', 'show', 'Instrument', 'combo', self.instruments, self.setInstrument, 'InstrumentCombo'], ['pair', 'show', 'Chopper', 'combo', '', self.setChopper, 'ChopperCombo'], ['pair', 'show', 'Frequency', 'combo', '', self.setFreq, 'FrequencyCombo'], ['pair', 'hide', 'Pulse remover chopper freq', 'combo', '', self.setFreq, 'PulseRemoverCombo'], ['pair', 'show', 'Ei', 'edit', '', self.setEi, 'EiEdit'], ['pair', 'hide', 'Chopper 2 phase delay time', 'edit', '5', self.setFreq, 'Chopper2Phase'], ['spacer'], ['single', 'show', 'Calculate and Plot', 'button', self.calc_callback, 'CalculateButton'], ['single', 'show', 'Hold current plot', 'check', lambda: None, 'HoldCheck'], ['single', 'show', 'Show multi-reps', 'check', lambda: None, 'MultiRepCheck'], ['spacer'], ['single', 'show', 'Show data ascii window', 'button', self.showText, 'ShowAsciiButton'], ['single', 'show', 'Save data as ascii', 'button', self.saveText, 'SaveAsciiButton'] ] self.droplabels = [] self.dropboxes = [] self.singles = [] self.widgets = {} self.leftPanel = QVBoxLayout() self.rightPanel = QVBoxLayout() self.tabs = QTabWidget(self) self.fullWindow = QGridLayout() for widget in self.widgetslist: if 'pair' in widget[0]: self.droplabels.append(QLabel(widget[2])) if 'combo' in widget[3]: self.dropboxes.append(QComboBox(self)) self.dropboxes[-1].activated['QString'].connect(widget[5]) for item in widget[4]: self.dropboxes[-1].addItem(item) self.widgets[widget[-1]] = {'Combo':self.dropboxes[-1], 'Label':self.droplabels[-1]} elif 'edit' in widget[3]: self.dropboxes.append(QLineEdit(self)) self.dropboxes[-1].returnPressed.connect(widget[5]) self.widgets[widget[-1]] = {'Edit':self.dropboxes[-1], 'Label':self.droplabels[-1]} else: raise RuntimeError('Bug in code - widget %s is not recognised.' % (widget[3])) self.leftPanel.addWidget(self.droplabels[-1]) self.leftPanel.addWidget(self.dropboxes[-1]) if 'hide' in widget[1]: self.droplabels[-1].hide() self.dropboxes[-1].hide() elif 'single' in widget[0]: if 'check' in widget[3]: self.singles.append(QCheckBox(widget[2], self)) self.singles[-1].stateChanged.connect(widget[4]) elif 'button' in widget[3]: self.singles.append(QPushButton(widget[2])) self.singles[-1].clicked.connect(widget[4]) else: raise RuntimeError('Bug in code - widget %s is not recognised.' % (widget[3])) self.leftPanel.addWidget(self.singles[-1]) if 'hide' in widget[1]: self.singles[-1].hide() self.widgets[widget[-1]] = self.singles[-1] elif 'spacer' in widget[0]: self.leftPanel.addItem(QSpacerItem(0, 35)) else: raise RuntimeError('Bug in code - widget class %s is not recognised.' % (widget[0])) # Right panel, matplotlib figures self.resfig = Figure() self.resfig.patch.set_facecolor('white') self.rescanvas = FigureCanvas(self.resfig) self.resaxes = self.resfig.add_subplot(111) self.resaxes.axhline(color='k') self.resaxes.set_xlabel('Energy Transfer (meV)') self.resaxes.set_ylabel(r'$\Delta$E (meV FWHM)') self.resfig_controls = NavigationToolbar(self.rescanvas, self) self.restab = QWidget(self.tabs) self.restabbox = QVBoxLayout() self.restabbox.addWidget(self.rescanvas) self.restabbox.addWidget(self.resfig_controls) self.restab.setLayout(self.restabbox) self.flxfig = Figure() self.flxfig.patch.set_facecolor('white') self.flxcanvas = FigureCanvas(self.flxfig) self.flxaxes1 = self.flxfig.add_subplot(121) self.flxaxes1.set_xlabel('Incident Energy (meV)') self.flxaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.flxaxes2 = self.flxfig.add_subplot(122) self.flxaxes2.set_xlabel('Incident Energy (meV)') self.flxaxes2.set_ylabel('Elastic Resolution FWHM (meV)') self.flxfig_controls = NavigationToolbar(self.flxcanvas, self) self.flxsldfg = Figure() self.flxsldfg.patch.set_facecolor('white') self.flxsldcv = FigureCanvas(self.flxsldfg) self.flxsldax = self.flxsldfg.add_subplot(111) self.flxslder = Slider(self.flxsldax, 'Ei (meV)', 0, 100, valinit=100) self.flxslder.valtext.set_visible(False) self.flxslder.on_changed(self.update_slider) self.flxedt = QLineEdit() self.flxedt.setText('1000') self.flxedt.returnPressed.connect(self.update_slider) self.flxtab = QWidget(self.tabs) self.flxsldbox = QHBoxLayout() self.flxsldbox.addWidget(self.flxsldcv) self.flxsldbox.addWidget(self.flxedt) self.flxsldwdg = QWidget() self.flxsldwdg.setLayout(self.flxsldbox) sz = self.flxsldwdg.maximumSize() sz.setHeight(50) self.flxsldwdg.setMaximumSize(sz) self.flxtabbox = QVBoxLayout() self.flxtabbox.addWidget(self.flxcanvas) self.flxtabbox.addWidget(self.flxsldwdg) self.flxtabbox.addWidget(self.flxfig_controls) self.flxtab.setLayout(self.flxtabbox) self.frqfig = Figure() self.frqfig.patch.set_facecolor('white') self.frqcanvas = FigureCanvas(self.frqfig) self.frqaxes1 = self.frqfig.add_subplot(121) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes1.set_ylabel('Flux (n/cm$^2$/s)') self.frqaxes2 = self.frqfig.add_subplot(122) self.frqaxes1.set_xlabel('Chopper Frequency (Hz)') self.frqaxes2.set_ylabel('Elastic Resolution FWHM (meV)') self.frqfig_controls = NavigationToolbar(self.frqcanvas, self) self.frqtab = QWidget(self.tabs) self.frqtabbox = QVBoxLayout() self.frqtabbox.addWidget(self.frqcanvas) self.frqtabbox.addWidget(self.frqfig_controls) self.frqtab.setLayout(self.frqtabbox) self.repfig = Figure() self.repfig.patch.set_facecolor('white') self.repcanvas = FigureCanvas(self.repfig) self.repaxes = self.repfig.add_subplot(111) self.repaxes.axhline(color='k') self.repaxes.set_xlabel(r'TOF ($\mu$sec)') self.repaxes.set_ylabel('Distance (m)') self.repfig_controls = NavigationToolbar(self.repcanvas, self) self.repfig_nframe_label = QLabel('Number of frames to plot') self.repfig_nframe_edit = QLineEdit('1') self.repfig_nframe_button = QPushButton('Replot') self.repfig_nframe_button.clicked.connect(lambda: self.plot_frame()) self.repfig_nframe_rep1only = QCheckBox('First Rep Only') self.repfig_nframe_box = QHBoxLayout() self.repfig_nframe_box.addWidget(self.repfig_nframe_label) self.repfig_nframe_box.addWidget(self.repfig_nframe_edit) self.repfig_nframe_box.addWidget(self.repfig_nframe_button) self.repfig_nframe_box.addWidget(self.repfig_nframe_rep1only) self.reptab = QWidget(self.tabs) self.repfig_nframe = QWidget(self.reptab) self.repfig_nframe.setLayout(self.repfig_nframe_box) self.repfig_nframe.setSizePolicy(QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)) self.reptabbox = QVBoxLayout() self.reptabbox.addWidget(self.repcanvas) self.reptabbox.addWidget(self.repfig_nframe) self.reptabbox.addWidget(self.repfig_controls) self.reptab.setLayout(self.reptabbox) self.qefig = Figure() self.qefig.patch.set_facecolor('white') self.qecanvas = FigureCanvas(self.qefig) self.qeaxes = self.qefig.add_subplot(111) self.qeaxes.axhline(color='k') self.qeaxes.set_xlabel(r'$|Q| (\mathrm{\AA}^{-1})$') self.qeaxes.set_ylabel('Energy Transfer (meV)') self.qefig_controls = NavigationToolbar(self.qecanvas, self) self.qetabbox = QVBoxLayout() self.qetabbox.addWidget(self.qecanvas) self.qetabbox.addWidget(self.qefig_controls) self.qetab = QWidget(self.tabs) self.qetab.setLayout(self.qetabbox) self.scrtab = QWidget(self.tabs) self.scredt = QTextEdit() self.scrcls = QPushButton("Clear") self.scrcls.clicked.connect(lambda: self.scredt.clear()) self.scrbox = QVBoxLayout() self.scrbox.addWidget(self.scredt) self.scrbox.addWidget(self.scrcls) self.scrtab.setLayout(self.scrbox) self.scrtab.hide() self.tabs.addTab(self.restab, 'Resolution') self.tabs.addTab(self.flxtab, 'Flux-Ei') self.tabs.addTab(self.frqtab, 'Flux-Freq') self.tabs.addTab(self.reptab, 'Time-Distance') self.tdtabID = 3 self.tabs.setTabEnabled(self.tdtabID, False) self.tabs.addTab(self.qetab, 'Q-E') self.qetabID = 4 self.tabs.setTabEnabled(self.qetabID, False) self.scrtabID = 5 self.rightPanel.addWidget(self.tabs) self.menuLoad = QMenu('Load') self.loadAct = QAction('Load YAML', self.menuLoad) self.loadAct.triggered.connect(self.loadYaml) self.menuLoad.addAction(self.loadAct) self.menuOptions = QMenu('Options') self.instSciAct = QAction('Instrument Scientist Mode', self.menuOptions, checkable=True) self.instSciAct.triggered.connect(self.instSciCB) self.menuOptions.addAction(self.instSciAct) self.eiPlots = QAction('Press Enter in Ei box updates plots', self.menuOptions, checkable=True) self.menuOptions.addAction(self.eiPlots) self.overwriteload = QAction('Always overwrite instruments in memory', self.menuOptions, checkable=True) self.menuOptions.addAction(self.overwriteload) self.menuBar().addMenu(self.menuLoad) self.menuBar().addMenu(self.menuOptions) self.leftPanelWidget = QWidget() self.leftPanelWidget.setLayout(self.leftPanel) self.leftPanelWidget.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)) self.fullWindow.addWidget(self.leftPanelWidget, 0, 0) self.fullWindow.addLayout(self.rightPanel, 0, 1) self.helpbtn = QPushButton("?", self) self.helpbtn.setMaximumWidth(30) self.helpbtn.clicked.connect(self.onHelp) self.fullWindow.addWidget(self.helpbtn, 1, 0, 1, -1) self.mainWidget = QWidget() self.mainWidget.setLayout(self.fullWindow) self.setCentralWidget(self.mainWidget) self.setWindowTitle('PyChopGUI') self.show()
class PackagesDialog(DialogBase): """Package dependencies dialog.""" sig_setup_ready = Signal() def __init__( self, parent=None, packages=None, pip_packages=None, remove_only=False, update_only=False, ): """About dialog.""" super(PackagesDialog, self).__init__(parent=parent) # Variables self.api = AnacondaAPI() self.actions = None self.packages = packages or [] self.pip_packages = pip_packages or [] # Widgets self.stack = QStackedWidget() self.table = QTableWidget() self.text = QTextEdit() self.label_description = LabelBase() self.label_status = LabelBase() self.progress_bar = QProgressBar() self.button_ok = ButtonPrimary('Apply') self.button_cancel = ButtonNormal('Cancel') # Widget setup self.text.setReadOnly(True) self.stack.addWidget(self.table) self.stack.addWidget(self.text) if remove_only: text = 'The following packages will be removed:<br>' else: text = 'The following packages will be modified:<br>' self.label_description.setText(text) self.label_description.setWordWrap(True) self.label_description.setWordWrap(True) self.label_status.setWordWrap(True) self.table.horizontalScrollBar().setVisible(False) self.table.setSelectionBehavior(QAbstractItemView.SelectRows) self.table.setAlternatingRowColors(True) self.table.setSelectionMode(QAbstractItemView.NoSelection) self.table.setSortingEnabled(True) self._hheader = self.table.horizontalHeader() self._vheader = self.table.verticalHeader() self._hheader.setStretchLastSection(True) self._hheader.setDefaultAlignment(Qt.AlignLeft) self._hheader.setSectionResizeMode(self._hheader.Fixed) self._vheader.setSectionResizeMode(self._vheader.Fixed) self.button_ok.setMinimumWidth(70) self.button_ok.setDefault(True) self.base_minimum_width = 300 if remove_only else 420 if remove_only: self.setWindowTitle("Remove Packages") elif update_only: self.setWindowTitle("Update Packages") else: self.setWindowTitle("Install Packages") self.setMinimumWidth(self.base_minimum_width) # Layouts layout_progress = QHBoxLayout() layout_progress.addWidget(self.label_status) layout_progress.addWidget(SpacerHorizontal()) layout_progress.addWidget(self.progress_bar) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) layout = QVBoxLayout() layout.addWidget(self.label_description) layout.addWidget(SpacerVertical()) layout.addWidget(self.stack) layout.addWidget(SpacerVertical()) layout.addLayout(layout_progress) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout.addLayout(layout_buttons) self.setLayout(layout) # Signals self.button_ok.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_ok.setDisabled(True) # Setup self.table.setDisabled(True) self.update_status('Solving package specifications', value=0, max_value=0) def setup(self, worker, output, error): """Setup the widget to include the list of dependencies.""" if not isinstance(output, dict): output = {} packages = sorted(pkg.split('==')[0] for pkg in self.packages) success = output.get('success') error = output.get('error', '') exception_name = output.get('exception_name', '') actions = output.get('actions', []) prefix = worker.prefix if exception_name: message = exception_name else: # All requested packages already installed message = output.get('message', ' ') navi_deps_error = self.api.check_navigator_dependencies( actions, prefix) description = self.label_description.text() if error: description = 'No packages will be modified.' self.stack.setCurrentIndex(1) self.button_ok.setDisabled(True) if self.api.is_offline(): error = ("Some of the functionality of Anaconda Navigator " "will be limited in <b>offline mode</b>. <br><br>" "Installation and upgrade actions will be subject to " "the packages currently available on your package " "cache.") self.text.setText(error) elif navi_deps_error: description = 'No packages will be modified.' error = ('Downgrading/removing these packages will modify ' 'Anaconda Navigator dependencies.') self.text.setText(error) self.stack.setCurrentIndex(1) message = 'NavigatorDependenciesError' self.button_ok.setDisabled(True) elif success and actions: self.stack.setCurrentIndex(0) # Conda 4.3.x if isinstance(actions, list): actions_link = actions[0].get('LINK', []) actions_unlink = actions[0].get('UNLINK', []) # Conda 4.4.x else: actions_link = actions.get('LINK', []) actions_unlink = actions.get('UNLINK', []) deps = set() deps = deps.union({p['name'] for p in actions_link}) deps = deps.union({p['name'] for p in actions_unlink}) deps = deps - set(packages) deps = sorted(list(deps)) count_total_packages = len(packages) + len(deps) plural_total = 's' if count_total_packages != 1 else '' plural_selected = 's' if len(packages) != 1 else '' self.table.setRowCount(count_total_packages) self.table.setColumnCount(4) if actions_link: description = '{0} package{1} will be installed'.format( count_total_packages, plural_total) self.table.showColumn(2) self.table.showColumn(3) elif actions_unlink and not actions_link: self.table.hideColumn(2) self.table.hideColumn(3) self.table.setHorizontalHeaderLabels( ['Name', 'Unlink', 'Link', 'Channel']) description = '{0} package{1} will be removed'.format( count_total_packages, plural_total) for row, pkg in enumerate(packages + deps): link_item = [p for p in actions_link if p['name'] == pkg] if not link_item: link_item = { 'version': '-'.center(len('link')), 'channel': '-'.center(len('channel')), } else: link_item = link_item[0] unlink_item = [p for p in actions_unlink if p['name'] == pkg] if not unlink_item: unlink_item = { 'version': '-'.center(len('link')), } else: unlink_item = unlink_item[0] unlink_version = str(unlink_item['version']) link_version = str(link_item['version']) item_unlink_v = QTableWidgetItem(unlink_version) item_link_v = QTableWidgetItem(link_version) item_link_c = QTableWidgetItem(link_item['channel']) if pkg in packages: item_name = QTableWidgetItem(pkg) else: item_name = QTableWidgetItem('*' + pkg) items = [item_name, item_unlink_v, item_link_v, item_link_c] for column, item in enumerate(items): item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) self.table.setItem(row, column, item) if deps: message = ( '<b>*</b> indicates the package is a dependency of a ' 'selected package{0}<br>').format(plural_selected) self.button_ok.setEnabled(True) self.table.resizeColumnsToContents() unlink_width = self.table.columnWidth(1) if unlink_width < 60: self.table.setColumnWidth(1, 60) self.table.setHorizontalHeaderLabels( ['Name ', 'Unlink ', 'Link ', 'Channel ']) self.table.setEnabled(True) self.update_status(message=message) self.label_description.setText(description) # Adjust size after data has populated the table self.table.resizeColumnsToContents() width = sum( self.table.columnWidth(i) for i in range(self.table.columnCount())) delta = (self.width() - self.table.width() + self.table.verticalHeader().width() + 10) new_width = width + delta if new_width < self.base_minimum_width: new_width = self.base_minimum_width self.setMinimumWidth(new_width) self.setMaximumWidth(new_width) self.sig_setup_ready.emit() def update_status(self, message='', value=None, max_value=None): """Update status of packages dialog.""" self.label_status.setText(message) if max_value is None and value is None: self.progress_bar.setVisible(False) else: self.progress_bar.setVisible(True) self.progress_bar.setMaximum(max_value) self.progress_bar.setValue(value)
class MeasurementSettings(QWidget): """ :type settings: Settings """ def __init__(self, settings: PartSettings, parent=None): super().__init__(parent) self.chosen_element: Optional[MeasurementListWidgetItem] = None self.chosen_element_area: Optional[Tuple[AreaType, float]] = None self.settings = settings self.profile_list = QListWidget(self) self.profile_description = QTextEdit(self) self.profile_description.setReadOnly(True) self.profile_options = QListWidget() self.profile_options_chosen = QListWidget() self.measurement_area_choose = QEnumComboBox(enum_class=AreaType) self.per_component = QEnumComboBox(enum_class=PerComponent) self.power_num = QDoubleSpinBox() self.power_num.setDecimals(3) self.power_num.setRange(-100, 100) self.power_num.setValue(1) self.choose_butt = QPushButton("→", self) self.discard_butt = QPushButton("←", self) self.proportion_butt = QPushButton("Ratio", self) self.proportion_butt.setToolTip("Create proportion from two parameter") self.move_up = QPushButton("↑", self) self.move_down = QPushButton("↓", self) self.remove_button = QPushButton("Remove") self.save_butt = QPushButton("Save") self.save_butt.setToolTip( "Set name for set and choose at least one parameter") self.save_butt_with_name = QPushButton( "Save with custom parameters designation") self.save_butt_with_name.setToolTip( "Set name for set and choose at least one parameter") self.reset_butt = QPushButton("Clear") self.soft_reset_butt = QPushButton("Remove user parameters") self.profile_name = QLineEdit(self) self.delete_profile_butt = QPushButton("Delete ") self.export_profiles_butt = QPushButton("Export") self.import_profiles_butt = QPushButton("Import") self.edit_profile_butt = QPushButton("Edit") self.choose_butt.setDisabled(True) self.choose_butt.clicked.connect(self.choose_option) self.discard_butt.setDisabled(True) self.discard_butt.clicked.connect(self.discard_option) self.proportion_butt.setDisabled(True) self.proportion_butt.clicked.connect(self.proportion_action) self.save_butt.setDisabled(True) self.save_butt.clicked.connect(self.save_action) self.save_butt_with_name.setDisabled(True) self.save_butt_with_name.clicked.connect(self.named_save_action) self.profile_name.textChanged.connect(self.name_changed) self.move_down.setDisabled(True) self.move_down.clicked.connect(self.move_down_fun) self.move_up.setDisabled(True) self.move_up.clicked.connect(self.move_up_fun) self.remove_button.setDisabled(True) self.remove_button.clicked.connect(self.remove_element) self.reset_butt.clicked.connect(self.reset_action) self.soft_reset_butt.clicked.connect(self.soft_reset) self.delete_profile_butt.setDisabled(True) self.delete_profile_butt.clicked.connect(self.delete_profile) self.export_profiles_butt.clicked.connect( self.export_measurement_profiles) self.import_profiles_butt.clicked.connect( self.import_measurement_profiles) self.edit_profile_butt.clicked.connect(self.edit_profile) self.profile_list.itemSelectionChanged.connect(self.profile_chosen) self.profile_options.itemSelectionChanged.connect( self.create_selection_changed) self.profile_options_chosen.itemSelectionChanged.connect( self.create_selection_chosen_changed) self.settings.measurement_profiles_changed.connect( self._refresh_profiles) layout = QVBoxLayout() layout.addWidget(QLabel("Measurement set:")) profile_layout = QHBoxLayout() profile_layout.addWidget(self.profile_list) profile_layout.addWidget(self.profile_description) profile_buttons_layout = QHBoxLayout() profile_buttons_layout.addWidget(self.delete_profile_butt) profile_buttons_layout.addWidget(self.export_profiles_butt) profile_buttons_layout.addWidget(self.import_profiles_butt) profile_buttons_layout.addWidget(self.edit_profile_butt) profile_buttons_layout.addStretch() layout.addLayout(profile_layout) layout.addLayout(profile_buttons_layout) heading_layout = QHBoxLayout() # heading_layout.addWidget(QLabel("Create profile"), 1) heading_layout.addWidget(h_line(), 6) layout.addLayout(heading_layout) name_layout = QHBoxLayout() name_layout.addWidget(QLabel("Set name:")) name_layout.addWidget(self.profile_name) name_layout.addStretch() name_layout.addWidget(QLabel("Per component:")) name_layout.addWidget(self.per_component) name_layout.addWidget(QLabel("Area:")) name_layout.addWidget(self.measurement_area_choose) name_layout.addWidget(QLabel("to power:")) name_layout.addWidget(self.power_num) layout.addLayout(name_layout) create_layout = QHBoxLayout() create_layout.addWidget(self.profile_options) butt_op_layout = QVBoxLayout() butt_op_layout.addStretch() butt_op_layout.addWidget(self.choose_butt) butt_op_layout.addWidget(self.discard_butt) butt_op_layout.addWidget(self.proportion_butt) butt_op_layout.addWidget(self.reset_butt) butt_op_layout.addStretch() create_layout.addLayout(butt_op_layout) create_layout.addWidget(self.profile_options_chosen) butt_move_layout = QVBoxLayout() butt_move_layout.addStretch() butt_move_layout.addWidget(self.move_up) butt_move_layout.addWidget(self.move_down) butt_move_layout.addWidget(self.remove_button) butt_move_layout.addStretch() create_layout.addLayout(butt_move_layout) layout.addLayout(create_layout) save_butt_layout = QHBoxLayout() save_butt_layout.addWidget(self.soft_reset_butt) save_butt_layout.addStretch() save_butt_layout.addWidget(self.save_butt) save_butt_layout.addWidget(self.save_butt_with_name) layout.addLayout(save_butt_layout) self.setLayout(layout) for profile in MEASUREMENT_DICT.values(): help_text = profile.get_description() lw = MeasurementListWidgetItem(profile.get_starting_leaf()) lw.setToolTip(help_text) self.profile_options.addItem(lw) self._refresh_profiles() def _refresh_profiles(self): item = self.profile_list.currentItem() items = list(self.settings.measurement_profiles.keys()) try: index = items.index(item.text()) except (ValueError, AttributeError): index = -1 self.profile_list.clear() self.profile_list.addItems(items) self.profile_list.setCurrentRow(index) if self.profile_list.count() == 0: self.export_profiles_butt.setDisabled(True) else: self.export_profiles_butt.setEnabled(True) def remove_element(self): elem = self.profile_options_chosen.currentItem() if elem is None: return index = self.profile_options_chosen.currentRow() self.profile_options_chosen.takeItem(index) if self.profile_options_chosen.count() == 0: self.move_down.setDisabled(True) self.move_up.setDisabled(True) self.remove_button.setDisabled(True) self.discard_butt.setDisabled(True) self.save_butt.setDisabled(True) self.save_butt_with_name.setDisabled(True) def delete_profile(self): item = self.profile_list.currentItem() del self.settings.measurement_profiles[str(item.text())] def profile_chosen(self): self.delete_profile_butt.setEnabled(True) if self.profile_list.count() == 0: self.profile_description.setText("") return item = self.profile_list.currentItem() if item is None: self.profile_description.setText("") return profile = self.settings.measurement_profiles[item.text()] self.profile_description.setText(str(profile)) def create_selection_changed(self): self.choose_butt.setEnabled(True) self.proportion_butt.setEnabled(True) def proportion_action(self): # TODO use get_parameters if self.chosen_element is None: item = self.profile_options.currentItem() self.chosen_element_area = self.get_parameters( deepcopy(item.stat), self.measurement_area_choose.currentEnum(), self.per_component.currentEnum(), self.power_num.value(), ) if self.chosen_element_area is None: return self.chosen_element = item item.setIcon(QIcon(os.path.join(icons_dir, "task-accepted.png"))) elif (self.profile_options.currentItem() == self.chosen_element and self.measurement_area_choose.currentEnum() == self.chosen_element_area.area and self.per_component.currentEnum() == self.chosen_element_area.per_component): self.chosen_element.setIcon(QIcon()) self.chosen_element = None else: item: MeasurementListWidgetItem = self.profile_options.currentItem( ) leaf = self.get_parameters( deepcopy(item.stat), self.measurement_area_choose.currentEnum(), self.per_component.currentEnum(), self.power_num.value(), ) if leaf is None: return lw = MeasurementListWidgetItem( Node(op="/", left=self.chosen_element_area, right=leaf)) lw.setToolTip("User defined") self._add_option(lw) self.chosen_element.setIcon(QIcon()) self.chosen_element = None self.chosen_element_area = None def _add_option(self, item: MeasurementListWidgetItem): for i in range(self.profile_options_chosen.count()): if item.text() == self.profile_options_chosen.item(i).text(): return self.profile_options_chosen.addItem(item) if self.good_name(): self.save_butt.setEnabled(True) self.save_butt_with_name.setEnabled(True) if self.profile_options.count() == 0: self.choose_butt.setDisabled(True) def create_selection_chosen_changed(self): # print(self.profile_options_chosen.count()) self.remove_button.setEnabled(True) if self.profile_options_chosen.count() == 0: self.move_down.setDisabled(True) self.move_up.setDisabled(True) self.remove_button.setDisabled(True) return self.discard_butt.setEnabled(True) if self.profile_options_chosen.currentRow() != 0: self.move_up.setEnabled(True) else: self.move_up.setDisabled(True) if self.profile_options_chosen.currentRow( ) != self.profile_options_chosen.count() - 1: self.move_down.setEnabled(True) else: self.move_down.setDisabled(True) def good_name(self): return str(self.profile_name.text()).strip() != "" def move_down_fun(self): row = self.profile_options_chosen.currentRow() item = self.profile_options_chosen.takeItem(row) self.profile_options_chosen.insertItem(row + 1, item) self.profile_options_chosen.setCurrentRow(row + 1) self.create_selection_chosen_changed() def move_up_fun(self): row = self.profile_options_chosen.currentRow() item = self.profile_options_chosen.takeItem(row) self.profile_options_chosen.insertItem(row - 1, item) self.profile_options_chosen.setCurrentRow(row - 1) self.create_selection_chosen_changed() def name_changed(self): if self.good_name() and self.profile_options_chosen.count() > 0: self.save_butt.setEnabled(True) self.save_butt_with_name.setEnabled(True) else: self.save_butt.setDisabled(True) self.save_butt_with_name.setDisabled(True) def form_dialog(self, arguments): return FormDialog(arguments, settings=self.settings, parent=self) def get_parameters(self, node: Union[Node, Leaf], area: AreaType, component: PerComponent, power: float): if isinstance(node, Node): return node node = node.replace_(power=power) if node.area is None: node = node.replace_(area=area) if node.per_component is None: node = node.replace_(per_component=component) with suppress(KeyError): arguments = MEASUREMENT_DICT[str(node.name)].get_fields() if len(arguments) > 0 and len(node.dict) == 0: dial = self.form_dialog(arguments) if dial.exec_(): node = node._replace(dict=dial.get_values()) else: return return node def choose_option(self): selected_item = self.profile_options.currentItem() # selected_row = self.profile_options.currentRow() if not isinstance(selected_item, MeasurementListWidgetItem): raise ValueError( f"Current item (type: {type(selected_item)} is not instance of MeasurementListWidgetItem" ) node = deepcopy(selected_item.stat) # noinspection PyTypeChecker node = self.get_parameters(node, self.measurement_area_choose.currentEnum(), self.per_component.currentEnum(), self.power_num.value()) if node is None: return lw = MeasurementListWidgetItem(node) lw.setToolTip(selected_item.toolTip()) self._add_option(lw) def discard_option(self): selected_item: MeasurementListWidgetItem = self.profile_options_chosen.currentItem( ) # selected_row = self.profile_options_chosen.currentRow() lw = MeasurementListWidgetItem(deepcopy(selected_item.stat)) lw.setToolTip(selected_item.toolTip()) self.create_selection_chosen_changed() for i in range(self.profile_options.count()): if lw.text() == self.profile_options.item(i).text(): return self.profile_options.addItem(lw) def edit_profile(self): item = self.profile_list.currentItem() if item is None: return profile = self.settings.measurement_profiles[str(item.text())] self.profile_options_chosen.clear() self.profile_name.setText(item.text()) for ch in profile.chosen_fields: self.profile_options_chosen.addItem( MeasurementListWidgetItem(ch.calculation_tree)) # self.gauss_img.setChecked(profile.use_gauss_image) self.save_butt.setEnabled(True) self.save_butt_with_name.setEnabled(True) def save_action(self): if self.profile_name.text() in self.settings.measurement_profiles: ret = QMessageBox.warning( self, "Profile exist", "Profile exist\nWould you like to overwrite it?", QMessageBox.No | QMessageBox.Yes, ) if ret == QMessageBox.No: return selected_values = [] for i in range(self.profile_options_chosen.count()): element: MeasurementListWidgetItem = self.profile_options_chosen.item( i) selected_values.append( MeasurementEntry(element.text(), element.stat)) stat_prof = MeasurementProfile(self.profile_name.text(), selected_values) self.settings.measurement_profiles[stat_prof.name] = stat_prof self.settings.dump() self.export_profiles_butt.setEnabled(True) def named_save_action(self): if self.profile_name.text() in self.settings.measurement_profiles: ret = QMessageBox.warning( self, "Profile exist", "Profile exist\nWould you like to overwrite it?", QMessageBox.No | QMessageBox.Yes, ) if ret == QMessageBox.No: return selected_values = [] for i in range(self.profile_options_chosen.count()): txt = str(self.profile_options_chosen.item(i).text()) selected_values.append((txt, str, txt)) val_dialog = MultipleInput("Set fields name", list(selected_values), parent=self) if val_dialog.exec_(): selected_values = [] for i in range(self.profile_options_chosen.count()): element: MeasurementListWidgetItem = self.profile_options_chosen.item( i) selected_values.append( MeasurementEntry(val_dialog.result[element.text()], element.stat)) stat_prof = MeasurementProfile(self.profile_name.text(), selected_values) self.settings.measurement_profiles[stat_prof.name] = stat_prof self.export_profiles_butt.setEnabled(True) def reset_action(self): self.profile_options.clear() self.profile_options_chosen.clear() self.profile_name.setText("") self.save_butt.setDisabled(True) self.save_butt_with_name.setDisabled(True) self.move_down.setDisabled(True) self.move_up.setDisabled(True) self.proportion_butt.setDisabled(True) self.choose_butt.setDisabled(True) self.discard_butt.setDisabled(True) for profile in MEASUREMENT_DICT.values(): help_text = profile.get_description() lw = MeasurementListWidgetItem(profile.get_starting_leaf()) lw.setToolTip(help_text) self.profile_options.addItem(lw) def soft_reset(self): # TODO rim should not be removed shift = 0 for i in range(self.profile_options.count()): item = self.profile_options.item(i - shift) if str(item.text()) not in MEASUREMENT_DICT: self.profile_options.takeItem(i - shift) if item == self.chosen_element: self.chosen_element = None del item shift += 1 self.create_selection_changed() def export_measurement_profiles(self): exp = ExportDialog(self.settings.measurement_profiles, StringViewer, parent=self) if not exp.exec_(): return dial = PSaveDialog( "Measurement profile (*.json)", settings=self.settings, path="io.export_directory", caption="Export settings profiles", ) dial.selectFile("measurements_profile.json") if dial.exec_(): file_path = str(dial.selectedFiles()[0]) data = { x: self.settings.measurement_profiles[x] for x in exp.get_export_list() } with open(file_path, "w", encoding="utf-8") as ff: json.dump(data, ff, cls=self.settings.json_encoder_class, indent=2) def import_measurement_profiles(self): dial = PLoadDialog( "Measurement profile (*.json)", settings=self.settings, path="io.export_directory", caption="Import settings profiles", parent=self, ) if dial.exec_(): file_path = str(dial.selectedFiles()[0]) stat, err = self.settings.load_part(file_path) if err: QMessageBox.warning( self, "Import error", "error during importing, part of data were filtered.") measurement_dict = self.settings.measurement_profiles imp = ImportDialog(stat, measurement_dict, StringViewer) if not imp.exec_(): return for original_name, final_name in imp.get_import_list(): measurement_dict[final_name] = stat[original_name] self.settings.dump()
class ImageInformation(QWidget): def __init__(self, settings: StackSettings, parent=None): """:type settings: ImageSettings""" super().__init__(parent) self._settings = settings self.path = QTextEdit("<b>Path:</b> example image") self.path.setWordWrapMode(QTextOption.WrapAnywhere) self.path.setReadOnly(True) self.setMinimumHeight(20) self.spacing = [QDoubleSpinBox() for _ in range(3)] self.multiple_files = QCheckBox("Show multiple files panel") self.multiple_files.setChecked( settings.get("multiple_files_widget", True)) self.multiple_files.stateChanged.connect(self.set_multiple_files) units_value = self._settings.get("units_value", Units.nm) for el in self.spacing: el.setAlignment(Qt.AlignRight) el.setButtonSymbols(QAbstractSpinBox.NoButtons) el.setRange(0, 100000) # noinspection PyUnresolvedReferences el.valueChanged.connect(self.image_spacing_change) self.units = QEnumComboBox(enum_class=Units) self.units.setCurrentEnum(units_value) # noinspection PyUnresolvedReferences self.units.currentIndexChanged.connect(self.update_spacing) self.add_files = AddFiles(settings, btn_layout=FlowLayout) spacing_layout = QFormLayout() spacing_layout.addRow("x:", self.spacing[0]) spacing_layout.addRow("y:", self.spacing[1]) spacing_layout.addRow("z:", self.spacing[2]) spacing_layout.addRow("Units:", self.units) layout = QVBoxLayout() layout.addWidget(self.path) layout.addWidget(QLabel("Image spacing:")) layout.addLayout(spacing_layout) layout.addWidget(self.add_files) layout.addStretch(1) layout.addWidget(self.multiple_files) self.setLayout(layout) self._settings.image_changed[str].connect(self.set_image_path) @Slot(int) def set_multiple_files(self, val): self._settings.set("multiple_files_widget", val) def update_spacing(self, index=None): units_value = self.units.currentEnum() if index is not None: self._settings.set("units_value", units_value) for el, val in zip(self.spacing, self._settings.image_spacing[::-1]): el.blockSignals(True) el.setValue(val * UNIT_SCALE[units_value.value]) el.blockSignals(False) if self._settings.is_image_2d(): # self.spacing[2].setValue(0) self.spacing[2].setDisabled(True) else: self.spacing[2].setDisabled(False) def set_image_path(self, value): self.path.setText(f"<b>Path:</b> {value}") self.update_spacing() def image_spacing_change(self): self._settings.image_spacing = [ el.value() / UNIT_SCALE[self.units.currentIndex()] for i, el in enumerate(self.spacing[::-1]) ] def showEvent(self, _a0): units_value = self._settings.get("units_value", Units.nm) for el, val in zip(self.spacing, self._settings.image_spacing[::-1]): el.setValue(val * UNIT_SCALE[units_value.value]) if self._settings.is_image_2d(): self.spacing[2].setValue(0) self.spacing[2].setDisabled(True) else: self.spacing[2].setDisabled(False)
class LogViewerDialog(DialogBase): def __init__(self): super(LogViewerDialog, self).__init__() # Widgets self.label = QLabel('Select log file:') self.combobox = QComboBox() self.text_edit = QTextEdit() # Widget setup self.text_edit.setFrameStyle(QFrame.Plain) self.setWindowTitle('Log Viewer') self.setMinimumWidth(600) self.setMinimumHeight(400) self.text_edit.setReadOnly(True) # Layouts top_layout = QHBoxLayout() top_layout.addWidget(self.label) top_layout.addWidget(self.combobox) layout = QVBoxLayout() layout.addLayout(top_layout) layout.addWidget(self.text_edit) self.setLayout(layout) # Signals self.combobox.currentIndexChanged.connect(self.update_text) # Setup() self.setup() self.update_style_sheet() def update_style_sheet(self, style_sheet=None): if style_sheet is None: style_sheet = load_style_sheet() self.setStyleSheet(style_sheet) def setup(self): """ """ self.combobox.clear() logfiles = [conda_manager_logfile] navigator_logfiles = [os.path.join(LOG_FOLDER, fname) for fname in os.listdir(LOG_FOLDER)] for logf in sorted(navigator_logfiles) + logfiles: if os.path.isfile(logf): self.combobox.addItem(logf) def update_text(self, index): """ """ text = self.combobox.currentText() if os.path.isfile(text): with open(text, 'r') as f: data = f.read() self.text_edit.setText(data) def refresh(self): """
class MessageBox(DialogBase): """Base message box dialog.""" QUESTION_BOX = 100 INFORMATION_BOX = 101 ERROR_BOX = 102 REMOVE_BOX = 103 sig_url_clicked = Signal(object) def __init__(self, type_, error='', title='', text='', learn_more=None): """Base message box dialog.""" super(MessageBox, self).__init__() from anaconda_navigator.utils.analytics import GATracker self.tracker = GATracker() self.label_text = QLabel(to_text_string(text)) self.textbox_error = QTextEdit() self.button_ok = ButtonPrimary('Ok') self.button_yes = ButtonPrimary('Yes') self.button_no = ButtonNormal('No') self.button_copy = ButtonNormal('Copy text') self.button_learn = ButtonNormal('Learn more') self.button_remove = ButtonDanger('Remove') self.button_cancel = ButtonNormal('Cancel') self.button_send = ButtonNormal('Report Issue', parent=self) self.label_text.setOpenExternalLinks(False) self.label_text.setWordWrap(True) self.label_text.linkActivated.connect(self.url_clicked) self.textbox_error.setReadOnly(True) self.textbox_error.setFrameStyle(QTextEdit.Plain) self.textbox_error.setFrameShape(QTextEdit.NoFrame) self.setMinimumWidth(260) self.textbox_error.verticalScrollBar().show() self.setWindowTitle(to_text_string(title)) error = to_text_string(error).split('\n') error = '<br>'.join(error) self.textbox_error.setText(error) # Layouts layout = QVBoxLayout() layout.addWidget(self.label_text) layout.addWidget(SpacerVertical()) if error: layout.addWidget(self.textbox_error) layout.addWidget(SpacerVertical()) layout.addWidget(self.button_copy) layout.addWidget(SpacerVertical()) layout.addWidget(SpacerVertical()) layout_buttons = QHBoxLayout() layout_buttons.addStretch() layout.addLayout(layout_buttons) self.layout = layout self.setLayout(layout) # Signals self.button_copy.clicked.connect(self.copy_text) self.button_ok.clicked.connect(self.accept) self.button_yes.clicked.connect(self.accept) self.button_no.clicked.connect(self.reject) self.button_remove.clicked.connect(self.accept) self.button_cancel.clicked.connect(self.reject) self.button_send.clicked.connect(self.send) # Setup self.button_learn.setVisible(bool(learn_more)) if bool(learn_more): layout_buttons.addWidget(self.button_learn) layout_buttons.addWidget(SpacerHorizontal()) self.button_learn.clicked.connect( lambda: self.show_url(learn_more) ) if type_ == self.ERROR_BOX: layout_buttons.addWidget(self.button_send) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.INFORMATION_BOX: layout_buttons.addWidget(self.button_ok) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.textbox_error.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.QUESTION_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_no) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_yes) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_remove.setVisible(False) self.button_cancel.setVisible(False) elif type_ == self.REMOVE_BOX: layout_buttons.addStretch() layout_buttons.addWidget(self.button_cancel) layout_buttons.addWidget(SpacerHorizontal()) layout_buttons.addWidget(self.button_remove) layout_buttons.addWidget(SpacerHorizontal()) self.textbox_error.setVisible(False) self.button_ok.setVisible(False) self.button_copy.setVisible(False) self.button_yes.setVisible(False) self.button_no.setVisible(False) self.button_send.setVisible(False) self.layout_buttons = layout_buttons def url_clicked(self, url): """Emit url interaction.""" self.sig_url_clicked.emit(url) def copy_text(self): """Copy all the content of the displayed error message.""" self.textbox_error.selectAll() self.textbox_error.copy() def show_url(self, url=None): """Open url in default browser.""" if url: qurl = QUrl(url) QDesktopServices.openUrl(qurl) self.tracker.track_event('help', 'documentation', url) def send(self): """Send error report to github and create an issue with a template.""" import webbrowser from anaconda_navigator.utils.analytics import GATracker base = "https://github.com/ContinuumIO/anaconda-issues/issues/new?{0}" template = ''' ## Main error {text} ## Traceback ``` {trace} ``` ## System information ``` {info} ``` ''' info = GATracker().info info = '\n'.join('{}: {}'.format(k, v) for k, v in info.items()) query = parse.urlencode( { 'title': "Navigator Error", 'labels': "tag:navigator", 'body': template.format( text=self.text, trace=self.error, info=info ) } ) url = base.format(query) webbrowser.open_new_tab(url)
class MainWindow(QMainWindow): def selectFileToOpen(self): def getPreProcessingChoice(self, filename, filestructure): items = ("Choose the longest", "Merge all") item, okPressed = QInputDialog.getItem( self, "Multiple tracks/segments", "File '" + filename + "' contains more than one track/segment\n\n" + infos + "\nWhat to do?", items, 0, False) if okPressed and item: return items.index(item) else: return 0 # Try to recover the last used directory old_directory = self.settings.value("lastdirectory", str) # Check if the setting exists if old_directory is not None: # Check if it's not empty if old_directory: old_directory = old_directory else: old_directory = bombo.TRACKS_FOLDER else: old_directory = bombo.TRACKS_FOLDER # Open the dialog box fullfilename_list = QFileDialog.getOpenFileNames( self, 'Open .gpx', old_directory, "GPX files (*.gpx)") if os.environ['QT_API'] == 'pyqt': pass elif os.environ['QT_API'] == 'pyqt5': fullfilename_list = fullfilename_list[0] # Process every selected file for i, fullfilename in enumerate(fullfilename_list): # Process filename directory, filename = os.path.split(str(fullfilename)) filename, fileextension = os.path.splitext(filename) # Save the new directory in the application settings (it only # needs to be done once) if i == 0: # print "New directory to be saved: {}\n".format(directory) if os.environ['QT_API'] == 'pyqt': self.settings.setValue("lastdirectory", str(directory)) elif os.environ['QT_API'] == 'pyqt5': self.settings.setValue("lastdirectory", QtCore.QVariant(str(directory))) # Open file and inspect what's inside gpxraw, longest_traseg, Ntracks, Nsegments, infos = bombo.LoadGPX( fullfilename) # If there's more than one track or segment, ask how to proceed if (Ntracks > 1) or (Nsegments > 1): preprocessingchoice = getPreProcessingChoice( self, filename, infos) if preprocessingchoice == 0: preprocessedgpx = bombo.SelectOneTrackAndSegmentFromGPX( gpxraw, longest_traseg[0], longest_traseg[1]) listname = filename + " (longest)" elif preprocessingchoice == 1: preprocessedgpx = bombo.MergeAllTracksAndSegmentsFromGPX( gpxraw) listname = filename + " (merged)" else: preprocessedgpx = gpxraw listname = filename # Append the list of open GPX files using the next available color (that's the size of the list -1) self.gpxlist.append(preprocessedgpx) self.gpxnamelist.append(listname) newitem = QListWidgetItem(listname) newitem.setBackground( QtGui.QColor(self.palette[len(self.gpxlist) - 1])) self.tracklist.addItem(newitem) return def Go(self): if len(self.gpxselectedlist) > 0: # Temporarily change cursor QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) # Clear up global variables self.proc_coords = [] self.proc_measurements = [] self.proc_state_means = [] self.proc_state_vars = [] self.proc_new_coords = [] self.proc_new_gpx = [] self.proc_coords_to_plot = [] self.proc_coords_to_plot2 = [] self.proc_balloondata = [] # For every GPX file that is selected self.textWarningConsole.clear() for i, currentgpx in enumerate(self.gpxselectedlist): # Parse the GPX file gpx, coords, dinfos_before, warnings = bombo.ParseGPX( currentgpx, track_nr=0, segment_nr=0, use_srtm_elevation=bool(self.checkUseSRTM.isChecked())) self.textWarningConsole.append(warnings) # Kalman processing coords, measurements, state_means, state_vars, dinfos_during = bombo.ApplyKalmanFilter( coords, gpx, method=self.comboBoxProcessingMethod.currentIndex(), use_acceleration=self.checkUseAcceleration.isChecked(), extra_smooth=self.checkExtraSmooth.isChecked(), debug_plot=False) # Save data in GPX structure to compute speed and elevations new_coords, new_gpx, dinfos_after = bombo.SaveDataToCoordsAndGPX( coords, state_means) # Update GUI with the computed stats parent = QtGui.QStandardItem(self.gpxselectednamelist[i]) parent_beforeprocessing = QtGui.QStandardItem("Raw GPX stats") parent_beforeprocessing.appendRow([ QtGui.QStandardItem("Total distance"), QtGui.QStandardItem(dinfos_before['total_distance']) ]) parent_beforeprocessing_moving = QtGui.QStandardItem("Moving") parent_beforeprocessing_moving.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_before['moving_time']) ]) parent_beforeprocessing_moving.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_before['moving_distance']) ]) parent_beforeprocessing.appendRow( parent_beforeprocessing_moving) parent_beforeprocessing_idle = QtGui.QStandardItem("Idle") parent_beforeprocessing_idle.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_before['idle_time']) ]) parent_beforeprocessing_idle.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_before['idle_distance']) ]) parent_beforeprocessing.appendRow(parent_beforeprocessing_idle) parent_beforeprocessing.appendRow([ QtGui.QStandardItem("Elevation"), QtGui.QStandardItem(dinfos_before['elevation']) ]) parent_beforeprocessing.appendRow([ QtGui.QStandardItem("Climb"), QtGui.QStandardItem(dinfos_before['climb']) ]) parent.appendRow(parent_beforeprocessing) parent.appendRow([ QtGui.QStandardItem("Samples"), QtGui.QStandardItem(dinfos_during['nsamples']) ]) parent.appendRow([ QtGui.QStandardItem("Total distance"), QtGui.QStandardItem(dinfos_after['total_distance']) ]) parent_moving = QtGui.QStandardItem("Moving") parent_moving.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_after['moving_time']) ]) parent_moving.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_after['moving_distance']) ]) parent.appendRow(parent_moving) parent_idle = QtGui.QStandardItem("Idle") parent_idle.appendRow([ QtGui.QStandardItem("Time"), QtGui.QStandardItem(dinfos_after['idle_time']) ]) parent_idle.appendRow([ QtGui.QStandardItem("Distance"), QtGui.QStandardItem(dinfos_after['idle_distance']) ]) parent.appendRow(parent_idle) parent.appendRow([ QtGui.QStandardItem("Elevation"), QtGui.QStandardItem(dinfos_after['elevation']) ]) parent.appendRow([ QtGui.QStandardItem("Climb"), QtGui.QStandardItem(dinfos_after['climb']) ]) self.treemodel.appendRow(parent) # Create balloondata for the html plot balloondata = { 'distance': np.cumsum( bombo.HaversineDistance(np.asarray(new_coords['lat']), np.asarray( new_coords['lon']))), 'elevation': np.asarray(new_coords['ele']), 'speed': None } # Create extra data for the html plot (fully implemented in bombo, not here) """ data = np.ones((len(lat_cleaned),2)) data[:,0] = h_filtered / np.max(h_filtered) * 0.0004 data[:,1] = np.hstack((np.asarray([0]), speed_h)) / np.max(np.hstack((np.asarray([0]), speed_h))) * 0.0004 tangentdata = {'data': data, 'sides': (0, 1), 'palette': ('blue','red')} """ # Save relevant output in global variables self.proc_coords.append(coords) self.proc_measurements.append(measurements) self.proc_state_means.append(state_means) self.proc_state_vars.append(state_vars) self.proc_new_coords.append(new_coords) self.proc_new_gpx.append(new_gpx) self.proc_coords_to_plot.append( np.vstack((new_coords['lat'], new_coords['lon'])).T) self.proc_coords_to_plot2.append( np.vstack((coords['lat'], coords['lon'])).T) self.proc_balloondata.append(balloondata) # Restore original cursor QApplication.restoreOverrideCursor() # Generate embedded plots if len(self.gpxselectedlist) == 1: self.plotEmbeddedElevationAndSpeed.update_figure( measurements, state_means, new_gpx.tracks[0].segments[0]) self.plotEmbeddedDetails.update_figure( measurements, state_means, state_vars, new_gpx.tracks[0].segments[0]) else: # Commentato per adesso # self.plotEmbeddedElevationAndSpeed.update_figure_multiple_tracks(self.proc_measurements, self.proc_state_means, self.proc_new_gpx) self.plotEmbeddedElevationAndSpeed.clear_figure() self.plotEmbeddedDetails.clear_figure() # Generate html plot, if only one track is selected, proceed with the complete output, otherwise just plot the traces if len(self.gpxselectedlist) is 1: bombo.PlotOnMap( coords_array_list=self.proc_coords_to_plot, coords_array2_list=self.proc_coords_to_plot2, coords_palette=self.selectedpalette, tangentdata=None, balloondata_list=self.proc_balloondata, rdp_reduction=self.checkUseRDP.isChecked(), showmap=bool(self.check2DMapInExternalBrowser.isChecked())) else: bombo.PlotOnMap( coords_array_list=self.proc_coords_to_plot, coords_array2_list=None, coords_palette=self.selectedpalette, tangentdata=None, balloondata_list=self.proc_balloondata, rdp_reduction=self.checkUseRDP.isChecked(), showmap=bool(self.check2DMapInExternalBrowser.isChecked())) self.map2d.load(QtCore.QUrl(bombo.MAP_2D_FILENAME)) self.map2d.show() # Generate 3D plot, only with one track for the moment if len(self.gpxselectedlist) == 1: if self.check3DMapSelection.isChecked(): tile_selection = 'auto' else: tile_selection = self.text3DMapName.text() terrain, track, warnings = bombo.Generate3DMap( new_coords['lat'], new_coords['lon'], tile_selection=tile_selection, margin=self.spinbox3DMargin.value(), elevation_scale=self.spinbox3DElevationScale.value(), mapping='coords', use_osm_texture=True, texture_type='osm', texture_zoom=self.spinbox3DOSMZoom.value(), texture_invert=self.check3DOSMInvert.isChecked(), use_proxy=self.use_proxy, proxy_data=self.proxy_config, verbose=False) self.textWarningConsole.append(warnings) if terrain is not None: self.map3d.update_plot(terrain, track) else: self.textWarningConsole.setText( "You need to open a .gpx file before!") return def PlotSpecificAreaDialog(self): def PlotSpecificArea(): # Save coordinates for the next time if os.environ['QT_API'] == 'pyqt': self.settings.setValue("last_point_coord_lat", self.spinboxLatDec.value()) self.settings.setValue("last_point_coord_lon", self.spinboxLonDec.value()) elif os.environ['QT_API'] == 'pyqt5': self.settings.setValue( "last_point_coord_lat", QtCore.QVariant(self.spinboxLatDec.value())) self.settings.setValue( "last_point_coord_lon", QtCore.QVariant(self.spinboxLonDec.value())) # Select the 3D Map tab self.tab.setCurrentIndex(2) # Plot if self.check3DMapSelection.isChecked(): tile_selection = 'auto' else: tile_selection = self.text3DMapName.text() terrain, track, warnings = bombo.Generate3DMap( [self.spinboxLatDec.value()], [self.spinboxLonDec.value()], tile_selection=tile_selection, margin=self.spinbox3DMargin.value(), elevation_scale=self.spinbox3DElevationScale.value(), mapping='coords', use_osm_texture=True, texture_type='osm', texture_zoom=self.spinbox3DOSMZoom.value(), texture_invert=self.check3DOSMInvert.isChecked(), use_proxy=self.use_proxy, proxy_data=self.proxy_config, verbose=False) self.textWarningConsole.append(warnings) if terrain is not None: self.map3d.update_plot(terrain, track) d.done(0) def Convert(): try: dd = bombo.parse_dms(self.textLatLonGMS.text()) self.spinboxLatDec.setValue(dd[0]) self.spinboxLonDec.setValue(dd[1]) except: pass d = QDialog() grid = QGridLayout() hBox_coordsGMS = QHBoxLayout() hBox_coordsGMS.setSpacing(5) label = QLabel('Coordinates (gms)') grid.addWidget(label, 0, 0) self.textLatLonGMS = QLineEdit() self.textLatLonGMS.setText("") grid.addWidget(self.textLatLonGMS, 0, 1, 1, 2) button1 = QPushButton("Convert to decimal") button1.clicked.connect(Convert) grid.addWidget(button1, 0, 3) label = QLabel('Coordinates (decimal)') grid.addWidget(label, 1, 0) self.spinboxLatDec = QDoubleSpinBox() self.spinboxLatDec.setRange(-90, +90) self.spinboxLatDec.setSingleStep(0.0000001) self.spinboxLatDec.setDecimals(7) grid.addWidget(self.spinboxLatDec, 1, 1) self.spinboxLonDec = QDoubleSpinBox() self.spinboxLonDec.setRange(-180, +180) self.spinboxLonDec.setSingleStep(0.0000001) self.spinboxLonDec.setDecimals(7) grid.addWidget(self.spinboxLonDec, 1, 2) # Try to recover the last used points try: old_lat = self.settings.value("last_point_coord_lat", type=float) old_lon = self.settings.value("last_point_coord_lon", type=float) self.spinboxLatDec.setValue(old_lat) self.spinboxLonDec.setValue(old_lon) except: # Coordinates of Mt. Rinjani in Indonesia self.spinboxLatDec.setValue(-8.4166000) self.spinboxLonDec.setValue(116.4666000) button2 = QPushButton("Show 3D map") button2.clicked.connect(PlotSpecificArea) grid.addWidget(button2, 1, 3) d.setWindowTitle("Show point on 3D map") d.setLayout(grid) d.setWindowModality(QtCore.Qt.ApplicationModal) d.exec_() def ProxyDialog(self): def SetProxy(): self.use_proxy = bool(self.checkUseProxy.isChecked()) self.proxy_config = self.textProxyConfig.text() if os.environ['QT_API'] == 'pyqt': self.settings.setValue("use_proxy", self.use_proxy) self.settings.setValue("proxy_config", str(self.proxy_config)) elif os.environ['QT_API'] == 'pyqt5': self.settings.setValue("use_proxy", QtCore.QVariant(self.use_proxy)) self.settings.setValue("proxy_config", QtCore.QVariant(str(self.proxy_config))) d.done(0) d = QDialog() box = QVBoxLayout() hBox_proxy = QHBoxLayout() hBox_proxy.setSpacing(5) label = QLabel('Proxy') hBox_proxy.addWidget(label) self.textProxyConfig = QLineEdit() try: self.textProxyConfig.setText( self.settings.value('proxy_config', str)) except: self.textProxyConfig.setText(bombo.PROXY_DATA) self.textProxyConfig.setMinimumWidth(200) hBox_proxy.addWidget(self.textProxyConfig) box.addLayout(hBox_proxy) self.checkUseProxy = QCheckBox("Use proxy") try: self.checkUseProxy.setChecked( self.settings.value('use_proxy', bool)) except: self.checkUseProxy.setChecked(bool(bombo.USE_PROXY)) box.addWidget(self.checkUseProxy) button = QPushButton("Save configuration") button.clicked.connect(SetProxy) box.addWidget(button) d.setWindowTitle("Proxy configuration") d.setLayout(box) d.setWindowModality(QtCore.Qt.ApplicationModal) d.exec_() def __init__(self, parent=None): super(MainWindow, self).__init__() self.initVariables() self.initUI() def initVariables(self): self.gpxlist = list() self.gpxnamelist = list() self.gpxselectedlist = list() self.gpxselectednamelist = list() self.palette = bombo.GeneratePalette(N=10) * 5 # replicated 5 times #self.palette = ["#0000FF", "#00FF00", "#00FFFF", "#FF0000", "#FF00FF", "#FFFF00", "#FFFFFF"] # test palette self.selectedpalette = list() self.proc_coords = list() self.proc_measurements = list() self.proc_state_means = list() self.proc_state_vars = list() self.proc_new_coords = list() self.proc_new_gpx = list() self.proc_coords_to_plot = list() self.proc_coords_to_plot2 = list() self.proc_balloondata = list() def initUI(self): def selection_changed(): # Retrieve selected items # selecteditems = self.tracklist.selectedItems() selectedindexes = self.tracklist.selectedIndexes() # Adding the selected items to the processing list self.gpxselectedlist[:] = [] self.gpxselectednamelist[:] = [] self.selectedpalette[:] = [] for i in selectedindexes: # print str(i.text()) self.gpxselectedlist.append(self.gpxlist[i.row()]) self.gpxselectednamelist.append(self.gpxnamelist[i.row()]) self.selectedpalette.append(self.palette[i.row()]) def ClearStats(): """ # Some other code that could be used in the future index = self.treemodel.indexFromItem(parent1) self.tree.expand(index) selmod = self.tree.selectionModel() index2 = self.treemodel.indexFromItem(child2) selmod.select(index2, QtCore.QItemSelectionModel.Select|QtCore.QItemSelectionModel.Rows) root = self.treemodel.invisibleRootItem() (item.parent() or root).removeChild(item) """ # Returns a list of indexes. In our case, for each row there are 2 indexes, cos there are 2 columns. for index in self.tree.selectedIndexes(): # Consider only the first columns if index.column() == 0: # Need to check if it's a top item (i.e. track), otherwise if a subitem (i.e. distance or time) is selected, the result might be buggy parent = index.parent() parent_item = self.treemodel.itemFromIndex(parent) if parent_item is None: self.treemodel.removeRow(index.row()) # Application Settings QtCore.QCoreApplication.setOrganizationName("Ste") QtCore.QCoreApplication.setOrganizationDomain( "https://github.com/stesalati/sport/") QtCore.QCoreApplication.setApplicationName("TrackAnalyser") # Config settings self.settings = QtCore.QSettings(self) # Proxy settings try: self.use_proxy = self.settings.value('use_proxy', bool) self.proxy_config = self.settings.value('proxy_config', str) except: self.use_proxy = bombo.USE_PROXY self.proxy_config = bombo.PROXY_DATA # Actions openfile = QAction(QtGui.QIcon("icons/openfile.png"), "Open .gpx", self) openfile.setShortcut("Ctrl+O") openfile.setStatusTip("Open file") openfile.triggered.connect(self.selectFileToOpen) go = QAction(QtGui.QIcon("icons/go.png"), "Go!", self) go.setShortcut("Ctrl+R") go.setStatusTip("Run analysis") go.triggered.connect(self.Go) clearstats = QAction(QtGui.QIcon("icons/clear.png"), "Clear stats", self) clearstats.setShortcut("Ctrl+C") clearstats.setStatusTip("Clear stats") clearstats.triggered.connect(ClearStats) sep1 = QAction(self) sep1.setSeparator(True) showpoint = QAction(QtGui.QIcon("icons/point.png"), "Show point", self) showpoint.setShortcut("Ctrl+P") showpoint.setStatusTip("Show point") showpoint.triggered.connect(self.PlotSpecificAreaDialog) sep2 = QAction(self) sep2.setSeparator(True) quitapp = QAction(QtGui.QIcon("icons/quit.png"), "Quit", self) quitapp.setShortcut("Ctrl+Q") quitapp.setStatusTip("Quit application") quitapp.triggered.connect(qApp.quit) configs = QAction(QtGui.QIcon("icons/configs.png"), "Configs", self) configs.setStatusTip("Configs") configs.triggered.connect(self.ProxyDialog) # Menubar mainMenu = self.menuBar() configMenu = mainMenu.addMenu('&Config') configMenu.addAction(configs) # Toolbar toolbar = self.addToolBar('My tools') toolbar.addAction(openfile) toolbar.addAction(go) toolbar.addAction(clearstats) toolbar.addAction(sep1) toolbar.addAction(showpoint) toolbar.addAction(sep2) toolbar.addAction(quitapp) toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon) toolbar.setIconSize(QtCore.QSize(30, 30)) # Status bar self.statusBar().show() # Main widget (everything that's not toolbar, statusbar or menubar must be in this widget) self.scatola = QWidget() # Main horizontal impagination hBox = QHBoxLayout() hBox.setSpacing(5) # Vertical left column vBox_left = QVBoxLayout() vBox_left.setSpacing(5) # 1st vertical box, a list self.tracklist = QListWidget() vBox_left.addWidget(self.tracklist) self.tracklist.setSelectionMode(QAbstractItemView.ExtendedSelection) self.tracklist.itemSelectionChanged.connect(selection_changed) self.tracklist.setMaximumHeight(120) # 2nd vertical box, containing several horizontal boxes, one for each setting vBox2 = QVBoxLayout() vBox2.setSpacing(5) # Just the group label labelSettings = QLabel('Settings') vBox2.addWidget(labelSettings) # Use/don't use corrected altitude self.checkUseSRTM = QCheckBox( "Use SRTM corrected elevation (needs Internet)") self.checkUseSRTM.setChecked(False) vBox2.addWidget(self.checkUseSRTM) # Choose processing method + use/don't use acceleration hBoxProcessingMethod = QHBoxLayout() labelProcessingMethod = QLabel('Processing method') hBoxProcessingMethod.addWidget(labelProcessingMethod) self.comboBoxProcessingMethod = QComboBox() self.comboBoxProcessingMethod.addItem("Just use available data") self.comboBoxProcessingMethod.addItem( "Fill all gaps at T=1s (resample)") self.comboBoxProcessingMethod.addItem("Fill only smaller gaps at T=1s") hBoxProcessingMethod.addWidget(self.comboBoxProcessingMethod) self.checkUseAcceleration = QCheckBox("Use acceleration") self.checkUseAcceleration.setChecked(False) hBoxProcessingMethod.addWidget(self.checkUseAcceleration) vBox2.addLayout(hBoxProcessingMethod) # Use/don't use variance smooth self.checkExtraSmooth = QCheckBox("Extra smooth") self.checkExtraSmooth.setChecked(False) vBox2.addWidget(self.checkExtraSmooth) # 2D interactive map settings hBox2DMap = QHBoxLayout() self.checkUseRDP = QCheckBox("Use RDP to reduce points") self.checkUseRDP.setChecked(False) hBox2DMap.addWidget(self.checkUseRDP) self.check2DMapInExternalBrowser = QCheckBox( "Show in external browser") self.check2DMapInExternalBrowser.setChecked(False) hBox2DMap.addWidget(self.check2DMapInExternalBrowser) vBox2.addLayout(hBox2DMap) # Settings for the 3D map line3DViewSettings = QFrame() #line3DViewSettings.setGeometry(QtCore.QRect(320, 150, 118, 3)) line3DViewSettings.setFrameShape(QFrame.HLine) line3DViewSettings.setFrameShadow(QFrame.Sunken) vBox2.addWidget(line3DViewSettings) label3DViewSettings = QLabel('3D view settings') vBox2.addWidget(label3DViewSettings) hBox3DMapSelection = QHBoxLayout() self.check3DMapSelection = QCheckBox( "Select elevation tiles automatically, otherwise") self.check3DMapSelection.setChecked(True) hBox3DMapSelection.addWidget(self.check3DMapSelection) self.text3DMapName = QLineEdit() self.text3DMapName.setText("Iceland.tif") hBox3DMapSelection.addWidget(self.text3DMapName) vBox2.addLayout(hBox3DMapSelection) hBox3D = QHBoxLayout() label3DMargin = QLabel('Margin') hBox3D.addWidget(label3DMargin) self.spinbox3DMargin = QSpinBox() self.spinbox3DMargin.setRange(50, 1000) self.spinbox3DMargin.setValue(100) self.spinbox3DMargin.setSingleStep(10) hBox3D.addWidget(self.spinbox3DMargin) labelSpace = QLabel(' ') hBox3D.addWidget(labelSpace) label3DElevationScale = QLabel('Elev. scale') hBox3D.addWidget(label3DElevationScale) self.spinbox3DElevationScale = QDoubleSpinBox() self.spinbox3DElevationScale.setRange(1, 50) self.spinbox3DElevationScale.setSingleStep(0.1) hBox3D.addWidget(self.spinbox3DElevationScale) hBox3D.addWidget(labelSpace) label3DOSMZoom = QLabel('Zoom') hBox3D.addWidget(label3DOSMZoom) self.spinbox3DOSMZoom = QSpinBox() self.spinbox3DOSMZoom.setRange(8, 15) self.spinbox3DOSMZoom.setValue(13) self.spinbox3DOSMZoom.setSingleStep(1) hBox3D.addWidget(self.spinbox3DOSMZoom) hBox3D.addWidget(labelSpace) self.check3DOSMInvert = QCheckBox("Invert") self.check3DOSMInvert.setChecked(False) hBox3D.addWidget(self.check3DOSMInvert) vBox2.addLayout(hBox3D) vBox_left.addLayout(vBox2) # 3rd stats tree lineTree = QFrame() lineTree.setFrameShape(QFrame.HLine) lineTree.setFrameShadow(QFrame.Sunken) vBox2.addWidget(lineTree) labelTree = QLabel('Track stats') vBox2.addWidget(labelTree) self.tree = QTreeView() self.tree.setSelectionBehavior(QAbstractItemView.SelectRows) self.treemodel = QtGui.QStandardItemModel() self.treemodel.setHorizontalHeaderLabels(['Name', 'Value']) self.tree.setModel(self.treemodel) self.tree.setUniformRowHeights(True) self.tree.setColumnWidth(0, 200) vBox_left.addWidget(self.tree) # 4th text, containing text messages/errors self.textWarningConsole = QTextEdit() self.textWarningConsole.setReadOnly(True) self.textWarningConsole.setFont(QtGui.QFont("Courier New", FONTSIZE)) self.textWarningConsole.clear() self.textWarningConsole.setMaximumHeight(50) vBox_left.addWidget(self.textWarningConsole) # I put "vBox_left" inside a widget and then the widget inside "hBox" # instead of just doing "hBox.addLayout(vBox_left) so I can set its # maximum width. vBox_left_widget = QWidget() vBox_left_widget.setLayout(vBox_left) vBox_left_widget.setMinimumWidth(400) vBox_left_widget.setMaximumWidth(500) hBox.addWidget(vBox_left_widget) # Vertical right column self.tab = QTabWidget() # Tab 1: Summary: elevation and speed tab1 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Plot area self.plotEmbeddedElevationAndSpeed = EmbeddedPlot_ElevationSpeed( width=5, height=4, dpi=100) self.plotEmbeddedElevationAndSpeed.setMinimumWidth(800) # Add toolbar to the plot self.mpl_toolbar1 = NavigationToolbar( self.plotEmbeddedElevationAndSpeed, self.scatola) # Add widgets to the layout vBox_tab.addWidget(self.plotEmbeddedElevationAndSpeed) vBox_tab.addWidget(self.mpl_toolbar1) # Associate the layout to the tab tab1.setLayout(vBox_tab) # Tab 2: html 2D map tab2 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Area self.map2d = QtWebEngineWidgets.QWebEngineView() # Add widgets to the layout vBox_tab.addWidget(self.map2d) # Associate the layout to the tab tab2.setLayout(vBox_tab) # Tab 3: 3D plot tab3 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Area self.map3d = MayaviQWidget() # Add widgets to the layout vBox_tab.addWidget(self.map3d) # Associate the layout to the tab tab3.setLayout(vBox_tab) # Tab 4: Details tab4 = QWidget() # The tab layout vBox_tab = QVBoxLayout() vBox_tab.setSpacing(5) # Plot area self.plotEmbeddedDetails = EmbeddedPlot_Details(width=5, height=4, dpi=100) self.plotEmbeddedDetails.setMinimumWidth(800) # Add toolbar to the plot self.mpl_toolbar2 = NavigationToolbar(self.plotEmbeddedDetails, self.scatola) # Add widgets to the layout vBox_tab.addWidget(self.plotEmbeddedDetails) vBox_tab.addWidget(self.mpl_toolbar2) # Associate the layout to the tab tab4.setLayout(vBox_tab) # Associate tabs self.tab.addTab(tab1, "Summary") self.tab.addTab(tab2, "2D Map") self.tab.addTab(tab3, "3D Map") self.tab.addTab(tab4, "Details") hBox.addWidget(self.tab) # Setting hBox as main box self.scatola.setLayout(hBox) self.setCentralWidget(self.scatola) # Application settings self.setWindowTitle('TrackAnalyser') self.setWindowIcon((QtGui.QIcon('icons/app.png'))) self.setGeometry(100, 100, 1200, 700) self.show()
class TextEditor(QDialog): """Array Editor Dialog""" def __init__(self, text, title='', font=None, parent=None, readonly=False, size=(400, 300)): QDialog.__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.text = None self.btn_save_and_close = None # Display text as unicode if it comes as bytes, so users see # its right representation if is_binary_string(text): self.is_binary = True text = to_text_string(text, 'utf8') else: self.is_binary = False self.layout = QVBoxLayout() self.setLayout(self.layout) # Text edit self.edit = QTextEdit(parent) self.edit.setReadOnly(readonly) self.edit.textChanged.connect(self.text_changed) self.edit.setPlainText(text) if font is None: font = get_font() self.edit.setFont(font) self.layout.addWidget(self.edit) # Buttons configuration btn_layout = QHBoxLayout() btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout) # Make the dialog act as a window self.setWindowFlags(Qt.Window) self.setWindowIcon(ima.icon('edit')) self.setWindowTitle(_("Text editor") + \ "%s" % (" - "+str(title) if str(title) else "")) self.resize(size[0], size[1]) @Slot() def text_changed(self): """Text has changed""" # Save text as bytes, if it was initially bytes if self.is_binary: self.text = to_binary_string(self.edit.toPlainText(), 'utf8') else: self.text = to_text_string(self.edit.toPlainText()) if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def get_value(self): """Return modified text""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.text def setup_and_check(self, value): """Verify if TextEditor is able to display strings passed to it.""" try: to_text_string(value, 'utf8') return True except: return False
class ContentsWidget(QWidget): """Import wizard contents widget""" asDataChanged = Signal(bool) def __init__(self, parent, text): QWidget.__init__(self, parent) self.text_editor = QTextEdit(self) self.text_editor.setText(text) self.text_editor.setReadOnly(True) # Type frame type_layout = QHBoxLayout() type_label = QLabel(_("Import as")) type_layout.addWidget(type_label) data_btn = QRadioButton(_("data")) data_btn.setChecked(True) self._as_data = True type_layout.addWidget(data_btn) code_btn = QRadioButton(_("code")) self._as_code = False type_layout.addWidget(code_btn) txt_btn = QRadioButton(_("text")) type_layout.addWidget(txt_btn) h_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) type_layout.addItem(h_spacer) type_frame = QFrame() type_frame.setLayout(type_layout) # Opts frame grid_layout = QGridLayout() grid_layout.setSpacing(0) col_label = QLabel(_("Column separator:")) grid_layout.addWidget(col_label, 0, 0) col_w = QWidget() col_btn_layout = QHBoxLayout() self.tab_btn = QRadioButton(_("Tab")) self.tab_btn.setChecked(False) col_btn_layout.addWidget(self.tab_btn) self.ws_btn = QRadioButton(_("Whitespace")) self.ws_btn.setChecked(False) col_btn_layout.addWidget(self.ws_btn) other_btn_col = QRadioButton(_("other")) other_btn_col.setChecked(True) col_btn_layout.addWidget(other_btn_col) col_w.setLayout(col_btn_layout) grid_layout.addWidget(col_w, 0, 1) self.line_edt = QLineEdit(",") self.line_edt.setMaximumWidth(30) self.line_edt.setEnabled(True) other_btn_col.toggled.connect(self.line_edt.setEnabled) grid_layout.addWidget(self.line_edt, 0, 2) row_label = QLabel(_("Row separator:")) grid_layout.addWidget(row_label, 1, 0) row_w = QWidget() row_btn_layout = QHBoxLayout() self.eol_btn = QRadioButton(_("EOL")) self.eol_btn.setChecked(True) row_btn_layout.addWidget(self.eol_btn) other_btn_row = QRadioButton(_("other")) row_btn_layout.addWidget(other_btn_row) row_w.setLayout(row_btn_layout) grid_layout.addWidget(row_w, 1, 1) self.line_edt_row = QLineEdit(";") self.line_edt_row.setMaximumWidth(30) self.line_edt_row.setEnabled(False) other_btn_row.toggled.connect(self.line_edt_row.setEnabled) grid_layout.addWidget(self.line_edt_row, 1, 2) grid_layout.setRowMinimumHeight(2, 15) other_group = QGroupBox(_("Additional options")) other_layout = QGridLayout() other_group.setLayout(other_layout) skiprows_label = QLabel(_("Skip rows:")) other_layout.addWidget(skiprows_label, 0, 0) self.skiprows_edt = QLineEdit('0') self.skiprows_edt.setMaximumWidth(30) intvalid = QIntValidator(0, len(to_text_string(text).splitlines()), self.skiprows_edt) self.skiprows_edt.setValidator(intvalid) other_layout.addWidget(self.skiprows_edt, 0, 1) other_layout.setColumnMinimumWidth(2, 5) comments_label = QLabel(_("Comments:")) other_layout.addWidget(comments_label, 0, 3) self.comments_edt = QLineEdit('#') self.comments_edt.setMaximumWidth(30) other_layout.addWidget(self.comments_edt, 0, 4) self.trnsp_box = QCheckBox(_("Transpose")) #self.trnsp_box.setEnabled(False) other_layout.addWidget(self.trnsp_box, 1, 0, 2, 0) grid_layout.addWidget(other_group, 3, 0, 2, 0) opts_frame = QFrame() opts_frame.setLayout(grid_layout) data_btn.toggled.connect(opts_frame.setEnabled) data_btn.toggled.connect(self.set_as_data) code_btn.toggled.connect(self.set_as_code) # self.connect(txt_btn, SIGNAL("toggled(bool)"), # self, SLOT("is_text(bool)")) # Final layout layout = QVBoxLayout() layout.addWidget(type_frame) layout.addWidget(self.text_editor) layout.addWidget(opts_frame) self.setLayout(layout) def get_as_data(self): """Return if data type conversion""" return self._as_data def get_as_code(self): """Return if code type conversion""" return self._as_code def get_as_num(self): """Return if numeric type conversion""" return self._as_num def get_col_sep(self): """Return the column separator""" if self.tab_btn.isChecked(): return u"\t" elif self.ws_btn.isChecked(): return None return to_text_string(self.line_edt.text()) def get_row_sep(self): """Return the row separator""" if self.eol_btn.isChecked(): return u"\n" return to_text_string(self.line_edt_row.text()) def get_skiprows(self): """Return number of lines to be skipped""" return int(to_text_string(self.skiprows_edt.text())) def get_comments(self): """Return comment string""" return to_text_string(self.comments_edt.text()) @Slot(bool) def set_as_data(self, as_data): """Set if data type conversion""" self._as_data = as_data self.asDataChanged.emit(as_data) @Slot(bool) def set_as_code(self, as_code): """Set if code type conversion""" self._as_code = as_code
class ContentsWidget(QWidget): """Import wizard contents widget""" asDataChanged = Signal(bool) def __init__(self, parent, text): QWidget.__init__(self, parent) self.text_editor = QTextEdit(self) self.text_editor.setText(text) self.text_editor.setReadOnly(True) # Type frame type_layout = QHBoxLayout() type_label = QLabel(_("Import as")) type_layout.addWidget(type_label) data_btn = QRadioButton(_("data")) data_btn.setChecked(True) self._as_data= True type_layout.addWidget(data_btn) code_btn = QRadioButton(_("code")) self._as_code = False type_layout.addWidget(code_btn) txt_btn = QRadioButton(_("text")) type_layout.addWidget(txt_btn) h_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) type_layout.addItem(h_spacer) type_frame = QFrame() type_frame.setLayout(type_layout) # Opts frame grid_layout = QGridLayout() grid_layout.setSpacing(0) col_label = QLabel(_("Column separator:")) grid_layout.addWidget(col_label, 0, 0) col_w = QWidget() col_btn_layout = QHBoxLayout() self.tab_btn = QRadioButton(_("Tab")) self.tab_btn.setChecked(False) col_btn_layout.addWidget(self.tab_btn) other_btn_col = QRadioButton(_("other")) other_btn_col.setChecked(True) col_btn_layout.addWidget(other_btn_col) col_w.setLayout(col_btn_layout) grid_layout.addWidget(col_w, 0, 1) self.line_edt = QLineEdit(",") self.line_edt.setMaximumWidth(30) self.line_edt.setEnabled(True) other_btn_col.toggled.connect(self.line_edt.setEnabled) grid_layout.addWidget(self.line_edt, 0, 2) row_label = QLabel(_("Row separator:")) grid_layout.addWidget(row_label, 1, 0) row_w = QWidget() row_btn_layout = QHBoxLayout() self.eol_btn = QRadioButton(_("EOL")) self.eol_btn.setChecked(True) row_btn_layout.addWidget(self.eol_btn) other_btn_row = QRadioButton(_("other")) row_btn_layout.addWidget(other_btn_row) row_w.setLayout(row_btn_layout) grid_layout.addWidget(row_w, 1, 1) self.line_edt_row = QLineEdit(";") self.line_edt_row.setMaximumWidth(30) self.line_edt_row.setEnabled(False) other_btn_row.toggled.connect(self.line_edt_row.setEnabled) grid_layout.addWidget(self.line_edt_row, 1, 2) grid_layout.setRowMinimumHeight(2, 15) other_group = QGroupBox(_("Additional options")) other_layout = QGridLayout() other_group.setLayout(other_layout) skiprows_label = QLabel(_("Skip rows:")) other_layout.addWidget(skiprows_label, 0, 0) self.skiprows_edt = QLineEdit('0') self.skiprows_edt.setMaximumWidth(30) intvalid = QIntValidator(0, len(to_text_string(text).splitlines()), self.skiprows_edt) self.skiprows_edt.setValidator(intvalid) other_layout.addWidget(self.skiprows_edt, 0, 1) other_layout.setColumnMinimumWidth(2, 5) comments_label = QLabel(_("Comments:")) other_layout.addWidget(comments_label, 0, 3) self.comments_edt = QLineEdit('#') self.comments_edt.setMaximumWidth(30) other_layout.addWidget(self.comments_edt, 0, 4) self.trnsp_box = QCheckBox(_("Transpose")) #self.trnsp_box.setEnabled(False) other_layout.addWidget(self.trnsp_box, 1, 0, 2, 0) grid_layout.addWidget(other_group, 3, 0, 2, 0) opts_frame = QFrame() opts_frame.setLayout(grid_layout) data_btn.toggled.connect(opts_frame.setEnabled) data_btn.toggled.connect(self.set_as_data) code_btn.toggled.connect(self.set_as_code) # self.connect(txt_btn, SIGNAL("toggled(bool)"), # self, SLOT("is_text(bool)")) # Final layout layout = QVBoxLayout() layout.addWidget(type_frame) layout.addWidget(self.text_editor) layout.addWidget(opts_frame) self.setLayout(layout) def get_as_data(self): """Return if data type conversion""" return self._as_data def get_as_code(self): """Return if code type conversion""" return self._as_code def get_as_num(self): """Return if numeric type conversion""" return self._as_num def get_col_sep(self): """Return the column separator""" if self.tab_btn.isChecked(): return u"\t" return to_text_string(self.line_edt.text()) def get_row_sep(self): """Return the row separator""" if self.eol_btn.isChecked(): return u"\n" return to_text_string(self.line_edt_row.text()) def get_skiprows(self): """Return number of lines to be skipped""" return int(to_text_string(self.skiprows_edt.text())) def get_comments(self): """Return comment string""" return to_text_string(self.comments_edt.text()) @Slot(bool) def set_as_data(self, as_data): """Set if data type conversion""" self._as_data = as_data self.asDataChanged.emit(as_data) @Slot(bool) def set_as_code(self, as_code): """Set if code type conversion""" self._as_code = as_code
class TextEditor(BaseDialog): """Array Editor Dialog""" def __init__(self, text, title='', font=None, parent=None, readonly=False): QDialog.__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.text = None self.btn_save_and_close = None # Display text as unicode if it comes as bytes, so users see # its right representation if is_binary_string(text): self.is_binary = True text = to_text_string(text, 'utf8') else: self.is_binary = False self.layout = QVBoxLayout() self.setLayout(self.layout) # Text edit self.edit = QTextEdit(parent) self.edit.setReadOnly(readonly) self.edit.textChanged.connect(self.text_changed) self.edit.setPlainText(text) if font is None: font = get_font() self.edit.setFont(font) self.layout.addWidget(self.edit) # Buttons configuration btn_layout = QHBoxLayout() btn_layout.addStretch() if not readonly: self.btn_save_and_close = QPushButton(_('Save and Close')) self.btn_save_and_close.setDisabled(True) self.btn_save_and_close.clicked.connect(self.accept) btn_layout.addWidget(self.btn_save_and_close) self.btn_close = QPushButton(_('Close')) self.btn_close.setAutoDefault(True) self.btn_close.setDefault(True) self.btn_close.clicked.connect(self.reject) btn_layout.addWidget(self.btn_close) self.layout.addLayout(btn_layout) # Make the dialog act as a window self.setWindowFlags(Qt.Window) self.setWindowIcon(ima.icon('edit')) if title: try: unicode_title = to_text_string(title) except UnicodeEncodeError: unicode_title = u'' else: unicode_title = u'' self.setWindowTitle(_("Text editor") + \ u"%s" % (u" - " + unicode_title if unicode_title else u"")) @Slot() def text_changed(self): """Text has changed""" # Save text as bytes, if it was initially bytes if self.is_binary: self.text = to_binary_string(self.edit.toPlainText(), 'utf8') else: self.text = to_text_string(self.edit.toPlainText()) if self.btn_save_and_close: self.btn_save_and_close.setEnabled(True) self.btn_save_and_close.setAutoDefault(True) self.btn_save_and_close.setDefault(True) def get_value(self): """Return modified text""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.text def setup_and_check(self, value): """Verify if TextEditor is able to display strings passed to it.""" try: to_text_string(value, 'utf8') return True except: return False
class TextEditor(QDialog): """Array Editor Dialog""" def __init__(self, text, title='', font=None, parent=None, readonly=False, size=(400, 300)): QDialog.__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.text = None # Display text as unicode if it comes as bytes, so users see # its right representation if is_binary_string(text): self.is_binary = True text = to_text_string(text, 'utf8') else: self.is_binary = False self.layout = QVBoxLayout() self.setLayout(self.layout) # Text edit self.edit = QTextEdit(parent) self.edit.textChanged.connect(self.text_changed) self.edit.setReadOnly(readonly) self.edit.setPlainText(text) if font is None: font = get_font() self.edit.setFont(font) self.layout.addWidget(self.edit) # Buttons configuration buttons = QDialogButtonBox.Ok if not readonly: buttons = buttons | QDialogButtonBox.Cancel bbox = QDialogButtonBox(buttons) bbox.accepted.connect(self.accept) bbox.rejected.connect(self.reject) self.layout.addWidget(bbox) # Make the dialog act as a window self.setWindowFlags(Qt.Window) self.setWindowIcon(ima.icon('edit')) self.setWindowTitle(_("Text editor") + \ "%s" % (" - "+str(title) if str(title) else "")) self.resize(size[0], size[1]) def text_changed(self): """Text has changed""" # Save text as bytes, if it was initially bytes if self.is_binary: self.text = to_binary_string(self.edit.toPlainText(), 'utf8') else: self.text = to_text_string(self.edit.toPlainText()) def get_value(self): """Return modified text""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.text def setup_and_check(self, value): """Verify if TextEditor is able to display strings passed to it.""" try: to_text_string(value, 'utf8') return True except: return False