1
# -*- coding: utf-8 -*-
3
# Copyright © 2009-2011 Pierre Raybaut
4
# Licensed under the terms of the MIT License
5
# (see spyderlib/__init__.py for details)
9
# pylint: disable=C0103
10
# pylint: disable=R0903
11
# pylint: disable=R0911
12
# pylint: disable=R0201
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,
20
from spyderlib.qt.QtCore import (SIGNAL, Qt, QFileInfo, QThread, QObject,
21
QByteArray, QSize, QPoint, QTimer)
22
from spyderlib.qt.compat import getsavefilename
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,
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
47
class FileListDialog(QDialog):
48
def __init__(self, parent, tabs, fullpath_sorting):
49
QDialog.__init__(self, parent)
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)
59
self.setWindowIcon(get_icon('filelist.png'))
60
self.setWindowTitle(_("File list management"))
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)
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*)"),
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)
89
btn_layout.addStretch()
90
btn_layout.addSpacing(150)
91
btn_layout.addStretch()
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)
101
hint = QLabel(_("Hint: press <b>Alt</b> to show accelerators"))
102
hint.setAlignment(Qt.AlignCenter)
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)
112
self.fullpath_sorting = fullpath_sorting
113
self.buttons = (edit_btn, close_btn)
116
row = self.listwidget.currentRow()
117
if self.listwidget.count() > 0 and row >= 0:
118
self.emit(SIGNAL("edit_file(int)"), self.indexes[row])
121
def item_selection_changed(self):
122
for btn in self.buttons:
123
btn.setEnabled(self.listwidget.currentRow() >= 0)
125
def synchronize(self, stack_index):
126
count = self.tabs.count()
130
self.listwidget.setTextElideMode(Qt.ElideMiddle
131
if self.fullpath_sorting
133
current_row = self.listwidget.currentRow()
135
current_text = unicode(self.listwidget.currentItem().text())
138
self.listwidget.clear()
140
new_current_index = stack_index
141
filter_text = unicode(self.edit.text())
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
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)
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
166
self.source_code = source_code
170
self.results = self.checker(self.source_code)
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 = {}
182
def close_threads(self, parent):
183
"""Close threads associated to parent_id"""
185
print >>STDOUT, "Call to 'close_threads'"
187
# Closing all threads
188
self.pending_threads = []
190
for threads in self.started_threads.values():
191
threadlist += threads
193
parent_id = id(parent)
194
self.pending_threads = [(_th, _id) for (_th, _id)
195
in self.pending_threads
197
threadlist = self.started_threads.get(parent_id, [])
198
for thread in threadlist:
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()
205
def close_all_threads(self):
206
"""Close all threads"""
208
print >>STDOUT, "Call to 'close_all_threads'"
209
self.close_threads(None)
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))
218
print >>STDOUT, "Added thread %r to queue" % thread
219
QTimer.singleShot(50, self.update_queue)
221
def update_queue(self):
224
for parent_id, threadlist in self.started_threads.items():
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)
233
still_running.append(thread)
237
self.started_threads[parent_id] = still_running
239
self.started_threads.pop(parent_id)
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]
250
print >>STDOUT, "===>starting:", thread
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
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()
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)
276
self.connect(editor, SIGNAL('textChanged()'),
279
self.connect(editor, SIGNAL('breakpoints_changed()'),
280
self.breakpoints_changed)
282
self.pyflakes_results = None
283
self.pep8_results = None
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'))
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')
296
if text.startswith('import '):
297
comp_list = moduleCompletion(text)
298
words = text.split(' ')
300
words = words[-1].split(',')
302
self.editor.show_completion_list(comp_list,
303
completion_text=words[-1],
306
elif text.startswith('from '):
307
comp_list = moduleCompletion(text)
308
words = text.split(' ')
310
words = words[:-2] + words[-1].split('(')
312
words = words[:-2] + words[-1].split(',')
313
self.editor.show_completion_list(comp_list,
314
completion_text=words[-1],
318
textlist = self.rope_project.get_completion_list(source_code,
322
completion_text = re.split(r"[^a-zA-Z0-9_]", text)[-1]
323
if text.lstrip().startswith('#') and text.endswith('.'):
326
self.editor.show_completion_list(textlist, completion_text,
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())
339
textlist = self.rope_project.get_calltip_text(source_code, offset,
345
cts, doc_text = textlist
346
cts = cts.replace('.__init__', '')
347
parpos = cts.find('(')
349
obj_fullname = cts[:parpos]
350
obj_name = obj_fullname.split('.')[-1]
351
cts = cts.replace(obj_fullname, obj_name)
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)
359
obj_fullname = codeeditor.get_primary_at(source_code, offset)
360
if obj_fullname and not obj_fullname.startswith('self.') and doc_text:
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
368
self.emit(SIGNAL("send_to_inspector(QString,QString,bool)"),
369
obj_fullname, text, not auto)
371
signatures = ['<b>'+s.replace('(', '(</b>'
372
).replace(')', '<b>)</b>')
374
self.editor.show_calltip(obj_fullname, '<br>'.join(signatures),
375
at_position=position)
377
def go_to_definition(self, position):
378
"""Go to definition"""
379
source_code = unicode(self.editor.toPlainText())
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)"),
387
def get_source_code(self):
388
"""Return associated editor source code"""
389
return unicode(self.editor.toPlainText()).encode('utf-8')
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()
401
self.pyflakes_results = None
403
self.pep8_results = None
405
self.threadmanager.add_thread(codeanalysis.check_with_pyflakes,
406
self.pyflakes_analysis_finished,
409
self.threadmanager.add_thread(codeanalysis.check_with_pep8,
410
self.pep8_analysis_finished,
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()
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()
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()'))
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)
435
def cleanup_analysis_results(self):
436
"""Clean-up analysis results"""
437
self.analysis_results = []
438
self.editor.cleanup_code_analysis()
440
def run_todo_finder(self):
441
"""Run TODO finder"""
442
if self.editor.is_python():
443
self.threadmanager.add_thread(codeanalysis.find_tasks,
445
self.get_source_code(), self)
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()'))
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)
457
def cleanup_todo_results(self):
458
"""Clean-up TODO finder results"""
459
self.todo_results = []
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))
468
class EditorStack(QWidget):
469
def __init__(self, parent, actions):
470
QWidget.__init__(self, parent)
472
self.setAttribute(Qt.WA_DeleteOnClose)
474
self.threadmanager = ThreadManager(self)
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()
482
layout = QVBoxLayout()
483
layout.setContentsMargins(0, 0, 0, 0)
484
self.setLayout(layout)
487
self.filelist_dlg = None
488
# self.filelist_btn = None
489
# self.previous_btn = None
490
# self.next_btn = None
493
self.stack_history = []
495
self.setup_editorstack(parent, layout)
497
self.find_widget = None
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"),
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)
550
if ccs not in syntaxhighlighters.COLOR_SCHEME_NAMES:
551
ccs = syntaxhighlighters.COLOR_SCHEME_NAMES[0]
552
self.color_scheme = ccs
554
self.__file_status_flag = False
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()"),
564
self.setAcceptDrops(True)
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,
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,
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)
592
def get_shortcut_data(self):
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
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"),
607
def setup_editorstack(self, parent, layout):
608
"""Setup editorstack's layout"""
609
menu_btn = create_toolbutton(self, icon=get_icon("tooloptions.png"),
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)
616
# self.filelist_btn = create_toolbutton(self,
617
# icon=get_icon('filelist.png'),
618
# tip=_("File list management"),
619
# triggered=self.open_filelistdialog)
621
# self.previous_btn = create_toolbutton(self,
622
# icon=get_icon('previous.png'),
623
# tip=_("Previous file"),
624
# triggered=self.go_to_previous_file)
626
# self.next_btn = create_toolbutton(self,
627
# icon=get_icon('next.png'),
628
# tip=_("Next file"),
629
# triggered=self.go_to_next_file)
632
# corner_widgets = {Qt.TopRightCorner: [self.previous_btn,
633
# self.filelist_btn, self.next_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)
646
def add_corner_widgets_to_tabbar(self, widgets):
647
self.tabs.add_corner_widgets(widgets)
649
def closeEvent(self, event):
650
self.threadmanager.close_all_threads()
651
self.disconnect(self.analysis_timer, SIGNAL("timeout()"),
653
QWidget.closeEvent(self, event)
655
self.emit(SIGNAL('destroyed()'))
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)
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())
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())
682
self.filelist_dlg = None
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())
689
def go_to_line(self):
690
"""Go to line dialog"""
692
self.get_current_editor().exec_gotolinedialog()
694
def set_or_clear_breakpoint(self):
695
"""Set/clear breakpoint"""
697
editor = self.get_current_editor()
698
editor.add_remove_breakpoint()
700
def set_or_edit_conditional_breakpoint(self):
701
"""Set conditional breakpoint"""
703
editor = self.get_current_editor()
704
editor.add_remove_breakpoint(edit_condition=True)
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)
714
text = self.get_current_editor().get_current_object()
716
self.send_to_inspector(text, force=True)
719
#------ Editor Widget Settings
720
def set_closable(self, state):
721
"""Parent widget must handle the closable state"""
722
self.is_closable = state
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
731
def set_find_widget(self, find_widget):
732
self.find_widget = find_widget
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)
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])
746
def set_inspector(self, inspector):
747
self.inspector = inspector
749
def set_tempfile_path(self, path):
750
self.tempfile_path = path
752
def set_title(self, text):
755
def __update_editor_margins(self, editor):
756
editor.setup_margins(linenumbers=self.linenumbers_enabled,
757
markers=self.has_markers())
759
def __codeanalysis_settings_changed(self, current_finfo):
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)
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)
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)
779
def has_markers(self):
780
"""Return True if this editorstack has a marker margin for TODOs or
782
return self.todolist_enabled or self.pyflakes_enabled\
785
def set_todolist_enabled(self, state, current_finfo=None):
786
# CONF.get(self.CONF_SECTION, 'todo_list')
787
self.todolist_enabled = state
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()
796
def set_realtime_analysis_enabled(self, state):
797
self.realtime_analysis_enabled = state
799
def set_realtime_analysis_timeout(self, timeout):
800
self.analysis_timer.setInterval(timeout)
802
def set_linenumbers_enabled(self, state, current_finfo=None):
803
# CONF.get(self.CONF_SECTION, 'line_numbers')
804
self.linenumbers_enabled = state
806
for finfo in self.data:
807
self.__update_editor_margins(finfo.editor)
809
def set_edgeline_enabled(self, state):
810
# CONF.get(self.CONF_SECTION, 'edge_line')
811
self.edgeline_enabled = state
813
for finfo in self.data:
814
finfo.editor.set_edge_line_enabled(state)
816
def set_edgeline_column(self, column):
817
# CONF.get(self.CONF_SECTION, 'edge_line_column')
818
self.edgeline_column = column
820
for finfo in self.data:
821
finfo.editor.set_edge_line_column(column)
823
def set_codecompletion_auto_enabled(self, state):
824
# CONF.get(self.CONF_SECTION, 'codecompletion_auto')
825
self.codecompletion_auto_enabled = state
827
for finfo in self.data:
828
finfo.editor.set_codecompletion_auto(state)
830
def set_codecompletion_case_enabled(self, state):
831
self.codecompletion_case_enabled = state
833
for finfo in self.data:
834
finfo.editor.set_codecompletion_case(state)
836
def set_codecompletion_single_enabled(self, state):
837
self.codecompletion_single_enabled = state
839
for finfo in self.data:
840
finfo.editor.set_codecompletion_single(state)
842
def set_codecompletion_enter_enabled(self, state):
843
self.codecompletion_enter_enabled = state
845
for finfo in self.data:
846
finfo.editor.set_codecompletion_enter(state)
848
def set_calltips_enabled(self, state):
849
# CONF.get(self.CONF_SECTION, 'calltips')
850
self.calltips_enabled = state
852
for finfo in self.data:
853
finfo.editor.set_calltips(state)
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
859
for finfo in self.data:
860
finfo.editor.set_go_to_definition_enabled(state)
862
def set_close_parentheses_enabled(self, state):
863
# CONF.get(self.CONF_SECTION, 'close_parentheses')
864
self.close_parentheses_enabled = state
866
for finfo in self.data:
867
finfo.editor.set_close_parentheses_enabled(state)
869
def set_auto_unindent_enabled(self, state):
870
# CONF.get(self.CONF_SECTION, 'auto_unindent')
871
self.auto_unindent_enabled = state
873
for finfo in self.data:
874
finfo.editor.set_auto_unindent_enabled(state)
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
881
for finfo in self.data:
882
finfo.editor.set_indent_chars(indent_chars)
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
888
for finfo in self.data:
889
finfo.editor.setTabStopWidth(tab_stop_width)
891
def set_inspector_enabled(self, state):
892
self.inspector_enabled = state
894
def set_outlineexplorer_enabled(self, state):
895
# CONF.get(self.CONF_SECTION, 'outline_explorer')
896
self.outlineexplorer_enabled = state
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
904
for finfo in self.data:
905
finfo.editor.set_font(font, color_scheme)
907
def set_color_scheme(self, color_scheme):
908
self.color_scheme = color_scheme
910
for finfo in self.data:
911
finfo.editor.set_color_scheme(color_scheme)
913
def set_wrap_enabled(self, state):
914
# CONF.get(self.CONF_SECTION, 'wrap')
915
self.wrap_enabled = state
917
for finfo in self.data:
918
finfo.editor.toggle_wrap_mode(state)
920
def set_tabmode_enabled(self, state):
921
# CONF.get(self.CONF_SECTION, 'tab_always_indent')
922
self.tabmode_enabled = state
924
for finfo in self.data:
925
finfo.editor.set_tab_mode(state)
927
def set_intelligent_backspace_enabled(self, state):
928
# CONF.get(self.CONF_SECTION, 'intelligent_backspace')
929
self.intelligent_backspace_enabled = state
931
for finfo in self.data:
932
finfo.editor.toggle_intelligent_backspace(state)
934
def set_occurence_highlighting_enabled(self, state):
935
# CONF.get(self.CONF_SECTION, 'occurence_highlighting')
936
self.occurence_highlighting_enabled = state
938
for finfo in self.data:
939
finfo.editor.set_occurence_highlighting(state)
941
def set_occurence_highlighting_timeout(self, timeout):
942
# CONF.get(self.CONF_SECTION, 'occurence_highlighting/timeout')
943
self.occurence_highlighting_timeout = timeout
945
for finfo in self.data:
946
finfo.editor.set_occurence_timeout(timeout)
948
def set_highlight_current_line_enabled(self, state):
949
self.highlight_current_line_enabled = state
951
for finfo in self.data:
952
finfo.editor.set_highlight_current_line(state)
954
def set_checkeolchars_enabled(self, state):
955
# CONF.get(self.CONF_SECTION, 'check_eol_chars')
956
self.checkeolchars_enabled = state
958
def set_fullpath_sorting_enabled(self, state):
959
# CONF.get(self.CONF_SECTION, 'fullpath_sorting')
960
self.fullpath_sorting_enabled = state
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)
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
973
#------ Stacked widget management
974
def get_stack_index(self):
975
return self.tabs.currentIndex()
977
def get_current_finfo(self):
979
return self.data[self.get_stack_index()]
981
def get_current_editor(self):
982
return self.tabs.currentWidget()
984
def get_stack_count(self):
985
return self.tabs.count()
987
def set_stack_index(self, index):
988
self.tabs.setCurrentIndex(index)
990
def set_tabbar_visible(self, state):
991
self.tabs.tabBar().setVisible(state)
993
def remove_from_data(self, index):
994
self.tabs.blockSignals(True)
995
self.tabs.removeTab(index)
997
self.tabs.blockSignals(False)
998
self.update_actions()
999
self.update_filelistdialog()
1001
def __modified_readonly_title(self, title, is_modified, is_readonly):
1002
if is_modified is not None and is_modified:
1004
if is_readonly is not None and is_readonly:
1005
title = "(%s)" % title
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)
1013
def get_tab_tip(self, filename, is_modified=None, is_readonly=None):
1014
"""Return tab menu title"""
1015
if self.fullpath_sorting_enabled:
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)
1026
return text % (temp_file_str, self.tempfile_path)
1028
if self.fullpath_sorting_enabled:
1031
return text % (osp.basename(filename), osp.dirname(filename))
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))
1038
return lambda item: osp.basename(item.filename)
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)
1049
self.set_stack_index(index)
1050
self.current_changed(index)
1051
self.update_actions()
1052
self.update_filelistdialog()
1054
def __repopulate_stack(self):
1055
self.tabs.blockSignals(True)
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()
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()
1078
self.set_stack_index(new_index)
1079
if self.outlineexplorer is not None:
1080
self.outlineexplorer.file_renamed(finfo.editor, finfo.filename)
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)
1093
#------ Context menu
1094
def __setup_menu(self):
1095
"""Setup tab context menu before showing it"""
1098
actions = self.menu_actions
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)
1106
#------ Hor/Ver splitting
1107
def __get_split_actions(self):
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()")))
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]
1126
def reset_orientation(self):
1127
self.horsplit_action.setEnabled(True)
1128
self.versplit_action.setEnabled(True)
1130
def set_orientation(self, orientation):
1131
self.horsplit_action.setEnabled(orientation == Qt.Horizontal)
1132
self.versplit_action.setEnabled(orientation == Qt.Vertical)
1134
def update_actions(self):
1135
state = self.get_stack_count() > 0
1136
self.horsplit_action.setEnabled(state)
1137
self.versplit_action.setEnabled(state)
1141
def get_current_filename(self):
1143
return self.data[self.get_stack_index()].filename
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):
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
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
1165
return self.has_filename(filename)
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()
1177
index = current_index
1179
self.find_widget.set_editor(None)
1183
if current_index == index:
1184
new_index = self._get_previous_file_index()
1186
new_index = current_index
1187
is_ok = force or self.save_if_changed(cancelable=True, index=index)
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)
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)
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()'))
1207
if new_index is not None:
1208
if index < new_index:
1210
self.set_stack_index(new_index)
1213
def close_all_files(self):
1214
"""Close all opened scripts"""
1215
while self.close_file():
1220
def save_if_changed(self, cancelable=False, index=None):
1221
"""Ask user to save file if modified"""
1223
indexes = range(self.get_stack_count())
1226
buttons = QMessageBox.Yes | QMessageBox.No
1228
buttons |= QMessageBox.Cancel
1230
for index in indexes:
1231
if self.data[index].editor.document().isModified():
1237
buttons |= QMessageBox.YesAll | QMessageBox.NoAll
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:
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),
1251
if answer == QMessageBox.Yes:
1254
elif answer == QMessageBox.YesAll:
1258
elif answer == QMessageBox.NoAll:
1260
elif answer == QMessageBox.Cancel:
1264
def save(self, index=None, force=False):
1267
# Save the currently edited file
1268
if not self.get_stack_count():
1270
index = self.get_stack_index()
1272
finfo = self.data[index]
1273
if not finfo.editor.document().isModified() and not force:
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())
1282
finfo.encoding = encoding.write(txt, finfo.filename,
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()
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()
1300
self._refresh_outlineexplorer(index)
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),
1310
def file_saved_in_other_editorstack(self, index):
1312
File was just saved in another editorstack, let's synchronize!
1313
This avoid file to be automatically reloaded
1315
finfo = self.data[index]
1316
finfo.newly_created = False
1317
finfo.lastmodified = QFileInfo(finfo.filename).lastModified()
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)
1325
return osp.normpath(filename)
1327
def save_as(self, index=None):
1328
"""Save file as..."""
1330
# Save the currently edited file
1331
index = self.get_stack_index()
1332
finfo = self.data[index]
1333
filename = self.select_savename(finfo.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):
1340
if ao_index < index:
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)
1351
"""Save all opened files"""
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))
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()
1365
def analyze_script(self, index=None):
1366
"""Analyze current script with pyflakes + find todos"""
1367
if self.is_analysis_done:
1370
index = self.get_stack_index()
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
1380
def set_analysis_results(self, index, analysis_results):
1381
"""Synchronize analysis results between editorstacks"""
1382
self.data[index].set_analysis_results(analysis_results)
1384
def get_analysis_results(self):
1386
return self.data[self.get_stack_index()].analysis_results
1388
def set_todo_results(self, index, todo_results):
1389
"""Synchronize todo results between editorstacks"""
1390
self.data[index].set_todo_results(todo_results)
1392
def get_todo_results(self):
1394
return self.data[self.get_stack_index()].todo_results
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)
1402
editor = self.get_current_editor()
1406
print >>STDOUT, "setfocusto:", editor
1408
self.emit(SIGNAL('reset_statusbar()'))
1409
self.emit(SIGNAL('opened_files_list_changed()'))
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)
1422
print >>STDOUT, "current_changed:", index, self.data[index].editor,
1423
print >>STDOUT, self.data[index].editor.get_document_id()
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'))
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:
1440
def go_to_previous_file(self):
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())]
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)
1459
elif len(self.stack_history) == 0 and self.get_stack_count():
1460
self.stack_history = [id(self.tabs.currentWidget())]
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:
1468
self.emit(SIGNAL("editor_focus_changed()"))
1470
def _refresh_outlineexplorer(self, index=None, update=True, clear=False):
1471
"""Refresh outline explorer panel"""
1472
oe = self.outlineexplorer
1476
index = self.get_stack_index()
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() \
1488
oe.set_current_editor(finfo.editor, finfo.filename,
1489
update=update, clear=clear)
1491
oe.setEnabled(False)
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)'),
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)
1508
finfo.editor.setReadOnly(read_only)
1509
self.emit(SIGNAL('readonly_changed(bool)'), read_only)
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
1520
self.__file_status_flag = True
1522
finfo = self.data[index]
1523
if finfo.newly_created:
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)
1537
finfo.newly_created = True
1538
finfo.editor.document().setModified(True)
1539
self.modification_changed(index=index)
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,
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:
1555
finfo.lastmodified = lastm
1559
# Finally, resetting temporary flag:
1560
self.__file_status_flag = False
1562
def refresh(self, index=None):
1563
"""Refresh tabwidget"""
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
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()'))
1580
# Update the modification-state-dependent parameters
1581
self.modification_changed()
1582
# Update FindReplace binding
1583
self.find_widget.set_editor(editor, refresh=False)
1585
def modification_changed(self, state=None, index=None, editor_id=None):
1587
Current editor's modification state has changed
1588
--> change tab title depending on new modification state
1589
--> enable/disable save/save all actions
1591
if editor_id is not None:
1592
for index, _finfo in enumerate(self.data):
1593
if id(_finfo.editor) == editor_id:
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()'))
1600
index = self.get_stack_index()
1603
finfo = self.data[index]
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)
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)
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:
1645
def create_new_editor(self, fname, enc, txt,
1646
set_current, new=False, cloned_from=None):
1648
Create a new editor instance
1649
Returns finfo object (instead of editor as in previous releases)
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)"),
1666
self.emit(SIGNAL("save_breakpoints(QString,QString)"),
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)'),
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()"),
1709
self.outlineexplorer.remove_editor(obj))
1711
self.find_widget.set_editor(editor)
1713
self.emit(SIGNAL('refresh_file_dependent_actions()'))
1714
self.modification_changed(index=self.data.index(finfo))
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)'),
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:
1727
if self.inspector is not None \
1728
and (force or self.inspector.dockwidget.isVisible()):
1729
# ObjectInspector widget exists and is visible
1731
self.inspector.set_object_text(qstr1, ignore_unknown=True,
1732
force_refresh=force)
1734
self.inspector.set_rope_doc(unicode(qstr1), unicode(qstr2),
1735
force_refresh=force)
1736
editor = self.get_current_editor()
1739
def new(self, filename, encoding, text):
1741
Create new filename with *encoding* and *text*
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)
1749
def load(self, filename, set_current=True):
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)
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,
1772
self.set_os_eol_chars(index)
1773
self.is_analysis_done = False
1776
def set_os_eol_chars(self, index=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)
1784
def remove_trailing_spaces(self, index=None):
1785
"""Remove trailing spaces"""
1787
index = self.get_stack_index()
1788
finfo = self.data[index]
1789
finfo.editor.remove_trailing_spaces()
1791
def fix_indentation(self, index=None):
1792
"""Replace tab characters by spaces"""
1794
index = self.get_stack_index()
1795
finfo = self.data[index]
1796
finfo.editor.fix_indentation()
1799
def run_selection_or_block(self):
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
1805
self.emit(SIGNAL('external_console_execute_lines(QString)'),
1806
self.get_current_editor().get_executable_text())
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()
1818
elif source.hasText():
1819
event.acceptProposedAction()
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)
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()
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)
1846
self.toolbar_list = None
1847
self.menu_list = None
1849
self.plugin = plugin
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
1858
self.menu_actions = menu_actions
1859
self.editorstack = EditorStack(self, menu_actions)
1860
self.register_editorstack_cb(self.editorstack)
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)
1871
def closeEvent(self, event):
1872
QSplitter.closeEvent(self, event)
1874
self.emit(SIGNAL('destroyed()'))
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()
1881
def editorstack_closed(self):
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
1889
close_splitter = self.count() == 1
1890
except RuntimeError:
1891
# editorsplitter has been destroyed (happens when closing a
1892
# EditorMainWindow instance)
1895
# editorstack just closed was the last widget in this QSplitter
1898
self.__give_focus_to_remaining_editor()
1900
def editorsplitter_closed(self):
1902
print >>STDOUT, "method 'editorsplitter_closed':"
1903
print >>STDOUT, " self :", self
1904
# print >>STDOUT, " sender:", self.sender()
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)
1912
# editorsplitter just closed was the last widget in this QSplitter
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()
1921
def split(self, orientation=Qt.Vertical):
1922
self.setOrientation(orientation)
1923
self.editorstack.set_orientation(orientation)
1924
editorsplitter = EditorSplitter(self.parent(), self.plugin,
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()
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()
1942
def get_layout_settings(self):
1943
"""Return layout state"""
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)
1953
def set_layout_settings(self, settings):
1954
"""Restore layout state"""
1955
splitsettings = settings.get('splitsettings')
1956
if splitsettings is None:
1960
for index, (is_vertical, cfname, clines) in enumerate(splitsettings):
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:
1980
#==============================================================================
1981
# Status bar widgets
1982
#==============================================================================
1983
class StatusBarWidget(QWidget):
1984
def __init__(self, parent, statusbar):
1985
QWidget.__init__(self, parent)
1987
self.label_font = font = get_font('editor')
1988
font.setPointSize(self.font().pointSize())
1991
layout = QHBoxLayout()
1992
layout.setContentsMargins(0, 0, 0, 0)
1993
self.setLayout(layout)
1996
statusbar.addPermanentWidget(self)
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)
2008
def readonly_changed(self, readonly):
2009
readwrite = "R" if readonly else "RW"
2010
self.readwrite.setText(readwrite.ljust(3))
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:")))
2019
self.eol.setFont(self.label_font)
2020
layout.addWidget(self.eol)
2021
layout.addSpacing(20)
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"))
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)
2038
def encoding_changed(self, encoding):
2039
self.encoding.setText(str(encoding).upper().ljust(15))
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)
2056
def cursor_position_changed(self, line, index):
2057
self.line.setText("%-6d" % (line+1))
2058
self.column.setText("%-4d" % (index+1))
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)
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)
2074
self.editorstacks = []
2076
self.plugin = plugin
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()))
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)
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)
2110
# Refreshing outline explorer
2111
for index in range(editorsplitter.editorstack.get_stack_count()):
2112
editorsplitter.editorstack._refresh_outlineexplorer(index,
2115
def register_editorstack(self, editorstack):
2116
self.editorstacks.append(editorstack)
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])
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
2150
def unregister_editorstack(self, editorstack):
2152
print >>STDOUT, "EditorWidget.unregister_editorstack:", editorstack
2153
self.plugin.unregister_editorstack(editorstack)
2154
self.editorstacks.pop(self.editorstacks.index(editorstack))
2156
self.__print_editorstacks()
2159
class EditorMainWindow(QMainWindow):
2160
def __init__(self, plugin, menu_actions, toolbar_list, menu_list,
2161
show_fullpath, fullpath_sorting, show_all_files,
2163
QMainWindow.__init__(self)
2164
self.setAttribute(Qt.WA_DeleteOnClose)
2166
self.window_size = None
2168
self.editorwidget = EditorWidget(self, plugin, menu_actions,
2169
show_fullpath, fullpath_sorting,
2170
show_all_files, show_comments)
2171
self.setCentralWidget(self.editorwidget)
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:
2179
self.setWindowTitle("Spyder - %s" % plugin.windowTitle())
2180
self.setWindowIcon(plugin.windowIcon())
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)
2190
quit_action = create_action(self, _("Close window"),
2191
icon="close_panel.png",
2192
tip=_("Close this window"),
2193
triggered=self.close)
2195
for index, (title, actions) in enumerate(menu_list):
2196
menu = self.menuBar().addMenu(title)
2199
add_actions(menu, actions+[None, quit_action])
2201
add_actions(menu, actions)
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)
2210
def closeEvent(self, event):
2211
"""Reimplement Qt method"""
2212
QMainWindow.closeEvent(self, event)
2214
self.emit(SIGNAL('destroyed()'))
2215
for editorstack in self.editorwidget.editorstacks[:]:
2217
print >>STDOUT, "--> destroy_editorstack:", editorstack
2218
editorstack.emit(SIGNAL('destroyed()'))
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)
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')
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)
2251
class EditorPluginExample(QSplitter):
2253
QSplitter.__init__(self)
2257
self.editorstacks = []
2258
self.editorwindows = []
2260
self.last_focus_editorstack = {} # fake
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)"),
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,
2275
editor_layout.addWidget(self.find_widget)
2277
self.setContentsMargins(0, 0, 0, 0)
2278
self.addWidget(editor_widgets)
2279
self.addWidget(self.outlineexplorer)
2281
self.setStretchFactor(0, 5)
2282
self.setStretchFactor(1, 1)
2284
self.menu_actions = menu_actions
2285
self.toolbar_list = None
2286
self.menu_list = None
2287
self.setup_window([], [])
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)
2295
def closeEvent(self, event):
2296
for win in self.editorwindows[:]:
2299
print >>STDOUT, len(self.editorwindows), ":", self.editorwindows
2300
print >>STDOUT, len(self.editorstacks), ":", self.editorstacks
2304
def load(self, fname):
2305
QApplication.processEvents()
2306
editorstack = self.editorstacks[0]
2307
editorstack.load(fname)
2308
editorstack.analyze_script()
2310
def register_editorstack(self, editorstack):
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])
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)'),
2336
def unregister_editorstack(self, editorstack):
2338
print >>STDOUT, "FakePlugin.unregister_editorstack:", editorstack
2339
self.editorstacks.pop(self.editorstacks.index(editorstack))
2341
def clone_editorstack(self, editorstack):
2342
editorstack.clone_from(self.editorstacks[0])
2344
def setup_window(self, toolbar_list, menu_list):
2345
self.toolbar_list = toolbar_list
2346
self.menu_list = menu_list
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())
2355
self.register_editorwindow(window)
2356
self.connect(window, SIGNAL("destroyed()"),
2357
lambda win=window: self.unregister_editorwindow(win))
2359
def register_editorwindow(self, window):
2361
print >>STDOUT, "register_editorwindowQObject*:", window
2362
self.editorwindows.append(window)
2364
def unregister_editorwindow(self, window):
2366
print >>STDOUT, "unregister_editorwindow:", window
2367
self.editorwindows.pop(self.editorwindows.index(window))
2369
def get_focus_widget(self):
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)
2379
def register_widget_shortcuts(self, context, widget):
2384
from spyderlib.utils.qthelpers import qapplication
2385
app = qapplication()
2386
test = EditorPluginExample()
2387
test.resize(900, 700)
2390
test.load("explorer.py")
2391
test.load("dicteditor.py")
2392
test.load("sourcecode/codeeditor.py")
2393
sys.exit(app.exec_())
2395
if __name__ == "__main__":