def contextMenuEvent(self, event): """Override Qt method.""" copy_action = create_action( self, _("Copy text"), icon=ima.icon('editcopy'), triggered=self.copy, shortcut=self.CONF.get_shortcut(CONF_SECTION, 'copy')) paste_action = create_action( self, _("Paste text"), icon=ima.icon('editpaste'), triggered=self.paste, shortcut=self.CONF.get_shortcut(CONF_SECTION, 'paste')) clear_action = create_action( self, _("Clear Terminal"), triggered=self.clear, shortcut=self.CONF.get_shortcut(CONF_SECTION, 'clear')) zoom_in = create_action( self, _("Zoom in"), triggered=self.increase_font, shortcut=self.CONF.get_shortcut(CONF_SECTION, 'zoom in')) zoom_out = create_action( self, _("Zoom out"), triggered=self.decrease_font, shortcut=self.CONF.get_shortcut(CONF_SECTION, 'zoom out')) menu = QMenu(self) actions = [self.pageAction(QWebEnginePage.SelectAll), copy_action, paste_action, clear_action, None, zoom_in, zoom_out] add_actions(menu, actions) menu.popup(event.globalPos()) event.accept()
def show_contextmenu(self, pos: QPoint): menu = QMenu(self) index = self.currentIndex() node = index.internalPointer() # insert item and node menu.addAction(self.insertitemAction) menu.addAction(self.insertnodeAction) # edit key if isinstance(node, (JsonNode, JsonItem)): menu.addSeparator() menu.addAction(self.editkeyAction) if isinstance(node, JsonItem): menu.addAction(self.editAction) self.editAction.setEnabled(not node.readonly) # remove if isinstance(node, (JsonNode, JsonItem)): menu.addSeparator() menu.addAction(self.removeitemAction) # properties if isinstance(node, JsonItem): menu.addSeparator() menu.addAction(self.propertiesAction) menu.setDefaultAction(self.propertiesAction) menu.popup(self.viewport().mapToGlobal(pos), self.editAction)
def showPopup(self): """Override Qt method.""" index = self.currentIndex() menu = QMenu(self) menu.setToolTipsVisible(True) actions = [] for i in range(self.count()): tip = self.itemData(i, Qt.ToolTipRole) text = self.itemText(i) action = create_action( self, text, toggled=lambda v=None, i=i: self.setCurrentIndex(i), tip=tip) actions.append(action) if i == index: action.setChecked(True) add_actions(menu, actions) menu.setFixedWidth(self.width()) bottom_left = self.contentsRect().bottomLeft() menu.popup(self.mapToGlobal(bottom_left))
class ContextMenuTabBar(QTabBar): def __init__(self): super(ContextMenuTabBar, self).__init__() self.contextMenu = QMenu() self.closeaction = QAction("&Close") self.closeaction.triggered.connect(self.close) self.closeothersaction = QAction("Close &Others") self.closeothersaction.triggered.connect(self.closeothers) self.closeallaction = QAction("Close &All") self.closeallaction.triggered.connect(self.closeall) self.contextMenu.addActions( [self.closeaction, self.closeothersaction, self.closeallaction]) self._rightclickedtab = None def close(self): self.tabCloseRequested.emit(self._rightclickedtab) def closeothers(self): for i in reversed(range(self.count())): if i != self._rightclickedtab: self.tabCloseRequested.emit(i) def closeall(self): for i in reversed(range(self.count())): self.tabCloseRequested.emit(i) def mousePressEvent(self, event: QMouseEvent): super(ContextMenuTabBar, self).mousePressEvent(event) self._rightclickedtab = self.tabAt(event.pos()) if self._rightclickedtab != -1: if event.button() == Qt.RightButton: self.contextMenu.popup(self.mapToGlobal(event.pos()))
def showPopup(self): """Override Qt method.""" index = self.currentIndex() menu = QMenu(self) # See: https://github.com/ContinuumIO/navigator/issues/1565 try: menu.setToolTipsVisible(True) except AttributeError: pass actions = [] for i in range(self.count()): tip = self.itemData(i, Qt.ToolTipRole) text = self.itemText(i) action = create_action( self, text, toggled=lambda v=None, i=i: self.setCurrentIndex(i), tip=tip ) actions.append(action) if i == index: action.setChecked(True) add_actions(menu, actions) menu.setFixedWidth(self.width()) bottom_left = self.contentsRect().bottomLeft() menu.popup(self.mapToGlobal(bottom_left))
def _customContextMenu(self, pos): menu = QMenu(self) menu.addAction(self.closeAction) menu.addSeparator() menu.addAction(self.dockProperties) menu.popup(self.mapToGlobal(pos))
def open_menu(self, position): menu = QMenu(self) preset_menu = menu.addMenu('Presets') preset_menu.addAction('New preset', self.new_preset_dialog) preset_menu.addSeparator() preset_names = CONFIG.get_header_presets() if len(preset_names) == 0: action = preset_menu.addAction('No presets') action.setEnabled(False) else: delete_menu = menu.addMenu('Delete preset') for name in preset_names: preset_menu.addAction(name, partial(self.load_preset, name)) delete_menu.addAction(name, partial(self.delete_preset, name)) menu.addSeparator() menu.addAction('New column...', self.create_new_column_dialog) if len(self.columnList.selectedIndexes()) > 0: menu.addAction('Delete selected', self.delete_selected) menu.popup(self.columnList.viewport().mapToGlobal(position))
def contextMenuEvent(self, event): """ Handle context menu events. This overrides WebView.contextMenuEvent() in order to add the actions in `self.actions` and remove the Back and Forward actions which have no meaning for the notebook widget. Parameters ---------- event : QContextMenuEvent The context menu event that needs to be handled. """ if self.actions is None: actions = [] else: actions = self.actions + [None] actions += [ self.pageAction(QWebEnginePage.SelectAll), self.pageAction(QWebEnginePage.Copy), None, self.zoom_in_action, self.zoom_out_action ] if not WEBENGINE: settings = self.page().settings() settings.setAttribute(QWebEngineSettings.DeveloperExtrasEnabled, True) actions += [None, self.pageAction(QWebEnginePage.InspectElement)] menu = QMenu(self) add_actions(menu, actions) menu.popup(event.globalPos()) event.accept()
class GenericRightClickTreeView(QTreeView2): """ creates a QTreeView with: - all the features of QTreeView2 - a right click context menu with: - Clear Active Results - Apply Results to Fringe - Apply Results to Displacement - Apply Results to Vector - Delete Case """ def __init__(self, parent, data, choices, actions, **kwargs): QTreeView2.__init__(self, parent, data, choices) # # TODO: create a menu that only has clear/normals/fringe/delete # if there is no transient result # #print(data) #print(choices) #print('kwargs', kwargs) self.right_click_menu = QMenu() def false_callback(callback_func): """dont validate the click""" #print('false') #print(' ', callback_func) #print(' ', menu) callback_func() def true_callback(callback_func): """a validated callback returns the row""" #print('true') #print(' ', callback_func) #print(' ', menu) #print(' ', self) unused_is_valid, icase = self.get_row() #print('callback =', callback_func) callback_func(icase) for actioni in actions: (right_click_msg, callback_func, validate) = actioni action = self.right_click_menu.addAction(right_click_msg) true_false_callback = true_callback if validate else false_callback trigger_func = partial(true_false_callback, callback_func) action.triggered.connect(trigger_func) #self.clear = self.right_click_menu.addAction("Clear Results...") #self.clear.triggered.connect(self.on_clear_results) def on_right_mouse_button(self): """interfaces with the right click menu""" self.set_rows() is_valid, unused_icase = self.get_row() if not is_valid: return # TODO: check if we should show disp/vector self.right_click_menu.popup(QtGui.QCursor.pos())
def customMenuRequested(self, position): """Builds a custom menu for items items""" index = self.indexAt(position) # type: QModelIndex menu = QMenu(self) if index.isValid(): if index.data(EnsembleModel.data_type_role ) == WorkspaceDataType.Ensemble: # Allow renaming the ensemble via the context menu rename_action = QAction("Rename Collection", menu) rename_action.triggered.connect(self._rename_action) menu.addAction(rename_action) # Allow toggling the active ensemble via the context menu # * there can only be at most 1 active ensemble # * there are only 0 active ensembles when data has not yet been loaded ??? # * opening data updates the active ensemble to that data is_active = index.data(EnsembleModel.active_role) active_text = "Active" toggle_active_action = QAction(active_text, menu) toggle_active_action.setCheckable(True) if is_active is True: toggle_active_action.setChecked(True) else: toggle_active_action.setChecked(False) toggle_active_action.setText(f"Not {active_text}") # Make sure to update the model with the active / deactivated ensemble toggle_active_action.toggled.connect(self._set_active_action) # Don't allow deactivating the active ensemble if there is only one loaded if self.model().rowCount() == 1: toggle_active_action.setEnabled(False) menu.addAction(toggle_active_action) menu.addSeparator() remove_text = "Remove " data_type_role = index.data(EnsembleModel.data_type_role) if data_type_role == WorkspaceDataType.Ensemble: remove_text += "Ensemble" elif data_type_role == WorkspaceDataType.Catalog: remove_text += "Catalog" elif data_type_role == WorkspaceDataType.Intent: remove_text += "Item" remove_action = QAction(remove_text, menu) remove_action.triggered.connect(self._remove_action) menu.addAction(remove_action) else: create_ensemble_action = QAction("Create New Collection", menu) create_ensemble_action.triggered.connect( self._create_ensemble_action) menu.addAction(create_ensemble_action) # Display menu wherever the user right-clicked menu.popup(self.viewport().mapToGlobal(position))
class RightClickTreeView(QTreeView2): """ creates a QTreeView with: - all the features of QTreeView2 - a right click context menu with: - Edit - TODO - Cut - TODO - Copy - TODO - Rename - TODO - Delete """ def __init__(self, parent, data, choices): QTreeView2.__init__(self, parent, data, choices) self.right_click_menu = QMenu() edit = self.right_click_menu.addAction("Edit...") cut = self.right_click_menu.addAction("Cut...") copy = self.right_click_menu.addAction("Copy...") paste = self.right_click_menu.addAction("Paste...") rename = self.right_click_menu.addAction("Rename...") delete = self.right_click_menu.addAction("Delete...") edit.triggered.connect(self.on_edit) cut.triggered.connect(self.on_cut) copy.triggered.connect(self.on_copy) paste.triggered.connect(self.on_paste) rename.triggered.connect(self.on_rename) delete.triggered.connect(self.on_delete) def on_edit(self): rows = self.find_list_index() print('edit...rows=%s' % rows) def on_cut(self): pass def on_copy(self): pass def on_paste(self): pass def on_rename(self): rows = self.find_list_index() list_data = self.data for row in rows[:-1]: list_datai = list_data[row] list_data = list_datai[2] list_datai = list_data[rows[-1]] list_data = list_datai[0] print('list_datai* = ', list_datai) #print('list_data2* = %r' % list_data) list_datai[0] = 'cat' self.parent.update_data(self.data) #self.update() #def on_delete(self): #pass def on_right_mouse_button(self): self.right_click_menu.popup(QtGui.QCursor.pos())
def context_menu_requested(self, event): """Popup context menu.""" if self.fig: pos = QPoint(event.x(), event.y()) context_menu = QMenu(self) context_menu.addAction(ima.icon('editcopy'), "Copy Image", self.copy_figure, QKeySequence(get_shortcut('plots', 'copy'))) context_menu.popup(self.mapToGlobal(pos))
class TasksWidget(QWidget): """Shows tasks table.""" stop_task = Signal(Bot, Task) force_start_task = Signal(Bot, Task) def __init__(self, bot, parent): super(TasksWidget, self).__init__(parent) self.bot = bot self.menu = None self.task_details_window = None self.widget_layout = QHBoxLayout(self) self.setLayout(self.widget_layout) self.table = TasksTableView(self) self.table.context_menu_requested.connect(self.show_context_menu) self.table.task_double_clicked.connect(self.create_task_details_window) self.model = TasksTableModel(self) self.model.setBot(bot) self.table.setModel(self.model) self.widget_layout.addWidget(self.table) @Slot(list) def update_tasks(self, data): self.model.setEvents(data) @Slot(Task) def create_task_details_window(self, task: Task): self.task_details_window = TaskDetailsWindow(self) self.task_details_window.setTask(task) self.task_details_window.show() @Slot(int) def show_context_menu(self, task_id) -> None: self.menu = QMenu(self) force_start_action = QAction('Force start', self) force_start_action.triggered.connect( lambda: self.on_force_start_action_triggered(task_id)) stop_action = QAction('Stop', self) stop_action.triggered.connect( lambda: self.on_stop_action_triggered(task_id)) self.menu.addAction(force_start_action) self.menu.addAction(stop_action) self.menu.popup(QCursor.pos()) @Slot(int) def on_force_start_action_triggered(self, task_id): task = self.table.model().getEventById(task_id) if task: self.force_start_task.emit(self.bot, task) @Slot(int) def on_stop_action_triggered(self, task_id): task = self.table.model().getEventById(task_id) if task: self.stop_task.emit(self.bot, task)
def contextMenuEvent(self, event): """Show context menu.""" menu = QMenu(self) menu.addAction(self._act_check_all) menu.addAction(self._act_uncheck_all) menu.addSeparator() menu.addAction(self._act_expand_all) menu.addAction(self._act_collapse_all) menu.popup(event.globalPos())
def context_menu_requested(self, event): """Popup context menu.""" if self.fig: pos = QPoint(event.x(), event.y()) context_menu = QMenu(self) context_menu.addAction(ima.icon('editcopy'), "Copy Image", self.copy_figure, QKeySequence( get_shortcut('plots', 'copy'))) context_menu.popup(self.mapToGlobal(pos))
def contextMenuEvent(self, event): """Show a custom context menu.""" point = event.pos() menu = QMenu("Actions", self) menu.addAction(self.blctrl_enbl_act) menu.addAction(self.blctrl_dsbl_act) menu.addSeparator() action = menu.addAction('Show Connections...') action.triggered.connect(self.show_connections) menu.popup(self.mapToGlobal(point))
def open_namespace_table_menu(self, position): menu = QMenu(self) include_children_action = menu.addAction("Selection includes children") include_children_action.setCheckable(True) if self.filter_model_enabled: include_children_action.setChecked( self.filter_model.selection_includes_children) else: include_children_action.setEnabled(False) include_children_action.triggered.connect( self.toggle_selection_includes_children) menu.popup(self.namespaceTreeView.viewport().mapToGlobal(position))
def contextMenuEvent(self, event): """Implement context menu to add auxiliary actions.""" pos = self.mapToGlobal(event.pos()) if not self.pannel.underMouse(): return menu = QMenu(self) show = menu.addAction('Show all curves') show.triggered.connect(_part(self._set_checkbox_state, True)) hide = menu.addAction('Hide all curves') hide.triggered.connect(_part(self._set_checkbox_state, False)) conn = menu.addAction('Show Connections...') conn.triggered.connect(self._show_connections) menu.popup(pos)
def open_levels_table_menu(self, position): menu = QMenu(self) enable_all_action = menu.addAction("Enable all") enable_all_action.triggered.connect(self.enable_all_levels) disable_all_action = menu.addAction("Disable all") disable_all_action.triggered.connect(self.disable_all_levels) menu.addSeparator() if self.levelsTable.selectedIndexes(): edit_action = menu.addAction("Edit selected level") edit_action.triggered.connect(self.open_level_edit_dialog) presets_dialog_action = menu.addAction("Presets") presets_dialog_action.triggered.connect(self.open_levels_preset_dialog) menu.popup(self.levelsTable.viewport().mapToGlobal(position))
def context_menu_requested(self, event): pos = QPoint(event.x(), event.y()) menu = QMenu(self) actions = [] action_title = create_action(self, _('Go to step: '), icon=QIcon()) action_title.setDisabled(True) actions.append(action_title) # actions.append(create_action(self, _(': '), icon=QIcon())) add_actions(menu, actions) menu.popup(self.mapToGlobal(pos))
def contextMenuEvent(self, event): """Show a custom context menu.""" point = event.pos() menu = QMenu("Actions", self) menu.addAction(self.turn_on_act) menu.addAction(self.turn_off_act) menu.addAction(self.pulse_on_act) menu.addAction(self.pulse_off_act) menu.addAction(self.reset_act) menu.addAction(self.set_voltage) menu.addSeparator() action = menu.addAction('Show Connections...') action.triggered.connect(self.show_connections) menu.popup(self.mapToGlobal(point))
def contextMenuEvent(self, event): menu = QMenu(self) actions = [self.pageAction(QWebEnginePage.Back), self.pageAction(QWebEnginePage.Forward), None, self.pageAction(QWebEnginePage.SelectAll), self.pageAction(QWebEnginePage.Copy), None, self.zoom_in_action, self.zoom_out_action] if DEV and not WEBENGINE: settings = self.page().settings() settings.setAttribute(QWebEngineSettings.DeveloperExtrasEnabled, True) actions += [None, self.pageAction(QWebEnginePage.InspectElement)] add_actions(menu, actions) menu.popup(event.globalPos()) event.accept()
def contextMenuEvent(self, event): """Override Qt method.""" menu = QMenu(self) actions = [self.pageAction(QWebEnginePage.SelectAll), self.copy_action, self.paste_action, None, self.zoom_in_action, self.zoom_out_action] if DEV and not WEBENGINE: settings = self.page().settings() settings.setAttribute(QWebEngineSettings.DeveloperExtrasEnabled, True) actions += [None, self.pageAction(QWebEnginePage.InspectElement)] add_actions(menu, actions) menu.popup(event.globalPos()) event.accept()
def context_menu_requested(self, event): """ """ pos = QPoint(event.x(), event.y()) menu = QMenu(self) actions = [] action_title = create_action(self, _('Go to step: '), icon=QIcon()) action_title.setDisabled(True) actions.append(action_title) # actions.append(create_action(self, _(': '), icon=QIcon())) add_actions(menu, actions) menu.popup(self.mapToGlobal(pos))
class ModulesWidget(QWidget): """Widget with table with Python modules loaded on bot.""" unload_module = Signal(Bot, Module) def __init__(self, bot, parent): super(ModulesWidget, self).__init__(parent) self.bot = bot self.menu = None self.module_code_window = None self.widget_layout = QHBoxLayout(self) self.setLayout(self.widget_layout) self.table = ModulesTableView(self) self.table.context_menu_requested.connect(self.show_context_menu) self.table.module_double_clicked.connect( self.create_module_details_window) self.model = ModulesTableModel(self) self.model.setBot(bot) self.table.setModel(self.model) self.widget_layout.addWidget(self.table) @Slot(list) def update_tasks(self, data): self.model.setEvents(data) @Slot(Module) def create_module_details_window(self, module: Module): self.module_code_window = ModuleDetailsWindow(self) self.module_code_window.set_module(module) self.module_code_window.show() @Slot(str) def show_context_menu(self, module_name) -> None: self.menu = QMenu(self) unload_action = QAction("Unload", self) unload_action.triggered.connect( lambda: self.on_unload_action_triggered(module_name)) self.menu.addAction(unload_action) self.menu.popup(QCursor.pos()) @Slot(str) def on_unload_action_triggered(self, module_name): module = self.table.model().getModuleByName(module_name) if module: self.unload_module.emit(self.bot, module)
def _showHeaderMenu(self, point): column = self.headers.logicalIndexAt(point.x()) if column == -1: return menu = QMenu(self) # Actions cols = self.table.selectionModel().selectedColumns() if len(cols) != 2 or column not in [col.column() for col in cols]: self.table.selectColumn(column) menu.aboutToHide.connect(lambda: self.table.clearSelection()) save = QAction("Save", menu) save.triggered.connect(lambda: self._saveConfiguration(column)) save_all = QAction("Save all", menu) save_all.triggered.connect(lambda: self._saveChanges()) save_all.setShortcut(QKeySequence.Save) rename = QAction("Rename", menu) rename.triggered.connect(lambda: self._renameConfiguration(column)) close = QAction("Close", menu) close.triggered.connect(lambda: self._closeConfiguration(column)) close.setShortcut(QKeySequence.Close) close_right = QAction("Close to the right", menu) close_right.triggered.connect( lambda: self._closeConfigurationsToTheRight(column)) close_others = QAction("Close other", menu) close_others.triggered.connect( lambda: self._closeOtherConfigurations(column)) close_all = QAction("Close all", menu) close_all.triggered.connect(lambda: self._closeAllConfigurations()) tune = QAction("Tune", menu) tune.triggered.connect(lambda: self._tuneConfiguration(column)) menu.addActions([save, save_all]) menu.addSeparator() menu.addActions([rename]) menu.addSeparator() menu.addActions([close, close_right, close_others, close_all]) menu.addSeparator() menu.addActions([tune]) else: bar = QAction("Interpolate", menu) bar.triggered.connect(lambda: self._barConfiguration(cols)) menu.addAction(bar) vheader_offset = self.table.verticalHeader().width() point.setX(point.x() + vheader_offset) menu.popup(self.mapToGlobal(point))
def contextMenuEvent(self, event): """Don't show some actions which have no meaning for the notebook.""" menu = QMenu(self) plugin_actions = self.parent().plugin_actions actions = plugin_actions + [None, self.pageAction(QWebEnginePage.SelectAll), self.pageAction(QWebEnginePage.Copy), None, self.zoom_in_action, self.zoom_out_action] if not WEBENGINE: settings = self.page().settings() settings.setAttribute(QWebEngineSettings.DeveloperExtrasEnabled, True) actions += [None, self.pageAction(QWebEnginePage.InspectElement)] add_actions(menu, actions) menu.popup(event.globalPos()) event.accept()
def contextMenuEvent(self, event): menu = QMenu(self) actions = [ self.pageAction(QWebEnginePage.Back), self.pageAction(QWebEnginePage.Forward), None, self.pageAction(QWebEnginePage.SelectAll), self.pageAction(QWebEnginePage.Copy), None, self.zoom_in_action, self.zoom_out_action ] if DEV: settings = self.page().settings() settings.setAttribute(QWebEngineSettings.DeveloperExtrasEnabled, True) actions += [None, self.pageAction(QWebEnginePage.InspectElement)] add_actions(menu, actions) menu.popup(event.globalPos()) event.accept()
def context_menu_requested(self, event): """Popup context menu.""" if self.fig: pos = QPoint(event.x(), event.y()) context_menu = QMenu(self) context_menu.addAction( ima.icon('filesave'), _("Save plot as..."), lambda: self.sig_save_fig_requested.emit(), QKeySequence(CONF.get_shortcut('plots', 'save'))) context_menu.addAction( ima.icon('editcopy'), _("Copy Image"), self.copy_figure, QKeySequence(CONF.get_shortcut('plots', 'copy'))) context_menu.addAction( ima.icon('editclear'), _("Remove plot"), lambda: self.sig_clear_fig_requested.emit(), QKeySequence(CONF.get_shortcut('plots', 'close'))) context_menu.popup(self.mapToGlobal(pos))
def contextMenuEvent(self, event): """Show a custom context menu.""" point = event.pos() menu = QMenu("Actions", self) menu.addAction(self.turn_on_act) menu.addAction(self.turn_off_act) menu.addAction(self.ctrlloop_close_act) menu.addAction(self.ctrlloop_open_act) menu.addAction(self.set_current_sp_act) if not self._dev_list[0].dev in ('FCH', 'FCV'): menu.addAction(self.set_slowref_act) menu.addAction(self.reset_act) menu.addAction(self.wfmupdate_on_act) menu.addAction(self.wfmupdate_off_act) menu.addAction(self.updparms_act) menu.addSeparator() action = menu.addAction('Show Connections...') action.triggered.connect(self.show_connections) menu.popup(self.mapToGlobal(point))
def open_logger_table_menu(self, position): # Needed as a workaround for when the header column count is 0 and it becomes invisible if self.table_header.column_count == 0: self.open_header_menu(position) return selected = self.loggerTable.selectedIndexes() if not selected: return row_index = selected[0] record = self.get_record(row_index) menu = QMenu(self) view_message = menu.addAction("View message") view_message.triggered.connect( partial(self.open_text_view_dialog, row_index, False)) if record.exc_text: view_traceback = menu.addAction("View traceback") view_traceback.triggered.connect( partial(self.open_text_view_dialog, row_index, True)) menu.popup(self.table_header_view.viewport().mapToGlobal(position))
def contextMenuEvent(self, event): """ Handle context menu events. This overrides WebView.contextMenuEvent() in order to add the actions in `self.actions` and remove the Back and Forward actions which have no meaning for the notebook widget. If Shift is pressed, then instead display the standard Qt context menu, per gh:spyder-ide/spyder-notebook#279 Parameters ---------- event : QContextMenuEvent The context menu event that needs to be handled. """ if QApplication.keyboardModifiers() & Qt.ShiftModifier: return QWebEngineView.contextMenuEvent(self, event) if self.actions is None: actions = [] else: actions = self.actions + [None] actions += [ self.pageAction(QWebEnginePage.SelectAll), self.pageAction(QWebEnginePage.Copy), None, self.zoom_in_action, self.zoom_out_action] if not WEBENGINE: settings = self.page().settings() settings.setAttribute(QWebEngineSettings.DeveloperExtrasEnabled, True) actions += [None, self.pageAction(QWebEnginePage.InspectElement)] menu = QMenu(self) add_actions(menu, actions) menu.popup(event.globalPos()) event.accept()
def contextMenuEvent(self, event, return_menu=False): """Show a custom context menu.""" point = event.pos() if not self.graph.geometry().contains(point) or return_menu: menu = QMenu("Actions", self) menu.addAction(self.cmd_turnon_act) menu.addAction(self.cmd_turnoff_act) menu.addAction(self.cmd_ctrlloopclose_act) menu.addAction(self.cmd_ctrlloopopen_act) if SiriusPVName(self._psnames[0]).dev in ('FCH', 'FCV'): menu.addAction(self.cmd_acqtrigrep_act) menu.addAction(self.cmd_acqtrigstart_act) menu.addAction(self.cmd_acqtrigstop_act) menu.addAction(self.cmd_acqtrigabort_act) else: menu.addAction(self.cmd_setslowref_act) menu.addAction(self.cmd_setcurrent_act) menu.addAction(self.cmd_reset_act) if return_menu: return menu menu.popup(self.mapToGlobal(point))
def contextMenuEvent(self, event): """Overload to create a custom context menu.""" widget = self.childAt(event.pos()) parent = widget.parent() grand_parent = parent.parent() if widget.objectName() == 'DCLinkContainer' or \ parent.objectName() == 'DCLinkContainer' or \ grand_parent.objectName() == 'DCLinkContainer': menu = QMenu(self) menu.addAction(self._turn_on_action) menu.addAction(self._turn_off_action) menu.addSeparator() menu.addAction(self._close_loop_action) menu.addAction(self._open_loop_action) menu.addSeparator() menu.addAction(self._set_setpoint_action) menu.addSeparator() menu.addAction(self._reset_intlk_action) menu.addSeparator() action = menu.addAction('Show Connections...') action.triggered.connect(self.show_connections) menu.popup(event.globalPos()) else: super().contextMenuEvent(event)
class TableCondaPackages(QTableView): """ """ WIDTH_TYPE = 24 WIDTH_NAME = 120 WIDTH_ACTIONS = 24 WIDTH_VERSION = 90 sig_status_updated = Signal(str, bool, list, bool) sig_conda_action_requested = Signal(str, int, str, object, object) sig_pip_action_requested = Signal(str, int) sig_actions_updated = Signal(int) sig_next_focus = Signal() sig_previous_focus = Signal() def __init__(self, parent): super(TableCondaPackages, self).__init__(parent) self._parent = parent self._searchbox = u'' self._filterbox = const.ALL self._delegate = CustomDelegate(self) self.row_count = None self._advanced_mode = True self._current_hover_row = None self._menu = None self._palette = {} # To manage icon states self._model_index_clicked = None self.valid = False self.column_ = None self.current_index = None # To prevent triggering the keyrelease after closing a dialog # but hititng enter on it self.pressed_here = False self.source_model = None self.proxy_model = None self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionMode(QAbstractItemView.SingleSelection) self.verticalHeader().hide() self.setSortingEnabled(True) self.setMouseTracking(True) self.setAlternatingRowColors(True) self._delegate.current_row = self.current_row self._delegate.current_hover_row = self.current_hover_row self._delegate.update_index = self.update self._delegate.has_focus_or_context = self.has_focus_or_context self.setItemDelegate(self._delegate) self.setShowGrid(False) self.setWordWrap(True) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.horizontalHeader().setStretchLastSection(True) # Header setup self._hheader = self.horizontalHeader() if PYQT5: self._hheader.setSectionResizeMode(self._hheader.Fixed) else: self._hheader.setResizeMode(self._hheader.Fixed) # self._hheader.setStyleSheet("""QHeaderView {border: 0px; # border-radius: 0px;}; # """) self.sortByColumn(const.COL_NAME, Qt.AscendingOrder) self.setContextMenuPolicy(Qt.CustomContextMenu) self.hide_columns() def setup_model(self, packages, data, metadata_links={}): """ """ self.proxy_model = MultiColumnSortFilterProxy(self) self.source_model = CondaPackagesModel(self, packages, data) self.proxy_model.setSourceModel(self.source_model) self.setModel(self.proxy_model) self.metadata_links = metadata_links # FIXME: packages sizes... move to a better place? packages_sizes = {} for name in packages: packages_sizes[name] = packages[name].get('size') self._packages_sizes = packages_sizes # Custom Proxy Model setup self.proxy_model.setDynamicSortFilter(True) filter_text = \ (lambda row, text, status: ( all([t in row[const.COL_NAME].lower() for t in to_text_string(text).lower().split()]) or all([t in row[const.COL_DESCRIPTION].lower() for t in to_text_string(text).split()]))) filter_status = (lambda row, text, status: to_text_string(row[const.COL_STATUS]) in to_text_string(status)) self.model().add_filter_function('status-search', filter_status) self.model().add_filter_function('text-search', filter_text) # Signals and slots self.verticalScrollBar().valueChanged.connect(self.resize_rows) self.hide_columns() self.resize_rows() self.refresh_actions() self.source_model.update_style_palette(self._palette) def update_style_palette(self, palette={}): self._palette = palette def resize_rows(self): """ """ delta_y = 10 height = self.height() y = 0 while y < height: row = self.rowAt(y) self.resizeRowToContents(row) row_height = self.rowHeight(row) self.setRowHeight(row, row_height + delta_y) y += self.rowHeight(row) + delta_y def hide_columns(self): """ """ for col in const.COLUMNS: self.showColumn(col) hide = HIDE_COLUMNS if self._advanced_mode: columns = const.ACTION_COLUMNS[:] columns.remove(const.COL_ACTION) hide += columns else: hide += [const.COL_ACTION] for col in hide: self.hideColumn(col) def filter_changed(self): """Trigger the filter""" group = self._filterbox text = self._searchbox if group in [const.ALL]: group = ''.join([to_text_string(const.INSTALLED), to_text_string(const.UPGRADABLE), to_text_string(const.NOT_INSTALLED), to_text_string(const.DOWNGRADABLE), to_text_string(const.MIXGRADABLE)]) elif group in [const.INSTALLED]: group = ''.join([to_text_string(const.INSTALLED), to_text_string(const.UPGRADABLE), to_text_string(const.DOWNGRADABLE), to_text_string(const.MIXGRADABLE)]) elif group in [const.UPGRADABLE]: group = ''.join([to_text_string(const.UPGRADABLE), to_text_string(const.MIXGRADABLE)]) elif group in [const.DOWNGRADABLE]: group = ''.join([to_text_string(const.DOWNGRADABLE), to_text_string(const.MIXGRADABLE)]) else: group = to_text_string(group) if self.proxy_model is not None: self.proxy_model.set_filter(text, group) self.resize_rows() # Update label count count = self.verticalHeader().count() if count == 0: count_text = _("0 packages available ") elif count == 1: count_text = _("1 package available ") elif count > 1: count_text = to_text_string(count) + _(" packages available ") if text != '': count_text = count_text + _('matching "{0}"').format(text) self.sig_status_updated.emit(count_text, False, [0, 0], True) def search_string_changed(self, text): """ """ text = to_text_string(text) self._searchbox = text self.filter_changed() def filter_status_changed(self, text): """ """ if text not in const.PACKAGE_STATUS: text = const.PACKAGE_STATUS[text] for key in const.COMBOBOX_VALUES: val = const.COMBOBOX_VALUES[key] if to_text_string(val) == to_text_string(text): group = val break self._filterbox = group self.filter_changed() def resizeEvent(self, event): """Override Qt method""" w = self.width() width_start = 20 width_end = width_start if self._advanced_mode: action_cols = [const.COL_ACTION] else: action_cols = [const.COL_UPGRADE, const.COL_INSTALL, const.COL_REMOVE, const.COL_DOWNGRADE] self.setColumnWidth(const.COL_START, width_start) self.setColumnWidth(const.COL_PACKAGE_TYPE, self.WIDTH_TYPE) self.setColumnWidth(const.COL_NAME, self.WIDTH_NAME) self.setColumnWidth(const.COL_VERSION, self.WIDTH_VERSION) w_new = w - (width_start + self.WIDTH_ACTIONS + self.WIDTH_TYPE + self.WIDTH_NAME + self.WIDTH_VERSION + (len(action_cols))*self.WIDTH_ACTIONS + width_end) self.setColumnWidth(const.COL_DESCRIPTION, w_new) self.setColumnWidth(const.COL_END, width_end) for col in action_cols: self.setColumnWidth(col, self.WIDTH_ACTIONS) QTableView.resizeEvent(self, event) self.resize_rows() def update_visible_rows(self): current_index = self.currentIndex() row = current_index.row() if self.proxy_model: for r in range(row - 50, row + 50): for co in const.COLUMNS: index = self.proxy_model.index(r, co) self.update(index) self.resize_rows() def current_row(self): if self._menu and self._menu.isVisible(): return self.currentIndex().row() elif self.hasFocus(): return self.currentIndex().row() else: return -1 def current_hover_row(self): return self._current_hover_row def has_focus_or_context(self): return self.hasFocus() or (self._menu and self._menu.isVisible()) def mouseMoveEvent(self, event): super(TableCondaPackages, self).mouseMoveEvent(event) pos = event.pos() self._current_hover_row = self.rowAt(pos.y()) def leaveEvent(self, event): super(TableCondaPackages, self).leaveEvent(event) self._current_hover_row = None def keyPressEvent(self, event): """ Override Qt method. """ index = self.currentIndex() key = event.key() if key in [Qt.Key_Enter, Qt.Key_Return]: # self.action_pressed(index) self.setCurrentIndex(self.proxy_model.index(index.row(), const.COL_ACTION)) self.pressed_here = True elif key in [Qt.Key_Tab]: new_row = index.row() + 1 if not self.proxy_model or new_row == self.proxy_model.rowCount(): self.sig_next_focus.emit() else: new_index = self.proxy_model.index(new_row, 0) self.setCurrentIndex(new_index) elif key in [Qt.Key_Backtab]: new_row = index.row() - 1 if new_row < 0: self.sig_previous_focus.emit() else: new_index = self.proxy_model.index(new_row, 0) self.setCurrentIndex(new_index) else: QTableView.keyPressEvent(self, event) self.update_visible_rows() def keyReleaseEvent(self, event): """Override Qt method""" QTableView.keyReleaseEvent(self, event) key = event.key() index = self.currentIndex() if key in [Qt.Key_Enter, Qt.Key_Return] and self.pressed_here: self.context_menu_requested(event) # self.action_released() elif key in [Qt.Key_Menu]: self.setCurrentIndex(self.proxy_model.index(index.row(), const.COL_ACTION)) self.context_menu_requested(event, right_click=True) self.pressed_here = False self.update_visible_rows() def mousePressEvent(self, event): """Override Qt method""" QTableView.mousePressEvent(self, event) self.current_index = self.currentIndex() column = self.current_index.column() if event.button() == Qt.LeftButton and column == const.COL_ACTION: pos = QPoint(event.x(), event.y()) index = self.indexAt(pos) self.action_pressed(index) self.context_menu_requested(event) elif event.button() == Qt.RightButton: self.context_menu_requested(event, right_click=True) self.update_visible_rows() def mouseReleaseEvent(self, event): """Override Qt method""" if event.button() == Qt.LeftButton: self.action_released() self.update_visible_rows() def action_pressed(self, index): """ DEPRECATED """ column = index.column() if self.proxy_model is not None: model_index = self.proxy_model.mapToSource(index) model = self.source_model self._model_index_clicked = model_index self.valid = True if (column == const.COL_INSTALL and model.is_installable(model_index)): model.update_row_icon(model_index.row(), const.COL_INSTALL) elif (column == const.COL_INSTALL and model.is_removable(model_index)): model.update_row_icon(model_index.row(), const.COL_REMOVE) elif ((column == const.COL_UPGRADE and model.is_upgradable(model_index)) or (column == const.COL_DOWNGRADE and model.is_downgradable(model_index))): model.update_row_icon(model_index.row(), model_index.column()) else: self._model_index_clicked = None self.valid = False def action_released(self): """ DEPRECATED """ model = self.source_model model_index = self._model_index_clicked actions = {const.COL_INSTALL: const.ACTION_INSTALL, const.COL_REMOVE: const.ACTION_REMOVE, const.COL_UPGRADE: const.ACTION_UPGRADE, const.COL_DOWNGRADE: const.ACTION_DOWNGRADE, } if model_index: column = model_index.column() if column == const.COL_INSTALL and model.is_removable(model_index): column = const.COL_REMOVE self.source_model.update_row_icon(model_index.row(), column) if self.valid: row_data = self.source_model.row(model_index.row()) type_ = row_data[const.COL_PACKAGE_TYPE] name = row_data[const.COL_NAME] version = self.source_model.get_package_version(name) versions = self.source_model.get_package_versions(name) if not versions: versions = [version] action = actions.get(column, None) if type_ == const.CONDA_PACKAGE: self.sig_conda_action_requested.emit(name, action, version, versions, self._packages_sizes) elif type_ == const.PIP_PACKAGE: self.sig_pip_action_requested.emit(name, action) else: pass def set_advanced_mode(self, value=True): self._advanced_mode = value # self.resizeEvent(None) def set_action_status(self, model_index, status=const.ACTION_NONE, version=None): self.source_model.set_action_status(model_index, status, version) self.refresh_actions() def context_menu_requested(self, event, right_click=False): """ Custom context menu. """ if self.proxy_model is None: return self._menu = QMenu(self) index = self.currentIndex() model_index = self.proxy_model.mapToSource(index) row_data = self.source_model.row(model_index.row()) column = model_index.column() name = row_data[const.COL_NAME] # package_type = row_data[const.COL_PACKAGE_TYPE] versions = self.source_model.get_package_versions(name) current_version = self.source_model.get_package_version(name) # if column in [const.COL_ACTION, const.COL_VERSION, const.COL_NAME]: if column in [const.COL_ACTION] and not right_click: is_installable = self.source_model.is_installable(model_index) is_removable = self.source_model.is_removable(model_index) is_upgradable = self.source_model.is_upgradable(model_index) action_status = self.source_model.action_status(model_index) actions = [] action_unmark = create_action( self, _('Unmark'), triggered=lambda: self.set_action_status(model_index, const.ACTION_NONE, current_version)) action_install = create_action( self, _('Mark for installation'), triggered=lambda: self.set_action_status(model_index, const.ACTION_INSTALL, versions[-1])) action_upgrade = create_action( self, _('Mark for upgrade'), triggered=lambda: self.set_action_status(model_index, const.ACTION_UPGRADE, versions[-1])) action_remove = create_action( self, _('Mark for removal'), triggered=lambda: self.set_action_status(model_index, const.ACTION_REMOVE, current_version)) version_actions = [] for version in reversed(versions): def trigger(model_index=model_index, action=const.ACTION_INSTALL, version=version): return lambda: self.set_action_status(model_index, status=action, version=version) if version == current_version: version_action = create_action( self, version, icon=QIcon(), triggered=trigger(model_index, const.ACTION_INSTALL, version)) if not is_installable: version_action.setCheckable(True) version_action.setChecked(True) version_action.setDisabled(True) elif version != current_version: if ((version in versions and versions.index(version)) > (current_version in versions and versions.index(current_version))): upgrade_or_downgrade_action = const.ACTION_UPGRADE else: upgrade_or_downgrade_action = const.ACTION_DOWNGRADE if is_installable: upgrade_or_downgrade_action = const.ACTION_INSTALL version_action = create_action( self, version, icon=QIcon(), triggered=trigger(model_index, upgrade_or_downgrade_action, version)) version_actions.append(version_action) install_versions_menu = QMenu('Mark for specific version ' 'installation', self) add_actions(install_versions_menu, version_actions) actions = [action_unmark, action_install, action_upgrade, action_remove] actions += [None, install_versions_menu] install_versions_menu.setEnabled(len(version_actions) > 1) if action_status is const.ACTION_NONE: action_unmark.setDisabled(True) action_install.setDisabled(not is_installable) action_upgrade.setDisabled(not is_upgradable) action_remove.setDisabled(not is_removable) install_versions_menu.setDisabled(False) else: action_unmark.setDisabled(False) action_install.setDisabled(True) action_upgrade.setDisabled(True) action_remove.setDisabled(True) install_versions_menu.setDisabled(True) elif right_click: license_ = row_data[const.COL_LICENSE] metadata = self.metadata_links.get(name, {}) pypi = metadata.get('pypi', '') home = metadata.get('home', '') dev = metadata.get('dev', '') docs = metadata.get('docs', '') q_pypi = QIcon(get_image_path('python.png')) q_home = QIcon(get_image_path('home.png')) q_docs = QIcon(get_image_path('conda_docs.png')) if 'git' in dev: q_dev = QIcon(get_image_path('conda_github.png')) elif 'bitbucket' in dev: q_dev = QIcon(get_image_path('conda_bitbucket.png')) else: q_dev = QIcon() if 'mit' in license_.lower(): lic = 'http://opensource.org/licenses/MIT' elif 'bsd' == license_.lower(): lic = 'http://opensource.org/licenses/BSD-3-Clause' else: lic = None actions = [] if license_ != '': actions.append(create_action(self, _('License: ' + license_), icon=QIcon(), triggered=lambda: self.open_url(lic))) actions.append(None) if pypi != '': actions.append(create_action(self, _('Python Package Index'), icon=q_pypi, triggered=lambda: self.open_url(pypi))) if home != '': actions.append(create_action(self, _('Homepage'), icon=q_home, triggered=lambda: self.open_url(home))) if docs != '': actions.append(create_action(self, _('Documentation'), icon=q_docs, triggered=lambda: self.open_url(docs))) if dev != '': actions.append(create_action(self, _('Development'), icon=q_dev, triggered=lambda: self.open_url(dev))) if actions and len(actions) > 1: # self._menu = QMenu(self) add_actions(self._menu, actions) if event.type() == QEvent.KeyRelease: rect = self.visualRect(index) global_pos = self.viewport().mapToGlobal(rect.bottomRight()) else: pos = QPoint(event.x(), event.y()) global_pos = self.viewport().mapToGlobal(pos) self._menu.popup(global_pos) def get_actions(self): if self.source_model: return self.source_model.get_actions() def clear_actions(self): index = self.currentIndex() if self.source_model: self.source_model.clear_actions() self.refresh_actions() self.setFocus() self.setCurrentIndex(index) def refresh_actions(self): if self.source_model: actions_per_package_type = self.source_model.get_actions() number_of_actions = 0 for type_ in actions_per_package_type: actions = actions_per_package_type[type_] for key in actions: data = actions[key] number_of_actions += len(data) self.sig_actions_updated.emit(number_of_actions) def open_url(self, url): """ Open link from action in default operating system browser. """ if url is None: return QDesktopServices.openUrl(QUrl(url))
class GofRView(MplGraphicsView): """ Graphics view for G(R) """ def __init__(self, parent): """ Initialization """ MplGraphicsView.__init__(self, parent) # class variable containers self._grDict = dict() self._colorList = ['black', 'red', 'blue', 'green', 'brown', 'orange'] self._colorIndex = 0 # define the event handlers to the mouse actions self._myCanvas.mpl_connect('button_press_event', self.on_mouse_press_event) # self._myCanvas.mpl_connect('button_release_event', self.on_mouse_release_event) # self._myCanvas.mpl_connect('motion_notify_event', self.on_mouse_motion) # class variable self._minY = None self._maxY = None # variable self._isLegendOn = False return def on_mouse_press_event(self, event): """ Event handling for mouse press action Args: event: Returns: """ # get the button and position information. curr_x = event.xdata curr_y = event.ydata if curr_x is None or curr_y is None: # outside of canvas return button = event.button if button == 1: # left button: no operation pass elif button == 3: # right button: # Pop-out menu self.menu = QMenu(self) if self.get_canvas().is_legend_on: # figure has legend: remove legend action1 = QAction('Hide legend', self) action1.triggered.connect(self._myCanvas.hide_legend) action2 = QAction('Legend font larger', self) action2.triggered.connect(self._myCanvas.increase_legend_font_size) action3 = QAction('Legend font smaller', self) action3.triggered.connect(self._myCanvas.decrease_legend_font_size) self.menu.addAction(action2) self.menu.addAction(action3) else: # figure does not have legend: add legend action1 = QAction('Show legend', self) action1.triggered.connect(self._myCanvas.show_legend) self.menu.addAction(action1) # pop up menu self.menu.popup(QCursor.pos()) # END-IF-ELSE return def plot_gr(self, plot_key, ws_name, plotError=False, color='black', style='.', marker=None, alpha=1., label=None): """ Plot G(r) :param plot_key: a key to the current plot :param vec_r: numpy array for R :param vec_g: numpy array for G(r) :param vec_e: numpy array for G(r) error :param plot_error: :param color: :param style: :param marker: :param alpha: :param label: label for the line to plot :return: """ # q_min = 10., q_max = 50. # alpha = 1. - (q_now - q_min)/(q_max - q_min) if not label: label = str(plot_key) line_id = self.add_plot_1d(ws_name, wkspindex=0, marker=marker, color=color, line_style=style, alpha=alpha, label=label, x_label=r'r ($\AA$)', plotError=plotError) self._colorIndex += 1 self._grDict[str(plot_key)] = line_id # check the low/max self.auto_scale_y() def _reset_y_range(self, vec_gr): """ reset the Y range :param vec_gr: :return: """ this_min = min(vec_gr) this_max = max(vec_gr) if self._minY is None or this_min < self._minY: self._minY = this_min if self._maxY is None or this_max > self._maxY: self._maxY = this_max return def _auto_rescale_y(self): """ :return: """ if self._minY is None or self._maxY is None: return delta_y = self._maxY - self._minY lower_boundary = self._minY - delta_y * 0.05 upper_boundary = self._maxY + delta_y * 0.05 self.setXYLimit(ymin=lower_boundary, ymax=upper_boundary) return def has_gr(self, gr_ws_name): """Check whether a plot of G(r) exists on the canvas :param gr_ws_name: :return: """ return gr_ws_name in self._grDict def get_current_grs(self): """ list all the G(r) plotted on the figure now :return: """ return list(self._grDict.keys()) def remove_gr(self, plot_key): """Remove a plotted G(r) from canvas :param plot_key: key to locate the 1-D plot on canvas :return: boolean, string (as error message) """ # check assert isinstance(plot_key, str), 'Key for the plot must be a string but not %s.' % str(type(plot_key)) if plot_key not in self._grDict: return False, 'Workspace %s cannot be found in GofR dictionary of canvas' % plot_key # get line ID line_id = self._grDict[plot_key] # remove from plot self.remove_line(line_id) # clean G(r) plot del self._grDict[plot_key] # reset min and max self._minY = None self._maxY = None return def reset_color(self): """Reset color scheme :return: """ self._colorIndex = 0 def reset(self): """ Reset the canvas by deleting all lines and clean the dictionary Returns: """ # remove all lines and reset marker/color default sequence self.clear_all_lines() self.reset_line_color_marker_index() self._colorIndex = 0 # clean dictionary self._grDict.clear() return def update_gr(self, plot_key, ws_name, plotError=False): """update the value of an existing G(r) :param plot_key: :param vec_r: :param vec_g: :param vec_ge: :return: """ # check existence if plot_key not in self._grDict: raise RuntimeError('Plot with key/workspace name {0} does not exist on plot. Current plots are ' '{1}'.format(plot_key, list(self._grDict.keys()))) # update line_key = self._grDict[plot_key] self.updateLine(ikey=line_key, wkspname=ws_name) # update range self.auto_scale_y() return
class PreviewTable(QTableView): """Import wizard preview widget""" def __init__(self, parent): QTableView.__init__(self, parent) self._model = None # Setting up actions self.date_dayfirst_action = create_action(self, "dayfirst", triggered=ft_partial(self.parse_to_type, atype="date", dayfirst=True)) self.date_monthfirst_action = create_action(self, "monthfirst", triggered=ft_partial(self.parse_to_type, atype="date", dayfirst=False)) self.perc_action = create_action(self, "perc", triggered=ft_partial(self.parse_to_type, atype="perc")) self.acc_action = create_action(self, "account", triggered=ft_partial(self.parse_to_type, atype="account")) self.str_action = create_action(self, "unicode", triggered=ft_partial(self.parse_to_type, atype="unicode")) self.int_action = create_action(self, "int", triggered=ft_partial(self.parse_to_type, atype="int")) self.float_action = create_action(self, "float", triggered=ft_partial(self.parse_to_type, atype="float")) # Setting up menus self.date_menu = QMenu() self.date_menu.setTitle("Date") add_actions( self.date_menu, (self.date_dayfirst_action, self.date_monthfirst_action)) self.parse_menu = QMenu(self) self.parse_menu.addMenu(self.date_menu) add_actions( self.parse_menu, (self.perc_action, self.acc_action)) self.parse_menu.setTitle("String to") self.opt_menu = QMenu(self) self.opt_menu.addMenu(self.parse_menu) add_actions( self.opt_menu, (self.str_action, self.int_action, self.float_action)) def _shape_text(self, text, colsep=u"\t", rowsep=u"\n", transpose=False, skiprows=0, comments='#'): """Decode the shape of the given text""" assert colsep != rowsep out = [] text_rows = text.split(rowsep)[skiprows:] for row in text_rows: stripped = to_text_string(row).strip() if len(stripped) == 0 or stripped.startswith(comments): continue line = to_text_string(row).split(colsep) line = [try_to_parse(to_text_string(x)) for x in line] out.append(line) # Replace missing elements with np.nan's or None's if programs.is_module_installed('numpy'): from numpy import nan out = list(zip_longest(*out, fillvalue=nan)) else: out = list(zip_longest(*out, fillvalue=None)) # Tranpose the last result to get the expected one out = [[r[col] for r in out] for col in range(len(out[0]))] if transpose: return [[r[col] for r in out] for col in range(len(out[0]))] return out def get_data(self): """Return model data""" if self._model is None: return None return self._model.get_data() def process_data(self, text, colsep=u"\t", rowsep=u"\n", transpose=False, skiprows=0, comments='#'): """Put data into table model""" data = self._shape_text(text, colsep, rowsep, transpose, skiprows, comments) self._model = PreviewTableModel(data) self.setModel(self._model) @Slot() def parse_to_type(self,**kwargs): """Parse to a given type""" indexes = self.selectedIndexes() if not indexes: return for index in indexes: self.model().parse_data_type(index, **kwargs) def contextMenuEvent(self, event): """Reimplement Qt method""" self.opt_menu.popup(event.globalPos()) event.accept()
class ShellBaseWidget(ConsoleBaseWidget, SaveHistoryMixin): """ Shell base widget """ redirect_stdio = Signal(bool) sig_keyboard_interrupt = Signal() execute = Signal(str) append_to_history = Signal(str, str) def __init__(self, parent, history_filename, profile=False): """ parent : specifies the parent widget """ ConsoleBaseWidget.__init__(self, parent) SaveHistoryMixin.__init__(self) # Prompt position: tuple (line, index) self.current_prompt_pos = None self.new_input_line = True # History self.histidx = None self.hist_wholeline = False assert is_text_string(history_filename) self.history_filename = history_filename self.history = self.load_history() # Session self.historylog_filename = CONF.get('main', 'historylog_filename', get_conf_path('history.log')) # Context menu self.menu = None self.setup_context_menu() # Simple profiling test self.profile = profile # Buffer to increase performance of write/flush operations self.__buffer = [] self.__timestamp = 0.0 self.__flushtimer = QTimer(self) self.__flushtimer.setSingleShot(True) self.__flushtimer.timeout.connect(self.flush) # Give focus to widget self.setFocus() # Cursor width self.setCursorWidth( CONF.get('main', 'cursor/width') ) def toggle_wrap_mode(self, enable): """Enable/disable wrap mode""" self.set_wrap_mode('character' if enable else None) def set_font(self, font): """Set shell styles font""" self.setFont(font) self.set_pythonshell_font(font) cursor = self.textCursor() cursor.select(QTextCursor.Document) charformat = QTextCharFormat() charformat.setFontFamily(font.family()) charformat.setFontPointSize(font.pointSize()) cursor.mergeCharFormat(charformat) #------ Context menu def setup_context_menu(self): """Setup shell context menu""" self.menu = QMenu(self) self.cut_action = create_action(self, _("Cut"), shortcut=keybinding('Cut'), icon=ima.icon('editcut'), triggered=self.cut) self.copy_action = create_action(self, _("Copy"), shortcut=keybinding('Copy'), icon=ima.icon('editcopy'), triggered=self.copy) paste_action = create_action(self, _("Paste"), shortcut=keybinding('Paste'), icon=ima.icon('editpaste'), triggered=self.paste) save_action = create_action(self, _("Save history log..."), icon=ima.icon('filesave'), tip=_("Save current history log (i.e. all " "inputs and outputs) in a text file"), triggered=self.save_historylog) self.delete_action = create_action(self, _("Delete"), shortcut=keybinding('Delete'), icon=ima.icon('editdelete'), triggered=self.delete) selectall_action = create_action(self, _("Select All"), shortcut=keybinding('SelectAll'), icon=ima.icon('selectall'), triggered=self.selectAll) add_actions(self.menu, (self.cut_action, self.copy_action, paste_action, self.delete_action, None, selectall_action, None, save_action) ) def contextMenuEvent(self, event): """Reimplement Qt method""" state = self.has_selected_text() self.copy_action.setEnabled(state) self.cut_action.setEnabled(state) self.delete_action.setEnabled(state) self.menu.popup(event.globalPos()) event.accept() #------ Input buffer def get_current_line_from_cursor(self): return self.get_text('cursor', 'eof') def _select_input(self): """Select current line (without selecting console prompt)""" line, index = self.get_position('eof') if self.current_prompt_pos is None: pline, pindex = line, index else: pline, pindex = self.current_prompt_pos self.setSelection(pline, pindex, line, index) @Slot() def clear_line(self): """Clear current line (without clearing console prompt)""" if self.current_prompt_pos is not None: self.remove_text(self.current_prompt_pos, 'eof') @Slot() def clear_terminal(self): """ Clear terminal window Child classes reimplement this method to write prompt """ self.clear() # The buffer being edited def _set_input_buffer(self, text): """Set input buffer""" if self.current_prompt_pos is not None: self.replace_text(self.current_prompt_pos, 'eol', text) else: self.insert(text) self.set_cursor_position('eof') def _get_input_buffer(self): """Return input buffer""" input_buffer = '' if self.current_prompt_pos is not None: input_buffer = self.get_text(self.current_prompt_pos, 'eol') input_buffer = input_buffer.replace(os.linesep, '\n') return input_buffer input_buffer = Property("QString", _get_input_buffer, _set_input_buffer) #------ Prompt def new_prompt(self, prompt): """ Print a new prompt and save its (line, index) position """ if self.get_cursor_line_column()[1] != 0: self.write('\n') self.write(prompt, prompt=True) # now we update our cursor giving end of prompt self.current_prompt_pos = self.get_position('cursor') self.ensureCursorVisible() self.new_input_line = False def check_selection(self): """ Check if selected text is r/w, otherwise remove read-only parts of selection """ if self.current_prompt_pos is None: self.set_cursor_position('eof') else: self.truncate_selection(self.current_prompt_pos) #------ Copy / Keyboard interrupt @Slot() def copy(self): """Copy text to clipboard... or keyboard interrupt""" if self.has_selected_text(): ConsoleBaseWidget.copy(self) elif not sys.platform == 'darwin': self.interrupt() def interrupt(self): """Keyboard interrupt""" self.sig_keyboard_interrupt.emit() @Slot() def cut(self): """Cut text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.cut(self) @Slot() def delete(self): """Remove selected text""" self.check_selection() if self.has_selected_text(): ConsoleBaseWidget.remove_selected_text(self) @Slot() def save_historylog(self): """Save current history log (all text in console)""" title = _("Save history log") self.redirect_stdio.emit(False) filename, _selfilter = getsavefilename(self, title, self.historylog_filename, "%s (*.log)" % _("History logs")) self.redirect_stdio.emit(True) if filename: filename = osp.normpath(filename) try: encoding.write(to_text_string(self.get_text_with_eol()), filename) self.historylog_filename = filename CONF.set('main', 'historylog_filename', filename) except EnvironmentError as error: QMessageBox.critical(self, title, _("<b>Unable to save file '%s'</b>" "<br><br>Error message:<br>%s" ) % (osp.basename(filename), to_text_string(error))) #------ Basic keypress event handler def on_enter(self, command): """on_enter""" self.execute_command(command) def execute_command(self, command): self.execute.emit(command) self.add_to_history(command) self.new_input_line = True def on_new_line(self): """On new input line""" self.set_cursor_position('eof') self.current_prompt_pos = self.get_position('cursor') self.new_input_line = False @Slot() def paste(self): """Reimplemented slot to handle multiline paste action""" if self.new_input_line: self.on_new_line() ConsoleBaseWidget.paste(self) def keyPressEvent(self, event): """ Reimplement Qt Method Basic keypress event handler (reimplemented in InternalShell to add more sophisticated features) """ if self.preprocess_keyevent(event): # Event was accepted in self.preprocess_keyevent return self.postprocess_keyevent(event) def preprocess_keyevent(self, event): """Pre-process keypress event: return True if event is accepted, false otherwise""" # Copy must be done first to be able to copy read-only text parts # (otherwise, right below, we would remove selection # if not on current line) ctrl = event.modifiers() & Qt.ControlModifier meta = event.modifiers() & Qt.MetaModifier # meta=ctrl in OSX if event.key() == Qt.Key_C and \ ((Qt.MetaModifier | Qt.ControlModifier) & event.modifiers()): if meta and sys.platform == 'darwin': self.interrupt() elif ctrl: self.copy() event.accept() return True if self.new_input_line and ( len(event.text()) or event.key() in \ (Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right) ): self.on_new_line() return False def postprocess_keyevent(self, event): """Post-process keypress event: in InternalShell, this is method is called when shell is ready""" event, text, key, ctrl, shift = restore_keyevent(event) # Is cursor on the last line? and after prompt? if len(text): #XXX: Shouldn't it be: `if len(unicode(text).strip(os.linesep))` ? if self.has_selected_text(): self.check_selection() self.restrict_cursor_position(self.current_prompt_pos, 'eof') cursor_position = self.get_position('cursor') if key in (Qt.Key_Return, Qt.Key_Enter): if self.is_cursor_on_last_line(): self._key_enter() # add and run selection else: self.insert_text(self.get_selected_text(), at_end=True) elif key == Qt.Key_Insert and not shift and not ctrl: self.setOverwriteMode(not self.overwriteMode()) elif key == Qt.Key_Delete: if self.has_selected_text(): self.check_selection() self.remove_selected_text() elif self.is_cursor_on_last_line(): self.stdkey_clear() elif key == Qt.Key_Backspace: self._key_backspace(cursor_position) elif key == Qt.Key_Tab: self._key_tab() elif key == Qt.Key_Space and ctrl: self._key_ctrl_space() elif key == Qt.Key_Left: if self.current_prompt_pos == cursor_position: # Avoid moving cursor on prompt return method = self.extend_selection_to_next if shift \ else self.move_cursor_to_next method('word' if ctrl else 'character', direction='left') elif key == Qt.Key_Right: if self.is_cursor_at_end(): return method = self.extend_selection_to_next if shift \ else self.move_cursor_to_next method('word' if ctrl else 'character', direction='right') elif (key == Qt.Key_Home) or ((key == Qt.Key_Up) and ctrl): self._key_home(shift, ctrl) elif (key == Qt.Key_End) or ((key == Qt.Key_Down) and ctrl): self._key_end(shift, ctrl) elif key == Qt.Key_Up: if not self.is_cursor_on_last_line(): self.set_cursor_position('eof') y_cursor = self.get_coordinates(cursor_position)[1] y_prompt = self.get_coordinates(self.current_prompt_pos)[1] if y_cursor > y_prompt: self.stdkey_up(shift) else: self.browse_history(backward=True) elif key == Qt.Key_Down: if not self.is_cursor_on_last_line(): self.set_cursor_position('eof') y_cursor = self.get_coordinates(cursor_position)[1] y_end = self.get_coordinates('eol')[1] if y_cursor < y_end: self.stdkey_down(shift) else: self.browse_history(backward=False) elif key in (Qt.Key_PageUp, Qt.Key_PageDown): #XXX: Find a way to do this programmatically instead of calling # widget keyhandler (this won't work if the *event* is coming from # the event queue - i.e. if the busy buffer is ever implemented) ConsoleBaseWidget.keyPressEvent(self, event) elif key == Qt.Key_Escape and shift: self.clear_line() elif key == Qt.Key_Escape: self._key_escape() elif key == Qt.Key_L and ctrl: self.clear_terminal() elif key == Qt.Key_V and ctrl: self.paste() elif key == Qt.Key_X and ctrl: self.cut() elif key == Qt.Key_Z and ctrl: self.undo() elif key == Qt.Key_Y and ctrl: self.redo() elif key == Qt.Key_A and ctrl: self.selectAll() elif key == Qt.Key_Question and not self.has_selected_text(): self._key_question(text) elif key == Qt.Key_ParenLeft and not self.has_selected_text(): self._key_parenleft(text) elif key == Qt.Key_Period and not self.has_selected_text(): self._key_period(text) elif len(text) and not self.isReadOnly(): self.hist_wholeline = False self.insert_text(text) self._key_other(text) else: # Let the parent widget handle the key press event ConsoleBaseWidget.keyPressEvent(self, event) #------ Key handlers def _key_enter(self): command = self.input_buffer self.insert_text('\n', at_end=True) self.on_enter(command) self.flush() def _key_other(self, text): raise NotImplementedError def _key_backspace(self, cursor_position): raise NotImplementedError def _key_tab(self): raise NotImplementedError def _key_ctrl_space(self): raise NotImplementedError def _key_home(self, shift, ctrl): if self.is_cursor_on_last_line(): self.stdkey_home(shift, ctrl, self.current_prompt_pos) def _key_end(self, shift, ctrl): if self.is_cursor_on_last_line(): self.stdkey_end(shift, ctrl) def _key_pageup(self): raise NotImplementedError def _key_pagedown(self): raise NotImplementedError def _key_escape(self): raise NotImplementedError def _key_question(self, text): raise NotImplementedError def _key_parenleft(self, text): raise NotImplementedError def _key_period(self, text): raise NotImplementedError #------ History Management def load_history(self): """Load history from a .py file in user home directory""" if osp.isfile(self.history_filename): rawhistory, _ = encoding.readlines(self.history_filename) rawhistory = [line.replace('\n', '') for line in rawhistory] if rawhistory[1] != self.INITHISTORY[1]: rawhistory[1] = self.INITHISTORY[1] else: rawhistory = self.INITHISTORY history = [line for line in rawhistory \ if line and not line.startswith('#')] # Truncating history to X entries: while len(history) >= CONF.get('historylog', 'max_entries'): del history[0] while rawhistory[0].startswith('#'): del rawhistory[0] del rawhistory[0] # Saving truncated history: encoding.writelines(rawhistory, self.history_filename) return history def browse_history(self, backward): """Browse history""" if self.is_cursor_before('eol') and self.hist_wholeline: self.hist_wholeline = False tocursor = self.get_current_line_to_cursor() text, self.histidx = self.__find_in_history(tocursor, self.histidx, backward) if text is not None: if self.hist_wholeline: self.clear_line() self.insert_text(text) else: cursor_position = self.get_position('cursor') # Removing text from cursor to the end of the line self.remove_text('cursor', 'eol') # Inserting history text self.insert_text(text) self.set_cursor_position(cursor_position) def __find_in_history(self, tocursor, start_idx, backward): """Find text 'tocursor' in history, from index 'start_idx'""" if start_idx is None: start_idx = len(self.history) # Finding text in history step = -1 if backward else 1 idx = start_idx if len(tocursor) == 0 or self.hist_wholeline: idx += step if idx >= len(self.history) or len(self.history) == 0: return "", len(self.history) elif idx < 0: idx = 0 self.hist_wholeline = True return self.history[idx], idx else: for index in range(len(self.history)): idx = (start_idx+step*(index+1)) % len(self.history) entry = self.history[idx] if entry.startswith(tocursor): return entry[len(tocursor):], idx else: return None, start_idx #------ Simulation standards input/output def write_error(self, text): """Simulate stderr""" self.flush() self.write(text, flush=True, error=True) if DEBUG: STDERR.write(text) def write(self, text, flush=False, error=False, prompt=False): """Simulate stdout and stderr""" if prompt: self.flush() if not is_string(text): # This test is useful to discriminate QStrings from decoded str text = to_text_string(text) self.__buffer.append(text) ts = time.time() if flush or prompt: self.flush(error=error, prompt=prompt) elif ts - self.__timestamp > 0.05: self.flush(error=error) self.__timestamp = ts # Timer to flush strings cached by last write() operation in series self.__flushtimer.start(50) def flush(self, error=False, prompt=False): """Flush buffer, write text to console""" # Fix for Issue 2452 if PY3: try: text = "".join(self.__buffer) except TypeError: text = b"".join(self.__buffer) try: text = text.decode( locale.getdefaultlocale()[1] ) except: pass else: text = "".join(self.__buffer) self.__buffer = [] self.insert_text(text, at_end=True, error=error, prompt=prompt) QCoreApplication.processEvents() self.repaint() # Clear input buffer: self.new_input_line = True #------ Text Insertion def insert_text(self, text, at_end=False, error=False, prompt=False): """ Insert text at the current cursor position or at the end of the command line """ if at_end: # Insert text at the end of the command line self.append_text_to_shell(text, error, prompt) else: # Insert text at current cursor position ConsoleBaseWidget.insert_text(self, text) #------ Re-implemented Qt Methods def focusNextPrevChild(self, next): """ Reimplemented to stop Tab moving to the next window """ if next: return False return ConsoleBaseWidget.focusNextPrevChild(self, next) #------ Drag and drop def dragEnterEvent(self, event): """Drag and Drop - Enter event""" event.setAccepted(event.mimeData().hasFormat("text/plain")) def dragMoveEvent(self, event): """Drag and Drop - Move event""" if (event.mimeData().hasFormat("text/plain")): event.setDropAction(Qt.MoveAction) event.accept() else: event.ignore() def dropEvent(self, event): """Drag and Drop - Drop event""" if (event.mimeData().hasFormat("text/plain")): text = to_text_string(event.mimeData().text()) if self.new_input_line: self.on_new_line() self.insert_text(text, at_end=True) self.setFocus() event.setDropAction(Qt.MoveAction) event.accept() else: event.ignore() def drop_pathlist(self, pathlist): """Drop path list""" raise NotImplementedError
class SofQView(MplGraphicsView): """ Graphics view for S(Q) """ # boundary moving signal (1) int for left/right boundary indicator (2) boundaryMoveSignal = Signal(int, float) # resolution of boundary indicator to be selected IndicatorResolution = 0.01 def __init__(self, parent): """ Initialization :param parent:t """ self._myParent = parent MplGraphicsView.__init__(self, parent) # declare event handling to indicators self._myCanvas.mpl_connect('button_press_event', self.on_mouse_press_event) self._myCanvas.mpl_connect('button_release_event', self.on_mouse_release_event) self._myCanvas.mpl_connect('motion_notify_event', self.on_mouse_motion) self._mainApp = None # dictionary to record all the plots, key: (usually) SofQ's name, value: plot ID self._sqLineDict = dict() # S(Q) plot's information including color, marker and etc. self._sqPlotInfoDict = dict() # list of SofQ that are plot on the canvas self._shownSQNameList = list() # link signal # self.boundaryMoveSignal.connect(self._myParent.update_sq_boundary) # declare class variables for moving boundary self._showBoundary = False self._leftID = None self._rightID = None self._selectedBoundary = 0 self._prevCursorPos = None return def get_plot_info(self, sofq_name): """ get the information of a plot including color, marker and etc. :param sofq_name: :return: """ # check assert isinstance(sofq_name, str), 'SofQ {0} must be a string but not a {1}'.format(sofq_name, type(sofq_name)) if sofq_name not in self._sqPlotInfoDict: raise RuntimeError('SofQ-view does not have S(Q) plot {0}'.format(sofq_name)) # return return self._sqPlotInfoDict[sofq_name] def get_shown_sq_names(self): """ get the names of S(q) workspaces that are shown on the canvas Returns ------- """ return self._shownSQNameList[:] def is_boundary_shown(self): """ Returns ------- """ return self._showBoundary def is_on_canvas(self, sq_name): """ check whether an S(Q) is on canvas now Args: sq_name: Returns: boolean. True if on canvas; Otherwise False """ # check input assert isinstance(sq_name, str), 'SofQ name %s must be a string but not of type %s.' \ '' % (str(sq_name), str(type(sq_name))) # return return sq_name in self._sqLineDict def move_left_indicator(self, displacement, relative): """ Args: displacement: relative: Returns: """ # check assert isinstance(displacement, float) assert isinstance(relative, bool) if relative: self.move_indicator(self._leftID, displacement) else: self.set_indicator_position(self._leftID, displacement, 0) return def move_right_indicator(self, displacement, relative): """ Args: displacement: relative: Returns: """ # check assert isinstance(displacement, float) assert isinstance(relative, bool) if relative: self.move_indicator(self._rightID, displacement) else: self.set_indicator_position(self._rightID, displacement, 0) return def on_mouse_motion(self, event): """ Returns ------- """ # ignore if boundary is not shown if not self._showBoundary: return # ignore if no boundary is selected if self._selectedBoundary == 0: return elif self._selectedBoundary > 2: raise RuntimeError('Impossible to have selected boundary mode %d' % self._selectedBoundary) cursor_pos = event.xdata # ignore if the cursor is out of canvas if cursor_pos is None: return cursor_displace = cursor_pos - self._prevCursorPos left_bound_pos = self.get_indicator_position(self._leftID)[0] right_bound_pos = self.get_indicator_position(self._rightID)[0] x_range = self.getXLimit() resolution = (x_range[1] - x_range[0]) * self.IndicatorResolution if self._selectedBoundary == 1: # left boundary new_left_bound = left_bound_pos + cursor_displace # return if the left boundary is too close to right if new_left_bound > right_bound_pos - resolution * 5: return # move left boundary self.move_indicator(self._leftID, cursor_displace, 0) # signal main self.boundaryMoveSignal.emit(1, new_left_bound) else: # right boundary new_right_bound = right_bound_pos + cursor_displace # return if the right boundary is too close or left to the left boundary if new_right_bound < left_bound_pos + resolution * 5: return # move right boundary self.move_indicator(self._rightID, cursor_displace, 0) # emit signal to the main app self.boundaryMoveSignal.emit(2, new_right_bound) # update cursor position self._prevCursorPos = cursor_pos return def on_mouse_press_event(self, event): """ Handle mouse pressing event (1) left mouse: in show-boundary mode, check the action to select a boundary indicator (2) right mouse: pop up the menu Returns ------- """ # get the button button = event.button if button == 3: # right button: # Pop-out menu self.menu = QMenu(self) if self.get_canvas().is_legend_on: # figure has legend: remove legend action1 = QAction('Hide legend', self) action1.triggered.connect(self._myCanvas.hide_legend) action2 = QAction('Legend font larger', self) action2.triggered.connect(self._myCanvas.increase_legend_font_size) action3 = QAction('Legend font smaller', self) action3.triggered.connect(self._myCanvas.decrease_legend_font_size) self.menu.addAction(action2) self.menu.addAction(action3) else: # figure does not have legend: add legend action1 = QAction('Show legend', self) action1.triggered.connect(self._myCanvas.show_legend) self.menu.addAction(action1) # pop up menu self.menu.popup(QCursor.pos()) return # END-IF # ignore if boundary is not shown and the pressed mouse button is left or middle if not self._showBoundary: return # get mouse cursor x position mouse_x_pos = event.xdata if mouse_x_pos is None: return else: self._prevCursorPos = mouse_x_pos # get absolute resolution x_range = self.getXLimit() resolution = (x_range[1] - x_range[0]) * self.IndicatorResolution # see whether it is close enough to any boundary left_bound_pos = self.get_indicator_position(self._leftID)[0] right_bound_pos = self.get_indicator_position(self._rightID)[0] if abs(mouse_x_pos - left_bound_pos) < resolution: self._selectedBoundary = 1 elif abs(mouse_x_pos - right_bound_pos) < resolution: self._selectedBoundary = 2 else: self._selectedBoundary = 0 # END-IF-ELSE return def on_mouse_release_event(self, event): """ handling the event that mouse is released The operations include setting some flags' values :param event: :return: """ # ignore if boundary is not shown if not self._showBoundary: return # get mouse cursor position self._prevCursorPos = event.xdata self._prevCursorPos = None self._selectedBoundary = 0 return def plot_sq(self, ws_name, sq_y_label=None, reset_color_mark=None, color=None, marker=None, plotError=False): """Plot S(Q) :param sq_name: :param sq_y_label: label for Y-axis :param reset_color_mark: boolean to reset color marker :param color: :param color_marker: :return: """ # check whether it is a new plot or an update if ws_name in self._sqLineDict: # exiting S(q) workspace, do update sq_key = self._sqLineDict[ws_name] self.updateLine(ikey=sq_key, wkspname=ws_name, wkspindex=0) else: # new S(Q) plot on the canvas assert isinstance(sq_y_label, str), 'S(Q) label {0} must be a string but not a {1}.' \ ''.format(sq_y_label, type(sq_y_label)) # define color if color is None: if reset_color_mark: self.reset_line_color_marker_index() marker, color = self.getNextLineMarkerColorCombo() else: marker = None # plot plot_id = self.add_plot_1d(ws_name, wkspindex=0, color=color, x_label='Q', y_label=sq_y_label, marker=marker, label=ws_name, plotError=plotError) self._sqLineDict[ws_name] = plot_id self._sqPlotInfoDict[ws_name] = color, marker if ws_name not in self._shownSQNameList: self._shownSQNameList.append(ws_name) # auto scale self.auto_scale_y(room_percent=0.05, lower_boundary=0.) return def set_main(self, main_app): """ Returns ------- """ self._mainApp = main_app # link signal self.boundaryMoveSignal.connect(self._mainApp.update_sq_boundary) return def remove_sq(self, sq_ws_name): """ Remove 1 S(q) line from canvas Args: sq_ws_name: workspace name as plot key Returns: """ # check whether S(Q) does exist assert isinstance(sq_ws_name, str), 'S(Q) workspace name {0} must be a string but not a {1}.' \ ''.format(sq_ws_name, type(sq_ws_name)) if sq_ws_name not in self._sqLineDict: raise RuntimeError('key (SofQ name) {0} does not exist on the S(Q) canvas.'.format(sq_ws_name)) # retrieve the plot and remove it from the dictionary plot_id = self._sqLineDict[sq_ws_name] sq_color, sq_marker = self._sqPlotInfoDict[sq_ws_name] del self._sqLineDict[sq_ws_name] del self._sqPlotInfoDict[sq_ws_name] # delete from canvas self.remove_line(plot_id) # delete from on-show S(q) list self._shownSQNameList.remove(sq_ws_name) return sq_color, sq_marker def reset(self): """ Reset the canvas including removing all the 1-D plots and boundary indicators Returns: """ # clear the dictionary and on-show Sq list self._sqLineDict.clear() self._sqPlotInfoDict.clear() self._shownSQNameList = list() # clear the image and reset the marker/color scheme self.clear_all_lines() self.reset_line_color_marker_index() # clear the boundary flag self._showBoundary = False return def toggle_boundary(self, q_left, q_right): """ Turn on or off the left and right boundary to select Q-range Parameters ---------- q_left :: q_right :: Returns ------- """ # check assert isinstance(q_left, float) and isinstance(q_right, float) assert q_left < q_right if self._showBoundary: # Q-boundary indicator is on. turn off self.remove_indicator(self._leftID) self.remove_indicator(self._rightID) self._leftID = None self._rightID = None self._showBoundary = False else: self._leftID = self.add_vertical_indicator(q_left, 'red') self._rightID = self.add_vertical_indicator(q_right, 'red') self._showBoundary = True # reset the x-range x_range = self.getXLimit() if x_range[0] > q_left - 1: self.setXYLimit(xmin=q_left-1) # END-IF-ELSE (show boundary) return
class OneColumnTree(QTreeWidget): """One-column tree widget with context menu, ...""" def __init__(self, parent): QTreeWidget.__init__(self, parent) self.setItemsExpandable(True) self.setColumnCount(1) self.itemActivated.connect(self.activated) self.itemClicked.connect(self.clicked) # Setup context menu self.menu = QMenu(self) self.collapse_all_action = None self.collapse_selection_action = None self.expand_all_action = None self.expand_selection_action = None self.common_actions = self.setup_common_actions() self.__expanded_state = None self.itemSelectionChanged.connect(self.item_selection_changed) self.item_selection_changed() def activated(self, item): """Double-click event""" raise NotImplementedError def clicked(self, item): pass def set_title(self, title): self.setHeaderLabels([title]) def setup_common_actions(self): """Setup context menu common actions""" self.collapse_all_action = create_action(self, text=_('Collapse all'), icon=ima.icon('collapse'), triggered=self.collapseAll) self.expand_all_action = create_action(self, text=_('Expand all'), icon=ima.icon('expand'), triggered=self.expandAll) self.restore_action = create_action(self, text=_('Restore'), tip=_('Restore original tree layout'), icon=ima.icon('restore'), triggered=self.restore) self.collapse_selection_action = create_action(self, text=_('Collapse selection'), icon=ima.icon('collapse_selection'), triggered=self.collapse_selection) self.expand_selection_action = create_action(self, text=_('Expand selection'), icon=ima.icon('expand_selection'), triggered=self.expand_selection) return [self.collapse_all_action, self.expand_all_action, self.restore_action, None, self.collapse_selection_action, self.expand_selection_action] def update_menu(self): self.menu.clear() items = self.selectedItems() actions = self.get_actions_from_items(items) if actions: actions.append(None) actions += self.common_actions add_actions(self.menu, actions) def get_actions_from_items(self, items): # Right here: add other actions if necessary # (reimplement this method) return [] @Slot() def restore(self): self.collapseAll() for item in self.get_top_level_items(): self.expandItem(item) def is_item_expandable(self, item): """To be reimplemented in child class See example in project explorer widget""" return True def __expand_item(self, item): if self.is_item_expandable(item): self.expandItem(item) for index in range(item.childCount()): child = item.child(index) self.__expand_item(child) @Slot() def expand_selection(self): items = self.selectedItems() if not items: items = self.get_top_level_items() for item in items: self.__expand_item(item) if items: self.scrollToItem(items[0]) def __collapse_item(self, item): self.collapseItem(item) for index in range(item.childCount()): child = item.child(index) self.__collapse_item(child) @Slot() def collapse_selection(self): items = self.selectedItems() if not items: items = self.get_top_level_items() for item in items: self.__collapse_item(item) if items: self.scrollToItem(items[0]) def item_selection_changed(self): """Item selection has changed""" is_selection = len(self.selectedItems()) > 0 self.expand_selection_action.setEnabled(is_selection) self.collapse_selection_action.setEnabled(is_selection) def get_top_level_items(self): """Iterate over top level items""" return [self.topLevelItem(_i) for _i in range(self.topLevelItemCount())] def get_items(self): """Return items (excluding top level items)""" itemlist = [] def add_to_itemlist(item): for index in range(item.childCount()): citem = item.child(index) itemlist.append(citem) add_to_itemlist(citem) for tlitem in self.get_top_level_items(): add_to_itemlist(tlitem) return itemlist def get_scrollbar_position(self): return (self.horizontalScrollBar().value(), self.verticalScrollBar().value()) def set_scrollbar_position(self, position): hor, ver = position self.horizontalScrollBar().setValue(hor) self.verticalScrollBar().setValue(ver) def get_expanded_state(self): self.save_expanded_state() return self.__expanded_state def set_expanded_state(self, state): self.__expanded_state = state self.restore_expanded_state() def save_expanded_state(self): """Save all items expanded state""" self.__expanded_state = {} def add_to_state(item): user_text = get_item_user_text(item) self.__expanded_state[hash(user_text)] = item.isExpanded() def browse_children(item): add_to_state(item) for index in range(item.childCount()): citem = item.child(index) user_text = get_item_user_text(citem) self.__expanded_state[hash(user_text)] = citem.isExpanded() browse_children(citem) for tlitem in self.get_top_level_items(): browse_children(tlitem) def restore_expanded_state(self): """Restore all items expanded state""" if self.__expanded_state is None: return for item in self.get_items()+self.get_top_level_items(): user_text = get_item_user_text(item) is_expanded = self.__expanded_state.get(hash(user_text)) if is_expanded is not None: item.setExpanded(is_expanded) def sort_top_level_items(self, key): """Sorting tree wrt top level items""" self.save_expanded_state() items = sorted([self.takeTopLevelItem(0) for index in range(self.topLevelItemCount())], key=key) for index, item in enumerate(items): self.insertTopLevelItem(index, item) self.restore_expanded_state() def contextMenuEvent(self, event): """Override Qt method""" self.update_menu() self.menu.popup(event.globalPos())
class DirView(QTreeView): """Base file/directory tree view""" def __init__(self, parent=None): super(DirView, self).__init__(parent) self.name_filters = ['*.py'] self.parent_widget = parent self.show_all = None self.menu = None self.common_actions = None self.__expanded_state = None self._to_be_loaded = None self.fsmodel = None self.setup_fs_model() self._scrollbar_positions = None #---- Model def setup_fs_model(self): """Setup filesystem model""" filters = QDir.AllDirs | QDir.Files | QDir.Drives | QDir.NoDotAndDotDot self.fsmodel = QFileSystemModel(self) self.fsmodel.setFilter(filters) self.fsmodel.setNameFilterDisables(False) def install_model(self): """Install filesystem model""" self.setModel(self.fsmodel) def setup_view(self): """Setup view""" self.install_model() if not is_pyqt46: self.fsmodel.directoryLoaded.connect( lambda: self.resizeColumnToContents(0)) self.setAnimated(False) self.setSortingEnabled(True) self.sortByColumn(0, Qt.AscendingOrder) self.fsmodel.modelReset.connect(self.reset_icon_provider) self.reset_icon_provider() def set_name_filters(self, name_filters): """Set name filters""" self.name_filters = name_filters self.fsmodel.setNameFilters(name_filters) def set_show_all(self, state): """Toggle 'show all files' state""" if state: self.fsmodel.setNameFilters([]) else: self.fsmodel.setNameFilters(self.name_filters) def get_filename(self, index): """Return filename associated with *index*""" if index: return osp.normpath(to_text_string(self.fsmodel.filePath(index))) def get_index(self, filename): """Return index associated with filename""" return self.fsmodel.index(filename) def get_selected_filenames(self): """Return selected filenames""" if self.selectionMode() == self.ExtendedSelection: return [self.get_filename(idx) for idx in self.selectedIndexes()] else: return [self.get_filename(self.currentIndex())] def get_dirname(self, index): """Return dirname associated with *index*""" fname = self.get_filename(index) if fname: if osp.isdir(fname): return fname else: return osp.dirname(fname) #---- Tree view widget def setup(self, name_filters=['*.py', '*.pyw'], show_all=False): """Setup tree widget""" self.setup_view() self.set_name_filters(name_filters) self.show_all = show_all # Setup context menu self.menu = QMenu(self) self.common_actions = self.setup_common_actions() def reset_icon_provider(self): """Reset file system model icon provider The purpose of this is to refresh files/directories icons""" self.fsmodel.setIconProvider(IconProvider(self)) #---- Context menu def setup_common_actions(self): """Setup context menu common actions""" # Filters filters_action = create_action(self, _("Edit filename filters..."), None, ima.icon('filter'), triggered=self.edit_filter) # Show all files all_action = create_action(self, _("Show all files"), toggled=self.toggle_all) all_action.setChecked(self.show_all) self.toggle_all(self.show_all) return [filters_action, all_action] @Slot() def edit_filter(self): """Edit name filters""" filters, valid = QInputDialog.getText(self, _('Edit filename filters'), _('Name filters:'), QLineEdit.Normal, ", ".join(self.name_filters)) if valid: filters = [f.strip() for f in to_text_string(filters).split(',')] self.parent_widget.sig_option_changed.emit('name_filters', filters) self.set_name_filters(filters) @Slot(bool) def toggle_all(self, checked): """Toggle all files mode""" self.parent_widget.sig_option_changed.emit('show_all', checked) self.show_all = checked self.set_show_all(checked) def create_file_new_actions(self, fnames): """Return actions for submenu 'New...'""" if not fnames: return [] new_file_act = create_action(self, _("File..."), icon=ima.icon('filenew'), triggered=lambda: self.new_file(fnames[-1])) new_module_act = create_action(self, _("Module..."), icon=ima.icon('spyder'), triggered=lambda: self.new_module(fnames[-1])) new_folder_act = create_action(self, _("Folder..."), icon=ima.icon('folder_new'), triggered=lambda: self.new_folder(fnames[-1])) new_package_act = create_action(self, _("Package..."), icon=ima.icon('package_new'), triggered=lambda: self.new_package(fnames[-1])) return [new_file_act, new_folder_act, None, new_module_act, new_package_act] def create_file_import_actions(self, fnames): """Return actions for submenu 'Import...'""" return [] def create_file_manage_actions(self, fnames): """Return file management actions""" only_files = all([osp.isfile(_fn) for _fn in fnames]) only_modules = all([osp.splitext(_fn)[1] in ('.py', '.pyw', '.ipy') for _fn in fnames]) only_notebooks = all([osp.splitext(_fn)[1] == '.ipynb' for _fn in fnames]) only_valid = all([encoding.is_text_file(_fn) for _fn in fnames]) run_action = create_action(self, _("Run"), icon=ima.icon('run'), triggered=self.run) edit_action = create_action(self, _("Edit"), icon=ima.icon('edit'), triggered=self.clicked) move_action = create_action(self, _("Move..."), icon="move.png", triggered=self.move) delete_action = create_action(self, _("Delete..."), icon=ima.icon('editdelete'), triggered=self.delete) rename_action = create_action(self, _("Rename..."), icon=ima.icon('rename'), triggered=self.rename) open_action = create_action(self, _("Open"), triggered=self.open) ipynb_convert_action = create_action(self, _("Convert to Python script"), icon=ima.icon('python'), triggered=self.convert_notebooks) actions = [] if only_modules: actions.append(run_action) if only_valid and only_files: actions.append(edit_action) else: actions.append(open_action) actions += [delete_action, rename_action] basedir = fixpath(osp.dirname(fnames[0])) if all([fixpath(osp.dirname(_fn)) == basedir for _fn in fnames]): actions.append(move_action) actions += [None] if only_notebooks and nbexporter is not None: actions.append(ipynb_convert_action) # VCS support is quite limited for now, so we are enabling the VCS # related actions only when a single file/folder is selected: dirname = fnames[0] if osp.isdir(fnames[0]) else osp.dirname(fnames[0]) if len(fnames) == 1 and vcs.is_vcs_repository(dirname): # QAction.triggered works differently for PySide and PyQt if not API == 'pyside': commit_slot = lambda _checked, fnames=[dirname]:\ self.vcs_command(fnames, 'commit') browse_slot = lambda _checked, fnames=[dirname]:\ self.vcs_command(fnames, 'browse') else: commit_slot = lambda fnames=[dirname]:\ self.vcs_command(fnames, 'commit') browse_slot = lambda fnames=[dirname]:\ self.vcs_command(fnames, 'browse') vcs_ci = create_action(self, _("Commit"), icon=ima.icon('vcs_commit'), triggered=commit_slot) vcs_log = create_action(self, _("Browse repository"), icon=ima.icon('vcs_browse'), triggered=browse_slot) actions += [None, vcs_ci, vcs_log] return actions def create_folder_manage_actions(self, fnames): """Return folder management actions""" actions = [] if os.name == 'nt': _title = _("Open command prompt here") else: _title = _("Open terminal here") action = create_action(self, _title, icon=ima.icon('cmdprompt'), triggered=lambda: self.open_terminal(fnames)) actions.append(action) _title = _("Open Python console here") action = create_action(self, _title, icon=ima.icon('python'), triggered=lambda: self.open_interpreter(fnames)) actions.append(action) return actions def create_context_menu_actions(self): """Create context menu actions""" actions = [] fnames = self.get_selected_filenames() new_actions = self.create_file_new_actions(fnames) if len(new_actions) > 1: # Creating a submenu only if there is more than one entry new_act_menu = QMenu(_('New'), self) add_actions(new_act_menu, new_actions) actions.append(new_act_menu) else: actions += new_actions import_actions = self.create_file_import_actions(fnames) if len(import_actions) > 1: # Creating a submenu only if there is more than one entry import_act_menu = QMenu(_('Import'), self) add_actions(import_act_menu, import_actions) actions.append(import_act_menu) else: actions += import_actions if actions: actions.append(None) if fnames: actions += self.create_file_manage_actions(fnames) if actions: actions.append(None) if fnames and all([osp.isdir(_fn) for _fn in fnames]): actions += self.create_folder_manage_actions(fnames) if actions: actions.append(None) actions += self.common_actions return actions def update_menu(self): """Update context menu""" self.menu.clear() add_actions(self.menu, self.create_context_menu_actions()) #---- Events def viewportEvent(self, event): """Reimplement Qt method""" # Prevent Qt from crashing or showing warnings like: # "QSortFilterProxyModel: index from wrong model passed to # mapFromSource", probably due to the fact that the file system model # is being built. See Issue 1250. # # This workaround was inspired by the following KDE bug: # https://bugs.kde.org/show_bug.cgi?id=172198 # # Apparently, this is a bug from Qt itself. self.executeDelayedItemsLayout() return QTreeView.viewportEvent(self, event) def contextMenuEvent(self, event): """Override Qt method""" self.update_menu() self.menu.popup(event.globalPos()) def keyPressEvent(self, event): """Reimplement Qt method""" if event.key() in (Qt.Key_Enter, Qt.Key_Return): self.clicked() elif event.key() == Qt.Key_F2: self.rename() elif event.key() == Qt.Key_Delete: self.delete() elif event.key() == Qt.Key_Backspace: self.go_to_parent_directory() else: QTreeView.keyPressEvent(self, event) def mouseDoubleClickEvent(self, event): """Reimplement Qt method""" QTreeView.mouseDoubleClickEvent(self, event) self.clicked() @Slot() def clicked(self): """Selected item was double-clicked or enter/return was pressed""" fnames = self.get_selected_filenames() for fname in fnames: if osp.isdir(fname): self.directory_clicked(fname) else: self.open([fname]) def directory_clicked(self, dirname): """Directory was just clicked""" pass #---- Drag def dragEnterEvent(self, event): """Drag and Drop - Enter event""" event.setAccepted(event.mimeData().hasFormat("text/plain")) def dragMoveEvent(self, event): """Drag and Drop - Move event""" if (event.mimeData().hasFormat("text/plain")): event.setDropAction(Qt.MoveAction) event.accept() else: event.ignore() def startDrag(self, dropActions): """Reimplement Qt Method - handle drag event""" data = QMimeData() data.setUrls([QUrl(fname) for fname in self.get_selected_filenames()]) drag = QDrag(self) drag.setMimeData(data) drag.exec_() #---- File/Directory actions @Slot() def open(self, fnames=None): """Open files with the appropriate application""" if fnames is None: fnames = self.get_selected_filenames() for fname in fnames: if osp.isfile(fname) and encoding.is_text_file(fname): self.parent_widget.sig_open_file.emit(fname) else: self.open_outside_spyder([fname]) def open_outside_spyder(self, fnames): """Open file outside Spyder with the appropriate application If this does not work, opening unknown file in Spyder, as text file""" for path in sorted(fnames): path = file_uri(path) ok = programs.start_file(path) if not ok: self.parent_widget.edit.emit(path) def open_terminal(self, fnames): """Open terminal""" for path in sorted(fnames): self.parent_widget.open_terminal.emit(path) def open_interpreter(self, fnames): """Open interpreter""" for path in sorted(fnames): self.parent_widget.open_interpreter.emit(path) @Slot() def run(self, fnames=None): """Run Python scripts""" if fnames is None: fnames = self.get_selected_filenames() for fname in fnames: self.parent_widget.run.emit(fname) def remove_tree(self, dirname): """Remove whole directory tree Reimplemented in project explorer widget""" shutil.rmtree(dirname, onerror=misc.onerror) def delete_file(self, fname, multiple, yes_to_all): """Delete file""" if multiple: buttons = QMessageBox.Yes|QMessageBox.YesAll| \ QMessageBox.No|QMessageBox.Cancel else: buttons = QMessageBox.Yes|QMessageBox.No if yes_to_all is None: answer = QMessageBox.warning(self, _("Delete"), _("Do you really want " "to delete <b>%s</b>?" ) % osp.basename(fname), buttons) if answer == QMessageBox.No: return yes_to_all elif answer == QMessageBox.Cancel: return False elif answer == QMessageBox.YesAll: yes_to_all = True try: if osp.isfile(fname): misc.remove_file(fname) self.parent_widget.removed.emit(fname) else: self.remove_tree(fname) self.parent_widget.removed_tree.emit(fname) return yes_to_all except EnvironmentError as error: action_str = _('delete') QMessageBox.critical(self, _("Project Explorer"), _("<b>Unable to %s <i>%s</i></b>" "<br><br>Error message:<br>%s" ) % (action_str, fname, to_text_string(error))) return False @Slot() def delete(self, fnames=None): """Delete files""" if fnames is None: fnames = self.get_selected_filenames() multiple = len(fnames) > 1 yes_to_all = None for fname in fnames: yes_to_all = self.delete_file(fname, multiple, yes_to_all) if yes_to_all is not None and not yes_to_all: # Canceled return def convert_notebook(self, fname): """Convert an IPython notebook to a Python script in editor""" try: script = nbexporter().from_filename(fname)[0] except Exception as e: QMessageBox.critical(self, _('Conversion error'), _("It was not possible to convert this " "notebook. The error is:\n\n") + \ to_text_string(e)) return self.parent_widget.sig_new_file.emit(script) @Slot() def convert_notebooks(self): """Convert IPython notebooks to Python scripts in editor""" fnames = self.get_selected_filenames() if not isinstance(fnames, (tuple, list)): fnames = [fnames] for fname in fnames: self.convert_notebook(fname) def rename_file(self, fname): """Rename file""" path, valid = QInputDialog.getText(self, _('Rename'), _('New name:'), QLineEdit.Normal, osp.basename(fname)) if valid: path = osp.join(osp.dirname(fname), to_text_string(path)) if path == fname: return if osp.exists(path): if QMessageBox.warning(self, _("Rename"), _("Do you really want to rename <b>%s</b> and " "overwrite the existing file <b>%s</b>?" ) % (osp.basename(fname), osp.basename(path)), QMessageBox.Yes|QMessageBox.No) == QMessageBox.No: return try: misc.rename_file(fname, path) self.parent_widget.renamed.emit(fname, path) return path except EnvironmentError as error: QMessageBox.critical(self, _("Rename"), _("<b>Unable to rename file <i>%s</i></b>" "<br><br>Error message:<br>%s" ) % (osp.basename(fname), to_text_string(error))) @Slot() def rename(self, fnames=None): """Rename files""" if fnames is None: fnames = self.get_selected_filenames() if not isinstance(fnames, (tuple, list)): fnames = [fnames] for fname in fnames: self.rename_file(fname) @Slot() def move(self, fnames=None): """Move files/directories""" if fnames is None: fnames = self.get_selected_filenames() orig = fixpath(osp.dirname(fnames[0])) while True: self.parent_widget.redirect_stdio.emit(False) folder = getexistingdirectory(self, _("Select directory"), orig) self.parent_widget.redirect_stdio.emit(True) if folder: folder = fixpath(folder) if folder != orig: break else: return for fname in fnames: basename = osp.basename(fname) try: misc.move_file(fname, osp.join(folder, basename)) except EnvironmentError as error: QMessageBox.critical(self, _("Error"), _("<b>Unable to move <i>%s</i></b>" "<br><br>Error message:<br>%s" ) % (basename, to_text_string(error))) def create_new_folder(self, current_path, title, subtitle, is_package): """Create new folder""" if current_path is None: current_path = '' if osp.isfile(current_path): current_path = osp.dirname(current_path) name, valid = QInputDialog.getText(self, title, subtitle, QLineEdit.Normal, "") if valid: dirname = osp.join(current_path, to_text_string(name)) try: os.mkdir(dirname) except EnvironmentError as error: QMessageBox.critical(self, title, _("<b>Unable " "to create folder <i>%s</i></b>" "<br><br>Error message:<br>%s" ) % (dirname, to_text_string(error))) finally: if is_package: fname = osp.join(dirname, '__init__.py') try: with open(fname, 'wb') as f: f.write(to_binary_string('#')) return dirname except EnvironmentError as error: QMessageBox.critical(self, title, _("<b>Unable " "to create file <i>%s</i></b>" "<br><br>Error message:<br>%s" ) % (fname, to_text_string(error))) def new_folder(self, basedir): """New folder""" title = _('New folder') subtitle = _('Folder name:') self.create_new_folder(basedir, title, subtitle, is_package=False) def new_package(self, basedir): """New package""" title = _('New package') subtitle = _('Package name:') self.create_new_folder(basedir, title, subtitle, is_package=True) def create_new_file(self, current_path, title, filters, create_func): """Create new file Returns True if successful""" if current_path is None: current_path = '' if osp.isfile(current_path): current_path = osp.dirname(current_path) self.parent_widget.redirect_stdio.emit(False) fname, _selfilter = getsavefilename(self, title, current_path, filters) self.parent_widget.redirect_stdio.emit(True) if fname: try: create_func(fname) return fname except EnvironmentError as error: QMessageBox.critical(self, _("New file"), _("<b>Unable to create file <i>%s</i>" "</b><br><br>Error message:<br>%s" ) % (fname, to_text_string(error))) def new_file(self, basedir): """New file""" title = _("New file") filters = _("All files")+" (*)" def create_func(fname): """File creation callback""" if osp.splitext(fname)[1] in ('.py', '.pyw', '.ipy'): create_script(fname) else: with open(fname, 'wb') as f: f.write(to_binary_string('')) fname = self.create_new_file(basedir, title, filters, create_func) if fname is not None: self.open([fname]) def new_module(self, basedir): """New module""" title = _("New module") filters = _("Python scripts")+" (*.py *.pyw *.ipy)" create_func = lambda fname: self.parent_widget.create_module.emit(fname) self.create_new_file(basedir, title, filters, create_func) def go_to_parent_directory(self): pass #----- VCS actions def vcs_command(self, fnames, action): """VCS action (commit, browse)""" try: for path in sorted(fnames): vcs.run_vcs_tool(path, action) except vcs.ActionToolNotFound as error: msg = _("For %s support, please install one of the<br/> " "following tools:<br/><br/> %s")\ % (error.vcsname, ', '.join(error.tools)) QMessageBox.critical(self, _("Error"), _("""<b>Unable to find external program.</b><br><br>%s""") % to_text_string(msg)) #----- Settings def get_scrollbar_position(self): """Return scrollbar positions""" return (self.horizontalScrollBar().value(), self.verticalScrollBar().value()) def set_scrollbar_position(self, position): """Set scrollbar positions""" # Scrollbars will be restored after the expanded state self._scrollbar_positions = position if self._to_be_loaded is not None and len(self._to_be_loaded) == 0: self.restore_scrollbar_positions() def restore_scrollbar_positions(self): """Restore scrollbar positions once tree is loaded""" hor, ver = self._scrollbar_positions self.horizontalScrollBar().setValue(hor) self.verticalScrollBar().setValue(ver) def get_expanded_state(self): """Return expanded state""" self.save_expanded_state() return self.__expanded_state def set_expanded_state(self, state): """Set expanded state""" self.__expanded_state = state self.restore_expanded_state() def save_expanded_state(self): """Save all items expanded state""" model = self.model() # If model is not installed, 'model' will be None: this happens when # using the Project Explorer without having selected a workspace yet if model is not None: self.__expanded_state = [] for idx in model.persistentIndexList(): if self.isExpanded(idx): self.__expanded_state.append(self.get_filename(idx)) def restore_directory_state(self, fname): """Restore directory expanded state""" root = osp.normpath(to_text_string(fname)) if not osp.exists(root): # Directory has been (re)moved outside Spyder return for basename in os.listdir(root): path = osp.normpath(osp.join(root, basename)) if osp.isdir(path) and path in self.__expanded_state: self.__expanded_state.pop(self.__expanded_state.index(path)) if self._to_be_loaded is None: self._to_be_loaded = [] self._to_be_loaded.append(path) self.setExpanded(self.get_index(path), True) if not self.__expanded_state and not is_pyqt46: self.fsmodel.directoryLoaded.disconnect(self.restore_directory_state) def follow_directories_loaded(self, fname): """Follow directories loaded during startup""" if self._to_be_loaded is None: return path = osp.normpath(to_text_string(fname)) if path in self._to_be_loaded: self._to_be_loaded.remove(path) if self._to_be_loaded is not None and len(self._to_be_loaded) == 0 \ and not is_pyqt46: self.fsmodel.directoryLoaded.disconnect( self.follow_directories_loaded) if self._scrollbar_positions is not None: # The tree view need some time to render branches: QTimer.singleShot(50, self.restore_scrollbar_positions) def restore_expanded_state(self): """Restore all items expanded state""" if self.__expanded_state is not None: # In the old project explorer, the expanded state was a dictionnary: if isinstance(self.__expanded_state, list) and not is_pyqt46: self.fsmodel.directoryLoaded.connect( self.restore_directory_state) self.fsmodel.directoryLoaded.connect( self.follow_directories_loaded)
class CondaPackagesTable(QTableView): """ """ WIDTH_TYPE = 24 WIDTH_NAME = 120 WIDTH_ACTIONS = 24 WIDTH_VERSION = 70 def __init__(self, parent): super(CondaPackagesTable, self).__init__(parent) self._parent = parent self._searchbox = u'' self._filterbox = const.ALL self._delegate = CustomDelegate(self) self.row_count = None # To manage icon states self._model_index_clicked = None self.valid = False self.column_ = None self.current_index = None # To prevent triggering the keyrelease after closing a dialog # but hititng enter on it self.pressed_here = False self.source_model = None self.proxy_model = None self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSelectionMode(QAbstractItemView.SingleSelection) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.verticalHeader().hide() self.setSortingEnabled(True) self.setAlternatingRowColors(True) self.setItemDelegate(self._delegate) self.setShowGrid(False) self.setWordWrap(True) self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._palette = QPalette() # Header setup self._hheader = self.horizontalHeader() self._hheader.setResizeMode(self._hheader.Fixed) self._hheader.setStyleSheet("""QHeaderView {border: 0px; border-radius: 0px;};""") self.setPalette(self._palette) self.sortByColumn(const.NAME, Qt.AscendingOrder) self.setContextMenuPolicy(Qt.CustomContextMenu) self.hide_columns() def setup_model(self, packages_names, packages_versions, row_data): """ """ self.proxy_model = MultiColumnSortFilterProxy(self) self.source_model = CondaPackagesModel(self, packages_names, packages_versions, row_data) self.proxy_model.setSourceModel(self.source_model) self.setModel(self.proxy_model) # Custom Proxy Model setup self.proxy_model.setDynamicSortFilter(True) filter_text = \ (lambda row, text, status: ( all([t in row[const.NAME].lower() for t in to_text_string(text).lower().split()]) or all([t in row[const.DESCRIPTION].lower() for t in to_text_string(text).split()]))) filter_status = (lambda row, text, status: to_text_string(row[const.STATUS]) in to_text_string(status)) self.model().add_filter_function('status-search', filter_status) self.model().add_filter_function('text-search', filter_text) # Signals and slots self.verticalScrollBar().valueChanged.connect(self.resize_rows) self.hide_columns() # self.resizeRowsToContents() self.resize_rows() def resize_rows(self): """ """ delta_y = 10 height = self.height() y = 0 while y < height: row = self.rowAt(y) self.resizeRowToContents(row) row_height = self.rowHeight(row) self.setRowHeight(row, row_height + delta_y) y += self.rowHeight(row) + delta_y def hide_columns(self): """ """ for col in const.COLUMNS: self.showColumn(col) for col in HIDE_COLUMNS: self.hideColumn(col) def hide_action_columns(self): """ """ for col in const.COLUMNS: self.showColumn(col) for col in HIDE_COLUMNS + const.ACTION_COLUMNS: self.hideColumn(col) def filter_changed(self): """Trigger the filter""" group = self._filterbox text = self._searchbox if group in [const.ALL]: group = ''.join([to_text_string(const.INSTALLED), to_text_string(const.UPGRADABLE), to_text_string(const.NOT_INSTALLED), to_text_string(const.DOWNGRADABLE), to_text_string(const.MIXGRADABLE), to_text_string(const.NOT_INSTALLABLE)]) elif group in [const.INSTALLED]: group = ''.join([to_text_string(const.INSTALLED), to_text_string(const.UPGRADABLE), to_text_string(const.DOWNGRADABLE), to_text_string(const.MIXGRADABLE)]) elif group in [const.UPGRADABLE]: group = ''.join([to_text_string(const.UPGRADABLE), to_text_string(const.MIXGRADABLE)]) elif group in [const.DOWNGRADABLE]: group = ''.join([to_text_string(const.DOWNGRADABLE), to_text_string(const.MIXGRADABLE)]) elif group in [const.ALL_INSTALLABLE]: group = ''.join([to_text_string(const.INSTALLED), to_text_string(const.UPGRADABLE), to_text_string(const.NOT_INSTALLED), to_text_string(const.DOWNGRADABLE), to_text_string(const.MIXGRADABLE)]) else: group = to_text_string(group) if self.proxy_model is not None: self.proxy_model.set_filter(text, group) self.resize_rows() # Update label count count = self.verticalHeader().count() if count == 0: count_text = _("0 packages available ") elif count == 1: count_text = _("1 package available ") elif count > 1: count_text = to_text_string(count) + _(" packages available ") if text != '': count_text = count_text + _('matching "{0}"').format(text) self._parent._update_status(status=count_text, hide=False, env=True) def search_string_changed(self, text): """ """ text = to_text_string(text) self._searchbox = text self.filter_changed() def filter_status_changed(self, text): """ """ if text not in const.PACKAGE_STATUS: text = const.PACKAGE_STATUS[text] for key in const.COMBOBOX_VALUES: val = const.COMBOBOX_VALUES[key] if to_text_string(val) == to_text_string(text): group = val break self._filterbox = group self.filter_changed() def resizeEvent(self, event): """Override Qt method""" w = self.width() self.setColumnWidth(const.PACKAGE_TYPE, self.WIDTH_TYPE) self.setColumnWidth(const.NAME, self.WIDTH_NAME) self.setColumnWidth(const.VERSION, self.WIDTH_VERSION) w_new = w - (self.WIDTH_TYPE + self.WIDTH_NAME + self.WIDTH_VERSION + (len(const.ACTION_COLUMNS) + 1)*self.WIDTH_ACTIONS) self.setColumnWidth(const.DESCRIPTION, w_new) for col in const.ACTION_COLUMNS: self.setColumnWidth(col, self.WIDTH_ACTIONS) QTableView.resizeEvent(self, event) self.resize_rows() def keyPressEvent(self, event): """Override Qt method""" QTableView.keyPressEvent(self, event) if event.key() in [Qt.Key_Enter, Qt.Key_Return]: index = self.currentIndex() self.action_pressed(index) self.pressed_here = True def keyReleaseEvent(self, event): """Override Qt method""" QTableView.keyReleaseEvent(self, event) if event.key() in [Qt.Key_Enter, Qt.Key_Return] and self.pressed_here: self.action_released() self.pressed_here = False def mousePressEvent(self, event): """Override Qt method""" QTableView.mousePressEvent(self, event) self.current_index = self.currentIndex() if event.button() == Qt.LeftButton: pos = QPoint(event.x(), event.y()) index = self.indexAt(pos) self.action_pressed(index) elif event.button() == Qt.RightButton: self.context_menu_requested(event) def mouseReleaseEvent(self, event): """Override Qt method""" if event.button() == Qt.LeftButton: self.action_released() def action_pressed(self, index): """ """ column = index.column() if self.proxy_model is not None: model_index = self.proxy_model.mapToSource(index) model = self.source_model self._model_index_clicked = model_index self.valid = True if column == const.INSTALL and model.is_installable(model_index): model.update_row_icon(model_index.row(), const.INSTALL) elif column == const.INSTALL and model.is_removable(model_index): model.update_row_icon(model_index.row(), const.REMOVE) elif ((column == const.UPGRADE and model.is_upgradable(model_index)) or (column == const.DOWNGRADE and model.is_downgradable(model_index))): model.update_row_icon(model_index.row(), model_index.column()) else: self._model_index_clicked = None self.valid = False def action_released(self): """ """ model = self.source_model model_index = self._model_index_clicked if model_index: column = model_index.column() if column == const.INSTALL and model.is_removable(model_index): column = const.REMOVE self.source_model.update_row_icon(model_index.row(), column) if self.valid: row_data = self.source_model.row(model_index.row()) type_ = row_data[const.PACKAGE_TYPE] name = row_data[const.NAME] versions = self.source_model.get_package_versions(name) version = self.source_model.get_package_version(name) if type_ == const.CONDA: self._parent._run_action(name, column, version, versions) elif type_ == const.PIP: QMessageBox.information(self, "Remove pip package: " "{0}".format(name), "This functionality is not yet " "available.") else: pass def context_menu_requested(self, event): """Custom context menu.""" index = self.current_index model_index = self.proxy_model.mapToSource(index) row = self.source_model.row(model_index.row()) column = model_index.column() if column in [const.INSTALL, const.UPGRADE, const.DOWNGRADE]: return elif column in [const.VERSION]: name = self.source_model.row(model_index.row())[const.NAME] versions = self.source_model.get_package_versions(name) actions = [] for version in reversed(versions): actions.append(create_action(self, version, icon=QIcon())) else: name, license_ = row[const.NAME], row[const.LICENSE] metadata = self._parent.get_package_metadata(name) pypi = metadata['pypi'] home = metadata['home'] dev = metadata['dev'] docs = metadata['docs'] q_pypi = QIcon(get_image_path('python.png')) q_home = QIcon(get_image_path('home.png')) q_docs = QIcon(get_image_path('conda_docs.png')) if 'git' in dev: q_dev = QIcon(get_image_path('conda_github.png')) elif 'bitbucket' in dev: q_dev = QIcon(get_image_path('conda_bitbucket.png')) else: q_dev = QIcon() if 'mit' in license_.lower(): lic = 'http://opensource.org/licenses/MIT' elif 'bsd' == license_.lower(): lic = 'http://opensource.org/licenses/BSD-3-Clause' else: lic = None actions = [] if license_ != '': actions.append(create_action(self, _('License: ' + license_), icon=QIcon(), triggered=lambda: self.open_url(lic))) actions.append(None) if pypi != '': actions.append(create_action(self, _('Python Package Index'), icon=q_pypi, triggered=lambda: self.open_url(pypi))) if home != '': actions.append(create_action(self, _('Homepage'), icon=q_home, triggered=lambda: self.open_url(home))) if docs != '': actions.append(create_action(self, _('Documentation'), icon=q_docs, triggered=lambda: self.open_url(docs))) if dev != '': actions.append(create_action(self, _('Development'), icon=q_dev, triggered=lambda: self.open_url(dev))) if len(actions) > 1: self._menu = QMenu(self) pos = QPoint(event.x(), event.y()) add_actions(self._menu, actions) self._menu.popup(self.viewport().mapToGlobal(pos)) def open_url(self, url): """Open link from action in default operating system browser""" if url is None: return QDesktopServices.openUrl(QUrl(url))
class BreakpointTableView(QTableView): edit_goto = Signal(str, int, str) clear_breakpoint = Signal(str, int) clear_all_breakpoints = Signal() set_or_edit_conditional_breakpoint = Signal() def __init__(self, parent, data): QTableView.__init__(self, parent) self.model = BreakpointTableModel(self, data) self.setModel(self.model) self.delegate = BreakpointDelegate(self) self.setItemDelegate(self.delegate) self.setup_table() def setup_table(self): """Setup table""" self.horizontalHeader().setStretchLastSection(True) self.adjust_columns() self.columnAt(0) # Sorting columns self.setSortingEnabled(False) self.sortByColumn(0, Qt.DescendingOrder) def adjust_columns(self): """Resize three first columns to contents""" for col in range(3): self.resizeColumnToContents(col) def mouseDoubleClickEvent(self, event): """Reimplement Qt method""" index_clicked = self.indexAt(event.pos()) if self.model.breakpoints: filename = self.model.breakpoints[index_clicked.row()][0] line_number_str = self.model.breakpoints[index_clicked.row()][1] self.edit_goto.emit(filename, int(line_number_str), '') if index_clicked.column()==2: self.set_or_edit_conditional_breakpoint.emit() def contextMenuEvent(self, event): index_clicked = self.indexAt(event.pos()) actions = [] self.popup_menu = QMenu(self) clear_all_breakpoints_action = create_action(self, _("Clear breakpoints in all files"), triggered=lambda: self.clear_all_breakpoints.emit()) actions.append(clear_all_breakpoints_action) if self.model.breakpoints: filename = self.model.breakpoints[index_clicked.row()][0] lineno = int(self.model.breakpoints[index_clicked.row()][1]) # QAction.triggered works differently for PySide and PyQt if not API == 'pyside': clear_slot = lambda _checked, filename=filename, lineno=lineno: \ self.clear_breakpoint.emit(filename, lineno) edit_slot = lambda _checked, filename=filename, lineno=lineno: \ (self.edit_goto.emit(filename, lineno, ''), self.set_or_edit_conditional_breakpoint.emit()) else: clear_slot = lambda filename=filename, lineno=lineno: \ self.clear_breakpoint.emit(filename, lineno) edit_slot = lambda filename=filename, lineno=lineno: \ (self.edit_goto.emit(filename, lineno, ''), self.set_or_edit_conditional_breakpoint.emit()) clear_breakpoint_action = create_action(self, _("Clear this breakpoint"), triggered=clear_slot) actions.insert(0,clear_breakpoint_action) edit_breakpoint_action = create_action(self, _("Edit this breakpoint"), triggered=edit_slot) actions.append(edit_breakpoint_action) add_actions(self.popup_menu, actions) self.popup_menu.popup(event.globalPos()) event.accept()
class BaseTabs(QTabWidget): """TabWidget with context menu and corner widgets""" sig_close_tab = Signal(int) def __init__(self, parent, actions=None, menu=None, corner_widgets=None, menu_use_tooltips=False): QTabWidget.__init__(self, parent) self.setUsesScrollButtons(True) # To style tabs on Mac if sys.platform == 'darwin': self.setObjectName('plugin-tab') self.corner_widgets = {} self.menu_use_tooltips = menu_use_tooltips if menu is None: self.menu = QMenu(self) if actions: add_actions(self.menu, actions) else: self.menu = menu # Corner widgets if corner_widgets is None: corner_widgets = {} corner_widgets.setdefault(Qt.TopLeftCorner, []) corner_widgets.setdefault(Qt.TopRightCorner, []) self.browse_button = create_toolbutton(self, icon=ima.icon('browse_tab'), tip=_("Browse tabs")) self.browse_tabs_menu = QMenu(self) self.browse_button.setMenu(self.browse_tabs_menu) self.browse_button.setPopupMode(self.browse_button.InstantPopup) self.browse_tabs_menu.aboutToShow.connect(self.update_browse_tabs_menu) corner_widgets[Qt.TopLeftCorner] += [self.browse_button] self.set_corner_widgets(corner_widgets) def update_browse_tabs_menu(self): """Update browse tabs menu""" self.browse_tabs_menu.clear() names = [] dirnames = [] for index in range(self.count()): if self.menu_use_tooltips: text = to_text_string(self.tabToolTip(index)) else: text = to_text_string(self.tabText(index)) names.append(text) if osp.isfile(text): # Testing if tab names are filenames dirnames.append(osp.dirname(text)) offset = None # If tab names are all filenames, removing common path: if len(names) == len(dirnames): common = get_common_path(dirnames) if common is None: offset = None else: offset = len(common)+1 if offset <= 3: # Common path is not a path but a drive letter... offset = None for index, text in enumerate(names): tab_action = create_action(self, text[offset:], icon=self.tabIcon(index), toggled=lambda state, index=index: self.setCurrentIndex(index), tip=self.tabToolTip(index)) tab_action.setChecked(index == self.currentIndex()) self.browse_tabs_menu.addAction(tab_action) def set_corner_widgets(self, corner_widgets): """ Set tabs corner widgets corner_widgets: dictionary of (corner, widgets) corner: Qt.TopLeftCorner or Qt.TopRightCorner widgets: list of widgets (may contains integers to add spacings) """ assert isinstance(corner_widgets, dict) assert all(key in (Qt.TopLeftCorner, Qt.TopRightCorner) for key in corner_widgets) self.corner_widgets.update(corner_widgets) for corner, widgets in list(self.corner_widgets.items()): cwidget = QWidget() cwidget.hide() prev_widget = self.cornerWidget(corner) if prev_widget: prev_widget.close() self.setCornerWidget(cwidget, corner) clayout = QHBoxLayout() clayout.setContentsMargins(0, 0, 0, 0) for widget in widgets: if isinstance(widget, int): clayout.addSpacing(widget) else: clayout.addWidget(widget) cwidget.setLayout(clayout) cwidget.show() def add_corner_widgets(self, widgets, corner=Qt.TopRightCorner): self.set_corner_widgets({corner: self.corner_widgets.get(corner, [])+widgets}) def contextMenuEvent(self, event): """Override Qt method""" self.setCurrentIndex(self.tabBar().tabAt(event.pos())) if self.menu: self.menu.popup(event.globalPos()) def mousePressEvent(self, event): """Override Qt method""" if event.button() == Qt.MidButton: index = self.tabBar().tabAt(event.pos()) if index >= 0: self.sig_close_tab.emit(index) event.accept() return QTabWidget.mousePressEvent(self, event) def keyPressEvent(self, event): """Override Qt method""" ctrl = event.modifiers() & Qt.ControlModifier key = event.key() handled = False if ctrl and self.count() > 0: index = self.currentIndex() if key == Qt.Key_PageUp: if index > 0: self.setCurrentIndex(index - 1) else: self.setCurrentIndex(self.count() - 1) handled = True elif key == Qt.Key_PageDown: if index < self.count() - 1: self.setCurrentIndex(index + 1) else: self.setCurrentIndex(0) handled = True if not handled: QTabWidget.keyPressEvent(self, event) def set_close_function(self, func): """Setting Tabs close function None -> tabs are not closable""" state = func is not None if state: self.sig_close_tab.connect(func) try: # Assuming Qt >= 4.5 QTabWidget.setTabsClosable(self, state) self.tabCloseRequested.connect(func) except AttributeError: # Workaround for Qt < 4.5 close_button = create_toolbutton(self, triggered=func, icon=ima.icon('fileclose'), tip=_("Close current tab")) self.setCornerWidget(close_button if state else None)