~ubuntu-branches/ubuntu/trusty/spyder/trusty-backports

« back to all changes in this revision

Viewing changes to spyderlib/widgets/editor.py

  • Committer: Bazaar Package Importer
  • Author(s): Picca Frédéric-Emmanuel
  • Date: 2011-03-05 18:03:43 UTC
  • mfrom: (1.1.3 upstream)
  • Revision ID: james.westby@ubuntu.com-20110305180343-its88tucbyvtevjf
Tags: 2.0.8-1
* Imported Upstream version 2.0.8 (Closes: #609789)
* add a watch file
* build for all python2 versions (it can be use as module by other packages)
* change the documentation section to Programming/Python
* use the Recommendes found in the documentation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# -*- coding: utf-8 -*-
2
2
#
3
 
# Copyright © 2009 Pierre Raybaut
 
3
# Copyright © 2009-2010 Pierre Raybaut
4
4
# Licensed under the terms of the MIT License
5
5
# (see spyderlib/__init__.py for details)
6
6
 
11
11
# pylint: disable-msg=R0911
12
12
# pylint: disable-msg=R0201
13
13
 
14
 
#TODO: Add a button "Opened files management" -> opens a qlistwidget with
15
 
#      checkboxes + toolbar with "Save", "Close"
16
 
 
17
14
from PyQt4.QtGui import (QVBoxLayout, QFileDialog, QMessageBox, QMenu, QFont,
18
 
                         QAction, QApplication, QWidget, QHBoxLayout, QSplitter,
19
 
                         QComboBox, QKeySequence, QShortcut, QSizePolicy,
20
 
                         QMainWindow, QLabel)
 
15
                         QAction, QApplication, QWidget, QHBoxLayout, QLabel,
 
16
                         QKeySequence, QShortcut, QMainWindow, QSplitter,
 
17
                         QListWidget, QListWidgetItem, QDialog, QLineEdit, 
 
18
                         QIntValidator, QDialogButtonBox, QGridLayout)
21
19
from PyQt4.QtCore import (SIGNAL, Qt, QFileInfo, QThread, QObject, QByteArray,
22
 
                          PYQT_VERSION_STR, QSize, QPoint)
 
20
                          PYQT_VERSION_STR, QSize, QPoint, SLOT, QTimer)
23
21
 
24
22
import os, sys, re
25
23
import os.path as osp
29
27
DEBUG = False
30
28
 
31
29
# Local imports
32
 
from spyderlib.utils import encoding, sourcecode
 
30
from spyderlib.utils import encoding, sourcecode, programs
33
31
from spyderlib.config import get_icon, get_font
34
32
from spyderlib.utils.qthelpers import (create_action, add_actions, mimedata2url,
35
33
                                       get_filetype_icon, translate,
36
34
                                       create_toolbutton)
37
35
from spyderlib.widgets.tabs import BaseTabs
38
36
from spyderlib.widgets.findreplace import FindReplace
39
 
from spyderlib.widgets.editortools import check, ClassBrowser
40
 
try:
41
 
    from spyderlib.widgets.qscieditor.qscieditor import QsciEditor as CodeEditor
42
 
    from spyderlib.widgets.qscieditor.qscieditor import Printer #@UnusedImport
43
 
    from spyderlib.widgets.qscieditor.qscibase import TextEditBaseWidget #@UnusedImport
44
 
except ImportError:
45
 
    from spyderlib.widgets.qteditor.qteditor import QtEditor as CodeEditor
46
 
    from spyderlib.widgets.qteditor.qteditor import Printer #@UnusedImport
47
 
    from spyderlib.widgets.qteditor.qtebase import TextEditBaseWidget #@UnusedImport
48
 
 
 
37
from spyderlib.widgets.editortools import OutlineExplorer, check
 
38
from spyderlib.widgets.codeeditor import syntaxhighlighters
 
39
from spyderlib.widgets.codeeditor.base import TextEditBaseWidget #@UnusedImport
 
40
from spyderlib.widgets.codeeditor.codeeditor import CodeEditor, get_primary_at
 
41
from spyderlib.widgets.codeeditor.codeeditor import Printer #@UnusedImport
 
42
 
 
43
 
 
44
class GoToLineDialog(QDialog):
 
45
    def __init__(self, editor):
 
46
        QDialog.__init__(self, editor)
 
47
        self.editor = editor
 
48
        
 
49
        self.setWindowTitle(translate("Editor", "Editor"))
 
50
        self.setModal(True)
 
51
        
 
52
        label = QLabel(translate("Editor", "Go to line:"))
 
53
        self.lineedit = QLineEdit()
 
54
        validator = QIntValidator(self.lineedit)
 
55
        validator.setRange(1, editor.get_line_count())
 
56
        self.lineedit.setValidator(validator)        
 
57
        cl_label = QLabel(translate("Editor", "Current line:"))
 
58
        cl_label_v = QLabel("<b>%d</b>" % editor.get_cursor_line_number())
 
59
        last_label = QLabel(translate("Editor", "Line count:"))
 
60
        last_label_v = QLabel("%d" % editor.get_line_count())
 
61
        
 
62
        glayout = QGridLayout()
 
63
        glayout.addWidget(label, 0, 0, Qt.AlignVCenter|Qt.AlignRight)
 
64
        glayout.addWidget(self.lineedit, 0, 1, Qt.AlignVCenter)
 
65
        glayout.addWidget(cl_label, 1, 0, Qt.AlignVCenter|Qt.AlignRight)
 
66
        glayout.addWidget(cl_label_v, 1, 1, Qt.AlignVCenter)
 
67
        glayout.addWidget(last_label, 2, 0, Qt.AlignVCenter|Qt.AlignRight)
 
68
        glayout.addWidget(last_label_v, 2, 1, Qt.AlignVCenter)
 
69
 
 
70
        bbox = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel,
 
71
                                Qt.Vertical, self)
 
72
        self.connect(bbox, SIGNAL("accepted()"), SLOT("accept()"))
 
73
        self.connect(bbox, SIGNAL("rejected()"), SLOT("reject()"))
 
74
        btnlayout = QVBoxLayout()
 
75
        btnlayout.addWidget(bbox)
 
76
        btnlayout.addStretch(1)
 
77
 
 
78
        ok_button = bbox.button(QDialogButtonBox.Ok)
 
79
        ok_button.setEnabled(False)
 
80
        self.connect(self.lineedit, SIGNAL("textChanged(QString)"),
 
81
                     lambda text: ok_button.setEnabled(len(text) > 0))
 
82
        
 
83
        layout = QHBoxLayout()
 
84
        layout.addLayout(glayout)
 
85
        layout.addLayout(btnlayout)
 
86
        self.setLayout(layout)
 
87
 
 
88
        self.lineedit.setFocus()
 
89
        
 
90
    def get_line_number(self):
 
91
        return int(self.lineedit.text())
 
92
        
 
93
 
 
94
class FileListDialog(QDialog):
 
95
    def __init__(self, parent, tabs, fullpath_sorting):
 
96
        QDialog.__init__(self, parent)
 
97
        
 
98
        self.indexes = None
 
99
        
 
100
        self.setWindowIcon(get_icon('filelist.png'))
 
101
        self.setWindowTitle(translate("Editor", "File list management"))
 
102
        
 
103
        self.setModal(True)
 
104
        
 
105
        flabel = QLabel(translate("Editor", "Filter:"))
 
106
        self.edit = QLineEdit(self)
 
107
        self.connect(self.edit, SIGNAL("returnPressed()"), self.edit_file)
 
108
        self.connect(self.edit, SIGNAL("textChanged(QString)"),
 
109
                     lambda text: self.synchronize(0))
 
110
        fhint = QLabel(translate("Editor", "(press <b>Enter</b> to edit file)"))
 
111
        edit_layout = QHBoxLayout()
 
112
        edit_layout.addWidget(flabel)
 
113
        edit_layout.addWidget(self.edit)
 
114
        edit_layout.addWidget(fhint)
 
115
        
 
116
        self.listwidget = QListWidget(self)
 
117
        self.listwidget.setResizeMode(QListWidget.Adjust)
 
118
        self.connect(self.listwidget, SIGNAL("itemSelectionChanged()"),
 
119
                     self.item_selection_changed)
 
120
        self.connect(self.listwidget, SIGNAL("itemActivated(QListWidgetItem*)"),
 
121
                     self.edit_file)
 
122
        
 
123
        btn_layout = QHBoxLayout()
 
124
        edit_btn = create_toolbutton(self, icon=get_icon('edit.png'),
 
125
                     text=translate("Editor", "&Edit file"), autoraise=False,
 
126
                     triggered=self.edit_file, text_beside_icon=True)
 
127
        edit_btn.setMinimumHeight(28)
 
128
        btn_layout.addWidget(edit_btn)
 
129
        
 
130
        btn_layout.addStretch()
 
131
        btn_layout.addSpacing(150)
 
132
        btn_layout.addStretch()
 
133
        
 
134
        close_btn = create_toolbutton(self,
 
135
              text=translate("Editor", "&Close file"),
 
136
              icon=get_icon("fileclose.png"),
 
137
              autoraise=False, text_beside_icon=True,
 
138
              triggered=lambda: self.emit(SIGNAL("close_file(int)"),
 
139
                                  self.indexes[self.listwidget.currentRow()]))
 
140
        close_btn.setMinimumHeight(28)
 
141
        btn_layout.addWidget(close_btn)
 
142
 
 
143
        hint = QLabel(translate("Editor",
 
144
                             "Hint: press <b>Alt</b> to show accelerators"))
 
145
        hint.setAlignment(Qt.AlignCenter)
 
146
        
 
147
        vlayout = QVBoxLayout()
 
148
        vlayout.addLayout(edit_layout)
 
149
        vlayout.addWidget(self.listwidget)
 
150
        vlayout.addLayout(btn_layout)
 
151
        vlayout.addWidget(hint)
 
152
        self.setLayout(vlayout)
 
153
        
 
154
        self.tabs = tabs
 
155
        self.fullpath_sorting = fullpath_sorting
 
156
        self.buttons = (edit_btn, close_btn)
 
157
        
 
158
    def edit_file(self):
 
159
        row = self.listwidget.currentRow()
 
160
        if self.listwidget.count() > 0 and row >= 0:
 
161
            self.emit(SIGNAL("edit_file(int)"), self.indexes[row])
 
162
            self.accept()
 
163
            
 
164
    def item_selection_changed(self):
 
165
        for btn in self.buttons:
 
166
            btn.setEnabled(self.listwidget.currentRow() >= 0)
 
167
        
 
168
    def synchronize(self, stack_index):
 
169
        count = self.tabs.count()
 
170
        if count == 0:
 
171
            self.accept()
 
172
            return
 
173
        self.listwidget.setTextElideMode(Qt.ElideMiddle if self.fullpath_sorting
 
174
                                         else Qt.ElideRight)
 
175
        current_row = self.listwidget.currentRow()
 
176
        if current_row >= 0:
 
177
            current_text = unicode(self.listwidget.currentItem().text())
 
178
        else:
 
179
            current_text = ""
 
180
        self.listwidget.clear()
 
181
        self.indexes = []
 
182
        new_current_index = stack_index
 
183
        filter_text = unicode(self.edit.text())
 
184
        lw_index = 0
 
185
        for index in range(count):
 
186
            text = unicode(self.tabs.tabText(index))
 
187
            if len(filter_text) == 0 or filter_text in text:
 
188
                if text == current_text:
 
189
                    new_current_index = lw_index
 
190
                lw_index += 1
 
191
                item = QListWidgetItem(self.tabs.tabIcon(index),
 
192
                                       text, self.listwidget)
 
193
                item.setSizeHint(QSize(0, 25))
 
194
                self.listwidget.addItem(item)
 
195
                self.indexes.append(index)
 
196
        if new_current_index < self.listwidget.count():
 
197
            self.listwidget.setCurrentRow(new_current_index)
 
198
        for btn in self.buttons:
 
199
            btn.setEnabled(lw_index > 0)
 
200
        
49
201
 
50
202
class CodeAnalysisThread(QThread):
51
203
    """Pyflakes code analysis thread"""
52
 
    def __init__(self, parent):
53
 
        QThread.__init__(self, parent)
 
204
    def __init__(self, editor):
 
205
        QThread.__init__(self, editor)
 
206
        self.editor = editor
54
207
        self.filename = None
 
208
        self.analysis_results = []
55
209
        
56
210
    def set_filename(self, filename):
57
211
        self.filename = filename
58
212
        
59
213
    def run(self):
60
 
        self.analysis_results = check(self.filename)
 
214
        source_code = unicode(self.editor.toPlainText()).encode('utf-8')
 
215
        self.analysis_results = check(source_code, filename=self.filename)
61
216
        
62
217
    def get_results(self):
63
218
        return self.analysis_results
65
220
 
66
221
class ToDoFinderThread(QThread):
67
222
    """TODO finder thread"""
68
 
    PATTERN = r"# ?TODO ?:[^#]*|# ?FIXME ?:[^#]*|# ?XXX ?:?[^#]*"
 
223
    PATTERN = r"# ?TODO ?:[^#]*|# ?FIXME ?:[^#]*|# ?XXX ?:?[^#]*|# ?HINT ?:?[^#]*|# ?TIP ?:?[^#]*"
69
224
    def __init__(self, parent):
70
225
        QThread.__init__(self, parent)
71
226
        self.text = None
 
227
        self.todo_results = []
72
228
        
73
229
    def set_text(self, text):
74
230
        self.text = unicode(text)
84
240
        return self.todo_results
85
241
 
86
242
 
87
 
class TabInfo(QObject):
 
243
class FileInfo(QObject):
88
244
    """File properties"""
89
245
    def __init__(self, filename, encoding, editor, new):
90
246
        QObject.__init__(self)
 
247
        self.is_closing = False
 
248
        self.project = None
91
249
        self.filename = filename
92
250
        self.newly_created = new
93
251
        self.encoding = encoding
96
254
        self.analysis_results = []
97
255
        self.todo_results = []
98
256
        self.lastmodified = QFileInfo(filename).lastModified()
 
257
        
 
258
        self.connect(editor, SIGNAL('trigger_code_completion(bool)'),
 
259
                     self.trigger_code_completion)
 
260
        self.connect(editor, SIGNAL('trigger_calltip(int)'),
 
261
                     self.trigger_calltip)
 
262
        self.connect(editor, SIGNAL("go_to_definition(int)"),
 
263
                     self.go_to_definition)
 
264
        
 
265
        self.connect(editor, SIGNAL('textChanged()'),
 
266
                     self.text_changed)
 
267
        
 
268
        self.connect(editor, SIGNAL('breakpoints_changed()'),
 
269
                     self.breakpoints_changed)
99
270
            
100
 
        self.analysis_thread = CodeAnalysisThread(self)
 
271
        self.analysis_thread = CodeAnalysisThread(self.editor)
101
272
        self.connect(self.analysis_thread, SIGNAL('finished()'),
102
273
                     self.code_analysis_finished)
103
274
        
104
275
        self.todo_thread = ToDoFinderThread(self)
105
276
        self.connect(self.todo_thread, SIGNAL('finished()'),
106
277
                     self.todo_finished)
 
278
        
 
279
    def close_threads(self):
 
280
        self.is_closing = True
 
281
        while self.analysis_thread.isRunning():
 
282
            pass
 
283
        while self.todo_thread.isRunning():
 
284
            pass
 
285
        
 
286
    def set_project(self, project):
 
287
        self.project = project
 
288
        
 
289
    def text_changed(self):
 
290
        self.emit(SIGNAL('text_changed_at(QString,int)'),
 
291
                  self.filename, self.editor.get_position('cursor'))
 
292
        
 
293
    def validate_project(self):
 
294
        if self.project is None:
 
295
            return []
 
296
        self.project.validate_rope_project()
 
297
        
 
298
    def trigger_code_completion(self, automatic):
 
299
        if self.project is None:
 
300
            return []
 
301
        source_code = unicode(self.editor.toPlainText())
 
302
        offset = self.editor.get_position('cursor')
 
303
        
 
304
        textlist = self.project.get_completion_list(source_code, offset,
 
305
                                                    self.filename)
 
306
        if textlist:
 
307
            text = self.editor.get_text('sol', 'cursor')
 
308
            completion_text = re.split(r"[^a-zA-Z0-9_]", text)[-1]
 
309
            self.editor.show_completion_list(textlist, completion_text,
 
310
                                             automatic)
 
311
        
 
312
    def trigger_calltip(self, position):
 
313
        if self.project is None:
 
314
            return
 
315
        source_code = unicode(self.editor.toPlainText())
 
316
        offset = position
 
317
        
 
318
        textlist = self.project.get_calltip_text(source_code, offset,
 
319
                                                 self.filename)
 
320
        text = ''
 
321
        if textlist:
 
322
            parpos = textlist[0].find('(')
 
323
            if parpos:
 
324
                text = textlist[0][:parpos]
 
325
        if not text:
 
326
            text = get_primary_at(source_code, offset)
 
327
        if text and not text.startswith('self.'):
 
328
            doc_text = ''
 
329
            if len(textlist) == 2:
 
330
                doc_text = textlist.pop(1)
 
331
            if doc_text:
 
332
                self.emit(SIGNAL("send_to_inspector(QString,QString)"),
 
333
                          text, doc_text)
 
334
        if textlist:
 
335
            self.editor.show_calltip("rope", textlist)
 
336
                    
 
337
    def go_to_definition(self, position):
 
338
        if self.project is None:
 
339
            return
 
340
        source_code = unicode(self.editor.toPlainText())
 
341
        offset = position
 
342
        fname, lineno = self.project.get_definition_location(source_code,
 
343
                                                        offset, self.filename)
 
344
        if fname is not None and lineno is not None:
 
345
            self.emit(SIGNAL("edit_goto(QString,int,QString)"),
 
346
                      fname, lineno, "")
107
347
    
108
348
    def run_code_analysis(self):
109
 
        if self.editor.is_python():
 
349
        if self.editor.is_python() and not self.is_closing:
110
350
            self.analysis_thread.set_filename(self.filename)
111
351
            self.analysis_thread.start()
112
352
        
125
365
        self.editor.cleanup_code_analysis()
126
366
            
127
367
    def run_todo_finder(self):
128
 
        if self.editor.is_python():
129
 
            self.todo_thread.set_text(self.editor.get_text())
 
368
        if self.editor.is_python() and not self.is_closing:
 
369
            self.todo_thread.set_text(self.editor.toPlainText())
130
370
            self.todo_thread.start()
131
371
        
132
372
    def todo_finished(self):
141
381
        
142
382
    def cleanup_todo_results(self):
143
383
        self.todo_results = []
144
 
 
145
 
 
 
384
        
 
385
    def breakpoints_changed(self):
 
386
        """Breakpoint list has changed"""
 
387
        breakpoints = self.editor.get_breakpoints()
 
388
        self.emit(SIGNAL("save_breakpoints(QString,QString)"),
 
389
                  self.filename, repr(breakpoints))
 
390
 
 
391
 
 
392
def get_file_language(filename, text=None):
 
393
    ext = osp.splitext(filename)[1]
 
394
    if ext.startswith('.'):
 
395
        ext = ext[1:] # file extension with leading dot
 
396
    language = ext
 
397
    if not ext:
 
398
        if text is None:
 
399
            text, _enc = encoding.read(filename)
 
400
        for line in text.splitlines():
 
401
            if not line.strip():
 
402
                continue
 
403
            if line.startswith('#!') and \
 
404
               line[2:].split() == ['/usr/bin/env', 'python']:
 
405
                    language = 'python'
 
406
            else:
 
407
                break
 
408
    return language
 
409
        
146
410
class EditorStack(QWidget):
147
411
    def __init__(self, parent, plugin, actions):
148
412
        QWidget.__init__(self, parent)
156
420
        self.__get_split_actions()
157
421
        
158
422
        layout = QVBoxLayout()
 
423
        layout.setContentsMargins(0, 0, 0, 0)
159
424
        self.setLayout(layout)
160
425
 
161
 
        self.header_layout = None
162
426
        self.menu = None
163
 
        self.combo = None
164
 
        self.default_combo_font = None
165
 
        self.previous_btn = None
166
 
        self.next_btn = None
 
427
        self.filelist_dlg = None
 
428
#        self.filelist_btn = None
 
429
#        self.previous_btn = None
 
430
#        self.next_btn = None
167
431
        self.tabs = None
168
 
        self.close_btn = None
169
432
 
170
433
        self.stack_history = []
171
434
        
172
 
        self.setup_editorstack(parent, layout, actions)
 
435
        self.setup_editorstack(parent, layout)
173
436
 
174
437
        self.find_widget = None
175
438
 
176
439
        self.data = []
177
440
        
178
 
        self.menu_actions = actions
179
 
        self.classbrowser = None
 
441
        filelist_action = create_action(self,
 
442
                                 translate("Editor", "File list management"),
 
443
                                 icon=get_icon('filelist.png'),
 
444
                                 triggered=self.open_filelistdialog)
 
445
        copy_to_cb_action = create_action(self,
 
446
                translate("Editor", "Copy path to clipboard"),
 
447
                icon="editcopy.png",
 
448
                triggered=lambda:
 
449
                QApplication.clipboard().setText(self.get_current_filename()))
 
450
        self.menu_actions = actions+[None, filelist_action, copy_to_cb_action]
 
451
        self.outlineexplorer = None
 
452
        self.projectexplorer = None
 
453
        self.inspector = None
180
454
        self.unregister_callback = None
181
455
        self.is_closable = False
182
456
        self.new_action = None
183
457
        self.open_action = None
184
458
        self.save_action = None
 
459
        self.revert_action = None
185
460
        self.tempfile_path = None
186
461
        self.title = translate("Editor", "Editor")
187
462
        self.filetype_filters = None
188
463
        self.valid_types = None
189
464
        self.codeanalysis_enabled = True
190
465
        self.todolist_enabled = True
191
 
        self.classbrowser_enabled = True
192
 
        self.codefolding_enabled = True
 
466
        self.realtime_analysis_enabled = False
 
467
        self.is_analysis_done = False
 
468
        self.linenumbers_enabled = True
 
469
        self.edgeline_enabled = True
 
470
        self.edgeline_column = 80
 
471
        self.outlineexplorer_enabled = True
 
472
        self.codecompletion_auto_enabled = False
 
473
        self.codecompletion_case_enabled = False
 
474
        self.codecompletion_single_enabled = False
 
475
        self.codecompletion_enter_enabled = False
 
476
        self.calltips_enabled = False
 
477
        self.go_to_definition_enabled = False
 
478
        self.close_parentheses_enabled = True
 
479
        self.auto_unindent_enabled = True
 
480
        self.inspector_enabled = False
193
481
        self.default_font = None
194
482
        self.wrap_enabled = False
195
483
        self.tabmode_enabled = False
 
484
        self.intelligent_backspace_enabled = True
 
485
        self.highlight_current_line_enabled = False
196
486
        self.occurence_highlighting_enabled = True
197
487
        self.checkeolchars_enabled = True
198
488
        self.fullpath_sorting_enabled = None
199
489
        self.set_fullpath_sorting_enabled(False)
 
490
        ccs = 'Spyder'
 
491
        if ccs not in syntaxhighlighters.COLOR_SCHEME_NAMES:
 
492
            ccs = syntaxhighlighters.COLOR_SCHEME_NAMES[0]
 
493
        self.color_scheme = ccs
200
494
        
201
495
        self.cursor_position_changed_callback = lambda line, index: \
202
496
                self.emit(SIGNAL('cursorPositionChanged(int,int)'), line, index)
205
499
        
206
500
        self.__file_status_flag = False
207
501
        
 
502
        # Real-time code analysis
 
503
        self.analysis_timer = QTimer(self)
 
504
        self.analysis_timer.setSingleShot(True)
 
505
        self.analysis_timer.setInterval(2000)
 
506
        self.connect(self.analysis_timer, SIGNAL("timeout()"), 
 
507
                     self.analyze_script)
 
508
        
208
509
        # Accepting drops
209
510
        self.setAcceptDrops(True)
210
 
        
211
 
    def setup_editorstack(self, parent, layout, actions):
 
511
 
 
512
        # Local shortcuts
 
513
        self.inspectsc = QShortcut(QKeySequence("Ctrl+I"), self,
 
514
                                   self.inspect_current_object)
 
515
        self.inspectsc.setContext(Qt.WidgetWithChildrenShortcut)
 
516
        self.breakpointsc = QShortcut(QKeySequence("F12"), self,
 
517
                                      self.set_or_clear_breakpoint)
 
518
        self.breakpointsc.setContext(Qt.WidgetWithChildrenShortcut)
 
519
        self.cbreakpointsc = QShortcut(QKeySequence("Shift+F12"), self,
 
520
                                       self.set_or_edit_conditional_breakpoint)
 
521
        self.cbreakpointsc.setContext(Qt.WidgetWithChildrenShortcut)
 
522
        self.gotolinesc = QShortcut(QKeySequence("Ctrl+L"), self,
 
523
                                    self.go_to_line)
 
524
        self.gotolinesc.setContext(Qt.WidgetWithChildrenShortcut)
 
525
        self.filelistsc = QShortcut(QKeySequence("Ctrl+E"), self,
 
526
                                    self.open_filelistdialog)
 
527
        self.filelistsc.setContext(Qt.WidgetWithChildrenShortcut)
 
528
        self.tabsc = QShortcut(QKeySequence("Ctrl+Tab"), self,
 
529
                               self.go_to_previous_file)
 
530
        self.tabsc.setContext(Qt.WidgetWithChildrenShortcut)
 
531
        self.closesc = QShortcut(QKeySequence("Ctrl+F4"), self,
 
532
                                 self.close_file)
 
533
        self.closesc.setContext(Qt.WidgetWithChildrenShortcut)
 
534
        self.tabshiftsc = QShortcut(QKeySequence("Ctrl+Shift+Tab"), self,
 
535
                                    self.go_to_next_file)
 
536
        self.tabshiftsc.setContext(Qt.WidgetWithChildrenShortcut)
 
537
        
 
538
    def get_shortcut_data(self):
 
539
        """
 
540
        Returns shortcut data, a list of tuples (shortcut, text, default)
 
541
        shortcut (QShortcut or QAction instance)
 
542
        text (string): action/shortcut description
 
543
        default (string): default key sequence
 
544
        """
 
545
        return [
 
546
                (self.inspectsc, "Inspect current object", "Ctrl+I"),
 
547
                (self.breakpointsc, "Breakpoint", "F12"),
 
548
                (self.cbreakpointsc, "Conditional breakpoint", "Shift+F12"),
 
549
                (self.gotolinesc, "Go to line", "Ctrl+L"),
 
550
                (self.filelistsc, "File list management", "Ctrl+E"),
 
551
                ]
 
552
        
 
553
    def setup_editorstack(self, parent, layout):
212
554
        """Setup editorstack's layout"""
213
 
        self.header_layout = QHBoxLayout()
214
 
        self.header_layout.setContentsMargins(0, 0, 0, 0)
215
 
        
216
 
        # Buttons to the left of file combo box
217
555
        menu_btn = create_toolbutton(self, icon=get_icon("tooloptions.png"),
218
556
                                     tip=translate("Editor", "Options"))
219
557
        self.menu = QMenu(self)
220
558
        menu_btn.setMenu(self.menu)
221
559
        menu_btn.setPopupMode(menu_btn.InstantPopup)
222
560
        self.connect(self.menu, SIGNAL("aboutToShow()"), self.__setup_menu)
223
 
        self.add_widget_to_header(menu_btn)
224
 
 
225
 
#        newwin_btn = create_toolbutton(self, text_beside_icon=False)
226
 
#        newwin_btn.setDefaultAction(self.newwindow_action)
227
 
#        self.add_widget_to_header(newwin_btn)
228
 
        
229
 
#        versplit_btn = create_toolbutton(self, text_beside_icon=False)
230
 
#        versplit_btn.setDefaultAction(self.versplit_action)
231
 
#        self.add_widget_to_header(versplit_btn)
232
 
        
233
 
#        horsplit_btn = create_toolbutton(self, text_beside_icon=False)
234
 
#        horsplit_btn.setDefaultAction(self.horsplit_action)
235
 
#        self.add_widget_to_header(horsplit_btn)
236
 
 
237
 
        self.previous_btn = create_toolbutton(self, get_icon('previous.png'),
238
 
                         tip=translate("Editor", "Previous file (Ctrl+Tab)"),
239
 
                         triggered=self.go_to_previous_file)
240
 
        self.add_widget_to_header(self.previous_btn, space_before=True)
241
 
        
242
 
        self.next_btn = create_toolbutton(self, get_icon('next.png'),
243
 
                         tip=translate("Editor", "Next file (Ctrl+Shift+Tab)"),
244
 
                         triggered=self.go_to_next_file)
245
 
        self.add_widget_to_header(self.next_btn)
 
561
 
 
562
#        self.filelist_btn = create_toolbutton(self,
 
563
#                             icon=get_icon('filelist.png'),
 
564
#                             tip=translate("Editor", "File list management"),
 
565
#                             triggered=self.open_filelistdialog)
 
566
#        
 
567
#        self.previous_btn = create_toolbutton(self,
 
568
#                             icon=get_icon('previous.png'),
 
569
#                             tip=translate("Editor", "Previous file"),
 
570
#                             triggered=self.go_to_previous_file)
 
571
#        
 
572
#        self.next_btn = create_toolbutton(self,
 
573
#                             icon=get_icon('next.png'),
 
574
#                             tip=translate("Editor", "Next file"),
 
575
#                             triggered=self.go_to_next_file)
246
576
                
247
 
        # File combo box
248
 
        self.combo = QComboBox(self)
249
 
        self.default_combo_font = self.combo.font()
250
 
        self.combo.setMaxVisibleItems(20)
251
 
        self.combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength)
252
 
        self.combo.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
253
 
        self.connect(self.combo, SIGNAL('currentIndexChanged(int)'),
254
 
                     self.current_changed)
255
 
        self.add_widget_to_header(self.combo)
256
 
 
257
 
        # Buttons to the right of file combo box
258
 
        self.close_btn = create_toolbutton(self, triggered=self.close_file,
259
 
                                       icon=get_icon("fileclose.png"),
260
 
                                       tip=translate("Editor", "Close file"))
261
 
        self.add_widget_to_header(self.close_btn)
262
 
        layout.addLayout(self.header_layout)
263
 
 
264
 
        # Local shortcuts
265
 
        tabsc = QShortcut(QKeySequence("Ctrl+Tab"), parent,
266
 
                          self.go_to_previous_file)
267
 
        tabsc.setContext(Qt.WidgetWithChildrenShortcut)
268
 
        tabshiftsc = QShortcut(QKeySequence("Ctrl+Shift+Tab"), parent,
269
 
                               self.go_to_next_file)
270
 
        tabshiftsc.setContext(Qt.WidgetWithChildrenShortcut)
271
 
        
272
577
        # Optional tabs
273
 
        self.tabs = BaseTabs(self, menu=self.menu)
 
578
#        corner_widgets = {Qt.TopRightCorner: [self.previous_btn,
 
579
#                                              self.filelist_btn, self.next_btn,
 
580
#                                              5, menu_btn]}
 
581
        corner_widgets = {Qt.TopRightCorner: [menu_btn]}
 
582
        self.tabs = BaseTabs(self, menu=self.menu, menu_use_tooltips=True,
 
583
                             corner_widgets=corner_widgets)
274
584
        self.tabs.set_close_function(self.close_file)
275
585
        if hasattr(self.tabs, 'setDocumentMode'):
276
586
            self.tabs.setDocumentMode(True)
277
 
        self.connect(self.combo, SIGNAL('currentIndexChanged(int)'),
278
 
                     self.tabs.setCurrentIndex)
279
587
        self.connect(self.tabs, SIGNAL('currentChanged(int)'),
280
 
                     self.combo.setCurrentIndex)
 
588
                     self.current_changed)
281
589
        layout.addWidget(self.tabs)
282
590
        
283
 
    def add_widget_to_header(self, widget, space_before=False):
284
 
        if space_before:
285
 
            self.header_layout.addSpacing(7)
286
 
        self.header_layout.addWidget(widget)
 
591
    def add_corner_widgets_to_tabbar(self, widgets):
 
592
        self.tabs.add_corner_widgets(widgets)
287
593
        
288
594
    def closeEvent(self, event):
289
 
        super(EditorStack, self).closeEvent(event)
 
595
        self.disconnect(self.analysis_timer, SIGNAL("timeout()"),
 
596
                        self.analyze_script)
 
597
        QWidget.closeEvent(self, event)
290
598
        if PYQT_VERSION_STR.startswith('4.6'):
291
599
            self.emit(SIGNAL('destroyed()'))        
292
600
            
301
609
            finfo.set_todo_results(other_finfo.todo_results)
302
610
        self.set_stack_index(other.get_stack_index())
303
611
        
 
612
    def open_filelistdialog(self):
 
613
        """Open file list management dialog box"""
 
614
        self.filelist_dlg = dlg = FileListDialog(self, self.tabs,
 
615
                                                 self.fullpath_sorting_enabled)
 
616
        self.connect(dlg, SIGNAL("edit_file(int)"), self.set_stack_index)
 
617
        self.connect(dlg, SIGNAL("close_file(int)"), self.close_file)
 
618
        dlg.synchronize(self.get_stack_index())
 
619
        dlg.exec_()
 
620
        self.filelist_dlg = None
 
621
        
 
622
    def update_filelistdialog(self):
 
623
        """Synchronize file list dialog box with editor widget tabs"""
 
624
        if self.filelist_dlg is not None:
 
625
            self.filelist_dlg.synchronize(self.get_stack_index())
 
626
            
 
627
    def go_to_line(self):
 
628
        """Go to line dialog"""
 
629
        if self.data:
 
630
            editor = self.get_current_editor()
 
631
            dlg = GoToLineDialog(editor)
 
632
            if dlg.exec_():
 
633
                editor.go_to_line(dlg.get_line_number())
 
634
                
 
635
    def set_or_clear_breakpoint(self):
 
636
        """Set/clear breakpoint"""
 
637
        if self.data:
 
638
            editor = self.get_current_editor()
 
639
            editor.add_remove_breakpoint()
 
640
            
 
641
    def set_or_edit_conditional_breakpoint(self):
 
642
        """Set conditional breakpoint"""
 
643
        if self.data:
 
644
            editor = self.get_current_editor()
 
645
            editor.add_remove_breakpoint(edit_condition=True)
 
646
            
 
647
    def inspect_current_object(self):
 
648
        """Inspect current object in Object Inspector"""
 
649
        if programs.is_module_installed('rope'):
 
650
            editor = self.get_current_editor()
 
651
            position = editor.get_position('cursor')
 
652
            editor.emit(SIGNAL('trigger_calltip(int)'), position)
 
653
        else:
 
654
            text = self.get_current_editor().get_current_object()
 
655
            if text:
 
656
                self.send_to_inspector(text)
 
657
        
 
658
        
304
659
    #------ Editor Widget Settings
305
660
    def set_closable(self, state):
306
661
        """Parent widget must handle the closable state"""
307
662
        self.is_closable = state
308
663
        
309
 
    def set_io_actions(self, new_action, open_action, save_action):
 
664
    def set_io_actions(self, new_action, open_action,
 
665
                       save_action, revert_action):
310
666
        self.new_action = new_action
311
667
        self.open_action = open_action
312
668
        self.save_action = save_action
 
669
        self.revert_action = revert_action
313
670
        
314
671
    def set_find_widget(self, find_widget):
315
672
        self.find_widget = find_widget
316
673
        
317
 
    def set_classbrowser(self, classbrowser):
318
 
        self.classbrowser = classbrowser
319
 
        self.classbrowser_enabled = True
320
 
        self.connect(self.classbrowser, SIGNAL("classbrowser_is_visible()"),
321
 
                     self._refresh_classbrowser)
 
674
    def set_outlineexplorer(self, outlineexplorer):
 
675
        self.outlineexplorer = outlineexplorer
 
676
        self.outlineexplorer_enabled = True
 
677
        self.connect(self.outlineexplorer,
 
678
                     SIGNAL("outlineexplorer_is_visible()"),
 
679
                     self._refresh_outlineexplorer)
 
680
        
 
681
    def set_projectexplorer(self, projectexplorer):
 
682
        self.projectexplorer = projectexplorer
 
683
        for finfo in self.data:
 
684
            project = self.projectexplorer.get_source_project(finfo.filename)
 
685
            finfo.set_project(project)
 
686
        
 
687
    def set_inspector(self, inspector):
 
688
        self.inspector = inspector
322
689
        
323
690
    def set_tempfile_path(self, path):
324
691
        self.tempfile_path = path
333
700
        self.valid_types = valid_types
334
701
        
335
702
    def __update_editor_margins(self, editor):
336
 
        editor.setup_margins(linenumbers=True,
337
 
                             code_folding=self.codefolding_enabled,
 
703
        editor.setup_margins(linenumbers=self.linenumbers_enabled,
338
704
                             code_analysis=self.codeanalysis_enabled,
339
705
                             todo_list=self.todolist_enabled)
340
706
        
341
707
    def set_codeanalysis_enabled(self, state, current_finfo=None):
342
 
        # CONF.get(self.ID, 'code_analysis')
 
708
        # CONF.get(self.CONF_SECTION, 'code_analysis')
343
709
        self.codeanalysis_enabled = state
344
710
        if self.data:
345
711
            for finfo in self.data:
350
716
                        finfo.run_code_analysis()                    
351
717
    
352
718
    def set_todolist_enabled(self, state, current_finfo=None):
353
 
        # CONF.get(self.ID, 'todo_list')
 
719
        # CONF.get(self.CONF_SECTION, 'todo_list')
354
720
        self.todolist_enabled = state
355
721
        if self.data:
356
722
            for finfo in self.data:
359
725
                if state and current_finfo is not None:
360
726
                    if current_finfo is not finfo:
361
727
                        finfo.run_todo_finder()
362
 
        
363
 
    def set_codefolding_enabled(self, state):
364
 
        # CONF.get(self.ID, 'code_folding')
365
 
        self.codefolding_enabled = state
 
728
                        
 
729
    def set_realtime_analysis_enabled(self, state):
 
730
        self.realtime_analysis_enabled = state
 
731
 
 
732
    def set_realtime_analysis_timeout(self, timeout):
 
733
        self.analysis_timer.setInterval(timeout)
 
734
    
 
735
    def set_linenumbers_enabled(self, state, current_finfo=None):
 
736
        # CONF.get(self.CONF_SECTION, 'line_numbers')
 
737
        self.linenumbers_enabled = state
366
738
        if self.data:
367
739
            for finfo in self.data:
368
740
                self.__update_editor_margins(finfo.editor)
369
 
                if not state:
370
 
                    finfo.editor.unfold_all()
371
 
        
372
 
    def set_classbrowser_enabled(self, state):
373
 
        # CONF.get(self.ID, 'class_browser')
374
 
        self.classbrowser_enabled = state
375
 
        
376
 
    def set_default_font(self, font):
377
 
        # get_font(self.ID)
 
741
        
 
742
    def set_edgeline_enabled(self, state):
 
743
        # CONF.get(self.CONF_SECTION, 'edge_line')
 
744
        self.edgeline_enabled = state
 
745
        if self.data:
 
746
            for finfo in self.data:
 
747
                finfo.editor.set_edge_line_enabled(state)
 
748
        
 
749
    def set_edgeline_column(self, column):
 
750
        # CONF.get(self.CONF_SECTION, 'edge_line_column')
 
751
        self.edgeline_column = column
 
752
        if self.data:
 
753
            for finfo in self.data:
 
754
                finfo.editor.set_edge_line_column(column)
 
755
        
 
756
    def set_codecompletion_auto_enabled(self, state):
 
757
        # CONF.get(self.CONF_SECTION, 'codecompletion_auto')
 
758
        self.codecompletion_auto_enabled = state
 
759
        if self.data:
 
760
            for finfo in self.data:
 
761
                finfo.editor.set_codecompletion_auto(state)
 
762
                
 
763
    def set_codecompletion_case_enabled(self, state):
 
764
        self.codecompletion_case_enabled = state
 
765
        if self.data:
 
766
            for finfo in self.data:
 
767
                finfo.editor.set_codecompletion_case(state)
 
768
                
 
769
    def set_codecompletion_single_enabled(self, state):
 
770
        self.codecompletion_single_enabled = state
 
771
        if self.data:
 
772
            for finfo in self.data:
 
773
                finfo.editor.set_codecompletion_single(state)
 
774
                    
 
775
    def set_codecompletion_enter_enabled(self, state):
 
776
        self.codecompletion_enter_enabled = state
 
777
        if self.data:
 
778
            for finfo in self.data:
 
779
                finfo.editor.set_codecompletion_enter(state)
 
780
                
 
781
    def set_calltips_enabled(self, state):
 
782
        # CONF.get(self.CONF_SECTION, 'calltips')
 
783
        self.calltips_enabled = state
 
784
        if self.data:
 
785
            for finfo in self.data:
 
786
                finfo.editor.set_calltips(state)
 
787
                
 
788
    def set_go_to_definition_enabled(self, state):
 
789
        # CONF.get(self.CONF_SECTION, 'go_to_definition')
 
790
        self.go_to_definition_enabled = state
 
791
        if self.data:
 
792
            for finfo in self.data:
 
793
                finfo.editor.set_go_to_definition_enabled(state)
 
794
                
 
795
    def set_close_parentheses_enabled(self, state):
 
796
        # CONF.get(self.CONF_SECTION, 'close_parentheses')
 
797
        self.close_parentheses_enabled = state
 
798
        if self.data:
 
799
            for finfo in self.data:
 
800
                finfo.editor.set_close_parentheses_enabled(state)
 
801
                
 
802
    def set_auto_unindent_enabled(self, state):
 
803
        # CONF.get(self.CONF_SECTION, 'auto_unindent')
 
804
        self.auto_unindent_enabled = state
 
805
        if self.data:
 
806
            for finfo in self.data:
 
807
                finfo.editor.set_auto_unindent_enabled(state)
 
808
                
 
809
    def set_inspector_enabled(self, state):
 
810
        self.inspector_enabled = state
 
811
        
 
812
    def set_outlineexplorer_enabled(self, state):
 
813
        # CONF.get(self.CONF_SECTION, 'outline_explorer')
 
814
        self.outlineexplorer_enabled = state
 
815
        
 
816
    def set_default_font(self, font, color_scheme=None):
 
817
        # get_font(self.CONF_SECTION)
378
818
        self.default_font = font
379
 
        self.__update_combobox()
380
 
        if self.data:
381
 
            for finfo in self.data:
382
 
                finfo.editor.set_font(font)
 
819
        if color_scheme is not None:
 
820
            self.color_scheme = color_scheme
 
821
        if self.data:
 
822
            for finfo in self.data:
 
823
                finfo.editor.set_font(font, color_scheme)
 
824
            
 
825
    def set_color_scheme(self, color_scheme):
 
826
        self.color_scheme = color_scheme
 
827
        if self.data:
 
828
            for finfo in self.data:
 
829
                finfo.editor.set_color_scheme(color_scheme)
383
830
        
384
831
    def set_wrap_enabled(self, state):
385
 
        # CONF.get(self.ID, 'wrap')
 
832
        # CONF.get(self.CONF_SECTION, 'wrap')
386
833
        self.wrap_enabled = state
387
834
        if self.data:
388
835
            for finfo in self.data:
389
836
                finfo.editor.toggle_wrap_mode(state)
390
837
        
391
838
    def set_tabmode_enabled(self, state):
392
 
        # CONF.get(self.ID, 'tab_always_indent'))
 
839
        # CONF.get(self.CONF_SECTION, 'tab_always_indent')
393
840
        self.tabmode_enabled = state
394
841
        if self.data:
395
842
            for finfo in self.data:
396
843
                finfo.editor.set_tab_mode(state)
 
844
                
 
845
    def set_intelligent_backspace_enabled(self, state):
 
846
        # CONF.get(self.CONF_SECTION, 'intelligent_backspace')
 
847
        self.intelligent_backspace_enabled = state
 
848
        if self.data:
 
849
            for finfo in self.data:
 
850
                finfo.editor.toggle_intelligent_backspace(state)
397
851
        
398
852
    def set_occurence_highlighting_enabled(self, state):
399
 
        # CONF.get(self.ID, 'occurence_highlighting'))
 
853
        # CONF.get(self.CONF_SECTION, 'occurence_highlighting')
400
854
        self.occurence_highlighting_enabled = state
401
855
        if self.data:
402
856
            for finfo in self.data:
403
857
                finfo.editor.set_occurence_highlighting(state)
 
858
                
 
859
    def set_highlight_current_line_enabled(self, state):
 
860
        self.highlight_current_line_enabled = state
 
861
        if self.data:
 
862
            for finfo in self.data:
 
863
                finfo.editor.set_highlight_current_line(state)
404
864
        
405
865
    def set_checkeolchars_enabled(self, state):
406
 
        # CONF.get(self.ID, 'check_eol_chars')
 
866
        # CONF.get(self.CONF_SECTION, 'check_eol_chars')
407
867
        self.checkeolchars_enabled = state
408
868
        
409
 
    def __update_combobox(self):
410
 
        if self.fullpath_sorting_enabled:
411
 
            if self.default_font is not None:
412
 
                combo_font = QFont(self.default_font)
413
 
                combo_font.setPointSize(combo_font.pointSize()-1)
414
 
                self.combo.setFont(combo_font)
415
 
            self.combo.setEditable(True)
416
 
            self.combo.lineEdit().setReadOnly(True)
417
 
        else:
418
 
            self.combo.setFont(self.default_combo_font)
419
 
            self.combo.setEditable(False)
420
 
        
421
869
    def set_fullpath_sorting_enabled(self, state):
422
 
        # CONF.get(self.ID, 'fullpath_sorting')
 
870
        # CONF.get(self.CONF_SECTION, 'fullpath_sorting')
423
871
        self.fullpath_sorting_enabled = state
424
 
        self.__update_combobox()
425
872
        if self.data:
426
873
            finfo = self.data[self.get_stack_index()]
427
874
            self.data.sort(key=self.__get_sorting_func())
428
875
            new_index = self.data.index(finfo)
429
876
            self.__repopulate_stack()
430
877
            self.set_stack_index(new_index)
 
878
            
431
879
    
432
880
    #------ Stacked widget management
433
881
    def get_stack_index(self):
434
882
        return self.tabs.currentIndex()
435
883
    
436
884
    def get_current_finfo(self):
437
 
        return self.data[self.get_stack_index()]
 
885
        if self.data:
 
886
            return self.data[self.get_stack_index()]
438
887
    
439
888
    def get_current_editor(self):
440
889
        return self.tabs.currentWidget()
443
892
        return self.tabs.count()
444
893
    
445
894
    def set_stack_index(self, index):
446
 
        for widget in (self.tabs, self.combo):
447
 
            widget.setCurrentIndex(index)
 
895
        self.tabs.setCurrentIndex(index)
448
896
            
449
897
    def set_tabbar_visible(self, state):
450
898
        self.tabs.tabBar().setVisible(state)
451
899
    
452
900
    def remove_from_data(self, index):
 
901
        self.tabs.blockSignals(True)
453
902
        self.tabs.removeTab(index)
454
903
        self.data.pop(index)
455
 
        self.combo.removeItem(index)
 
904
        self.tabs.blockSignals(False)
456
905
        self.update_actions()
457
 
    
 
906
        self.update_filelistdialog()
 
907
    
 
908
    def __modified_readonly_title(self, title, is_modified, is_readonly):
 
909
        if is_modified is not None and is_modified:
 
910
            title += "*"
 
911
        if is_readonly is not None and is_readonly:
 
912
            title = "(%s)" % title
 
913
        return title
 
914
    
 
915
    def get_tab_text(self, filename, is_modified=None, is_readonly=None):
 
916
        """Return tab title"""
 
917
        return self.__modified_readonly_title(osp.basename(filename),
 
918
                                              is_modified, is_readonly)
 
919
                
 
920
    def get_tab_tip(self, filename, is_modified=None, is_readonly=None):
 
921
        """Return tab menu title"""
 
922
        if self.fullpath_sorting_enabled:
 
923
            text = filename
 
924
        else:
 
925
            text = u"%s — %s"
 
926
        text = self.__modified_readonly_title(text,
 
927
                                              is_modified, is_readonly)
 
928
        if filename == encoding.to_unicode(self.tempfile_path):
 
929
            temp_file_str = unicode(translate("Editor", "Temporary file"))
 
930
            if self.fullpath_sorting_enabled:
 
931
                return "%s (%s)" % (text, temp_file_str)
 
932
            else:
 
933
                return text % (temp_file_str, self.tempfile_path)
 
934
        else:
 
935
            if self.fullpath_sorting_enabled:
 
936
                return text
 
937
            else:
 
938
                return text % (osp.basename(filename), osp.dirname(filename))
 
939
        
458
940
    def __get_sorting_func(self):
459
941
        if self.fullpath_sorting_enabled:
460
942
            return lambda item: osp.join(osp.dirname(item.filename),
467
949
        self.data.sort(key=self.__get_sorting_func())
468
950
        index = self.data.index(finfo)
469
951
        fname, editor = finfo.filename, finfo.editor
470
 
        self.combo.blockSignals(True)
471
952
        self.tabs.insertTab(index, editor, get_filetype_icon(fname),
472
 
                            self.get_tab_title(fname))
473
 
        self.combo.insertItem(index, get_filetype_icon(fname),
474
 
                              self.get_combo_title(fname))
 
953
                            self.get_tab_text(fname))
 
954
        self.set_stack_title(index, False)
475
955
        if set_current:
476
956
            self.set_stack_index(index)
477
 
        self.combo.blockSignals(False)
478
957
        if set_current:
479
958
            self.current_changed(index)
480
959
        self.update_actions()
 
960
        self.update_filelistdialog()
481
961
        
482
962
    def __repopulate_stack(self):
483
 
        self.combo.blockSignals(True)
484
 
        for _i in range(self.tabs.count()):
485
 
            self.tabs.removeTab(_i)
486
 
        self.combo.clear()
487
 
        for _i, _fi in enumerate(self.data):
488
 
            fname, editor = _fi.filename, _fi.editor
489
 
            self.tabs.insertTab(_i, editor, get_filetype_icon(fname),
490
 
                                self.get_tab_title(fname))
491
 
            self.combo.insertItem(_i, get_filetype_icon(fname),
492
 
                                  self.get_combo_title(fname))
493
 
        self.combo.blockSignals(False)
 
963
        self.tabs.blockSignals(True)
 
964
        self.tabs.clear()
 
965
        for finfo in self.data:
 
966
            icon = get_filetype_icon(finfo.filename)
 
967
            tab_text = self.get_tab_text(finfo.filename)
 
968
            tab_tip = self.get_tab_tip(finfo.filename)
 
969
            index = self.tabs.addTab(finfo.editor, icon, tab_text)
 
970
            self.tabs.setTabToolTip(index, tab_tip)
 
971
        self.tabs.blockSignals(False)
 
972
        self.update_filelistdialog()
494
973
        
495
974
    def rename_in_data(self, index, new_filename):
496
975
        finfo = self.data[index]
 
976
        if osp.splitext(finfo.filename)[1] != osp.splitext(new_filename)[1]:
 
977
            # File type has changed!
 
978
            language = get_file_language(new_filename)
 
979
            finfo.editor.set_language(language)
497
980
        set_new_index = index == self.get_stack_index()
498
981
        finfo.filename = new_filename
499
982
        self.data.sort(key=self.__get_sorting_func())
501
984
        self.__repopulate_stack()
502
985
        if set_new_index:
503
986
            self.set_stack_index(new_index)
504
 
        if self.classbrowser is not None:
505
 
            self.classbrowser.file_renamed(finfo.editor, finfo.filename)
 
987
        if self.outlineexplorer is not None:
 
988
            self.outlineexplorer.file_renamed(finfo.editor, finfo.filename)
506
989
        return new_index
507
990
        
508
 
    def set_stack_title(self, index, combo_title, tab_title):
509
 
        self.combo.setItemText(index, combo_title)
510
 
        self.tabs.setTabText(index, tab_title)
 
991
    def set_stack_title(self, index, is_modified):
 
992
        finfo = self.data[index]
 
993
        fname = finfo.filename
 
994
        is_readonly = finfo.editor.isReadOnly()
 
995
        tab_text = self.get_tab_text(fname, is_modified, is_readonly)
 
996
        tab_tip = self.get_tab_tip(fname, is_modified, is_readonly)
 
997
        self.tabs.setTabText(index, tab_text)
 
998
        self.tabs.setTabToolTip(index, tab_tip)
511
999
        
512
1000
        
513
1001
    #------ Context menu
615
1103
                new_index = current_index
616
1104
        is_ok = self.save_if_changed(cancelable=True, index=index)
617
1105
        if is_ok:
618
 
            # Removing editor reference from class browser settings:
619
 
            if self.classbrowser is not None:
620
 
                self.classbrowser.remove_editor(self.data[index].editor)
 
1106
            finfo = self.data[index]
 
1107
            finfo.close_threads()
 
1108
            # Removing editor reference from outline explorer settings:
 
1109
            if self.outlineexplorer is not None:
 
1110
                self.outlineexplorer.remove_editor(finfo.editor)
621
1111
            
622
1112
            self.remove_from_data(index)
623
1113
            self.emit(SIGNAL('close_file(int)'), index)
627
1117
                self.close()
628
1118
            self.emit(SIGNAL('opened_files_list_changed()'))
629
1119
            self.emit(SIGNAL('update_code_analysis_actions()'))
630
 
            self._refresh_classbrowser()
 
1120
            self._refresh_outlineexplorer()
631
1121
            self.emit(SIGNAL('refresh_file_dependent_actions()'))
632
1122
            
633
1123
            if new_index is not None:
654
1144
            buttons |= QMessageBox.Cancel
655
1145
        unsaved_nb = 0
656
1146
        for index in indexes:
657
 
            if self.data[index].editor.isModified():
 
1147
            if self.data[index].editor.document().isModified():
658
1148
                unsaved_nb += 1
659
1149
        if not unsaved_nb:
660
1150
            # No file to save
668
1158
            if finfo.filename == self.tempfile_path or yes_all:
669
1159
                if not self.save(refresh_explorer=False):
670
1160
                    return False
671
 
            elif finfo.editor.isModified():
 
1161
            elif finfo.editor.document().isModified():
672
1162
                answer = QMessageBox.question(self, self.title,
673
1163
                            translate("Editor",
674
1164
                                      "<b>%1</b> has been modified."
696
1186
            index = self.get_stack_index()
697
1187
            
698
1188
        finfo = self.data[index]
699
 
        if not finfo.editor.isModified() and not force:
 
1189
        if not finfo.editor.document().isModified() and not force:
700
1190
            return True
701
1191
        if not osp.isfile(finfo.filename) and not force:
702
1192
            # File has not been saved yet
703
 
            filename = self.select_savename(finfo.filename)
704
 
            if filename:
705
 
                finfo.filename = filename
706
 
            else:
707
 
                return False
708
 
        txt = unicode(finfo.editor.get_text())
 
1193
            return self.save_as(index=index)
 
1194
        txt = unicode(finfo.editor.get_text_with_eol())
709
1195
        try:
710
1196
            finfo.encoding = encoding.write(txt, finfo.filename, finfo.encoding)
711
1197
            finfo.newly_created = False
712
1198
            self.emit(SIGNAL('encoding_changed(QString)'), finfo.encoding)
713
1199
            finfo.lastmodified = QFileInfo(finfo.filename).lastModified()
714
1200
            self.emit(SIGNAL('file_saved(int)'), index)
715
 
            finfo.editor.setModified(False)
 
1201
            finfo.editor.document().setModified(False)
716
1202
            self.modification_changed(index=index)
717
1203
            self.analyze_script(index)
 
1204
            finfo.validate_project()
718
1205
            
719
 
            #XXX QtEditor-only: re-scan the whole text to rebuild class browser 
 
1206
            #XXX CodeEditor-only: re-scan the whole text to rebuild outline explorer 
720
1207
            #    data from scratch (could be optimized because rehighlighting
721
1208
            #    text means searching for all syntax coloring patterns instead 
722
1209
            #    of only searching for class/def patterns which would be 
723
 
            #    sufficient for class browser data.
 
1210
            #    sufficient for outline explorer data.
724
1211
            finfo.editor.rehighlight()
725
1212
            
726
 
            self._refresh_classbrowser(index)
 
1213
            self._refresh_outlineexplorer(index)
727
1214
            if refresh_explorer:
728
1215
                # Refresh the explorer widget if it exists:
729
1216
                self.emit(SIGNAL("refresh_explorer(QString)"),
755
1242
        if filename:
756
1243
            return osp.normpath(unicode(filename))
757
1244
    
758
 
    def save_as(self):
 
1245
    def save_as(self, index=None):
759
1246
        """Save file as..."""
760
 
        index = self.get_stack_index()
 
1247
        if index is None:
 
1248
            # Save the currently edited file
 
1249
            index = self.get_stack_index()
761
1250
        finfo = self.data[index]
762
1251
        filename = self.select_savename(finfo.filename)
763
1252
        if filename:
764
1253
            ao_index = self.has_filename(filename)
765
 
            if ao_index:
 
1254
            # Note: ao_index == index --> saving an untitled file
 
1255
            if ao_index and ao_index != index:
766
1256
                if not self.close_file(ao_index):
767
1257
                    return
768
1258
                if ao_index < index:
769
1259
                    index -= 1
770
1260
            new_index = self.rename_in_data(index, new_filename=filename)
771
 
            self.save(index=new_index, force=True)
 
1261
            ok = self.save(index=new_index, force=True)
772
1262
            self.refresh(new_index)
773
1263
            self.set_stack_index(new_index)
 
1264
            return ok
 
1265
        else:
 
1266
            return False
774
1267
        
775
1268
    def save_all(self):
776
1269
        """Save all opened files"""
777
1270
        folders = set()
778
1271
        for index in range(self.get_stack_count()):
779
 
            folders.add(osp.dirname(self.data[index].filename))
780
 
            self.save(index, refresh_explorer=False)
 
1272
            if self.data[index].editor.document().isModified():
 
1273
                folders.add(osp.dirname(self.data[index].filename))
 
1274
                self.save(index, refresh_explorer=False)
781
1275
        for folder in folders:
782
1276
            self.emit(SIGNAL("refresh_explorer(QString)"), folder)
783
1277
    
784
1278
    #------ Update UI
 
1279
    def start_stop_analysis_timer(self):
 
1280
        self.is_analysis_done = False
 
1281
        if self.realtime_analysis_enabled:
 
1282
            self.analysis_timer.stop()
 
1283
            self.analysis_timer.start()
 
1284
    
785
1285
    def analyze_script(self, index=None):
786
1286
        """Analyze current script with pyflakes + find todos"""
 
1287
        if self.is_analysis_done:
 
1288
            return
787
1289
        if index is None:
788
1290
            index = self.get_stack_index()
789
1291
        if self.data:
792
1294
                finfo.run_code_analysis()
793
1295
            if self.todolist_enabled:
794
1296
                finfo.run_todo_finder()
 
1297
        self.is_analysis_done = True
795
1298
                
796
1299
    def set_analysis_results(self, index, analysis_results):
797
1300
        """Synchronize analysis results between editorstacks"""
811
1314
        
812
1315
    def current_changed(self, index):
813
1316
        """Stack index has changed"""
814
 
        count = self.get_stack_count()
815
 
        if self.close_btn is not None:
816
 
            self.close_btn.setEnabled(count > 0)
817
 
        for btn in (self.previous_btn, self.next_btn):
818
 
            btn.setEnabled(count > 1)
 
1317
#        count = self.get_stack_count()
 
1318
#        for btn in (self.filelist_btn, self.previous_btn, self.next_btn):
 
1319
#            btn.setEnabled(count > 1)
819
1320
        
820
1321
        editor = self.get_current_editor()
821
1322
        if index != -1:
839
1340
        if DEBUG:
840
1341
            print >>STDOUT, "current_changed:", index, self.data[index].editor,
841
1342
            print >>STDOUT, self.data[index].editor.get_document_id()
 
1343
            
 
1344
        if editor is not None:
 
1345
            self.emit(SIGNAL('current_file_changed(QString,int)'),
 
1346
                      self.data[index].filename, editor.get_position('cursor'))
842
1347
        
843
1348
    def _get_previous_file_index(self):
844
1349
        if len(self.stack_history) > 1:
879
1384
            if fwidget is finfo.editor:
880
1385
                self.refresh()
881
1386
        
882
 
    def _refresh_classbrowser(self, index=None, update=True):
883
 
        """Refresh class browser panel"""
 
1387
    def _refresh_outlineexplorer(self, index=None, update=True):
 
1388
        """Refresh outline explorer panel"""
884
1389
        if index is None:
885
1390
            index = self.get_stack_index()
886
1391
        enable = False
887
 
        cb = self.classbrowser
 
1392
        oe = self.outlineexplorer
888
1393
        if self.data:
889
1394
            finfo = self.data[index]
890
 
            # cb_visible: if class browser is not visible, maybe the whole
 
1395
            # oe_visible: if outline explorer is not visible, maybe the whole
891
1396
            # GUI is not visible (Spyder is starting up) -> in this case,
892
 
            # it is necessary to update the class browser
893
 
            cb_visible = cb.isVisible() or not self.isVisible()
894
 
            if self.classbrowser_enabled and finfo.editor.is_python() \
895
 
               and cb_visible:
 
1397
            # it is necessary to update the outline explorer
 
1398
            oe_visible = oe.isVisible() or not self.isVisible()
 
1399
            if self.outlineexplorer_enabled and finfo.editor.is_python() \
 
1400
               and oe_visible:
896
1401
                enable = True
897
 
                cb.setEnabled(True)
898
 
                cb.set_current_editor(finfo.editor, finfo.filename,
 
1402
                oe.setEnabled(True)
 
1403
                oe.set_current_editor(finfo.editor, finfo.filename,
899
1404
                                      update=update)
900
1405
        if not enable:
901
 
            cb.setEnabled(False)
 
1406
            oe.setEnabled(False)
902
1407
            
903
1408
    def __refresh_statusbar(self, index):
904
1409
        """Refreshing statusbar widgets"""
920
1425
    def __check_file_status(self, index):
921
1426
        if self.__file_status_flag:
922
1427
            # Avoid infinite loop: when the QMessageBox.question pops, it
923
 
            # gets focus and then give it back to the QsciEditor instance,
 
1428
            # gets focus and then give it back to the CodeEditor instance,
924
1429
            # triggering a refresh cycle which calls this method
925
1430
            return
926
1431
        
946
1451
            # Else, testing if it has been modified elsewhere:
947
1452
            lastm = QFileInfo(finfo.filename).lastModified()
948
1453
            if lastm.toString().compare(finfo.lastmodified.toString()):
949
 
                if finfo.editor.isModified():
 
1454
                if finfo.editor.document().isModified():
950
1455
                    answer = QMessageBox.question(self,
951
1456
                        self.title,
952
1457
                        translate("Editor",
974
1479
            finfo = self.data[index]
975
1480
            editor = finfo.editor
976
1481
            editor.setFocus()
977
 
            self._refresh_classbrowser(index, update=False)
 
1482
            self._refresh_outlineexplorer(index, update=False)
978
1483
            self.emit(SIGNAL('update_code_analysis_actions()'))
979
1484
            self.__refresh_statusbar(index)
980
1485
            self.__refresh_readonly(index)
986
1491
        # Update FindReplace binding
987
1492
        self.find_widget.set_editor(editor, refresh=False)
988
1493
                
989
 
    def __modified_readonly_title(self, title, is_modified, is_readonly):
990
 
        if is_modified is not None and is_modified:
991
 
            title += "*"
992
 
        if is_readonly is not None and is_readonly:
993
 
            title = "(%s)" % title
994
 
        return title
995
 
    
996
 
    def get_tab_title(self, filename, is_modified=None, is_readonly=None):
997
 
        """Return tab title"""
998
 
        return self.__modified_readonly_title(osp.basename(filename),
999
 
                                              is_modified, is_readonly)
1000
 
                
1001
 
    def get_combo_title(self, filename, is_modified=None, is_readonly=None):
1002
 
        """Return combo box title"""
1003
 
        if self.fullpath_sorting_enabled:
1004
 
            text = filename
1005
 
        else:
1006
 
            text = u"%s — %s"
1007
 
        text = self.__modified_readonly_title(text,
1008
 
                                              is_modified, is_readonly)
1009
 
        if filename == encoding.to_unicode(self.tempfile_path):
1010
 
            temp_file_str = unicode(translate("Editor", "Temporary file"))
1011
 
            if self.fullpath_sorting_enabled:
1012
 
                return "%s (%s)" % (text, temp_file_str)
1013
 
            else:
1014
 
                return text % (temp_file_str, self.tempfile_path)
1015
 
        else:
1016
 
            if self.fullpath_sorting_enabled:
1017
 
                return text
1018
 
            else:
1019
 
                return text % (osp.basename(filename), osp.dirname(filename))
1020
 
        
1021
 
    def get_titles(self, is_modified, finfo):
1022
 
        """Return combo box and tab titles"""
1023
 
        fname = finfo.filename
1024
 
        is_readonly = finfo.editor.isReadOnly()
1025
 
        combo_title = self.get_combo_title(fname, is_modified, is_readonly)
1026
 
        tab_title = self.get_tab_title(fname, is_modified, is_readonly)
1027
 
        return combo_title, tab_title
1028
 
    
1029
1494
    def modification_changed(self, state=None, index=None):
1030
1495
        """
1031
1496
        Current editor's modification state has changed
1047
1512
            return
1048
1513
        finfo = self.data[index]
1049
1514
        if state is None:
1050
 
            state = finfo.editor.isModified()
1051
 
        combo_title, tab_title = self.get_titles(state, finfo)
1052
 
        self.set_stack_title(index, combo_title, tab_title)
 
1515
            state = finfo.editor.document().isModified()
 
1516
        self.set_stack_title(index, state)
1053
1517
        # Toggle save/save all actions state
1054
1518
        self.save_action.setEnabled(state)
 
1519
        self.revert_action.setEnabled(state)
1055
1520
        self.emit(SIGNAL('refresh_save_all_action()'))
1056
1521
        # Refreshing eol mode
1057
1522
        eol_chars = finfo.editor.get_line_separator()
1058
1523
        os_name = sourcecode.get_os_name_from_eol_chars(eol_chars)
1059
 
        self.emit(SIGNAL('refresh_eol_mode(QString)'), os_name)
 
1524
        self.emit(SIGNAL('refresh_eol_chars(QString)'), os_name)
1060
1525
        
1061
1526
 
1062
1527
    #------ Load, reload
1066
1531
        finfo.lastmodified = QFileInfo(finfo.filename).lastModified()
1067
1532
        position = finfo.editor.get_position('cursor')
1068
1533
        finfo.editor.set_text(txt)
1069
 
        finfo.editor.setModified(False)
 
1534
        finfo.editor.document().setModified(False)
1070
1535
        finfo.editor.set_cursor_position(position)
 
1536
        finfo.validate_project()
 
1537
        
 
1538
    def revert(self):
 
1539
        index = self.get_stack_index()
 
1540
        filename = self.data[index].filename
 
1541
        answer = QMessageBox.warning(self, self.title,
 
1542
                    translate("Editor",
 
1543
                              "All changes to <b>%1</b> will be lost."
 
1544
                              "<br>Do you want to revert file from disk?").arg(
 
1545
                    osp.basename(filename)), QMessageBox.Yes|QMessageBox.No)
 
1546
        if answer == QMessageBox.Yes:
 
1547
            self.reload(index)
1071
1548
        
1072
1549
    def create_new_editor(self, fname, enc, txt,
1073
1550
                          set_current, new=False, cloned_from=None):
1075
1552
        Create a new editor instance
1076
1553
        Returns finfo object (instead of editor as in previous releases)
1077
1554
        """
1078
 
        ext = osp.splitext(fname)[1]
1079
 
        if ext.startswith('.'):
1080
 
            ext = ext[1:] # file extension with leading dot
1081
 
        language = ext
1082
 
        if not ext:
1083
 
            for line in txt.splitlines():
1084
 
                if not line.strip():
1085
 
                    continue
1086
 
                if line.startswith('#!') and \
1087
 
                   line[2:].split() == ['/usr/bin/env', 'python']:
1088
 
                        language = 'python'
1089
 
                else:
1090
 
                    break
1091
1555
        editor = CodeEditor(self)
1092
 
        finfo = TabInfo(fname, enc, editor, new)
 
1556
        finfo = FileInfo(fname, enc, editor, new)
 
1557
        if self.projectexplorer is not None:
 
1558
            finfo.set_project(self.projectexplorer.get_source_project(fname))
1093
1559
        self.add_to_data(finfo, set_current)
 
1560
        self.connect(finfo, SIGNAL("send_to_inspector(QString,QString)"),
 
1561
                     self.send_to_inspector)
1094
1562
        self.connect(finfo, SIGNAL('analysis_results_changed()'),
1095
1563
                     lambda: self.emit(SIGNAL('analysis_results_changed()')))
1096
1564
        self.connect(finfo, SIGNAL('todo_results_changed()'),
1097
1565
                     lambda: self.emit(SIGNAL('todo_results_changed()')))
1098
 
        editor.setup_editor(linenumbers=True, language=language,
 
1566
        self.connect(finfo, SIGNAL("edit_goto(QString,int,QString)"),
 
1567
                     lambda fname, lineno, name:
 
1568
                     self.emit(SIGNAL("edit_goto(QString,int,QString)"),
 
1569
                               fname, lineno, name))
 
1570
        self.connect(finfo, SIGNAL("save_breakpoints(QString,QString)"),
 
1571
                     lambda s1, s2:
 
1572
                     self.emit(SIGNAL("save_breakpoints(QString,QString)"),
 
1573
                               s1, s2))
 
1574
        language = get_file_language(fname, txt)
 
1575
        editor.setup_editor(
 
1576
                linenumbers=self.linenumbers_enabled,
 
1577
                edge_line=self.edgeline_enabled,
 
1578
                edge_line_column=self.edgeline_column, language=language,
1099
1579
                code_analysis=self.codeanalysis_enabled,
1100
 
                code_folding=self.codefolding_enabled,
1101
1580
                todo_list=self.todolist_enabled, font=self.default_font,
 
1581
                color_scheme=self.color_scheme,
1102
1582
                wrap=self.wrap_enabled, tab_mode=self.tabmode_enabled,
 
1583
                intelligent_backspace=self.intelligent_backspace_enabled,
 
1584
                highlight_current_line=self.highlight_current_line_enabled,
1103
1585
                occurence_highlighting=self.occurence_highlighting_enabled,
 
1586
                codecompletion_auto=self.codecompletion_auto_enabled,
 
1587
                codecompletion_case=self.codecompletion_case_enabled,
 
1588
                codecompletion_single=self.codecompletion_single_enabled,
 
1589
                codecompletion_enter=self.codecompletion_enter_enabled,
 
1590
                calltips=self.calltips_enabled,
 
1591
                go_to_definition=self.go_to_definition_enabled,
 
1592
                close_parentheses=self.close_parentheses_enabled,
 
1593
                auto_unindent=self.auto_unindent_enabled,
1104
1594
                cloned_from=cloned_from)
1105
1595
        if cloned_from is None:
1106
1596
            editor.set_text(txt)
1107
 
            editor.setModified(False)
 
1597
            editor.document().setModified(False)
 
1598
        self.connect(finfo, SIGNAL('text_changed_at(QString,int)'),
 
1599
                     lambda fname, position:
 
1600
                     self.emit(SIGNAL('text_changed_at(QString,int)'),
 
1601
                               fname, position))
1108
1602
        self.connect(editor, SIGNAL('cursorPositionChanged(int,int)'),
1109
1603
                     self.cursor_position_changed_callback)
 
1604
        self.connect(editor, SIGNAL('textChanged()'),
 
1605
                     self.start_stop_analysis_timer)
1110
1606
        self.connect(editor, SIGNAL('modificationChanged(bool)'),
1111
1607
                     self.modification_changed)
1112
1608
        self.connect(editor, SIGNAL("focus_in()"), self.focus_changed)
1113
1609
        self.connect(editor, SIGNAL("focus_changed()"),
1114
1610
                     self.focus_changed_callback)
1115
 
        if self.classbrowser is not None:
1116
 
            # Removing editor reference from class browser settings:
 
1611
        if self.outlineexplorer is not None:
 
1612
            # Removing editor reference from outline explorer settings:
1117
1613
            self.connect(editor, SIGNAL("destroyed()"),
1118
1614
                         lambda obj=editor:
1119
 
                         self.classbrowser.remove_editor(obj))
 
1615
                         self.outlineexplorer.remove_editor(obj))
1120
1616
 
1121
1617
        self.find_widget.set_editor(editor)
1122
1618
       
1125
1621
        
1126
1622
        return finfo
1127
1623
    
 
1624
    def send_to_inspector(self, qstr1, qstr2=None):
 
1625
        """qstr1: obj_text, qstr2: doc_text"""
 
1626
        if not self.inspector_enabled:
 
1627
            return
 
1628
        if self.inspector is not None and self.inspector.dockwidget.isVisible():
 
1629
            # ObjectInspector widget exists and is visible
 
1630
            if qstr2 is None:
 
1631
                self.inspector.set_object_text(qstr1, ignore_unknown=True)
 
1632
            else:
 
1633
                self.inspector.set_rope_doc(unicode(qstr1), unicode(qstr2))
 
1634
            editor = self.get_current_editor()
 
1635
            editor.setFocus()
 
1636
    
1128
1637
    def new(self, filename, encoding, text):
1129
1638
        """
1130
1639
        Create new filename with *encoding* and *text*
1131
1640
        """
1132
1641
        finfo = self.create_new_editor(filename, encoding, text,
1133
1642
                                       set_current=True, new=True)
1134
 
        editor = finfo.editor
1135
 
        editor.set_cursor_position('eof')
1136
 
        editor.insert_text(os.linesep)
1137
 
        return editor
 
1643
        finfo.editor.set_cursor_position('eof')
 
1644
        finfo.editor.insert_text(os.linesep)
 
1645
        return finfo.editor
1138
1646
        
1139
1647
    def load(self, filename, set_current=True):
1140
1648
        """
1149
1657
        text, enc = encoding.read(filename)
1150
1658
        finfo = self.create_new_editor(filename, enc, text, set_current)
1151
1659
        index = self.get_stack_index()
1152
 
        self._refresh_classbrowser(index, update=True)
 
1660
        self._refresh_outlineexplorer(index, update=True)
1153
1661
        self.emit(SIGNAL('ending_long_process(QString)'), "")
1154
1662
        if self.isVisible() and self.checkeolchars_enabled \
1155
1663
           and sourcecode.has_mixed_eol_chars(text):
1161
1669
                                      "automatically.").arg(name),
1162
1670
                            QMessageBox.Ok)
1163
1671
            self.set_os_eol_chars(index)
 
1672
        self.is_analysis_done = False
1164
1673
        return finfo.editor
1165
1674
    
1166
1675
    def set_os_eol_chars(self, index=None):
1167
1676
        if index is None:
1168
1677
            index = self.get_stack_index()
1169
1678
        finfo = self.data[index]
1170
 
        eol_mode = sourcecode.get_eol_chars_from_os_name(os.name)
1171
 
        finfo.editor.set_eol_mode(eol_mode)
1172
 
        finfo.editor.setModified(True)
 
1679
        eol_chars = sourcecode.get_eol_chars_from_os_name(os.name)
 
1680
        finfo.editor.set_eol_chars(eol_chars)
 
1681
        finfo.editor.document().setModified(True)
1173
1682
        
1174
1683
    def remove_trailing_spaces(self, index=None):
1175
1684
        """Remove trailing spaces"""
1193
1702
        _indent = lambda line: len(line)-len(line.lstrip())
1194
1703
        
1195
1704
        line_from, line_to = editor.get_selection_bounds()
1196
 
        text = unicode(editor.selectedText())
 
1705
        text = editor.get_selected_text()
1197
1706
 
1198
1707
        lines = text.split(ls)
1199
1708
        if len(lines) > 1:
1200
1709
            # Multiline selection -> eventually fixing indentation
1201
 
            original_indent = _indent(unicode(editor.text(line_from)))
 
1710
            original_indent = _indent(editor.get_text_line(line_from))
1202
1711
            text = (" "*(original_indent-_indent(lines[0])))+text
1203
1712
        
1204
1713
        # If there is a common indent to all lines, remove it
1210
1719
            text = ls.join([line[min_indent:] for line in text.split(ls)])
1211
1720
 
1212
1721
        last_line = text.split(ls)[-1]
1213
 
        if last_line.strip() == unicode(editor.text(line_to)).strip():
 
1722
        if last_line.strip() == editor.get_text_line(line_to).strip():
1214
1723
            # If last line is complete, add an EOL character
1215
1724
            text += ls
1216
1725
        
1217
1726
        return text
1218
1727
    
1219
 
    def __run_in_interactive_console(self, lines):
1220
 
        self.emit(SIGNAL('interactive_console_execute_lines(QString)'), lines)
1221
 
 
1222
1728
    def __run_in_external_console(self, lines):
1223
1729
        self.emit(SIGNAL('external_console_execute_lines(QString)'), lines)
1224
1730
    
1225
 
    def run_selection_or_block(self, external=False):
 
1731
    def run_selection_or_block(self):
1226
1732
        """
1227
1733
        Run selected text in console and set focus to console
1228
1734
        *or*, if there is no selection,
1229
1735
        Run current block of lines in console and go to next block
1230
1736
        """
1231
 
        if external:
1232
 
            run_callback = self.__run_in_external_console
1233
 
        else:
1234
 
            run_callback = self.__run_in_interactive_console
1235
1737
        editor = self.get_current_editor()
1236
 
        if editor.hasSelectedText():
1237
 
            # Run selected text in interactive console and set focus to console
1238
 
            run_callback( self.__process_lines() )
 
1738
        if editor.has_selected_text():
 
1739
            # Run selected text in external console and set focus to console
 
1740
            self.__run_in_external_console( self.__process_lines() )
1239
1741
        else:
1240
 
            # Run current block in interactive console and go to next block
 
1742
            # Run current block in external console and go to next block
1241
1743
            editor.select_current_block()
1242
 
            run_callback( self.__process_lines() )
 
1744
            self.__run_in_external_console( self.__process_lines() )
1243
1745
            editor.setFocus()
1244
 
            editor.move_cursor_to_next('block', 'down')
 
1746
            editor.clear_selection()
1245
1747
            
1246
1748
    #------ Drag and drop
1247
1749
    def dragEnterEvent(self, event):
1307
1809
        self.addWidget(self.editorstack)
1308
1810
 
1309
1811
    def closeEvent(self, event):
1310
 
        super(EditorSplitter, self).closeEvent(event)
 
1812
        QSplitter.closeEvent(self, event)
1311
1813
        if PYQT_VERSION_STR.startswith('4.6'):
1312
1814
            self.emit(SIGNAL('destroyed()'))
1313
1815
                                
1498
2000
 
1499
2001
 
1500
2002
class EditorWidget(QSplitter):
1501
 
    def __init__(self, parent, plugin, menu_actions, toolbar_list, menu_list,
1502
 
                 show_fullpath, fullpath_sorting, show_all_files):
1503
 
        super(EditorWidget, self).__init__(parent)
 
2003
    def __init__(self, parent, plugin, menu_actions, show_fullpath,
 
2004
                 fullpath_sorting, show_all_files, show_comments):
 
2005
        QSplitter.__init__(self, parent)
1504
2006
        self.setAttribute(Qt.WA_DeleteOnClose)
1505
2007
        
1506
2008
        statusbar = parent.statusBar() # Create a status bar
1514
2016
        self.plugin = plugin
1515
2017
        
1516
2018
        self.find_widget = FindReplace(self, enable_replace=True)
 
2019
        self.plugin.register_widget_shortcuts("Editor", self.find_widget)
1517
2020
        self.find_widget.hide()
1518
 
        self.classbrowser = ClassBrowser(self, show_fullpath=show_fullpath,
1519
 
                                         fullpath_sorting=fullpath_sorting,
1520
 
                                         show_all_files=show_all_files)
1521
 
        self.connect(self.classbrowser,
1522
 
                     SIGNAL("edit_goto(QString,int,QString)"), plugin.load)
 
2021
        self.outlineexplorer = OutlineExplorer(self, show_fullpath=show_fullpath,
 
2022
                                            fullpath_sorting=fullpath_sorting,
 
2023
                                            show_all_files=show_all_files,
 
2024
                                            show_comments=show_comments)
 
2025
        self.connect(self.outlineexplorer,
 
2026
                     SIGNAL("edit_goto(QString,int,QString)"),
 
2027
                     lambda filenames, goto, word:
 
2028
                     plugin.load(filenames=filenames, goto=goto, word=word,
 
2029
                                 editorwindow=self.parent()))
1523
2030
        
1524
2031
        editor_widgets = QWidget(self)
1525
2032
        editor_layout = QVBoxLayout()
1533
2040
        editor_layout.addWidget(self.find_widget)
1534
2041
        
1535
2042
        splitter = QSplitter(self)
 
2043
        splitter.setContentsMargins(0, 0, 0, 0)
1536
2044
        splitter.addWidget(editor_widgets)
1537
 
        splitter.addWidget(self.classbrowser)
 
2045
        splitter.addWidget(self.outlineexplorer)
1538
2046
        splitter.setStretchFactor(0, 5)
1539
2047
        splitter.setStretchFactor(1, 1)
1540
2048
 
1541
 
        # Refreshing class browser
 
2049
        # Refreshing outline explorer
1542
2050
        for index in range(editorsplitter.editorstack.get_stack_count()):
1543
 
            editorsplitter.editorstack._refresh_classbrowser(index, update=True)
 
2051
            editorsplitter.editorstack._refresh_outlineexplorer(index, update=True)
1544
2052
        
1545
2053
    def register_editorstack(self, editorstack):
1546
2054
        self.editorstacks.append(editorstack)
1547
2055
        if DEBUG:
1548
2056
            print >>STDOUT, "EditorWidget.register_editorstack:", editorstack
1549
2057
            self.__print_editorstacks()
 
2058
        self.plugin.last_focus_editorstack[self.parent()] = editorstack
1550
2059
        editorstack.set_closable( len(self.editorstacks) > 1 )
1551
 
        editorstack.set_classbrowser(self.classbrowser)
 
2060
        editorstack.set_outlineexplorer(self.outlineexplorer)
1552
2061
        editorstack.set_find_widget(self.find_widget)
1553
2062
        self.connect(editorstack, SIGNAL('reset_statusbar()'),
1554
2063
                     self.readwrite_status.hide)
1562
2071
                     self.encoding_status.encoding_changed)
1563
2072
        self.connect(editorstack, SIGNAL('cursorPositionChanged(int,int)'),
1564
2073
                     self.cursorpos_status.cursor_position_changed)
1565
 
        self.connect(editorstack, SIGNAL('refresh_eol_mode(QString)'),
 
2074
        self.connect(editorstack, SIGNAL('refresh_eol_chars(QString)'),
1566
2075
                     self.eol_status.eol_changed)
1567
2076
        self.plugin.register_editorstack(editorstack)
1568
 
        cb_btn = create_toolbutton(self, text_beside_icon=False)
1569
 
        cb_btn.setDefaultAction(self.classbrowser.visibility_action)
1570
 
        editorstack.add_widget_to_header(cb_btn, space_before=True)
 
2077
        oe_btn = create_toolbutton(self)
 
2078
        oe_btn.setDefaultAction(self.outlineexplorer.visibility_action)
 
2079
        editorstack.add_corner_widgets_to_tabbar([5, oe_btn])
1571
2080
        
1572
2081
    def __print_editorstacks(self):
1573
2082
        print >>STDOUT, "%d editorstack(s) in editorwidget:" \
1586
2095
 
1587
2096
class EditorMainWindow(QMainWindow):
1588
2097
    def __init__(self, plugin, menu_actions, toolbar_list, menu_list,
1589
 
                 show_fullpath, fullpath_sorting, show_all_files):
1590
 
        super(EditorMainWindow, self).__init__()
 
2098
                 show_fullpath, fullpath_sorting, show_all_files,
 
2099
                 show_comments):
 
2100
        QMainWindow.__init__(self)
1591
2101
        self.setAttribute(Qt.WA_DeleteOnClose)
1592
2102
 
1593
2103
        self.window_size = None
1594
2104
        
1595
2105
        self.editorwidget = EditorWidget(self, plugin, menu_actions,
1596
 
                                         toolbar_list, menu_list,
1597
2106
                                         show_fullpath, fullpath_sorting,
1598
 
                                         show_all_files)
 
2107
                                         show_all_files, show_comments)
1599
2108
        self.setCentralWidget(self.editorwidget)
1600
2109
 
1601
2110
        # Give focus to current editor to update/show all status bar widgets
1638
2147
                
1639
2148
    def closeEvent(self, event):
1640
2149
        """Reimplement Qt method"""
1641
 
        super(EditorMainWindow, self).closeEvent(event)
 
2150
        QMainWindow.closeEvent(self, event)
1642
2151
        if PYQT_VERSION_STR.startswith('4.6'):
1643
2152
            self.emit(SIGNAL('destroyed()'))
1644
2153
            for editorstack in self.editorwidget.editorstacks[:]:
1687
2196
        self.editorwindows = []
1688
2197
 
1689
2198
        self.find_widget = FindReplace(self, enable_replace=True)
1690
 
        self.classbrowser = ClassBrowser(self, show_fullpath=False,
1691
 
                                         show_all_files=False)
 
2199
        self.outlineexplorer = OutlineExplorer(self, show_fullpath=False,
 
2200
                                               show_all_files=False)
1692
2201
 
1693
2202
        editor_widgets = QWidget(self)
1694
2203
        editor_layout = QVBoxLayout()
1698
2207
                                               first=True))
1699
2208
        editor_layout.addWidget(self.find_widget)
1700
2209
        
 
2210
        self.setContentsMargins(0, 0, 0, 0)
1701
2211
        self.addWidget(editor_widgets)
1702
 
        self.addWidget(self.classbrowser)
 
2212
        self.addWidget(self.outlineexplorer)
1703
2213
        
1704
2214
        self.setStretchFactor(0, 5)
1705
2215
        self.setStretchFactor(1, 1)
1728
2238
        self.editorstacks.append(editorstack)
1729
2239
        if self.isAncestorOf(editorstack):
1730
2240
            # editorstack is a child of the Editor plugin
 
2241
            editorstack.set_fullpath_sorting_enabled(True)
1731
2242
            editorstack.set_closable( len(self.editorstacks) > 1 )
1732
 
            editorstack.set_classbrowser(self.classbrowser)
 
2243
            editorstack.set_outlineexplorer(self.outlineexplorer)
1733
2244
            editorstack.set_find_widget(self.find_widget)
1734
 
            cb_btn = create_toolbutton(self, text_beside_icon=False)
1735
 
            cb_btn.setDefaultAction(self.classbrowser.visibility_action)
1736
 
            editorstack.add_widget_to_header(cb_btn, space_before=True)
 
2245
            oe_btn = create_toolbutton(self)
 
2246
            oe_btn.setDefaultAction(self.outlineexplorer.visibility_action)
 
2247
            editorstack.add_corner_widgets_to_tabbar([5, oe_btn])
1737
2248
            
1738
2249
        action = QAction(self)
1739
 
        editorstack.set_io_actions(action, action, action)
 
2250
        editorstack.set_io_actions(action, action, action, action)
1740
2251
        font = QFont("Courier New")
1741
2252
        font.setPointSize(10)
1742
 
        editorstack.set_default_font(font)
 
2253
        editorstack.set_default_font(font, color_scheme='Spyder')
1743
2254
        self.connect(editorstack, SIGNAL('close_file(int)'),
1744
2255
                     self.close_file_in_all_editorstacks)
1745
2256
        self.connect(editorstack, SIGNAL("create_new_window()"),
1763
2274
        window = EditorMainWindow(self, self.menu_actions,
1764
2275
                                  self.toolbar_list, self.menu_list,
1765
2276
                                  show_fullpath=False, fullpath_sorting=True,
1766
 
                                  show_all_files=False)
 
2277
                                  show_all_files=False, show_comments=True)
1767
2278
        window.resize(self.size())
1768
2279
        window.show()
1769
2280
        self.register_editorwindow(window)
1790
2301
                editorstack.blockSignals(True)
1791
2302
                editorstack.close_file(index)
1792
2303
                editorstack.blockSignals(False)
 
2304
                
 
2305
    def register_widget_shortcuts(self, context, widget):
 
2306
        """Fake!"""
 
2307
        pass
1793
2308
    
1794
2309
 
1795
2310
def test():
1800
2315
    test.load(__file__)
1801
2316
    test.load("explorer.py")
1802
2317
    test.load("dicteditor.py")
1803
 
    test.load("qscieditor/qscieditor.py")
 
2318
    test.load("codeeditor/codeeditor.py")
1804
2319
    test.show()
1805
2320
    sys.exit(app.exec_())
1806
2321