24
24
from PyQt4 .Qsci import (QsciScintilla ,
25
25
QsciScintillaBase ,
26
26
QsciLexerPython ,
27
- QsciAPIs )
27
+ QsciAPIs ,
28
+ QsciStyle )
28
29
from qgis .core import QgsApplication
29
30
from qgis .gui import QgsMessageBar
30
31
import sys
@@ -73,6 +74,7 @@ def eventFilter(self, obj, event):
73
74
return QObject .eventFilter (self , obj , event )
74
75
75
76
class Editor (QsciScintilla ):
77
+ MARKER_NUM = 6
76
78
def __init__ (self , parent = None ):
77
79
super (Editor ,self ).__init__ (parent )
78
80
self .parent = parent
@@ -81,6 +83,9 @@ def __init__(self, parent=None):
81
83
self .opening = ['(' , '{' , '[' , "'" , '"' ]
82
84
self .closing = [')' , '}' , ']' , "'" , '"' ]
83
85
86
+ ## List of marker line to be deleted from check syntax
87
+ self .bufferMarkerLine = []
88
+
84
89
self .settings = QSettings ()
85
90
86
91
# Enable non-ascii chars for editor
@@ -95,24 +100,17 @@ def __init__(self, parent=None):
95
100
self .setMarginsFont (font )
96
101
# Margin 0 is used for line numbers
97
102
#fm = QFontMetrics(font)
98
- # fontmetrics = QFontMetrics(font)
103
+ fontmetrics = QFontMetrics (font )
99
104
self .setMarginsFont (font )
100
- self .setMarginWidth (1 , "00000" )
101
- self .setMarginLineNumbers (1 , True )
105
+ self .setMarginWidth (0 , fontmetrics . width ( "0000" ) + 5 )
106
+ self .setMarginLineNumbers (0 , True )
102
107
self .setMarginsForegroundColor (QColor ("#3E3EE3" ))
103
108
self .setMarginsBackgroundColor (QColor ("#f9f9f9" ))
104
109
self .setCaretLineVisible (True )
105
110
self .setCaretLineBackgroundColor (QColor ("#fcf3ed" ))
106
111
107
- # Clickable margin 1 for showing markers
108
- # self.setMarginSensitivity(1, True)
109
- # self.connect(self,
110
- # SIGNAL('marginClicked(int, int, Qt::KeyboardModifiers)'),
111
- # self.on_margin_clicked)
112
- # self.markerDefine(QsciScintilla.RightArrow,
113
- # self.ARROW_MARKER_NUM)
114
- # self.setMarkerBackgroundColor(QColor("#ee1111"),
115
- # self.ARROW_MARKER_NUM)
112
+ self .markerDefine (QgsApplication .getThemePixmap ("console/iconSyntaxErrorConsole.png" ),
113
+ self .MARKER_NUM )
116
114
117
115
self .setMinimumHeight (120 )
118
116
#self.setMinimumWidth(300)
@@ -138,7 +136,7 @@ def __init__(self, parent=None):
138
136
self .settingsEditor ()
139
137
140
138
# Annotations
141
- # self.setAnnotationDisplay(QsciScintilla.ANNOTATION_BOXED)
139
+ self .setAnnotationDisplay (QsciScintilla .ANNOTATION_BOXED )
142
140
143
141
# Indentation
144
142
self .setAutoIndent (True )
@@ -170,13 +168,17 @@ def __init__(self, parent=None):
170
168
self .runScriptScut .setContext (Qt .WidgetShortcut )
171
169
self .runScriptScut .activated .connect (self .runScriptCode )
172
170
171
+ self .syntaxCheckScut = QShortcut (QKeySequence (Qt .CTRL + Qt .Key_4 ), self )
172
+ self .syntaxCheckScut .setContext (Qt .WidgetShortcut )
173
+ self .syntaxCheckScut .activated .connect (self .syntaxCheck )
173
174
self .commentScut = QShortcut (QKeySequence (Qt .CTRL + Qt .Key_3 ), self )
174
175
self .commentScut .setContext (Qt .WidgetShortcut )
175
176
self .commentScut .activated .connect (self .parent .pc .commentCode )
176
177
self .uncommentScut = QShortcut (QKeySequence (Qt .SHIFT + Qt .CTRL + Qt .Key_3 ), self )
177
178
self .uncommentScut .setContext (Qt .WidgetShortcut )
178
179
self .uncommentScut .activated .connect (self .parent .pc .uncommentCode )
179
180
self .modificationChanged .connect (self .parent .modified )
181
+ self .modificationAttempted .connect (self .fileReadOnly )
180
182
181
183
def settingsEditor (self ):
182
184
# Set Python lexer
@@ -206,13 +208,6 @@ def autoCompleteKeyBinding(self):
206
208
elif radioButtonSource == 'fromDocAPI' :
207
209
self .autoCompleteFromAll ()
208
210
209
- def on_margin_clicked (self , nmargin , nline , modifiers ):
210
- # Toggle marker for the line the margin was clicked on
211
- if self .markersAtLine (nline ) != 0 :
212
- self .markerDelete (nline , self .ARROW_MARKER_NUM )
213
- else :
214
- self .markerAdd (nline , self .ARROW_MARKER_NUM )
215
-
216
211
def setLexers (self ):
217
212
from qgis .core import QgsApplication
218
213
@@ -275,6 +270,7 @@ def contextMenuEvent(self, e):
275
270
iconUncommentEditor = QgsApplication .getThemeIcon ("console/iconUncommentEditorConsole.png" )
276
271
iconSettings = QgsApplication .getThemeIcon ("console/iconSettingsConsole.png" )
277
272
iconFind = QgsApplication .getThemeIcon ("console/iconSearchEditorConsole.png" )
273
+ iconSyntaxCk = QgsApplication .getThemeIcon ("console/iconSyntaxErrorConsole.png" )
278
274
hideEditorAction = menu .addAction ("Hide Editor" ,
279
275
self .hideEditor )
280
276
# menu.addSeparator()
@@ -284,9 +280,12 @@ def contextMenuEvent(self, e):
284
280
# closeTabAction = menu.addAction("Close Tab",
285
281
# self.parent.close, 'Ctrl+W')
286
282
menu .addSeparator ()
283
+ syntaxCheck = menu .addAction (iconSyntaxCk , "Check Syntax" ,
284
+ self .syntaxCheck , 'Ctrl+4' )
285
+ menu .addSeparator ()
287
286
runSelected = menu .addAction (iconRun ,
288
- "Enter selected" ,
289
- self .runSelectedCode , 'Ctrl+E' )
287
+ "Enter selected" ,
288
+ self .runSelectedCode , 'Ctrl+E' )
290
289
runScript = menu .addAction (iconRunScript ,
291
290
"Run Script" ,
292
291
self .runScriptCode , 'Shift+Ctrl+E' )
@@ -317,7 +316,7 @@ def contextMenuEvent(self, e):
317
316
self .codepad )
318
317
menu .addSeparator ()
319
318
showCodeInspection = menu .addAction ("Hide/Show Object list" ,
320
- self .objectListEditor )
319
+ self .objectListEditor )
321
320
menu .addSeparator ()
322
321
selectAllAction = menu .addAction ("Select All" ,
323
322
self .selectAll ,
@@ -326,6 +325,7 @@ def contextMenuEvent(self, e):
326
325
settingsDialog = menu .addAction (iconSettings ,
327
326
"Settings" ,
328
327
self .parent .pc .openSettings )
328
+ syntaxCheck .setEnabled (False )
329
329
pasteAction .setEnabled (False )
330
330
codePadAction .setEnabled (False )
331
331
cutAction .setEnabled (False )
@@ -344,6 +344,7 @@ def contextMenuEvent(self, e):
344
344
codePadAction .setEnabled (True )
345
345
if not self .text () == '' :
346
346
selectAllAction .setEnabled (True )
347
+ syntaxCheck .setEnabled (True )
347
348
if self .isUndoAvailable ():
348
349
undoAction .setEnabled (True )
349
350
if self .isRedoAvailable ():
@@ -479,8 +480,8 @@ def _runSubProcess(self, filename, tmp=False):
479
480
pass
480
481
else :
481
482
raise e
482
- tmpFileTr = QCoreApplication .translate ('PythonConsole' , ' [Temporary file saved in ' )
483
483
if tmp :
484
+ tmpFileTr = QCoreApplication .translate ('PythonConsole' , ' [Temporary file saved in ' )
484
485
name = name + tmpFileTr + dir + ']'
485
486
if _traceback :
486
487
msgTraceTr = QCoreApplication .translate ('PythonConsole' , '## Script error: %1' ).arg (name )
@@ -500,9 +501,9 @@ def _runSubProcess(self, filename, tmp=False):
500
501
os .remove (filename )
501
502
except IOError , error :
502
503
IOErrorTr = QCoreApplication .translate ('PythonConsole' ,
503
- 'Cannot execute file %1. Error: %2' ) \
504
- .arg (filename ).arg (error .strerror )
505
- print IOErrorTr
504
+ 'Cannot execute file %1. Error: %2\n ' ) \
505
+ .arg (str ( filename ) ).arg (error .strerror )
506
+ print '## Error: ' + IOErrorTr
506
507
except :
507
508
s = traceback .format_exc ()
508
509
print '## Error: '
@@ -518,7 +519,7 @@ def runScriptCode(self):
518
519
msgEditorBlank = QCoreApplication .translate ('PythonConsole' ,
519
520
'Hey, type something for running !' )
520
521
msgEditorUnsaved = QCoreApplication .translate ('PythonConsole' ,
521
- 'You have to save the file before running.' )
522
+ 'You have to save the file before running.' )
522
523
if not autoSave :
523
524
if filename is None :
524
525
if not self .isModified ():
@@ -531,10 +532,12 @@ def runScriptCode(self):
531
532
self .parent .pc .callWidgetMessageBarEditor (msgEditorUnsaved , 0 , True )
532
533
return
533
534
else :
534
- self ._runSubProcess (filename )
535
+ if self .syntaxCheck (fromContextMenu = False ):
536
+ self ._runSubProcess (filename )
535
537
else :
536
- tmpFile = self .createTempFile ()
537
- self ._runSubProcess (tmpFile , True )
538
+ if self .syntaxCheck (fromContextMenu = False ):
539
+ tmpFile = self .createTempFile ()
540
+ self ._runSubProcess (tmpFile , True )
538
541
539
542
def runSelectedCode (self ):
540
543
cmd = self .selectedText ()
@@ -559,6 +562,54 @@ def goToLine(self, objName, linenr):
559
562
self .ensureLineVisible (linenr )
560
563
self .setFocus ()
561
564
565
+ def syntaxCheck (self , filename = None , fromContextMenu = True ):
566
+ eline = None
567
+ ecolumn = 0
568
+ edescr = ''
569
+ source = unicode (self .text ())
570
+ try :
571
+ if not filename :
572
+ filename = self .parent .tw .currentWidget ().path
573
+ #source = open(filename, 'r').read() + '\n'
574
+ if type (source ) == type (u"" ):
575
+ source = source .encode ('utf-8' )
576
+ compile (source , str (filename ), 'exec' )
577
+ except SyntaxError , detail :
578
+ s = traceback .format_exception_only (SyntaxError , detail )
579
+ fn = detail .filename
580
+ eline = detail .lineno and detail .lineno or 1
581
+ ecolumn = detail .offset and detail .offset or 1
582
+ edescr = detail .msg
583
+ if eline != None :
584
+ eline -= 1
585
+ for markerLine in self .bufferMarkerLine :
586
+ self .markerDelete (markerLine )
587
+ self .clearAnnotations (markerLine )
588
+ self .bufferMarkerLine .remove (markerLine )
589
+ if (eline ) not in self .bufferMarkerLine :
590
+ self .bufferMarkerLine .append (eline )
591
+ self .markerAdd (eline , self .MARKER_NUM )
592
+ loadFont = self .settings .value ("pythonConsole/fontfamilytextEditor" ,
593
+ "Monospace" ).toString ()
594
+ styleAnn = QsciStyle (- 1 ,"Annotation" ,
595
+ QColor (255 ,0 ,0 ),
596
+ QColor (255 ,200 ,0 ),
597
+ QFont (loadFont , 8 ,- 1 ,True ),
598
+ True )
599
+ self .annotate (eline , edescr , styleAnn )
600
+ self .setCursorPosition (eline , ecolumn - 1 )
601
+ #self.setSelection(eline, ecolumn, eline, self.lineLength(eline)-1)
602
+ self .ensureLineVisible (eline )
603
+ #self.ensureCursorVisible()
604
+ return False
605
+ else :
606
+ self .markerDeleteAll ()
607
+ self .clearAnnotations ()
608
+ if fromContextMenu :
609
+ msgText = QCoreApplication .translate ('PythonConsole' , 'Syntax ok' )
610
+ self .parent .pc .callWidgetMessageBarEditor (msgText , 0 , True )
611
+ return True
612
+
562
613
def keyPressEvent (self , e ):
563
614
t = unicode (e .text ())
564
615
## Close bracket automatically
@@ -581,15 +632,11 @@ def focusInEvent(self, e):
581
632
self .selectAll ()
582
633
#fileReplaced = self.selectedText()
583
634
self .removeSelectedText ()
635
+ file = open (pathfile , "r" )
636
+ fileLines = file .readlines ()
637
+ file .close ()
584
638
QApplication .setOverrideCursor (Qt .WaitCursor )
585
- try :
586
- file = open (pathfile , "r" ).readlines ()
587
- except IOError , error :
588
- IOErrorTr = QCoreApplication .translate ('PythonConsole' ,
589
- 'The file %1 could not be opened. Error: %2' ) \
590
- .arg (pathfile ).arg (error .strerror )
591
- print IOErrorTr
592
- for line in reversed (file ):
639
+ for line in reversed (fileLines ):
593
640
self .insert (line )
594
641
QApplication .restoreOverrideCursor ()
595
642
self .setModified (True )
@@ -603,12 +650,18 @@ def focusInEvent(self, e):
603
650
self .parent .pc .callWidgetMessageBarEditor (msgText , 1 , False )
604
651
QsciScintilla .focusInEvent (self , e )
605
652
653
+ def fileReadOnly (self ):
654
+ msgText = QCoreApplication .translate ('PythonConsole' ,
655
+ 'Read only file, please save to different file first.' )
656
+ self .parent .pc .callWidgetMessageBarEditor (msgText , 1 , False )
657
+
606
658
class EditorTab (QWidget ):
607
- def __init__ (self , parent , parentConsole , filename , * args ):
608
- QWidget . __init__ ( self , parent = None , * args )
659
+ def __init__ (self , parent , parentConsole , filename , readOnly ):
660
+ super ( EditorTab , self ). __init__ ( parent )
609
661
self .tw = parent
610
662
self .pc = parentConsole
611
663
self .path = None
664
+ self .readOnly = readOnly
612
665
613
666
self .fileExcuteList = {}
614
667
self .fileExcuteList = dict ()
@@ -638,17 +691,13 @@ def __init__(self, parent, parentConsole, filename, *args):
638
691
self .setEventFilter (self .keyFilter )
639
692
640
693
def loadFile (self , filename , modified ):
641
- try :
642
- fn = open (unicode (filename ), "rb" )
643
- except IOError , error :
644
- IOErrorTr = QCoreApplication .translate ('PythonConsole' ,
645
- 'The file <b>%1</b> could not be opened. Error: %2' ) \
646
- .arg (filename ).arg (error .strerror )
647
- print IOErrorTr
648
- QApplication .setOverrideCursor (QCursor (Qt .WaitCursor ))
694
+ fn = open (unicode (filename ), "rb" )
649
695
txt = fn .read ()
650
696
fn .close ()
697
+ QApplication .setOverrideCursor (QCursor (Qt .WaitCursor ))
651
698
self .newEditor .setText (txt )
699
+ if self .readOnly :
700
+ self .newEditor .setReadOnly (self .readOnly )
652
701
QApplication .restoreOverrideCursor ()
653
702
self .newEditor .setModified (modified )
654
703
self .newEditor .mtime = os .stat (filename ).st_mtime
@@ -795,7 +844,7 @@ def __init__(self, parent):
795
844
self .connect (self , SIGNAL ("tabCloseRequested(int)" ), self ._removeTab )
796
845
self .connect (self , SIGNAL ('currentChanged(int)' ), self ._currentWidgetChanged )
797
846
798
- # Open button
847
+ # New Editor button
799
848
self .newTabButton = QToolButton ()
800
849
txtToolTipNewTab = QCoreApplication .translate ("PythonConsole" ,
801
850
"New Editor" )
@@ -859,31 +908,42 @@ def closeAll(self):
859
908
self ._removeTab (0 )
860
909
861
910
def enableSaveIfModified (self , tab ):
862
- self .parent .saveFileButton .setEnabled (self .widget (tab ).newEditor .isModified ())
911
+ tabWidget = self .widget (tab )
912
+ if tabWidget :
913
+ self .parent .saveFileButton .setEnabled (tabWidget .newEditor .isModified ())
863
914
864
915
def enableToolBarEditor (self , enable ):
865
916
if self .topFrame .isVisible ():
866
917
enable = False
867
918
self .parent .toolBarEditor .setEnabled (enable )
868
919
869
920
def newTabEditor (self , tabName = None , filename = None ):
921
+ readOnly = False
922
+ if filename :
923
+ readOnly = not QFileInfo (filename ).isWritable ()
924
+ try :
925
+ fn = open (unicode (filename ), "rb" )
926
+ txt = fn .read ()
927
+ fn .close ()
928
+ except IOError , error :
929
+ IOErrorTr = QCoreApplication .translate ('PythonConsole' ,
930
+ 'The file %1 could not be opened. Error: %2\n ' ) \
931
+ .arg (str (filename )).arg (error .strerror )
932
+ print '## Error: '
933
+ sys .stderr .write (IOErrorTr )
934
+ return
935
+
870
936
nr = self .count ()
871
937
if not tabName :
872
938
tabName = QCoreApplication .translate ('PythonConsole' , 'Untitled-%1' ).arg (nr )
873
- # if self.count() < 1:
874
- # self.setTabsClosable(False)
875
- # else:
876
- # if not self.tabsClosable():
877
- # self.setTabsClosable(True)
878
- self .tab = EditorTab (self , self .parent , filename )
939
+ self .tab = EditorTab (self , self .parent , filename , readOnly )
879
940
self .iconTab = QgsApplication .getThemeIcon ('console/iconTabEditorConsole.png' )
880
- self .addTab (self .tab , self .iconTab , tabName )
941
+ self .addTab (self .tab , self .iconTab , tabName + ' (ro)' if readOnly else tabName )
881
942
self .setCurrentWidget (self .tab )
882
943
if filename :
883
944
self .setTabToolTip (self .currentIndex (), unicode (filename ))
884
945
else :
885
946
self .setTabToolTip (self .currentIndex (), tabName )
886
- self .parent .saveFileButton .setEnabled (False )
887
947
888
948
def tabModified (self , tab , modified ):
889
949
index = self .indexOf (tab )
@@ -892,15 +952,8 @@ def tabModified(self, tab, modified):
892
952
self .parent .saveFileButton .setEnabled (modified )
893
953
894
954
def closeTab (self , tab ):
895
- # Check if file has been saved
896
- #if isModified:
897
- #self.checkSaveFile()
898
- #else:
899
- #if self.indexOf(tab) > 0:
900
955
if self .count () < 2 :
901
- #self.setTabsClosable(False)
902
956
self .removeTab (self .indexOf (tab ))
903
- #pass
904
957
self .newTabEditor ()
905
958
else :
906
959
self .removeTab (self .indexOf (tab ))
@@ -926,13 +979,14 @@ def _removeTab(self, tab, tab2index=False):
926
979
return
927
980
else :
928
981
self .parent .updateTabListScript (self .widget (tab ).path )
929
- self .removeTab (tab )
982
+ if self .count () <= 1 :
983
+ self .removeTab (tab )
984
+ self .newTabEditor ()
930
985
else :
931
986
if self .widget (tab ).path is not None or \
932
987
self .widget (tab ).path in self .restoreTabList :
933
988
self .parent .updateTabListScript (self .widget (tab ).path )
934
989
if self .count () <= 1 :
935
- # self.setTabsClosable(False)
936
990
self .removeTab (tab )
937
991
self .newTabEditor ()
938
992
else :
@@ -950,7 +1004,6 @@ def closeCurrentWidget(self):
950
1004
if currWidget :
951
1005
currWidget .setFocus (Qt .TabFocusReason )
952
1006
if currWidget .path in self .restoreTabList :
953
- #print currWidget.path
954
1007
self .parent .updateTabListScript (currWidget .path )
955
1008
956
1009
def restoreTabs (self ):
@@ -1055,9 +1108,16 @@ def listObject(self, tab):
1055
1108
if found :
1056
1109
sys .path .remove (pathFile )
1057
1110
except :
1058
- s = traceback .format_exc ()
1059
- print '## Error: '
1060
- sys .stderr .write (s )
1111
+ msgItem = QTreeWidgetItem ()
1112
+ msgItem .setText (0 , QCoreApplication .translate ("PythonConsole" , "Check Syntax" ))
1113
+ msgItem .setText (1 , 'syntaxError' )
1114
+ iconWarning = QgsApplication .getThemeIcon ("console/iconSyntaxErrorConsole.png" )
1115
+ msgItem .setIcon (0 , iconWarning )
1116
+ self .parent .listClassMethod .addTopLevelItem (msgItem )
1117
+ #s = traceback.format_exc()
1118
+ #print '## Error: '
1119
+ #sys.stderr.write(s)
1120
+ #pass
1061
1121
1062
1122
def refreshSettingsEditor (self ):
1063
1123
countTab = self .count ()
0 commit comments