# -*- coding: utf-8 -*-
#
# Copyright © 2010 Pierre Raybaut
# Licensed under the terms of the MIT License
# (see spyderlib/__init__.py for details)
# pylint: disable=C0103
# pylint: disable=R0903
# pylint: disable=R0911
# pylint: disable=R0201
import sys
import os
import os.path as osp
from time import time, strftime, gmtime
from spyderlib.qt.QtGui import (QApplication, QWidget, QVBoxLayout,
QHBoxLayout, QMenu, QLabel, QInputDialog,
QLineEdit, QToolButton)
from spyderlib.qt.QtCore import (QProcess, SIGNAL, QByteArray, QTimer, Qt,
QTextCodec)
LOCALE_CODEC = QTextCodec.codecForLocale()
# Local imports
from spyderlib.utils.qthelpers import (get_icon, create_toolbutton,
create_action, add_actions)
from spyderlib.baseconfig import get_conf_path, _
from spyderlib.py3compat import is_text_string, to_text_string
def add_pathlist_to_PYTHONPATH(env, pathlist, drop_env=False):
# PyQt API 1/2 compatibility-related tests:
assert isinstance(env, list)
assert all([is_text_string(path) for path in env])
pypath = "PYTHONPATH"
pathstr = os.pathsep.join(pathlist)
if os.environ.get(pypath) is not None and not drop_env:
for index, var in enumerate(env[:]):
if var.startswith(pypath+'='):
env[index] = var.replace(pypath+'=',
pypath+'='+pathstr+os.pathsep)
env.append('OLD_PYTHONPATH='+os.environ[pypath])
else:
env.append(pypath+'='+pathstr)
#TODO: code refactoring/cleaning (together with systemshell.py and pythonshell.py)
class ExternalShellBase(QWidget):
"""External Shell widget: execute Python script in a separate process"""
SHELL_CLASS = None
def __init__(self, parent=None, fname=None, wdir=None,
history_filename=None, show_icontext=True,
light_background=True, menu_actions=None,
show_buttons_inside=True, show_elapsed_time=True):
QWidget.__init__(self, parent)
self.menu_actions = menu_actions
self.run_button = None
self.kill_button = None
self.options_button = None
self.icontext_action = None
self.show_elapsed_time = show_elapsed_time
self.fname = fname
if wdir is None:
wdir = osp.dirname(osp.abspath(fname))
self.wdir = wdir if osp.isdir(wdir) else None
self.arguments = ""
self.shell = self.SHELL_CLASS(parent, get_conf_path(history_filename))
self.shell.set_light_background(light_background)
self.connect(self.shell, SIGNAL("execute(QString)"),
self.send_to_process)
self.connect(self.shell, SIGNAL("keyboard_interrupt()"),
self.keyboard_interrupt)
# Redirecting some SIGNALs:
self.connect(self.shell, SIGNAL('redirect_stdio(bool)'),
lambda state: self.emit(SIGNAL('redirect_stdio(bool)'),
state))
self.state_label = None
self.time_label = None
vlayout = QVBoxLayout()
toolbar_buttons = self.get_toolbar_buttons()
if show_buttons_inside:
self.state_label = QLabel()
hlayout = QHBoxLayout()
hlayout.addWidget(self.state_label)
hlayout.addStretch(0)
hlayout.addWidget(self.create_time_label())
hlayout.addStretch(0)
for button in toolbar_buttons:
hlayout.addWidget(button)
vlayout.addLayout(hlayout)
else:
vlayout.setContentsMargins(0, 0, 0, 0)
vlayout.addWidget(self.get_shell_widget())
self.setLayout(vlayout)
self.resize(640, 480)
if parent is None:
self.setWindowIcon(self.get_icon())
self.setWindowTitle(_("Console"))
self.t0 = None
self.timer = QTimer(self)
self.process = None
self.is_closing = False
if show_buttons_inside:
self.update_time_label_visibility()
def set_elapsed_time_visible(self, state):
self.show_elapsed_time = state
if self.time_label is not None:
self.time_label.setVisible(state)
def create_time_label(self):
"""Create elapsed time label widget (if necessary) and return it"""
if self.time_label is None:
self.time_label = QLabel()
return self.time_label
def update_time_label_visibility(self):
self.time_label.setVisible(self.show_elapsed_time)
def is_running(self):
if self.process is not None:
return self.process.state() == QProcess.Running
def get_toolbar_buttons(self):
if self.run_button is None:
self.run_button = create_toolbutton(self, text=_("Run"),
icon=get_icon('run.png'),
tip=_("Run again this program"),
triggered=self.start_shell)
if self.kill_button is None:
self.kill_button = create_toolbutton(self, text=_("Kill"),
icon=get_icon('kill.png'),
tip=_("Kills the current process, "
"causing it to exit immediately"))
buttons = [self.run_button]
if self.options_button is None:
options = self.get_options_menu()
if options:
self.options_button = create_toolbutton(self, text=_("Options"),
icon=get_icon('tooloptions.png'))
self.options_button.setPopupMode(QToolButton.InstantPopup)
menu = QMenu(self)
add_actions(menu, options)
self.options_button.setMenu(menu)
if self.options_button is not None:
buttons.append(self.options_button)
buttons.append(self.kill_button)
return buttons
def set_icontext_visible(self, state):
"""Set icon text visibility"""
for widget in self.get_toolbar_buttons():
if state:
widget.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
else:
widget.setToolButtonStyle(Qt.ToolButtonIconOnly)
def get_options_menu(self):
self.show_time_action = create_action(self, _("Show elapsed time"),
toggled=self.set_elapsed_time_visible)
self.show_time_action.setChecked(self.show_elapsed_time)
actions = [self.show_time_action]
if self.menu_actions is not None:
actions += [None]+self.menu_actions
return actions
def get_shell_widget(self):
return self.shell
def get_icon(self):
raise NotImplementedError
def show_time(self, end=False):
if self.time_label is None:
return
elapsed_time = time()-self.t0
if elapsed_time > 24*3600: # More than a day...!
format = "%d %H:%M:%S"
else:
format = "%H:%M:%S"
if end:
color = "#AAAAAA"
else:
color = "#AA6655"
text = "%s" \
"" % (color, strftime(format, gmtime(elapsed_time)))
self.time_label.setText(text)
def closeEvent(self, event):
if self.process is not None:
self.is_closing = True
self.process.kill()
self.process.waitForFinished(100)
self.disconnect(self.timer, SIGNAL("timeout()"), self.show_time)
def set_running_state(self, state=True):
self.set_buttons_runnning_state(state)
self.shell.setReadOnly(not state)
if state:
if self.state_label is not None:
self.state_label.setText(_(
"Running..."))
self.t0 = time()
self.connect(self.timer, SIGNAL("timeout()"), self.show_time)
self.timer.start(1000)
else:
if self.state_label is not None:
self.state_label.setText(_('Terminated.'))
self.disconnect(self.timer, SIGNAL("timeout()"), self.show_time)
def set_buttons_runnning_state(self, state):
self.run_button.setVisible(not state and not self.is_ipykernel)
self.kill_button.setVisible(state)
def start_shell(self, ask_for_arguments=False):
"""Start shell"""
if ask_for_arguments and not self.get_arguments():
self.set_running_state(False)
return
try:
self.disconnect(self.terminate_button, SIGNAL("clicked()"),
self.process.terminate)
self.disconnect(self.kill_button, SIGNAL("clicked()"),
self.process.terminate)
except:
pass
self.create_process()
def get_arguments(self):
arguments, valid = QInputDialog.getText(self, _('Arguments'),
_('Command line arguments:'),
QLineEdit.Normal,
self.arguments)
if valid:
self.arguments = to_text_string(arguments)
return valid
def create_process(self):
raise NotImplementedError
def finished(self, exit_code, exit_status):
self.shell.flush()
self.emit(SIGNAL('finished()'))
if self.is_closing:
return
self.set_running_state(False)
self.show_time(end=True)
#===============================================================================
# Input/Output
#===============================================================================
def transcode(self, qba):
try:
return to_text_string(qba.data(), 'utf8')
except UnicodeDecodeError:
return qba.data()
def get_stdout(self):
self.process.setReadChannel(QProcess.StandardOutput)
qba = QByteArray()
while self.process.bytesAvailable():
qba += self.process.readAllStandardOutput()
return self.transcode(qba)
def get_stderr(self):
self.process.setReadChannel(QProcess.StandardError)
qba = QByteArray()
while self.process.bytesAvailable():
qba += self.process.readAllStandardError()
return self.transcode(qba)
def write_output(self):
self.shell.write(self.get_stdout(), flush=True)
QApplication.processEvents()
def send_to_process(self, qstr):
raise NotImplementedError
def send_ctrl_to_process(self, letter):
char = chr("abcdefghijklmnopqrstuvwxyz".index(letter) + 1)
byte_array = QByteArray()
byte_array.append(char)
self.process.write(byte_array)
self.process.waitForBytesWritten(-1)
self.shell.write(LOCALE_CODEC.toUnicode(byte_array), flush=True)
def keyboard_interrupt(self):
raise NotImplementedError
def test():
from spyderlib.utils.qthelpers import qapplication
app = qapplication()
from spyderlib.widgets.externalshell.pythonshell import ExternalPythonShell
from spyderlib.widgets.externalshell.systemshell import ExternalSystemShell
import spyderlib
from spyderlib.plugins.variableexplorer import VariableExplorer
settings = VariableExplorer.get_settings()
shell = ExternalPythonShell(wdir=osp.dirname(spyderlib.__file__),
ipykernel=True, stand_alone=settings,
arguments="-q4thread -pylab -colors LightBG",
light_background=False)
# shell = ExternalPythonShell(wdir=osp.dirname(spyderlib.__file__),
# interact=True, umr_enabled=True,
# stand_alone=settings,
# umr_namelist=['guidata', 'guiqwt'],
# umr_verbose=True, light_background=False)
# shell = ExternalSystemShell(wdir=osp.dirname(spyderlib.__file__),
# light_background=False)
shell.shell.toggle_wrap_mode(True)
shell.start_shell(False)
from spyderlib.qt.QtGui import QFont
font = QFont("Lucida console")
font.setPointSize(10)
shell.shell.set_font(font)
shell.show()
sys.exit(app.exec_())
if __name__ == "__main__":
test()