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

« back to all changes in this revision

Viewing changes to spyderlib/widgets/editor.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-2011 Pierre Raybaut
4
 
# Licensed under the terms of the MIT License
5
 
# (see spyderlib/__init__.py for details)
6
 
 
7
 
"""Editor Widget"""
8
 
 
9
 
# pylint: disable=C0103
10
 
# pylint: disable=R0903
11
 
# pylint: disable=R0911
12
 
# pylint: disable=R0201
13
 
 
14
 
from spyderlib.qt import is_pyqt46
15
 
from spyderlib.qt.QtGui import (QVBoxLayout, QMessageBox, QMenu, QFont,
16
 
                                QAction, QApplication, QWidget, QHBoxLayout,
17
 
                                QLabel, QKeySequence, QShortcut, QMainWindow,
18
 
                                QSplitter, QListWidget, QListWidgetItem,
19
 
                                QDialog, QLineEdit)
20
 
from spyderlib.qt.QtCore import (SIGNAL, Qt, QFileInfo, QThread, QObject,
21
 
                                 QByteArray, QSize, QPoint, QTimer)
22
 
from spyderlib.qt.compat import getsavefilename
23
 
 
24
 
import os
25
 
import sys
26
 
import re
27
 
import os.path as osp
28
 
 
29
 
# Local imports
30
 
from spyderlib.utils import encoding, sourcecode, programs, codeanalysis
31
 
from spyderlib.utils.dochelpers import getsignaturesfromtext
32
 
from spyderlib.utils.module_completion import moduleCompletion
33
 
from spyderlib.baseconfig import _, DEBUG, STDOUT, STDERR
34
 
from spyderlib.config import EDIT_FILTERS, EDIT_EXT
35
 
from spyderlib.guiconfig import get_icon
36
 
from spyderlib.utils.qthelpers import (create_action, add_actions,
37
 
                                       mimedata2url, get_filetype_icon,
38
 
                                       create_toolbutton)
39
 
from spyderlib.widgets.tabs import BaseTabs
40
 
from spyderlib.widgets.findreplace import FindReplace
41
 
from spyderlib.widgets.editortools import OutlineExplorerWidget
42
 
from spyderlib.widgets.status import (ReadWriteStatus, EOLStatus,
43
 
                                      EncodingStatus, CursorPositionStatus)
44
 
from spyderlib.widgets.sourcecode import syntaxhighlighters, codeeditor
45
 
from spyderlib.widgets.sourcecode.base import TextEditBaseWidget  #analysis:ignore
46
 
from spyderlib.widgets.sourcecode.codeeditor import Printer  #analysis:ignore
47
 
from spyderlib.widgets.sourcecode.codeeditor import get_file_language
48
 
 
49
 
 
50
 
class FileListDialog(QDialog):
51
 
    def __init__(self, parent, tabs, fullpath_sorting):
52
 
        QDialog.__init__(self, parent)
53
 
        
54
 
        # Destroying the C++ object right after closing the dialog box,
55
 
        # otherwise it may be garbage-collected in another QThread
56
 
        # (e.g. the editor's analysis thread in Spyder), thus leading to
57
 
        # a segmentation fault on UNIX or an application crash on Windows
58
 
        self.setAttribute(Qt.WA_DeleteOnClose)
59
 
        
60
 
        self.indexes = None
61
 
        
62
 
        self.setWindowIcon(get_icon('filelist.png'))
63
 
        self.setWindowTitle(_("File list management"))
64
 
        
65
 
        self.setModal(True)
66
 
        
67
 
        flabel = QLabel(_("Filter:"))
68
 
        self.edit = QLineEdit(self)
69
 
        self.connect(self.edit, SIGNAL("returnPressed()"), self.edit_file)
70
 
        self.connect(self.edit, SIGNAL("textChanged(QString)"),
71
 
                     lambda text: self.synchronize(0))
72
 
        fhint = QLabel(_("(press <b>Enter</b> to edit file)"))
73
 
        edit_layout = QHBoxLayout()
74
 
        edit_layout.addWidget(flabel)
75
 
        edit_layout.addWidget(self.edit)
76
 
        edit_layout.addWidget(fhint)
77
 
        
78
 
        self.listwidget = QListWidget(self)
79
 
        self.listwidget.setResizeMode(QListWidget.Adjust)
80
 
        self.connect(self.listwidget, SIGNAL("itemSelectionChanged()"),
81
 
                     self.item_selection_changed)
82
 
        self.connect(self.listwidget, SIGNAL("itemActivated(QListWidgetItem*)"),
83
 
                     self.edit_file)
84
 
        
85
 
        btn_layout = QHBoxLayout()
86
 
        edit_btn = create_toolbutton(self, icon=get_icon('edit.png'),
87
 
                     text=_("&Edit file"), autoraise=False,
88
 
                     triggered=self.edit_file, text_beside_icon=True)
89
 
        edit_btn.setMinimumHeight(28)
90
 
        btn_layout.addWidget(edit_btn)
91
 
        
92
 
        btn_layout.addStretch()
93
 
        btn_layout.addSpacing(150)
94
 
        btn_layout.addStretch()
95
 
        
96
 
        close_btn = create_toolbutton(self, text=_("&Close file"),
97
 
              icon=get_icon("fileclose.png"),
98
 
              autoraise=False, text_beside_icon=True,
99
 
              triggered=lambda: self.emit(SIGNAL("close_file(int)"),
100
 
                                  self.indexes[self.listwidget.currentRow()]))
101
 
        close_btn.setMinimumHeight(28)
102
 
        btn_layout.addWidget(close_btn)
103
 
 
104
 
        hint = QLabel(_("Hint: press <b>Alt</b> to show accelerators"))
105
 
        hint.setAlignment(Qt.AlignCenter)
106
 
        
107
 
        vlayout = QVBoxLayout()
108
 
        vlayout.addLayout(edit_layout)
109
 
        vlayout.addWidget(self.listwidget)
110
 
        vlayout.addLayout(btn_layout)
111
 
        vlayout.addWidget(hint)
112
 
        self.setLayout(vlayout)
113
 
        
114
 
        self.tabs = tabs
115
 
        self.fullpath_sorting = fullpath_sorting
116
 
        self.buttons = (edit_btn, close_btn)
117
 
        
118
 
    def edit_file(self):
119
 
        row = self.listwidget.currentRow()
120
 
        if self.listwidget.count() > 0 and row >= 0:
121
 
            self.emit(SIGNAL("edit_file(int)"), self.indexes[row])
122
 
            self.accept()
123
 
            
124
 
    def item_selection_changed(self):
125
 
        for btn in self.buttons:
126
 
            btn.setEnabled(self.listwidget.currentRow() >= 0)
127
 
        
128
 
    def synchronize(self, stack_index):
129
 
        count = self.tabs.count()
130
 
        if count == 0:
131
 
            self.accept()
132
 
            return
133
 
        self.listwidget.setTextElideMode(Qt.ElideMiddle
134
 
                                         if self.fullpath_sorting
135
 
                                         else Qt.ElideRight)
136
 
        current_row = self.listwidget.currentRow()
137
 
        if current_row >= 0:
138
 
            current_text = unicode(self.listwidget.currentItem().text())
139
 
        else:
140
 
            current_text = ""
141
 
        self.listwidget.clear()
142
 
        self.indexes = []
143
 
        new_current_index = stack_index
144
 
        filter_text = unicode(self.edit.text())
145
 
        lw_index = 0
146
 
        for index in range(count):
147
 
            text = unicode(self.tabs.tabText(index))
148
 
            if len(filter_text) == 0 or filter_text in text:
149
 
                if text == current_text:
150
 
                    new_current_index = lw_index
151
 
                lw_index += 1
152
 
                item = QListWidgetItem(self.tabs.tabIcon(index),
153
 
                                       text, self.listwidget)
154
 
                item.setSizeHint(QSize(0, 25))
155
 
                self.listwidget.addItem(item)
156
 
                self.indexes.append(index)
157
 
        if new_current_index < self.listwidget.count():
158
 
            self.listwidget.setCurrentRow(new_current_index)
159
 
        for btn in self.buttons:
160
 
            btn.setEnabled(lw_index > 0)
161
 
 
162
 
 
163
 
class AnalysisThread(QThread):
164
 
    """Analysis thread"""
165
 
    def __init__(self, parent, checker, source_code):
166
 
        super(AnalysisThread, self).__init__(parent)
167
 
        self.checker = checker
168
 
        self.results = None
169
 
        self.source_code = source_code
170
 
    
171
 
    def run(self):
172
 
        """Run analysis"""
173
 
        try:
174
 
            self.results = self.checker(self.source_code)
175
 
        except Exception:
176
 
            if DEBUG:
177
 
                import traceback
178
 
                traceback.print_exc(file=STDERR)
179
 
 
180
 
class ThreadManager(QObject):
181
 
    """Analysis thread manager"""
182
 
    def __init__(self, parent, max_simultaneous_threads=2):
183
 
        super(ThreadManager, self).__init__(parent)
184
 
        self.max_simultaneous_threads = max_simultaneous_threads
185
 
        self.started_threads = {}
186
 
        self.pending_threads = []
187
 
        self.end_callbacks = {}
188
 
        
189
 
    def close_threads(self, parent):
190
 
        """Close threads associated to parent_id"""
191
 
        if DEBUG:
192
 
            print >>STDOUT, "Call to 'close_threads'"
193
 
        if parent is None:
194
 
            # Closing all threads
195
 
            self.pending_threads = []
196
 
            threadlist = []
197
 
            for threads in self.started_threads.values():
198
 
                threadlist += threads
199
 
        else:
200
 
            parent_id = id(parent)
201
 
            self.pending_threads = [(_th, _id) for (_th, _id)
202
 
                                    in self.pending_threads
203
 
                                    if _id != parent_id]
204
 
            threadlist = self.started_threads.get(parent_id, [])
205
 
        for thread in threadlist:
206
 
            if DEBUG:
207
 
                print >>STDOUT, "Waiting for thread %r to finish" % thread
208
 
            while thread.isRunning():
209
 
                # We can't terminate thread safely, so we simply wait...
210
 
                QApplication.processEvents()
211
 
                
212
 
    def close_all_threads(self):
213
 
        """Close all threads"""
214
 
        if DEBUG:
215
 
            print >>STDOUT, "Call to 'close_all_threads'"
216
 
        self.close_threads(None)
217
 
        
218
 
    def add_thread(self, checker, end_callback, source_code, parent):
219
 
        """Add thread to queue"""
220
 
        parent_id = id(parent)
221
 
        thread = AnalysisThread(self, checker, source_code)
222
 
        self.end_callbacks[id(thread)] = end_callback
223
 
        self.pending_threads.append((thread, parent_id))
224
 
        if DEBUG:
225
 
            print >>STDOUT, "Added thread %r to queue" % thread
226
 
        QTimer.singleShot(50, self.update_queue)
227
 
    
228
 
    def update_queue(self):
229
 
        """Update queue"""
230
 
        started = 0
231
 
        for parent_id, threadlist in self.started_threads.items():
232
 
            still_running = []
233
 
            for thread in threadlist:
234
 
                if thread.isFinished():
235
 
                    end_callback = self.end_callbacks.pop(id(thread))
236
 
                    if thread.results is not None:
237
 
                        #  The thread was executed successfully
238
 
                        end_callback(thread.results)
239
 
                    thread.setParent(None)
240
 
                    thread = None
241
 
                else:
242
 
                    still_running.append(thread)
243
 
                    started += 1
244
 
            threadlist = None
245
 
            if still_running:
246
 
                self.started_threads[parent_id] = still_running
247
 
            else:
248
 
                self.started_threads.pop(parent_id)
249
 
        if DEBUG:
250
 
            print >>STDOUT, "Updating queue:"
251
 
            print >>STDOUT, "    started:", started
252
 
            print >>STDOUT, "    pending:", len(self.pending_threads)
253
 
        if self.pending_threads and started < self.max_simultaneous_threads:
254
 
            thread, parent_id = self.pending_threads.pop(0)
255
 
            self.connect(thread, SIGNAL('finished()'), self.update_queue)
256
 
            threadlist = self.started_threads.get(parent_id, [])
257
 
            self.started_threads[parent_id] = threadlist+[thread]
258
 
            if DEBUG:
259
 
                print >>STDOUT, "===>starting:", thread
260
 
            thread.start()
261
 
 
262
 
 
263
 
class FileInfo(QObject):
264
 
    """File properties"""
265
 
    def __init__(self, filename, encoding, editor, new, threadmanager):
266
 
        QObject.__init__(self)
267
 
        self.threadmanager = threadmanager
268
 
        self.filename = filename
269
 
        self.newly_created = new
270
 
        self.encoding = encoding
271
 
        self.editor = editor
272
 
        self.rope_project = codeeditor.get_rope_project()
273
 
        self.classes = (filename, None, None)
274
 
        self.analysis_results = []
275
 
        self.todo_results = []
276
 
        self.lastmodified = QFileInfo(filename).lastModified()
277
 
        
278
 
        self.connect(editor, SIGNAL('trigger_code_completion(bool)'),
279
 
                     self.trigger_code_completion)
280
 
        self.connect(editor, SIGNAL('trigger_calltip(int)'),
281
 
                     self.trigger_calltip)
282
 
        self.connect(editor, SIGNAL("go_to_definition(int)"),
283
 
                     self.go_to_definition)
284
 
        
285
 
        self.connect(editor, SIGNAL('textChanged()'),
286
 
                     self.text_changed)
287
 
        
288
 
        self.connect(editor, SIGNAL('breakpoints_changed()'),
289
 
                     self.breakpoints_changed)
290
 
        
291
 
        self.pyflakes_results = None
292
 
        self.pep8_results = None
293
 
        
294
 
    def text_changed(self):
295
 
        """Editor's text has changed"""
296
 
        self.emit(SIGNAL('text_changed_at(QString,int)'),
297
 
                  self.filename, self.editor.get_position('cursor'))
298
 
        
299
 
    def trigger_code_completion(self, automatic):
300
 
        """Trigger code completion"""
301
 
        source_code = unicode(self.editor.toPlainText())
302
 
        offset = self.editor.get_position('cursor')
303
 
        text = self.editor.get_text('sol', 'cursor')
304
 
        
305
 
        if text.startswith('import '):
306
 
            comp_list = moduleCompletion(text)
307
 
            words = text.split(' ')
308
 
            if ',' in words[-1]:
309
 
                words = words[-1].split(',')
310
 
            if comp_list:
311
 
                self.editor.show_completion_list(comp_list,
312
 
                                                 completion_text=words[-1],
313
 
                                                 automatic=automatic)
314
 
            return
315
 
        elif text.startswith('from '):
316
 
            comp_list = moduleCompletion(text)
317
 
            words = text.split(' ')
318
 
            if '(' in words[-1]:
319
 
                words = words[:-2] + words[-1].split('(')
320
 
            if ',' in words[-1]:
321
 
                words = words[:-2] + words[-1].split(',')
322
 
            self.editor.show_completion_list(comp_list,
323
 
                                             completion_text=words[-1],
324
 
                                             automatic=automatic)
325
 
            return
326
 
        else:
327
 
            textlist = self.rope_project.get_completion_list(source_code,
328
 
                                                             offset,
329
 
                                                             self.filename)
330
 
            if textlist:
331
 
                completion_text = re.split(r"[^a-zA-Z0-9_]", text)[-1]
332
 
                if text.lstrip().startswith('#') and text.endswith('.'):
333
 
                    return
334
 
                else:
335
 
                    self.editor.show_completion_list(textlist, completion_text,
336
 
                                                     automatic)
337
 
                return
338
 
        
339
 
    def trigger_calltip(self, position, auto=True):
340
 
        """Trigger calltip"""
341
 
        # auto is True means that trigger_calltip was called automatically,
342
 
        # i.e. the user has just entered an opening parenthesis -- in that 
343
 
        # case, we don't want to force the object inspector to be visible, 
344
 
        # to avoid polluting the window layout
345
 
        source_code = unicode(self.editor.toPlainText())
346
 
        offset = position
347
 
        
348
 
        textlist = self.rope_project.get_calltip_text(source_code, offset,
349
 
                                                      self.filename)
350
 
        if not textlist:
351
 
            return
352
 
        obj_fullname = ''
353
 
        signatures = []
354
 
        cts, doc_text = textlist
355
 
        cts = cts.replace('.__init__', '')
356
 
        parpos = cts.find('(')
357
 
        if parpos:
358
 
            obj_fullname = cts[:parpos]
359
 
            obj_name = obj_fullname.split('.')[-1]
360
 
            cts = cts.replace(obj_fullname, obj_name)
361
 
            signatures = [cts]
362
 
            if '()' in cts:
363
 
                # Either inspected object has no argument, or it's 
364
 
                # a builtin or an extension -- in this last case 
365
 
                # the following attempt may succeed:
366
 
                signatures = getsignaturesfromtext(doc_text, obj_name)
367
 
        if not obj_fullname:
368
 
            obj_fullname = codeeditor.get_primary_at(source_code, offset)
369
 
        if obj_fullname and not obj_fullname.startswith('self.') and doc_text:
370
 
            if signatures:
371
 
                argspec_st = signatures[0].find('(')
372
 
                argspec = signatures[0][argspec_st:]
373
 
                module_end = obj_fullname.rfind('.')
374
 
                module = obj_fullname[:module_end]
375
 
                note = 'Present in %s module' % module
376
 
            else:
377
 
                argspec = ''
378
 
                note = ''
379
 
            self.emit(SIGNAL(
380
 
                    "send_to_inspector(QString,QString,QString,QString,bool)"),
381
 
                    obj_fullname, argspec, note, doc_text, not auto)
382
 
        if signatures:
383
 
            signatures = ['<b>'+s.replace('(', '(</b>'
384
 
                                          ).replace(')', '<b>)</b>')
385
 
                          for s in signatures]
386
 
            self.editor.show_calltip(obj_fullname, '<br>'.join(signatures),
387
 
                                     at_position=position)
388
 
                    
389
 
    def go_to_definition(self, position):
390
 
        """Go to definition"""
391
 
        source_code = unicode(self.editor.toPlainText())
392
 
        offset = position
393
 
        fname, lineno = self.rope_project.get_definition_location(source_code,
394
 
                                                    offset, self.filename)
395
 
        if fname is not None and lineno is not None:
396
 
            self.emit(SIGNAL("edit_goto(QString,int,QString)"),
397
 
                      fname, lineno, "")
398
 
    
399
 
    def get_source_code(self):
400
 
        """Return associated editor source code"""
401
 
        return unicode(self.editor.toPlainText()).encode('utf-8')
402
 
    
403
 
    def run_code_analysis(self, run_pyflakes, run_pep8):
404
 
        """Run code analysis"""
405
 
        run_pyflakes = run_pyflakes and codeanalysis.is_pyflakes_installed()
406
 
        run_pep8 = run_pep8 and\
407
 
                   codeanalysis.get_checker_executable('pep8') is not None
408
 
        self.pyflakes_results = []
409
 
        self.pep8_results = []
410
 
        if self.editor.is_python():
411
 
            source_code = self.get_source_code()
412
 
            if run_pyflakes:
413
 
                self.pyflakes_results = None
414
 
            if run_pep8:
415
 
                self.pep8_results = None
416
 
            if run_pyflakes:
417
 
                self.threadmanager.add_thread(codeanalysis.check_with_pyflakes,
418
 
                                              self.pyflakes_analysis_finished,
419
 
                                              source_code, self)
420
 
            if run_pep8:
421
 
                self.threadmanager.add_thread(codeanalysis.check_with_pep8,
422
 
                                              self.pep8_analysis_finished,
423
 
                                              source_code, self)
424
 
        
425
 
    def pyflakes_analysis_finished(self, results):
426
 
        """Pyflakes code analysis thread has finished"""
427
 
        self.pyflakes_results = results
428
 
        if self.pep8_results is not None:
429
 
            self.code_analysis_finished()
430
 
        
431
 
    def pep8_analysis_finished(self, results):
432
 
        """Pep8 code analysis thread has finished"""
433
 
        self.pep8_results = results
434
 
        if self.pyflakes_results is not None:
435
 
            self.code_analysis_finished()
436
 
        
437
 
    def code_analysis_finished(self):
438
 
        """Code analysis thread has finished"""
439
 
        self.set_analysis_results(self.pyflakes_results+self.pep8_results)
440
 
        self.emit(SIGNAL('analysis_results_changed()'))
441
 
        
442
 
    def set_analysis_results(self, results):
443
 
        """Set analysis results and update warning markers in editor"""
444
 
        self.analysis_results = results
445
 
        self.editor.process_code_analysis(results)
446
 
        
447
 
    def cleanup_analysis_results(self):
448
 
        """Clean-up analysis results"""
449
 
        self.analysis_results = []
450
 
        self.editor.cleanup_code_analysis()
451
 
            
452
 
    def run_todo_finder(self):
453
 
        """Run TODO finder"""
454
 
        if self.editor.is_python():
455
 
            self.threadmanager.add_thread(codeanalysis.find_tasks,
456
 
                                          self.todo_finished,
457
 
                                          self.get_source_code(), self)
458
 
        
459
 
    def todo_finished(self, results):
460
 
        """Code analysis thread has finished"""
461
 
        self.set_todo_results(results)
462
 
        self.emit(SIGNAL('todo_results_changed()'))
463
 
        
464
 
    def set_todo_results(self, results):
465
 
        """Set TODO results and update markers in editor"""
466
 
        self.todo_results = results
467
 
        self.editor.process_todo(results)
468
 
        
469
 
    def cleanup_todo_results(self):
470
 
        """Clean-up TODO finder results"""
471
 
        self.todo_results = []
472
 
        
473
 
    def breakpoints_changed(self):
474
 
        """Breakpoint list has changed"""
475
 
        breakpoints = self.editor.get_breakpoints()
476
 
        if self.editor.breakpoints != breakpoints:
477
 
            self.editor.breakpoints = breakpoints
478
 
            self.emit(SIGNAL("save_breakpoints(QString,QString)"),
479
 
                      self.filename, repr(breakpoints))
480
 
        
481
 
 
482
 
class EditorStack(QWidget):
483
 
    def __init__(self, parent, actions):
484
 
        QWidget.__init__(self, parent)
485
 
        
486
 
        self.setAttribute(Qt.WA_DeleteOnClose)
487
 
        
488
 
        self.threadmanager = ThreadManager(self)
489
 
        
490
 
        self.newwindow_action = None
491
 
        self.horsplit_action = None
492
 
        self.versplit_action = None
493
 
        self.close_action = None
494
 
        self.__get_split_actions()
495
 
        
496
 
        layout = QVBoxLayout()
497
 
        layout.setContentsMargins(0, 0, 0, 0)
498
 
        self.setLayout(layout)
499
 
 
500
 
        self.menu = None
501
 
        self.filelist_dlg = None
502
 
#        self.filelist_btn = None
503
 
#        self.previous_btn = None
504
 
#        self.next_btn = None
505
 
        self.tabs = None
506
 
 
507
 
        self.stack_history = []
508
 
        
509
 
        self.setup_editorstack(parent, layout)
510
 
 
511
 
        self.find_widget = None
512
 
 
513
 
        self.data = []
514
 
        
515
 
        filelist_action = create_action(self, _("File list management"),
516
 
                                 icon=get_icon('filelist.png'),
517
 
                                 triggered=self.open_filelistdialog)
518
 
        copy_to_cb_action = create_action(self, _("Copy path to clipboard"),
519
 
                icon="editcopy.png",
520
 
                triggered=lambda:
521
 
                QApplication.clipboard().setText(self.get_current_filename()))
522
 
        self.menu_actions = actions+[None, filelist_action, copy_to_cb_action]
523
 
        self.outlineexplorer = None
524
 
        self.inspector = None
525
 
        self.unregister_callback = None
526
 
        self.is_closable = False
527
 
        self.new_action = None
528
 
        self.open_action = None
529
 
        self.save_action = None
530
 
        self.revert_action = None
531
 
        self.tempfile_path = None
532
 
        self.title = _("Editor")
533
 
        self.pyflakes_enabled = True
534
 
        self.pep8_enabled = False
535
 
        self.todolist_enabled = True
536
 
        self.realtime_analysis_enabled = False
537
 
        self.is_analysis_done = False
538
 
        self.linenumbers_enabled = True
539
 
        self.edgeline_enabled = True
540
 
        self.edgeline_column = 79
541
 
        self.outlineexplorer_enabled = True
542
 
        self.codecompletion_auto_enabled = True
543
 
        self.codecompletion_case_enabled = False
544
 
        self.codecompletion_single_enabled = False
545
 
        self.codecompletion_enter_enabled = False
546
 
        self.calltips_enabled = True
547
 
        self.go_to_definition_enabled = True
548
 
        self.close_parentheses_enabled = True
549
 
        self.close_quotes_enabled = True
550
 
        self.add_colons_enabled = True
551
 
        self.auto_unindent_enabled = True
552
 
        self.indent_chars = " "*4
553
 
        self.tab_stop_width = 40
554
 
        self.inspector_enabled = False
555
 
        self.default_font = None
556
 
        self.wrap_enabled = False
557
 
        self.tabmode_enabled = False
558
 
        self.intelligent_backspace_enabled = True
559
 
        self.highlight_current_line_enabled = False
560
 
        self.occurence_highlighting_enabled = True
561
 
        self.checkeolchars_enabled = True
562
 
        self.always_remove_trailing_spaces = False
563
 
        self.fullpath_sorting_enabled = None
564
 
        self.set_fullpath_sorting_enabled(False)
565
 
        ccs = 'Spyder'
566
 
        if ccs not in syntaxhighlighters.COLOR_SCHEME_NAMES:
567
 
            ccs = syntaxhighlighters.COLOR_SCHEME_NAMES[0]
568
 
        self.color_scheme = ccs
569
 
        
570
 
        self.__file_status_flag = False
571
 
        
572
 
        # Real-time code analysis
573
 
        self.analysis_timer = QTimer(self)
574
 
        self.analysis_timer.setSingleShot(True)
575
 
        self.analysis_timer.setInterval(2000)
576
 
        self.connect(self.analysis_timer, SIGNAL("timeout()"), 
577
 
                     self.analyze_script)
578
 
        
579
 
        # Accepting drops
580
 
        self.setAcceptDrops(True)
581
 
 
582
 
        # Local shortcuts
583
 
        self.inspectsc = QShortcut(QKeySequence("Ctrl+I"), self,
584
 
                                   self.inspect_current_object)
585
 
        self.inspectsc.setContext(Qt.WidgetWithChildrenShortcut)
586
 
        self.breakpointsc = QShortcut(QKeySequence("F12"), self,
587
 
                                      self.set_or_clear_breakpoint)
588
 
        self.breakpointsc.setContext(Qt.WidgetWithChildrenShortcut)
589
 
        self.cbreakpointsc = QShortcut(QKeySequence("Shift+F12"), self,
590
 
                                       self.set_or_edit_conditional_breakpoint)
591
 
        self.cbreakpointsc.setContext(Qt.WidgetWithChildrenShortcut)
592
 
        self.gotolinesc = QShortcut(QKeySequence("Ctrl+L"), self,
593
 
                                    self.go_to_line)
594
 
        self.gotolinesc.setContext(Qt.WidgetWithChildrenShortcut)
595
 
        self.filelistsc = QShortcut(QKeySequence("Ctrl+E"), self,
596
 
                                    self.open_filelistdialog)
597
 
        self.filelistsc.setContext(Qt.WidgetWithChildrenShortcut)
598
 
        self.tabsc = QShortcut(QKeySequence("Ctrl+Tab"), self,
599
 
                               self.go_to_previous_file)
600
 
        self.tabsc.setContext(Qt.WidgetWithChildrenShortcut)
601
 
        self.closesc = QShortcut(QKeySequence("Ctrl+F4"), self,
602
 
                                 self.close_file)
603
 
        self.closesc.setContext(Qt.WidgetWithChildrenShortcut)
604
 
        self.tabshiftsc = QShortcut(QKeySequence("Ctrl+Shift+Tab"), self,
605
 
                                    self.go_to_next_file)
606
 
        self.tabshiftsc.setContext(Qt.WidgetWithChildrenShortcut)
607
 
        
608
 
    def get_shortcut_data(self):
609
 
        """
610
 
        Returns shortcut data, a list of tuples (shortcut, text, default)
611
 
        shortcut (QShortcut or QAction instance)
612
 
        text (string): action/shortcut description
613
 
        default (string): default key sequence
614
 
        """
615
 
        return [
616
 
                (self.inspectsc, "Inspect current object", "Ctrl+I"),
617
 
                (self.breakpointsc, "Breakpoint", "F12"),
618
 
                (self.cbreakpointsc, "Conditional breakpoint", "Shift+F12"),
619
 
                (self.gotolinesc, "Go to line", "Ctrl+L"),
620
 
                (self.filelistsc, "File list management", "Ctrl+E"),
621
 
                ]
622
 
        
623
 
    def setup_editorstack(self, parent, layout):
624
 
        """Setup editorstack's layout"""
625
 
        menu_btn = create_toolbutton(self, icon=get_icon("tooloptions.png"),
626
 
                                     tip=_("Options"))
627
 
        self.menu = QMenu(self)
628
 
        menu_btn.setMenu(self.menu)
629
 
        menu_btn.setPopupMode(menu_btn.InstantPopup)
630
 
        self.connect(self.menu, SIGNAL("aboutToShow()"), self.__setup_menu)
631
 
 
632
 
#        self.filelist_btn = create_toolbutton(self,
633
 
#                             icon=get_icon('filelist.png'),
634
 
#                             tip=_("File list management"),
635
 
#                             triggered=self.open_filelistdialog)
636
 
#        
637
 
#        self.previous_btn = create_toolbutton(self,
638
 
#                             icon=get_icon('previous.png'),
639
 
#                             tip=_("Previous file"),
640
 
#                             triggered=self.go_to_previous_file)
641
 
#        
642
 
#        self.next_btn = create_toolbutton(self,
643
 
#                             icon=get_icon('next.png'),
644
 
#                             tip=_("Next file"),
645
 
#                             triggered=self.go_to_next_file)
646
 
                
647
 
        # Optional tabs
648
 
#        corner_widgets = {Qt.TopRightCorner: [self.previous_btn,
649
 
#                                              self.filelist_btn, self.next_btn,
650
 
#                                              5, menu_btn]}
651
 
        corner_widgets = {Qt.TopRightCorner: [menu_btn]}
652
 
        self.tabs = BaseTabs(self, menu=self.menu, menu_use_tooltips=True,
653
 
                             corner_widgets=corner_widgets)
654
 
        self.tabs.set_close_function(self.close_file)
655
 
        if hasattr(self.tabs, 'setDocumentMode') \
656
 
           and not sys.platform == 'darwin':
657
 
            # Don't set document mode to true on OSX because it generates
658
 
            # a crash when the editor is detached from the main window
659
 
            # Fixes Issue 561
660
 
            self.tabs.setDocumentMode(True)
661
 
        self.connect(self.tabs, SIGNAL('currentChanged(int)'),
662
 
                     self.current_changed)
663
 
        layout.addWidget(self.tabs)
664
 
        
665
 
    def add_corner_widgets_to_tabbar(self, widgets):
666
 
        self.tabs.add_corner_widgets(widgets)
667
 
        
668
 
    def closeEvent(self, event):
669
 
        self.threadmanager.close_all_threads()
670
 
        self.disconnect(self.analysis_timer, SIGNAL("timeout()"),
671
 
                        self.analyze_script)
672
 
        QWidget.closeEvent(self, event)
673
 
        if is_pyqt46:
674
 
            self.emit(SIGNAL('destroyed()'))        
675
 
    
676
 
    def clone_editor_from(self, other_finfo, set_current):
677
 
        fname = other_finfo.filename
678
 
        enc = other_finfo.encoding
679
 
        new = other_finfo.newly_created
680
 
        finfo = self.create_new_editor(fname, enc, "",
681
 
                                       set_current=set_current, new=new,
682
 
                                       cloned_from=other_finfo.editor)
683
 
        finfo.set_analysis_results(other_finfo.analysis_results)
684
 
        finfo.set_todo_results(other_finfo.todo_results)
685
 
        return finfo.editor
686
 
            
687
 
    def clone_from(self, other):
688
 
        """Clone EditorStack from other instance"""
689
 
        for other_finfo in other.data:
690
 
            self.clone_editor_from(other_finfo, set_current=True)
691
 
        self.set_stack_index(other.get_stack_index())
692
 
        
693
 
    def open_filelistdialog(self):
694
 
        """Open file list management dialog box"""
695
 
        self.filelist_dlg = dlg = FileListDialog(self, self.tabs,
696
 
                                                 self.fullpath_sorting_enabled)
697
 
        self.connect(dlg, SIGNAL("edit_file(int)"), self.set_stack_index)
698
 
        self.connect(dlg, SIGNAL("close_file(int)"), self.close_file)
699
 
        dlg.synchronize(self.get_stack_index())
700
 
        dlg.exec_()
701
 
        self.filelist_dlg = None
702
 
        
703
 
    def update_filelistdialog(self):
704
 
        """Synchronize file list dialog box with editor widget tabs"""
705
 
        if self.filelist_dlg is not None:
706
 
            self.filelist_dlg.synchronize(self.get_stack_index())
707
 
            
708
 
    def go_to_line(self):
709
 
        """Go to line dialog"""
710
 
        if self.data:
711
 
            self.get_current_editor().exec_gotolinedialog()
712
 
                
713
 
    def set_or_clear_breakpoint(self):
714
 
        """Set/clear breakpoint"""
715
 
        if self.data:
716
 
            editor = self.get_current_editor()
717
 
            editor.add_remove_breakpoint()
718
 
            
719
 
    def set_or_edit_conditional_breakpoint(self):
720
 
        """Set conditional breakpoint"""
721
 
        if self.data:
722
 
            editor = self.get_current_editor()
723
 
            editor.add_remove_breakpoint(edit_condition=True)
724
 
            
725
 
    def inspect_current_object(self):
726
 
        """Inspect current object in Object Inspector"""
727
 
        if programs.is_module_installed('rope'):
728
 
            editor = self.get_current_editor()
729
 
            position = editor.get_position('cursor')
730
 
            finfo = self.get_current_finfo()
731
 
            finfo.trigger_calltip(position, auto=False)
732
 
        else:
733
 
            text = self.get_current_editor().get_current_object()
734
 
            if text:
735
 
                self.send_to_inspector(text, force=True)
736
 
        
737
 
        
738
 
    #------ Editor Widget Settings
739
 
    def set_closable(self, state):
740
 
        """Parent widget must handle the closable state"""
741
 
        self.is_closable = state
742
 
        
743
 
    def set_io_actions(self, new_action, open_action,
744
 
                       save_action, revert_action):
745
 
        self.new_action = new_action
746
 
        self.open_action = open_action
747
 
        self.save_action = save_action
748
 
        self.revert_action = revert_action
749
 
        
750
 
    def set_find_widget(self, find_widget):
751
 
        self.find_widget = find_widget
752
 
        
753
 
    def set_outlineexplorer(self, outlineexplorer):
754
 
        self.outlineexplorer = outlineexplorer
755
 
        self.outlineexplorer_enabled = True
756
 
        self.connect(self.outlineexplorer,
757
 
                     SIGNAL("outlineexplorer_is_visible()"),
758
 
                     self._refresh_outlineexplorer)
759
 
        
760
 
    def add_outlineexplorer_button(self, editor_plugin):
761
 
        oe_btn = create_toolbutton(editor_plugin)
762
 
        oe_btn.setDefaultAction(self.outlineexplorer.visibility_action)
763
 
        self.add_corner_widgets_to_tabbar([5, oe_btn])
764
 
        
765
 
    def set_inspector(self, inspector):
766
 
        self.inspector = inspector
767
 
        
768
 
    def set_tempfile_path(self, path):
769
 
        self.tempfile_path = path
770
 
        
771
 
    def set_title(self, text):
772
 
        self.title = text
773
 
        
774
 
    def __update_editor_margins(self, editor):
775
 
        editor.setup_margins(linenumbers=self.linenumbers_enabled,
776
 
                             markers=self.has_markers())
777
 
        
778
 
    def __codeanalysis_settings_changed(self, current_finfo):
779
 
        if self.data:
780
 
            run_pyflakes, run_pep8 = self.pyflakes_enabled, self.pep8_enabled
781
 
            for finfo in self.data:
782
 
                self.__update_editor_margins(finfo.editor)
783
 
                finfo.cleanup_analysis_results()
784
 
                if (run_pyflakes or run_pep8) and current_finfo is not None:
785
 
                    if current_finfo is not finfo:
786
 
                        finfo.run_code_analysis(run_pyflakes, run_pep8)
787
 
        
788
 
    def set_pyflakes_enabled(self, state, current_finfo=None):
789
 
        # CONF.get(self.CONF_SECTION, 'code_analysis/pyflakes')
790
 
        self.pyflakes_enabled = state
791
 
        self.__codeanalysis_settings_changed(current_finfo)
792
 
        
793
 
    def set_pep8_enabled(self, state, current_finfo=None):
794
 
        # CONF.get(self.CONF_SECTION, 'code_analysis/pep8')
795
 
        self.pep8_enabled = state
796
 
        self.__codeanalysis_settings_changed(current_finfo)
797
 
    
798
 
    def has_markers(self):
799
 
        """Return True if this editorstack has a marker margin for TODOs or
800
 
        code analysis"""
801
 
        return self.todolist_enabled or self.pyflakes_enabled\
802
 
               or self.pep8_enabled
803
 
    
804
 
    def set_todolist_enabled(self, state, current_finfo=None):
805
 
        # CONF.get(self.CONF_SECTION, 'todo_list')
806
 
        self.todolist_enabled = state
807
 
        if self.data:
808
 
            for finfo in self.data:
809
 
                self.__update_editor_margins(finfo.editor)
810
 
                finfo.cleanup_todo_results()
811
 
                if state and current_finfo is not None:
812
 
                    if current_finfo is not finfo:
813
 
                        finfo.run_todo_finder()
814
 
                        
815
 
    def set_realtime_analysis_enabled(self, state):
816
 
        self.realtime_analysis_enabled = state
817
 
 
818
 
    def set_realtime_analysis_timeout(self, timeout):
819
 
        self.analysis_timer.setInterval(timeout)
820
 
    
821
 
    def set_linenumbers_enabled(self, state, current_finfo=None):
822
 
        # CONF.get(self.CONF_SECTION, 'line_numbers')
823
 
        self.linenumbers_enabled = state
824
 
        if self.data:
825
 
            for finfo in self.data:
826
 
                self.__update_editor_margins(finfo.editor)
827
 
        
828
 
    def set_edgeline_enabled(self, state):
829
 
        # CONF.get(self.CONF_SECTION, 'edge_line')
830
 
        self.edgeline_enabled = state
831
 
        if self.data:
832
 
            for finfo in self.data:
833
 
                finfo.editor.set_edge_line_enabled(state)
834
 
        
835
 
    def set_edgeline_column(self, column):
836
 
        # CONF.get(self.CONF_SECTION, 'edge_line_column')
837
 
        self.edgeline_column = column
838
 
        if self.data:
839
 
            for finfo in self.data:
840
 
                finfo.editor.set_edge_line_column(column)
841
 
        
842
 
    def set_codecompletion_auto_enabled(self, state):
843
 
        # CONF.get(self.CONF_SECTION, 'codecompletion_auto')
844
 
        self.codecompletion_auto_enabled = state
845
 
        if self.data:
846
 
            for finfo in self.data:
847
 
                finfo.editor.set_codecompletion_auto(state)
848
 
                
849
 
    def set_codecompletion_case_enabled(self, state):
850
 
        self.codecompletion_case_enabled = state
851
 
        if self.data:
852
 
            for finfo in self.data:
853
 
                finfo.editor.set_codecompletion_case(state)
854
 
                
855
 
    def set_codecompletion_single_enabled(self, state):
856
 
        self.codecompletion_single_enabled = state
857
 
        if self.data:
858
 
            for finfo in self.data:
859
 
                finfo.editor.set_codecompletion_single(state)
860
 
                    
861
 
    def set_codecompletion_enter_enabled(self, state):
862
 
        self.codecompletion_enter_enabled = state
863
 
        if self.data:
864
 
            for finfo in self.data:
865
 
                finfo.editor.set_codecompletion_enter(state)
866
 
                
867
 
    def set_calltips_enabled(self, state):
868
 
        # CONF.get(self.CONF_SECTION, 'calltips')
869
 
        self.calltips_enabled = state
870
 
        if self.data:
871
 
            for finfo in self.data:
872
 
                finfo.editor.set_calltips(state)
873
 
                
874
 
    def set_go_to_definition_enabled(self, state):
875
 
        # CONF.get(self.CONF_SECTION, 'go_to_definition')
876
 
        self.go_to_definition_enabled = state
877
 
        if self.data:
878
 
            for finfo in self.data:
879
 
                finfo.editor.set_go_to_definition_enabled(state)
880
 
                
881
 
    def set_close_parentheses_enabled(self, state):
882
 
        # CONF.get(self.CONF_SECTION, 'close_parentheses')
883
 
        self.close_parentheses_enabled = state
884
 
        if self.data:
885
 
            for finfo in self.data:
886
 
                finfo.editor.set_close_parentheses_enabled(state)
887
 
                
888
 
    def set_close_quotes_enabled(self, state):
889
 
        # CONF.get(self.CONF_SECTION, 'close_quotes')
890
 
        self.close_quotes_enabled = state
891
 
        if self.data:
892
 
            for finfo in self.data:
893
 
                finfo.editor.set_close_quotes_enabled(state)
894
 
    
895
 
    def set_add_colons_enabled(self, state):
896
 
        # CONF.get(self.CONF_SECTION, 'add_colons')
897
 
        self.add_colons_enabled = state
898
 
        if self.data:
899
 
            for finfo in self.data:
900
 
                finfo.editor.set_add_colons_enabled(state)
901
 
    
902
 
    def set_auto_unindent_enabled(self, state):
903
 
        # CONF.get(self.CONF_SECTION, 'auto_unindent')
904
 
        self.auto_unindent_enabled = state
905
 
        if self.data:
906
 
            for finfo in self.data:
907
 
                finfo.editor.set_auto_unindent_enabled(state)
908
 
                
909
 
    def set_indent_chars(self, indent_chars):
910
 
        # CONF.get(self.CONF_SECTION, 'indent_chars')
911
 
        indent_chars = indent_chars[1:-1] # removing the leading/ending '*'
912
 
        self.indent_chars = indent_chars
913
 
        if self.data:
914
 
            for finfo in self.data:
915
 
                finfo.editor.set_indent_chars(indent_chars)
916
 
                
917
 
    def set_tab_stop_width(self, tab_stop_width):
918
 
        # CONF.get(self.CONF_SECTION, 'tab_stop_width')
919
 
        self.tab_stop_width = tab_stop_width
920
 
        if self.data:
921
 
            for finfo in self.data:
922
 
                finfo.editor.setTabStopWidth(tab_stop_width)
923
 
                
924
 
    def set_inspector_enabled(self, state):
925
 
        self.inspector_enabled = state
926
 
        
927
 
    def set_outlineexplorer_enabled(self, state):
928
 
        # CONF.get(self.CONF_SECTION, 'outline_explorer')
929
 
        self.outlineexplorer_enabled = state
930
 
        
931
 
    def set_default_font(self, font, color_scheme=None):
932
 
        # get_font(self.CONF_SECTION)
933
 
        self.default_font = font
934
 
        if color_scheme is not None:
935
 
            self.color_scheme = color_scheme
936
 
        if self.data:
937
 
            for finfo in self.data:
938
 
                finfo.editor.set_font(font, color_scheme)
939
 
            
940
 
    def set_color_scheme(self, color_scheme):
941
 
        self.color_scheme = color_scheme
942
 
        if self.data:
943
 
            for finfo in self.data:
944
 
                finfo.editor.set_color_scheme(color_scheme)
945
 
        
946
 
    def set_wrap_enabled(self, state):
947
 
        # CONF.get(self.CONF_SECTION, 'wrap')
948
 
        self.wrap_enabled = state
949
 
        if self.data:
950
 
            for finfo in self.data:
951
 
                finfo.editor.toggle_wrap_mode(state)
952
 
        
953
 
    def set_tabmode_enabled(self, state):
954
 
        # CONF.get(self.CONF_SECTION, 'tab_always_indent')
955
 
        self.tabmode_enabled = state
956
 
        if self.data:
957
 
            for finfo in self.data:
958
 
                finfo.editor.set_tab_mode(state)
959
 
                
960
 
    def set_intelligent_backspace_enabled(self, state):
961
 
        # CONF.get(self.CONF_SECTION, 'intelligent_backspace')
962
 
        self.intelligent_backspace_enabled = state
963
 
        if self.data:
964
 
            for finfo in self.data:
965
 
                finfo.editor.toggle_intelligent_backspace(state)
966
 
        
967
 
    def set_occurence_highlighting_enabled(self, state):
968
 
        # CONF.get(self.CONF_SECTION, 'occurence_highlighting')
969
 
        self.occurence_highlighting_enabled = state
970
 
        if self.data:
971
 
            for finfo in self.data:
972
 
                finfo.editor.set_occurence_highlighting(state)
973
 
                
974
 
    def set_occurence_highlighting_timeout(self, timeout):
975
 
        # CONF.get(self.CONF_SECTION, 'occurence_highlighting/timeout')
976
 
        self.occurence_highlighting_timeout = timeout
977
 
        if self.data:
978
 
            for finfo in self.data:
979
 
                finfo.editor.set_occurence_timeout(timeout)
980
 
                
981
 
    def set_highlight_current_line_enabled(self, state):
982
 
        self.highlight_current_line_enabled = state
983
 
        if self.data:
984
 
            for finfo in self.data:
985
 
                finfo.editor.set_highlight_current_line(state)
986
 
        
987
 
    def set_checkeolchars_enabled(self, state):
988
 
        # CONF.get(self.CONF_SECTION, 'check_eol_chars')
989
 
        self.checkeolchars_enabled = state
990
 
        
991
 
    def set_fullpath_sorting_enabled(self, state):
992
 
        # CONF.get(self.CONF_SECTION, 'fullpath_sorting')
993
 
        self.fullpath_sorting_enabled = state
994
 
        if self.data:
995
 
            finfo = self.data[self.get_stack_index()]
996
 
            self.data.sort(key=self.__get_sorting_func())
997
 
            new_index = self.data.index(finfo)
998
 
            self.__repopulate_stack()
999
 
            self.set_stack_index(new_index)
1000
 
        
1001
 
    def set_always_remove_trailing_spaces(self, state):
1002
 
        # CONF.get(self.CONF_SECTION, 'always_remove_trailing_spaces')
1003
 
        self.always_remove_trailing_spaces = state
1004
 
            
1005
 
    
1006
 
    #------ Stacked widget management
1007
 
    def get_stack_index(self):
1008
 
        return self.tabs.currentIndex()
1009
 
    
1010
 
    def get_current_finfo(self):
1011
 
        if self.data:
1012
 
            return self.data[self.get_stack_index()]
1013
 
    
1014
 
    def get_current_editor(self):
1015
 
        return self.tabs.currentWidget()
1016
 
    
1017
 
    def get_stack_count(self):
1018
 
        return self.tabs.count()
1019
 
    
1020
 
    def set_stack_index(self, index):
1021
 
        self.tabs.setCurrentIndex(index)
1022
 
            
1023
 
    def set_tabbar_visible(self, state):
1024
 
        self.tabs.tabBar().setVisible(state)
1025
 
    
1026
 
    def remove_from_data(self, index):
1027
 
        self.tabs.blockSignals(True)
1028
 
        self.tabs.removeTab(index)
1029
 
        self.data.pop(index)
1030
 
        self.tabs.blockSignals(False)
1031
 
        self.update_actions()
1032
 
        self.update_filelistdialog()
1033
 
    
1034
 
    def __modified_readonly_title(self, title, is_modified, is_readonly):
1035
 
        if is_modified is not None and is_modified:
1036
 
            title += "*"
1037
 
        if is_readonly is not None and is_readonly:
1038
 
            title = "(%s)" % title
1039
 
        return title
1040
 
    
1041
 
    def get_tab_text(self, filename, is_modified=None, is_readonly=None):
1042
 
        """Return tab title"""
1043
 
        return self.__modified_readonly_title(osp.basename(filename),
1044
 
                                              is_modified, is_readonly)
1045
 
                
1046
 
    def get_tab_tip(self, filename, is_modified=None, is_readonly=None):
1047
 
        """Return tab menu title"""
1048
 
        if self.fullpath_sorting_enabled:
1049
 
            text = filename
1050
 
        else:
1051
 
            text = u"%s — %s"
1052
 
        text = self.__modified_readonly_title(text,
1053
 
                                              is_modified, is_readonly)
1054
 
        if self.tempfile_path is not None\
1055
 
           and filename == encoding.to_unicode_from_fs(self.tempfile_path):
1056
 
            temp_file_str = unicode(_("Temporary file"))
1057
 
            if self.fullpath_sorting_enabled:
1058
 
                return "%s (%s)" % (text, temp_file_str)
1059
 
            else:
1060
 
                return text % (temp_file_str, self.tempfile_path)
1061
 
        else:
1062
 
            if self.fullpath_sorting_enabled:
1063
 
                return text
1064
 
            else:
1065
 
                return text % (osp.basename(filename), osp.dirname(filename))
1066
 
        
1067
 
    def __get_sorting_func(self):
1068
 
        if self.fullpath_sorting_enabled:
1069
 
            return lambda item: osp.join(osp.dirname(item.filename),
1070
 
                                         '_'+osp.basename(item.filename))
1071
 
        else:
1072
 
            return lambda item: osp.basename(item.filename)
1073
 
    
1074
 
    def add_to_data(self, finfo, set_current):
1075
 
        self.data.append(finfo)
1076
 
        self.data.sort(key=self.__get_sorting_func())
1077
 
        index = self.data.index(finfo)
1078
 
        fname, editor = finfo.filename, finfo.editor
1079
 
        self.tabs.insertTab(index, editor, get_filetype_icon(fname),
1080
 
                            self.get_tab_text(fname))
1081
 
        self.set_stack_title(index, False)
1082
 
        if set_current:
1083
 
            self.set_stack_index(index)
1084
 
            self.current_changed(index)
1085
 
        self.update_actions()
1086
 
        self.update_filelistdialog()
1087
 
        
1088
 
    def __repopulate_stack(self):
1089
 
        self.tabs.blockSignals(True)
1090
 
        self.tabs.clear()
1091
 
        for finfo in self.data:
1092
 
            icon = get_filetype_icon(finfo.filename)
1093
 
            tab_text = self.get_tab_text(finfo.filename)
1094
 
            tab_tip = self.get_tab_tip(finfo.filename)
1095
 
            index = self.tabs.addTab(finfo.editor, icon, tab_text)
1096
 
            self.tabs.setTabToolTip(index, tab_tip)
1097
 
        self.tabs.blockSignals(False)
1098
 
        self.update_filelistdialog()
1099
 
        
1100
 
    def rename_in_data(self, index, new_filename):
1101
 
        finfo = self.data[index]
1102
 
        if osp.splitext(finfo.filename)[1] != osp.splitext(new_filename)[1]:
1103
 
            # File type has changed!
1104
 
            txt = unicode(finfo.editor.get_text_with_eol())
1105
 
            language = get_file_language(new_filename, txt)
1106
 
            finfo.editor.set_language(language)
1107
 
        set_new_index = index == self.get_stack_index()
1108
 
        finfo.filename = new_filename
1109
 
        self.data.sort(key=self.__get_sorting_func())
1110
 
        new_index = self.data.index(finfo)
1111
 
        self.__repopulate_stack()
1112
 
        if set_new_index:
1113
 
            self.set_stack_index(new_index)
1114
 
        if self.outlineexplorer is not None:
1115
 
            self.outlineexplorer.file_renamed(finfo.editor, finfo.filename)
1116
 
        return new_index
1117
 
        
1118
 
    def set_stack_title(self, index, is_modified):
1119
 
        finfo = self.data[index]
1120
 
        fname = finfo.filename
1121
 
        is_readonly = finfo.editor.isReadOnly()
1122
 
        tab_text = self.get_tab_text(fname, is_modified, is_readonly)
1123
 
        tab_tip = self.get_tab_tip(fname, is_modified, is_readonly)
1124
 
        self.tabs.setTabText(index, tab_text)
1125
 
        self.tabs.setTabToolTip(index, tab_tip)
1126
 
        
1127
 
        
1128
 
    #------ Context menu
1129
 
    def __setup_menu(self):
1130
 
        """Setup tab context menu before showing it"""
1131
 
        self.menu.clear()
1132
 
        if self.data:
1133
 
            actions = self.menu_actions
1134
 
        else:
1135
 
            actions = (self.new_action, self.open_action)
1136
 
            self.setFocus() # --> Editor.__get_focus_editortabwidget
1137
 
        add_actions(self.menu, list(actions)+self.__get_split_actions())
1138
 
        self.close_action.setEnabled(self.is_closable)
1139
 
 
1140
 
 
1141
 
    #------ Hor/Ver splitting
1142
 
    def __get_split_actions(self):
1143
 
        # New window
1144
 
        self.newwindow_action = create_action(self, _("New window"),
1145
 
                icon="newwindow.png", tip=_("Create a new editor window"),
1146
 
                triggered=lambda: self.emit(SIGNAL("create_new_window()")))
1147
 
        # Splitting
1148
 
        self.versplit_action = create_action(self, _("Split vertically"),
1149
 
                icon="versplit.png",
1150
 
                tip=_("Split vertically this editor window"),
1151
 
                triggered=lambda: self.emit(SIGNAL("split_vertically()")))
1152
 
        self.horsplit_action = create_action(self, _("Split horizontally"),
1153
 
                icon="horsplit.png",
1154
 
                tip=_("Split horizontally this editor window"),
1155
 
                triggered=lambda: self.emit(SIGNAL("split_horizontally()")))
1156
 
        self.close_action = create_action(self, _("Close this panel"),
1157
 
                icon="close_panel.png", triggered=self.close)
1158
 
        return [None, self.newwindow_action, None, 
1159
 
                self.versplit_action, self.horsplit_action, self.close_action]
1160
 
        
1161
 
    def reset_orientation(self):
1162
 
        self.horsplit_action.setEnabled(True)
1163
 
        self.versplit_action.setEnabled(True)
1164
 
        
1165
 
    def set_orientation(self, orientation):
1166
 
        self.horsplit_action.setEnabled(orientation == Qt.Horizontal)
1167
 
        self.versplit_action.setEnabled(orientation == Qt.Vertical)
1168
 
        
1169
 
    def update_actions(self):
1170
 
        state = self.get_stack_count() > 0
1171
 
        self.horsplit_action.setEnabled(state)
1172
 
        self.versplit_action.setEnabled(state)
1173
 
    
1174
 
    
1175
 
    #------ Accessors
1176
 
    def get_current_filename(self):
1177
 
        if self.data:
1178
 
            return self.data[self.get_stack_index()].filename
1179
 
        
1180
 
    def has_filename(self, filename):
1181
 
        fixpath = lambda path: osp.normcase(osp.realpath(path))
1182
 
        for index, finfo in enumerate(self.data):
1183
 
            if fixpath(filename) == fixpath(finfo.filename):
1184
 
                return index
1185
 
        
1186
 
    def set_current_filename(self, filename):
1187
 
        """Set current filename and return the associated editor instance"""
1188
 
        index = self.has_filename(filename)
1189
 
        if index is not None:
1190
 
            self.set_stack_index(index)
1191
 
            editor = self.data[index].editor
1192
 
            editor.setFocus()
1193
 
            return editor
1194
 
        
1195
 
    def is_file_opened(self, filename=None):
1196
 
        if filename is None:
1197
 
            # Is there any file opened?
1198
 
            return len(self.data) > 0
1199
 
        else:
1200
 
            return self.has_filename(filename)
1201
 
 
1202
 
        
1203
 
    #------ Close file, tabwidget...
1204
 
    def close_file(self, index=None, force=False):
1205
 
        """Close file (index=None -> close current file)
1206
 
        Keep current file index unchanged (if current file 
1207
 
        that is being closed)"""
1208
 
        current_index = self.get_stack_index()
1209
 
        count = self.get_stack_count()
1210
 
        if index is None:
1211
 
            if count > 0:
1212
 
                index = current_index
1213
 
            else:
1214
 
                self.find_widget.set_editor(None)
1215
 
                return
1216
 
        new_index = None
1217
 
        if count > 1:
1218
 
            if current_index == index:
1219
 
                new_index = self._get_previous_file_index()
1220
 
            else:
1221
 
                new_index = current_index
1222
 
        is_ok = force or self.save_if_changed(cancelable=True, index=index)
1223
 
        if is_ok:
1224
 
            finfo = self.data[index]
1225
 
            self.threadmanager.close_threads(finfo)
1226
 
            # Removing editor reference from outline explorer settings:
1227
 
            if self.outlineexplorer is not None:
1228
 
                self.outlineexplorer.remove_editor(finfo.editor)
1229
 
            
1230
 
            self.remove_from_data(index)
1231
 
            
1232
 
            # We pass self object ID as a QString, because otherwise it would 
1233
 
            # depend on the platform: long for 64bit, int for 32bit. Replacing 
1234
 
            # by long all the time is not working on some 32bit platforms 
1235
 
            # (see Issue 1094, Issue 1098)
1236
 
            self.emit(SIGNAL('close_file(QString,int)'), str(id(self)), index)
1237
 
            
1238
 
            if not self.data and self.is_closable:
1239
 
                # editortabwidget is empty: removing it
1240
 
                # (if it's not the first editortabwidget)
1241
 
                self.close()
1242
 
            self.emit(SIGNAL('opened_files_list_changed()'))
1243
 
            self.emit(SIGNAL('update_code_analysis_actions()'))
1244
 
            self._refresh_outlineexplorer()
1245
 
            self.emit(SIGNAL('refresh_file_dependent_actions()'))
1246
 
            self.emit(SIGNAL('update_plugin_title()'))
1247
 
            
1248
 
            if new_index is not None:
1249
 
                if index < new_index:
1250
 
                    new_index -= 1
1251
 
                self.set_stack_index(new_index)
1252
 
        return is_ok
1253
 
    
1254
 
    def close_all_files(self):
1255
 
        """Close all opened scripts"""
1256
 
        while self.close_file():
1257
 
            pass
1258
 
        
1259
 
 
1260
 
    #------ Save
1261
 
    def save_if_changed(self, cancelable=False, index=None):
1262
 
        """Ask user to save file if modified"""
1263
 
        if index is None:
1264
 
            indexes = range(self.get_stack_count())
1265
 
        else:
1266
 
            indexes = [index]
1267
 
        buttons = QMessageBox.Yes | QMessageBox.No
1268
 
        if cancelable:
1269
 
            buttons |= QMessageBox.Cancel
1270
 
        unsaved_nb = 0
1271
 
        for index in indexes:
1272
 
            if self.data[index].editor.document().isModified():
1273
 
                unsaved_nb += 1
1274
 
        if not unsaved_nb:
1275
 
            # No file to save
1276
 
            return True
1277
 
        if unsaved_nb > 1:
1278
 
            buttons |= QMessageBox.YesAll | QMessageBox.NoAll
1279
 
        yes_all = False
1280
 
        for index in indexes:
1281
 
            self.set_stack_index(index)
1282
 
            finfo = self.data[index]
1283
 
            if finfo.filename == self.tempfile_path or yes_all:
1284
 
                if not self.save():
1285
 
                    return False
1286
 
            elif finfo.editor.document().isModified():
1287
 
                answer = QMessageBox.question(self, self.title,
1288
 
                            _("<b>%s</b> has been modified."
1289
 
                              "<br>Do you want to save changes?"
1290
 
                              ) % osp.basename(finfo.filename),
1291
 
                            buttons)
1292
 
                if answer == QMessageBox.Yes:
1293
 
                    if not self.save():
1294
 
                        return False
1295
 
                elif answer == QMessageBox.YesAll:
1296
 
                    if not self.save():
1297
 
                        return False
1298
 
                    yes_all = True
1299
 
                elif answer == QMessageBox.NoAll:
1300
 
                    return True
1301
 
                elif answer == QMessageBox.Cancel:
1302
 
                    return False
1303
 
        return True
1304
 
    
1305
 
    def save(self, index=None, force=False):
1306
 
        """Save file"""
1307
 
        if index is None:
1308
 
            # Save the currently edited file
1309
 
            if not self.get_stack_count():
1310
 
                return
1311
 
            index = self.get_stack_index()
1312
 
            
1313
 
        finfo = self.data[index]
1314
 
        if not finfo.editor.document().isModified() and not force:
1315
 
            return True
1316
 
        if not osp.isfile(finfo.filename) and not force:
1317
 
            # File has not been saved yet
1318
 
            return self.save_as(index=index)
1319
 
        if self.always_remove_trailing_spaces:
1320
 
            self.remove_trailing_spaces(index)
1321
 
        txt = unicode(finfo.editor.get_text_with_eol())
1322
 
        try:
1323
 
            finfo.encoding = encoding.write(txt, finfo.filename,
1324
 
                                            finfo.encoding)
1325
 
            finfo.newly_created = False
1326
 
            self.emit(SIGNAL('encoding_changed(QString)'), finfo.encoding)
1327
 
            finfo.lastmodified = QFileInfo(finfo.filename).lastModified()
1328
 
            
1329
 
            # We pass self object ID as a QString, because otherwise it would 
1330
 
            # depend on the platform: long for 64bit, int for 32bit. Replacing 
1331
 
            # by long all the time is not working on some 32bit platforms 
1332
 
            # (see Issue 1094, Issue 1098)
1333
 
            self.emit(SIGNAL('file_saved(QString,int,QString)'),
1334
 
                      str(id(self)), index, finfo.filename)
1335
 
 
1336
 
            finfo.editor.document().setModified(False)
1337
 
            self.modification_changed(index=index)
1338
 
            self.analyze_script(index)
1339
 
            codeeditor.validate_rope_project()
1340
 
            
1341
 
            #XXX CodeEditor-only: re-scan the whole text to rebuild outline 
1342
 
            # explorer data from scratch (could be optimized because 
1343
 
            # rehighlighting text means searching for all syntax coloring 
1344
 
            # patterns instead of only searching for class/def patterns which 
1345
 
            # would be sufficient for outline explorer data.
1346
 
            finfo.editor.rehighlight()
1347
 
            
1348
 
            self._refresh_outlineexplorer(index)
1349
 
            return True
1350
 
        except EnvironmentError, error:
1351
 
            QMessageBox.critical(self, _("Save"),
1352
 
                                 _("<b>Unable to save script '%s'</b>"
1353
 
                                   "<br><br>Error message:<br>%s"
1354
 
                                   ) % (osp.basename(finfo.filename),
1355
 
                                        str(error)))
1356
 
            return False
1357
 
        
1358
 
    def file_saved_in_other_editorstack(self, index, filename):
1359
 
        """
1360
 
        File was just saved in another editorstack, let's synchronize!
1361
 
        This avoid file to be automatically reloaded
1362
 
        
1363
 
        Filename is passed in case file was just saved as another name
1364
 
        """
1365
 
        finfo = self.data[index]
1366
 
        finfo.newly_created = False
1367
 
        finfo.filename = unicode(filename)
1368
 
        finfo.lastmodified = QFileInfo(finfo.filename).lastModified()
1369
 
    
1370
 
    def select_savename(self, original_filename):
1371
 
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
1372
 
        filename, _selfilter = getsavefilename(self, _("Save Python script"),
1373
 
                                   original_filename, EDIT_FILTERS)
1374
 
        self.emit(SIGNAL('redirect_stdio(bool)'), True)
1375
 
        if filename:
1376
 
            return osp.normpath(filename)
1377
 
    
1378
 
    def save_as(self, index=None):
1379
 
        """Save file as..."""
1380
 
        if index is None:
1381
 
            # Save the currently edited file
1382
 
            index = self.get_stack_index()
1383
 
        finfo = self.data[index]
1384
 
        filename = self.select_savename(finfo.filename)
1385
 
        if filename:
1386
 
            ao_index = self.has_filename(filename)
1387
 
            # Note: ao_index == index --> saving an untitled file
1388
 
            if ao_index and ao_index != index:
1389
 
                if not self.close_file(ao_index):
1390
 
                    return
1391
 
                if ao_index < index:
1392
 
                    index -= 1
1393
 
            new_index = self.rename_in_data(index, new_filename=filename)
1394
 
            ok = self.save(index=new_index, force=True)
1395
 
            self.refresh(new_index)
1396
 
            self.set_stack_index(new_index)
1397
 
            return ok
1398
 
        else:
1399
 
            return False
1400
 
        
1401
 
    def save_all(self):
1402
 
        """Save all opened files"""
1403
 
        folders = set()
1404
 
        for index in range(self.get_stack_count()):
1405
 
            if self.data[index].editor.document().isModified():
1406
 
                folders.add(osp.dirname(self.data[index].filename))
1407
 
                self.save(index)
1408
 
    
1409
 
    #------ Update UI
1410
 
    def start_stop_analysis_timer(self):
1411
 
        self.is_analysis_done = False
1412
 
        if self.realtime_analysis_enabled:
1413
 
            self.analysis_timer.stop()
1414
 
            self.analysis_timer.start()
1415
 
    
1416
 
    def analyze_script(self, index=None):
1417
 
        """Analyze current script with pyflakes + find todos"""
1418
 
        if self.is_analysis_done:
1419
 
            return
1420
 
        if index is None:
1421
 
            index = self.get_stack_index()
1422
 
        if self.data:
1423
 
            finfo = self.data[index]
1424
 
            run_pyflakes, run_pep8 = self.pyflakes_enabled, self.pep8_enabled
1425
 
            if run_pyflakes or run_pep8:
1426
 
                finfo.run_code_analysis(run_pyflakes, run_pep8)
1427
 
            if self.todolist_enabled:
1428
 
                finfo.run_todo_finder()
1429
 
        self.is_analysis_done = True
1430
 
                
1431
 
    def set_analysis_results(self, index, analysis_results):
1432
 
        """Synchronize analysis results between editorstacks"""
1433
 
        self.data[index].set_analysis_results(analysis_results)
1434
 
        
1435
 
    def get_analysis_results(self):
1436
 
        if self.data:
1437
 
            return self.data[self.get_stack_index()].analysis_results
1438
 
                
1439
 
    def set_todo_results(self, index, todo_results):
1440
 
        """Synchronize todo results between editorstacks"""
1441
 
        self.data[index].set_todo_results(todo_results)
1442
 
        
1443
 
    def get_todo_results(self):
1444
 
        if self.data:
1445
 
            return self.data[self.get_stack_index()].todo_results
1446
 
        
1447
 
    def current_changed(self, index):
1448
 
        """Stack index has changed"""
1449
 
#        count = self.get_stack_count()
1450
 
#        for btn in (self.filelist_btn, self.previous_btn, self.next_btn):
1451
 
#            btn.setEnabled(count > 1)
1452
 
        
1453
 
        editor = self.get_current_editor()
1454
 
        if index != -1:
1455
 
            editor.setFocus()
1456
 
            if DEBUG:
1457
 
                print >>STDOUT, "setfocusto:", editor
1458
 
        else:
1459
 
            self.emit(SIGNAL('reset_statusbar()'))
1460
 
        self.emit(SIGNAL('opened_files_list_changed()'))
1461
 
        
1462
 
        # Index history management
1463
 
        id_list = [id(self.tabs.widget(_i))
1464
 
                   for _i in range(self.tabs.count())]
1465
 
        for _id in self.stack_history[:]:
1466
 
            if _id not in id_list:
1467
 
                self.stack_history.pop(self.stack_history.index(_id))
1468
 
        current_id = id(self.tabs.widget(index))
1469
 
        while current_id in self.stack_history:
1470
 
            self.stack_history.pop(self.stack_history.index(current_id))
1471
 
        self.stack_history.append(current_id)
1472
 
        if DEBUG:
1473
 
            print >>STDOUT, "current_changed:", index, self.data[index].editor,
1474
 
            print >>STDOUT, self.data[index].editor.get_document_id()
1475
 
            
1476
 
        self.emit(SIGNAL('update_plugin_title()'))
1477
 
        if editor is not None:
1478
 
            self.emit(SIGNAL('current_file_changed(QString,int)'),
1479
 
                      self.data[index].filename, editor.get_position('cursor'))
1480
 
        
1481
 
    def _get_previous_file_index(self):
1482
 
        if len(self.stack_history) > 1:
1483
 
            last = len(self.stack_history)-1
1484
 
            w_id = self.stack_history.pop(last)
1485
 
            self.stack_history.insert(0, w_id)
1486
 
            last_id = self.stack_history[last]
1487
 
            for _i in range(self.tabs.count()):
1488
 
                if id(self.tabs.widget(_i)) == last_id:
1489
 
                    return _i
1490
 
        
1491
 
    def go_to_previous_file(self):
1492
 
        """Ctrl+Tab"""
1493
 
        prev_index = self._get_previous_file_index()
1494
 
        if prev_index is not None:
1495
 
            self.set_stack_index(prev_index)
1496
 
        elif len(self.stack_history) == 0 and self.get_stack_count():
1497
 
            self.stack_history = [id(self.tabs.currentWidget())]
1498
 
    
1499
 
    def go_to_next_file(self):
1500
 
        """Ctrl+Shift+Tab"""
1501
 
        if len(self.stack_history) > 1:
1502
 
            last = len(self.stack_history)-1
1503
 
            w_id = self.stack_history.pop(0)
1504
 
            self.stack_history.append(w_id)
1505
 
            last_id = self.stack_history[last]
1506
 
            for _i in range(self.tabs.count()):
1507
 
                if id(self.tabs.widget(_i)) == last_id:
1508
 
                    self.set_stack_index(_i)
1509
 
                    break
1510
 
        elif len(self.stack_history) == 0 and self.get_stack_count():
1511
 
            self.stack_history = [id(self.tabs.currentWidget())]
1512
 
    
1513
 
    def focus_changed(self):
1514
 
        """Editor focus has changed"""
1515
 
        fwidget = QApplication.focusWidget()
1516
 
        for finfo in self.data:
1517
 
            if fwidget is finfo.editor:
1518
 
                self.refresh()
1519
 
        self.emit(SIGNAL("editor_focus_changed()"))
1520
 
        
1521
 
    def _refresh_outlineexplorer(self, index=None, update=True, clear=False):
1522
 
        """Refresh outline explorer panel"""
1523
 
        oe = self.outlineexplorer
1524
 
        if oe is None:
1525
 
            return
1526
 
        if index is None:
1527
 
            index = self.get_stack_index()
1528
 
        enable = False
1529
 
        if self.data:
1530
 
            finfo = self.data[index]
1531
 
            # oe_visible: if outline explorer is not visible, maybe the whole
1532
 
            # GUI is not visible (Spyder is starting up) -> in this case,
1533
 
            # it is necessary to update the outline explorer
1534
 
            oe_visible = oe.isVisible() or not self.isVisible()
1535
 
            if self.outlineexplorer_enabled and finfo.editor.is_python() \
1536
 
               and oe_visible:
1537
 
                enable = True
1538
 
                oe.setEnabled(True)
1539
 
                oe.set_current_editor(finfo.editor, finfo.filename,
1540
 
                                      update=update, clear=clear)
1541
 
        if not enable:
1542
 
            oe.setEnabled(False)
1543
 
            
1544
 
    def __refresh_statusbar(self, index):
1545
 
        """Refreshing statusbar widgets"""
1546
 
        finfo = self.data[index]
1547
 
        self.emit(SIGNAL('encoding_changed(QString)'), finfo.encoding)
1548
 
        # Refresh cursor position status:
1549
 
        line, index = finfo.editor.get_cursor_line_column()
1550
 
        self.emit(SIGNAL('editor_cursor_position_changed(int,int)'),
1551
 
                  line, index)
1552
 
        
1553
 
    def __refresh_readonly(self, index):
1554
 
        finfo = self.data[index]
1555
 
        read_only = not QFileInfo(finfo.filename).isWritable()
1556
 
        if not osp.isfile(finfo.filename):
1557
 
            # This is an 'untitledX.py' file (newly created)
1558
 
            read_only = False
1559
 
        finfo.editor.setReadOnly(read_only)
1560
 
        self.emit(SIGNAL('readonly_changed(bool)'), read_only)
1561
 
        
1562
 
    def __check_file_status(self, index):
1563
 
        """Check if file has been changed in any way outside Spyder:
1564
 
        1. removed, moved or renamed outside Spyder
1565
 
        2. modified outside Spyder"""
1566
 
        if self.__file_status_flag:
1567
 
            # Avoid infinite loop: when the QMessageBox.question pops, it
1568
 
            # gets focus and then give it back to the CodeEditor instance,
1569
 
            # triggering a refresh cycle which calls this method
1570
 
            return
1571
 
        self.__file_status_flag = True
1572
 
 
1573
 
        finfo = self.data[index]
1574
 
        if finfo.newly_created:
1575
 
            return
1576
 
        name = osp.basename(finfo.filename)        
1577
 
        # First, testing if file still exists (removed, moved or offline):
1578
 
        if not osp.isfile(finfo.filename):
1579
 
            answer = QMessageBox.warning(self, self.title,
1580
 
                                _("<b>%s</b> is unavailable "
1581
 
                                  "(this file may have been removed, moved "
1582
 
                                  "or renamed outside Spyder)."
1583
 
                                  "<br>Do you want to close it?") % name,
1584
 
                                QMessageBox.Yes | QMessageBox.No)
1585
 
            if answer == QMessageBox.Yes:
1586
 
                self.close_file(index)
1587
 
            else:
1588
 
                finfo.newly_created = True
1589
 
                finfo.editor.document().setModified(True)
1590
 
                self.modification_changed(index=index)
1591
 
        else:
1592
 
            # Else, testing if it has been modified elsewhere:
1593
 
            lastm = QFileInfo(finfo.filename).lastModified()
1594
 
            if unicode(lastm.toString()) \
1595
 
               != unicode(finfo.lastmodified.toString()):
1596
 
                if finfo.editor.document().isModified():
1597
 
                    answer = QMessageBox.question(self,
1598
 
                                self.title,
1599
 
                                _("<b>%s</b> has been modified outside Spyder."
1600
 
                                  "<br>Do you want to reload it and loose all "
1601
 
                                  "your changes?") % name,
1602
 
                                QMessageBox.Yes | QMessageBox.No)
1603
 
                    if answer == QMessageBox.Yes:
1604
 
                        self.reload(index)
1605
 
                    else:
1606
 
                        finfo.lastmodified = lastm
1607
 
                else:
1608
 
                    self.reload(index)
1609
 
 
1610
 
        # Finally, resetting temporary flag:
1611
 
        self.__file_status_flag = False
1612
 
        
1613
 
    def refresh(self, index=None):
1614
 
        """Refresh tabwidget"""
1615
 
        if index is None:
1616
 
            index = self.get_stack_index()
1617
 
        # Set current editor
1618
 
        if self.get_stack_count():
1619
 
            index = self.get_stack_index()
1620
 
            finfo = self.data[index]
1621
 
            editor = finfo.editor
1622
 
            editor.setFocus()
1623
 
            self._refresh_outlineexplorer(index, update=False)
1624
 
            self.emit(SIGNAL('update_code_analysis_actions()'))
1625
 
            self.__refresh_statusbar(index)
1626
 
            self.__refresh_readonly(index)
1627
 
            self.__check_file_status(index)
1628
 
            self.emit(SIGNAL('update_plugin_title()'))
1629
 
        else:
1630
 
            editor = None
1631
 
        # Update the modification-state-dependent parameters
1632
 
        self.modification_changed()
1633
 
        # Update FindReplace binding
1634
 
        self.find_widget.set_editor(editor, refresh=False)
1635
 
                
1636
 
    def modification_changed(self, state=None, index=None, editor_id=None):
1637
 
        """
1638
 
        Current editor's modification state has changed
1639
 
        --> change tab title depending on new modification state
1640
 
        --> enable/disable save/save all actions
1641
 
        """
1642
 
        if editor_id is not None:
1643
 
            for index, _finfo in enumerate(self.data):
1644
 
                if id(_finfo.editor) == editor_id:
1645
 
                    break
1646
 
        # This must be done before refreshing save/save all actions:
1647
 
        # (otherwise Save/Save all actions will always be enabled)
1648
 
        self.emit(SIGNAL('opened_files_list_changed()'))
1649
 
        # --
1650
 
        if index is None:
1651
 
            index = self.get_stack_index()
1652
 
        if index == -1:
1653
 
            return
1654
 
        finfo = self.data[index]
1655
 
        if state is None:
1656
 
            state = finfo.editor.document().isModified()
1657
 
        self.set_stack_title(index, state)
1658
 
        # Toggle save/save all actions state
1659
 
        self.save_action.setEnabled(state)
1660
 
        self.emit(SIGNAL('refresh_save_all_action()'))
1661
 
        # Refreshing eol mode
1662
 
        eol_chars = finfo.editor.get_line_separator()
1663
 
        os_name = sourcecode.get_os_name_from_eol_chars(eol_chars)
1664
 
        self.emit(SIGNAL('refresh_eol_chars(QString)'), os_name)
1665
 
        
1666
 
 
1667
 
    #------ Load, reload
1668
 
    def reload(self, index):
1669
 
        """Reload file from disk"""
1670
 
        finfo = self.data[index]
1671
 
        txt, finfo.encoding = encoding.read(finfo.filename)
1672
 
        finfo.lastmodified = QFileInfo(finfo.filename).lastModified()
1673
 
        position = finfo.editor.get_position('cursor')
1674
 
        finfo.editor.set_text(txt)
1675
 
        finfo.editor.document().setModified(False)
1676
 
        finfo.editor.set_cursor_position(position)
1677
 
        codeeditor.validate_rope_project()
1678
 
        self._refresh_outlineexplorer(index, update=True, clear=True)
1679
 
        
1680
 
    def revert(self):
1681
 
        """Revert file from disk"""
1682
 
        index = self.get_stack_index()
1683
 
        finfo = self.data[index]
1684
 
        filename = finfo.filename
1685
 
        if finfo.editor.document().isModified():
1686
 
            answer = QMessageBox.warning(self, self.title,
1687
 
                                _("All changes to <b>%s</b> will be lost."
1688
 
                                  "<br>Do you want to revert file from disk?"
1689
 
                                  ) % osp.basename(filename),
1690
 
                                  QMessageBox.Yes|QMessageBox.No)
1691
 
            if answer != QMessageBox.Yes:
1692
 
                return
1693
 
        self.reload(index)
1694
 
        
1695
 
    def create_new_editor(self, fname, enc, txt,
1696
 
                          set_current, new=False, cloned_from=None):
1697
 
        """
1698
 
        Create a new editor instance
1699
 
        Returns finfo object (instead of editor as in previous releases)
1700
 
        """
1701
 
        editor = codeeditor.CodeEditor(self)
1702
 
        finfo = FileInfo(fname, enc, editor, new, self.threadmanager)
1703
 
        self.add_to_data(finfo, set_current)
1704
 
        self.connect(finfo, SIGNAL(
1705
 
                    "send_to_inspector(QString,QString,QString,QString,bool)"),
1706
 
                    self.send_to_inspector)
1707
 
        self.connect(finfo, SIGNAL('analysis_results_changed()'),
1708
 
                     lambda: self.emit(SIGNAL('analysis_results_changed()')))
1709
 
        self.connect(finfo, SIGNAL('todo_results_changed()'),
1710
 
                     lambda: self.emit(SIGNAL('todo_results_changed()')))
1711
 
        self.connect(finfo, SIGNAL("edit_goto(QString,int,QString)"),
1712
 
                     lambda fname, lineno, name:
1713
 
                     self.emit(SIGNAL("edit_goto(QString,int,QString)"),
1714
 
                               fname, lineno, name))
1715
 
        self.connect(finfo, SIGNAL("save_breakpoints(QString,QString)"),
1716
 
                     lambda s1, s2:
1717
 
                     self.emit(SIGNAL("save_breakpoints(QString,QString)"),
1718
 
                               s1, s2))
1719
 
        language = get_file_language(fname, txt)
1720
 
        editor.setup_editor(
1721
 
                linenumbers=self.linenumbers_enabled,
1722
 
                edge_line=self.edgeline_enabled,
1723
 
                edge_line_column=self.edgeline_column, language=language,
1724
 
                markers=self.has_markers(), font=self.default_font,
1725
 
                color_scheme=self.color_scheme,
1726
 
                wrap=self.wrap_enabled, tab_mode=self.tabmode_enabled,
1727
 
                intelligent_backspace=self.intelligent_backspace_enabled,
1728
 
                highlight_current_line=self.highlight_current_line_enabled,
1729
 
                occurence_highlighting=self.occurence_highlighting_enabled,
1730
 
                codecompletion_auto=self.codecompletion_auto_enabled,
1731
 
                codecompletion_case=self.codecompletion_case_enabled,
1732
 
                codecompletion_single=self.codecompletion_single_enabled,
1733
 
                codecompletion_enter=self.codecompletion_enter_enabled,
1734
 
                calltips=self.calltips_enabled,
1735
 
                go_to_definition=self.go_to_definition_enabled,
1736
 
                close_parentheses=self.close_parentheses_enabled,
1737
 
                close_quotes=self.close_quotes_enabled,
1738
 
                add_colons=self.add_colons_enabled,
1739
 
                auto_unindent=self.auto_unindent_enabled,
1740
 
                indent_chars=self.indent_chars,
1741
 
                tab_stop_width=self.tab_stop_width,
1742
 
                cloned_from=cloned_from)
1743
 
        if cloned_from is None:
1744
 
            editor.set_text(txt)
1745
 
            editor.document().setModified(False)
1746
 
        self.connect(finfo, SIGNAL('text_changed_at(QString,int)'),
1747
 
                     lambda fname, position:
1748
 
                     self.emit(SIGNAL('text_changed_at(QString,int)'),
1749
 
                               fname, position))
1750
 
        self.connect(editor, SIGNAL('cursorPositionChanged(int,int)'),
1751
 
                     self.editor_cursor_position_changed)
1752
 
        self.connect(editor, SIGNAL('textChanged()'),
1753
 
                     self.start_stop_analysis_timer)
1754
 
        self.connect(editor, SIGNAL('modificationChanged(bool)'),
1755
 
                     lambda state: self.modification_changed(state,
1756
 
                                                    editor_id=id(editor)))
1757
 
        self.connect(editor, SIGNAL("focus_in()"), self.focus_changed)
1758
 
        if self.outlineexplorer is not None:
1759
 
            # Removing editor reference from outline explorer settings:
1760
 
            self.connect(editor, SIGNAL("destroyed()"),
1761
 
                         lambda obj=editor:
1762
 
                         self.outlineexplorer.remove_editor(obj))
1763
 
 
1764
 
        self.find_widget.set_editor(editor)
1765
 
       
1766
 
        self.emit(SIGNAL('refresh_file_dependent_actions()'))
1767
 
        self.modification_changed(index=self.data.index(finfo))
1768
 
        
1769
 
        return finfo
1770
 
    
1771
 
    def editor_cursor_position_changed(self, line, index):
1772
 
        """Cursor position of one of the editor in the stack has changed"""
1773
 
        self.emit(SIGNAL('editor_cursor_position_changed(int,int)'),
1774
 
                  line, index)
1775
 
    
1776
 
    def send_to_inspector(self, qstr1, qstr2=None, qstr3=None,
1777
 
                          qstr4=None, force=False):
1778
 
        """qstr1: obj_text, qstr2: argpspec, qstr3: note, qstr4: doc_text"""
1779
 
        if not force and not self.inspector_enabled:
1780
 
            return
1781
 
        if self.inspector is not None \
1782
 
           and (force or self.inspector.dockwidget.isVisible()):
1783
 
            # ObjectInspector widget exists and is visible
1784
 
            if qstr4 is None:
1785
 
                self.inspector.set_object_text(qstr1, ignore_unknown=True,
1786
 
                                               force_refresh=force)
1787
 
            else:
1788
 
                objtxt = unicode(qstr1)
1789
 
                title = objtxt.split('.')[-1]
1790
 
                argspec = unicode(qstr2)
1791
 
                note = unicode(qstr3)
1792
 
                doc = unicode(qstr4)
1793
 
                text = {'obj_text': objtxt, 'title': title, 'argspec': argspec,
1794
 
                        'note': note, 'doc': doc}
1795
 
                self.inspector.set_rope_doc(text, force_refresh=force)
1796
 
            editor = self.get_current_editor()
1797
 
            editor.setFocus()
1798
 
    
1799
 
    def new(self, filename, encoding, text):
1800
 
        """
1801
 
        Create new filename with *encoding* and *text*
1802
 
        """
1803
 
        finfo = self.create_new_editor(filename, encoding, text,
1804
 
                                       set_current=False, new=True)
1805
 
        finfo.editor.set_cursor_position('eof')
1806
 
        finfo.editor.insert_text(os.linesep)
1807
 
        return finfo
1808
 
        
1809
 
    def load(self, filename, set_current=True):
1810
 
        """
1811
 
        Load filename, create an editor instance and return it
1812
 
        *Warning* This is loading file, creating editor but not executing
1813
 
        the source code analysis -- the analysis must be done by the editor
1814
 
        plugin (in case multiple editorstack instances are handled)
1815
 
        """
1816
 
        filename = osp.abspath(unicode(filename))
1817
 
        self.emit(SIGNAL('starting_long_process(QString)'),
1818
 
                  _("Loading %s...") % filename)
1819
 
        text, enc = encoding.read(filename)
1820
 
        finfo = self.create_new_editor(filename, enc, text, set_current)
1821
 
        index = self.data.index(finfo)
1822
 
        self._refresh_outlineexplorer(index, update=True)
1823
 
        self.emit(SIGNAL('ending_long_process(QString)'), "")
1824
 
        if self.isVisible() and self.checkeolchars_enabled \
1825
 
           and sourcecode.has_mixed_eol_chars(text):
1826
 
            name = osp.basename(filename)
1827
 
            QMessageBox.warning(self, self.title,
1828
 
                                _("<b>%s</b> contains mixed end-of-line "
1829
 
                                  "characters.<br>Spyder will fix this "
1830
 
                                  "automatically.") % name,
1831
 
                                QMessageBox.Ok)
1832
 
            self.set_os_eol_chars(index)
1833
 
        self.is_analysis_done = False
1834
 
        return finfo
1835
 
    
1836
 
    def set_os_eol_chars(self, index=None):
1837
 
        if index is None:
1838
 
            index = self.get_stack_index()
1839
 
        finfo = self.data[index]
1840
 
        eol_chars = sourcecode.get_eol_chars_from_os_name(os.name)
1841
 
        finfo.editor.set_eol_chars(eol_chars)
1842
 
        finfo.editor.document().setModified(True)
1843
 
        
1844
 
    def remove_trailing_spaces(self, index=None):
1845
 
        """Remove trailing spaces"""
1846
 
        if index is None:
1847
 
            index = self.get_stack_index()
1848
 
        finfo = self.data[index]
1849
 
        finfo.editor.remove_trailing_spaces()
1850
 
        
1851
 
    def fix_indentation(self, index=None):
1852
 
        """Replace tab characters by spaces"""
1853
 
        if index is None:
1854
 
            index = self.get_stack_index()
1855
 
        finfo = self.data[index]
1856
 
        finfo.editor.fix_indentation()
1857
 
 
1858
 
    #------ Run
1859
 
    def run_selection_or_block(self):
1860
 
        """
1861
 
        Run selected text in console and set focus to console
1862
 
        *or*, if there is no selection,
1863
 
        Run current block of lines in console and go to next block
1864
 
        """
1865
 
        self.emit(SIGNAL('external_console_execute_lines(QString)'),
1866
 
                  self.get_current_editor().get_executable_text())
1867
 
            
1868
 
    #------ Drag and drop
1869
 
    def dragEnterEvent(self, event):
1870
 
        """Reimplement Qt method
1871
 
        Inform Qt about the types of data that the widget accepts"""
1872
 
        source = event.mimeData()
1873
 
        if source.hasUrls():
1874
 
            if mimedata2url(source, extlist=EDIT_EXT):
1875
 
                event.acceptProposedAction()
1876
 
            else:
1877
 
                event.ignore()
1878
 
        elif source.hasText():
1879
 
            event.acceptProposedAction()
1880
 
        else:
1881
 
            event.ignore()
1882
 
            
1883
 
    def dropEvent(self, event):
1884
 
        """Reimplement Qt method
1885
 
        Unpack dropped data and handle it"""
1886
 
        source = event.mimeData()
1887
 
        if source.hasUrls():
1888
 
            files = mimedata2url(source, extlist=EDIT_EXT)
1889
 
            if files:
1890
 
                for fname in files:
1891
 
                    self.emit(SIGNAL('plugin_load(QString)'), fname)
1892
 
        elif source.hasText():
1893
 
            editor = self.get_current_editor()
1894
 
            if editor is not None:
1895
 
                editor.insert_text( source.text() )
1896
 
        event.acceptProposedAction()
1897
 
 
1898
 
 
1899
 
class EditorSplitter(QSplitter):
1900
 
    def __init__(self, parent, plugin, menu_actions, first=False,
1901
 
                 register_editorstack_cb=None, unregister_editorstack_cb=None):
1902
 
        QSplitter.__init__(self, parent)
1903
 
        self.setAttribute(Qt.WA_DeleteOnClose)
1904
 
        self.setChildrenCollapsible(False)
1905
 
        
1906
 
        self.toolbar_list = None
1907
 
        self.menu_list = None
1908
 
        
1909
 
        self.plugin = plugin
1910
 
        
1911
 
        if register_editorstack_cb is None:
1912
 
            register_editorstack_cb = self.plugin.register_editorstack
1913
 
        self.register_editorstack_cb = register_editorstack_cb
1914
 
        if unregister_editorstack_cb is None:
1915
 
            unregister_editorstack_cb = self.plugin.unregister_editorstack
1916
 
        self.unregister_editorstack_cb = unregister_editorstack_cb
1917
 
        
1918
 
        self.menu_actions = menu_actions
1919
 
        self.editorstack = EditorStack(self, menu_actions)
1920
 
        self.register_editorstack_cb(self.editorstack)
1921
 
        if not first:
1922
 
            self.plugin.clone_editorstack(editorstack=self.editorstack)
1923
 
        self.connect(self.editorstack, SIGNAL("destroyed()"),
1924
 
                     lambda: self.editorstack_closed())
1925
 
        self.connect(self.editorstack, SIGNAL("split_vertically()"),
1926
 
                     lambda: self.split(orientation=Qt.Vertical))
1927
 
        self.connect(self.editorstack, SIGNAL("split_horizontally()"),
1928
 
                     lambda: self.split(orientation=Qt.Horizontal))
1929
 
        self.addWidget(self.editorstack)
1930
 
 
1931
 
    def closeEvent(self, event):
1932
 
        QSplitter.closeEvent(self, event)
1933
 
        if is_pyqt46:
1934
 
            self.emit(SIGNAL('destroyed()'))
1935
 
                                
1936
 
    def __give_focus_to_remaining_editor(self):
1937
 
        focus_widget = self.plugin.get_focus_widget()
1938
 
        if focus_widget is not None:
1939
 
            focus_widget.setFocus()
1940
 
        
1941
 
    def editorstack_closed(self):
1942
 
        if DEBUG:
1943
 
            print >>STDOUT, "method 'editorstack_closed':"
1944
 
            print >>STDOUT, "    self  :", self
1945
 
#            print >>STDOUT, "    sender:", self.sender()
1946
 
        self.unregister_editorstack_cb(self.editorstack)
1947
 
        self.editorstack = None
1948
 
        try:
1949
 
            close_splitter = self.count() == 1
1950
 
        except RuntimeError:
1951
 
            # editorsplitter has been destroyed (happens when closing a
1952
 
            # EditorMainWindow instance)
1953
 
            return
1954
 
        if close_splitter:
1955
 
            # editorstack just closed was the last widget in this QSplitter
1956
 
            self.close()
1957
 
            return
1958
 
        self.__give_focus_to_remaining_editor()
1959
 
        
1960
 
    def editorsplitter_closed(self):
1961
 
        if DEBUG:
1962
 
            print >>STDOUT, "method 'editorsplitter_closed':"
1963
 
            print >>STDOUT, "    self  :", self
1964
 
#            print >>STDOUT, "    sender:", self.sender()
1965
 
        try:
1966
 
            close_splitter = self.count() == 1 and self.editorstack is None
1967
 
        except RuntimeError:
1968
 
            # editorsplitter has been destroyed (happens when closing a
1969
 
            # EditorMainWindow instance)
1970
 
            return
1971
 
        if close_splitter:
1972
 
            # editorsplitter just closed was the last widget in this QSplitter
1973
 
            self.close()
1974
 
            return
1975
 
        elif self.count() == 2 and self.editorstack:
1976
 
            # back to the initial state: a single editorstack instance,
1977
 
            # as a single widget in this QSplitter: orientation may be changed
1978
 
            self.editorstack.reset_orientation()
1979
 
        self.__give_focus_to_remaining_editor()
1980
 
        
1981
 
    def split(self, orientation=Qt.Vertical):
1982
 
        self.setOrientation(orientation)
1983
 
        self.editorstack.set_orientation(orientation)
1984
 
        editorsplitter = EditorSplitter(self.parent(), self.plugin,
1985
 
                    self.menu_actions,
1986
 
                    register_editorstack_cb=self.register_editorstack_cb,
1987
 
                    unregister_editorstack_cb=self.unregister_editorstack_cb)
1988
 
        self.addWidget(editorsplitter)
1989
 
        self.connect(editorsplitter, SIGNAL("destroyed()"),
1990
 
                     lambda: self.editorsplitter_closed())
1991
 
        current_editor = editorsplitter.editorstack.get_current_editor()
1992
 
        if current_editor is not None:
1993
 
            current_editor.setFocus()
1994
 
            
1995
 
    def iter_editorstacks(self):
1996
 
        editorstacks = [(self.widget(0), self.orientation())]
1997
 
        if self.count() > 1:
1998
 
            editorsplitter = self.widget(1)
1999
 
            editorstacks += editorsplitter.iter_editorstacks()
2000
 
        return editorstacks
2001
 
 
2002
 
    def get_layout_settings(self):
2003
 
        """Return layout state"""
2004
 
        splitsettings = []
2005
 
        for editorstack, orientation in self.iter_editorstacks():
2006
 
            clines = [finfo.editor.get_cursor_line_number()
2007
 
                      for finfo in editorstack.data]
2008
 
            cfname = editorstack.get_current_filename()
2009
 
            splitsettings.append((orientation == Qt.Vertical, cfname, clines))
2010
 
        return dict(hexstate=str(self.saveState().toHex()),
2011
 
                    sizes=self.sizes(), splitsettings=splitsettings)
2012
 
    
2013
 
    def set_layout_settings(self, settings):
2014
 
        """Restore layout state"""
2015
 
        splitsettings = settings.get('splitsettings')
2016
 
        if splitsettings is None:
2017
 
            return
2018
 
        splitter = self
2019
 
        editor = None
2020
 
        for index, (is_vertical, cfname, clines) in enumerate(splitsettings):
2021
 
            if index > 0:
2022
 
                splitter.split(Qt.Vertical if is_vertical else Qt.Horizontal)
2023
 
                splitter = splitter.widget(1)
2024
 
            editorstack = splitter.widget(0)
2025
 
            for index, finfo in enumerate(editorstack.data):
2026
 
                editor = finfo.editor
2027
 
                editor.go_to_line(clines[index])
2028
 
            editorstack.set_current_filename(cfname)
2029
 
        hexstate = settings.get('hexstate')
2030
 
        if hexstate is not None:
2031
 
            self.restoreState( QByteArray().fromHex(str(hexstate)) )
2032
 
        sizes = settings.get('sizes')
2033
 
        if sizes is not None:
2034
 
            self.setSizes(sizes)
2035
 
        if editor is not None:
2036
 
            editor.clearFocus()
2037
 
            editor.setFocus()
2038
 
 
2039
 
 
2040
 
class EditorWidget(QSplitter):
2041
 
    def __init__(self, parent, plugin, menu_actions, show_fullpath,
2042
 
                 fullpath_sorting, show_all_files, show_comments):
2043
 
        QSplitter.__init__(self, parent)
2044
 
        self.setAttribute(Qt.WA_DeleteOnClose)
2045
 
        
2046
 
        statusbar = parent.statusBar() # Create a status bar
2047
 
        self.readwrite_status = ReadWriteStatus(self, statusbar)
2048
 
        self.eol_status = EOLStatus(self, statusbar)
2049
 
        self.encoding_status = EncodingStatus(self, statusbar)
2050
 
        self.cursorpos_status = CursorPositionStatus(self, statusbar)
2051
 
        
2052
 
        self.editorstacks = []
2053
 
        
2054
 
        self.plugin = plugin
2055
 
        
2056
 
        self.find_widget = FindReplace(self, enable_replace=True)
2057
 
        self.plugin.register_widget_shortcuts("Editor", self.find_widget)
2058
 
        self.find_widget.hide()
2059
 
        self.outlineexplorer = OutlineExplorerWidget(self,
2060
 
                                            show_fullpath=show_fullpath,
2061
 
                                            fullpath_sorting=fullpath_sorting,
2062
 
                                            show_all_files=show_all_files,
2063
 
                                            show_comments=show_comments)
2064
 
        self.connect(self.outlineexplorer,
2065
 
                     SIGNAL("edit_goto(QString,int,QString)"),
2066
 
                     lambda filenames, goto, word:
2067
 
                     plugin.load(filenames=filenames, goto=goto, word=word,
2068
 
                                 editorwindow=self.parent()))
2069
 
        
2070
 
        editor_widgets = QWidget(self)
2071
 
        editor_layout = QVBoxLayout()
2072
 
        editor_layout.setContentsMargins(0, 0, 0, 0)
2073
 
        editor_widgets.setLayout(editor_layout)
2074
 
        editorsplitter = EditorSplitter(self, plugin, menu_actions,
2075
 
                        register_editorstack_cb=self.register_editorstack,
2076
 
                        unregister_editorstack_cb=self.unregister_editorstack)
2077
 
        self.editorsplitter = editorsplitter
2078
 
        editor_layout.addWidget(editorsplitter)
2079
 
        editor_layout.addWidget(self.find_widget)
2080
 
        
2081
 
        splitter = QSplitter(self)
2082
 
        splitter.setContentsMargins(0, 0, 0, 0)
2083
 
        splitter.addWidget(editor_widgets)
2084
 
        splitter.addWidget(self.outlineexplorer)
2085
 
        splitter.setStretchFactor(0, 5)
2086
 
        splitter.setStretchFactor(1, 1)
2087
 
 
2088
 
        # Refreshing outline explorer
2089
 
        for index in range(editorsplitter.editorstack.get_stack_count()):
2090
 
            editorsplitter.editorstack._refresh_outlineexplorer(index,
2091
 
                                                                update=True)
2092
 
        
2093
 
    def register_editorstack(self, editorstack):
2094
 
        self.editorstacks.append(editorstack)
2095
 
        if DEBUG:
2096
 
            print >>STDOUT, "EditorWidget.register_editorstack:", editorstack
2097
 
            self.__print_editorstacks()
2098
 
        self.plugin.last_focus_editorstack[self.parent()] = editorstack
2099
 
        editorstack.set_closable( len(self.editorstacks) > 1 )
2100
 
        editorstack.set_outlineexplorer(self.outlineexplorer)
2101
 
        editorstack.set_find_widget(self.find_widget)
2102
 
        self.connect(editorstack, SIGNAL('reset_statusbar()'),
2103
 
                     self.readwrite_status.hide)
2104
 
        self.connect(editorstack, SIGNAL('reset_statusbar()'),
2105
 
                     self.encoding_status.hide)
2106
 
        self.connect(editorstack, SIGNAL('reset_statusbar()'),
2107
 
                     self.cursorpos_status.hide)
2108
 
        self.connect(editorstack, SIGNAL('readonly_changed(bool)'),
2109
 
                     self.readwrite_status.readonly_changed)
2110
 
        self.connect(editorstack, SIGNAL('encoding_changed(QString)'),
2111
 
                     self.encoding_status.encoding_changed)
2112
 
        self.connect(editorstack,
2113
 
                     SIGNAL('editor_cursor_position_changed(int,int)'),
2114
 
                     self.cursorpos_status.cursor_position_changed)
2115
 
        self.connect(editorstack, SIGNAL('refresh_eol_chars(QString)'),
2116
 
                     self.eol_status.eol_changed)
2117
 
        self.plugin.register_editorstack(editorstack)
2118
 
        oe_btn = create_toolbutton(self)
2119
 
        oe_btn.setDefaultAction(self.outlineexplorer.visibility_action)
2120
 
        editorstack.add_corner_widgets_to_tabbar([5, oe_btn])
2121
 
        
2122
 
    def __print_editorstacks(self):
2123
 
        print >>STDOUT, "%d editorstack(s) in editorwidget:" \
2124
 
                        % len(self.editorstacks)
2125
 
        for edst in self.editorstacks:
2126
 
            print >>STDOUT, "    ", edst
2127
 
        
2128
 
    def unregister_editorstack(self, editorstack):
2129
 
        if DEBUG:
2130
 
            print >>STDOUT, "EditorWidget.unregister_editorstack:", editorstack
2131
 
        self.plugin.unregister_editorstack(editorstack)
2132
 
        self.editorstacks.pop(self.editorstacks.index(editorstack))
2133
 
        if DEBUG:
2134
 
            self.__print_editorstacks()
2135
 
        
2136
 
 
2137
 
class EditorMainWindow(QMainWindow):
2138
 
    def __init__(self, plugin, menu_actions, toolbar_list, menu_list,
2139
 
                 show_fullpath, fullpath_sorting, show_all_files,
2140
 
                 show_comments):
2141
 
        QMainWindow.__init__(self)
2142
 
        self.setAttribute(Qt.WA_DeleteOnClose)
2143
 
 
2144
 
        self.window_size = None
2145
 
        
2146
 
        self.editorwidget = EditorWidget(self, plugin, menu_actions,
2147
 
                                         show_fullpath, fullpath_sorting,
2148
 
                                         show_all_files, show_comments)
2149
 
        self.setCentralWidget(self.editorwidget)
2150
 
 
2151
 
        # Give focus to current editor to update/show all status bar widgets
2152
 
        editorstack = self.editorwidget.editorsplitter.editorstack
2153
 
        editor = editorstack.get_current_editor()
2154
 
        if editor is not None:
2155
 
            editor.setFocus()
2156
 
        
2157
 
        self.setWindowTitle("Spyder - %s" % plugin.windowTitle())
2158
 
        self.setWindowIcon(plugin.windowIcon())
2159
 
        
2160
 
        if toolbar_list:
2161
 
            toolbars = []
2162
 
            for title, actions in toolbar_list:
2163
 
                toolbar = self.addToolBar(title)
2164
 
                toolbar.setObjectName(str(id(toolbar)))
2165
 
                add_actions(toolbar, actions)
2166
 
                toolbars.append(toolbar)
2167
 
        if menu_list:
2168
 
            quit_action = create_action(self, _("Close window"),
2169
 
                                        icon="close_panel.png",
2170
 
                                        tip=_("Close this window"),
2171
 
                                        triggered=self.close)
2172
 
            menus = []
2173
 
            for index, (title, actions) in enumerate(menu_list):
2174
 
                menu = self.menuBar().addMenu(title)
2175
 
                if index == 0:
2176
 
                    # File menu
2177
 
                    add_actions(menu, actions+[None, quit_action])
2178
 
                else:
2179
 
                    add_actions(menu, actions)
2180
 
                menus.append(menu)
2181
 
            
2182
 
    def resizeEvent(self, event):
2183
 
        """Reimplement Qt method"""
2184
 
        if not self.isMaximized() and not self.isFullScreen():
2185
 
            self.window_size = self.size()
2186
 
        QMainWindow.resizeEvent(self, event)
2187
 
                
2188
 
    def closeEvent(self, event):
2189
 
        """Reimplement Qt method"""
2190
 
        QMainWindow.closeEvent(self, event)
2191
 
        if is_pyqt46:
2192
 
            self.emit(SIGNAL('destroyed()'))
2193
 
            for editorstack in self.editorwidget.editorstacks[:]:
2194
 
                if DEBUG:
2195
 
                    print >>STDOUT, "--> destroy_editorstack:", editorstack
2196
 
                editorstack.emit(SIGNAL('destroyed()'))
2197
 
                                
2198
 
    def get_layout_settings(self):
2199
 
        """Return layout state"""
2200
 
        splitsettings = self.editorwidget.editorsplitter.get_layout_settings()
2201
 
        return dict(size=(self.window_size.width(), self.window_size.height()),
2202
 
                    pos=(self.pos().x(), self.pos().y()),
2203
 
                    is_maximized=self.isMaximized(),
2204
 
                    is_fullscreen=self.isFullScreen(),
2205
 
                    hexstate=str(self.saveState().toHex()),
2206
 
                    splitsettings=splitsettings)
2207
 
    
2208
 
    def set_layout_settings(self, settings):
2209
 
        """Restore layout state"""
2210
 
        size = settings.get('size')
2211
 
        if size is not None:
2212
 
            self.resize( QSize(*size) )
2213
 
            self.window_size = self.size()
2214
 
        pos = settings.get('pos')
2215
 
        if pos is not None:
2216
 
            self.move( QPoint(*pos) )
2217
 
        hexstate = settings.get('hexstate')
2218
 
        if hexstate is not None:
2219
 
            self.restoreState( QByteArray().fromHex(str(hexstate)) )
2220
 
        if settings.get('is_maximized'):
2221
 
            self.setWindowState(Qt.WindowMaximized)
2222
 
        if settings.get('is_fullscreen'):
2223
 
            self.setWindowState(Qt.WindowFullScreen)
2224
 
        splitsettings = settings.get('splitsettings')
2225
 
        if splitsettings is not None:
2226
 
            self.editorwidget.editorsplitter.set_layout_settings(splitsettings)
2227
 
 
2228
 
 
2229
 
class EditorPluginExample(QSplitter):
2230
 
    def __init__(self):
2231
 
        QSplitter.__init__(self)
2232
 
                
2233
 
        menu_actions = []
2234
 
                
2235
 
        self.editorstacks = []
2236
 
        self.editorwindows = []
2237
 
        
2238
 
        self.last_focus_editorstack = {} # fake
2239
 
 
2240
 
        self.find_widget = FindReplace(self, enable_replace=True)
2241
 
        self.outlineexplorer = OutlineExplorerWidget(self, show_fullpath=False,
2242
 
                                                     show_all_files=False)
2243
 
        self.connect(self.outlineexplorer,
2244
 
                     SIGNAL("edit_goto(QString,int,QString)"),
2245
 
                     self.go_to_file)
2246
 
        
2247
 
        editor_widgets = QWidget(self)
2248
 
        editor_layout = QVBoxLayout()
2249
 
        editor_layout.setContentsMargins(0, 0, 0, 0)
2250
 
        editor_widgets.setLayout(editor_layout)
2251
 
        editor_layout.addWidget(EditorSplitter(self, self, menu_actions,
2252
 
                                               first=True))
2253
 
        editor_layout.addWidget(self.find_widget)
2254
 
        
2255
 
        self.setContentsMargins(0, 0, 0, 0)
2256
 
        self.addWidget(editor_widgets)
2257
 
        self.addWidget(self.outlineexplorer)
2258
 
        
2259
 
        self.setStretchFactor(0, 5)
2260
 
        self.setStretchFactor(1, 1)
2261
 
        
2262
 
        self.menu_actions = menu_actions
2263
 
        self.toolbar_list = None
2264
 
        self.menu_list = None
2265
 
        self.setup_window([], [])
2266
 
        
2267
 
    def go_to_file(self, fname, lineno, text):
2268
 
        editorstack = self.editorstacks[0]
2269
 
        editorstack.set_current_filename(unicode(fname))
2270
 
        editor = editorstack.get_current_editor()
2271
 
        editor.go_to_line(lineno, word=text)
2272
 
 
2273
 
    def closeEvent(self, event):
2274
 
        for win in self.editorwindows[:]:
2275
 
            win.close()
2276
 
        if DEBUG:
2277
 
            print >>STDOUT, len(self.editorwindows), ":", self.editorwindows
2278
 
            print >>STDOUT, len(self.editorstacks), ":", self.editorstacks
2279
 
        
2280
 
        event.accept()
2281
 
        
2282
 
    def load(self, fname):
2283
 
        QApplication.processEvents()
2284
 
        editorstack = self.editorstacks[0]
2285
 
        editorstack.load(fname)
2286
 
        editorstack.analyze_script()
2287
 
    
2288
 
    def register_editorstack(self, editorstack):
2289
 
        if DEBUG:
2290
 
            print >>STDOUT, "FakePlugin.register_editorstack:", editorstack
2291
 
        self.editorstacks.append(editorstack)
2292
 
        if self.isAncestorOf(editorstack):
2293
 
            # editorstack is a child of the Editor plugin
2294
 
            editorstack.set_fullpath_sorting_enabled(True)
2295
 
            editorstack.set_closable( len(self.editorstacks) > 1 )
2296
 
            editorstack.set_outlineexplorer(self.outlineexplorer)
2297
 
            editorstack.set_find_widget(self.find_widget)
2298
 
            oe_btn = create_toolbutton(self)
2299
 
            oe_btn.setDefaultAction(self.outlineexplorer.visibility_action)
2300
 
            editorstack.add_corner_widgets_to_tabbar([5, oe_btn])
2301
 
            
2302
 
        action = QAction(self)
2303
 
        editorstack.set_io_actions(action, action, action, action)
2304
 
        font = QFont("Courier New")
2305
 
        font.setPointSize(10)
2306
 
        editorstack.set_default_font(font, color_scheme='Spyder')
2307
 
        self.connect(editorstack, SIGNAL('close_file(QString,int)'),
2308
 
                     self.close_file_in_all_editorstacks)
2309
 
        self.connect(editorstack, SIGNAL("create_new_window()"),
2310
 
                     self.create_new_window)
2311
 
        self.connect(editorstack, SIGNAL('plugin_load(QString)'),
2312
 
                     self.load)
2313
 
                    
2314
 
    def unregister_editorstack(self, editorstack):
2315
 
        if DEBUG:
2316
 
            print >>STDOUT, "FakePlugin.unregister_editorstack:", editorstack
2317
 
        self.editorstacks.pop(self.editorstacks.index(editorstack))
2318
 
        
2319
 
    def clone_editorstack(self, editorstack):
2320
 
        editorstack.clone_from(self.editorstacks[0])
2321
 
        
2322
 
    def setup_window(self, toolbar_list, menu_list):
2323
 
        self.toolbar_list = toolbar_list
2324
 
        self.menu_list = menu_list
2325
 
        
2326
 
    def create_new_window(self):
2327
 
        window = EditorMainWindow(self, self.menu_actions,
2328
 
                                  self.toolbar_list, self.menu_list,
2329
 
                                  show_fullpath=False, fullpath_sorting=True,
2330
 
                                  show_all_files=False, show_comments=True)
2331
 
        window.resize(self.size())
2332
 
        window.show()
2333
 
        self.register_editorwindow(window)
2334
 
        self.connect(window, SIGNAL("destroyed()"),
2335
 
                     lambda win=window: self.unregister_editorwindow(win))
2336
 
        
2337
 
    def register_editorwindow(self, window):
2338
 
        if DEBUG:
2339
 
            print >>STDOUT, "register_editorwindowQObject*:", window
2340
 
        self.editorwindows.append(window)
2341
 
        
2342
 
    def unregister_editorwindow(self, window):
2343
 
        if DEBUG:
2344
 
            print >>STDOUT, "unregister_editorwindow:", window
2345
 
        self.editorwindows.pop(self.editorwindows.index(window))
2346
 
    
2347
 
    def get_focus_widget(self):
2348
 
        pass
2349
 
 
2350
 
    def close_file_in_all_editorstacks(self, editorstack_id_str, index):
2351
 
        for editorstack in self.editorstacks:
2352
 
            if str(id(editorstack)) != editorstack_id_str:
2353
 
                editorstack.blockSignals(True)
2354
 
                editorstack.close_file(index, force=True)
2355
 
                editorstack.blockSignals(False)
2356
 
                
2357
 
    def register_widget_shortcuts(self, context, widget):
2358
 
        """Fake!"""
2359
 
        pass
2360
 
    
2361
 
def test():
2362
 
    from spyderlib.utils.qthelpers import qapplication
2363
 
    app = qapplication()
2364
 
    test = EditorPluginExample()
2365
 
    test.resize(900, 700)
2366
 
    test.show()
2367
 
    import time
2368
 
    t0 = time.time()
2369
 
    test.load(__file__)
2370
 
    test.load("explorer.py")
2371
 
    test.load("dicteditor.py")
2372
 
    test.load("sourcecode/codeeditor.py")
2373
 
    test.load("../spyder.py")
2374
 
    print "Elapsed time: %.3f s" % (time.time()-t0)
2375
 
    sys.exit(app.exec_())
2376
 
    
2377
 
if __name__ == "__main__":
2378
 
    test()
 
1
# -*- coding: utf-8 -*-
 
2
#
 
3
# Copyright © 2009-2011 Pierre Raybaut
 
4
# Licensed under the terms of the MIT License
 
5
# (see spyderlib/__init__.py for details)
 
6
 
 
7
"""Editor Widget"""
 
8
 
 
9
# pylint: disable=C0103
 
10
# pylint: disable=R0903
 
11
# pylint: disable=R0911
 
12
# pylint: disable=R0201
 
13
 
 
14
from spyderlib.qt import is_pyqt46
 
15
from spyderlib.qt.QtGui import (QVBoxLayout, QMessageBox, QMenu, QFont,
 
16
                                QAction, QApplication, QWidget, QHBoxLayout,
 
17
                                QLabel, QKeySequence, QShortcut, QMainWindow,
 
18
                                QSplitter, QListWidget, QListWidgetItem,
 
19
                                QDialog, QLineEdit)
 
20
from spyderlib.qt.QtCore import (SIGNAL, Qt, QFileInfo, QThread, QObject,
 
21
                                 QByteArray, QSize, QPoint, QTimer, Slot)
 
22
from spyderlib.qt.compat import getsavefilename
 
23
 
 
24
import os
 
25
import sys
 
26
import re
 
27
import os.path as osp
 
28
 
 
29
# Local imports
 
30
from spyderlib.utils import encoding, sourcecode, programs, codeanalysis
 
31
from spyderlib.utils.dochelpers import getsignaturesfromtext
 
32
from spyderlib.utils.module_completion import (module_completion,
 
33
                                               get_preferred_submodules)
 
34
from spyderlib.baseconfig import _, DEBUG, STDOUT, STDERR
 
35
from spyderlib.config import EDIT_FILTERS, EDIT_EXT
 
36
from spyderlib.utils.qthelpers import (get_icon, create_action, add_actions,
 
37
                                       mimedata2url, get_filetype_icon,
 
38
                                       create_toolbutton)
 
39
from spyderlib.widgets.tabs import BaseTabs
 
40
from spyderlib.widgets.findreplace import FindReplace
 
41
from spyderlib.widgets.editortools import OutlineExplorerWidget
 
42
from spyderlib.widgets.status import (ReadWriteStatus, EOLStatus,
 
43
                                      EncodingStatus, CursorPositionStatus)
 
44
from spyderlib.widgets.sourcecode import syntaxhighlighters, codeeditor
 
45
from spyderlib.widgets.sourcecode.base import TextEditBaseWidget  #analysis:ignore
 
46
from spyderlib.widgets.sourcecode.codeeditor import Printer  #analysis:ignore
 
47
from spyderlib.widgets.sourcecode.codeeditor import get_file_language
 
48
 
 
49
 
 
50
class FileListDialog(QDialog):
 
51
    def __init__(self, parent, tabs, fullpath_sorting):
 
52
        QDialog.__init__(self, parent)
 
53
        
 
54
        # Destroying the C++ object right after closing the dialog box,
 
55
        # otherwise it may be garbage-collected in another QThread
 
56
        # (e.g. the editor's analysis thread in Spyder), thus leading to
 
57
        # a segmentation fault on UNIX or an application crash on Windows
 
58
        self.setAttribute(Qt.WA_DeleteOnClose)
 
59
        
 
60
        self.indexes = None
 
61
        
 
62
        self.setWindowIcon(get_icon('filelist.png'))
 
63
        self.setWindowTitle(_("File list management"))
 
64
        
 
65
        self.setModal(True)
 
66
        
 
67
        flabel = QLabel(_("Filter:"))
 
68
        self.edit = QLineEdit(self)
 
69
        self.connect(self.edit, SIGNAL("returnPressed()"), self.edit_file)
 
70
        self.connect(self.edit, SIGNAL("textChanged(QString)"),
 
71
                     lambda text: self.synchronize(0))
 
72
        fhint = QLabel(_("(press <b>Enter</b> to edit file)"))
 
73
        edit_layout = QHBoxLayout()
 
74
        edit_layout.addWidget(flabel)
 
75
        edit_layout.addWidget(self.edit)
 
76
        edit_layout.addWidget(fhint)
 
77
        
 
78
        self.listwidget = QListWidget(self)
 
79
        self.listwidget.setResizeMode(QListWidget.Adjust)
 
80
        self.connect(self.listwidget, SIGNAL("itemSelectionChanged()"),
 
81
                     self.item_selection_changed)
 
82
        self.connect(self.listwidget, SIGNAL("itemActivated(QListWidgetItem*)"),
 
83
                     self.edit_file)
 
84
        
 
85
        btn_layout = QHBoxLayout()
 
86
        edit_btn = create_toolbutton(self, icon=get_icon('edit.png'),
 
87
                     text=_("&Edit file"), autoraise=False,
 
88
                     triggered=self.edit_file, text_beside_icon=True)
 
89
        edit_btn.setMinimumHeight(28)
 
90
        btn_layout.addWidget(edit_btn)
 
91
        
 
92
        btn_layout.addStretch()
 
93
        btn_layout.addSpacing(150)
 
94
        btn_layout.addStretch()
 
95
        
 
96
        close_btn = create_toolbutton(self, text=_("&Close file"),
 
97
              icon=get_icon("fileclose.png"),
 
98
              autoraise=False, text_beside_icon=True,
 
99
              triggered=lambda: self.emit(SIGNAL("close_file(int)"),
 
100
                                  self.indexes[self.listwidget.currentRow()]))
 
101
        close_btn.setMinimumHeight(28)
 
102
        btn_layout.addWidget(close_btn)
 
103
 
 
104
        hint = QLabel(_("Hint: press <b>Alt</b> to show accelerators"))
 
105
        hint.setAlignment(Qt.AlignCenter)
 
106
        
 
107
        vlayout = QVBoxLayout()
 
108
        vlayout.addLayout(edit_layout)
 
109
        vlayout.addWidget(self.listwidget)
 
110
        vlayout.addLayout(btn_layout)
 
111
        vlayout.addWidget(hint)
 
112
        self.setLayout(vlayout)
 
113
        
 
114
        self.tabs = tabs
 
115
        self.fullpath_sorting = fullpath_sorting
 
116
        self.buttons = (edit_btn, close_btn)
 
117
        
 
118
    def edit_file(self):
 
119
        row = self.listwidget.currentRow()
 
120
        if self.listwidget.count() > 0 and row >= 0:
 
121
            self.emit(SIGNAL("edit_file(int)"), self.indexes[row])
 
122
            self.accept()
 
123
            
 
124
    def item_selection_changed(self):
 
125
        for btn in self.buttons:
 
126
            btn.setEnabled(self.listwidget.currentRow() >= 0)
 
127
        
 
128
    def synchronize(self, stack_index):
 
129
        count = self.tabs.count()
 
130
        if count == 0:
 
131
            self.accept()
 
132
            return
 
133
        self.listwidget.setTextElideMode(Qt.ElideMiddle
 
134
                                         if self.fullpath_sorting
 
135
                                         else Qt.ElideRight)
 
136
        current_row = self.listwidget.currentRow()
 
137
        if current_row >= 0:
 
138
            current_text = unicode(self.listwidget.currentItem().text())
 
139
        else:
 
140
            current_text = ""
 
141
        self.listwidget.clear()
 
142
        self.indexes = []
 
143
        new_current_index = stack_index
 
144
        filter_text = unicode(self.edit.text())
 
145
        lw_index = 0
 
146
        for index in range(count):
 
147
            text = unicode(self.tabs.tabText(index))
 
148
            if len(filter_text) == 0 or filter_text in text:
 
149
                if text == current_text:
 
150
                    new_current_index = lw_index
 
151
                lw_index += 1
 
152
                item = QListWidgetItem(self.tabs.tabIcon(index),
 
153
                                       text, self.listwidget)
 
154
                item.setSizeHint(QSize(0, 25))
 
155
                self.listwidget.addItem(item)
 
156
                self.indexes.append(index)
 
157
        if new_current_index < self.listwidget.count():
 
158
            self.listwidget.setCurrentRow(new_current_index)
 
159
        for btn in self.buttons:
 
160
            btn.setEnabled(lw_index > 0)
 
161
 
 
162
 
 
163
class AnalysisThread(QThread):
 
164
    """Analysis thread"""
 
165
    def __init__(self, parent, checker, source_code):
 
166
        super(AnalysisThread, self).__init__(parent)
 
167
        self.checker = checker
 
168
        self.results = None
 
169
        self.source_code = source_code
 
170
    
 
171
    def run(self):
 
172
        """Run analysis"""
 
173
        try:
 
174
            self.results = self.checker(self.source_code)
 
175
        except Exception:
 
176
            if DEBUG:
 
177
                import traceback
 
178
                traceback.print_exc(file=STDERR)
 
179
 
 
180
 
 
181
class GetSubmodulesThread(QThread):
 
182
    """
 
183
    A thread to generate a list of submodules to be passed to Rope
 
184
    extension_modules preference
 
185
    """
 
186
    def __init__(self):
 
187
        super(GetSubmodulesThread, self).__init__()
 
188
        self.submods = []
 
189
 
 
190
    def run(self):
 
191
        self.submods = get_preferred_submodules()
 
192
        self.emit(SIGNAL('submods_ready()'))
 
193
 
 
194
 
 
195
class ThreadManager(QObject):
 
196
    """Analysis thread manager"""
 
197
    def __init__(self, parent, max_simultaneous_threads=2):
 
198
        super(ThreadManager, self).__init__(parent)
 
199
        self.max_simultaneous_threads = max_simultaneous_threads
 
200
        self.started_threads = {}
 
201
        self.pending_threads = []
 
202
        self.end_callbacks = {}
 
203
        
 
204
    def close_threads(self, parent):
 
205
        """Close threads associated to parent_id"""
 
206
        if DEBUG:
 
207
            print >>STDOUT, "Call to 'close_threads'"
 
208
        if parent is None:
 
209
            # Closing all threads
 
210
            self.pending_threads = []
 
211
            threadlist = []
 
212
            for threads in self.started_threads.values():
 
213
                threadlist += threads
 
214
        else:
 
215
            parent_id = id(parent)
 
216
            self.pending_threads = [(_th, _id) for (_th, _id)
 
217
                                    in self.pending_threads
 
218
                                    if _id != parent_id]
 
219
            threadlist = self.started_threads.get(parent_id, [])
 
220
        for thread in threadlist:
 
221
            if DEBUG:
 
222
                print >>STDOUT, "Waiting for thread %r to finish" % thread
 
223
            while thread.isRunning():
 
224
                # We can't terminate thread safely, so we simply wait...
 
225
                QApplication.processEvents()
 
226
                
 
227
    def close_all_threads(self):
 
228
        """Close all threads"""
 
229
        if DEBUG:
 
230
            print >>STDOUT, "Call to 'close_all_threads'"
 
231
        self.close_threads(None)
 
232
        
 
233
    def add_thread(self, checker, end_callback, source_code, parent):
 
234
        """Add thread to queue"""
 
235
        parent_id = id(parent)
 
236
        thread = AnalysisThread(self, checker, source_code)
 
237
        self.end_callbacks[id(thread)] = end_callback
 
238
        self.pending_threads.append((thread, parent_id))
 
239
        if DEBUG:
 
240
            print >>STDOUT, "Added thread %r to queue" % thread
 
241
        QTimer.singleShot(50, self.update_queue)
 
242
    
 
243
    def update_queue(self):
 
244
        """Update queue"""
 
245
        started = 0
 
246
        for parent_id, threadlist in self.started_threads.items():
 
247
            still_running = []
 
248
            for thread in threadlist:
 
249
                if thread.isFinished():
 
250
                    end_callback = self.end_callbacks.pop(id(thread))
 
251
                    if thread.results is not None:
 
252
                        #  The thread was executed successfully
 
253
                        end_callback(thread.results)
 
254
                    thread.setParent(None)
 
255
                    thread = None
 
256
                else:
 
257
                    still_running.append(thread)
 
258
                    started += 1
 
259
            threadlist = None
 
260
            if still_running:
 
261
                self.started_threads[parent_id] = still_running
 
262
            else:
 
263
                self.started_threads.pop(parent_id)
 
264
        if DEBUG:
 
265
            print >>STDOUT, "Updating queue:"
 
266
            print >>STDOUT, "    started:", started
 
267
            print >>STDOUT, "    pending:", len(self.pending_threads)
 
268
        if self.pending_threads and started < self.max_simultaneous_threads:
 
269
            thread, parent_id = self.pending_threads.pop(0)
 
270
            self.connect(thread, SIGNAL('finished()'), self.update_queue)
 
271
            threadlist = self.started_threads.get(parent_id, [])
 
272
            self.started_threads[parent_id] = threadlist+[thread]
 
273
            if DEBUG:
 
274
                print >>STDOUT, "===>starting:", thread
 
275
            thread.start()
 
276
 
 
277
 
 
278
class FileInfo(QObject):
 
279
    """File properties"""
 
280
    def __init__(self, filename, encoding, editor, new, threadmanager):
 
281
        QObject.__init__(self)
 
282
        self.threadmanager = threadmanager
 
283
        self.filename = filename
 
284
        self.newly_created = new
 
285
        self.encoding = encoding
 
286
        self.editor = editor
 
287
        self.path = []
 
288
        self.submods_thread = GetSubmodulesThread()
 
289
        self.rope_project = codeeditor.get_rope_project()
 
290
        self.classes = (filename, None, None)
 
291
        self.analysis_results = []
 
292
        self.todo_results = []
 
293
        self.lastmodified = QFileInfo(filename).lastModified()
 
294
 
 
295
        self.submods_thread.start()
 
296
        self.connect(editor, SIGNAL('trigger_code_completion(bool)'),
 
297
                     self.trigger_code_completion)
 
298
        self.connect(editor, SIGNAL('trigger_calltip_and_doc_rendering(int)'),
 
299
                     self.trigger_calltip_and_doc_rendering)
 
300
        self.connect(editor, SIGNAL("go_to_definition(int)"),
 
301
                     self.go_to_definition)
 
302
        
 
303
        self.connect(editor, SIGNAL('textChanged()'),
 
304
                     self.text_changed)
 
305
        
 
306
        self.connect(editor, SIGNAL('breakpoints_changed()'),
 
307
                     self.breakpoints_changed)
 
308
        self.connect(self.submods_thread, SIGNAL('submods_ready()'),
 
309
                     self.update_extension_modules)
 
310
        
 
311
        self.pyflakes_results = None
 
312
        self.pep8_results = None
 
313
    
 
314
    def update_extension_modules(self):
 
315
        self.rope_project.set_pref('extension_modules',
 
316
                                   self.submods_thread.submods)
 
317
    
 
318
    def text_changed(self):
 
319
        """Editor's text has changed"""
 
320
        self.emit(SIGNAL('text_changed_at(QString,int)'),
 
321
                  self.filename, self.editor.get_position('cursor'))
 
322
        
 
323
    def trigger_code_completion(self, automatic):
 
324
        """Trigger code completion"""
 
325
        source_code = unicode(self.editor.toPlainText())
 
326
        offset = self.editor.get_position('cursor')
 
327
        text = self.editor.get_text('sol', 'cursor')
 
328
        
 
329
        if text.lstrip().startswith('import '):
 
330
            text = text.lstrip()
 
331
            comp_list = module_completion(text, self.path)
 
332
            words = text.split(' ')
 
333
            if ',' in words[-1]:
 
334
                words = words[-1].split(',')
 
335
            if comp_list:
 
336
                self.editor.show_completion_list(comp_list,
 
337
                                                 completion_text=words[-1],
 
338
                                                 automatic=automatic)
 
339
            return
 
340
        elif text.lstrip().startswith('from '):
 
341
            text = text.lstrip()
 
342
            comp_list = module_completion(text, self.path)
 
343
            words = text.split(' ')
 
344
            if '(' in words[-1]:
 
345
                words = words[:-2] + words[-1].split('(')
 
346
            if ',' in words[-1]:
 
347
                words = words[:-2] + words[-1].split(',')
 
348
            self.editor.show_completion_list(comp_list,
 
349
                                             completion_text=words[-1],
 
350
                                             automatic=automatic)
 
351
            return
 
352
        else:
 
353
            textlist = self.rope_project.get_completion_list(source_code,
 
354
                                                             offset,
 
355
                                                             self.filename)
 
356
            if textlist:
 
357
                completion_text = re.split(r"[^a-zA-Z0-9_]", text)[-1]
 
358
                if text.lstrip().startswith('#') and text.endswith('.'):
 
359
                    return
 
360
                else:
 
361
                    self.editor.show_completion_list(textlist, completion_text,
 
362
                                                     automatic)
 
363
                return
 
364
        
 
365
    def trigger_calltip_and_doc_rendering(self, position, auto=True):
 
366
        """Trigger calltip and docstring rendering in the Object Inspector"""
 
367
        # auto is True means that trigger_calltip was called automatically,
 
368
        # i.e. the user has just entered an opening parenthesis -- in that 
 
369
        # case, we don't want to force the object inspector to be visible, 
 
370
        # to avoid polluting the window layout
 
371
        source_code = unicode(self.editor.toPlainText())
 
372
        offset = position
 
373
        
 
374
        helplist = self.rope_project.get_calltip_and_docs(source_code, offset,
 
375
                                                          self.filename)
 
376
        if not helplist:
 
377
            return
 
378
        obj_fullname = ''
 
379
        signatures = []
 
380
        cts, doc_text = helplist
 
381
        if cts:
 
382
            cts = cts.replace('.__init__', '')
 
383
            parpos = cts.find('(')
 
384
            if parpos:
 
385
                obj_fullname = cts[:parpos]
 
386
                obj_name = obj_fullname.split('.')[-1]
 
387
                cts = cts.replace(obj_fullname, obj_name)
 
388
                signatures = [cts]
 
389
                if '()' in cts:
 
390
                    # Either inspected object has no argument, or it's 
 
391
                    # a builtin or an extension -- in this last case 
 
392
                    # the following attempt may succeed:
 
393
                    signatures = getsignaturesfromtext(doc_text, obj_name)
 
394
        if not obj_fullname:
 
395
            obj_fullname = codeeditor.get_primary_at(source_code, offset)
 
396
        if obj_fullname and not obj_fullname.startswith('self.') and doc_text:
 
397
            # doc_text was generated by utils.dochelpers.getdoc
 
398
            if type(doc_text) is dict:
 
399
                obj_fullname = doc_text['name']
 
400
                argspec = doc_text['argspec']
 
401
                note = doc_text['note']
 
402
                doc_text = doc_text['docstring']
 
403
            elif signatures:
 
404
                argspec_st = signatures[0].find('(')
 
405
                argspec = signatures[0][argspec_st:]
 
406
                module_end = obj_fullname.rfind('.')
 
407
                module = obj_fullname[:module_end]
 
408
                note = 'Present in %s module' % module
 
409
            else:
 
410
                argspec = ''
 
411
                note = ''
 
412
            self.emit(SIGNAL(
 
413
                    "send_to_inspector(QString,QString,QString,QString,bool)"),
 
414
                    obj_fullname, argspec, note, doc_text, not auto)
 
415
        if signatures:
 
416
            signatures = ['<b>'+s.replace('(', '(</b>'
 
417
                                          ).replace(')', '<b>)</b>')
 
418
                          for s in signatures]
 
419
            self.editor.show_calltip(obj_fullname, '<br>'.join(signatures),
 
420
                                     at_position=position)
 
421
                    
 
422
    def go_to_definition(self, position):
 
423
        """Go to definition"""
 
424
        source_code = unicode(self.editor.toPlainText())
 
425
        offset = position
 
426
        fname, lineno = self.rope_project.get_definition_location(source_code,
 
427
                                                    offset, self.filename)
 
428
        if fname is not None and lineno is not None:
 
429
            self.emit(SIGNAL("edit_goto(QString,int,QString)"),
 
430
                      fname, lineno, "")
 
431
    
 
432
    def get_source_code(self):
 
433
        """Return associated editor source code"""
 
434
        return unicode(self.editor.toPlainText()).encode('utf-8')
 
435
    
 
436
    def run_code_analysis(self, run_pyflakes, run_pep8):
 
437
        """Run code analysis"""
 
438
        run_pyflakes = run_pyflakes and codeanalysis.is_pyflakes_installed()
 
439
        run_pep8 = run_pep8 and\
 
440
                   codeanalysis.get_checker_executable('pep8') is not None
 
441
        self.pyflakes_results = []
 
442
        self.pep8_results = []
 
443
        if self.editor.is_python():
 
444
            source_code = self.get_source_code()
 
445
            if run_pyflakes:
 
446
                self.pyflakes_results = None
 
447
            if run_pep8:
 
448
                self.pep8_results = None
 
449
            if run_pyflakes:
 
450
                self.threadmanager.add_thread(codeanalysis.check_with_pyflakes,
 
451
                                              self.pyflakes_analysis_finished,
 
452
                                              source_code, self)
 
453
            if run_pep8:
 
454
                self.threadmanager.add_thread(codeanalysis.check_with_pep8,
 
455
                                              self.pep8_analysis_finished,
 
456
                                              source_code, self)
 
457
        
 
458
    def pyflakes_analysis_finished(self, results):
 
459
        """Pyflakes code analysis thread has finished"""
 
460
        self.pyflakes_results = results
 
461
        if self.pep8_results is not None:
 
462
            self.code_analysis_finished()
 
463
        
 
464
    def pep8_analysis_finished(self, results):
 
465
        """Pep8 code analysis thread has finished"""
 
466
        self.pep8_results = results
 
467
        if self.pyflakes_results is not None:
 
468
            self.code_analysis_finished()
 
469
        
 
470
    def code_analysis_finished(self):
 
471
        """Code analysis thread has finished"""
 
472
        self.set_analysis_results(self.pyflakes_results+self.pep8_results)
 
473
        self.emit(SIGNAL('analysis_results_changed()'))
 
474
        
 
475
    def set_analysis_results(self, results):
 
476
        """Set analysis results and update warning markers in editor"""
 
477
        self.analysis_results = results
 
478
        self.editor.process_code_analysis(results)
 
479
        
 
480
    def cleanup_analysis_results(self):
 
481
        """Clean-up analysis results"""
 
482
        self.analysis_results = []
 
483
        self.editor.cleanup_code_analysis()
 
484
            
 
485
    def run_todo_finder(self):
 
486
        """Run TODO finder"""
 
487
        if self.editor.is_python():
 
488
            self.threadmanager.add_thread(codeanalysis.find_tasks,
 
489
                                          self.todo_finished,
 
490
                                          self.get_source_code(), self)
 
491
        
 
492
    def todo_finished(self, results):
 
493
        """Code analysis thread has finished"""
 
494
        self.set_todo_results(results)
 
495
        self.emit(SIGNAL('todo_results_changed()'))
 
496
        
 
497
    def set_todo_results(self, results):
 
498
        """Set TODO results and update markers in editor"""
 
499
        self.todo_results = results
 
500
        self.editor.process_todo(results)
 
501
        
 
502
    def cleanup_todo_results(self):
 
503
        """Clean-up TODO finder results"""
 
504
        self.todo_results = []
 
505
        
 
506
    def breakpoints_changed(self):
 
507
        """Breakpoint list has changed"""
 
508
        breakpoints = self.editor.get_breakpoints()
 
509
        if self.editor.breakpoints != breakpoints:
 
510
            self.editor.breakpoints = breakpoints
 
511
            self.emit(SIGNAL("save_breakpoints(QString,QString)"),
 
512
                      self.filename, repr(breakpoints))
 
513
        
 
514
 
 
515
class EditorStack(QWidget):
 
516
    def __init__(self, parent, actions):
 
517
        QWidget.__init__(self, parent)
 
518
        
 
519
        self.setAttribute(Qt.WA_DeleteOnClose)
 
520
        
 
521
        self.threadmanager = ThreadManager(self)
 
522
        
 
523
        self.newwindow_action = None
 
524
        self.horsplit_action = None
 
525
        self.versplit_action = None
 
526
        self.close_action = None
 
527
        self.__get_split_actions()
 
528
        
 
529
        layout = QVBoxLayout()
 
530
        layout.setContentsMargins(0, 0, 0, 0)
 
531
        self.setLayout(layout)
 
532
 
 
533
        self.menu = None
 
534
        self.filelist_dlg = None
 
535
#        self.filelist_btn = None
 
536
#        self.previous_btn = None
 
537
#        self.next_btn = None
 
538
        self.tabs = None
 
539
 
 
540
        self.stack_history = []
 
541
        
 
542
        self.setup_editorstack(parent, layout)
 
543
 
 
544
        self.find_widget = None
 
545
 
 
546
        self.data = []
 
547
        
 
548
        filelist_action = create_action(self, _("File list management"),
 
549
                                 icon=get_icon('filelist.png'),
 
550
                                 triggered=self.open_filelistdialog)
 
551
        copy_to_cb_action = create_action(self, _("Copy path to clipboard"),
 
552
                icon="editcopy.png",
 
553
                triggered=lambda:
 
554
                QApplication.clipboard().setText(self.get_current_filename()))
 
555
        self.menu_actions = actions+[None, filelist_action, copy_to_cb_action]
 
556
        self.outlineexplorer = None
 
557
        self.inspector = None
 
558
        self.unregister_callback = None
 
559
        self.is_closable = False
 
560
        self.new_action = None
 
561
        self.open_action = None
 
562
        self.save_action = None
 
563
        self.revert_action = None
 
564
        self.tempfile_path = None
 
565
        self.title = _("Editor")
 
566
        self.pyflakes_enabled = True
 
567
        self.pep8_enabled = False
 
568
        self.todolist_enabled = True
 
569
        self.realtime_analysis_enabled = False
 
570
        self.is_analysis_done = False
 
571
        self.linenumbers_enabled = True
 
572
        self.edgeline_enabled = True
 
573
        self.edgeline_column = 79
 
574
        self.outlineexplorer_enabled = True
 
575
        self.codecompletion_auto_enabled = True
 
576
        self.codecompletion_case_enabled = False
 
577
        self.codecompletion_single_enabled = False
 
578
        self.codecompletion_enter_enabled = False
 
579
        self.calltips_enabled = True
 
580
        self.go_to_definition_enabled = True
 
581
        self.close_parentheses_enabled = True
 
582
        self.close_quotes_enabled = True
 
583
        self.add_colons_enabled = True
 
584
        self.auto_unindent_enabled = True
 
585
        self.indent_chars = " "*4
 
586
        self.tab_stop_width = 40
 
587
        self.inspector_enabled = False
 
588
        self.default_font = None
 
589
        self.wrap_enabled = False
 
590
        self.tabmode_enabled = False
 
591
        self.intelligent_backspace_enabled = True
 
592
        self.highlight_current_line_enabled = False
 
593
        self.occurence_highlighting_enabled = True
 
594
        self.checkeolchars_enabled = True
 
595
        self.always_remove_trailing_spaces = False
 
596
        self.fullpath_sorting_enabled = None
 
597
        self.set_fullpath_sorting_enabled(False)
 
598
        ccs = 'Spyder'
 
599
        if ccs not in syntaxhighlighters.COLOR_SCHEME_NAMES:
 
600
            ccs = syntaxhighlighters.COLOR_SCHEME_NAMES[0]
 
601
        self.color_scheme = ccs
 
602
        
 
603
        self.__file_status_flag = False
 
604
        
 
605
        # Real-time code analysis
 
606
        self.analysis_timer = QTimer(self)
 
607
        self.analysis_timer.setSingleShot(True)
 
608
        self.analysis_timer.setInterval(2000)
 
609
        self.connect(self.analysis_timer, SIGNAL("timeout()"), 
 
610
                     self.analyze_script)
 
611
        
 
612
        # Accepting drops
 
613
        self.setAcceptDrops(True)
 
614
 
 
615
        # Local shortcuts
 
616
        self.inspectsc = QShortcut(QKeySequence("Ctrl+I"), self,
 
617
                                   self.inspect_current_object)
 
618
        self.inspectsc.setContext(Qt.WidgetWithChildrenShortcut)
 
619
        self.breakpointsc = QShortcut(QKeySequence("F12"), self,
 
620
                                      self.set_or_clear_breakpoint)
 
621
        self.breakpointsc.setContext(Qt.WidgetWithChildrenShortcut)
 
622
        self.cbreakpointsc = QShortcut(QKeySequence("Shift+F12"), self,
 
623
                                       self.set_or_edit_conditional_breakpoint)
 
624
        self.cbreakpointsc.setContext(Qt.WidgetWithChildrenShortcut)
 
625
        self.gotolinesc = QShortcut(QKeySequence("Ctrl+L"), self,
 
626
                                    self.go_to_line)
 
627
        self.gotolinesc.setContext(Qt.WidgetWithChildrenShortcut)
 
628
        self.filelistsc = QShortcut(QKeySequence("Ctrl+E"), self,
 
629
                                    self.open_filelistdialog)
 
630
        self.filelistsc.setContext(Qt.WidgetWithChildrenShortcut)
 
631
        self.tabsc = QShortcut(QKeySequence("Ctrl+Tab"), self,
 
632
                               self.go_to_previous_file)
 
633
        self.tabsc.setContext(Qt.WidgetWithChildrenShortcut)
 
634
        self.closesc = QShortcut(QKeySequence("Ctrl+F4"), self,
 
635
                                 self.close_file)
 
636
        self.closesc.setContext(Qt.WidgetWithChildrenShortcut)
 
637
        self.tabshiftsc = QShortcut(QKeySequence("Ctrl+Shift+Tab"), self,
 
638
                                    self.go_to_next_file)
 
639
        self.tabshiftsc.setContext(Qt.WidgetWithChildrenShortcut)
 
640
        
 
641
    def get_shortcut_data(self):
 
642
        """
 
643
        Returns shortcut data, a list of tuples (shortcut, text, default)
 
644
        shortcut (QShortcut or QAction instance)
 
645
        text (string): action/shortcut description
 
646
        default (string): default key sequence
 
647
        """
 
648
        return [
 
649
                (self.inspectsc, "Inspect current object", "Ctrl+I"),
 
650
                (self.breakpointsc, "Breakpoint", "F12"),
 
651
                (self.cbreakpointsc, "Conditional breakpoint", "Shift+F12"),
 
652
                (self.gotolinesc, "Go to line", "Ctrl+L"),
 
653
                (self.filelistsc, "File list management", "Ctrl+E"),
 
654
                ]
 
655
        
 
656
    def setup_editorstack(self, parent, layout):
 
657
        """Setup editorstack's layout"""
 
658
        menu_btn = create_toolbutton(self, icon=get_icon("tooloptions.png"),
 
659
                                     tip=_("Options"))
 
660
        self.menu = QMenu(self)
 
661
        menu_btn.setMenu(self.menu)
 
662
        menu_btn.setPopupMode(menu_btn.InstantPopup)
 
663
        self.connect(self.menu, SIGNAL("aboutToShow()"), self.__setup_menu)
 
664
 
 
665
#        self.filelist_btn = create_toolbutton(self,
 
666
#                             icon=get_icon('filelist.png'),
 
667
#                             tip=_("File list management"),
 
668
#                             triggered=self.open_filelistdialog)
 
669
#        
 
670
#        self.previous_btn = create_toolbutton(self,
 
671
#                             icon=get_icon('previous.png'),
 
672
#                             tip=_("Previous file"),
 
673
#                             triggered=self.go_to_previous_file)
 
674
#        
 
675
#        self.next_btn = create_toolbutton(self,
 
676
#                             icon=get_icon('next.png'),
 
677
#                             tip=_("Next file"),
 
678
#                             triggered=self.go_to_next_file)
 
679
                
 
680
        # Optional tabs
 
681
#        corner_widgets = {Qt.TopRightCorner: [self.previous_btn,
 
682
#                                              self.filelist_btn, self.next_btn,
 
683
#                                              5, menu_btn]}
 
684
        corner_widgets = {Qt.TopRightCorner: [menu_btn]}
 
685
        self.tabs = BaseTabs(self, menu=self.menu, menu_use_tooltips=True,
 
686
                             corner_widgets=corner_widgets)
 
687
        self.tabs.set_close_function(self.close_file)
 
688
        if hasattr(self.tabs, 'setDocumentMode') \
 
689
           and not sys.platform == 'darwin':
 
690
            # Don't set document mode to true on OSX because it generates
 
691
            # a crash when the editor is detached from the main window
 
692
            # Fixes Issue 561
 
693
            self.tabs.setDocumentMode(True)
 
694
        self.connect(self.tabs, SIGNAL('currentChanged(int)'),
 
695
                     self.current_changed)
 
696
        layout.addWidget(self.tabs)
 
697
        
 
698
    def add_corner_widgets_to_tabbar(self, widgets):
 
699
        self.tabs.add_corner_widgets(widgets)
 
700
        
 
701
    def closeEvent(self, event):
 
702
        self.threadmanager.close_all_threads()
 
703
        self.disconnect(self.analysis_timer, SIGNAL("timeout()"),
 
704
                        self.analyze_script)
 
705
        QWidget.closeEvent(self, event)
 
706
        if is_pyqt46:
 
707
            self.emit(SIGNAL('destroyed()'))        
 
708
    
 
709
    def clone_editor_from(self, other_finfo, set_current):
 
710
        fname = other_finfo.filename
 
711
        enc = other_finfo.encoding
 
712
        new = other_finfo.newly_created
 
713
        finfo = self.create_new_editor(fname, enc, "",
 
714
                                       set_current=set_current, new=new,
 
715
                                       cloned_from=other_finfo.editor)
 
716
        finfo.set_analysis_results(other_finfo.analysis_results)
 
717
        finfo.set_todo_results(other_finfo.todo_results)
 
718
        return finfo.editor
 
719
            
 
720
    def clone_from(self, other):
 
721
        """Clone EditorStack from other instance"""
 
722
        for other_finfo in other.data:
 
723
            self.clone_editor_from(other_finfo, set_current=True)
 
724
        self.set_stack_index(other.get_stack_index())
 
725
        
 
726
    def open_filelistdialog(self):
 
727
        """Open file list management dialog box"""
 
728
        self.filelist_dlg = dlg = FileListDialog(self, self.tabs,
 
729
                                                 self.fullpath_sorting_enabled)
 
730
        self.connect(dlg, SIGNAL("edit_file(int)"), self.set_stack_index)
 
731
        self.connect(dlg, SIGNAL("close_file(int)"), self.close_file)
 
732
        dlg.synchronize(self.get_stack_index())
 
733
        dlg.exec_()
 
734
        self.filelist_dlg = None
 
735
        
 
736
    def update_filelistdialog(self):
 
737
        """Synchronize file list dialog box with editor widget tabs"""
 
738
        if self.filelist_dlg is not None:
 
739
            self.filelist_dlg.synchronize(self.get_stack_index())
 
740
            
 
741
    def go_to_line(self):
 
742
        """Go to line dialog"""
 
743
        if self.data:
 
744
            self.get_current_editor().exec_gotolinedialog()
 
745
                
 
746
    def set_or_clear_breakpoint(self):
 
747
        """Set/clear breakpoint"""
 
748
        if self.data:
 
749
            editor = self.get_current_editor()
 
750
            editor.add_remove_breakpoint()
 
751
            
 
752
    def set_or_edit_conditional_breakpoint(self):
 
753
        """Set conditional breakpoint"""
 
754
        if self.data:
 
755
            editor = self.get_current_editor()
 
756
            editor.add_remove_breakpoint(edit_condition=True)
 
757
            
 
758
    def inspect_current_object(self):
 
759
        """Inspect current object in Object Inspector"""
 
760
        if programs.is_module_installed('rope'):
 
761
            editor = self.get_current_editor()
 
762
            position = editor.get_position('cursor')
 
763
            finfo = self.get_current_finfo()
 
764
            finfo.trigger_calltip_and_doc_rendering(position, auto=False)
 
765
        else:
 
766
            text = self.get_current_editor().get_current_object()
 
767
            if text:
 
768
                self.send_to_inspector(text, force=True)
 
769
        
 
770
        
 
771
    #------ Editor Widget Settings
 
772
    def set_closable(self, state):
 
773
        """Parent widget must handle the closable state"""
 
774
        self.is_closable = state
 
775
        
 
776
    def set_io_actions(self, new_action, open_action,
 
777
                       save_action, revert_action):
 
778
        self.new_action = new_action
 
779
        self.open_action = open_action
 
780
        self.save_action = save_action
 
781
        self.revert_action = revert_action
 
782
        
 
783
    def set_find_widget(self, find_widget):
 
784
        self.find_widget = find_widget
 
785
        
 
786
    def set_outlineexplorer(self, outlineexplorer):
 
787
        self.outlineexplorer = outlineexplorer
 
788
        self.outlineexplorer_enabled = True
 
789
        self.connect(self.outlineexplorer,
 
790
                     SIGNAL("outlineexplorer_is_visible()"),
 
791
                     self._refresh_outlineexplorer)
 
792
        
 
793
    def add_outlineexplorer_button(self, editor_plugin):
 
794
        oe_btn = create_toolbutton(editor_plugin)
 
795
        oe_btn.setDefaultAction(self.outlineexplorer.visibility_action)
 
796
        self.add_corner_widgets_to_tabbar([5, oe_btn])
 
797
        
 
798
    def set_inspector(self, inspector):
 
799
        self.inspector = inspector
 
800
        
 
801
    def set_tempfile_path(self, path):
 
802
        self.tempfile_path = path
 
803
        
 
804
    def set_title(self, text):
 
805
        self.title = text
 
806
        
 
807
    def __update_editor_margins(self, editor):
 
808
        editor.setup_margins(linenumbers=self.linenumbers_enabled,
 
809
                             markers=self.has_markers())
 
810
        
 
811
    def __codeanalysis_settings_changed(self, current_finfo):
 
812
        if self.data:
 
813
            run_pyflakes, run_pep8 = self.pyflakes_enabled, self.pep8_enabled
 
814
            for finfo in self.data:
 
815
                self.__update_editor_margins(finfo.editor)
 
816
                finfo.cleanup_analysis_results()
 
817
                if (run_pyflakes or run_pep8) and current_finfo is not None:
 
818
                    if current_finfo is not finfo:
 
819
                        finfo.run_code_analysis(run_pyflakes, run_pep8)
 
820
        
 
821
    def set_pyflakes_enabled(self, state, current_finfo=None):
 
822
        # CONF.get(self.CONF_SECTION, 'code_analysis/pyflakes')
 
823
        self.pyflakes_enabled = state
 
824
        self.__codeanalysis_settings_changed(current_finfo)
 
825
        
 
826
    def set_pep8_enabled(self, state, current_finfo=None):
 
827
        # CONF.get(self.CONF_SECTION, 'code_analysis/pep8')
 
828
        self.pep8_enabled = state
 
829
        self.__codeanalysis_settings_changed(current_finfo)
 
830
    
 
831
    def has_markers(self):
 
832
        """Return True if this editorstack has a marker margin for TODOs or
 
833
        code analysis"""
 
834
        return self.todolist_enabled or self.pyflakes_enabled\
 
835
               or self.pep8_enabled
 
836
    
 
837
    def set_todolist_enabled(self, state, current_finfo=None):
 
838
        # CONF.get(self.CONF_SECTION, 'todo_list')
 
839
        self.todolist_enabled = state
 
840
        if self.data:
 
841
            for finfo in self.data:
 
842
                self.__update_editor_margins(finfo.editor)
 
843
                finfo.cleanup_todo_results()
 
844
                if state and current_finfo is not None:
 
845
                    if current_finfo is not finfo:
 
846
                        finfo.run_todo_finder()
 
847
                        
 
848
    def set_realtime_analysis_enabled(self, state):
 
849
        self.realtime_analysis_enabled = state
 
850
 
 
851
    def set_realtime_analysis_timeout(self, timeout):
 
852
        self.analysis_timer.setInterval(timeout)
 
853
    
 
854
    def set_linenumbers_enabled(self, state, current_finfo=None):
 
855
        # CONF.get(self.CONF_SECTION, 'line_numbers')
 
856
        self.linenumbers_enabled = state
 
857
        if self.data:
 
858
            for finfo in self.data:
 
859
                self.__update_editor_margins(finfo.editor)
 
860
        
 
861
    def set_edgeline_enabled(self, state):
 
862
        # CONF.get(self.CONF_SECTION, 'edge_line')
 
863
        self.edgeline_enabled = state
 
864
        if self.data:
 
865
            for finfo in self.data:
 
866
                finfo.editor.set_edge_line_enabled(state)
 
867
        
 
868
    def set_edgeline_column(self, column):
 
869
        # CONF.get(self.CONF_SECTION, 'edge_line_column')
 
870
        self.edgeline_column = column
 
871
        if self.data:
 
872
            for finfo in self.data:
 
873
                finfo.editor.set_edge_line_column(column)
 
874
        
 
875
    def set_codecompletion_auto_enabled(self, state):
 
876
        # CONF.get(self.CONF_SECTION, 'codecompletion_auto')
 
877
        self.codecompletion_auto_enabled = state
 
878
        if self.data:
 
879
            for finfo in self.data:
 
880
                finfo.editor.set_codecompletion_auto(state)
 
881
                
 
882
    def set_codecompletion_case_enabled(self, state):
 
883
        self.codecompletion_case_enabled = state
 
884
        if self.data:
 
885
            for finfo in self.data:
 
886
                finfo.editor.set_codecompletion_case(state)
 
887
                
 
888
    def set_codecompletion_single_enabled(self, state):
 
889
        self.codecompletion_single_enabled = state
 
890
        if self.data:
 
891
            for finfo in self.data:
 
892
                finfo.editor.set_codecompletion_single(state)
 
893
                    
 
894
    def set_codecompletion_enter_enabled(self, state):
 
895
        self.codecompletion_enter_enabled = state
 
896
        if self.data:
 
897
            for finfo in self.data:
 
898
                finfo.editor.set_codecompletion_enter(state)
 
899
                
 
900
    def set_calltips_enabled(self, state):
 
901
        # CONF.get(self.CONF_SECTION, 'calltips')
 
902
        self.calltips_enabled = state
 
903
        if self.data:
 
904
            for finfo in self.data:
 
905
                finfo.editor.set_calltips(state)
 
906
                
 
907
    def set_go_to_definition_enabled(self, state):
 
908
        # CONF.get(self.CONF_SECTION, 'go_to_definition')
 
909
        self.go_to_definition_enabled = state
 
910
        if self.data:
 
911
            for finfo in self.data:
 
912
                finfo.editor.set_go_to_definition_enabled(state)
 
913
                
 
914
    def set_close_parentheses_enabled(self, state):
 
915
        # CONF.get(self.CONF_SECTION, 'close_parentheses')
 
916
        self.close_parentheses_enabled = state
 
917
        if self.data:
 
918
            for finfo in self.data:
 
919
                finfo.editor.set_close_parentheses_enabled(state)
 
920
                
 
921
    def set_close_quotes_enabled(self, state):
 
922
        # CONF.get(self.CONF_SECTION, 'close_quotes')
 
923
        self.close_quotes_enabled = state
 
924
        if self.data:
 
925
            for finfo in self.data:
 
926
                finfo.editor.set_close_quotes_enabled(state)
 
927
    
 
928
    def set_add_colons_enabled(self, state):
 
929
        # CONF.get(self.CONF_SECTION, 'add_colons')
 
930
        self.add_colons_enabled = state
 
931
        if self.data:
 
932
            for finfo in self.data:
 
933
                finfo.editor.set_add_colons_enabled(state)
 
934
    
 
935
    def set_auto_unindent_enabled(self, state):
 
936
        # CONF.get(self.CONF_SECTION, 'auto_unindent')
 
937
        self.auto_unindent_enabled = state
 
938
        if self.data:
 
939
            for finfo in self.data:
 
940
                finfo.editor.set_auto_unindent_enabled(state)
 
941
                
 
942
    def set_indent_chars(self, indent_chars):
 
943
        # CONF.get(self.CONF_SECTION, 'indent_chars')
 
944
        indent_chars = indent_chars[1:-1] # removing the leading/ending '*'
 
945
        self.indent_chars = indent_chars
 
946
        if self.data:
 
947
            for finfo in self.data:
 
948
                finfo.editor.set_indent_chars(indent_chars)
 
949
                
 
950
    def set_tab_stop_width(self, tab_stop_width):
 
951
        # CONF.get(self.CONF_SECTION, 'tab_stop_width')
 
952
        self.tab_stop_width = tab_stop_width
 
953
        if self.data:
 
954
            for finfo in self.data:
 
955
                finfo.editor.setTabStopWidth(tab_stop_width)
 
956
                
 
957
    def set_inspector_enabled(self, state):
 
958
        self.inspector_enabled = state
 
959
        
 
960
    def set_outlineexplorer_enabled(self, state):
 
961
        # CONF.get(self.CONF_SECTION, 'outline_explorer')
 
962
        self.outlineexplorer_enabled = state
 
963
        
 
964
    def set_default_font(self, font, color_scheme=None):
 
965
        # get_font(self.CONF_SECTION)
 
966
        self.default_font = font
 
967
        if color_scheme is not None:
 
968
            self.color_scheme = color_scheme
 
969
        if self.data:
 
970
            for finfo in self.data:
 
971
                finfo.editor.set_font(font, color_scheme)
 
972
            
 
973
    def set_color_scheme(self, color_scheme):
 
974
        self.color_scheme = color_scheme
 
975
        if self.data:
 
976
            for finfo in self.data:
 
977
                finfo.editor.set_color_scheme(color_scheme)
 
978
        
 
979
    def set_wrap_enabled(self, state):
 
980
        # CONF.get(self.CONF_SECTION, 'wrap')
 
981
        self.wrap_enabled = state
 
982
        if self.data:
 
983
            for finfo in self.data:
 
984
                finfo.editor.toggle_wrap_mode(state)
 
985
        
 
986
    def set_tabmode_enabled(self, state):
 
987
        # CONF.get(self.CONF_SECTION, 'tab_always_indent')
 
988
        self.tabmode_enabled = state
 
989
        if self.data:
 
990
            for finfo in self.data:
 
991
                finfo.editor.set_tab_mode(state)
 
992
                
 
993
    def set_intelligent_backspace_enabled(self, state):
 
994
        # CONF.get(self.CONF_SECTION, 'intelligent_backspace')
 
995
        self.intelligent_backspace_enabled = state
 
996
        if self.data:
 
997
            for finfo in self.data:
 
998
                finfo.editor.toggle_intelligent_backspace(state)
 
999
        
 
1000
    def set_occurence_highlighting_enabled(self, state):
 
1001
        # CONF.get(self.CONF_SECTION, 'occurence_highlighting')
 
1002
        self.occurence_highlighting_enabled = state
 
1003
        if self.data:
 
1004
            for finfo in self.data:
 
1005
                finfo.editor.set_occurence_highlighting(state)
 
1006
                
 
1007
    def set_occurence_highlighting_timeout(self, timeout):
 
1008
        # CONF.get(self.CONF_SECTION, 'occurence_highlighting/timeout')
 
1009
        self.occurence_highlighting_timeout = timeout
 
1010
        if self.data:
 
1011
            for finfo in self.data:
 
1012
                finfo.editor.set_occurence_timeout(timeout)
 
1013
                
 
1014
    def set_highlight_current_line_enabled(self, state):
 
1015
        self.highlight_current_line_enabled = state
 
1016
        if self.data:
 
1017
            for finfo in self.data:
 
1018
                finfo.editor.set_highlight_current_line(state)
 
1019
        
 
1020
    def set_checkeolchars_enabled(self, state):
 
1021
        # CONF.get(self.CONF_SECTION, 'check_eol_chars')
 
1022
        self.checkeolchars_enabled = state
 
1023
        
 
1024
    def set_fullpath_sorting_enabled(self, state):
 
1025
        # CONF.get(self.CONF_SECTION, 'fullpath_sorting')
 
1026
        self.fullpath_sorting_enabled = state
 
1027
        if self.data:
 
1028
            finfo = self.data[self.get_stack_index()]
 
1029
            self.data.sort(key=self.__get_sorting_func())
 
1030
            new_index = self.data.index(finfo)
 
1031
            self.__repopulate_stack()
 
1032
            self.set_stack_index(new_index)
 
1033
        
 
1034
    def set_always_remove_trailing_spaces(self, state):
 
1035
        # CONF.get(self.CONF_SECTION, 'always_remove_trailing_spaces')
 
1036
        self.always_remove_trailing_spaces = state
 
1037
            
 
1038
    
 
1039
    #------ Stacked widget management
 
1040
    def get_stack_index(self):
 
1041
        return self.tabs.currentIndex()
 
1042
    
 
1043
    def get_current_finfo(self):
 
1044
        if self.data:
 
1045
            return self.data[self.get_stack_index()]
 
1046
    
 
1047
    def get_current_editor(self):
 
1048
        return self.tabs.currentWidget()
 
1049
    
 
1050
    def get_stack_count(self):
 
1051
        return self.tabs.count()
 
1052
    
 
1053
    def set_stack_index(self, index):
 
1054
        self.tabs.setCurrentIndex(index)
 
1055
            
 
1056
    def set_tabbar_visible(self, state):
 
1057
        self.tabs.tabBar().setVisible(state)
 
1058
    
 
1059
    def remove_from_data(self, index):
 
1060
        self.tabs.blockSignals(True)
 
1061
        self.tabs.removeTab(index)
 
1062
        self.data.pop(index)
 
1063
        self.tabs.blockSignals(False)
 
1064
        self.update_actions()
 
1065
        self.update_filelistdialog()
 
1066
    
 
1067
    def __modified_readonly_title(self, title, is_modified, is_readonly):
 
1068
        if is_modified is not None and is_modified:
 
1069
            title += "*"
 
1070
        if is_readonly is not None and is_readonly:
 
1071
            title = "(%s)" % title
 
1072
        return title
 
1073
    
 
1074
    def get_tab_text(self, filename, is_modified=None, is_readonly=None):
 
1075
        """Return tab title"""
 
1076
        return self.__modified_readonly_title(osp.basename(filename),
 
1077
                                              is_modified, is_readonly)
 
1078
                
 
1079
    def get_tab_tip(self, filename, is_modified=None, is_readonly=None):
 
1080
        """Return tab menu title"""
 
1081
        if self.fullpath_sorting_enabled:
 
1082
            text = filename
 
1083
        else:
 
1084
            text = u"%s — %s"
 
1085
        text = self.__modified_readonly_title(text,
 
1086
                                              is_modified, is_readonly)
 
1087
        if self.tempfile_path is not None\
 
1088
           and filename == encoding.to_unicode_from_fs(self.tempfile_path):
 
1089
            temp_file_str = unicode(_("Temporary file"))
 
1090
            if self.fullpath_sorting_enabled:
 
1091
                return "%s (%s)" % (text, temp_file_str)
 
1092
            else:
 
1093
                return text % (temp_file_str, self.tempfile_path)
 
1094
        else:
 
1095
            if self.fullpath_sorting_enabled:
 
1096
                return text
 
1097
            else:
 
1098
                return text % (osp.basename(filename), osp.dirname(filename))
 
1099
        
 
1100
    def __get_sorting_func(self):
 
1101
        if self.fullpath_sorting_enabled:
 
1102
            return lambda item: osp.join(osp.dirname(item.filename),
 
1103
                                         '_'+osp.basename(item.filename))
 
1104
        else:
 
1105
            return lambda item: osp.basename(item.filename)
 
1106
    
 
1107
    def add_to_data(self, finfo, set_current):
 
1108
        self.data.append(finfo)
 
1109
        self.data.sort(key=self.__get_sorting_func())
 
1110
        index = self.data.index(finfo)
 
1111
        fname, editor = finfo.filename, finfo.editor
 
1112
        self.tabs.insertTab(index, editor, get_filetype_icon(fname),
 
1113
                            self.get_tab_text(fname))
 
1114
        self.set_stack_title(index, False)
 
1115
        if set_current:
 
1116
            self.set_stack_index(index)
 
1117
            self.current_changed(index)
 
1118
        self.update_actions()
 
1119
        self.update_filelistdialog()
 
1120
        
 
1121
    def __repopulate_stack(self):
 
1122
        self.tabs.blockSignals(True)
 
1123
        self.tabs.clear()
 
1124
        for finfo in self.data:
 
1125
            icon = get_filetype_icon(finfo.filename)
 
1126
            if finfo.newly_created:
 
1127
                is_modified = True
 
1128
            else:
 
1129
                is_modified = None
 
1130
            tab_text = self.get_tab_text(finfo.filename, is_modified)
 
1131
            tab_tip = self.get_tab_tip(finfo.filename)
 
1132
            index = self.tabs.addTab(finfo.editor, icon, tab_text)
 
1133
            self.tabs.setTabToolTip(index, tab_tip)
 
1134
        self.tabs.blockSignals(False)
 
1135
        self.update_filelistdialog()
 
1136
        
 
1137
    def rename_in_data(self, index, new_filename):
 
1138
        finfo = self.data[index]
 
1139
        if osp.splitext(finfo.filename)[1] != osp.splitext(new_filename)[1]:
 
1140
            # File type has changed!
 
1141
            txt = unicode(finfo.editor.get_text_with_eol())
 
1142
            language = get_file_language(new_filename, txt)
 
1143
            finfo.editor.set_language(language)
 
1144
        set_new_index = index == self.get_stack_index()
 
1145
        finfo.filename = new_filename
 
1146
        self.data.sort(key=self.__get_sorting_func())
 
1147
        new_index = self.data.index(finfo)
 
1148
        self.__repopulate_stack()
 
1149
        if set_new_index:
 
1150
            self.set_stack_index(new_index)
 
1151
        if self.outlineexplorer is not None:
 
1152
            self.outlineexplorer.file_renamed(finfo.editor, finfo.filename)
 
1153
        return new_index
 
1154
        
 
1155
    def set_stack_title(self, index, is_modified):
 
1156
        finfo = self.data[index]
 
1157
        fname = finfo.filename
 
1158
        is_modified = is_modified or finfo.newly_created
 
1159
        is_readonly = finfo.editor.isReadOnly()
 
1160
        tab_text = self.get_tab_text(fname, is_modified, is_readonly)
 
1161
        tab_tip = self.get_tab_tip(fname, is_modified, is_readonly)
 
1162
        self.tabs.setTabText(index, tab_text)
 
1163
        self.tabs.setTabToolTip(index, tab_tip)
 
1164
        
 
1165
        
 
1166
    #------ Context menu
 
1167
    def __setup_menu(self):
 
1168
        """Setup tab context menu before showing it"""
 
1169
        self.menu.clear()
 
1170
        if self.data:
 
1171
            actions = self.menu_actions
 
1172
        else:
 
1173
            actions = (self.new_action, self.open_action)
 
1174
            self.setFocus() # --> Editor.__get_focus_editortabwidget
 
1175
        add_actions(self.menu, list(actions)+self.__get_split_actions())
 
1176
        self.close_action.setEnabled(self.is_closable)
 
1177
 
 
1178
 
 
1179
    #------ Hor/Ver splitting
 
1180
    def __get_split_actions(self):
 
1181
        # New window
 
1182
        self.newwindow_action = create_action(self, _("New window"),
 
1183
                icon="newwindow.png", tip=_("Create a new editor window"),
 
1184
                triggered=lambda: self.emit(SIGNAL("create_new_window()")))
 
1185
        # Splitting
 
1186
        self.versplit_action = create_action(self, _("Split vertically"),
 
1187
                icon="versplit.png",
 
1188
                tip=_("Split vertically this editor window"),
 
1189
                triggered=lambda: self.emit(SIGNAL("split_vertically()")))
 
1190
        self.horsplit_action = create_action(self, _("Split horizontally"),
 
1191
                icon="horsplit.png",
 
1192
                tip=_("Split horizontally this editor window"),
 
1193
                triggered=lambda: self.emit(SIGNAL("split_horizontally()")))
 
1194
        self.close_action = create_action(self, _("Close this panel"),
 
1195
                icon="close_panel.png", triggered=self.close)
 
1196
        return [None, self.newwindow_action, None, 
 
1197
                self.versplit_action, self.horsplit_action, self.close_action]
 
1198
        
 
1199
    def reset_orientation(self):
 
1200
        self.horsplit_action.setEnabled(True)
 
1201
        self.versplit_action.setEnabled(True)
 
1202
        
 
1203
    def set_orientation(self, orientation):
 
1204
        self.horsplit_action.setEnabled(orientation == Qt.Horizontal)
 
1205
        self.versplit_action.setEnabled(orientation == Qt.Vertical)
 
1206
        
 
1207
    def update_actions(self):
 
1208
        state = self.get_stack_count() > 0
 
1209
        self.horsplit_action.setEnabled(state)
 
1210
        self.versplit_action.setEnabled(state)
 
1211
    
 
1212
    
 
1213
    #------ Accessors
 
1214
    def get_current_filename(self):
 
1215
        if self.data:
 
1216
            return self.data[self.get_stack_index()].filename
 
1217
        
 
1218
    def has_filename(self, filename):
 
1219
        fixpath = lambda path: osp.normcase(osp.realpath(path))
 
1220
        for index, finfo in enumerate(self.data):
 
1221
            if fixpath(filename) == fixpath(finfo.filename):
 
1222
                return index
 
1223
        
 
1224
    def set_current_filename(self, filename):
 
1225
        """Set current filename and return the associated editor instance"""
 
1226
        index = self.has_filename(filename)
 
1227
        if index is not None:
 
1228
            self.set_stack_index(index)
 
1229
            editor = self.data[index].editor
 
1230
            editor.setFocus()
 
1231
            return editor
 
1232
        
 
1233
    def is_file_opened(self, filename=None):
 
1234
        if filename is None:
 
1235
            # Is there any file opened?
 
1236
            return len(self.data) > 0
 
1237
        else:
 
1238
            return self.has_filename(filename)
 
1239
 
 
1240
        
 
1241
    #------ Close file, tabwidget...
 
1242
    def close_file(self, index=None, force=False):
 
1243
        """Close file (index=None -> close current file)
 
1244
        Keep current file index unchanged (if current file 
 
1245
        that is being closed)"""
 
1246
        current_index = self.get_stack_index()
 
1247
        count = self.get_stack_count()
 
1248
        if index is None:
 
1249
            if count > 0:
 
1250
                index = current_index
 
1251
            else:
 
1252
                self.find_widget.set_editor(None)
 
1253
                return
 
1254
        new_index = None
 
1255
        if count > 1:
 
1256
            if current_index == index:
 
1257
                new_index = self._get_previous_file_index()
 
1258
            else:
 
1259
                new_index = current_index
 
1260
        is_ok = force or self.save_if_changed(cancelable=True, index=index)
 
1261
        if is_ok:
 
1262
            finfo = self.data[index]
 
1263
            self.threadmanager.close_threads(finfo)
 
1264
            # Removing editor reference from outline explorer settings:
 
1265
            if self.outlineexplorer is not None:
 
1266
                self.outlineexplorer.remove_editor(finfo.editor)
 
1267
            
 
1268
            self.remove_from_data(index)
 
1269
            
 
1270
            # We pass self object ID as a QString, because otherwise it would 
 
1271
            # depend on the platform: long for 64bit, int for 32bit. Replacing 
 
1272
            # by long all the time is not working on some 32bit platforms 
 
1273
            # (see Issue 1094, Issue 1098)
 
1274
            self.emit(SIGNAL('close_file(QString,int)'), str(id(self)), index)
 
1275
            
 
1276
            if not self.data and self.is_closable:
 
1277
                # editortabwidget is empty: removing it
 
1278
                # (if it's not the first editortabwidget)
 
1279
                self.close()
 
1280
            self.emit(SIGNAL('opened_files_list_changed()'))
 
1281
            self.emit(SIGNAL('update_code_analysis_actions()'))
 
1282
            self._refresh_outlineexplorer()
 
1283
            self.emit(SIGNAL('refresh_file_dependent_actions()'))
 
1284
            self.emit(SIGNAL('update_plugin_title()'))
 
1285
            
 
1286
            if new_index is not None:
 
1287
                if index < new_index:
 
1288
                    new_index -= 1
 
1289
                self.set_stack_index(new_index)
 
1290
        return is_ok
 
1291
    
 
1292
    def close_all_files(self):
 
1293
        """Close all opened scripts"""
 
1294
        while self.close_file():
 
1295
            pass
 
1296
        
 
1297
 
 
1298
    #------ Save
 
1299
    def save_if_changed(self, cancelable=False, index=None):
 
1300
        """Ask user to save file if modified"""
 
1301
        if index is None:
 
1302
            indexes = range(self.get_stack_count())
 
1303
        else:
 
1304
            indexes = [index]
 
1305
        buttons = QMessageBox.Yes | QMessageBox.No
 
1306
        if cancelable:
 
1307
            buttons |= QMessageBox.Cancel
 
1308
        unsaved_nb = 0
 
1309
        for index in indexes:
 
1310
            if self.data[index].editor.document().isModified():
 
1311
                unsaved_nb += 1
 
1312
        if not unsaved_nb:
 
1313
            # No file to save
 
1314
            return True
 
1315
        if unsaved_nb > 1:
 
1316
            buttons |= QMessageBox.YesAll | QMessageBox.NoAll
 
1317
        yes_all = False
 
1318
        for index in indexes:
 
1319
            self.set_stack_index(index)
 
1320
            finfo = self.data[index]
 
1321
            if finfo.filename == self.tempfile_path or yes_all:
 
1322
                if not self.save():
 
1323
                    return False
 
1324
            elif finfo.editor.document().isModified():
 
1325
                answer = QMessageBox.question(self, self.title,
 
1326
                            _("<b>%s</b> has been modified."
 
1327
                              "<br>Do you want to save changes?"
 
1328
                              ) % osp.basename(finfo.filename),
 
1329
                            buttons)
 
1330
                if answer == QMessageBox.Yes:
 
1331
                    if not self.save():
 
1332
                        return False
 
1333
                elif answer == QMessageBox.YesAll:
 
1334
                    if not self.save():
 
1335
                        return False
 
1336
                    yes_all = True
 
1337
                elif answer == QMessageBox.NoAll:
 
1338
                    return True
 
1339
                elif answer == QMessageBox.Cancel:
 
1340
                    return False
 
1341
        return True
 
1342
    
 
1343
    def save(self, index=None, force=False):
 
1344
        """Save file"""
 
1345
        if index is None:
 
1346
            # Save the currently edited file
 
1347
            if not self.get_stack_count():
 
1348
                return
 
1349
            index = self.get_stack_index()
 
1350
            
 
1351
        finfo = self.data[index]
 
1352
        if not finfo.editor.document().isModified() and not force:
 
1353
            return True
 
1354
        if not osp.isfile(finfo.filename) and not force:
 
1355
            # File has not been saved yet
 
1356
            return self.save_as(index=index)
 
1357
        if self.always_remove_trailing_spaces:
 
1358
            self.remove_trailing_spaces(index)
 
1359
        txt = unicode(finfo.editor.get_text_with_eol())
 
1360
        try:
 
1361
            finfo.encoding = encoding.write(txt, finfo.filename,
 
1362
                                            finfo.encoding)
 
1363
            finfo.newly_created = False
 
1364
            self.emit(SIGNAL('encoding_changed(QString)'), finfo.encoding)
 
1365
            finfo.lastmodified = QFileInfo(finfo.filename).lastModified()
 
1366
            
 
1367
            # We pass self object ID as a QString, because otherwise it would 
 
1368
            # depend on the platform: long for 64bit, int for 32bit. Replacing 
 
1369
            # by long all the time is not working on some 32bit platforms 
 
1370
            # (see Issue 1094, Issue 1098)
 
1371
            self.emit(SIGNAL('file_saved(QString,int,QString)'),
 
1372
                      str(id(self)), index, finfo.filename)
 
1373
 
 
1374
            finfo.editor.document().setModified(False)
 
1375
            self.modification_changed(index=index)
 
1376
            self.analyze_script(index)
 
1377
            codeeditor.validate_rope_project()
 
1378
            
 
1379
            #XXX CodeEditor-only: re-scan the whole text to rebuild outline 
 
1380
            # explorer data from scratch (could be optimized because 
 
1381
            # rehighlighting text means searching for all syntax coloring 
 
1382
            # patterns instead of only searching for class/def patterns which 
 
1383
            # would be sufficient for outline explorer data.
 
1384
            finfo.editor.rehighlight()
 
1385
            
 
1386
            self._refresh_outlineexplorer(index)
 
1387
            return True
 
1388
        except EnvironmentError, error:
 
1389
            QMessageBox.critical(self, _("Save"),
 
1390
                                 _("<b>Unable to save script '%s'</b>"
 
1391
                                   "<br><br>Error message:<br>%s"
 
1392
                                   ) % (osp.basename(finfo.filename),
 
1393
                                        str(error)))
 
1394
            return False
 
1395
        
 
1396
    def file_saved_in_other_editorstack(self, index, filename):
 
1397
        """
 
1398
        File was just saved in another editorstack, let's synchronize!
 
1399
        This avoid file to be automatically reloaded
 
1400
        
 
1401
        Filename is passed in case file was just saved as another name
 
1402
        """
 
1403
        finfo = self.data[index]
 
1404
        finfo.newly_created = False
 
1405
        finfo.filename = unicode(filename)
 
1406
        finfo.lastmodified = QFileInfo(finfo.filename).lastModified()
 
1407
    
 
1408
    def select_savename(self, original_filename):
 
1409
        self.emit(SIGNAL('redirect_stdio(bool)'), False)
 
1410
        filename, _selfilter = getsavefilename(self, _("Save Python script"),
 
1411
                                   original_filename, EDIT_FILTERS)
 
1412
        self.emit(SIGNAL('redirect_stdio(bool)'), True)
 
1413
        if filename:
 
1414
            return osp.normpath(filename)
 
1415
    
 
1416
    def save_as(self, index=None):
 
1417
        """Save file as..."""
 
1418
        if index is None:
 
1419
            # Save the currently edited file
 
1420
            index = self.get_stack_index()
 
1421
        finfo = self.data[index]
 
1422
        filename = self.select_savename(finfo.filename)
 
1423
        if filename:
 
1424
            ao_index = self.has_filename(filename)
 
1425
            # Note: ao_index == index --> saving an untitled file
 
1426
            if ao_index and ao_index != index:
 
1427
                if not self.close_file(ao_index):
 
1428
                    return
 
1429
                if ao_index < index:
 
1430
                    index -= 1
 
1431
 
 
1432
            new_index = self.rename_in_data(index, new_filename=filename)
 
1433
 
 
1434
            # We pass self object ID as a QString, because otherwise it would 
 
1435
            # depend on the platform: long for 64bit, int for 32bit. Replacing 
 
1436
            # by long all the time is not working on some 32bit platforms 
 
1437
            # (see Issue 1094, Issue 1098)
 
1438
            self.emit(SIGNAL('file_renamed_in_data(QString,int,QString)'),
 
1439
                      str(id(self)), index, filename)
 
1440
 
 
1441
            ok = self.save(index=new_index, force=True)
 
1442
            self.refresh(new_index)
 
1443
            self.set_stack_index(new_index)
 
1444
            return ok
 
1445
        else:
 
1446
            return False
 
1447
        
 
1448
    def save_all(self):
 
1449
        """Save all opened files"""
 
1450
        folders = set()
 
1451
        for index in range(self.get_stack_count()):
 
1452
            if self.data[index].editor.document().isModified():
 
1453
                folders.add(osp.dirname(self.data[index].filename))
 
1454
                self.save(index)
 
1455
    
 
1456
    #------ Update UI
 
1457
    def start_stop_analysis_timer(self):
 
1458
        self.is_analysis_done = False
 
1459
        if self.realtime_analysis_enabled:
 
1460
            self.analysis_timer.stop()
 
1461
            self.analysis_timer.start()
 
1462
    
 
1463
    def analyze_script(self, index=None):
 
1464
        """Analyze current script with pyflakes + find todos"""
 
1465
        if self.is_analysis_done:
 
1466
            return
 
1467
        if index is None:
 
1468
            index = self.get_stack_index()
 
1469
        if self.data:
 
1470
            finfo = self.data[index]
 
1471
            run_pyflakes, run_pep8 = self.pyflakes_enabled, self.pep8_enabled
 
1472
            if run_pyflakes or run_pep8:
 
1473
                finfo.run_code_analysis(run_pyflakes, run_pep8)
 
1474
            if self.todolist_enabled:
 
1475
                finfo.run_todo_finder()
 
1476
        self.is_analysis_done = True
 
1477
                
 
1478
    def set_analysis_results(self, index, analysis_results):
 
1479
        """Synchronize analysis results between editorstacks"""
 
1480
        self.data[index].set_analysis_results(analysis_results)
 
1481
        
 
1482
    def get_analysis_results(self):
 
1483
        if self.data:
 
1484
            return self.data[self.get_stack_index()].analysis_results
 
1485
                
 
1486
    def set_todo_results(self, index, todo_results):
 
1487
        """Synchronize todo results between editorstacks"""
 
1488
        self.data[index].set_todo_results(todo_results)
 
1489
        
 
1490
    def get_todo_results(self):
 
1491
        if self.data:
 
1492
            return self.data[self.get_stack_index()].todo_results
 
1493
        
 
1494
    def current_changed(self, index):
 
1495
        """Stack index has changed"""
 
1496
#        count = self.get_stack_count()
 
1497
#        for btn in (self.filelist_btn, self.previous_btn, self.next_btn):
 
1498
#            btn.setEnabled(count > 1)
 
1499
        
 
1500
        editor = self.get_current_editor()
 
1501
        if index != -1:
 
1502
            editor.setFocus()
 
1503
            if DEBUG:
 
1504
                print >>STDOUT, "setfocusto:", editor
 
1505
        else:
 
1506
            self.emit(SIGNAL('reset_statusbar()'))
 
1507
        self.emit(SIGNAL('opened_files_list_changed()'))
 
1508
        
 
1509
        # Index history management
 
1510
        id_list = [id(self.tabs.widget(_i))
 
1511
                   for _i in range(self.tabs.count())]
 
1512
        for _id in self.stack_history[:]:
 
1513
            if _id not in id_list:
 
1514
                self.stack_history.pop(self.stack_history.index(_id))
 
1515
        current_id = id(self.tabs.widget(index))
 
1516
        while current_id in self.stack_history:
 
1517
            self.stack_history.pop(self.stack_history.index(current_id))
 
1518
        self.stack_history.append(current_id)
 
1519
        if DEBUG:
 
1520
            print >>STDOUT, "current_changed:", index, self.data[index].editor,
 
1521
            print >>STDOUT, self.data[index].editor.get_document_id()
 
1522
            
 
1523
        self.emit(SIGNAL('update_plugin_title()'))
 
1524
        if editor is not None:
 
1525
            self.emit(SIGNAL('current_file_changed(QString,int)'),
 
1526
                      self.data[index].filename, editor.get_position('cursor'))
 
1527
        
 
1528
    def _get_previous_file_index(self):
 
1529
        if len(self.stack_history) > 1:
 
1530
            last = len(self.stack_history)-1
 
1531
            w_id = self.stack_history.pop(last)
 
1532
            self.stack_history.insert(0, w_id)
 
1533
            last_id = self.stack_history[last]
 
1534
            for _i in range(self.tabs.count()):
 
1535
                if id(self.tabs.widget(_i)) == last_id:
 
1536
                    return _i
 
1537
        
 
1538
    def go_to_previous_file(self):
 
1539
        """Ctrl+Tab"""
 
1540
        prev_index = self._get_previous_file_index()
 
1541
        if prev_index is not None:
 
1542
            self.set_stack_index(prev_index)
 
1543
        elif len(self.stack_history) == 0 and self.get_stack_count():
 
1544
            self.stack_history = [id(self.tabs.currentWidget())]
 
1545
    
 
1546
    def go_to_next_file(self):
 
1547
        """Ctrl+Shift+Tab"""
 
1548
        if len(self.stack_history) > 1:
 
1549
            last = len(self.stack_history)-1
 
1550
            w_id = self.stack_history.pop(0)
 
1551
            self.stack_history.append(w_id)
 
1552
            last_id = self.stack_history[last]
 
1553
            for _i in range(self.tabs.count()):
 
1554
                if id(self.tabs.widget(_i)) == last_id:
 
1555
                    self.set_stack_index(_i)
 
1556
                    break
 
1557
        elif len(self.stack_history) == 0 and self.get_stack_count():
 
1558
            self.stack_history = [id(self.tabs.currentWidget())]
 
1559
    
 
1560
    def focus_changed(self):
 
1561
        """Editor focus has changed"""
 
1562
        fwidget = QApplication.focusWidget()
 
1563
        for finfo in self.data:
 
1564
            if fwidget is finfo.editor:
 
1565
                self.refresh()
 
1566
        self.emit(SIGNAL("editor_focus_changed()"))
 
1567
        
 
1568
    def _refresh_outlineexplorer(self, index=None, update=True, clear=False):
 
1569
        """Refresh outline explorer panel"""
 
1570
        oe = self.outlineexplorer
 
1571
        if oe is None:
 
1572
            return
 
1573
        if index is None:
 
1574
            index = self.get_stack_index()
 
1575
        enable = False
 
1576
        if self.data:
 
1577
            finfo = self.data[index]
 
1578
            # oe_visible: if outline explorer is not visible, maybe the whole
 
1579
            # GUI is not visible (Spyder is starting up) -> in this case,
 
1580
            # it is necessary to update the outline explorer
 
1581
            oe_visible = oe.isVisible() or not self.isVisible()
 
1582
            if self.outlineexplorer_enabled and finfo.editor.is_python() \
 
1583
               and oe_visible:
 
1584
                enable = True
 
1585
                oe.setEnabled(True)
 
1586
                oe.set_current_editor(finfo.editor, finfo.filename,
 
1587
                                      update=update, clear=clear)
 
1588
        if not enable:
 
1589
            oe.setEnabled(False)
 
1590
            
 
1591
    def __refresh_statusbar(self, index):
 
1592
        """Refreshing statusbar widgets"""
 
1593
        finfo = self.data[index]
 
1594
        self.emit(SIGNAL('encoding_changed(QString)'), finfo.encoding)
 
1595
        # Refresh cursor position status:
 
1596
        line, index = finfo.editor.get_cursor_line_column()
 
1597
        self.emit(SIGNAL('editor_cursor_position_changed(int,int)'),
 
1598
                  line, index)
 
1599
        
 
1600
    def __refresh_readonly(self, index):
 
1601
        finfo = self.data[index]
 
1602
        read_only = not QFileInfo(finfo.filename).isWritable()
 
1603
        if not osp.isfile(finfo.filename):
 
1604
            # This is an 'untitledX.py' file (newly created)
 
1605
            read_only = False
 
1606
        finfo.editor.setReadOnly(read_only)
 
1607
        self.emit(SIGNAL('readonly_changed(bool)'), read_only)
 
1608
        
 
1609
    def __check_file_status(self, index):
 
1610
        """Check if file has been changed in any way outside Spyder:
 
1611
        1. removed, moved or renamed outside Spyder
 
1612
        2. modified outside Spyder"""
 
1613
        if self.__file_status_flag:
 
1614
            # Avoid infinite loop: when the QMessageBox.question pops, it
 
1615
            # gets focus and then give it back to the CodeEditor instance,
 
1616
            # triggering a refresh cycle which calls this method
 
1617
            return
 
1618
        self.__file_status_flag = True
 
1619
 
 
1620
        finfo = self.data[index]
 
1621
        if finfo.newly_created:
 
1622
            return
 
1623
        name = osp.basename(finfo.filename)        
 
1624
        # First, testing if file still exists (removed, moved or offline):
 
1625
        if not osp.isfile(finfo.filename):
 
1626
            answer = QMessageBox.warning(self, self.title,
 
1627
                                _("<b>%s</b> is unavailable "
 
1628
                                  "(this file may have been removed, moved "
 
1629
                                  "or renamed outside Spyder)."
 
1630
                                  "<br>Do you want to close it?") % name,
 
1631
                                QMessageBox.Yes | QMessageBox.No)
 
1632
            if answer == QMessageBox.Yes:
 
1633
                self.close_file(index)
 
1634
            else:
 
1635
                finfo.newly_created = True
 
1636
                finfo.editor.document().setModified(True)
 
1637
                self.modification_changed(index=index)
 
1638
        else:
 
1639
            # Else, testing if it has been modified elsewhere:
 
1640
            lastm = QFileInfo(finfo.filename).lastModified()
 
1641
            if unicode(lastm.toString()) \
 
1642
               != unicode(finfo.lastmodified.toString()):
 
1643
                if finfo.editor.document().isModified():
 
1644
                    answer = QMessageBox.question(self,
 
1645
                                self.title,
 
1646
                                _("<b>%s</b> has been modified outside Spyder."
 
1647
                                  "<br>Do you want to reload it and loose all "
 
1648
                                  "your changes?") % name,
 
1649
                                QMessageBox.Yes | QMessageBox.No)
 
1650
                    if answer == QMessageBox.Yes:
 
1651
                        self.reload(index)
 
1652
                    else:
 
1653
                        finfo.lastmodified = lastm
 
1654
                else:
 
1655
                    self.reload(index)
 
1656
 
 
1657
        # Finally, resetting temporary flag:
 
1658
        self.__file_status_flag = False
 
1659
        
 
1660
    def refresh(self, index=None):
 
1661
        """Refresh tabwidget"""
 
1662
        if index is None:
 
1663
            index = self.get_stack_index()
 
1664
        # Set current editor
 
1665
        if self.get_stack_count():
 
1666
            index = self.get_stack_index()
 
1667
            finfo = self.data[index]
 
1668
            editor = finfo.editor
 
1669
            editor.setFocus()
 
1670
            self._refresh_outlineexplorer(index, update=False)
 
1671
            self.emit(SIGNAL('update_code_analysis_actions()'))
 
1672
            self.__refresh_statusbar(index)
 
1673
            self.__refresh_readonly(index)
 
1674
            self.__check_file_status(index)
 
1675
            self.emit(SIGNAL('update_plugin_title()'))
 
1676
        else:
 
1677
            editor = None
 
1678
        # Update the modification-state-dependent parameters
 
1679
        self.modification_changed()
 
1680
        # Update FindReplace binding
 
1681
        self.find_widget.set_editor(editor, refresh=False)
 
1682
                
 
1683
    def modification_changed(self, state=None, index=None, editor_id=None):
 
1684
        """
 
1685
        Current editor's modification state has changed
 
1686
        --> change tab title depending on new modification state
 
1687
        --> enable/disable save/save all actions
 
1688
        """
 
1689
        if editor_id is not None:
 
1690
            for index, _finfo in enumerate(self.data):
 
1691
                if id(_finfo.editor) == editor_id:
 
1692
                    break
 
1693
        # This must be done before refreshing save/save all actions:
 
1694
        # (otherwise Save/Save all actions will always be enabled)
 
1695
        self.emit(SIGNAL('opened_files_list_changed()'))
 
1696
        # --
 
1697
        if index is None:
 
1698
            index = self.get_stack_index()
 
1699
        if index == -1:
 
1700
            return
 
1701
        finfo = self.data[index]
 
1702
        if state is None:
 
1703
            state = finfo.editor.document().isModified()
 
1704
        self.set_stack_title(index, state)
 
1705
        # Toggle save/save all actions state
 
1706
        self.save_action.setEnabled(state)
 
1707
        self.emit(SIGNAL('refresh_save_all_action()'))
 
1708
        # Refreshing eol mode
 
1709
        eol_chars = finfo.editor.get_line_separator()
 
1710
        os_name = sourcecode.get_os_name_from_eol_chars(eol_chars)
 
1711
        self.emit(SIGNAL('refresh_eol_chars(QString)'), os_name)
 
1712
        
 
1713
 
 
1714
    #------ Load, reload
 
1715
    def reload(self, index):
 
1716
        """Reload file from disk"""
 
1717
        finfo = self.data[index]
 
1718
        txt, finfo.encoding = encoding.read(finfo.filename)
 
1719
        finfo.lastmodified = QFileInfo(finfo.filename).lastModified()
 
1720
        position = finfo.editor.get_position('cursor')
 
1721
        finfo.editor.set_text(txt)
 
1722
        finfo.editor.document().setModified(False)
 
1723
        finfo.editor.set_cursor_position(position)
 
1724
        codeeditor.validate_rope_project()
 
1725
        self._refresh_outlineexplorer(index, update=True, clear=True)
 
1726
        
 
1727
    def revert(self):
 
1728
        """Revert file from disk"""
 
1729
        index = self.get_stack_index()
 
1730
        finfo = self.data[index]
 
1731
        filename = finfo.filename
 
1732
        if finfo.editor.document().isModified():
 
1733
            answer = QMessageBox.warning(self, self.title,
 
1734
                                _("All changes to <b>%s</b> will be lost."
 
1735
                                  "<br>Do you want to revert file from disk?"
 
1736
                                  ) % osp.basename(filename),
 
1737
                                  QMessageBox.Yes|QMessageBox.No)
 
1738
            if answer != QMessageBox.Yes:
 
1739
                return
 
1740
        self.reload(index)
 
1741
        
 
1742
    def create_new_editor(self, fname, enc, txt,
 
1743
                          set_current, new=False, cloned_from=None):
 
1744
        """
 
1745
        Create a new editor instance
 
1746
        Returns finfo object (instead of editor as in previous releases)
 
1747
        """
 
1748
        editor = codeeditor.CodeEditor(self)
 
1749
        finfo = FileInfo(fname, enc, editor, new, self.threadmanager)
 
1750
        self.add_to_data(finfo, set_current)
 
1751
        self.connect(finfo, SIGNAL(
 
1752
                    "send_to_inspector(QString,QString,QString,QString,bool)"),
 
1753
                    self.send_to_inspector)
 
1754
        self.connect(finfo, SIGNAL('analysis_results_changed()'),
 
1755
                     lambda: self.emit(SIGNAL('analysis_results_changed()')))
 
1756
        self.connect(finfo, SIGNAL('todo_results_changed()'),
 
1757
                     lambda: self.emit(SIGNAL('todo_results_changed()')))
 
1758
        self.connect(finfo, SIGNAL("edit_goto(QString,int,QString)"),
 
1759
                     lambda fname, lineno, name:
 
1760
                     self.emit(SIGNAL("edit_goto(QString,int,QString)"),
 
1761
                               fname, lineno, name))
 
1762
        self.connect(finfo, SIGNAL("save_breakpoints(QString,QString)"),
 
1763
                     lambda s1, s2:
 
1764
                     self.emit(SIGNAL("save_breakpoints(QString,QString)"),
 
1765
                               s1, s2))
 
1766
        language = get_file_language(fname, txt)
 
1767
        editor.setup_editor(
 
1768
                linenumbers=self.linenumbers_enabled,
 
1769
                edge_line=self.edgeline_enabled,
 
1770
                edge_line_column=self.edgeline_column, language=language,
 
1771
                markers=self.has_markers(), font=self.default_font,
 
1772
                color_scheme=self.color_scheme,
 
1773
                wrap=self.wrap_enabled, tab_mode=self.tabmode_enabled,
 
1774
                intelligent_backspace=self.intelligent_backspace_enabled,
 
1775
                highlight_current_line=self.highlight_current_line_enabled,
 
1776
                occurence_highlighting=self.occurence_highlighting_enabled,
 
1777
                codecompletion_auto=self.codecompletion_auto_enabled,
 
1778
                codecompletion_case=self.codecompletion_case_enabled,
 
1779
                codecompletion_single=self.codecompletion_single_enabled,
 
1780
                codecompletion_enter=self.codecompletion_enter_enabled,
 
1781
                calltips=self.calltips_enabled,
 
1782
                go_to_definition=self.go_to_definition_enabled,
 
1783
                close_parentheses=self.close_parentheses_enabled,
 
1784
                close_quotes=self.close_quotes_enabled,
 
1785
                add_colons=self.add_colons_enabled,
 
1786
                auto_unindent=self.auto_unindent_enabled,
 
1787
                indent_chars=self.indent_chars,
 
1788
                tab_stop_width=self.tab_stop_width,
 
1789
                cloned_from=cloned_from)
 
1790
        if cloned_from is None:
 
1791
            editor.set_text(txt)
 
1792
            editor.document().setModified(False)
 
1793
        self.connect(finfo, SIGNAL('text_changed_at(QString,int)'),
 
1794
                     lambda fname, position:
 
1795
                     self.emit(SIGNAL('text_changed_at(QString,int)'),
 
1796
                               fname, position))
 
1797
        self.connect(editor, SIGNAL('cursorPositionChanged(int,int)'),
 
1798
                     self.editor_cursor_position_changed)
 
1799
        self.connect(editor, SIGNAL('textChanged()'),
 
1800
                     self.start_stop_analysis_timer)
 
1801
        self.connect(editor, SIGNAL('modificationChanged(bool)'),
 
1802
                     lambda state: self.modification_changed(state,
 
1803
                                                    editor_id=id(editor)))
 
1804
        self.connect(editor, SIGNAL("focus_in()"), self.focus_changed)
 
1805
        self.connect(editor, SIGNAL('zoom_in()'),
 
1806
                     lambda: self.emit(SIGNAL('zoom_in()')))
 
1807
        self.connect(editor, SIGNAL('zoom_out()'),
 
1808
                     lambda: self.emit(SIGNAL('zoom_out()')))
 
1809
        if self.outlineexplorer is not None:
 
1810
            # Removing editor reference from outline explorer settings:
 
1811
            self.connect(editor, SIGNAL("destroyed()"),
 
1812
                         lambda obj=editor:
 
1813
                         self.outlineexplorer.remove_editor(obj))
 
1814
 
 
1815
        self.find_widget.set_editor(editor)
 
1816
       
 
1817
        self.emit(SIGNAL('refresh_file_dependent_actions()'))
 
1818
        self.modification_changed(index=self.data.index(finfo))
 
1819
        
 
1820
        return finfo
 
1821
    
 
1822
    def editor_cursor_position_changed(self, line, index):
 
1823
        """Cursor position of one of the editor in the stack has changed"""
 
1824
        self.emit(SIGNAL('editor_cursor_position_changed(int,int)'),
 
1825
                  line, index)
 
1826
    
 
1827
    def send_to_inspector(self, qstr1, qstr2=None, qstr3=None,
 
1828
                          qstr4=None, force=False):
 
1829
        """qstr1: obj_text, qstr2: argpspec, qstr3: note, qstr4: doc_text"""
 
1830
        if not force and not self.inspector_enabled:
 
1831
            return
 
1832
        if self.inspector is not None \
 
1833
           and (force or self.inspector.dockwidget.isVisible()):
 
1834
            # ObjectInspector widget exists and is visible
 
1835
            if qstr4 is None:
 
1836
                self.inspector.set_object_text(qstr1, ignore_unknown=True,
 
1837
                                               force_refresh=force)
 
1838
            else:
 
1839
                objtxt = unicode(qstr1)
 
1840
                name = objtxt.split('.')[-1]
 
1841
                argspec = unicode(qstr2)
 
1842
                note = unicode(qstr3)
 
1843
                docstring = unicode(qstr4)
 
1844
                text = {'obj_text': objtxt, 'name': name, 'argspec': argspec,
 
1845
                        'note': note, 'docstring': docstring}
 
1846
                self.inspector.set_rope_doc(text, force_refresh=force)
 
1847
            editor = self.get_current_editor()
 
1848
            editor.setFocus()
 
1849
    
 
1850
    def new(self, filename, encoding, text):
 
1851
        """
 
1852
        Create new filename with *encoding* and *text*
 
1853
        """
 
1854
        finfo = self.create_new_editor(filename, encoding, text,
 
1855
                                       set_current=False, new=True)
 
1856
        finfo.editor.set_cursor_position('eof')
 
1857
        finfo.editor.insert_text(os.linesep)
 
1858
        return finfo
 
1859
        
 
1860
    def load(self, filename, set_current=True):
 
1861
        """
 
1862
        Load filename, create an editor instance and return it
 
1863
        *Warning* This is loading file, creating editor but not executing
 
1864
        the source code analysis -- the analysis must be done by the editor
 
1865
        plugin (in case multiple editorstack instances are handled)
 
1866
        """
 
1867
        filename = osp.abspath(unicode(filename))
 
1868
        self.emit(SIGNAL('starting_long_process(QString)'),
 
1869
                  _("Loading %s...") % filename)
 
1870
        text, enc = encoding.read(filename)
 
1871
        finfo = self.create_new_editor(filename, enc, text, set_current)
 
1872
        index = self.data.index(finfo)
 
1873
        self._refresh_outlineexplorer(index, update=True)
 
1874
        self.emit(SIGNAL('ending_long_process(QString)'), "")
 
1875
        if self.isVisible() and self.checkeolchars_enabled \
 
1876
           and sourcecode.has_mixed_eol_chars(text):
 
1877
            name = osp.basename(filename)
 
1878
            QMessageBox.warning(self, self.title,
 
1879
                                _("<b>%s</b> contains mixed end-of-line "
 
1880
                                  "characters.<br>Spyder will fix this "
 
1881
                                  "automatically.") % name,
 
1882
                                QMessageBox.Ok)
 
1883
            self.set_os_eol_chars(index)
 
1884
        self.is_analysis_done = False
 
1885
        return finfo
 
1886
    
 
1887
    def set_os_eol_chars(self, index=None):
 
1888
        if index is None:
 
1889
            index = self.get_stack_index()
 
1890
        finfo = self.data[index]
 
1891
        eol_chars = sourcecode.get_eol_chars_from_os_name(os.name)
 
1892
        finfo.editor.set_eol_chars(eol_chars)
 
1893
        finfo.editor.document().setModified(True)
 
1894
        
 
1895
    def remove_trailing_spaces(self, index=None):
 
1896
        """Remove trailing spaces"""
 
1897
        if index is None:
 
1898
            index = self.get_stack_index()
 
1899
        finfo = self.data[index]
 
1900
        finfo.editor.remove_trailing_spaces()
 
1901
        
 
1902
    def fix_indentation(self, index=None):
 
1903
        """Replace tab characters by spaces"""
 
1904
        if index is None:
 
1905
            index = self.get_stack_index()
 
1906
        finfo = self.data[index]
 
1907
        finfo.editor.fix_indentation()
 
1908
 
 
1909
    #------ Run
 
1910
    def run_selection_or_block(self):
 
1911
        """
 
1912
        Run selected text in console and set focus to console
 
1913
        *or*, if there is no selection,
 
1914
        Run current block of lines in console and go to next block
 
1915
        """
 
1916
        self.emit(SIGNAL('external_console_execute_lines(QString)'),
 
1917
                  self.get_current_editor().get_executable_text())
 
1918
            
 
1919
    #------ Drag and drop
 
1920
    def dragEnterEvent(self, event):
 
1921
        """Reimplement Qt method
 
1922
        Inform Qt about the types of data that the widget accepts"""
 
1923
        source = event.mimeData()
 
1924
        if source.hasUrls():
 
1925
            if mimedata2url(source, extlist=EDIT_EXT):
 
1926
                event.acceptProposedAction()
 
1927
            else:
 
1928
                event.ignore()
 
1929
        elif source.hasText():
 
1930
            event.acceptProposedAction()
 
1931
        else:
 
1932
            event.ignore()
 
1933
            
 
1934
    def dropEvent(self, event):
 
1935
        """Reimplement Qt method
 
1936
        Unpack dropped data and handle it"""
 
1937
        source = event.mimeData()
 
1938
        if source.hasUrls():
 
1939
            files = mimedata2url(source, extlist=EDIT_EXT)
 
1940
            if files:
 
1941
                for fname in files:
 
1942
                    self.emit(SIGNAL('plugin_load(QString)'), fname)
 
1943
        elif source.hasText():
 
1944
            editor = self.get_current_editor()
 
1945
            if editor is not None:
 
1946
                editor.insert_text( source.text() )
 
1947
        event.acceptProposedAction()
 
1948
 
 
1949
 
 
1950
class EditorSplitter(QSplitter):
 
1951
    def __init__(self, parent, plugin, menu_actions, first=False,
 
1952
                 register_editorstack_cb=None, unregister_editorstack_cb=None):
 
1953
        QSplitter.__init__(self, parent)
 
1954
        self.setAttribute(Qt.WA_DeleteOnClose)
 
1955
        self.setChildrenCollapsible(False)
 
1956
        
 
1957
        self.toolbar_list = None
 
1958
        self.menu_list = None
 
1959
        
 
1960
        self.plugin = plugin
 
1961
        
 
1962
        if register_editorstack_cb is None:
 
1963
            register_editorstack_cb = self.plugin.register_editorstack
 
1964
        self.register_editorstack_cb = register_editorstack_cb
 
1965
        if unregister_editorstack_cb is None:
 
1966
            unregister_editorstack_cb = self.plugin.unregister_editorstack
 
1967
        self.unregister_editorstack_cb = unregister_editorstack_cb
 
1968
        
 
1969
        self.menu_actions = menu_actions
 
1970
        self.editorstack = EditorStack(self, menu_actions)
 
1971
        self.register_editorstack_cb(self.editorstack)
 
1972
        if not first:
 
1973
            self.plugin.clone_editorstack(editorstack=self.editorstack)
 
1974
        self.connect(self.editorstack, SIGNAL("destroyed()"),
 
1975
                     lambda: self.editorstack_closed())
 
1976
        self.connect(self.editorstack, SIGNAL("split_vertically()"),
 
1977
                     lambda: self.split(orientation=Qt.Vertical))
 
1978
        self.connect(self.editorstack, SIGNAL("split_horizontally()"),
 
1979
                     lambda: self.split(orientation=Qt.Horizontal))
 
1980
        self.addWidget(self.editorstack)
 
1981
 
 
1982
    def closeEvent(self, event):
 
1983
        QSplitter.closeEvent(self, event)
 
1984
        if is_pyqt46:
 
1985
            self.emit(SIGNAL('destroyed()'))
 
1986
                                
 
1987
    def __give_focus_to_remaining_editor(self):
 
1988
        focus_widget = self.plugin.get_focus_widget()
 
1989
        if focus_widget is not None:
 
1990
            focus_widget.setFocus()
 
1991
        
 
1992
    def editorstack_closed(self):
 
1993
        if DEBUG:
 
1994
            print >>STDOUT, "method 'editorstack_closed':"
 
1995
            print >>STDOUT, "    self  :", self
 
1996
#            print >>STDOUT, "    sender:", self.sender()
 
1997
        self.unregister_editorstack_cb(self.editorstack)
 
1998
        self.editorstack = None
 
1999
        try:
 
2000
            close_splitter = self.count() == 1
 
2001
        except RuntimeError:
 
2002
            # editorsplitter has been destroyed (happens when closing a
 
2003
            # EditorMainWindow instance)
 
2004
            return
 
2005
        if close_splitter:
 
2006
            # editorstack just closed was the last widget in this QSplitter
 
2007
            self.close()
 
2008
            return
 
2009
        self.__give_focus_to_remaining_editor()
 
2010
        
 
2011
    def editorsplitter_closed(self):
 
2012
        if DEBUG:
 
2013
            print >>STDOUT, "method 'editorsplitter_closed':"
 
2014
            print >>STDOUT, "    self  :", self
 
2015
#            print >>STDOUT, "    sender:", self.sender()
 
2016
        try:
 
2017
            close_splitter = self.count() == 1 and self.editorstack is None
 
2018
        except RuntimeError:
 
2019
            # editorsplitter has been destroyed (happens when closing a
 
2020
            # EditorMainWindow instance)
 
2021
            return
 
2022
        if close_splitter:
 
2023
            # editorsplitter just closed was the last widget in this QSplitter
 
2024
            self.close()
 
2025
            return
 
2026
        elif self.count() == 2 and self.editorstack:
 
2027
            # back to the initial state: a single editorstack instance,
 
2028
            # as a single widget in this QSplitter: orientation may be changed
 
2029
            self.editorstack.reset_orientation()
 
2030
        self.__give_focus_to_remaining_editor()
 
2031
        
 
2032
    def split(self, orientation=Qt.Vertical):
 
2033
        self.setOrientation(orientation)
 
2034
        self.editorstack.set_orientation(orientation)
 
2035
        editorsplitter = EditorSplitter(self.parent(), self.plugin,
 
2036
                    self.menu_actions,
 
2037
                    register_editorstack_cb=self.register_editorstack_cb,
 
2038
                    unregister_editorstack_cb=self.unregister_editorstack_cb)
 
2039
        self.addWidget(editorsplitter)
 
2040
        self.connect(editorsplitter, SIGNAL("destroyed()"),
 
2041
                     lambda: self.editorsplitter_closed())
 
2042
        current_editor = editorsplitter.editorstack.get_current_editor()
 
2043
        if current_editor is not None:
 
2044
            current_editor.setFocus()
 
2045
            
 
2046
    def iter_editorstacks(self):
 
2047
        editorstacks = [(self.widget(0), self.orientation())]
 
2048
        if self.count() > 1:
 
2049
            editorsplitter = self.widget(1)
 
2050
            editorstacks += editorsplitter.iter_editorstacks()
 
2051
        return editorstacks
 
2052
 
 
2053
    def get_layout_settings(self):
 
2054
        """Return layout state"""
 
2055
        splitsettings = []
 
2056
        for editorstack, orientation in self.iter_editorstacks():
 
2057
            clines = [finfo.editor.get_cursor_line_number()
 
2058
                      for finfo in editorstack.data]
 
2059
            cfname = editorstack.get_current_filename()
 
2060
            splitsettings.append((orientation == Qt.Vertical, cfname, clines))
 
2061
        return dict(hexstate=str(self.saveState().toHex()),
 
2062
                    sizes=self.sizes(), splitsettings=splitsettings)
 
2063
    
 
2064
    def set_layout_settings(self, settings):
 
2065
        """Restore layout state"""
 
2066
        splitsettings = settings.get('splitsettings')
 
2067
        if splitsettings is None:
 
2068
            return
 
2069
        splitter = self
 
2070
        editor = None
 
2071
        for index, (is_vertical, cfname, clines) in enumerate(splitsettings):
 
2072
            if index > 0:
 
2073
                splitter.split(Qt.Vertical if is_vertical else Qt.Horizontal)
 
2074
                splitter = splitter.widget(1)
 
2075
            editorstack = splitter.widget(0)
 
2076
            for index, finfo in enumerate(editorstack.data):
 
2077
                editor = finfo.editor
 
2078
                editor.go_to_line(clines[index])
 
2079
            editorstack.set_current_filename(cfname)
 
2080
        hexstate = settings.get('hexstate')
 
2081
        if hexstate is not None:
 
2082
            self.restoreState( QByteArray().fromHex(str(hexstate)) )
 
2083
        sizes = settings.get('sizes')
 
2084
        if sizes is not None:
 
2085
            self.setSizes(sizes)
 
2086
        if editor is not None:
 
2087
            editor.clearFocus()
 
2088
            editor.setFocus()
 
2089
 
 
2090
 
 
2091
class EditorWidget(QSplitter):
 
2092
    def __init__(self, parent, plugin, menu_actions, show_fullpath,
 
2093
                 fullpath_sorting, show_all_files, show_comments):
 
2094
        QSplitter.__init__(self, parent)
 
2095
        self.setAttribute(Qt.WA_DeleteOnClose)
 
2096
        
 
2097
        statusbar = parent.statusBar() # Create a status bar
 
2098
        self.readwrite_status = ReadWriteStatus(self, statusbar)
 
2099
        self.eol_status = EOLStatus(self, statusbar)
 
2100
        self.encoding_status = EncodingStatus(self, statusbar)
 
2101
        self.cursorpos_status = CursorPositionStatus(self, statusbar)
 
2102
        
 
2103
        self.editorstacks = []
 
2104
        
 
2105
        self.plugin = plugin
 
2106
        
 
2107
        self.find_widget = FindReplace(self, enable_replace=True)
 
2108
        self.plugin.register_widget_shortcuts("Editor", self.find_widget)
 
2109
        self.find_widget.hide()
 
2110
        self.outlineexplorer = OutlineExplorerWidget(self,
 
2111
                                            show_fullpath=show_fullpath,
 
2112
                                            fullpath_sorting=fullpath_sorting,
 
2113
                                            show_all_files=show_all_files,
 
2114
                                            show_comments=show_comments)
 
2115
        self.connect(self.outlineexplorer,
 
2116
                     SIGNAL("edit_goto(QString,int,QString)"),
 
2117
                     lambda filenames, goto, word:
 
2118
                     plugin.load(filenames=filenames, goto=goto, word=word,
 
2119
                                 editorwindow=self.parent()))
 
2120
        
 
2121
        editor_widgets = QWidget(self)
 
2122
        editor_layout = QVBoxLayout()
 
2123
        editor_layout.setContentsMargins(0, 0, 0, 0)
 
2124
        editor_widgets.setLayout(editor_layout)
 
2125
        editorsplitter = EditorSplitter(self, plugin, menu_actions,
 
2126
                        register_editorstack_cb=self.register_editorstack,
 
2127
                        unregister_editorstack_cb=self.unregister_editorstack)
 
2128
        self.editorsplitter = editorsplitter
 
2129
        editor_layout.addWidget(editorsplitter)
 
2130
        editor_layout.addWidget(self.find_widget)
 
2131
        
 
2132
        splitter = QSplitter(self)
 
2133
        splitter.setContentsMargins(0, 0, 0, 0)
 
2134
        splitter.addWidget(editor_widgets)
 
2135
        splitter.addWidget(self.outlineexplorer)
 
2136
        splitter.setStretchFactor(0, 5)
 
2137
        splitter.setStretchFactor(1, 1)
 
2138
 
 
2139
        # Refreshing outline explorer
 
2140
        for index in range(editorsplitter.editorstack.get_stack_count()):
 
2141
            editorsplitter.editorstack._refresh_outlineexplorer(index,
 
2142
                                                                update=True)
 
2143
        
 
2144
    def register_editorstack(self, editorstack):
 
2145
        self.editorstacks.append(editorstack)
 
2146
        if DEBUG:
 
2147
            print >>STDOUT, "EditorWidget.register_editorstack:", editorstack
 
2148
            self.__print_editorstacks()
 
2149
        self.plugin.last_focus_editorstack[self.parent()] = editorstack
 
2150
        editorstack.set_closable( len(self.editorstacks) > 1 )
 
2151
        editorstack.set_outlineexplorer(self.outlineexplorer)
 
2152
        editorstack.set_find_widget(self.find_widget)
 
2153
        self.connect(editorstack, SIGNAL('reset_statusbar()'),
 
2154
                     self.readwrite_status.hide)
 
2155
        self.connect(editorstack, SIGNAL('reset_statusbar()'),
 
2156
                     self.encoding_status.hide)
 
2157
        self.connect(editorstack, SIGNAL('reset_statusbar()'),
 
2158
                     self.cursorpos_status.hide)
 
2159
        self.connect(editorstack, SIGNAL('readonly_changed(bool)'),
 
2160
                     self.readwrite_status.readonly_changed)
 
2161
        self.connect(editorstack, SIGNAL('encoding_changed(QString)'),
 
2162
                     self.encoding_status.encoding_changed)
 
2163
        self.connect(editorstack,
 
2164
                     SIGNAL('editor_cursor_position_changed(int,int)'),
 
2165
                     self.cursorpos_status.cursor_position_changed)
 
2166
        self.connect(editorstack, SIGNAL('refresh_eol_chars(QString)'),
 
2167
                     self.eol_status.eol_changed)
 
2168
        self.plugin.register_editorstack(editorstack)
 
2169
        oe_btn = create_toolbutton(self)
 
2170
        oe_btn.setDefaultAction(self.outlineexplorer.visibility_action)
 
2171
        editorstack.add_corner_widgets_to_tabbar([5, oe_btn])
 
2172
        
 
2173
    def __print_editorstacks(self):
 
2174
        print >>STDOUT, "%d editorstack(s) in editorwidget:" \
 
2175
                        % len(self.editorstacks)
 
2176
        for edst in self.editorstacks:
 
2177
            print >>STDOUT, "    ", edst
 
2178
        
 
2179
    def unregister_editorstack(self, editorstack):
 
2180
        if DEBUG:
 
2181
            print >>STDOUT, "EditorWidget.unregister_editorstack:", editorstack
 
2182
        self.plugin.unregister_editorstack(editorstack)
 
2183
        self.editorstacks.pop(self.editorstacks.index(editorstack))
 
2184
        if DEBUG:
 
2185
            self.__print_editorstacks()
 
2186
        
 
2187
 
 
2188
class EditorMainWindow(QMainWindow):
 
2189
    def __init__(self, plugin, menu_actions, toolbar_list, menu_list,
 
2190
                 show_fullpath, fullpath_sorting, show_all_files,
 
2191
                 show_comments):
 
2192
        QMainWindow.__init__(self)
 
2193
        self.setAttribute(Qt.WA_DeleteOnClose)
 
2194
 
 
2195
        self.window_size = None
 
2196
        
 
2197
        self.editorwidget = EditorWidget(self, plugin, menu_actions,
 
2198
                                         show_fullpath, fullpath_sorting,
 
2199
                                         show_all_files, show_comments)
 
2200
        self.setCentralWidget(self.editorwidget)
 
2201
 
 
2202
        # Give focus to current editor to update/show all status bar widgets
 
2203
        editorstack = self.editorwidget.editorsplitter.editorstack
 
2204
        editor = editorstack.get_current_editor()
 
2205
        if editor is not None:
 
2206
            editor.setFocus()
 
2207
        
 
2208
        self.setWindowTitle("Spyder - %s" % plugin.windowTitle())
 
2209
        self.setWindowIcon(plugin.windowIcon())
 
2210
        
 
2211
        if toolbar_list:
 
2212
            toolbars = []
 
2213
            for title, actions in toolbar_list:
 
2214
                toolbar = self.addToolBar(title)
 
2215
                toolbar.setObjectName(str(id(toolbar)))
 
2216
                add_actions(toolbar, actions)
 
2217
                toolbars.append(toolbar)
 
2218
        if menu_list:
 
2219
            quit_action = create_action(self, _("Close window"),
 
2220
                                        icon="close_panel.png",
 
2221
                                        tip=_("Close this window"),
 
2222
                                        triggered=self.close)
 
2223
            menus = []
 
2224
            for index, (title, actions) in enumerate(menu_list):
 
2225
                menu = self.menuBar().addMenu(title)
 
2226
                if index == 0:
 
2227
                    # File menu
 
2228
                    add_actions(menu, actions+[None, quit_action])
 
2229
                else:
 
2230
                    add_actions(menu, actions)
 
2231
                menus.append(menu)
 
2232
            
 
2233
    def resizeEvent(self, event):
 
2234
        """Reimplement Qt method"""
 
2235
        if not self.isMaximized() and not self.isFullScreen():
 
2236
            self.window_size = self.size()
 
2237
        QMainWindow.resizeEvent(self, event)
 
2238
                
 
2239
    def closeEvent(self, event):
 
2240
        """Reimplement Qt method"""
 
2241
        QMainWindow.closeEvent(self, event)
 
2242
        if is_pyqt46:
 
2243
            self.emit(SIGNAL('destroyed()'))
 
2244
            for editorstack in self.editorwidget.editorstacks[:]:
 
2245
                if DEBUG:
 
2246
                    print >>STDOUT, "--> destroy_editorstack:", editorstack
 
2247
                editorstack.emit(SIGNAL('destroyed()'))
 
2248
                                
 
2249
    def get_layout_settings(self):
 
2250
        """Return layout state"""
 
2251
        splitsettings = self.editorwidget.editorsplitter.get_layout_settings()
 
2252
        return dict(size=(self.window_size.width(), self.window_size.height()),
 
2253
                    pos=(self.pos().x(), self.pos().y()),
 
2254
                    is_maximized=self.isMaximized(),
 
2255
                    is_fullscreen=self.isFullScreen(),
 
2256
                    hexstate=str(self.saveState().toHex()),
 
2257
                    splitsettings=splitsettings)
 
2258
    
 
2259
    def set_layout_settings(self, settings):
 
2260
        """Restore layout state"""
 
2261
        size = settings.get('size')
 
2262
        if size is not None:
 
2263
            self.resize( QSize(*size) )
 
2264
            self.window_size = self.size()
 
2265
        pos = settings.get('pos')
 
2266
        if pos is not None:
 
2267
            self.move( QPoint(*pos) )
 
2268
        hexstate = settings.get('hexstate')
 
2269
        if hexstate is not None:
 
2270
            self.restoreState( QByteArray().fromHex(str(hexstate)) )
 
2271
        if settings.get('is_maximized'):
 
2272
            self.setWindowState(Qt.WindowMaximized)
 
2273
        if settings.get('is_fullscreen'):
 
2274
            self.setWindowState(Qt.WindowFullScreen)
 
2275
        splitsettings = settings.get('splitsettings')
 
2276
        if splitsettings is not None:
 
2277
            self.editorwidget.editorsplitter.set_layout_settings(splitsettings)
 
2278
 
 
2279
 
 
2280
class EditorPluginExample(QSplitter):
 
2281
    def __init__(self):
 
2282
        QSplitter.__init__(self)
 
2283
                
 
2284
        menu_actions = []
 
2285
                
 
2286
        self.editorstacks = []
 
2287
        self.editorwindows = []
 
2288
        
 
2289
        self.last_focus_editorstack = {} # fake
 
2290
 
 
2291
        self.find_widget = FindReplace(self, enable_replace=True)
 
2292
        self.outlineexplorer = OutlineExplorerWidget(self, show_fullpath=False,
 
2293
                                                     show_all_files=False)
 
2294
        self.connect(self.outlineexplorer,
 
2295
                     SIGNAL("edit_goto(QString,int,QString)"),
 
2296
                     self.go_to_file)
 
2297
        
 
2298
        editor_widgets = QWidget(self)
 
2299
        editor_layout = QVBoxLayout()
 
2300
        editor_layout.setContentsMargins(0, 0, 0, 0)
 
2301
        editor_widgets.setLayout(editor_layout)
 
2302
        editor_layout.addWidget(EditorSplitter(self, self, menu_actions,
 
2303
                                               first=True))
 
2304
        editor_layout.addWidget(self.find_widget)
 
2305
        
 
2306
        self.setContentsMargins(0, 0, 0, 0)
 
2307
        self.addWidget(editor_widgets)
 
2308
        self.addWidget(self.outlineexplorer)
 
2309
        
 
2310
        self.setStretchFactor(0, 5)
 
2311
        self.setStretchFactor(1, 1)
 
2312
        
 
2313
        self.menu_actions = menu_actions
 
2314
        self.toolbar_list = None
 
2315
        self.menu_list = None
 
2316
        self.setup_window([], [])
 
2317
        
 
2318
    def go_to_file(self, fname, lineno, text):
 
2319
        editorstack = self.editorstacks[0]
 
2320
        editorstack.set_current_filename(unicode(fname))
 
2321
        editor = editorstack.get_current_editor()
 
2322
        editor.go_to_line(lineno, word=text)
 
2323
 
 
2324
    def closeEvent(self, event):
 
2325
        for win in self.editorwindows[:]:
 
2326
            win.close()
 
2327
        if DEBUG:
 
2328
            print >>STDOUT, len(self.editorwindows), ":", self.editorwindows
 
2329
            print >>STDOUT, len(self.editorstacks), ":", self.editorstacks
 
2330
        
 
2331
        event.accept()
 
2332
        
 
2333
    def load(self, fname):
 
2334
        QApplication.processEvents()
 
2335
        editorstack = self.editorstacks[0]
 
2336
        editorstack.load(fname)
 
2337
        editorstack.analyze_script()
 
2338
    
 
2339
    def register_editorstack(self, editorstack):
 
2340
        if DEBUG:
 
2341
            print >>STDOUT, "FakePlugin.register_editorstack:", editorstack
 
2342
        self.editorstacks.append(editorstack)
 
2343
        if self.isAncestorOf(editorstack):
 
2344
            # editorstack is a child of the Editor plugin
 
2345
            editorstack.set_fullpath_sorting_enabled(True)
 
2346
            editorstack.set_closable( len(self.editorstacks) > 1 )
 
2347
            editorstack.set_outlineexplorer(self.outlineexplorer)
 
2348
            editorstack.set_find_widget(self.find_widget)
 
2349
            oe_btn = create_toolbutton(self)
 
2350
            oe_btn.setDefaultAction(self.outlineexplorer.visibility_action)
 
2351
            editorstack.add_corner_widgets_to_tabbar([5, oe_btn])
 
2352
            
 
2353
        action = QAction(self)
 
2354
        editorstack.set_io_actions(action, action, action, action)
 
2355
        font = QFont("Courier New")
 
2356
        font.setPointSize(10)
 
2357
        editorstack.set_default_font(font, color_scheme='Spyder')
 
2358
 
 
2359
        self.connect(editorstack, SIGNAL('close_file(QString,int)'),
 
2360
                     self.close_file_in_all_editorstacks)
 
2361
        self.connect(editorstack, SIGNAL('file_saved(QString,int,QString)'),
 
2362
                     self.file_saved_in_editorstack)
 
2363
        self.connect(editorstack,
 
2364
                     SIGNAL('file_renamed_in_data(QString,int,QString)'),
 
2365
                     self.file_renamed_in_data_in_editorstack)
 
2366
 
 
2367
        self.connect(editorstack, SIGNAL("create_new_window()"),
 
2368
                     self.create_new_window)
 
2369
        self.connect(editorstack, SIGNAL('plugin_load(QString)'),
 
2370
                     self.load)
 
2371
                    
 
2372
    def unregister_editorstack(self, editorstack):
 
2373
        if DEBUG:
 
2374
            print >>STDOUT, "FakePlugin.unregister_editorstack:", editorstack
 
2375
        self.editorstacks.pop(self.editorstacks.index(editorstack))
 
2376
        
 
2377
    def clone_editorstack(self, editorstack):
 
2378
        editorstack.clone_from(self.editorstacks[0])
 
2379
        
 
2380
    def setup_window(self, toolbar_list, menu_list):
 
2381
        self.toolbar_list = toolbar_list
 
2382
        self.menu_list = menu_list
 
2383
        
 
2384
    def create_new_window(self):
 
2385
        window = EditorMainWindow(self, self.menu_actions,
 
2386
                                  self.toolbar_list, self.menu_list,
 
2387
                                  show_fullpath=False, fullpath_sorting=True,
 
2388
                                  show_all_files=False, show_comments=True)
 
2389
        window.resize(self.size())
 
2390
        window.show()
 
2391
        self.register_editorwindow(window)
 
2392
        self.connect(window, SIGNAL("destroyed()"),
 
2393
                     lambda win=window: self.unregister_editorwindow(win))
 
2394
        
 
2395
    def register_editorwindow(self, window):
 
2396
        if DEBUG:
 
2397
            print >>STDOUT, "register_editorwindowQObject*:", window
 
2398
        self.editorwindows.append(window)
 
2399
        
 
2400
    def unregister_editorwindow(self, window):
 
2401
        if DEBUG:
 
2402
            print >>STDOUT, "unregister_editorwindow:", window
 
2403
        self.editorwindows.pop(self.editorwindows.index(window))
 
2404
    
 
2405
    def get_focus_widget(self):
 
2406
        pass
 
2407
 
 
2408
    @Slot(int, int)
 
2409
    def close_file_in_all_editorstacks(self, editorstack_id_str, index):
 
2410
        for editorstack in self.editorstacks:
 
2411
            if str(id(editorstack)) != editorstack_id_str:
 
2412
                editorstack.blockSignals(True)
 
2413
                editorstack.close_file(index, force=True)
 
2414
                editorstack.blockSignals(False)
 
2415
 
 
2416
    # This method is never called in this plugin example. It's here only 
 
2417
    # to show how to use the file_saved signal (see above).
 
2418
    @Slot(int, int)
 
2419
    def file_saved_in_editorstack(self, editorstack_id_str, index, filename):
 
2420
        """A file was saved in editorstack, this notifies others"""
 
2421
        for editorstack in self.editorstacks:
 
2422
            if str(id(editorstack)) != editorstack_id_str:
 
2423
                editorstack.file_saved_in_other_editorstack(index, filename)
 
2424
 
 
2425
    # This method is never called in this plugin example. It's here only 
 
2426
    # to show how to use the file_saved signal (see above).
 
2427
    @Slot(int, int)
 
2428
    def file_renamed_in_data_in_editorstack(self, editorstack_id_str,
 
2429
                                            index, filename):
 
2430
        """A file was renamed in data in editorstack, this notifies others"""
 
2431
        for editorstack in self.editorstacks:
 
2432
            if str(id(editorstack)) != editorstack_id_str:
 
2433
                editorstack.rename_in_data(index, filename)
 
2434
 
 
2435
    def register_widget_shortcuts(self, context, widget):
 
2436
        """Fake!"""
 
2437
        pass
 
2438
    
 
2439
def test():
 
2440
    from spyderlib.utils.qthelpers import qapplication
 
2441
    app = qapplication()
 
2442
    test = EditorPluginExample()
 
2443
    test.resize(900, 700)
 
2444
    test.show()
 
2445
    import time
 
2446
    t0 = time.time()
 
2447
    test.load(__file__)
 
2448
    test.load("explorer.py")
 
2449
    test.load("dicteditor.py")
 
2450
    test.load("sourcecode/codeeditor.py")
 
2451
    test.load("../spyder.py")
 
2452
    print "Elapsed time: %.3f s" % (time.time()-t0)
 
2453
    sys.exit(app.exec_())
 
2454
    
 
2455
if __name__ == "__main__":
 
2456
    test()