~ubuntu-branches/ubuntu/trusty/spyder/trusty-proposed

« back to all changes in this revision

Viewing changes to .pc/0003-from-upstream-fix-1098.patch/spyderlib/widgets/editor.py

  • Committer: Package Import Robot
  • Author(s): Picca Frédéric-Emmanuel
  • Date: 2013-05-08 21:12:32 UTC
  • mfrom: (1.2.1) (18.1.5 experimental)
  • Revision ID: package-import@ubuntu.com-20130508211232-r867h9xoenknvuxf
Tags: 2.2.0+dfsg-1
* Imported Upstream version 2.2.0+dfsg
* Depends on ipython-qtconsole (< 1.0)
* Removed the obsolte DM-Upload-Allowed

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