123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884 |
- """The Qt MainWindow for the QtConsole
- This is a tabbed pseudo-terminal of Jupyter sessions, with a menu bar for
- common actions.
- """
- # Copyright (c) Jupyter Development Team.
- # Distributed under the terms of the Modified BSD License.
- import sys
- import webbrowser
- from threading import Thread
- from jupyter_core.paths import jupyter_runtime_dir
- from pygments.styles import get_all_styles
- from qtpy import QtGui, QtCore, QtWidgets
- from qtconsole import styles
- from qtconsole.jupyter_widget import JupyterWidget
- from qtconsole.usage import gui_reference
- def background(f):
- """call a function in a simple thread, to prevent blocking"""
- t = Thread(target=f)
- t.start()
- return t
- class MainWindow(QtWidgets.QMainWindow):
- #---------------------------------------------------------------------------
- # 'object' interface
- #---------------------------------------------------------------------------
- def __init__(self, app,
- confirm_exit=True,
- new_frontend_factory=None, slave_frontend_factory=None,
- connection_frontend_factory=None,
- ):
- """ Create a tabbed MainWindow for managing FrontendWidgets
- Parameters
- ----------
- app : reference to QApplication parent
- confirm_exit : bool, optional
- Whether we should prompt on close of tabs
- new_frontend_factory : callable
- A callable that returns a new JupyterWidget instance, attached to
- its own running kernel.
- slave_frontend_factory : callable
- A callable that takes an existing JupyterWidget, and returns a new
- JupyterWidget instance, attached to the same kernel.
- """
- super(MainWindow, self).__init__()
- self._kernel_counter = 0
- self._external_kernel_counter = 0
- self._app = app
- self.confirm_exit = confirm_exit
- self.new_frontend_factory = new_frontend_factory
- self.slave_frontend_factory = slave_frontend_factory
- self.connection_frontend_factory = connection_frontend_factory
- self.tab_widget = QtWidgets.QTabWidget(self)
- self.tab_widget.setDocumentMode(True)
- self.tab_widget.setTabsClosable(True)
- self.tab_widget.tabCloseRequested[int].connect(self.close_tab)
- self.setCentralWidget(self.tab_widget)
- # hide tab bar at first, since we have no tabs:
- self.tab_widget.tabBar().setVisible(False)
- # prevent focus in tab bar
- self.tab_widget.setFocusPolicy(QtCore.Qt.NoFocus)
- def update_tab_bar_visibility(self):
- """ update visibility of the tabBar depending of the number of tab
- 0 or 1 tab, tabBar hidden
- 2+ tabs, tabBar visible
- send a self.close if number of tab ==0
- need to be called explicitly, or be connected to tabInserted/tabRemoved
- """
- if self.tab_widget.count() <= 1:
- self.tab_widget.tabBar().setVisible(False)
- else:
- self.tab_widget.tabBar().setVisible(True)
- if self.tab_widget.count()==0 :
- self.close()
- @property
- def next_kernel_id(self):
- """constantly increasing counter for kernel IDs"""
- c = self._kernel_counter
- self._kernel_counter += 1
- return c
- @property
- def next_external_kernel_id(self):
- """constantly increasing counter for external kernel IDs"""
- c = self._external_kernel_counter
- self._external_kernel_counter += 1
- return c
- @property
- def active_frontend(self):
- return self.tab_widget.currentWidget()
- def create_tab_with_new_frontend(self):
- """create a new frontend and attach it to a new tab"""
- widget = self.new_frontend_factory()
- self.add_tab_with_frontend(widget)
- def set_window_title(self):
- """Set the title of the console window"""
- old_title = self.windowTitle()
- title, ok = QtWidgets.QInputDialog.getText(self,
- "Rename Window",
- "New title:",
- text=old_title)
- if ok:
- self.setWindowTitle(title)
- def create_tab_with_existing_kernel(self):
- """create a new frontend attached to an external kernel in a new tab"""
- connection_file, file_type = QtWidgets.QFileDialog.getOpenFileName(self,
- "Connect to Existing Kernel",
- jupyter_runtime_dir(),
- "Connection file (*.json)")
- if not connection_file:
- return
- widget = self.connection_frontend_factory(connection_file)
- name = "external {}".format(self.next_external_kernel_id)
- self.add_tab_with_frontend(widget, name=name)
- def create_tab_with_current_kernel(self):
- """create a new frontend attached to the same kernel as the current tab"""
- current_widget = self.tab_widget.currentWidget()
- current_widget_index = self.tab_widget.indexOf(current_widget)
- current_widget_name = self.tab_widget.tabText(current_widget_index)
- widget = self.slave_frontend_factory(current_widget)
- if 'slave' in current_widget_name:
- # don't keep stacking slaves
- name = current_widget_name
- else:
- name = '(%s) slave' % current_widget_name
- self.add_tab_with_frontend(widget,name=name)
- def set_tab_title(self):
- """Set the title of the current tab"""
- old_title = self.tab_widget.tabText(self.tab_widget.currentIndex())
- title, ok = QtWidgets.QInputDialog.getText(self,
- "Rename Tab",
- "New title:",
- text=old_title)
- if ok:
- self.tab_widget.setTabText(self.tab_widget.currentIndex(), title)
- def close_tab(self,current_tab):
- """ Called when you need to try to close a tab.
- It takes the number of the tab to be closed as argument, or a reference
- to the widget inside this tab
- """
- # let's be sure "tab" and "closing widget" are respectively the index
- # of the tab to close and a reference to the frontend to close
- if type(current_tab) is not int :
- current_tab = self.tab_widget.indexOf(current_tab)
- closing_widget=self.tab_widget.widget(current_tab)
- # when trying to be closed, widget might re-send a request to be
- # closed again, but will be deleted when event will be processed. So
- # need to check that widget still exists and skip if not. One example
- # of this is when 'exit' is sent in a slave tab. 'exit' will be
- # re-sent by this function on the master widget, which ask all slave
- # widgets to exit
- if closing_widget is None:
- return
- #get a list of all slave widgets on the same kernel.
- slave_tabs = self.find_slave_widgets(closing_widget)
- keepkernel = None #Use the prompt by default
- if hasattr(closing_widget,'_keep_kernel_on_exit'): #set by exit magic
- keepkernel = closing_widget._keep_kernel_on_exit
- # If signal sent by exit magic (_keep_kernel_on_exit, exist and not None)
- # we set local slave tabs._hidden to True to avoid prompting for kernel
- # restart when they get the signal. and then "forward" the 'exit'
- # to the main window
- if keepkernel is not None:
- for tab in slave_tabs:
- tab._hidden = True
- if closing_widget in slave_tabs:
- try :
- self.find_master_tab(closing_widget).execute('exit')
- except AttributeError:
- self.log.info("Master already closed or not local, closing only current tab")
- self.tab_widget.removeTab(current_tab)
- self.update_tab_bar_visibility()
- return
- kernel_client = closing_widget.kernel_client
- kernel_manager = closing_widget.kernel_manager
- if keepkernel is None and not closing_widget._confirm_exit:
- # don't prompt, just terminate the kernel if we own it
- # or leave it alone if we don't
- keepkernel = closing_widget._existing
- if keepkernel is None: #show prompt
- if kernel_client and kernel_client.channels_running:
- title = self.window().windowTitle()
- cancel = QtWidgets.QMessageBox.Cancel
- okay = QtWidgets.QMessageBox.Ok
- if closing_widget._may_close:
- msg = "You are closing the tab : "+'"'+self.tab_widget.tabText(current_tab)+'"'
- info = "Would you like to quit the Kernel and close all attached Consoles as well?"
- justthis = QtWidgets.QPushButton("&No, just this Tab", self)
- justthis.setShortcut('N')
- closeall = QtWidgets.QPushButton("&Yes, close all", self)
- closeall.setShortcut('Y')
- # allow ctrl-d ctrl-d exit, like in terminal
- closeall.setShortcut('Ctrl+D')
- box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Question,
- title, msg)
- box.setInformativeText(info)
- box.addButton(cancel)
- box.addButton(justthis, QtWidgets.QMessageBox.NoRole)
- box.addButton(closeall, QtWidgets.QMessageBox.YesRole)
- box.setDefaultButton(closeall)
- box.setEscapeButton(cancel)
- pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
- box.setIconPixmap(pixmap)
- reply = box.exec_()
- if reply == 1: # close All
- for slave in slave_tabs:
- background(slave.kernel_client.stop_channels)
- self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
- kernel_manager.shutdown_kernel()
- self.tab_widget.removeTab(current_tab)
- background(kernel_client.stop_channels)
- elif reply == 0: # close Console
- if not closing_widget._existing:
- # Have kernel: don't quit, just close the tab
- closing_widget.execute("exit True")
- self.tab_widget.removeTab(current_tab)
- background(kernel_client.stop_channels)
- else:
- reply = QtWidgets.QMessageBox.question(self, title,
- "Are you sure you want to close this Console?"+
- "\nThe Kernel and other Consoles will remain active.",
- okay|cancel,
- defaultButton=okay
- )
- if reply == okay:
- self.tab_widget.removeTab(current_tab)
- elif keepkernel: #close console but leave kernel running (no prompt)
- self.tab_widget.removeTab(current_tab)
- background(kernel_client.stop_channels)
- else: #close console and kernel (no prompt)
- self.tab_widget.removeTab(current_tab)
- if kernel_client and kernel_client.channels_running:
- for slave in slave_tabs:
- background(slave.kernel_client.stop_channels)
- self.tab_widget.removeTab(self.tab_widget.indexOf(slave))
- if kernel_manager:
- kernel_manager.shutdown_kernel()
- background(kernel_client.stop_channels)
- self.update_tab_bar_visibility()
- def add_tab_with_frontend(self,frontend,name=None):
- """ insert a tab with a given frontend in the tab bar, and give it a name
- """
- if not name:
- name = 'kernel %i' % self.next_kernel_id
- self.tab_widget.addTab(frontend,name)
- self.update_tab_bar_visibility()
- self.make_frontend_visible(frontend)
- frontend.exit_requested.connect(self.close_tab)
- def next_tab(self):
- self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()+1))
- def prev_tab(self):
- self.tab_widget.setCurrentIndex((self.tab_widget.currentIndex()-1))
- def make_frontend_visible(self,frontend):
- widget_index=self.tab_widget.indexOf(frontend)
- if widget_index > 0 :
- self.tab_widget.setCurrentIndex(widget_index)
- def find_master_tab(self,tab,as_list=False):
- """
- Try to return the frontend that owns the kernel attached to the given widget/tab.
- Only finds frontend owned by the current application. Selection
- based on port of the kernel might be inaccurate if several kernel
- on different ip use same port number.
- This function does the conversion tabNumber/widget if needed.
- Might return None if no master widget (non local kernel)
- Will crash if more than 1 masterWidget
- When asList set to True, always return a list of widget(s) owning
- the kernel. The list might be empty or containing several Widget.
- """
- #convert from/to int/richIpythonWidget if needed
- if isinstance(tab, int):
- tab = self.tab_widget.widget(tab)
- km=tab.kernel_client
- #build list of all widgets
- widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
- # widget that are candidate to be the owner of the kernel does have all the same port of the curent widget
- # And should have a _may_close attribute
- filtered_widget_list = [ widget for widget in widget_list if
- widget.kernel_client.connection_file == km.connection_file and
- hasattr(widget,'_may_close') ]
- # the master widget is the one that may close the kernel
- master_widget= [ widget for widget in filtered_widget_list if widget._may_close]
- if as_list:
- return master_widget
- assert(len(master_widget)<=1 )
- if len(master_widget)==0:
- return None
- return master_widget[0]
- def find_slave_widgets(self,tab):
- """return all the frontends that do not own the kernel attached to the given widget/tab.
- Only find frontends owned by the current application. Selection
- based on connection file of the kernel.
- This function does the conversion tabNumber/widget if needed.
- """
- #convert from/to int/richIpythonWidget if needed
- if isinstance(tab, int):
- tab = self.tab_widget.widget(tab)
- km=tab.kernel_client
- #build list of all widgets
- widget_list = [self.tab_widget.widget(i) for i in range(self.tab_widget.count())]
- # widget that are candidate not to be the owner of the kernel does have all the same port of the curent widget
- filtered_widget_list = ( widget for widget in widget_list if
- widget.kernel_client.connection_file == km.connection_file)
- # Get a list of all widget owning the same kernel and removed it from
- # the previous cadidate. (better using sets ?)
- master_widget_list = self.find_master_tab(tab, as_list=True)
- slave_list = [widget for widget in filtered_widget_list if widget not in master_widget_list]
- return slave_list
- # Populate the menu bar with common actions and shortcuts
- def add_menu_action(self, menu, action, defer_shortcut=False):
- """Add action to menu as well as self
- So that when the menu bar is invisible, its actions are still available.
- If defer_shortcut is True, set the shortcut context to widget-only,
- where it will avoid conflict with shortcuts already bound to the
- widgets themselves.
- """
- menu.addAction(action)
- self.addAction(action)
- if defer_shortcut:
- action.setShortcutContext(QtCore.Qt.WidgetShortcut)
- def init_menu_bar(self):
- #create menu in the order they should appear in the menu bar
- self.init_file_menu()
- self.init_edit_menu()
- self.init_view_menu()
- self.init_kernel_menu()
- self.init_window_menu()
- self.init_help_menu()
- def init_file_menu(self):
- self.file_menu = self.menuBar().addMenu("&File")
- self.new_kernel_tab_act = QtWidgets.QAction("New Tab with &New kernel",
- self,
- shortcut="Ctrl+T",
- triggered=self.create_tab_with_new_frontend)
- self.add_menu_action(self.file_menu, self.new_kernel_tab_act)
- self.slave_kernel_tab_act = QtWidgets.QAction("New Tab with Sa&me kernel",
- self,
- shortcut="Ctrl+Shift+T",
- triggered=self.create_tab_with_current_kernel)
- self.add_menu_action(self.file_menu, self.slave_kernel_tab_act)
- self.existing_kernel_tab_act = QtWidgets.QAction("New Tab with &Existing kernel",
- self,
- shortcut="Alt+T",
- triggered=self.create_tab_with_existing_kernel)
- self.add_menu_action(self.file_menu, self.existing_kernel_tab_act)
- self.file_menu.addSeparator()
- self.close_action=QtWidgets.QAction("&Close Tab",
- self,
- shortcut=QtGui.QKeySequence.Close,
- triggered=self.close_active_frontend
- )
- self.add_menu_action(self.file_menu, self.close_action)
- self.export_action=QtWidgets.QAction("&Save to HTML/XHTML",
- self,
- shortcut=QtGui.QKeySequence.Save,
- triggered=self.export_action_active_frontend
- )
- self.add_menu_action(self.file_menu, self.export_action, True)
- self.file_menu.addSeparator()
- printkey = QtGui.QKeySequence(QtGui.QKeySequence.Print)
- if printkey.matches("Ctrl+P") and sys.platform != 'darwin':
- # Only override the default if there is a collision.
- # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
- printkey = "Ctrl+Shift+P"
- self.print_action = QtWidgets.QAction("&Print",
- self,
- shortcut=printkey,
- triggered=self.print_action_active_frontend)
- self.add_menu_action(self.file_menu, self.print_action, True)
- if sys.platform != 'darwin':
- # OSX always has Quit in the Application menu, only add it
- # to the File menu elsewhere.
- self.file_menu.addSeparator()
- self.quit_action = QtWidgets.QAction("&Quit",
- self,
- shortcut=QtGui.QKeySequence.Quit,
- triggered=self.close,
- )
- self.add_menu_action(self.file_menu, self.quit_action)
- def init_edit_menu(self):
- self.edit_menu = self.menuBar().addMenu("&Edit")
- self.undo_action = QtWidgets.QAction("&Undo",
- self,
- shortcut=QtGui.QKeySequence.Undo,
- statusTip="Undo last action if possible",
- triggered=self.undo_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.undo_action)
- self.redo_action = QtWidgets.QAction("&Redo",
- self,
- shortcut=QtGui.QKeySequence.Redo,
- statusTip="Redo last action if possible",
- triggered=self.redo_active_frontend)
- self.add_menu_action(self.edit_menu, self.redo_action)
- self.edit_menu.addSeparator()
- self.cut_action = QtWidgets.QAction("&Cut",
- self,
- shortcut=QtGui.QKeySequence.Cut,
- triggered=self.cut_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.cut_action, True)
- self.copy_action = QtWidgets.QAction("&Copy",
- self,
- shortcut=QtGui.QKeySequence.Copy,
- triggered=self.copy_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.copy_action, True)
- self.copy_raw_action = QtWidgets.QAction("Copy (&Raw Text)",
- self,
- shortcut="Ctrl+Shift+C",
- triggered=self.copy_raw_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.copy_raw_action, True)
- self.paste_action = QtWidgets.QAction("&Paste",
- self,
- shortcut=QtGui.QKeySequence.Paste,
- triggered=self.paste_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.paste_action, True)
- self.edit_menu.addSeparator()
- selectall = QtGui.QKeySequence(QtGui.QKeySequence.SelectAll)
- if selectall.matches("Ctrl+A") and sys.platform != 'darwin':
- # Only override the default if there is a collision.
- # Qt ctrl = cmd on OSX, so the match gets a false positive on OSX.
- selectall = "Ctrl+Shift+A"
- self.select_all_action = QtWidgets.QAction("Select Cell/&All",
- self,
- shortcut=selectall,
- triggered=self.select_all_active_frontend
- )
- self.add_menu_action(self.edit_menu, self.select_all_action, True)
- def init_view_menu(self):
- self.view_menu = self.menuBar().addMenu("&View")
- if sys.platform != 'darwin':
- # disable on OSX, where there is always a menu bar
- self.toggle_menu_bar_act = QtWidgets.QAction("Toggle &Menu Bar",
- self,
- shortcut="Ctrl+Shift+M",
- statusTip="Toggle visibility of menubar",
- triggered=self.toggle_menu_bar)
- self.add_menu_action(self.view_menu, self.toggle_menu_bar_act)
- fs_key = "Ctrl+Meta+F" if sys.platform == 'darwin' else "F11"
- self.full_screen_act = QtWidgets.QAction("&Full Screen",
- self,
- shortcut=fs_key,
- statusTip="Toggle between Fullscreen and Normal Size",
- triggered=self.toggleFullScreen)
- self.add_menu_action(self.view_menu, self.full_screen_act)
- self.view_menu.addSeparator()
- self.increase_font_size = QtWidgets.QAction("Zoom &In",
- self,
- shortcut=QtGui.QKeySequence.ZoomIn,
- triggered=self.increase_font_size_active_frontend
- )
- self.add_menu_action(self.view_menu, self.increase_font_size, True)
- self.decrease_font_size = QtWidgets.QAction("Zoom &Out",
- self,
- shortcut=QtGui.QKeySequence.ZoomOut,
- triggered=self.decrease_font_size_active_frontend
- )
- self.add_menu_action(self.view_menu, self.decrease_font_size, True)
- self.reset_font_size = QtWidgets.QAction("Zoom &Reset",
- self,
- shortcut="Ctrl+0",
- triggered=self.reset_font_size_active_frontend
- )
- self.add_menu_action(self.view_menu, self.reset_font_size, True)
- self.view_menu.addSeparator()
- self.clear_action = QtWidgets.QAction("&Clear Screen",
- self,
- shortcut='Ctrl+L',
- statusTip="Clear the console",
- triggered=self.clear_active_frontend)
- self.add_menu_action(self.view_menu, self.clear_action)
- self.pager_menu = self.view_menu.addMenu("&Pager")
- hsplit_action = QtWidgets.QAction(".. &Horizontal Split",
- self,
- triggered=lambda: self.set_paging_active_frontend('hsplit'))
- vsplit_action = QtWidgets.QAction(" : &Vertical Split",
- self,
- triggered=lambda: self.set_paging_active_frontend('vsplit'))
- inside_action = QtWidgets.QAction(" &Inside Pager",
- self,
- triggered=lambda: self.set_paging_active_frontend('inside'))
- self.pager_menu.addAction(hsplit_action)
- self.pager_menu.addAction(vsplit_action)
- self.pager_menu.addAction(inside_action)
- available_syntax_styles = self.get_available_syntax_styles()
- if len(available_syntax_styles) > 0:
- self.syntax_style_menu = self.view_menu.addMenu("&Syntax Style")
- style_group = QtWidgets.QActionGroup(self)
- for style in available_syntax_styles:
- action = QtWidgets.QAction("{}".format(style), self,
- triggered=lambda v,
- syntax_style=style:
- self.set_syntax_style(
- syntax_style=syntax_style))
- action.setCheckable(True)
- style_group.addAction(action)
- self.syntax_style_menu.addAction(action)
- if style == 'default':
- action.setChecked(True)
- self.syntax_style_menu.setDefaultAction(action)
- def init_kernel_menu(self):
- self.kernel_menu = self.menuBar().addMenu("&Kernel")
- # Qt on OSX maps Ctrl to Cmd, and Meta to Ctrl
- # keep the signal shortcuts to ctrl, rather than
- # platform-default like we do elsewhere.
- ctrl = "Meta" if sys.platform == 'darwin' else "Ctrl"
- self.interrupt_kernel_action = QtWidgets.QAction("&Interrupt current Kernel",
- self,
- triggered=self.interrupt_kernel_active_frontend,
- shortcut=ctrl+"+C",
- )
- self.add_menu_action(self.kernel_menu, self.interrupt_kernel_action)
- self.restart_kernel_action = QtWidgets.QAction("&Restart current Kernel",
- self,
- triggered=self.restart_kernel_active_frontend,
- shortcut=ctrl+"+.",
- )
- self.add_menu_action(self.kernel_menu, self.restart_kernel_action)
- self.kernel_menu.addSeparator()
- self.confirm_restart_kernel_action = QtWidgets.QAction("&Confirm kernel restart",
- self,
- checkable=True,
- checked=self.active_frontend.confirm_restart,
- triggered=self.toggle_confirm_restart_active_frontend
- )
- self.add_menu_action(self.kernel_menu, self.confirm_restart_kernel_action)
- self.tab_widget.currentChanged.connect(self.update_restart_checkbox)
- def init_window_menu(self):
- self.window_menu = self.menuBar().addMenu("&Window")
- if sys.platform == 'darwin':
- # add min/maximize actions to OSX, which lacks default bindings.
- self.minimizeAct = QtWidgets.QAction("Mini&mize",
- self,
- shortcut="Ctrl+m",
- statusTip="Minimize the window/Restore Normal Size",
- triggered=self.toggleMinimized)
- # maximize is called 'Zoom' on OSX for some reason
- self.maximizeAct = QtWidgets.QAction("&Zoom",
- self,
- shortcut="Ctrl+Shift+M",
- statusTip="Maximize the window/Restore Normal Size",
- triggered=self.toggleMaximized)
- self.add_menu_action(self.window_menu, self.minimizeAct)
- self.add_menu_action(self.window_menu, self.maximizeAct)
- self.window_menu.addSeparator()
- prev_key = "Ctrl+Alt+Left" if sys.platform == 'darwin' else "Ctrl+PgUp"
- self.prev_tab_act = QtWidgets.QAction("Pre&vious Tab",
- self,
- shortcut=prev_key,
- statusTip="Select previous tab",
- triggered=self.prev_tab)
- self.add_menu_action(self.window_menu, self.prev_tab_act)
- next_key = "Ctrl+Alt+Right" if sys.platform == 'darwin' else "Ctrl+PgDown"
- self.next_tab_act = QtWidgets.QAction("Ne&xt Tab",
- self,
- shortcut=next_key,
- statusTip="Select next tab",
- triggered=self.next_tab)
- self.add_menu_action(self.window_menu, self.next_tab_act)
- self.rename_window_act = QtWidgets.QAction("Rename &Window",
- self,
- shortcut="Alt+R",
- statusTip="Rename window",
- triggered=self.set_window_title)
- self.add_menu_action(self.window_menu, self.rename_window_act)
- self.rename_current_tab_act = QtWidgets.QAction("&Rename Current Tab",
- self,
- shortcut="Ctrl+R",
- statusTip="Rename current tab",
- triggered=self.set_tab_title)
- self.add_menu_action(self.window_menu, self.rename_current_tab_act)
- def init_help_menu(self):
- # please keep the Help menu in Mac Os even if empty. It will
- # automatically contain a search field to search inside menus and
- # please keep it spelled in English, as long as Qt Doesn't support
- # a QAction.MenuRole like HelpMenuRole otherwise it will lose
- # this search field functionality
- self.help_menu = self.menuBar().addMenu("&Help")
- # Help Menu
- self.help_action = QtWidgets.QAction("Show &QtConsole help", self,
- triggered=self._show_help)
- self.online_help_action = QtWidgets.QAction("Open online &help", self,
- triggered=self._open_online_help)
- self.add_menu_action(self.help_menu, self.help_action)
- self.add_menu_action(self.help_menu, self.online_help_action)
- def _set_active_frontend_focus(self):
- # this is a hack, self.active_frontend._control seems to be
- # a private member. Unfortunately this is the only method
- # to set focus reliably
- QtCore.QTimer.singleShot(200, self.active_frontend._control.setFocus)
- # minimize/maximize/fullscreen actions:
- def toggle_menu_bar(self):
- menu_bar = self.menuBar()
- if menu_bar.isVisible():
- menu_bar.setVisible(False)
- else:
- menu_bar.setVisible(True)
- def toggleMinimized(self):
- if not self.isMinimized():
- self.showMinimized()
- else:
- self.showNormal()
- def _show_help(self):
- self.active_frontend._page(gui_reference)
- def _open_online_help(self):
- webbrowser.open("https://qtconsole.readthedocs.io", new=1, autoraise=True)
- def toggleMaximized(self):
- if not self.isMaximized():
- self.showMaximized()
- else:
- self.showNormal()
- # Min/Max imizing while in full screen give a bug
- # when going out of full screen, at least on OSX
- def toggleFullScreen(self):
- if not self.isFullScreen():
- self.showFullScreen()
- if sys.platform == 'darwin':
- self.maximizeAct.setEnabled(False)
- self.minimizeAct.setEnabled(False)
- else:
- self.showNormal()
- if sys.platform == 'darwin':
- self.maximizeAct.setEnabled(True)
- self.minimizeAct.setEnabled(True)
- def set_paging_active_frontend(self, paging):
- self.active_frontend._set_paging(paging)
- def get_available_syntax_styles(self):
- """Get a list with the syntax styles available."""
- styles = list(get_all_styles())
- return sorted(styles)
- def set_syntax_style(self, syntax_style):
- """Set up syntax style for the current console."""
- if syntax_style=='bw':
- colors='nocolor'
- elif styles.dark_style(syntax_style):
- colors='linux'
- else:
- colors='lightbg'
- self.active_frontend.syntax_style = syntax_style
- style_sheet = styles.sheet_from_template(syntax_style, colors)
- self.active_frontend.style_sheet = style_sheet
- self.active_frontend._syntax_style_changed()
- self.active_frontend._style_sheet_changed()
- self.active_frontend.reset(clear=True)
- self.active_frontend._execute("%colors linux", True)
- def close_active_frontend(self):
- self.close_tab(self.active_frontend)
- def restart_kernel_active_frontend(self):
- self.active_frontend.request_restart_kernel()
- def interrupt_kernel_active_frontend(self):
- self.active_frontend.request_interrupt_kernel()
- def toggle_confirm_restart_active_frontend(self):
- widget = self.active_frontend
- widget.confirm_restart = not widget.confirm_restart
- self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
- def update_restart_checkbox(self):
- if self.active_frontend is None:
- return
- widget = self.active_frontend
- self.confirm_restart_kernel_action.setChecked(widget.confirm_restart)
- def clear_active_frontend(self):
- self.active_frontend.clear()
- def cut_active_frontend(self):
- widget = self.active_frontend
- if widget.can_cut():
- widget.cut()
- def copy_active_frontend(self):
- widget = self.active_frontend
- widget.copy()
- def copy_raw_active_frontend(self):
- self.active_frontend._copy_raw_action.trigger()
- def paste_active_frontend(self):
- widget = self.active_frontend
- if widget.can_paste():
- widget.paste()
- def undo_active_frontend(self):
- self.active_frontend.undo()
- def redo_active_frontend(self):
- self.active_frontend.redo()
- def print_action_active_frontend(self):
- self.active_frontend.print_action.trigger()
- def export_action_active_frontend(self):
- self.active_frontend.export_action.trigger()
- def select_all_active_frontend(self):
- self.active_frontend.select_all_action.trigger()
- def increase_font_size_active_frontend(self):
- self.active_frontend.increase_font_size.trigger()
- def decrease_font_size_active_frontend(self):
- self.active_frontend.decrease_font_size.trigger()
- def reset_font_size_active_frontend(self):
- self.active_frontend.reset_font_size.trigger()
- #---------------------------------------------------------------------------
- # QWidget interface
- #---------------------------------------------------------------------------
- def closeEvent(self, event):
- """ Forward the close event to every tabs contained by the windows
- """
- if self.tab_widget.count() == 0:
- # no tabs, just close
- event.accept()
- return
- # Do Not loop on the widget count as it change while closing
- title = self.window().windowTitle()
- cancel = QtWidgets.QMessageBox.Cancel
- okay = QtWidgets.QMessageBox.Ok
- accept_role = QtWidgets.QMessageBox.AcceptRole
- if self.confirm_exit:
- if self.tab_widget.count() > 1:
- msg = "Close all tabs, stop all kernels, and Quit?"
- else:
- msg = "Close console, stop kernel, and Quit?"
- info = "Kernels not started here (e.g. notebooks) will be left alone."
- closeall = QtWidgets.QPushButton("&Quit", self)
- closeall.setShortcut('Q')
- box = QtWidgets.QMessageBox(QtWidgets.QMessageBox.Question,
- title, msg)
- box.setInformativeText(info)
- box.addButton(cancel)
- box.addButton(closeall, QtWidgets.QMessageBox.YesRole)
- box.setDefaultButton(closeall)
- box.setEscapeButton(cancel)
- pixmap = QtGui.QPixmap(self._app.icon.pixmap(QtCore.QSize(64,64)))
- box.setIconPixmap(pixmap)
- reply = box.exec_()
- else:
- reply = okay
- if reply == cancel:
- event.ignore()
- return
- if reply == okay or reply == accept_role:
- while self.tab_widget.count() >= 1:
- # prevent further confirmations:
- widget = self.active_frontend
- widget._confirm_exit = False
- self.close_tab(widget)
- event.accept()
|