~ubuntu-branches/debian/experimental/spyder/experimental

« back to all changes in this revision

Viewing changes to spyderlib/plugins/externalconsole.py

  • Committer: Package Import Robot
  • Author(s): Picca Frédéric-Emmanuel
  • Date: 2013-02-27 09:51:28 UTC
  • mfrom: (1.1.18)
  • Revision ID: package-import@ubuntu.com-20130227095128-wtx1irpvf4vl79lj
Tags: 2.2.0~beta3+dfsg-1
* Imported Upstream version 2.2.0~beta3+dfsg
* debian /patches
  - 0002-feature-forwarded-add-icon-to-desktop-file.patch (deleted)
    this patch was integrated by the upstream.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# -*- coding: utf-8 -*-
2
 
#
3
 
# Copyright © 2009-2010 Pierre Raybaut
4
 
# Licensed under the terms of the MIT License
5
 
# (see spyderlib/__init__.py for details)
6
 
 
7
 
"""External Console plugin"""
8
 
 
9
 
# pylint: disable=C0103
10
 
# pylint: disable=R0903
11
 
# pylint: disable=R0911
12
 
# pylint: disable=R0201
13
 
 
14
 
from spyderlib.qt.QtGui import (QVBoxLayout, QMessageBox, QInputDialog,
15
 
                                QLineEdit, QPushButton, QGroupBox, QLabel,
16
 
                                QTabWidget, QFontComboBox, QHBoxLayout,
17
 
                                QApplication, QCursor)
18
 
from spyderlib.qt.QtCore import SIGNAL, Qt
19
 
from spyderlib.qt.compat import getopenfilename
20
 
 
21
 
import sys
22
 
import os
23
 
import os.path as osp
24
 
import imp
25
 
import re
26
 
import atexit
27
 
 
28
 
# Local imports
29
 
from spyderlib.baseconfig import _, SCIENTIFIC_STARTUP
30
 
from spyderlib.config import CONF
31
 
from spyderlib.guiconfig import get_icon
32
 
from spyderlib.utils import programs
33
 
from spyderlib.utils.misc import (get_error_match, get_python_executable,
34
 
                                  remove_trailing_single_backslash,
35
 
                                  is_python_script)
36
 
from spyderlib.utils.qthelpers import create_action, mimedata2url
37
 
from spyderlib.widgets.tabs import Tabs
38
 
from spyderlib.widgets.externalshell.pythonshell import ExternalPythonShell
39
 
from spyderlib.widgets.externalshell.systemshell import ExternalSystemShell
40
 
from spyderlib.widgets.findreplace import FindReplace
41
 
from spyderlib.plugins import SpyderPluginWidget, PluginConfigPage
42
 
 
43
 
 
44
 
class ExternalConsoleConfigPage(PluginConfigPage):
45
 
    def __init__(self, plugin, parent):
46
 
        PluginConfigPage.__init__(self, plugin, parent)
47
 
        self.get_name = lambda: _("Console")
48
 
 
49
 
    def setup_page(self):
50
 
        interface_group = QGroupBox(_("Interface"))
51
 
        font_group = self.create_fontgroup(option=None, text=None,
52
 
                                    fontfilters=QFontComboBox.MonospacedFonts)
53
 
        newcb = self.create_checkbox
54
 
        singletab_box = newcb(_("One tab per script"), 'single_tab')
55
 
        showtime_box = newcb(_("Show elapsed time"), 'show_elapsed_time')
56
 
        icontext_box = newcb(_("Show icons and text"), 'show_icontext')
57
 
 
58
 
        # Interface Group
59
 
        interface_layout = QVBoxLayout()
60
 
        interface_layout.addWidget(singletab_box)
61
 
        interface_layout.addWidget(showtime_box)
62
 
        interface_layout.addWidget(icontext_box)
63
 
        interface_group.setLayout(interface_layout)
64
 
        
65
 
        # Source Code Group
66
 
        display_group = QGroupBox(_("Source code"))
67
 
        buffer_spin = self.create_spinbox(
68
 
                            _("Buffer: "), _(" lines"),
69
 
                            'max_line_count', min_=0, max_=1000000, step=100,
70
 
                            tip=_("Set maximum line count"))
71
 
        wrap_mode_box = newcb(_("Wrap lines"), 'wrap')
72
 
        merge_channels_box = newcb(
73
 
               _("Merge process standard output/error channels"),
74
 
               'merge_output_channels',
75
 
               tip=_("Merging the output channels of the process means that\n"
76
 
                     "the standard error won't be written in red anymore,\n"
77
 
                     "but this has the effect of speeding up display."))
78
 
        colorize_sys_stderr_box = newcb(
79
 
               _("Colorize standard error channel using ANSI escape codes"),
80
 
               'colorize_sys_stderr',
81
 
               tip=_("This method is the only way to have colorized standard\n"
82
 
                     "error channel when the output channels have been "
83
 
                     "merged."))
84
 
        self.connect(merge_channels_box, SIGNAL("toggled(bool)"),
85
 
                     colorize_sys_stderr_box.setEnabled)
86
 
        self.connect(merge_channels_box, SIGNAL("toggled(bool)"),
87
 
                     colorize_sys_stderr_box.setChecked)
88
 
        colorize_sys_stderr_box.setEnabled(
89
 
                                    self.get_option('merge_output_channels'))
90
 
        
91
 
        display_layout = QVBoxLayout()
92
 
        display_layout.addWidget(buffer_spin)
93
 
        display_layout.addWidget(wrap_mode_box)
94
 
        display_layout.addWidget(merge_channels_box)
95
 
        display_layout.addWidget(colorize_sys_stderr_box)
96
 
        display_group.setLayout(display_layout)
97
 
        
98
 
        # Background Color Group
99
 
        bg_group = QGroupBox(_("Background color"))
100
 
        bg_label = QLabel(_("This option will be applied the next time "
101
 
                            "a Python console or a terminal is opened."))
102
 
        bg_label.setWordWrap(True)
103
 
        lightbg_box = newcb(_("Light background (white color)"),
104
 
                            'light_background')
105
 
        bg_layout = QVBoxLayout()
106
 
        bg_layout.addWidget(bg_label)
107
 
        bg_layout.addWidget(lightbg_box)
108
 
        bg_group.setLayout(bg_layout)
109
 
 
110
 
        # Advanced settings
111
 
        source_group = QGroupBox(_("Source code"))
112
 
        completion_box = newcb(_("Automatic code completion"),
113
 
                               'codecompletion/auto')
114
 
        case_comp_box = newcb(_("Case sensitive code completion"),
115
 
                              'codecompletion/case_sensitive')
116
 
        show_single_box = newcb(_("Show single completion"),
117
 
                               'codecompletion/show_single')
118
 
        comp_enter_box = newcb(_("Enter key selects completion"),
119
 
                               'codecompletion/enter_key')
120
 
        calltips_box = newcb(_("Balloon tips"), 'calltips')
121
 
        inspector_box = newcb(
122
 
                  _("Automatic notification to object inspector"),
123
 
                  'object_inspector', default=True,
124
 
                  tip=_("If this option is enabled, object inspector\n"
125
 
                        "will automatically show informations on functions\n"
126
 
                        "entered in console (this is triggered when entering\n"
127
 
                        "a left parenthesis after a valid function name)"))
128
 
        
129
 
        source_layout = QVBoxLayout()
130
 
        source_layout.addWidget(completion_box)
131
 
        source_layout.addWidget(case_comp_box)
132
 
        source_layout.addWidget(show_single_box)
133
 
        source_layout.addWidget(comp_enter_box)
134
 
        source_layout.addWidget(calltips_box)
135
 
        source_layout.addWidget(inspector_box)
136
 
        source_group.setLayout(source_layout)
137
 
 
138
 
        # UMD Group
139
 
        umd_group = QGroupBox(_("User Module Deleter (UMD)"))
140
 
        umd_label = QLabel(_("UMD forces Python to reload modules which were "
141
 
                             "imported when executing a \nscript in the "
142
 
                             "external console with the 'runfile' function."))
143
 
        umd_enabled_box = newcb(_("Enable UMD"), 'umd/enabled',
144
 
                                msg_if_enabled=True, msg_warning=_(
145
 
                        "This option will enable the User Module Deleter (UMD) "
146
 
                        "in Python interpreters. UMD forces Python to "
147
 
                        "reload deeply modules during import when running a "
148
 
                        "Python script using the Spyder's builtin function "
149
 
                        "<b>runfile</b>."
150
 
                        "<br><br><b>1.</b> UMD may require to restart the "
151
 
                        "Python interpreter in which it will be called "
152
 
                        "(otherwise only newly imported modules will be "
153
 
                        "reloaded when executing scripts)."
154
 
                        "<br><br><b>2.</b> If errors occur when re-running a "
155
 
                        "PyQt-based program, please check that the Qt objects "
156
 
                        "are properly destroyed (e.g. you may have to use the "
157
 
                        "attribute <b>Qt.WA_DeleteOnClose</b> on your main "
158
 
                        "window, using the <b>setAttribute</b> method)"),
159
 
                                )
160
 
        umd_verbose_box = newcb(_("Show reloaded modules list"),
161
 
                                'umd/verbose', msg_info=_(
162
 
                                "Please note that these changes will "
163
 
                                "be applied only to new Python interpreters"))
164
 
        umd_namelist_btn = QPushButton(
165
 
                            _("Set UMD excluded (not reloaded) modules"))
166
 
        self.connect(umd_namelist_btn, SIGNAL('clicked()'),
167
 
                     self.plugin.set_umd_namelist)
168
 
        
169
 
        umd_layout = QVBoxLayout()
170
 
        umd_layout.addWidget(umd_label)
171
 
        umd_layout.addWidget(umd_enabled_box)
172
 
        umd_layout.addWidget(umd_verbose_box)
173
 
        umd_layout.addWidget(umd_namelist_btn)
174
 
        umd_group.setLayout(umd_layout)
175
 
        
176
 
        # Python executable Group
177
 
        pyexec_group = QGroupBox(_("Python executable"))
178
 
        pyexec_label = QLabel(_("Path to Python interpreter "
179
 
                                "executable binary:"))
180
 
        if os.name == 'nt':
181
 
            filters = _("Executables")+" (*.exe)"
182
 
        else:
183
 
            filters = None
184
 
        pyexec_file = self.create_browsefile('', 'pythonexecutable',
185
 
                                             filters=filters)
186
 
        
187
 
        pyexec_layout = QVBoxLayout()
188
 
        pyexec_layout.addWidget(pyexec_label)
189
 
        pyexec_layout.addWidget(pyexec_file)
190
 
        pyexec_group.setLayout(pyexec_layout)
191
 
        
192
 
        # Startup Group
193
 
        startup_group = QGroupBox(_("Startup"))
194
 
        pystartup_box = newcb(_("Open a Python interpreter at startup"),
195
 
                              'open_python_at_startup')
196
 
        
197
 
        startup_layout = QVBoxLayout()
198
 
        startup_layout.addWidget(pystartup_box)
199
 
        startup_group.setLayout(startup_layout)
200
 
        
201
 
        # PYTHONSTARTUP replacement
202
 
        pystartup_group = QGroupBox(_("PYTHONSTARTUP replacement"))
203
 
        pystartup_label = QLabel(_("This option will override the "
204
 
                                   "PYTHONSTARTUP environment variable which\n"
205
 
                                   "defines the script to be executed during "
206
 
                                   "the Python interpreter startup."))
207
 
        default_radio = self.create_radiobutton(
208
 
                                        _("Default PYTHONSTARTUP script"),
209
 
                                        'pythonstartup/default', True)
210
 
        custom_radio = self.create_radiobutton(
211
 
                                        _("Use the following startup script:"),
212
 
                                        'pythonstartup/custom', False)
213
 
        pystartup_file = self.create_browsefile('', 'pythonstartup', '',
214
 
                                                filters=_("Python scripts")+\
215
 
                                                " (*.py)")
216
 
        self.connect(default_radio, SIGNAL("toggled(bool)"),
217
 
                     pystartup_file.setDisabled)
218
 
        self.connect(custom_radio, SIGNAL("toggled(bool)"),
219
 
                     pystartup_file.setEnabled)
220
 
        
221
 
        pystartup_layout = QVBoxLayout()
222
 
        pystartup_layout.addWidget(pystartup_label)
223
 
        pystartup_layout.addWidget(default_radio)
224
 
        pystartup_layout.addWidget(custom_radio)
225
 
        pystartup_layout.addWidget(pystartup_file)
226
 
        pystartup_group.setLayout(pystartup_layout)
227
 
        
228
 
        # Monitor Group
229
 
        monitor_group = QGroupBox(_("Monitor"))
230
 
        monitor_label = QLabel(_("The monitor provides introspection "
231
 
                                 "features to console: code completion, "
232
 
                                 "calltips and variable explorer. "
233
 
                                 "Because it relies on several modules, "
234
 
                                 "disabling the monitor may be useful "
235
 
                                 "to accelerate console startup."))
236
 
        monitor_label.setWordWrap(True)
237
 
        monitor_box = newcb(_("Enable monitor"), 'monitor/enabled')
238
 
        for obj in (completion_box, case_comp_box, show_single_box,
239
 
                    comp_enter_box, calltips_box):
240
 
            self.connect(monitor_box, SIGNAL("toggled(bool)"), obj.setEnabled)
241
 
            obj.setEnabled(self.get_option('monitor/enabled'))
242
 
        
243
 
        monitor_layout = QVBoxLayout()
244
 
        monitor_layout.addWidget(monitor_label)
245
 
        monitor_layout.addWidget(monitor_box)
246
 
        monitor_group.setLayout(monitor_layout)
247
 
        
248
 
        # Qt Group
249
 
        # Do not test if PyQt4 or PySide is installed with the function 
250
 
        # spyderlib.utils.programs.is_module_installed because it will 
251
 
        # fail (both libraries can't be imported at the same time):
252
 
        try:
253
 
            imp.find_module('PyQt4')
254
 
            has_pyqt4 = True
255
 
        except ImportError:
256
 
            has_pyqt4 = False
257
 
        try:
258
 
            imp.find_module('PySide')
259
 
            has_pyside = True
260
 
        except ImportError:
261
 
            has_pyside = False
262
 
        opts = []
263
 
        if has_pyqt4:
264
 
            opts.append( ('PyQt4', 'pyqt') )
265
 
        if has_pyside:
266
 
            opts.append( ('PySide', 'pyside') )
267
 
        qt_group = QGroupBox(_("Qt (PyQt/PySide)"))
268
 
        qt_setapi_box = self.create_combobox(
269
 
                         _("Qt-Python bindings library selection:"),
270
 
                         [(_("Default library"), 'default')]+opts,
271
 
                         'qt/api', default='default', tip=_(
272
 
"""This option will act on libraries such as Matplotlib, guidata or ETS"""))
273
 
        qt_hook_box = newcb(_("Install Spyder's input hook for Qt"),
274
 
                              'qt/install_inputhook',
275
 
                              tip=_(
276
 
"""PyQt installs an input hook that allows creating and interacting
277
 
with Qt widgets in an interactive interpreter without blocking it. 
278
 
On Windows platforms, it is strongly recommended to replace it by Spyder's. 
279
 
Regarding PySide, note that it does not install an input hook, so it is 
280
 
required to enable this feature in order to be able to manipulate PySide/Qt 
281
 
objects interactively."""))
282
 
        
283
 
        qt_layout = QVBoxLayout()
284
 
        qt_layout.addWidget(qt_setapi_box)
285
 
        qt_layout.addWidget(qt_hook_box)
286
 
        qt_group.setLayout(qt_layout)
287
 
        qt_group.setEnabled(has_pyqt4 or has_pyside)
288
 
        
289
 
        # PyQt Group
290
 
        if has_pyqt4:
291
 
            pyqt_group = QGroupBox(_("PyQt"))
292
 
            setapi_box = self.create_combobox(
293
 
                _("API selection for QString and QVariant objects:"),
294
 
                ((_("Default API"), 0), (_("API #1"), 1), (_("API #2"), 2)),
295
 
                'pyqt/api_version', default=0, tip=_(
296
 
"""PyQt API #1 is the default API for Python 2. PyQt API #2 is the default 
297
 
API for Python 3 and is compatible with PySide.
298
 
Note that switching to API #2 may require to enable the Matplotlib patch."""))
299
 
            ignore_api_box = newcb(_("Ignore API change errors (sip.setapi)"),
300
 
                                     'pyqt/ignore_sip_setapi_errors', tip=_(
301
 
"""Enabling this option will ignore errors when changing PyQt API.
302
 
As PyQt does not support dynamic API changes, it is strongly recommended
303
 
to use this feature wisely, e.g. for debugging purpose."""))
304
 
            try:
305
 
                from sip import setapi #analysis:ignore
306
 
            except ImportError:
307
 
                setapi_box.setDisabled(True)
308
 
                ignore_api_box.setDisabled(True)
309
 
            
310
 
            pyqt_layout = QVBoxLayout()
311
 
            pyqt_layout.addWidget(setapi_box)
312
 
            pyqt_layout.addWidget(ignore_api_box)
313
 
            pyqt_group.setLayout(pyqt_layout)
314
 
            qt_layout.addWidget(pyqt_group)
315
 
        
316
 
        # Matplotlib Group
317
 
        mpl_group = QGroupBox(_("Matplotlib"))
318
 
        mpl_backend_box = newcb('', 'matplotlib/backend/enabled', True)
319
 
        mpl_backend_edit = self.create_lineedit(_("GUI backend:"),
320
 
                                'matplotlib/backend/value', "Qt4Agg",
321
 
                                _("Set the GUI toolkit used by Matplotlib to "
322
 
                                  "show figures (default: Qt4Agg)"),
323
 
                                alignment=Qt.Horizontal)
324
 
        self.connect(mpl_backend_box, SIGNAL("toggled(bool)"),
325
 
                     mpl_backend_edit.setEnabled)
326
 
        mpl_backend_layout = QHBoxLayout()
327
 
        mpl_backend_layout.addWidget(mpl_backend_box)
328
 
        mpl_backend_layout.addWidget(mpl_backend_edit)
329
 
        mpl_backend_edit.setEnabled(
330
 
                                self.get_option('matplotlib/backend/enabled'))
331
 
        mpl_patch_box = newcb(_("Patch Matplotlib figures"),
332
 
                              'matplotlib/patch', False)
333
 
        mpl_patch_label = QLabel(_("Patching Matplotlib library will add a "
334
 
                                   "button to customize figure options "
335
 
                                   "(Qt4Agg only) and fix some issues."))
336
 
        mpl_patch_label.setWordWrap(True)
337
 
        self.connect(mpl_patch_box, SIGNAL("toggled(bool)"),
338
 
                     mpl_patch_label.setEnabled)
339
 
        
340
 
        mpl_installed = programs.is_module_installed('matplotlib')
341
 
        if mpl_installed:
342
 
            from spyderlib import mpl_patch
343
 
            if not mpl_patch.is_available():
344
 
                mpl_patch_box.hide()
345
 
                mpl_patch_label.hide()
346
 
        
347
 
        mpl_layout = QVBoxLayout()
348
 
        mpl_layout.addLayout(mpl_backend_layout)
349
 
        mpl_layout.addWidget(mpl_patch_box)
350
 
        mpl_layout.addWidget(mpl_patch_label)
351
 
        mpl_group.setLayout(mpl_layout)
352
 
        mpl_group.setEnabled(mpl_installed)
353
 
        
354
 
        # ETS Group
355
 
        ets_group = QGroupBox(_("Enthought Tool Suite"))
356
 
        ets_label = QLabel(_("Enthought Tool Suite (ETS) supports "
357
 
                             "PyQt4 (qt4) and wxPython (wx) graphical "
358
 
                             "user interfaces."))
359
 
        ets_label.setWordWrap(True)
360
 
        ets_edit = self.create_lineedit(_("ETS_TOOLKIT:"), 'ets_backend',
361
 
                                        default='qt4', alignment=Qt.Horizontal)
362
 
        
363
 
        ets_layout = QVBoxLayout()
364
 
        ets_layout.addWidget(ets_label)
365
 
        ets_layout.addWidget(ets_edit)
366
 
        ets_group.setLayout(ets_layout)
367
 
        ets_group.setEnabled(programs.is_module_installed(
368
 
                                                    "enthought.etsconfig.api"))
369
 
        
370
 
        tabs = QTabWidget()
371
 
        tabs.addTab(self.create_tab(font_group, interface_group, display_group,
372
 
                                    bg_group),
373
 
                    _("Display"))
374
 
        tabs.addTab(self.create_tab(monitor_group, source_group),
375
 
                    _("Introspection"))
376
 
        tabs.addTab(self.create_tab(pyexec_group, startup_group,
377
 
                                    pystartup_group, umd_group),
378
 
                    _("Advanced settings"))
379
 
        tabs.addTab(self.create_tab(qt_group, mpl_group, ets_group),
380
 
                    _("External modules"))
381
 
        
382
 
        vlayout = QVBoxLayout()
383
 
        vlayout.addWidget(tabs)
384
 
        self.setLayout(vlayout)
385
 
 
386
 
 
387
 
class ExternalConsole(SpyderPluginWidget):
388
 
    """
389
 
    Console widget
390
 
    """
391
 
    CONF_SECTION = 'console'
392
 
    CONFIGWIDGET_CLASS = ExternalConsoleConfigPage
393
 
    def __init__(self, parent, light_mode):
394
 
        SpyderPluginWidget.__init__(self, parent)
395
 
        self.light_mode = light_mode
396
 
        self.tabwidget = None
397
 
        self.menu_actions = None
398
 
        
399
 
        self.inspector = None # Object inspector plugin
400
 
        self.historylog = None # History log plugin
401
 
        self.variableexplorer = None # Variable explorer plugin
402
 
        
403
 
        self.python_count = 0
404
 
        self.terminal_count = 0
405
 
 
406
 
        try:
407
 
            from sip import setapi #analysis:ignore
408
 
        except ImportError:
409
 
            self.set_option('pyqt/ignore_sip_setapi_errors', False)
410
 
 
411
 
        scientific = programs.is_module_installed('numpy') and\
412
 
                     programs.is_module_installed('scipy') and\
413
 
                     programs.is_module_installed('matplotlib')
414
 
        if self.get_option('pythonstartup/default', None) is None:
415
 
            self.set_option('pythonstartup/default', not scientific)
416
 
        if not osp.isfile(self.get_option('pythonstartup', '')):
417
 
            self.set_option('pythonstartup', SCIENTIFIC_STARTUP)
418
 
            self.set_option('pythonstartup/default', not scientific)
419
 
        # default/custom settings are mutually exclusive:
420
 
        self.set_option('pythonstartup/custom',
421
 
                        not self.get_option('pythonstartup/default'))
422
 
        
423
 
        executable = self.get_option('pythonexecutable',
424
 
                                     get_python_executable())
425
 
        if not osp.isfile(executable):
426
 
            # This is absolutely necessary, in case the Python interpreter
427
 
            # executable has been moved since last Spyder execution (following
428
 
            # a Python distribution upgrade for example)
429
 
            self.set_option('pythonexecutable', get_python_executable())
430
 
        elif executable.endswith('pythonw.exe'):
431
 
            # That should not be necessary because this case is already taken
432
 
            # care of by the `get_python_executable` function but, this was
433
 
            # implemented too late, so we have to fix it here too, in case
434
 
            # the Python executable has already been set with pythonw.exe:
435
 
            self.set_option('pythonexecutable',
436
 
                            executable.replace("pythonw.exe", "python.exe"))
437
 
        
438
 
        self.shellwidgets = []
439
 
        self.filenames = []
440
 
        self.icons = []
441
 
        self.runfile_args = ""
442
 
        
443
 
        # Initialize plugin
444
 
        self.initialize_plugin()
445
 
        
446
 
        layout = QVBoxLayout()
447
 
        self.tabwidget = Tabs(self, self.menu_actions)
448
 
        if hasattr(self.tabwidget, 'setDocumentMode')\
449
 
           and not sys.platform == 'darwin':
450
 
            # Don't set document mode to true on OSX because it generates
451
 
            # a crash when the console is detached from the main window
452
 
            # Fixes Issue 561
453
 
            self.tabwidget.setDocumentMode(True)
454
 
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
455
 
                     self.refresh_plugin)
456
 
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
457
 
                     self.move_tab)
458
 
                     
459
 
        self.tabwidget.set_close_function(self.close_console)
460
 
 
461
 
        layout.addWidget(self.tabwidget)
462
 
        
463
 
        # Find/replace widget
464
 
        self.find_widget = FindReplace(self)
465
 
        self.find_widget.hide()
466
 
        self.register_widget_shortcuts("Editor", self.find_widget)
467
 
        
468
 
        layout.addWidget(self.find_widget)
469
 
        
470
 
        self.setLayout(layout)
471
 
            
472
 
        # Accepting drops
473
 
        self.setAcceptDrops(True)
474
 
        
475
 
    def move_tab(self, index_from, index_to):
476
 
        """
477
 
        Move tab (tabs themselves have already been moved by the tabwidget)
478
 
        """
479
 
        filename = self.filenames.pop(index_from)
480
 
        shell = self.shellwidgets.pop(index_from)
481
 
        icons = self.icons.pop(index_from)
482
 
        
483
 
        self.filenames.insert(index_to, filename)
484
 
        self.shellwidgets.insert(index_to, shell)
485
 
        self.icons.insert(index_to, icons)
486
 
        self.emit(SIGNAL('update_plugin_title()'))
487
 
 
488
 
    def get_shell_index_from_id(self, shell_id):
489
 
        """Return shellwidget index from id"""
490
 
        for index, shell in enumerate(self.shellwidgets):
491
 
            if id(shell) == shell_id:
492
 
                return index
493
 
        
494
 
    def close_console(self, index=None, from_ipython_client=False):
495
 
        """Close console tab from index or widget (or close current tab)"""
496
 
        # Get tab index
497
 
        if not self.tabwidget.count():
498
 
            return
499
 
        if index is None:
500
 
            index = self.tabwidget.currentIndex()
501
 
        
502
 
        # Detect what widget we are trying to close
503
 
        for i, s in enumerate(self.shellwidgets):
504
 
            if index == i:
505
 
                shellwidget = s
506
 
        
507
 
        # If the tab is an IPython kernel, try to detect if it has a client
508
 
        # connected to it
509
 
        if shellwidget.is_ipython_kernel:
510
 
            ipyclients = self.main.ipyconsole.get_clients()
511
 
            if ipyclients:
512
 
                for ic in ipyclients:
513
 
                    if ic.kernel_widget_id == id(shellwidget):
514
 
                        connected_ipyclient = True
515
 
                        break
516
 
                else:
517
 
                    connected_ipyclient = False
518
 
            else:
519
 
                connected_ipyclient = False
520
 
        
521
 
        # Closing logic
522
 
        if not shellwidget.is_ipython_kernel or from_ipython_client or \
523
 
          not connected_ipyclient:
524
 
            self.tabwidget.widget(index).close()
525
 
            self.tabwidget.removeTab(index)
526
 
            self.filenames.pop(index)
527
 
            self.shellwidgets.pop(index)
528
 
            self.icons.pop(index)
529
 
            self.emit(SIGNAL('update_plugin_title()'))
530
 
        else:
531
 
            QMessageBox.question(self, _('Trying to kill a kernel?'),
532
 
                _("You can't close this kernel because it has one or more "
533
 
                  "consoles connected to it.<br><br>"
534
 
                  "You need to close them instead or you can kill the kernel "
535
 
                  "using the button far to the right."),
536
 
                  QMessageBox.Ok)
537
 
                                 
538
 
        
539
 
    def set_variableexplorer(self, variableexplorer):
540
 
        """Set variable explorer plugin"""
541
 
        self.variableexplorer = variableexplorer
542
 
        
543
 
    def __find_python_shell(self, interpreter_only=False):
544
 
        current_index = self.tabwidget.currentIndex()
545
 
        if current_index == -1:
546
 
            return
547
 
        from spyderlib.widgets.externalshell import pythonshell
548
 
        for index in [current_index]+range(self.tabwidget.count()):
549
 
            shellwidget = self.tabwidget.widget(index)
550
 
            if isinstance(shellwidget, pythonshell.ExternalPythonShell):
551
 
                if interpreter_only and not shellwidget.is_interpreter:
552
 
                    continue
553
 
                elif not shellwidget.is_running():
554
 
                    continue
555
 
                else:
556
 
                    self.tabwidget.setCurrentIndex(index)
557
 
                    return shellwidget
558
 
    
559
 
    def get_current_shell(self):
560
 
        """
561
 
        Called by object inspector to retrieve the current shell instance
562
 
        """
563
 
        shellwidget = self.__find_python_shell()
564
 
        return shellwidget.shell
565
 
                
566
 
    def get_running_python_shell(self):
567
 
        """
568
 
        Called by object inspector to retrieve a running Python shell instance
569
 
        """
570
 
        current_index = self.tabwidget.currentIndex()
571
 
        if current_index == -1:
572
 
            return
573
 
        from spyderlib.widgets.externalshell import pythonshell
574
 
        shellwidgets = [self.tabwidget.widget(index)
575
 
                        for index in range(self.tabwidget.count())]
576
 
        shellwidgets = [_w for _w in shellwidgets
577
 
                        if isinstance(_w, pythonshell.ExternalPythonShell) \
578
 
                        and _w.is_running()]
579
 
        if shellwidgets:
580
 
            # First, iterate on interpreters only:
581
 
            for shellwidget in shellwidgets:
582
 
                if shellwidget.is_interpreter:
583
 
                    return shellwidget.shell
584
 
            else:
585
 
                return shellwidgets[0].shell
586
 
        
587
 
    def run_script_in_current_shell(self, filename, wdir, args, debug):
588
 
        """Run script in current shell, if any"""
589
 
        line = "%s(r'%s'" % ('debugfile' if debug else 'runfile',
590
 
                             unicode(filename))
591
 
        norm = lambda text: remove_trailing_single_backslash(unicode(text))
592
 
        if args:
593
 
            line += ", args=r'%s'" % norm(args)
594
 
        if wdir:
595
 
            line += ", wdir=r'%s'" % norm(wdir)
596
 
        line += ")"
597
 
        self.execute_python_code(line, interpreter_only=True)
598
 
            
599
 
    def set_current_shell_working_directory(self, directory):
600
 
        """Set current shell working directory"""
601
 
        shellwidget = self.__find_python_shell()
602
 
        if shellwidget is not None:
603
 
            shellwidget.shell.set_cwd(unicode(directory))
604
 
        
605
 
    def execute_python_code(self, lines, interpreter_only=False):
606
 
        """Execute Python code in an already opened Python interpreter"""
607
 
        shellwidget = self.__find_python_shell(
608
 
                                        interpreter_only=interpreter_only)
609
 
        if shellwidget is not None:
610
 
            if shellwidget.is_ipython_kernel:
611
 
                #  IPython Console plugin
612
 
                ipyconsole = self.main.ipyconsole
613
 
                
614
 
                # Try to send code to the currently focused client. This is
615
 
                # necessary because several clients can be connected to the
616
 
                # same kernel
617
 
                ipyclient = ipyconsole.tabwidget.currentWidget()
618
 
                if ipyclient.kernel_widget_id == id(shellwidget):
619
 
                    ipyclient.ipython_widget.execute(unicode(lines))
620
 
                # This will send code to the first client whose kernel is
621
 
                # shellwidget. Useful in case the user has manually changed the
622
 
                # focused kernel.
623
 
                else:
624
 
                    ipw = ipyconsole.get_ipython_widget(id(shellwidget))
625
 
                    ipw.execute(unicode(lines))
626
 
                    ipw.setFocus()
627
 
            else:
628
 
                shellwidget.shell.execute_lines(unicode(lines))
629
 
                shellwidget.shell.setFocus()
630
 
            
631
 
    def pdb_has_stopped(self, fname, lineno, shellwidget):
632
 
        """Python debugger has just stopped at frame (fname, lineno)"""      
633
 
        # This is a unique form of the edit_goto signal that is intended to 
634
 
        # prevent keyboard input from accidentally entering the editor
635
 
        # during repeated, rapid entry of debugging commands.    
636
 
        self.emit(SIGNAL("edit_goto(QString,int,QString,bool)"),
637
 
                  fname, lineno, '',False)
638
 
        if shellwidget.is_ipython_kernel:
639
 
            # Focus client widget, not kernel
640
 
            ipw = self.main.ipyconsole.get_focus_widget()
641
 
            ipw.setFocus()
642
 
        else:
643
 
            shellwidget.shell.setFocus()
644
 
        
645
 
    def start(self, fname, wdir=None, args='', interact=False, debug=False,
646
 
              python=True, ipython_kernel=False, ipython_client=False,
647
 
              python_args=''):
648
 
        """
649
 
        Start new console
650
 
        
651
 
        fname:
652
 
          string: filename of script to run
653
 
          None: open an interpreter
654
 
        wdir: working directory
655
 
        args: command line options of the Python script
656
 
        interact: inspect script interactively after its execution
657
 
        debug: run pdb
658
 
        python: True: Python interpreter, False: terminal
659
 
        ipython_kernel: True: IPython kernel
660
 
        ipython_client: True: Automatically create an IPython client
661
 
        python_args: additionnal Python interpreter command line options
662
 
                   (option "-u" is mandatory, see widgets.externalshell package)
663
 
        """
664
 
        # Note: fname is None <=> Python interpreter
665
 
        if fname is not None and not isinstance(fname, basestring):
666
 
            fname = unicode(fname)
667
 
        if wdir is not None and not isinstance(wdir, basestring):
668
 
            wdir = unicode(wdir)
669
 
        
670
 
        if fname is not None and fname in self.filenames:
671
 
            index = self.filenames.index(fname)
672
 
            if self.get_option('single_tab'):
673
 
                old_shell = self.shellwidgets[index]
674
 
                if old_shell.is_running():
675
 
                    answer = QMessageBox.question(self, self.get_plugin_title(),
676
 
                        _("%s is already running in a separate process.\n"
677
 
                          "Do you want to kill the process before starting "
678
 
                          "a new one?") % osp.basename(fname),
679
 
                        QMessageBox.Yes | QMessageBox.Cancel)
680
 
                    if answer == QMessageBox.Yes:
681
 
                        old_shell.process.kill()
682
 
                        old_shell.process.waitForFinished()
683
 
                    else:
684
 
                        return
685
 
                self.close_console(index)
686
 
        else:
687
 
            index = self.tabwidget.count()
688
 
 
689
 
        # Creating a new external shell
690
 
        pythonpath = self.main.get_spyder_pythonpath()
691
 
        light_background = self.get_option('light_background')
692
 
        show_elapsed_time = self.get_option('show_elapsed_time')
693
 
        if python:
694
 
            pythonexecutable = self.get_option('pythonexecutable')
695
 
            if self.get_option('pythonstartup/default', True):
696
 
                pythonstartup = None
697
 
            else:
698
 
                pythonstartup = self.get_option('pythonstartup', None)
699
 
            monitor_enabled = self.get_option('monitor/enabled')
700
 
            mpl_patch_enabled = self.get_option('matplotlib/patch')
701
 
            if self.get_option('matplotlib/backend/enabled'):
702
 
                mpl_backend = self.get_option('matplotlib/backend/value')
703
 
            else:
704
 
                mpl_backend = None
705
 
            ets_backend = self.get_option('ets_backend', 'qt4')
706
 
            qt_api = self.get_option('qt/api')
707
 
            if qt_api not in ('pyqt', 'pyside'):
708
 
                qt_api = None
709
 
            install_qt_inputhook = self.get_option('qt/install_inputhook')
710
 
            pyqt_api = self.get_option('pyqt/api_version', 0)
711
 
            ignore_sip_setapi_errors = self.get_option(
712
 
                                            'pyqt/ignore_sip_setapi_errors')
713
 
            merge_output_channels = self.get_option('merge_output_channels')
714
 
            colorize_sys_stderr = self.get_option('colorize_sys_stderr')
715
 
            umd_enabled = self.get_option('umd/enabled')
716
 
            umd_namelist = self.get_option('umd/namelist')
717
 
            umd_verbose = self.get_option('umd/verbose')
718
 
            ar_timeout = CONF.get('variable_explorer', 'autorefresh/timeout')
719
 
            ar_state = CONF.get('variable_explorer', 'autorefresh')
720
 
            if self.light_mode:
721
 
                from spyderlib.plugins.variableexplorer import VariableExplorer
722
 
                sa_settings = VariableExplorer.get_settings()
723
 
            else:
724
 
                sa_settings = None
725
 
            shellwidget = ExternalPythonShell(self, fname, wdir,
726
 
                           interact, debug, path=pythonpath,
727
 
                           python_args=python_args,
728
 
                           ipython_kernel=ipython_kernel,
729
 
                           arguments=args, stand_alone=sa_settings,
730
 
                           pythonstartup=pythonstartup,
731
 
                           pythonexecutable=pythonexecutable,
732
 
                           umd_enabled=umd_enabled, umd_namelist=umd_namelist,
733
 
                           umd_verbose=umd_verbose, ets_backend=ets_backend,
734
 
                           monitor_enabled=monitor_enabled,
735
 
                           mpl_patch_enabled=mpl_patch_enabled,
736
 
                           mpl_backend=mpl_backend,
737
 
                           qt_api=qt_api, pyqt_api=pyqt_api,
738
 
                           install_qt_inputhook=install_qt_inputhook,
739
 
                           ignore_sip_setapi_errors=ignore_sip_setapi_errors,
740
 
                           merge_output_channels=merge_output_channels,
741
 
                           colorize_sys_stderr=colorize_sys_stderr,
742
 
                           autorefresh_timeout=ar_timeout,
743
 
                           autorefresh_state=ar_state,
744
 
                           light_background=light_background,
745
 
                           menu_actions=self.menu_actions,
746
 
                           show_buttons_inside=False,
747
 
                           show_elapsed_time=show_elapsed_time)
748
 
            self.connect(shellwidget, SIGNAL('pdb(QString,int)'),
749
 
                         lambda fname, lineno, shellwidget=shellwidget:
750
 
                         self.pdb_has_stopped(fname, lineno, shellwidget))
751
 
            self.register_widget_shortcuts("Console", shellwidget.shell)
752
 
        else:
753
 
            if os.name == 'posix':
754
 
                cmd = 'gnome-terminal'
755
 
                args = []
756
 
                if programs.is_program_installed(cmd):
757
 
                    if wdir:
758
 
                        args.extend(['--working-directory=%s' % wdir])
759
 
                    programs.run_program(cmd, args)
760
 
                    return
761
 
                cmd = 'konsole'
762
 
                if programs.is_program_installed(cmd):
763
 
                    if wdir:
764
 
                        args.extend(['--workdir', wdir])
765
 
                    programs.run_program(cmd, args)
766
 
                    return
767
 
            shellwidget = ExternalSystemShell(self, wdir, path=pythonpath,
768
 
                                          light_background=light_background,
769
 
                                          menu_actions=self.menu_actions,
770
 
                                          show_buttons_inside=False,
771
 
                                          show_elapsed_time=show_elapsed_time)
772
 
        
773
 
        # Code completion / calltips
774
 
        shellwidget.shell.setMaximumBlockCount(
775
 
                                            self.get_option('max_line_count') )
776
 
        shellwidget.shell.set_font( self.get_plugin_font() )
777
 
        shellwidget.shell.toggle_wrap_mode( self.get_option('wrap') )
778
 
        shellwidget.shell.set_calltips( self.get_option('calltips') )
779
 
        shellwidget.shell.set_codecompletion_auto(
780
 
                            self.get_option('codecompletion/auto') )
781
 
        shellwidget.shell.set_codecompletion_case(
782
 
                            self.get_option('codecompletion/case_sensitive') )
783
 
        shellwidget.shell.set_codecompletion_single(
784
 
                            self.get_option('codecompletion/show_single') )
785
 
        shellwidget.shell.set_codecompletion_enter(
786
 
                            self.get_option('codecompletion/enter_key') )
787
 
        if python and self.inspector is not None:
788
 
            shellwidget.shell.set_inspector(self.inspector)
789
 
            shellwidget.shell.set_inspector_enabled(
790
 
                                            self.get_option('object_inspector'))
791
 
        if self.historylog is not None:
792
 
            self.historylog.add_history(shellwidget.shell.history_filename)
793
 
            self.connect(shellwidget.shell,
794
 
                         SIGNAL('append_to_history(QString,QString)'),
795
 
                         self.historylog.append_to_history)
796
 
        self.connect(shellwidget.shell, SIGNAL("go_to_error(QString)"),
797
 
                     self.go_to_error)
798
 
        self.connect(shellwidget.shell, SIGNAL("focus_changed()"),
799
 
                     lambda: self.emit(SIGNAL("focus_changed()")))
800
 
        if python:
801
 
            if self.main.editor is not None:
802
 
                self.connect(shellwidget, SIGNAL('open_file(QString,int)'),
803
 
                             self.open_file_in_spyder)
804
 
            if fname is None:
805
 
                if ipython_kernel:
806
 
                    tab_name = _("Kernel")
807
 
                    tab_icon1 = get_icon('ipython_console.png')
808
 
                    tab_icon2 = get_icon('ipython_console_t.png')
809
 
                    if ipython_client:
810
 
                        self.connect(shellwidget,
811
 
                                 SIGNAL('create_ipython_client(QString)'),
812
 
                                 lambda cf: self.create_ipython_client(
813
 
                                         cf, kernel_widget=shellwidget))
814
 
                else:
815
 
                    self.python_count += 1
816
 
                    tab_name = "Python %d" % self.python_count
817
 
                    tab_icon1 = get_icon('python.png')
818
 
                    tab_icon2 = get_icon('python_t.png')
819
 
            else:
820
 
                tab_name = osp.basename(fname)
821
 
                tab_icon1 = get_icon('run.png')
822
 
                tab_icon2 = get_icon('terminated.png')
823
 
        else:
824
 
            fname = id(shellwidget)
825
 
            if os.name == 'nt':
826
 
                tab_name = _("Command Window")
827
 
            else:
828
 
                tab_name = _("Terminal")
829
 
            self.terminal_count += 1
830
 
            tab_name += (" %d" % self.terminal_count)
831
 
            tab_icon1 = get_icon('cmdprompt.png')
832
 
            tab_icon2 = get_icon('cmdprompt_t.png')
833
 
        self.shellwidgets.insert(index, shellwidget)
834
 
        self.filenames.insert(index, fname)
835
 
        self.icons.insert(index, (tab_icon1, tab_icon2))
836
 
        if index is None:
837
 
            index = self.tabwidget.addTab(shellwidget, tab_name)
838
 
        else:
839
 
            self.tabwidget.insertTab(index, shellwidget, tab_name)
840
 
        
841
 
        self.connect(shellwidget, SIGNAL("started()"),
842
 
                     lambda sid=id(shellwidget): self.process_started(sid))
843
 
        self.connect(shellwidget, SIGNAL("finished()"),
844
 
                     lambda sid=id(shellwidget): self.process_finished(sid))
845
 
        self.find_widget.set_editor(shellwidget.shell)
846
 
        self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir)
847
 
        self.tabwidget.setCurrentIndex(index)
848
 
        if self.dockwidget and not self.ismaximized and not ipython_kernel:
849
 
            self.dockwidget.setVisible(True)
850
 
            self.dockwidget.raise_()
851
 
        
852
 
        shellwidget.set_icontext_visible(self.get_option('show_icontext'))
853
 
        
854
 
        # Start process and give focus to console
855
 
        shellwidget.start_shell()
856
 
        if not ipython_kernel:
857
 
            shellwidget.shell.setFocus()
858
 
        
859
 
    def set_ipython_kernel_attrs(self, connection_file, kernel_widget):
860
 
        """Add the pid of the kernel process to an IPython kernel tab"""
861
 
        # Set connection file
862
 
        kernel_widget.connection_file = connection_file
863
 
        
864
 
        # If we've reached this point then it's safe to assume IPython
865
 
        # is available, and this import should be valid.
866
 
        from IPython.core.application import get_ipython_dir
867
 
        # For each kernel we launch, setup to delete the associated
868
 
        # connection file at the time Spyder exits.
869
 
        def cleanup_connection_file(connection_file):
870
 
            """Clean up the connection file for this console at exit"""
871
 
            connection_file = osp.join(get_ipython_dir(), 'profile_default',
872
 
                                   'security', connection_file)
873
 
            try:
874
 
                os.remove(connection_file)
875
 
            except OSError:
876
 
                pass
877
 
        atexit.register(cleanup_connection_file, connection_file)              
878
 
        
879
 
        # Set tab name
880
 
        index = self.get_shell_index_from_id(id(kernel_widget))
881
 
        match = re.match('^kernel-(\d+).json', connection_file)
882
 
        if match is not None:  # should not fail, but we never know...
883
 
            text = unicode(self.tabwidget.tabText(index))
884
 
            name = "%s (%s)" % (text, match.groups()[0])
885
 
            self.tabwidget.setTabText(index, name)
886
 
    
887
 
    def create_ipython_client(self, connection_file, kernel_widget):
888
 
        """Create a new IPython client connected to a kernel just started"""
889
 
        self.set_ipython_kernel_attrs(connection_file, kernel_widget)
890
 
        self.main.ipyconsole.new_client(connection_file, id(kernel_widget))
891
 
        # QApplication.restoreOverrideCursor()  # Stop busy cursor indication
892
 
        # QApplication.processEvents()
893
 
        
894
 
    def open_file_in_spyder(self, fname, lineno):
895
 
        """Open file in Spyder's editor from remote process"""
896
 
        self.main.editor.activateWindow()
897
 
        self.main.editor.raise_()
898
 
        self.main.editor.load(fname, lineno)
899
 
        
900
 
    #------ Private API -------------------------------------------------------
901
 
    def process_started(self, shell_id):
902
 
        index = self.get_shell_index_from_id(shell_id)
903
 
        shell = self.shellwidgets[index]
904
 
        icon, _icon = self.icons[index]
905
 
        self.tabwidget.setTabIcon(index, icon)
906
 
        if self.inspector is not None:
907
 
            self.inspector.set_shell(shell.shell)
908
 
        if self.variableexplorer is not None:
909
 
            self.variableexplorer.add_shellwidget(shell)
910
 
        
911
 
    def process_finished(self, shell_id):
912
 
        index = self.get_shell_index_from_id(shell_id)
913
 
        if index is not None:
914
 
            # Not sure why it happens, but sometimes the shellwidget has 
915
 
            # already been removed, so that's not bad if we can't change
916
 
            # the tab icon...
917
 
            _icon, icon = self.icons[index]
918
 
            self.tabwidget.setTabIcon(index, icon)
919
 
        if self.variableexplorer is not None:
920
 
            self.variableexplorer.remove_shellwidget(shell_id)
921
 
        
922
 
    #------ SpyderPluginWidget API --------------------------------------------
923
 
    def get_plugin_title(self):
924
 
        """Return widget title"""
925
 
        title = _('Console')
926
 
        if self.filenames:
927
 
            index = self.tabwidget.currentIndex()
928
 
            fname = self.filenames[index]
929
 
            if fname:
930
 
                title += ' - '+unicode(fname)
931
 
        return title
932
 
    
933
 
    def get_plugin_icon(self):
934
 
        """Return widget icon"""
935
 
        return get_icon('console.png')
936
 
    
937
 
    def get_focus_widget(self):
938
 
        """
939
 
        Return the widget to give focus to when
940
 
        this plugin's dockwidget is raised on top-level
941
 
        """
942
 
        return self.tabwidget.currentWidget()
943
 
        
944
 
    def get_plugin_actions(self):
945
 
        """Return a list of actions related to plugin"""
946
 
        interpreter_action = create_action(self,
947
 
                            _("Open a Python &interpreter"), None,
948
 
                            'python.png', triggered=self.open_interpreter)
949
 
        if os.name == 'nt':
950
 
            text = _("Open &command prompt")
951
 
            tip = _("Open a Windows command prompt")
952
 
        else:
953
 
            text = _("Open &terminal")
954
 
            tip = _("Open a terminal window inside Spyder")
955
 
        terminal_action = create_action(self, text, None, 'cmdprompt.png', tip,
956
 
                                        triggered=self.open_terminal)
957
 
        run_action = create_action(self,
958
 
                            _("&Run..."), None,
959
 
                            'run_small.png', _("Run a Python script"),
960
 
                            triggered=self.run_script)
961
 
 
962
 
        interact_menu_actions = [interpreter_action]
963
 
        tools_menu_actions = [terminal_action]
964
 
        self.menu_actions = [interpreter_action, terminal_action, run_action]
965
 
        
966
 
        self.ipython_kernel_action = create_action(self,
967
 
                                           _("Open an IPython console"), None,
968
 
                                           'ipython_console.png',
969
 
                                           triggered=self.start_ipython_kernel)
970
 
        if programs.is_module_installed('IPython.frontend.qt', '>=0.13'):
971
 
            interact_menu_actions.append(self.ipython_kernel_action)
972
 
        self.main.interact_menu_actions += interact_menu_actions
973
 
        self.main.tools_menu_actions += tools_menu_actions
974
 
        
975
 
        return self.menu_actions+interact_menu_actions+tools_menu_actions
976
 
    
977
 
    def register_plugin(self):
978
 
        """Register plugin in Spyder's main window"""
979
 
        if self.main.light:
980
 
            self.main.setCentralWidget(self)
981
 
            self.main.widgetlist.append(self)
982
 
        else:
983
 
            self.main.add_dockwidget(self)
984
 
            self.inspector = self.main.inspector
985
 
            if self.inspector is not None:
986
 
                self.inspector.set_external_console(self)
987
 
            self.historylog = self.main.historylog
988
 
            self.connect(self, SIGNAL("edit_goto(QString,int,QString)"),
989
 
                         self.main.editor.load)
990
 
            self.connect(self, SIGNAL("edit_goto(QString,int,QString,bool)"),
991
 
                         lambda fname, lineno, word, processevents:
992
 
                         self.main.editor.load(fname,lineno,word,
993
 
                                               processevents=processevents))
994
 
            self.connect(self.main.editor,
995
 
                         SIGNAL('run_in_current_console(QString,QString,QString,bool)'),
996
 
                         self.run_script_in_current_shell)
997
 
            self.connect(self.main.editor, SIGNAL("open_dir(QString)"),
998
 
                         self.set_current_shell_working_directory)
999
 
            self.connect(self.main.workingdirectory,
1000
 
                         SIGNAL("set_current_console_wd(QString)"),
1001
 
                         self.set_current_shell_working_directory)
1002
 
            self.connect(self, SIGNAL('focus_changed()'),
1003
 
                         self.main.plugin_focus_changed)
1004
 
            self.connect(self, SIGNAL('redirect_stdio(bool)'),
1005
 
                         self.main.redirect_internalshell_stdio)
1006
 
            expl = self.main.explorer
1007
 
            if expl is not None:
1008
 
                self.connect(expl, SIGNAL("open_terminal(QString)"),
1009
 
                             self.open_terminal)
1010
 
                self.connect(expl, SIGNAL("open_interpreter(QString)"),
1011
 
                             self.open_interpreter)
1012
 
            pexpl = self.main.projectexplorer
1013
 
            if pexpl is not None:
1014
 
                self.connect(pexpl, SIGNAL("open_terminal(QString)"),
1015
 
                             self.open_terminal)
1016
 
                self.connect(pexpl, SIGNAL("open_interpreter(QString)"),
1017
 
                             self.open_interpreter)
1018
 
        
1019
 
    def closing_plugin(self, cancelable=False):
1020
 
        """Perform actions before parent main window is closed"""
1021
 
        for shellwidget in self.shellwidgets:
1022
 
            shellwidget.close()
1023
 
        return True
1024
 
    
1025
 
    def refresh_plugin(self):
1026
 
        """Refresh tabwidget"""
1027
 
        shellwidget = None
1028
 
        if self.tabwidget.count():
1029
 
            shellwidget = self.tabwidget.currentWidget()
1030
 
            editor = shellwidget.shell
1031
 
            editor.setFocus()
1032
 
            widgets = [shellwidget.create_time_label(), 5
1033
 
                       ]+shellwidget.get_toolbar_buttons()+[5]
1034
 
        else:
1035
 
            editor = None
1036
 
            widgets = []
1037
 
        self.find_widget.set_editor(editor)
1038
 
        self.tabwidget.set_corner_widgets({Qt.TopRightCorner: widgets})
1039
 
        if shellwidget:
1040
 
            shellwidget.update_time_label_visibility()
1041
 
        self.emit(SIGNAL('update_plugin_title()'))
1042
 
    
1043
 
    def apply_plugin_settings(self, options):
1044
 
        """Apply configuration file's plugin settings"""
1045
 
        font = self.get_plugin_font()
1046
 
        showtime = self.get_option('show_elapsed_time')
1047
 
        icontext = self.get_option('show_icontext')
1048
 
        calltips = self.get_option('calltips')
1049
 
        inspector = self.get_option('object_inspector')
1050
 
        wrap = self.get_option('wrap')
1051
 
        compauto = self.get_option('codecompletion/auto')
1052
 
        case_comp = self.get_option('codecompletion/case_sensitive')
1053
 
        show_single = self.get_option('codecompletion/show_single')
1054
 
        compenter = self.get_option('codecompletion/enter_key')
1055
 
        mlc = self.get_option('max_line_count')
1056
 
        for shellwidget in self.shellwidgets:
1057
 
            shellwidget.shell.set_font(font)
1058
 
            shellwidget.set_elapsed_time_visible(showtime)
1059
 
            shellwidget.set_icontext_visible(icontext)
1060
 
            shellwidget.shell.set_calltips(calltips)
1061
 
            if isinstance(shellwidget.shell, ExternalPythonShell):
1062
 
                shellwidget.shell.set_inspector_enabled(inspector)
1063
 
            shellwidget.shell.toggle_wrap_mode(wrap)
1064
 
            shellwidget.shell.set_codecompletion_auto(compauto)
1065
 
            shellwidget.shell.set_codecompletion_case(case_comp)
1066
 
            shellwidget.shell.set_codecompletion_single(show_single)
1067
 
            shellwidget.shell.set_codecompletion_enter(compenter)
1068
 
            shellwidget.shell.setMaximumBlockCount(mlc)
1069
 
    
1070
 
    #------ Public API ---------------------------------------------------------
1071
 
    def open_interpreter_at_startup(self):
1072
 
        """Open an interpreter or an IPython kernel at startup"""
1073
 
        if self.get_option('open_python_at_startup', True):
1074
 
            self.open_interpreter()
1075
 
        if CONF.get('ipython_console', 'open_ipython_at_startup', False):
1076
 
            self.start_ipython_kernel()
1077
 
            
1078
 
    def open_interpreter(self, wdir=None):
1079
 
        """Open interpreter"""
1080
 
        if wdir is None:
1081
 
            wdir = os.getcwdu()
1082
 
        if not self.main.light:
1083
 
            self.visibility_changed(True)
1084
 
        self.start(fname=None, wdir=unicode(wdir), args='',
1085
 
                   interact=True, debug=False, python=True)
1086
 
        
1087
 
    def start_ipython_kernel(self, wdir=None, create_client=True):
1088
 
        """Start new IPython kernel"""
1089
 
        # Add a WaitCursor visual indication, because it takes too much time
1090
 
        # to display a new console (3 to 5 secs). It's stopped in
1091
 
        # create_ipython_client
1092
 
        # QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
1093
 
        # QApplication.processEvents()
1094
 
        
1095
 
        if wdir is None:
1096
 
            wdir = os.getcwdu()
1097
 
        self.main.ipyconsole.visibility_changed(True)
1098
 
        self.start(fname=None, wdir=unicode(wdir), args='',
1099
 
                   interact=True, debug=False, python=True,
1100
 
                   ipython_kernel=True, ipython_client=create_client)
1101
 
 
1102
 
    def open_terminal(self, wdir=None):
1103
 
        """Open terminal"""
1104
 
        if wdir is None:
1105
 
            wdir = os.getcwdu()
1106
 
        self.start(fname=None, wdir=unicode(wdir), args='',
1107
 
                   interact=True, debug=False, python=False)
1108
 
        
1109
 
    def run_script(self):
1110
 
        """Run a Python script"""
1111
 
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
1112
 
        filename, _selfilter = getopenfilename(self, _("Run Python script"),
1113
 
                os.getcwdu(), _("Python scripts")+" (*.py ; *.pyw ; *.ipy)")
1114
 
        self.emit(SIGNAL('redirect_stdio(bool)'), True)
1115
 
        if filename:
1116
 
            self.start(fname=filename, wdir=None, args='',
1117
 
                       interact=False, debug=False)
1118
 
        
1119
 
    def set_umd_namelist(self):
1120
 
        """Set UMD excluded modules name list"""
1121
 
        arguments, valid = QInputDialog.getText(self, _('UMD'),
1122
 
                                  _('UMD excluded modules:\n'
1123
 
                                          '(example: guidata, guiqwt)'),
1124
 
                                  QLineEdit.Normal,
1125
 
                                  ", ".join(self.get_option('umd/namelist')))
1126
 
        if valid:
1127
 
            arguments = unicode(arguments)
1128
 
            if arguments:
1129
 
                namelist = arguments.replace(' ', '').split(',')
1130
 
                fixed_namelist = [module_name for module_name in namelist
1131
 
                                  if programs.is_module_installed(module_name)]
1132
 
                invalid = ", ".join(set(namelist)-set(fixed_namelist))
1133
 
                if invalid:
1134
 
                    QMessageBox.warning(self, _('UMD'),
1135
 
                                        _("The following modules are not "
1136
 
                                          "installed on your machine:\n%s"
1137
 
                                          ) % invalid, QMessageBox.Ok)
1138
 
                QMessageBox.information(self, _('UMD'),
1139
 
                                    _("Please note that these changes will "
1140
 
                                      "be applied only to new Python/IPython "
1141
 
                                      "interpreters"), QMessageBox.Ok)
1142
 
            else:
1143
 
                fixed_namelist = []
1144
 
            self.set_option('umd/namelist', fixed_namelist)
1145
 
        
1146
 
    def go_to_error(self, text):
1147
 
        """Go to error if relevant"""
1148
 
        match = get_error_match(unicode(text))
1149
 
        if match:
1150
 
            fname, lnb = match.groups()
1151
 
            self.emit(SIGNAL("edit_goto(QString,int,QString)"),
1152
 
                      osp.abspath(fname), int(lnb), '')
1153
 
            
1154
 
    #----Drag and drop
1155
 
    def dragEnterEvent(self, event):
1156
 
        """Reimplement Qt method
1157
 
        Inform Qt about the types of data that the widget accepts"""
1158
 
        source = event.mimeData()
1159
 
        if source.hasUrls():
1160
 
            if mimedata2url(source):
1161
 
                pathlist = mimedata2url(source)
1162
 
                shellwidget = self.tabwidget.currentWidget()
1163
 
                if all([is_python_script(unicode(qstr)) for qstr in pathlist]):
1164
 
                    event.acceptProposedAction()
1165
 
                elif shellwidget is None or not shellwidget.is_running():
1166
 
                    event.ignore()
1167
 
                else:
1168
 
                    event.acceptProposedAction()
1169
 
            else:
1170
 
                event.ignore()
1171
 
        elif source.hasText():
1172
 
            event.acceptProposedAction()            
1173
 
            
1174
 
    def dropEvent(self, event):
1175
 
        """Reimplement Qt method
1176
 
        Unpack dropped data and handle it"""
1177
 
        source = event.mimeData()
1178
 
        shellwidget = self.tabwidget.currentWidget()
1179
 
        if source.hasText():
1180
 
            qstr = source.text()
1181
 
            if is_python_script(unicode(qstr)):
1182
 
                self.start(qstr)
1183
 
            elif shellwidget:
1184
 
                shellwidget.shell.insert_text(qstr)
1185
 
        elif source.hasUrls():
1186
 
            pathlist = mimedata2url(source)
1187
 
            if all([is_python_script(unicode(qstr)) for qstr in pathlist]):
1188
 
                for fname in pathlist:
1189
 
                    self.start(fname)
1190
 
            elif shellwidget:
1191
 
                shellwidget.shell.drop_pathlist(pathlist)
1192
 
        event.acceptProposedAction()
1193
 
 
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# Copyright © 2009-2010 Pierre Raybaut
 
4
# Licensed under the terms of the MIT License
 
5
# (see spyderlib/__init__.py for details)
 
6
 
 
7
"""External Console plugin"""
 
8
 
 
9
# pylint: disable=C0103
 
10
# pylint: disable=R0903
 
11
# pylint: disable=R0911
 
12
# pylint: disable=R0201
 
13
 
 
14
from spyderlib.qt.QtGui import (QVBoxLayout, QMessageBox, QInputDialog,
 
15
                                QLineEdit, QPushButton, QGroupBox, QLabel,
 
16
                                QTabWidget, QFontComboBox, QHBoxLayout)
 
17
from spyderlib.qt.QtCore import SIGNAL, Qt
 
18
from spyderlib.qt.compat import getopenfilename
 
19
 
 
20
import sys
 
21
import os
 
22
import os.path as osp
 
23
import imp
 
24
import re
 
25
import atexit
 
26
 
 
27
# Local imports
 
28
from spyderlib.baseconfig import _, SCIENTIFIC_STARTUP
 
29
from spyderlib.config import CONF
 
30
from spyderlib.utils import programs
 
31
from spyderlib.utils.misc import (get_error_match, get_python_executable,
 
32
                                  remove_trailing_single_backslash,
 
33
                                  is_python_script)
 
34
from spyderlib.utils.qthelpers import get_icon, create_action, mimedata2url
 
35
from spyderlib.widgets.tabs import Tabs
 
36
from spyderlib.widgets.externalshell.pythonshell import ExternalPythonShell
 
37
from spyderlib.widgets.externalshell.systemshell import ExternalSystemShell
 
38
from spyderlib.widgets.findreplace import FindReplace
 
39
from spyderlib.plugins import SpyderPluginWidget, PluginConfigPage
 
40
 
 
41
 
 
42
def is_mpl_patch_available():
 
43
    """Return True if Matplotlib patch is available"""
 
44
    if programs.is_module_installed('matplotlib'):
 
45
        from spyderlib import mpl_patch
 
46
        return mpl_patch.is_available()
 
47
    else:
 
48
        return False
 
49
 
 
50
 
 
51
class ExternalConsoleConfigPage(PluginConfigPage):
 
52
    def __init__(self, plugin, parent):
 
53
        PluginConfigPage.__init__(self, plugin, parent)
 
54
        self.get_name = lambda: _("Console")
 
55
 
 
56
    def setup_page(self):
 
57
        interface_group = QGroupBox(_("Interface"))
 
58
        font_group = self.create_fontgroup(option=None, text=None,
 
59
                                    fontfilters=QFontComboBox.MonospacedFonts)
 
60
        newcb = self.create_checkbox
 
61
        singletab_box = newcb(_("One tab per script"), 'single_tab')
 
62
        showtime_box = newcb(_("Show elapsed time"), 'show_elapsed_time')
 
63
        icontext_box = newcb(_("Show icons and text"), 'show_icontext')
 
64
 
 
65
        # Interface Group
 
66
        interface_layout = QVBoxLayout()
 
67
        interface_layout.addWidget(singletab_box)
 
68
        interface_layout.addWidget(showtime_box)
 
69
        interface_layout.addWidget(icontext_box)
 
70
        interface_group.setLayout(interface_layout)
 
71
        
 
72
        # Source Code Group
 
73
        display_group = QGroupBox(_("Source code"))
 
74
        buffer_spin = self.create_spinbox(
 
75
                            _("Buffer: "), _(" lines"),
 
76
                            'max_line_count', min_=0, max_=1000000, step=100,
 
77
                            tip=_("Set maximum line count"))
 
78
        wrap_mode_box = newcb(_("Wrap lines"), 'wrap')
 
79
        merge_channels_box = newcb(
 
80
               _("Merge process standard output/error channels"),
 
81
               'merge_output_channels',
 
82
               tip=_("Merging the output channels of the process means that\n"
 
83
                     "the standard error won't be written in red anymore,\n"
 
84
                     "but this has the effect of speeding up display."))
 
85
        colorize_sys_stderr_box = newcb(
 
86
               _("Colorize standard error channel using ANSI escape codes"),
 
87
               'colorize_sys_stderr',
 
88
               tip=_("This method is the only way to have colorized standard\n"
 
89
                     "error channel when the output channels have been "
 
90
                     "merged."))
 
91
        self.connect(merge_channels_box, SIGNAL("toggled(bool)"),
 
92
                     colorize_sys_stderr_box.setEnabled)
 
93
        self.connect(merge_channels_box, SIGNAL("toggled(bool)"),
 
94
                     colorize_sys_stderr_box.setChecked)
 
95
        colorize_sys_stderr_box.setEnabled(
 
96
                                    self.get_option('merge_output_channels'))
 
97
        
 
98
        display_layout = QVBoxLayout()
 
99
        display_layout.addWidget(buffer_spin)
 
100
        display_layout.addWidget(wrap_mode_box)
 
101
        display_layout.addWidget(merge_channels_box)
 
102
        display_layout.addWidget(colorize_sys_stderr_box)
 
103
        display_group.setLayout(display_layout)
 
104
        
 
105
        # Background Color Group
 
106
        bg_group = QGroupBox(_("Background color"))
 
107
        bg_label = QLabel(_("This option will be applied the next time "
 
108
                            "a Python console or a terminal is opened."))
 
109
        bg_label.setWordWrap(True)
 
110
        lightbg_box = newcb(_("Light background (white color)"),
 
111
                            'light_background')
 
112
        bg_layout = QVBoxLayout()
 
113
        bg_layout.addWidget(bg_label)
 
114
        bg_layout.addWidget(lightbg_box)
 
115
        bg_group.setLayout(bg_layout)
 
116
 
 
117
        # Advanced settings
 
118
        source_group = QGroupBox(_("Source code"))
 
119
        completion_box = newcb(_("Automatic code completion"),
 
120
                               'codecompletion/auto')
 
121
        case_comp_box = newcb(_("Case sensitive code completion"),
 
122
                              'codecompletion/case_sensitive')
 
123
        show_single_box = newcb(_("Show single completion"),
 
124
                               'codecompletion/show_single')
 
125
        comp_enter_box = newcb(_("Enter key selects completion"),
 
126
                               'codecompletion/enter_key')
 
127
        calltips_box = newcb(_("Balloon tips"), 'calltips')
 
128
        inspector_box = newcb(
 
129
                  _("Automatic notification to object inspector"),
 
130
                  'object_inspector', default=True,
 
131
                  tip=_("If this option is enabled, object inspector\n"
 
132
                        "will automatically show informations on functions\n"
 
133
                        "entered in console (this is triggered when entering\n"
 
134
                        "a left parenthesis after a valid function name)"))
 
135
        
 
136
        source_layout = QVBoxLayout()
 
137
        source_layout.addWidget(completion_box)
 
138
        source_layout.addWidget(case_comp_box)
 
139
        source_layout.addWidget(show_single_box)
 
140
        source_layout.addWidget(comp_enter_box)
 
141
        source_layout.addWidget(calltips_box)
 
142
        source_layout.addWidget(inspector_box)
 
143
        source_group.setLayout(source_layout)
 
144
 
 
145
        # UMD Group
 
146
        umd_group = QGroupBox(_("User Module Deleter (UMD)"))
 
147
        umd_label = QLabel(_("UMD forces Python to reload modules which were "
 
148
                             "imported when executing a \nscript in the "
 
149
                             "external console with the 'runfile' function."))
 
150
        umd_enabled_box = newcb(_("Enable UMD"), 'umd/enabled',
 
151
                                msg_if_enabled=True, msg_warning=_(
 
152
                        "This option will enable the User Module Deleter (UMD) "
 
153
                        "in Python interpreters. UMD forces Python to "
 
154
                        "reload deeply modules during import when running a "
 
155
                        "Python script using the Spyder's builtin function "
 
156
                        "<b>runfile</b>."
 
157
                        "<br><br><b>1.</b> UMD may require to restart the "
 
158
                        "Python interpreter in which it will be called "
 
159
                        "(otherwise only newly imported modules will be "
 
160
                        "reloaded when executing scripts)."
 
161
                        "<br><br><b>2.</b> If errors occur when re-running a "
 
162
                        "PyQt-based program, please check that the Qt objects "
 
163
                        "are properly destroyed (e.g. you may have to use the "
 
164
                        "attribute <b>Qt.WA_DeleteOnClose</b> on your main "
 
165
                        "window, using the <b>setAttribute</b> method)"),
 
166
                                )
 
167
        umd_verbose_box = newcb(_("Show reloaded modules list"),
 
168
                                'umd/verbose', msg_info=_(
 
169
                                "Please note that these changes will "
 
170
                                "be applied only to new Python interpreters"))
 
171
        umd_namelist_btn = QPushButton(
 
172
                            _("Set UMD excluded (not reloaded) modules"))
 
173
        self.connect(umd_namelist_btn, SIGNAL('clicked()'),
 
174
                     self.plugin.set_umd_namelist)
 
175
        
 
176
        umd_layout = QVBoxLayout()
 
177
        umd_layout.addWidget(umd_label)
 
178
        umd_layout.addWidget(umd_enabled_box)
 
179
        umd_layout.addWidget(umd_verbose_box)
 
180
        umd_layout.addWidget(umd_namelist_btn)
 
181
        umd_group.setLayout(umd_layout)
 
182
        
 
183
        # Python executable Group
 
184
        pyexec_group = QGroupBox(_("Python executable"))
 
185
        pyexec_label = QLabel(_("Path to Python interpreter "
 
186
                                "executable binary:"))
 
187
        if os.name == 'nt':
 
188
            filters = _("Executables")+" (*.exe)"
 
189
        else:
 
190
            filters = None
 
191
        pyexec_file = self.create_browsefile('', 'pythonexecutable',
 
192
                                             filters=filters)
 
193
        
 
194
        pyexec_layout = QVBoxLayout()
 
195
        pyexec_layout.addWidget(pyexec_label)
 
196
        pyexec_layout.addWidget(pyexec_file)
 
197
        pyexec_group.setLayout(pyexec_layout)
 
198
        
 
199
        # Startup Group
 
200
        startup_group = QGroupBox(_("Startup"))
 
201
        pystartup_box = newcb(_("Open a Python interpreter at startup"),
 
202
                              'open_python_at_startup')
 
203
        
 
204
        startup_layout = QVBoxLayout()
 
205
        startup_layout.addWidget(pystartup_box)
 
206
        startup_group.setLayout(startup_layout)
 
207
        
 
208
        # PYTHONSTARTUP replacement
 
209
        pystartup_group = QGroupBox(_("PYTHONSTARTUP replacement"))
 
210
        pystartup_label = QLabel(_("This option will override the "
 
211
                                   "PYTHONSTARTUP environment variable which\n"
 
212
                                   "defines the script to be executed during "
 
213
                                   "the Python interpreter startup."))
 
214
        default_radio = self.create_radiobutton(
 
215
                                        _("Default PYTHONSTARTUP script"),
 
216
                                        'pythonstartup/default', True)
 
217
        custom_radio = self.create_radiobutton(
 
218
                                        _("Use the following startup script:"),
 
219
                                        'pythonstartup/custom', False)
 
220
        pystartup_file = self.create_browsefile('', 'pythonstartup', '',
 
221
                                                filters=_("Python scripts")+\
 
222
                                                " (*.py)")
 
223
        self.connect(default_radio, SIGNAL("toggled(bool)"),
 
224
                     pystartup_file.setDisabled)
 
225
        self.connect(custom_radio, SIGNAL("toggled(bool)"),
 
226
                     pystartup_file.setEnabled)
 
227
        
 
228
        pystartup_layout = QVBoxLayout()
 
229
        pystartup_layout.addWidget(pystartup_label)
 
230
        pystartup_layout.addWidget(default_radio)
 
231
        pystartup_layout.addWidget(custom_radio)
 
232
        pystartup_layout.addWidget(pystartup_file)
 
233
        pystartup_group.setLayout(pystartup_layout)
 
234
        
 
235
        # Monitor Group
 
236
        monitor_group = QGroupBox(_("Monitor"))
 
237
        monitor_label = QLabel(_("The monitor provides introspection "
 
238
                                 "features to console: code completion, "
 
239
                                 "calltips and variable explorer. "
 
240
                                 "Because it relies on several modules, "
 
241
                                 "disabling the monitor may be useful "
 
242
                                 "to accelerate console startup."))
 
243
        monitor_label.setWordWrap(True)
 
244
        monitor_box = newcb(_("Enable monitor"), 'monitor/enabled')
 
245
        for obj in (completion_box, case_comp_box, show_single_box,
 
246
                    comp_enter_box, calltips_box):
 
247
            self.connect(monitor_box, SIGNAL("toggled(bool)"), obj.setEnabled)
 
248
            obj.setEnabled(self.get_option('monitor/enabled'))
 
249
        
 
250
        monitor_layout = QVBoxLayout()
 
251
        monitor_layout.addWidget(monitor_label)
 
252
        monitor_layout.addWidget(monitor_box)
 
253
        monitor_group.setLayout(monitor_layout)
 
254
        
 
255
        # Qt Group
 
256
        # Do not test if PyQt4 or PySide is installed with the function 
 
257
        # spyderlib.utils.programs.is_module_installed because it will 
 
258
        # fail (both libraries can't be imported at the same time):
 
259
        try:
 
260
            imp.find_module('PyQt4')
 
261
            has_pyqt4 = True
 
262
        except ImportError:
 
263
            has_pyqt4 = False
 
264
        try:
 
265
            imp.find_module('PySide')
 
266
            has_pyside = True
 
267
        except ImportError:
 
268
            has_pyside = False
 
269
        opts = []
 
270
        if has_pyqt4:
 
271
            opts.append( ('PyQt4', 'pyqt') )
 
272
        if has_pyside:
 
273
            opts.append( ('PySide', 'pyside') )
 
274
        qt_group = QGroupBox(_("Qt (PyQt/PySide)"))
 
275
        qt_setapi_box = self.create_combobox(
 
276
                         _("Qt-Python bindings library selection:"),
 
277
                         [(_("Default library"), 'default')]+opts,
 
278
                         'qt/api', default='default', tip=_(
 
279
"""This option will act on libraries such as Matplotlib, guidata or ETS"""))
 
280
        qt_hook_box = newcb(_("Install Spyder's input hook for Qt"),
 
281
                              'qt/install_inputhook',
 
282
                              tip=_(
 
283
"""PyQt installs an input hook that allows creating and interacting
 
284
with Qt widgets in an interactive interpreter without blocking it. 
 
285
On Windows platforms, it is strongly recommended to replace it by Spyder's. 
 
286
Regarding PySide, note that it does not install an input hook, so it is 
 
287
required to enable this feature in order to be able to manipulate PySide/Qt 
 
288
objects interactively."""))
 
289
        
 
290
        qt_layout = QVBoxLayout()
 
291
        qt_layout.addWidget(qt_setapi_box)
 
292
        qt_layout.addWidget(qt_hook_box)
 
293
        qt_group.setLayout(qt_layout)
 
294
        qt_group.setEnabled(has_pyqt4 or has_pyside)
 
295
        
 
296
        # PyQt Group
 
297
        if has_pyqt4:
 
298
            pyqt_group = QGroupBox(_("PyQt"))
 
299
            setapi_box = self.create_combobox(
 
300
                _("API selection for QString and QVariant objects:"),
 
301
                ((_("Default API"), 0), (_("API #1"), 1), (_("API #2"), 2)),
 
302
                'pyqt/api_version', default=0, tip=_(
 
303
"""PyQt API #1 is the default API for Python 2. PyQt API #2 is the default 
 
304
API for Python 3 and is compatible with PySide.
 
305
Note that switching to API #2 may require to enable the Matplotlib patch."""))
 
306
            ignore_api_box = newcb(_("Ignore API change errors (sip.setapi)"),
 
307
                                     'pyqt/ignore_sip_setapi_errors', tip=_(
 
308
"""Enabling this option will ignore errors when changing PyQt API.
 
309
As PyQt does not support dynamic API changes, it is strongly recommended
 
310
to use this feature wisely, e.g. for debugging purpose."""))
 
311
            try:
 
312
                from sip import setapi #analysis:ignore
 
313
            except ImportError:
 
314
                setapi_box.setDisabled(True)
 
315
                ignore_api_box.setDisabled(True)
 
316
            
 
317
            pyqt_layout = QVBoxLayout()
 
318
            pyqt_layout.addWidget(setapi_box)
 
319
            pyqt_layout.addWidget(ignore_api_box)
 
320
            pyqt_group.setLayout(pyqt_layout)
 
321
            qt_layout.addWidget(pyqt_group)
 
322
        
 
323
        # Matplotlib Group
 
324
        mpl_group = QGroupBox(_("Matplotlib"))
 
325
        mpl_backend_box = newcb('', 'matplotlib/backend/enabled', True)
 
326
        mpl_backend_edit = self.create_lineedit(_("GUI backend:"),
 
327
                                'matplotlib/backend/value', "Qt4Agg",
 
328
                                _("Set the GUI toolkit used by Matplotlib to "
 
329
                                  "show figures (default: Qt4Agg)"),
 
330
                                alignment=Qt.Horizontal)
 
331
        self.connect(mpl_backend_box, SIGNAL("toggled(bool)"),
 
332
                     mpl_backend_edit.setEnabled)
 
333
        mpl_backend_layout = QHBoxLayout()
 
334
        mpl_backend_layout.addWidget(mpl_backend_box)
 
335
        mpl_backend_layout.addWidget(mpl_backend_edit)
 
336
        mpl_backend_edit.setEnabled(
 
337
                                self.get_option('matplotlib/backend/enabled'))
 
338
        mpl_patch_box = newcb(_("Patch Matplotlib figures"),
 
339
                              'matplotlib/patch', False)
 
340
        mpl_patch_label = QLabel(_("Patching Matplotlib library will add a "
 
341
                                   "button to customize figure options "
 
342
                                   "(Qt4Agg only) and fix some issues."))
 
343
        mpl_patch_label.setWordWrap(True)
 
344
        self.connect(mpl_patch_box, SIGNAL("toggled(bool)"),
 
345
                     mpl_patch_label.setEnabled)
 
346
        
 
347
        mpl_installed = programs.is_module_installed('matplotlib')
 
348
        if not is_mpl_patch_available():
 
349
            mpl_patch_box.hide()
 
350
            mpl_patch_label.hide()
 
351
        
 
352
        mpl_layout = QVBoxLayout()
 
353
        mpl_layout.addLayout(mpl_backend_layout)
 
354
        mpl_layout.addWidget(mpl_patch_box)
 
355
        mpl_layout.addWidget(mpl_patch_label)
 
356
        mpl_group.setLayout(mpl_layout)
 
357
        mpl_group.setEnabled(mpl_installed)
 
358
        
 
359
        # ETS Group
 
360
        ets_group = QGroupBox(_("Enthought Tool Suite"))
 
361
        ets_label = QLabel(_("Enthought Tool Suite (ETS) supports "
 
362
                             "PyQt4 (qt4) and wxPython (wx) graphical "
 
363
                             "user interfaces."))
 
364
        ets_label.setWordWrap(True)
 
365
        ets_edit = self.create_lineedit(_("ETS_TOOLKIT:"), 'ets_backend',
 
366
                                        default='qt4', alignment=Qt.Horizontal)
 
367
        
 
368
        ets_layout = QVBoxLayout()
 
369
        ets_layout.addWidget(ets_label)
 
370
        ets_layout.addWidget(ets_edit)
 
371
        ets_group.setLayout(ets_layout)
 
372
        ets_group.setEnabled(programs.is_module_installed(
 
373
                                                    "enthought.etsconfig.api"))
 
374
        
 
375
        tabs = QTabWidget()
 
376
        tabs.addTab(self.create_tab(font_group, interface_group, display_group,
 
377
                                    bg_group),
 
378
                    _("Display"))
 
379
        tabs.addTab(self.create_tab(monitor_group, source_group),
 
380
                    _("Introspection"))
 
381
        tabs.addTab(self.create_tab(pyexec_group, startup_group,
 
382
                                    pystartup_group, umd_group),
 
383
                    _("Advanced settings"))
 
384
        tabs.addTab(self.create_tab(qt_group, mpl_group, ets_group),
 
385
                    _("External modules"))
 
386
        
 
387
        vlayout = QVBoxLayout()
 
388
        vlayout.addWidget(tabs)
 
389
        self.setLayout(vlayout)
 
390
 
 
391
 
 
392
class ExternalConsole(SpyderPluginWidget):
 
393
    """
 
394
    Console widget
 
395
    """
 
396
    CONF_SECTION = 'console'
 
397
    CONFIGWIDGET_CLASS = ExternalConsoleConfigPage
 
398
    def __init__(self, parent, light_mode):
 
399
        SpyderPluginWidget.__init__(self, parent)
 
400
        self.light_mode = light_mode
 
401
        self.tabwidget = None
 
402
        self.menu_actions = None
 
403
        
 
404
        self.inspector = None # Object inspector plugin
 
405
        self.historylog = None # History log plugin
 
406
        self.variableexplorer = None # Variable explorer plugin
 
407
        
 
408
        self.python_count = 0
 
409
        self.terminal_count = 0
 
410
 
 
411
        try:
 
412
            from sip import setapi #analysis:ignore
 
413
        except ImportError:
 
414
            self.set_option('pyqt/ignore_sip_setapi_errors', False)
 
415
 
 
416
        scientific = programs.is_module_installed('numpy') and\
 
417
                     programs.is_module_installed('scipy') and\
 
418
                     programs.is_module_installed('matplotlib')
 
419
        if self.get_option('pythonstartup/default', None) is None:
 
420
            self.set_option('pythonstartup/default', not scientific)
 
421
        if not osp.isfile(self.get_option('pythonstartup', '')):
 
422
            self.set_option('pythonstartup', SCIENTIFIC_STARTUP)
 
423
            self.set_option('pythonstartup/default', not scientific)
 
424
        # default/custom settings are mutually exclusive:
 
425
        self.set_option('pythonstartup/custom',
 
426
                        not self.get_option('pythonstartup/default'))
 
427
        
 
428
        executable = self.get_option('pythonexecutable',
 
429
                                     get_python_executable())
 
430
        if not osp.isfile(executable):
 
431
            # This is absolutely necessary, in case the Python interpreter
 
432
            # executable has been moved since last Spyder execution (following
 
433
            # a Python distribution upgrade for example)
 
434
            self.set_option('pythonexecutable', get_python_executable())
 
435
        elif executable.endswith('pythonw.exe'):
 
436
            # That should not be necessary because this case is already taken
 
437
            # care of by the `get_python_executable` function but, this was
 
438
            # implemented too late, so we have to fix it here too, in case
 
439
            # the Python executable has already been set with pythonw.exe:
 
440
            self.set_option('pythonexecutable',
 
441
                            executable.replace("pythonw.exe", "python.exe"))
 
442
        
 
443
        self.shellwidgets = []
 
444
        self.filenames = []
 
445
        self.icons = []
 
446
        self.runfile_args = ""
 
447
        
 
448
        # Initialize plugin
 
449
        self.initialize_plugin()
 
450
        
 
451
        layout = QVBoxLayout()
 
452
        self.tabwidget = Tabs(self, self.menu_actions)
 
453
        if hasattr(self.tabwidget, 'setDocumentMode')\
 
454
           and not sys.platform == 'darwin':
 
455
            # Don't set document mode to true on OSX because it generates
 
456
            # a crash when the console is detached from the main window
 
457
            # Fixes Issue 561
 
458
            self.tabwidget.setDocumentMode(True)
 
459
        self.connect(self.tabwidget, SIGNAL('currentChanged(int)'),
 
460
                     self.refresh_plugin)
 
461
        self.connect(self.tabwidget, SIGNAL('move_data(int,int)'),
 
462
                     self.move_tab)
 
463
        self.connect(self.main, SIGNAL("pythonpath_changed()"),
 
464
                     self.set_path)
 
465
                     
 
466
        self.tabwidget.set_close_function(self.close_console)
 
467
 
 
468
        layout.addWidget(self.tabwidget)
 
469
        
 
470
        # Find/replace widget
 
471
        self.find_widget = FindReplace(self)
 
472
        self.find_widget.hide()
 
473
        self.register_widget_shortcuts("Editor", self.find_widget)
 
474
        
 
475
        layout.addWidget(self.find_widget)
 
476
        
 
477
        self.setLayout(layout)
 
478
            
 
479
        # Accepting drops
 
480
        self.setAcceptDrops(True)
 
481
        
 
482
    def move_tab(self, index_from, index_to):
 
483
        """
 
484
        Move tab (tabs themselves have already been moved by the tabwidget)
 
485
        """
 
486
        filename = self.filenames.pop(index_from)
 
487
        shell = self.shellwidgets.pop(index_from)
 
488
        icons = self.icons.pop(index_from)
 
489
        
 
490
        self.filenames.insert(index_to, filename)
 
491
        self.shellwidgets.insert(index_to, shell)
 
492
        self.icons.insert(index_to, icons)
 
493
        self.emit(SIGNAL('update_plugin_title()'))
 
494
 
 
495
    def get_shell_index_from_id(self, shell_id):
 
496
        """Return shellwidget index from id"""
 
497
        for index, shell in enumerate(self.shellwidgets):
 
498
            if id(shell) == shell_id:
 
499
                return index
 
500
        
 
501
    def close_console(self, index=None, from_ipyclient=False):
 
502
        """Close console tab from index or widget (or close current tab)"""
 
503
        # Get tab index
 
504
        if not self.tabwidget.count():
 
505
            return
 
506
        if index is None:
 
507
            index = self.tabwidget.currentIndex()
 
508
        
 
509
        # Detect what widget we are trying to close
 
510
        for i, s in enumerate(self.shellwidgets):
 
511
            if index == i:
 
512
                shellwidget = s
 
513
        
 
514
        # If the tab is an IPython kernel, try to detect if it has a client
 
515
        # connected to it
 
516
        if shellwidget.is_ipykernel:
 
517
            ipyclients = self.main.ipyconsole.get_clients()
 
518
            if ipyclients:
 
519
                for ic in ipyclients:
 
520
                    if ic.kernel_widget_id == id(shellwidget):
 
521
                        connected_ipyclient = True
 
522
                        break
 
523
                else:
 
524
                    connected_ipyclient = False
 
525
            else:
 
526
                connected_ipyclient = False
 
527
        
 
528
        # Closing logic
 
529
        if not shellwidget.is_ipykernel or from_ipyclient or \
 
530
          not connected_ipyclient:
 
531
            self.tabwidget.widget(index).close()
 
532
            self.tabwidget.removeTab(index)
 
533
            self.filenames.pop(index)
 
534
            self.shellwidgets.pop(index)
 
535
            self.icons.pop(index)
 
536
            self.emit(SIGNAL('update_plugin_title()'))
 
537
        else:
 
538
            QMessageBox.question(self, _('Trying to kill a kernel?'),
 
539
                _("You can't close this kernel because it has one or more "
 
540
                  "consoles connected to it.<br><br>"
 
541
                  "You need to close them instead or you can kill the kernel "
 
542
                  "using the button far to the right."),
 
543
                  QMessageBox.Ok)
 
544
                                 
 
545
        
 
546
    def set_variableexplorer(self, variableexplorer):
 
547
        """Set variable explorer plugin"""
 
548
        self.variableexplorer = variableexplorer
 
549
    
 
550
    def set_path(self):
 
551
        """Set consoles PYTHONPATH if changed by the user"""
 
552
        from spyderlib.widgets.externalshell import pythonshell
 
553
        for sw in self.shellwidgets:
 
554
            if isinstance(sw, pythonshell.ExternalPythonShell):
 
555
                if sw.is_interpreter and sw.is_running():
 
556
                    sw.path = self.main.get_spyder_pythonpath()
 
557
                    sw.shell.path = sw.path
 
558
        
 
559
    def __find_python_shell(self, interpreter_only=False):
 
560
        current_index = self.tabwidget.currentIndex()
 
561
        if current_index == -1:
 
562
            return
 
563
        from spyderlib.widgets.externalshell import pythonshell
 
564
        for index in [current_index]+range(self.tabwidget.count()):
 
565
            shellwidget = self.tabwidget.widget(index)
 
566
            if isinstance(shellwidget, pythonshell.ExternalPythonShell):
 
567
                if interpreter_only and not shellwidget.is_interpreter:
 
568
                    continue
 
569
                elif not shellwidget.is_running():
 
570
                    continue
 
571
                else:
 
572
                    self.tabwidget.setCurrentIndex(index)
 
573
                    return shellwidget
 
574
    
 
575
    def get_current_shell(self):
 
576
        """
 
577
        Called by object inspector to retrieve the current shell instance
 
578
        """
 
579
        shellwidget = self.__find_python_shell()
 
580
        return shellwidget.shell
 
581
                
 
582
    def get_running_python_shell(self):
 
583
        """
 
584
        Called by object inspector to retrieve a running Python shell instance
 
585
        """
 
586
        current_index = self.tabwidget.currentIndex()
 
587
        if current_index == -1:
 
588
            return
 
589
        from spyderlib.widgets.externalshell import pythonshell
 
590
        shellwidgets = [self.tabwidget.widget(index)
 
591
                        for index in range(self.tabwidget.count())]
 
592
        shellwidgets = [_w for _w in shellwidgets
 
593
                        if isinstance(_w, pythonshell.ExternalPythonShell) \
 
594
                        and _w.is_running()]
 
595
        if shellwidgets:
 
596
            # First, iterate on interpreters only:
 
597
            for shellwidget in shellwidgets:
 
598
                if shellwidget.is_interpreter:
 
599
                    return shellwidget.shell
 
600
            else:
 
601
                return shellwidgets[0].shell
 
602
        
 
603
    def run_script_in_current_shell(self, filename, wdir, args, debug):
 
604
        """Run script in current shell, if any"""
 
605
        line = "%s(r'%s'" % ('debugfile' if debug else 'runfile',
 
606
                             unicode(filename))
 
607
        norm = lambda text: remove_trailing_single_backslash(unicode(text))
 
608
        if args:
 
609
            line += ", args=r'%s'" % norm(args)
 
610
        if wdir:
 
611
            line += ", wdir=r'%s'" % norm(wdir)
 
612
        line += ")"
 
613
        self.execute_python_code(line, interpreter_only=True)
 
614
            
 
615
    def set_current_shell_working_directory(self, directory):
 
616
        """Set current shell working directory"""
 
617
        shellwidget = self.__find_python_shell()
 
618
        if shellwidget is not None:
 
619
            shellwidget.shell.set_cwd(unicode(directory))
 
620
        
 
621
    def execute_python_code(self, lines, interpreter_only=False):
 
622
        """Execute Python code in an already opened Python interpreter"""
 
623
        shellwidget = self.__find_python_shell(
 
624
                                        interpreter_only=interpreter_only)
 
625
        if (shellwidget is not None) and (not shellwidget.is_ipykernel):
 
626
            shellwidget.shell.execute_lines(unicode(lines))
 
627
            self.activateWindow()
 
628
            shellwidget.shell.setFocus()
 
629
            
 
630
    def pdb_has_stopped(self, fname, lineno, shellwidget):
 
631
        """Python debugger has just stopped at frame (fname, lineno)"""      
 
632
        # This is a unique form of the edit_goto signal that is intended to 
 
633
        # prevent keyboard input from accidentally entering the editor
 
634
        # during repeated, rapid entry of debugging commands.    
 
635
        self.emit(SIGNAL("edit_goto(QString,int,QString,bool)"),
 
636
                  fname, lineno, '',False)
 
637
        if shellwidget.is_ipykernel:
 
638
            # Focus client widget, not kernel
 
639
            ipw = self.main.ipyconsole.get_focus_widget()
 
640
            self.main.ipyconsole.activateWindow()
 
641
            ipw.setFocus()
 
642
        else:
 
643
            self.activateWindow()
 
644
            shellwidget.shell.setFocus()
 
645
        
 
646
    def start(self, fname, wdir=None, args='', interact=False, debug=False,
 
647
              python=True, ipykernel=False, ipyclient=False,
 
648
              python_args=''):
 
649
        """
 
650
        Start new console
 
651
        
 
652
        fname:
 
653
          string: filename of script to run
 
654
          None: open an interpreter
 
655
        wdir: working directory
 
656
        args: command line options of the Python script
 
657
        interact: inspect script interactively after its execution
 
658
        debug: run pdb
 
659
        python: True: Python interpreter, False: terminal
 
660
        ipykernel: True: IPython kernel
 
661
        ipyclient: True: Automatically create an IPython client
 
662
        python_args: additionnal Python interpreter command line options
 
663
                   (option "-u" is mandatory, see widgets.externalshell package)
 
664
        """
 
665
        # Note: fname is None <=> Python interpreter
 
666
        if fname is not None and not isinstance(fname, basestring):
 
667
            fname = unicode(fname)
 
668
        if wdir is not None and not isinstance(wdir, basestring):
 
669
            wdir = unicode(wdir)
 
670
        
 
671
        if fname is not None and fname in self.filenames:
 
672
            index = self.filenames.index(fname)
 
673
            if self.get_option('single_tab'):
 
674
                old_shell = self.shellwidgets[index]
 
675
                if old_shell.is_running():
 
676
                    answer = QMessageBox.question(self, self.get_plugin_title(),
 
677
                        _("%s is already running in a separate process.\n"
 
678
                          "Do you want to kill the process before starting "
 
679
                          "a new one?") % osp.basename(fname),
 
680
                        QMessageBox.Yes | QMessageBox.Cancel)
 
681
                    if answer == QMessageBox.Yes:
 
682
                        old_shell.process.kill()
 
683
                        old_shell.process.waitForFinished()
 
684
                    else:
 
685
                        return
 
686
                self.close_console(index)
 
687
        else:
 
688
            index = self.tabwidget.count()
 
689
 
 
690
        # Creating a new external shell
 
691
        pythonpath = self.main.get_spyder_pythonpath()
 
692
        light_background = self.get_option('light_background')
 
693
        show_elapsed_time = self.get_option('show_elapsed_time')
 
694
        if python:
 
695
            pythonexecutable = self.get_option('pythonexecutable')
 
696
            if self.get_option('pythonstartup/default', True):
 
697
                pythonstartup = None
 
698
            else:
 
699
                pythonstartup = self.get_option('pythonstartup', None)
 
700
            monitor_enabled = self.get_option('monitor/enabled')
 
701
            mpl_patch_enabled = is_mpl_patch_available() and\
 
702
                                self.get_option('matplotlib/patch')
 
703
            if self.get_option('matplotlib/backend/enabled'):
 
704
                mpl_backend = self.get_option('matplotlib/backend/value')
 
705
            else:
 
706
                mpl_backend = None
 
707
            ets_backend = self.get_option('ets_backend', 'qt4')
 
708
            qt_api = self.get_option('qt/api')
 
709
            if qt_api not in ('pyqt', 'pyside'):
 
710
                qt_api = None
 
711
            install_qt_inputhook = self.get_option('qt/install_inputhook')
 
712
            pyqt_api = self.get_option('pyqt/api_version', 0)
 
713
            ignore_sip_setapi_errors = self.get_option(
 
714
                                            'pyqt/ignore_sip_setapi_errors')
 
715
            merge_output_channels = self.get_option('merge_output_channels')
 
716
            colorize_sys_stderr = self.get_option('colorize_sys_stderr')
 
717
            umd_enabled = self.get_option('umd/enabled')
 
718
            umd_namelist = self.get_option('umd/namelist')
 
719
            umd_verbose = self.get_option('umd/verbose')
 
720
            ar_timeout = CONF.get('variable_explorer', 'autorefresh/timeout')
 
721
            ar_state = CONF.get('variable_explorer', 'autorefresh')
 
722
            if self.light_mode:
 
723
                from spyderlib.plugins.variableexplorer import VariableExplorer
 
724
                sa_settings = VariableExplorer.get_settings()
 
725
            else:
 
726
                sa_settings = None
 
727
            shellwidget = ExternalPythonShell(self, fname, wdir,
 
728
                           interact, debug, path=pythonpath,
 
729
                           python_args=python_args,
 
730
                           ipykernel=ipykernel,
 
731
                           arguments=args, stand_alone=sa_settings,
 
732
                           pythonstartup=pythonstartup,
 
733
                           pythonexecutable=pythonexecutable,
 
734
                           umd_enabled=umd_enabled, umd_namelist=umd_namelist,
 
735
                           umd_verbose=umd_verbose, ets_backend=ets_backend,
 
736
                           monitor_enabled=monitor_enabled,
 
737
                           mpl_patch_enabled=mpl_patch_enabled,
 
738
                           mpl_backend=mpl_backend,
 
739
                           qt_api=qt_api, pyqt_api=pyqt_api,
 
740
                           install_qt_inputhook=install_qt_inputhook,
 
741
                           ignore_sip_setapi_errors=ignore_sip_setapi_errors,
 
742
                           merge_output_channels=merge_output_channels,
 
743
                           colorize_sys_stderr=colorize_sys_stderr,
 
744
                           autorefresh_timeout=ar_timeout,
 
745
                           autorefresh_state=ar_state,
 
746
                           light_background=light_background,
 
747
                           menu_actions=self.menu_actions,
 
748
                           show_buttons_inside=False,
 
749
                           show_elapsed_time=show_elapsed_time)
 
750
            self.connect(shellwidget, SIGNAL('pdb(QString,int)'),
 
751
                         lambda fname, lineno, shellwidget=shellwidget:
 
752
                         self.pdb_has_stopped(fname, lineno, shellwidget))
 
753
            self.register_widget_shortcuts("Console", shellwidget.shell)
 
754
        else:
 
755
            if os.name == 'posix':
 
756
                cmd = 'gnome-terminal'
 
757
                args = []
 
758
                if programs.is_program_installed(cmd):
 
759
                    if wdir:
 
760
                        args.extend(['--working-directory=%s' % wdir])
 
761
                    programs.run_program(cmd, args)
 
762
                    return
 
763
                cmd = 'konsole'
 
764
                if programs.is_program_installed(cmd):
 
765
                    if wdir:
 
766
                        args.extend(['--workdir', wdir])
 
767
                    programs.run_program(cmd, args)
 
768
                    return
 
769
            shellwidget = ExternalSystemShell(self, wdir, path=pythonpath,
 
770
                                          light_background=light_background,
 
771
                                          menu_actions=self.menu_actions,
 
772
                                          show_buttons_inside=False,
 
773
                                          show_elapsed_time=show_elapsed_time)
 
774
        
 
775
        # Code completion / calltips
 
776
        shellwidget.shell.setMaximumBlockCount(
 
777
                                            self.get_option('max_line_count') )
 
778
        shellwidget.shell.set_font( self.get_plugin_font() )
 
779
        shellwidget.shell.toggle_wrap_mode( self.get_option('wrap') )
 
780
        shellwidget.shell.set_calltips( self.get_option('calltips') )
 
781
        shellwidget.shell.set_codecompletion_auto(
 
782
                            self.get_option('codecompletion/auto') )
 
783
        shellwidget.shell.set_codecompletion_case(
 
784
                            self.get_option('codecompletion/case_sensitive') )
 
785
        shellwidget.shell.set_codecompletion_single(
 
786
                            self.get_option('codecompletion/show_single') )
 
787
        shellwidget.shell.set_codecompletion_enter(
 
788
                            self.get_option('codecompletion/enter_key') )
 
789
        if python and self.inspector is not None:
 
790
            shellwidget.shell.set_inspector(self.inspector)
 
791
            shellwidget.shell.set_inspector_enabled(
 
792
                                            self.get_option('object_inspector'))
 
793
        if self.historylog is not None:
 
794
            self.historylog.add_history(shellwidget.shell.history_filename)
 
795
            self.connect(shellwidget.shell,
 
796
                         SIGNAL('append_to_history(QString,QString)'),
 
797
                         self.historylog.append_to_history)
 
798
        self.connect(shellwidget.shell, SIGNAL("go_to_error(QString)"),
 
799
                     self.go_to_error)
 
800
        self.connect(shellwidget.shell, SIGNAL("focus_changed()"),
 
801
                     lambda: self.emit(SIGNAL("focus_changed()")))
 
802
        if python:
 
803
            if self.main.editor is not None:
 
804
                self.connect(shellwidget, SIGNAL('open_file(QString,int)'),
 
805
                             self.open_file_in_spyder)
 
806
            if fname is None:
 
807
                if ipykernel:
 
808
                    tab_name = _("Kernel")
 
809
                    tab_icon1 = get_icon('ipython_console.png')
 
810
                    tab_icon2 = get_icon('ipython_console_t.png')
 
811
                    if ipyclient:
 
812
                        self.connect(shellwidget,
 
813
                                 SIGNAL('create_ipython_client(QString)'),
 
814
                                 lambda cf: self.create_ipyclient(
 
815
                                         cf, kernel_widget=shellwidget))
 
816
                else:
 
817
                    self.python_count += 1
 
818
                    tab_name = "Python %d" % self.python_count
 
819
                    tab_icon1 = get_icon('python.png')
 
820
                    tab_icon2 = get_icon('python_t.png')
 
821
            else:
 
822
                tab_name = osp.basename(fname)
 
823
                tab_icon1 = get_icon('run.png')
 
824
                tab_icon2 = get_icon('terminated.png')
 
825
        else:
 
826
            fname = id(shellwidget)
 
827
            if os.name == 'nt':
 
828
                tab_name = _("Command Window")
 
829
            else:
 
830
                tab_name = _("Terminal")
 
831
            self.terminal_count += 1
 
832
            tab_name += (" %d" % self.terminal_count)
 
833
            tab_icon1 = get_icon('cmdprompt.png')
 
834
            tab_icon2 = get_icon('cmdprompt_t.png')
 
835
        self.shellwidgets.insert(index, shellwidget)
 
836
        self.filenames.insert(index, fname)
 
837
        self.icons.insert(index, (tab_icon1, tab_icon2))
 
838
        if index is None:
 
839
            index = self.tabwidget.addTab(shellwidget, tab_name)
 
840
        else:
 
841
            self.tabwidget.insertTab(index, shellwidget, tab_name)
 
842
        
 
843
        self.connect(shellwidget, SIGNAL("started()"),
 
844
                     lambda sid=id(shellwidget): self.process_started(sid))
 
845
        self.connect(shellwidget, SIGNAL("finished()"),
 
846
                     lambda sid=id(shellwidget): self.process_finished(sid))
 
847
        self.find_widget.set_editor(shellwidget.shell)
 
848
        self.tabwidget.setTabToolTip(index, fname if wdir is None else wdir)
 
849
        self.tabwidget.setCurrentIndex(index)
 
850
        if self.dockwidget and not self.ismaximized and not ipykernel:
 
851
            self.dockwidget.setVisible(True)
 
852
            self.dockwidget.raise_()
 
853
        
 
854
        shellwidget.set_icontext_visible(self.get_option('show_icontext'))
 
855
        
 
856
        # Start process and give focus to console
 
857
        shellwidget.start_shell()
 
858
        if not ipykernel:
 
859
            self.activateWindow()
 
860
            shellwidget.shell.setFocus()
 
861
        
 
862
    def set_ipykernel_attrs(self, connection_file, kernel_widget):
 
863
        """Add the pid of the kernel process to an IPython kernel tab"""
 
864
        # Set connection file
 
865
        kernel_widget.connection_file = connection_file
 
866
        
 
867
        # If we've reached this point then it's safe to assume IPython
 
868
        # is available, and this import should be valid.
 
869
        from IPython.core.application import get_ipython_dir
 
870
        # For each kernel we launch, setup to delete the associated
 
871
        # connection file at the time Spyder exits.
 
872
        def cleanup_connection_file(connection_file):
 
873
            """Clean up the connection file for this console at exit"""
 
874
            connection_file = osp.join(get_ipython_dir(), 'profile_default',
 
875
                                   'security', connection_file)
 
876
            try:
 
877
                os.remove(connection_file)
 
878
            except OSError:
 
879
                pass
 
880
        atexit.register(cleanup_connection_file, connection_file)              
 
881
        
 
882
        # Set tab name
 
883
        index = self.get_shell_index_from_id(id(kernel_widget))
 
884
        match = re.match('^kernel-(\d+).json', connection_file)
 
885
        if match is not None:  # should not fail, but we never know...
 
886
            text = unicode(self.tabwidget.tabText(index))
 
887
            name = "%s %s" % (text, match.groups()[0])
 
888
            self.tabwidget.setTabText(index, name)
 
889
    
 
890
    def create_ipyclient(self, connection_file, kernel_widget):
 
891
        """Create a new IPython client connected to a kernel just started"""
 
892
        self.set_ipykernel_attrs(connection_file, kernel_widget)
 
893
        self.main.ipyconsole.new_client(connection_file, id(kernel_widget))
 
894
        # QApplication.restoreOverrideCursor()  # Stop busy cursor indication
 
895
        # QApplication.processEvents()
 
896
        
 
897
    def open_file_in_spyder(self, fname, lineno):
 
898
        """Open file in Spyder's editor from remote process"""
 
899
        self.main.editor.activateWindow()
 
900
        self.main.editor.raise_()
 
901
        self.main.editor.load(fname, lineno)
 
902
        
 
903
    #------ Private API -------------------------------------------------------
 
904
    def process_started(self, shell_id):
 
905
        index = self.get_shell_index_from_id(shell_id)
 
906
        shell = self.shellwidgets[index]
 
907
        icon, _icon = self.icons[index]
 
908
        self.tabwidget.setTabIcon(index, icon)
 
909
        if self.inspector is not None:
 
910
            self.inspector.set_shell(shell.shell)
 
911
        if self.variableexplorer is not None:
 
912
            self.variableexplorer.add_shellwidget(shell)
 
913
        
 
914
    def process_finished(self, shell_id):
 
915
        index = self.get_shell_index_from_id(shell_id)
 
916
        if index is not None:
 
917
            # Not sure why it happens, but sometimes the shellwidget has 
 
918
            # already been removed, so that's not bad if we can't change
 
919
            # the tab icon...
 
920
            _icon, icon = self.icons[index]
 
921
            self.tabwidget.setTabIcon(index, icon)
 
922
        if self.variableexplorer is not None:
 
923
            self.variableexplorer.remove_shellwidget(shell_id)
 
924
        
 
925
    #------ SpyderPluginWidget API --------------------------------------------
 
926
    def get_plugin_title(self):
 
927
        """Return widget title"""
 
928
        title = _('Console')
 
929
        if self.filenames:
 
930
            index = self.tabwidget.currentIndex()
 
931
            fname = self.filenames[index]
 
932
            if fname:
 
933
                title += ' - '+unicode(fname)
 
934
        return title
 
935
    
 
936
    def get_plugin_icon(self):
 
937
        """Return widget icon"""
 
938
        return get_icon('console.png')
 
939
    
 
940
    def get_focus_widget(self):
 
941
        """
 
942
        Return the widget to give focus to when
 
943
        this plugin's dockwidget is raised on top-level
 
944
        """
 
945
        return self.tabwidget.currentWidget()
 
946
        
 
947
    def get_plugin_actions(self):
 
948
        """Return a list of actions related to plugin"""
 
949
        interpreter_action = create_action(self,
 
950
                            _("Open a Python &interpreter"), None,
 
951
                            'python.png', triggered=self.open_interpreter)
 
952
        if os.name == 'nt':
 
953
            text = _("Open &command prompt")
 
954
            tip = _("Open a Windows command prompt")
 
955
        else:
 
956
            text = _("Open &terminal")
 
957
            tip = _("Open a terminal window inside Spyder")
 
958
        terminal_action = create_action(self, text, None, 'cmdprompt.png', tip,
 
959
                                        triggered=self.open_terminal)
 
960
        run_action = create_action(self,
 
961
                            _("&Run..."), None,
 
962
                            'run_small.png', _("Run a Python script"),
 
963
                            triggered=self.run_script)
 
964
 
 
965
        interact_menu_actions = [interpreter_action]
 
966
        tools_menu_actions = [terminal_action]
 
967
        self.menu_actions = [interpreter_action, terminal_action, run_action]
 
968
        
 
969
        self.ipykernel_action = create_action(self,
 
970
                                              _("Open an IPython console"),
 
971
                                              None, 'ipython_console.png',
 
972
                                              triggered=self.start_ipykernel)
 
973
        if programs.is_module_installed('IPython.frontend.qt', '>=0.13'):
 
974
            interact_menu_actions.append(self.ipykernel_action)
 
975
        self.main.interact_menu_actions += interact_menu_actions
 
976
        self.main.tools_menu_actions += tools_menu_actions
 
977
        
 
978
        return self.menu_actions+interact_menu_actions+tools_menu_actions
 
979
    
 
980
    def register_plugin(self):
 
981
        """Register plugin in Spyder's main window"""
 
982
        if self.main.light:
 
983
            self.main.setCentralWidget(self)
 
984
            self.main.widgetlist.append(self)
 
985
        else:
 
986
            self.main.add_dockwidget(self)
 
987
            self.inspector = self.main.inspector
 
988
            if self.inspector is not None:
 
989
                self.inspector.set_external_console(self)
 
990
            self.historylog = self.main.historylog
 
991
            self.connect(self, SIGNAL("edit_goto(QString,int,QString)"),
 
992
                         self.main.editor.load)
 
993
            self.connect(self, SIGNAL("edit_goto(QString,int,QString,bool)"),
 
994
                         lambda fname, lineno, word, processevents:
 
995
                         self.main.editor.load(fname,lineno,word,
 
996
                                               processevents=processevents))
 
997
            self.connect(self.main.editor,
 
998
                         SIGNAL('run_in_current_extconsole(QString,QString,QString,bool)'),
 
999
                         self.run_script_in_current_shell)
 
1000
            self.connect(self.main.editor, SIGNAL("open_dir(QString)"),
 
1001
                         self.set_current_shell_working_directory)
 
1002
            self.connect(self.main.workingdirectory,
 
1003
                         SIGNAL("set_current_console_wd(QString)"),
 
1004
                         self.set_current_shell_working_directory)
 
1005
            self.connect(self, SIGNAL('focus_changed()'),
 
1006
                         self.main.plugin_focus_changed)
 
1007
            self.connect(self, SIGNAL('redirect_stdio(bool)'),
 
1008
                         self.main.redirect_internalshell_stdio)
 
1009
            expl = self.main.explorer
 
1010
            if expl is not None:
 
1011
                self.connect(expl, SIGNAL("open_terminal(QString)"),
 
1012
                             self.open_terminal)
 
1013
                self.connect(expl, SIGNAL("open_interpreter(QString)"),
 
1014
                             self.open_interpreter)
 
1015
            pexpl = self.main.projectexplorer
 
1016
            if pexpl is not None:
 
1017
                self.connect(pexpl, SIGNAL("open_terminal(QString)"),
 
1018
                             self.open_terminal)
 
1019
                self.connect(pexpl, SIGNAL("open_interpreter(QString)"),
 
1020
                             self.open_interpreter)
 
1021
        
 
1022
    def closing_plugin(self, cancelable=False):
 
1023
        """Perform actions before parent main window is closed"""
 
1024
        for shellwidget in self.shellwidgets:
 
1025
            shellwidget.close()
 
1026
        return True
 
1027
    
 
1028
    def refresh_plugin(self):
 
1029
        """Refresh tabwidget"""
 
1030
        shellwidget = None
 
1031
        if self.tabwidget.count():
 
1032
            shellwidget = self.tabwidget.currentWidget()
 
1033
            editor = shellwidget.shell
 
1034
            editor.setFocus()
 
1035
            widgets = [shellwidget.create_time_label(), 5
 
1036
                       ]+shellwidget.get_toolbar_buttons()+[5]
 
1037
        else:
 
1038
            editor = None
 
1039
            widgets = []
 
1040
        self.find_widget.set_editor(editor)
 
1041
        self.tabwidget.set_corner_widgets({Qt.TopRightCorner: widgets})
 
1042
        if shellwidget:
 
1043
            shellwidget.update_time_label_visibility()
 
1044
        self.emit(SIGNAL('update_plugin_title()'))
 
1045
    
 
1046
    def apply_plugin_settings(self, options):
 
1047
        """Apply configuration file's plugin settings"""
 
1048
        font = self.get_plugin_font()
 
1049
        showtime = self.get_option('show_elapsed_time')
 
1050
        icontext = self.get_option('show_icontext')
 
1051
        calltips = self.get_option('calltips')
 
1052
        inspector = self.get_option('object_inspector')
 
1053
        wrap = self.get_option('wrap')
 
1054
        compauto = self.get_option('codecompletion/auto')
 
1055
        case_comp = self.get_option('codecompletion/case_sensitive')
 
1056
        show_single = self.get_option('codecompletion/show_single')
 
1057
        compenter = self.get_option('codecompletion/enter_key')
 
1058
        mlc = self.get_option('max_line_count')
 
1059
        for shellwidget in self.shellwidgets:
 
1060
            shellwidget.shell.set_font(font)
 
1061
            shellwidget.set_elapsed_time_visible(showtime)
 
1062
            shellwidget.set_icontext_visible(icontext)
 
1063
            shellwidget.shell.set_calltips(calltips)
 
1064
            if isinstance(shellwidget.shell, ExternalPythonShell):
 
1065
                shellwidget.shell.set_inspector_enabled(inspector)
 
1066
            shellwidget.shell.toggle_wrap_mode(wrap)
 
1067
            shellwidget.shell.set_codecompletion_auto(compauto)
 
1068
            shellwidget.shell.set_codecompletion_case(case_comp)
 
1069
            shellwidget.shell.set_codecompletion_single(show_single)
 
1070
            shellwidget.shell.set_codecompletion_enter(compenter)
 
1071
            shellwidget.shell.setMaximumBlockCount(mlc)
 
1072
    
 
1073
    #------ Public API ---------------------------------------------------------
 
1074
    def open_interpreter_at_startup(self):
 
1075
        """Open an interpreter or an IPython kernel at startup"""
 
1076
        if self.get_option('open_python_at_startup', True):
 
1077
            self.open_interpreter()
 
1078
        if CONF.get('ipython_console', 'open_ipython_at_startup', False):
 
1079
            self.start_ipykernel()
 
1080
            
 
1081
    def open_interpreter(self, wdir=None):
 
1082
        """Open interpreter"""
 
1083
        if wdir is None:
 
1084
            wdir = os.getcwdu()
 
1085
        if not self.main.light:
 
1086
            self.visibility_changed(True)
 
1087
        self.start(fname=None, wdir=unicode(wdir), args='',
 
1088
                   interact=True, debug=False, python=True)
 
1089
        
 
1090
    def start_ipykernel(self, wdir=None, create_client=True):
 
1091
        """Start new IPython kernel"""
 
1092
        if create_client and not self.get_option('monitor/enabled'):
 
1093
            QMessageBox.warning(self, _('Open an IPython console'),
 
1094
                _("The console monitor was disabled: the IPython kernel will "
 
1095
                  "be started as expected, but an IPython console will have "
 
1096
                  "to be connected manually to the kernel."), QMessageBox.Ok)
 
1097
        
 
1098
        # Add a WaitCursor visual indication, because it takes too much time
 
1099
        # to display a new console (3 to 5 secs). It's stopped in
 
1100
        # create_ipyclient
 
1101
        # QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
 
1102
        # QApplication.processEvents()
 
1103
        
 
1104
        if wdir is None:
 
1105
            wdir = os.getcwdu()
 
1106
        self.main.ipyconsole.visibility_changed(True)
 
1107
        self.start(fname=None, wdir=unicode(wdir), args='',
 
1108
                   interact=True, debug=False, python=True,
 
1109
                   ipykernel=True, ipyclient=create_client)
 
1110
 
 
1111
    def open_terminal(self, wdir=None):
 
1112
        """Open terminal"""
 
1113
        if wdir is None:
 
1114
            wdir = os.getcwdu()
 
1115
        self.start(fname=None, wdir=unicode(wdir), args='',
 
1116
                   interact=True, debug=False, python=False)
 
1117
        
 
1118
    def run_script(self):
 
1119
        """Run a Python script"""
 
1120
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
 
1121
        filename, _selfilter = getopenfilename(self, _("Run Python script"),
 
1122
                os.getcwdu(), _("Python scripts")+" (*.py ; *.pyw ; *.ipy)")
 
1123
        self.emit(SIGNAL('redirect_stdio(bool)'), True)
 
1124
        if filename:
 
1125
            self.start(fname=filename, wdir=None, args='',
 
1126
                       interact=False, debug=False)
 
1127
        
 
1128
    def set_umd_namelist(self):
 
1129
        """Set UMD excluded modules name list"""
 
1130
        arguments, valid = QInputDialog.getText(self, _('UMD'),
 
1131
                                  _('UMD excluded modules:\n'
 
1132
                                          '(example: guidata, guiqwt)'),
 
1133
                                  QLineEdit.Normal,
 
1134
                                  ", ".join(self.get_option('umd/namelist')))
 
1135
        if valid:
 
1136
            arguments = unicode(arguments)
 
1137
            if arguments:
 
1138
                namelist = arguments.replace(' ', '').split(',')
 
1139
                fixed_namelist = [module_name for module_name in namelist
 
1140
                                  if programs.is_module_installed(module_name)]
 
1141
                invalid = ", ".join(set(namelist)-set(fixed_namelist))
 
1142
                if invalid:
 
1143
                    QMessageBox.warning(self, _('UMD'),
 
1144
                                        _("The following modules are not "
 
1145
                                          "installed on your machine:\n%s"
 
1146
                                          ) % invalid, QMessageBox.Ok)
 
1147
                QMessageBox.information(self, _('UMD'),
 
1148
                                    _("Please note that these changes will "
 
1149
                                      "be applied only to new Python/IPython "
 
1150
                                      "interpreters"), QMessageBox.Ok)
 
1151
            else:
 
1152
                fixed_namelist = []
 
1153
            self.set_option('umd/namelist', fixed_namelist)
 
1154
        
 
1155
    def go_to_error(self, text):
 
1156
        """Go to error if relevant"""
 
1157
        match = get_error_match(unicode(text))
 
1158
        if match:
 
1159
            fname, lnb = match.groups()
 
1160
            self.emit(SIGNAL("edit_goto(QString,int,QString)"),
 
1161
                      osp.abspath(fname), int(lnb), '')
 
1162
            
 
1163
    #----Drag and drop
 
1164
    def dragEnterEvent(self, event):
 
1165
        """Reimplement Qt method
 
1166
        Inform Qt about the types of data that the widget accepts"""
 
1167
        source = event.mimeData()
 
1168
        if source.hasUrls():
 
1169
            if mimedata2url(source):
 
1170
                pathlist = mimedata2url(source)
 
1171
                shellwidget = self.tabwidget.currentWidget()
 
1172
                if all([is_python_script(unicode(qstr)) for qstr in pathlist]):
 
1173
                    event.acceptProposedAction()
 
1174
                elif shellwidget is None or not shellwidget.is_running():
 
1175
                    event.ignore()
 
1176
                else:
 
1177
                    event.acceptProposedAction()
 
1178
            else:
 
1179
                event.ignore()
 
1180
        elif source.hasText():
 
1181
            event.acceptProposedAction()            
 
1182
            
 
1183
    def dropEvent(self, event):
 
1184
        """Reimplement Qt method
 
1185
        Unpack dropped data and handle it"""
 
1186
        source = event.mimeData()
 
1187
        shellwidget = self.tabwidget.currentWidget()
 
1188
        if source.hasText():
 
1189
            qstr = source.text()
 
1190
            if is_python_script(unicode(qstr)):
 
1191
                self.start(qstr)
 
1192
            elif shellwidget:
 
1193
                shellwidget.shell.insert_text(qstr)
 
1194
        elif source.hasUrls():
 
1195
            pathlist = mimedata2url(source)
 
1196
            if all([is_python_script(unicode(qstr)) for qstr in pathlist]):
 
1197
                for fname in pathlist:
 
1198
                    self.start(fname)
 
1199
            elif shellwidget:
 
1200
                shellwidget.shell.drop_pathlist(pathlist)
 
1201
        event.acceptProposedAction()
 
1202