root/branch/twedit/Pyeditor.py

Revision 1595, 31.9 kB (checked in by mswat, 16 months ago)
Line 
1
2
3
4# -*- coding: utf-8 -*-
5#
6# Copyright © 2009 Pierre Raybaut
7# Licensed under the terms of the MIT License
8# (see pydeelib/__init__.py for details)
9
10"""Editor widget based on QScintilla"""
11
12# pylint: disable-msg=C0103
13# pylint: disable-msg=R0903
14# pylint: disable-msg=R0911
15# pylint: disable-msg=R0201
16
17import sys, os, re
18from math import log
19
20from PyQt4.QtGui import QMouseEvent, QColor, QMenu, QPixmap
21from PyQt4.QtCore import Qt, SIGNAL, QString, QEvent, QTimer
22from PyQt4.Qsci import (QsciScintilla, QsciAPIs, QsciLexerCPP, QsciLexerCSS,
23                        QsciLexerDiff, QsciLexerHTML, QsciLexerPython,
24                        QsciLexerProperties, QsciLexerBatch)
25try:
26    # In some official binary PyQt4 distributions,
27    # the Fortran lexers are not included
28    from PyQt4.Qsci import QsciLexerFortran, QsciLexerFortran77
29except ImportError:
30    QsciLexerFortran = None
31    QsciLexerFortran77 = None
32
33# For debugging purpose:
34STDOUT = sys.stdout
35
36# Local import
37from pydeelib.config import CONF, get_font, get_icon, get_image_path
38from pydeelib.qthelpers import (add_actions, create_action, keybinding,
39                                 translate)
40from pydeelib.widgets.qscibase import QsciBase
41
42
43#===============================================================================
44# Pyflakes code analysis
45#===============================================================================
46import compiler
47from pydeelib.pyflakes import checker
48
49def check(filename):
50    try:
51        tree = compiler.parse(file(filename, 'U').read() + '\n')
52    except (SyntaxError, IndentationError), e:
53        message = e.args[0]
54        value = sys.exc_info()[1]
55        try:
56            (lineno, _offset, _text) = value[1][1:]
57        except IndexError:
58            # Could not compile script
59            return
60        return [ (message, lineno, True) ]
61    else:
62        results = []
63        w = checker.Checker(tree, filename)
64        w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
65        for warning in w.messages:
66            results.append( (warning.message % warning.message_args,
67                             warning.lineno, False) )
68        return results
69
70if __name__ == '__main__':
71    check_results = check(os.path.abspath("../pydee.py"))
72    for message, line, error in check_results:
73        print "Message: %s -- Line: %s -- Error? %s" % (message, line, error)
74
75   
76#===============================================================================
77# QsciEditor widget
78#===============================================================================
79class QsciEditor(QsciBase):
80    """
81    QScintilla Base Editor Widget
82    """
83    LEXERS = {
84              ('py', 'pyw', 'python'): (QsciLexerPython, '#'),
85              ('f', 'for'): (QsciLexerFortran77, 'c'),
86              ('f90', 'f95', 'f2k'): (QsciLexerFortran, '!'),
87              ('diff', 'patch', 'rej'): (QsciLexerDiff, ''),
88              'css': (QsciLexerCSS, '#'),
89              ('htm', 'html'): (QsciLexerHTML, ''),
90              ('c', 'cpp', 'h'): (QsciLexerCPP, '//'),
91              ('bat', 'cmd', 'nt'): (QsciLexerBatch, 'rem '),
92              ('properties', 'session', 'ini', 'inf', 'reg', 'url',
93               'cfg', 'cnf', 'aut', 'iss'): (QsciLexerProperties, '#'),
94              }
95    TAB_ALWAYS_INDENTS = ('py', 'pyw', 'python', 'c', 'cpp', 'h')
96    OCCURENCE_INDICATOR = QsciScintilla.INDIC_CONTAINER
97    print "demo print statement"
98    def __init__(self, parent=None, linenumbers=True, language=None,
99                 code_analysis=False, code_folding=False):
100        QsciBase.__init__(self, parent)
101                   
102        # Indicate occurences of the selected word
103        self.connect(self, SIGNAL('cursorPositionChanged(int, int)'),
104                     self.__cursor_position_changed)
105        self.__find_start = None
106        self.__find_end = None
107        self.__find_flags = None
108        self.SendScintilla(QsciScintilla.SCI_INDICSETSTYLE,
109                           self.OCCURENCE_INDICATOR,
110                           QsciScintilla.INDIC_BOX)
111        self.SendScintilla(QsciScintilla.SCI_INDICSETFORE,
112                           self.OCCURENCE_INDICATOR,
113                           0x4400FF)
114
115        self.supported_language = None
116        self.comment_string = None
117
118        # Mark errors, warnings, ...
119        self.markers = []
120        self.marker_lines = {}
121        self.error = self.markerDefine(QPixmap(get_image_path('error.png'),
122                                               'png'))
123        self.warning = self.markerDefine(QPixmap(get_image_path('warning.png'),
124                                                 'png'))
125           
126        # Scintilla Python API
127        self.api = None
128       
129        # Mark occurences timer
130        self.occurences_timer = QTimer(self)
131        self.occurences_timer.setSingleShot(True)
132        self.occurences_timer.setInterval(750)
133        self.connect(self.occurences_timer, SIGNAL("timeout()"),
134                     self.__mark_occurences)
135       
136        # Context menu
137        self.setup_context_menu()
138       
139        # Tab key behavior
140        self.tab_indents = None
141        self.tab_mode = True # see QsciEditor.set_tab_mode
142       
143    def setup_editor(self, linenumbers=True, language=None,
144                     code_analysis=False, code_folding=False,
145                     font=None, wrap=True):
146        # Lexer
147        self.set_language(language)
148               
149        # Tab always indents (event when cursor is not at the begin of line)
150        self.tab_indents = language in self.TAB_ALWAYS_INDENTS
151        if linenumbers:
152            self.connect( self, SIGNAL('linesChanged()'), self.__lines_changed )
153        self.setup_margins(linenumbers, code_analysis, code_folding)
154
155        if font is not None:
156            self.set_font(font)
157        self.set_wrap_mode(wrap)
158        self.setup_api()
159        self.setModified(False)
160       
161    def set_tab_mode(self, enable):
162        """
163        enabled = tab always indent
164        (otherwise tab indents only when cursor is at the beginning of a line)
165        """
166        self.tab_mode = enable
167       
168    def set_language(self, language):
169        self.supported_language = False
170        self.comment_string = ''
171        if language is not None:
172            for key in self.LEXERS:
173                if language.lower() in key:
174                    self.supported_language = True
175                    lexer_class, comment_string = self.LEXERS[key]
176                    self.comment_string = comment_string
177                    if lexer_class is not None:
178                        # Fortran lexers are sometimes unavailable:
179                        # the corresponding class is then replaced by None
180                        # (see the import lines at the beginning of the script)
181                        self.setLexer( lexer_class(self) )
182                    break
183       
184       
185#===============================================================================
186#    QScintilla
187#===============================================================================
188    def setup_scintilla(self):
189        """Reimplement QsciBase method"""
190        QsciBase.setup_scintilla(self)
191       
192        # Wrapping
193        if CONF.get('editor', 'wrapflag'):
194            self.setWrapVisualFlags(QsciScintilla.WrapFlagByBorder)
195       
196        # Indentation
197        self.setIndentationGuides(True)
198        self.setIndentationGuidesForegroundColor(Qt.lightGray)
199       
200        # 80-columns edge
201        self.setEdgeColumn(80)
202        self.setEdgeMode(QsciScintilla.EdgeLine)
203       
204        # Auto-completion
205        self.setAutoCompletionThreshold(-1)
206        self.setAutoCompletionSource(QsciScintilla.AcsAll)
207
208    def setup_margins(self, linenumbers=True,
209                      code_analysis=False, code_folding=False):
210        """
211        Setup margin settings
212        (except font, now set in self.set_font)
213        """
214        for i_margin in range(5):
215            # Reset margin settings
216            self.setMarginWidth(i_margin, 0)
217            self.setMarginLineNumbers(i_margin, False)
218            self.setMarginMarkerMask(i_margin, 0)
219            self.setMarginSensitivity(i_margin, False)
220        if linenumbers:
221            # 1: Line numbers margin
222            self.setMarginLineNumbers(1, True)
223            self.update_line_numbers_margin()
224            if code_analysis:
225                # 2: Errors/warnings margin
226                mask = (1 << self.error) | (1 << self.warning)
227                self.setMarginSensitivity(0, True)
228                self.setMarginMarkerMask(0, mask)
229                self.setMarginWidth(0, 14)
230                self.connect(self,
231                     SIGNAL('marginClicked(int,int,Qt::KeyboardModifiers)'),
232                     self.__margin_clicked)
233        if code_folding:
234            # 0: Folding margin
235            self.setMarginWidth(2, 14)
236            self.setFolding(QsciScintilla.BoxedFoldStyle)               
237        # Colors
238        fcol = CONF.get('scintilla', 'margins/foregroundcolor')
239        bcol = CONF.get('scintilla', 'margins/backgroundcolor')
240        if fcol:
241            self.setMarginsForegroundColor(QColor(fcol))
242        if bcol:
243            self.setMarginsBackgroundColor(QColor(bcol))
244        fcol = CONF.get('scintilla', 'foldmarginpattern/foregroundcolor')
245        bcol = CONF.get('scintilla', 'foldmarginpattern/backgroundcolor')
246        if fcol and bcol:
247            self.setFoldMarginColors(QColor(fcol), QColor(bcol))
248       
249    def setup_api(self):
250        """Load and prepare API"""
251        if self.lexer() is None:
252            return
253        self.api = QsciAPIs(self.lexer())
254        is_api_ready = False
255        api_path = CONF.get('editor', 'api')
256        if not os.path.isfile(api_path):
257            return False
258        api_stat = CONF.get('editor', 'api_stat', None)
259        current_api_stat = os.stat(api_path)
260        if (api_stat is not None) and (api_stat == current_api_stat):
261            if self.api.isPrepared():
262                is_api_ready = self.api.loadPrepared()
263        else:
264            CONF.set('editor', 'api_stat', current_api_stat)
265        if not is_api_ready:
266            if self.api.load(api_path):
267                self.api.prepare()
268                self.connect(self.api, SIGNAL("apiPreparationFinished()"),
269                             self.api.savePrepared)
270        return is_api_ready
271   
272    def set_eol_mode(self, text):
273        """
274        Set QScintilla widget EOL mode based on *text* EOL characters
275        """
276        if isinstance(text, QString):
277            text = unicode(text)
278        if text.find("\r\n") > -1:
279            self.setEolMode( QsciScintilla.EolMode(QsciScintilla.EolWindows) )
280        elif text.find("\n") > -1:
281            self.setEolMode( QsciScintilla.EolMode(QsciScintilla.EolUnix) )
282        elif text.find("\r") > -1:
283            self.setEolMode( QsciScintilla.EolMode(QsciScintilla.EolMac) )
284        else:
285            return None
286       
287    def get_line_separator(self):
288        """Return line separator based on current EOL mode"""
289        mode = self.eolMode()
290        if mode == QsciScintilla.EolWindows:
291            return '\r\n'
292        elif mode == QsciScintilla.EolUnix:
293            return '\n'
294        elif mode == QsciScintilla.EolMac:
295            return '\r'
296        else:
297            return ''
298   
299    def __find_first(self, text):
300        """Find first occurence"""
301        self.__find_flags = QsciScintilla.SCFIND_MATCHCASE | \
302                            QsciScintilla.SCFIND_WHOLEWORD
303        self.__find_start = 0
304        line = self.lines()-1
305        self.__find_end = self.position_from_lineindex(line,
306                                                       self.text(line).length())
307        return self.__find_next(text)
308   
309    def __find_next(self, text):
310        """Find next occurence"""
311        if self.__find_start == self.__find_end:
312            return False
313       
314        self.SendScintilla(QsciScintilla.SCI_SETTARGETSTART,
315                           self.__find_start)
316        self.SendScintilla(QsciScintilla.SCI_SETTARGETEND,
317                           self.__find_end)
318        self.SendScintilla(QsciScintilla.SCI_SETSEARCHFLAGS,
319                           self.__find_flags)
320        pos = self.SendScintilla(QsciScintilla.SCI_SEARCHINTARGET,
321                                 len(text), text)
322       
323        if pos == -1:
324            return False
325        self.__find_start = self.SendScintilla(QsciScintilla.SCI_GETTARGETEND)
326        return True
327       
328    def __get_found_occurence(self):
329        """Return found occurence"""
330        spos = self.SendScintilla(QsciScintilla.SCI_GETTARGETSTART)
331        epos = self.SendScintilla(QsciScintilla.SCI_GETTARGETEND)
332        return (spos, epos - spos)
333       
334    def __cursor_position_changed(self):
335        """Cursor position has changed"""
336        #TODO: Add attribute for occurences marking enable/disable:
337        # if self.occurences_marking:
338        self.occurences_timer.stop()
339        self.occurences_timer.start()
340       
341    def __mark_occurences(self):
342        """Marking occurences of the currently selected word"""
343        self.SendScintilla(QsciScintilla.SCI_SETINDICATORCURRENT,
344                           self.OCCURENCE_INDICATOR)
345        self.SendScintilla(QsciScintilla.SCI_INDICATORCLEARRANGE,
346                           0, self.length())
347
348        if not self.supported_language:
349            return
350           
351        text = self.get_current_word()
352        if text.isEmpty():
353            return
354
355        # Highlighting all occurences of word *text*
356        ok = self.__find_first(text)
357        while ok:
358            spos = self.SendScintilla(QsciScintilla.SCI_GETTARGETSTART)
359            epos = self.SendScintilla(QsciScintilla.SCI_GETTARGETEND)
360            self.SendScintilla(QsciScintilla.SCI_INDICATORFILLRANGE,
361                               spos, epos-spos)
362            ok = self.__find_next(text)
363       
364    def __lines_changed(self):
365        """Update margin"""
366        self.update_line_numbers_margin()
367       
368    def update_line_numbers_margin(self):
369        """Update margin width"""
370        width = log(self.lines(), 10) + 2
371        self.setMarginWidth(1, QString('0'*int(width)))
372
373    def delete(self):
374        """Remove selected text"""
375        # Used by global callbacks in Pydee -> delete_action
376        QsciScintilla.removeSelectedText(self)
377
378    def set_font(self, font):
379        """Set shell font"""
380        if self.lexer() is None:
381            self.setFont(font)
382        else:
383            self.lexer().setFont(font)
384            self.setLexer(self.lexer())
385        self.setMarginsFont(font)
386       
387    def set_text(self, text):
388        """Set the text of the editor"""
389        self.setText(text)
390        self.set_eol_mode(text)
391
392    def get_text(self):
393        """Return editor text"""
394        return self.text()
395   
396    def insert_text(self, text):
397        """Insert text at cursor position"""
398        line, col = self.getCursorPosition()
399        self.insertAt(text, line, col)
400        self.setCursorPosition(line, col + len(unicode(text)))
401       
402    def fold_header(self, line):
403        """Is it a fold header line?"""
404        lvl = self.SendScintilla(QsciScintilla.SCI_GETFOLDLEVEL, line)
405        return lvl & QsciScintilla.SC_FOLDLEVELHEADERFLAG
406   
407    def fold_expanded(self, line):
408        """Is fold expanded?"""
409        return self.SendScintilla(QsciScintilla.SCI_GETFOLDEXPANDED, line)
410       
411    def get_folded_lines(self):
412        """Return the list of folded line numbers"""
413        return [line for line in xrange(self.lines()) \
414                if self.fold_header(line) and not self.fold_expanded(line) ]
415       
416    def unfold_all(self):
417        """Unfold all folded lines"""
418        for line in self.get_folded_lines():
419            self.foldLine(line)
420       
421       
422#===============================================================================
423#    High-level editor features
424#===============================================================================
425    def highlight_line(self, line):
426        """Highlight line number *line*"""
427        text = unicode(self.text(line-1)).rstrip()
428        self.setSelection(line-1, 0, line-1, len(text))
429        self.ensureLineVisible(line-1)
430
431    def cleanup_code_analysis(self):
432        """Remove all code analysis markers"""
433        for marker in self.markers:
434            self.markerDeleteHandle(marker)
435        self.markers = []
436        self.marker_lines = {}
437       
438    def process_code_analysis(self, check_results):
439        """Analyze filename code with pyflakes"""
440        self.cleanup_code_analysis()
441        if check_results is None:
442            # Not able to compile module
443            return
444        for message, line0, error in check_results:
445            line1 = line0 - 1
446            marker = self.markerAdd(line1, 0 if error else 1)
447            self.markers.append(marker)
448            if line1 not in self.marker_lines:
449                self.marker_lines[line1] = []
450            self.marker_lines[line1].append( (message, error) )
451
452    def __highlight_warning(self, line):
453        self.highlight_line(line+1)
454        self.__show_code_analysis_results(line)
455
456    def go_to_next_warning(self):
457        """Go to next code analysis warning message"""
458        cline, _ = self.getCursorPosition()
459        lines = sorted(self.marker_lines.keys())
460        for line in lines:
461            if line > cline:
462                self.__highlight_warning(line)
463                return
464        else:
465            self.__highlight_warning(lines[0])
466
467    def go_to_previous_warning(self):
468        """Go to previous code analysis warning message"""
469        cline, _ = self.getCursorPosition()
470        lines = sorted(self.marker_lines.keys(), reverse=True)
471        for line in lines:
472            if line < cline:
473                self.__highlight_warning(line)
474                return
475        else:
476            self.__highlight_warning(lines[0])
477
478    def __show_code_analysis_results(self, line):
479        """Show warning/error messages"""
480        if line in self.marker_lines:
481            msglist = [ msg for msg, _error in self.marker_lines[line] ]
482            self.show_calltip(self.tr("Code analysis"), msglist,
483                              color='#129625', at_line=line)
484   
485    def __margin_clicked(self, margin, line, modifier):
486        """Margin was clicked, that's for sure!"""
487        if margin == 0:
488            self.__show_code_analysis_results(line)
489
490    def mouseMoveEvent(self, event):
491        line = self.lineAt(event.pos())
492        self.__show_code_analysis_results(line)
493        QsciScintilla.mouseMoveEvent(self, event)
494       
495    def add_prefix(self, prefix):
496        """Add prefix to current line or selected line(s)"""       
497        if self.hasSelectedText():
498            # Add prefix to selected line(s)
499            line_from, index_from, line_to, index_to = self.getSelection()
500            if index_to == 0:
501                line_to -= 1
502            self.beginUndoAction()
503            for line in range(line_from, line_to+1):
504                self.insertAt(prefix, line, 0)
505            self.endUndoAction()
506            if index_to == 0:
507                line_to += 1
508            else:
509                index_to += len(prefix)
510            self.setSelection(line_from, index_from+len(prefix),
511                              line_to, index_to)
512        else:
513            # Add prefix to current line
514            line, index = self.getCursorPosition()
515            self.beginUndoAction()
516            self.insertAt(prefix, line, 0)
517            self.endUndoAction()
518            self.setCursorPosition(line, index+len(prefix))
519   
520    def remove_prefix(self, prefix):
521        """Remove prefix from current line or selected line(s)"""       
522        if self.hasSelectedText():
523            # Remove prefix from selected line(s)
524            line_from, index_from, line_to, index_to = self.getSelection()
525            if index_to == 0:
526                line_to -= 1
527            self.beginUndoAction()
528            for line in range(line_from, line_to+1):
529                if not self.text(line).startsWith(prefix):
530                    continue
531                self.setSelection(line, 0, line, len(prefix))
532                self.removeSelectedText()
533                if line == line_from:
534                    index_from = max([0, index_from-len(prefix)])
535                if line == line_to and index_to != 0:
536                    index_to = max([0, index_to-len(prefix)])
537            if index_to == 0:
538                line_to += 1
539            self.setSelection(line_from, index_from, line_to, index_to)
540            self.endUndoAction()
541        else:
542            # Remove prefix from current line
543            line, index = self.getCursorPosition()
544            if not self.text(line).startsWith(prefix):
545                return
546            self.beginUndoAction()
547            self.setSelection(line, 0, line, len(prefix))
548            self.removeSelectedText()
549            self.setCursorPosition(line, index-len(prefix))
550            self.endUndoAction()
551            self.setCursorPosition(line, max([0, index-len(prefix)]))
552   
553    def fix_indent(self, forward=True):
554        """
555        Fix indentation (Python only, no text selection)
556        forward=True: fix indent only if text is not enough indented
557                      (otherwise force indent)
558        forward=False: fix indent only if text is too much indented
559                       (otherwise force unindent)
560        """
561        if not isinstance(self.lexer(), QsciLexerPython):
562            return       
563        line, index = self.getCursorPosition()
564        prevtext = unicode(self.text(line-1)).rstrip()
565        indent = self.indentation(line)
566        correct_indent = self.indentation(line-1)
567        if prevtext.endswith(':'):
568            # Indent           
569            correct_indent += 4
570        elif prevtext.endswith('continue') or prevtext.endswith('break'):
571            # Unindent
572            correct_indent -= 4
573        elif prevtext.endswith(','):
574            rlmap = {")":"(", "]":"[", "}":"{"}
575            for par in rlmap:
576                i_right = prevtext.rfind(par)
577                if i_right != -1:
578                    prevtext = prevtext[:i_right]
579                    i_left = prevtext.rfind(rlmap[par])
580                    if i_left != -1:
581                        prevtext = prevtext[:i_left]
582                    else:
583                        break
584            else:
585                prevexpr = re.split(r'\(|\{|\[', prevtext)[-1]
586                correct_indent = len(prevtext)-len(prevexpr)
587        if forward:
588            if indent == correct_indent or indent > correct_indent:
589                # Force indent
590                correct_indent = indent + 4
591        elif indent == correct_indent or indent < correct_indent:
592            # Force unindent
593            correct_indent = indent - 4
594           
595        if correct_indent >= 0:
596            self.beginUndoAction()
597            self.setSelection(line, 0, line, indent)
598            self.removeSelectedText()
599            if index > indent:
600                index -= indent-correct_indent
601            else:
602                index = correct_indent
603            self.insertAt(" "*correct_indent, line, 0)
604            self.setCursorPosition(line, index)
605            self.endUndoAction()
606   
607    def __no_char_before_cursor(self):
608        line, index = self.getCursorPosition()
609        self.setSelection(line, 0, line, index)
610        selected_text = unicode(self.selectedText())
611        self.clear_selection()
612        return len(selected_text.strip()) == 0
613   
614    def indent(self):
615        """Indent current line or selection"""
616        if self.hasSelectedText():
617            self.add_prefix( " "*4 )
618        elif self.__no_char_before_cursor() or \
619             (self.tab_indents and self.tab_mode):
620            if isinstance(self.lexer(), QsciLexerPython):
621                self.fix_indent(forward=True)
622            else:
623                self.add_prefix( " "*4 )
624        else:
625            self.SendScintilla(QsciScintilla.SCI_TAB)
626   
627    def unindent(self):
628        """Unindent current line or selection"""
629        if self.hasSelectedText():
630            self.remove_prefix( " "*4 )
631        elif self.__no_char_before_cursor() or \
632             (self.tab_indents and self.tab_mode):
633            if isinstance(self.lexer(), QsciLexerPython):
634                self.fix_indent(forward=False)
635            else:
636                self.remove_prefix( " "*4 )
637           
638    def comment(self):
639        """Comment current line or selection"""
640        self.add_prefix(self.comment_string)
641
642    def uncomment(self):
643        """Uncomment current line or selection"""
644        self.remove_prefix(self.comment_string)
645   
646    def blockcomment(self):
647        """Block comment current line or selection"""
648        comline = self.comment_string + '='*(80-len(self.comment_string)) \
649                  + self.get_line_separator()
650        if self.hasSelectedText():
651            line_from, _index_from, line_to, _index_to = self.getSelection()
652            lines = range(line_from, line_to+1)
653        else:
654            line, _index = self.getCursorPosition()
655            lines = [line]
656        self.beginUndoAction()
657        self.insertAt( comline, lines[-1]+1, 0 )
658        self.insertAt( comline, lines[0], 0 )
659        for l in lines:
660            self.insertAt( '# ', l+1, 0 )
661        self.endUndoAction()
662        self.setCursorPosition(lines[-1]+2, 80)
663
664    def __is_comment_bar(self, line):
665        comline = '#' + '='*79 + self.get_line_separator()
666        self.setSelection(line, 0, line+1, 0)
667        return unicode(self.selectedText()) == comline           
668   
669    def unblockcomment(self):
670        """Un-block comment current line or selection"""
671        line, index = self.getCursorPosition()
672        self.setSelection(line, 0, line, 1)
673        if unicode(self.selectedText()) != '#':
674            self.setCursorPosition(line, index)
675            return
676        # Finding first comment bar
677        line1 = line-1
678        while line1 >= 0 and not self.__is_comment_bar(line1):
679            line1 -= 1
680        if not self.__is_comment_bar(line1):
681            self.setCursorPosition(line, index)
682            return
683        # Finding second comment bar
684        line2 = line+1
685        while line2 < self.lines() and not self.__is_comment_bar(line2):
686            line2 += 1
687        if not self.__is_comment_bar(line2) or line2 > self.lines()-2:
688            self.setCursorPosition(line, index)
689            return
690        lines = range(line1+1, line2)
691        self.beginUndoAction()
692        self.setSelection(line2, 0, line2+1, 0)
693        self.removeSelectedText()
694        for l in lines:
695            self.setSelection(l, 0, l, 2)
696            self.removeSelectedText()
697        self.setSelection(line1, 0, line1+1, 0)
698        self.removeSelectedText()
699        self.endUndoAction()
700   
701#===============================================================================
702#    Qt Event handlers
703#===============================================================================
704    def setup_context_menu(self):
705        """Setup context menu"""
706        self.undo_action = create_action(self,
707                           translate("SimpleEditor", "Undo"),
708                           shortcut=keybinding('Undo'),
709                           icon=get_icon('undo.png'), triggered=self.undo)
710        self.redo_action = create_action(self,
711                           translate("SimpleEditor", "Redo"),
712                           shortcut=keybinding('Redo'),
713                           icon=get_icon('redo.png'), triggered=self.redo)
714        self.cut_action = create_action(self,
715                           translate("SimpleEditor", "Cut"),
716                           shortcut=keybinding('Cut'),
717                           icon=get_icon('editcut.png'), triggered=self.cut)
718        self.copy_action = create_action(self,
719                           translate("SimpleEditor", "Copy"),
720                           shortcut=keybinding('Copy'),
721                           icon=get_icon('editcopy.png'), triggered=self.copy)
722        paste_action = create_action(self,
723                           translate("SimpleEditor", "Paste"),
724                           shortcut=keybinding('Paste'),
725                           icon=get_icon('editpaste.png'), triggered=self.paste)
726        self.delete_action = create_action(self,
727                           translate("SimpleEditor", "Delete"),
728                           shortcut=keybinding('Delete'),
729                           icon=get_icon('editdelete.png'),
730                           triggered=self.removeSelectedText)
731        selectall_action = create_action(self,
732                           translate("SimpleEditor", "Select all"),
733                           shortcut=keybinding('SelectAll'),
734                           icon=get_icon('selectall.png'),
735                           triggered=self.selectAll)
736        self.menu = QMenu(self)
737        add_actions(self.menu, (self.undo_action, self.redo_action, None,
738                                self.cut_action, self.copy_action,
739                                paste_action, self.delete_action,
740                                None, selectall_action))       
741        # Read-only context-menu
742        self.readonly_menu = QMenu(self)
743        add_actions(self.readonly_menu,
744                    (self.copy_action, None, selectall_action))       
745           
746    def keyPressEvent(self, event):
747        """Reimplement Qt method"""
748        key = event.key()
749        ctrl = event.modifiers() & Qt.ControlModifier
750        shift = event.modifiers() & Qt.ShiftModifier
751        # Zoom in/out
752        if ((key == Qt.Key_Plus) and ctrl) \
753             or ((key==Qt.Key_Equal) and shift and ctrl):
754            self.zoomIn()
755            event.accept()
756        elif (key == Qt.Key_Minus) and ctrl:
757            self.zoomOut()
758            event.accept()
759        # Indent/unindent
760        elif key == Qt.Key_Backtab:
761            self.unindent()
762            event.accept()
763        elif (key == Qt.Key_Tab):
764            if self.isListActive():
765                self.SendScintilla(QsciScintilla.SCI_TAB)
766            else:
767                self.indent()
768            event.accept()
769#TODO: find other shortcuts...
770#        elif (key == Qt.Key_3) and ctrl:
771#            self.comment()
772#            event.accept()
773#        elif (key == Qt.Key_2) and ctrl:
774#            self.uncomment()
775#            event.accept()
776#        elif (key == Qt.Key_4) and ctrl:
777#            self.blockcomment()
778#            event.accept()
779#        elif (key == Qt.Key_5) and ctrl:
780#            self.unblockcomment()
781#            event.accept()
782        else:
783            QsciScintilla.keyPressEvent(self, event)
784           
785    def mousePressEvent(self, event):
786        """Reimplement Qt method"""
787        if event.button() == Qt.MidButton:
788            self.setFocus()
789            event = QMouseEvent(QEvent.MouseButtonPress, event.pos(),
790                                Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
791            QsciScintilla.mousePressEvent(self, event)
792            QsciScintilla.mouseReleaseEvent(self, event)
793            self.paste()
794        else:
795            QsciScintilla.mousePressEvent(self, event)
796           
797    def contextMenuEvent(self, event):
798        """Reimplement Qt method"""
799        state = self.hasSelectedText()
800        self.copy_action.setEnabled(state)
801        self.cut_action.setEnabled(state)
802        self.delete_action.setEnabled(state)
803        self.undo_action.setEnabled( self.isUndoAvailable() )
804        self.redo_action.setEnabled( self.isRedoAvailable() )
805        menu = self.menu
806        if self.isReadOnly():
807            menu = self.readonly_menu
808        menu.popup(event.globalPos())
809        event.accept()
Note: See TracBrowser for help on using the browser.