-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSPyE - Copy.py
4038 lines (3589 loc) · 155 KB
/
SPyE - Copy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import (
division,
absolute_import,
print_function,
unicode_literals
)
"""Main SPyE module.
#TODO, module docstring
Scintilla Python based Editor
"""
########################
# LEGEND:
# I = INFO
# n = scheduled NEXT
# p = in PROGRESS
# t = TODO
# v = DONE, solved
# x = FIX
##########
# CHECK concepts:
# I Read wxPyWiki thoroughly, again!
# I URL=https://wiki.wxpython.org/
# I Check 'PEP 8 -- Style Guide for Python Code' for naming conventions
# I URL=https://www.python.org/dev/peps/pep-0008/
# v evt.Skip(),
# v from __future__ import ..., i.e. unicode_literals, absolute_import
# v wx.EVT_MENU_HIGHLIGHT_ALL
##########
# EXAMPLE editors:
# I ConText, IDLE, IdleX, Notepad3
# I DrPython (contrib\TSNmod-DrPython_3.11.4)
# I Editra ( " \TSNmod-Editra-0.7.20)
# I PyPE ( " \TSNmod-PyPE-2.9.4)
# I UliPad ( " \TSNmod-UliPad-4.2)
# I peppy ( " \TSNmod-peppy-master)
# I Dabo ( " \dabo-0.9.14.zip), URL=https://dabodev.com
# I SciTE ( " \Scintilla-SciTE), URL=http://www.scintilla.org/SciTE.html
# I Notepad++ ( " \Notepad++), URL=https://notepad-plus-plus.org
# I TextEditor ( " \TextEditor - wxPython, Scintilla, Macros)
# I Twistpad ( " \Twistpad_Trial.zip)
# USEFUL tools:
# I code navigation:
# I universal-ctags (new), ctags (old), CTags (SublimeText plugin/wrapper)
# URL=https://ctags.io/
# URL=http://ctags.sourceforge.net
# URL=https://github.com/SublimeText/CTags
# I code checking/statistics/metrics:
# I flake8, pychecker, pylint, pymetrics, importchecker
# I radon -> radon raw <filename>
# I prospector
# pip install prospector
# I OpenStack Bandit
# URL=https://github.com/openstack/bandit
# I duplicate code detection:
# I clonedigger, PMD CPD, pysonarsq (PySonar2 fork)
# I code quality:
# I SonarQube, SonarPython
# I code documentation:
# I doxygen, doxypy, doxypypy, epydoc, pdoc, pydoc, sphinx
# URL=https://wiki.python.org/moin/DocumentationTools
# I code debugging:
# I winpdb (1.4.8) - platform independent GPL Python debugger
# URL=http://winpdb.org/
# I beeprint - friendly debug printing
# URL=https://github.com/panyanyany/beeprint
# EXAMPLE preferences:
# I Pyfa (_SRCREF-TSN-Python-Editor\other\Pyfa-1.32.0.zip)
##########
#NOTE
##########
# v PyInstaller problem: invalid bitmap when running .exe
# I absolute path 'os.path.join(icoPath, <img_file>)': NO SOLUTION
# I after refactoring images -> PyEmbeddedImage: NO SOLUTION...
# I temp replace of switch --windowed with --console in 'SPyE-BuildExe.cmd'
# I error indicated ImportError in pyclbr._readmodule
# v SOLVED: add THIS_PTH in call to readmodule_ex
##########
#TODO
##########
# FILE:
# t optimize method/class integration: _split_path/Editor
# t Recent Files
# EDITOR:
#-> p ruler UNDER page tab area including cursor position indicator, now ABOVE page tabs
# t (un)comment code, remove comments
# t convert text: EOLs, tabs, spaces
# t fill block, insert code from template
# t syntax highlighting for currently supported languages
# t smart indent
# t macro functionality, enhance...
# t split hor/ver/top/bot for 2nd view on same file as aui.Notebook lacks that feature
# I URL=http://proton-ce.sourceforge.net/rc/scintilla/pyframe/www.pyframe.com/stc/mult_views.html
# SEARCH:
# t implement REGEX, refactor wx.FindReplaceDialog to CUSTOM DIALOG
# I URL=D:\Dev\D\wx\TSN_SPyE\contrib\demo\demo_SearchSTC.py
# t check boxes: wrap search, from cursor/top, current file/all files
# BOOKMARKS:
# t testing + add/delete/list; add 1st free bmarknr when margin clicked?
# t add line text to jump menu item
# t Find/Replace DIALOG a la UEStudio/ConTEXT
# TOOLBAR:
# t customize, context menu
# t search control usage...
# t append accelerator to toolbar tooltip text, e.g. 'New (Ctrl+N)':
# t link TB_ <=> MB_ ids (when we have custom accelerator keys)
# t label text/font:
# self.tb.SetWindowStyle(self.tb.WindowStyle <-+> wx.TB_TEXT <-+> wx.TB_NO_TOOLTIPS)
# self.tb.SetOwnFont(wx.Font(wx.FontInfo(8).Bold().Italic().Underlined()))
# self.tb.SetOwnFont(wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, faceName='Courier New'))
# self.tb.Realize()
# STATUSBAR:
# t customize, context menu
# t field label text/font
# GENERAL:
# t Function List: manual refresh and/or autofill on new/open
# t add global/local functions/variables
# t ContextMenu for tb and sb DOES NOT SHOW CHECK MARK -> in handler: set explicitly w/ mb.Check
# t tooltips, global enable/disable, wx.ToolTip demo...
# t flexible logging Module next to current DEBUG
# t Replace aui.AuiNotebook by wx.lib.agw.flatnotebook ??
# t Replace _blink_text/_hover_text by EnhancedStatusBar ??
# t Command line parameter handling, i.e.: sys.argv, getopt, argparse
# v Config file handling, i.e.: ConfigObj, ConfigParser
# t Document Map, code overview in side panel in tiny font
# t Help System, including Context Sensitive Help
# PREFERENCES:
# t scan THIS source and config.py for candidate options to configure
# t keyboard SHORTCUT EDITOR in preferences or under separate menu item
# I URL="D:\Dev\Python27\Lib\site-packages\wx\lib\agw\shortcuteditor.py"
# t monitor MOUSE STATE info
# I URL="D:\Dev\Python27\Lib\site-packages\wx\DEMO\demo\GetMouseState.py"
# t retrieve system-specific info about all known MIME types and FILENAME EXTENSIONS
# I URL="D:\Dev\Python27\Lib\site-packages\wx\DEMO\demo\MimeTypesManager.py"
# t determine STANDARD LOCATION of certain types of files in a platform specific manner
# I URL="D:\Dev\Python27\Lib\site-packages\wx\DEMO\demo\StandardPaths.py"
##########
#FIX
##########
# x 2017-10-02 => replace '__, doc = self._getPagDoc()' by GLOBAL/self.curdoc
# x submenu item can not be disabled, greyed out
# v (solved): save margin views per DOCUMENT OR GLOBALLY, not both; see _update_margins etc...
# x toggle side panel open/close its respective tool's accelerator key
# x how to replace use of 'self.main' with 'import __main__ as _main'?
# v BUG (solved): error when changing page tab and FunctionList visible
##########
#DONE
##########
# v populate frame w/ empty aui.AuiNotebook
# v populate aui.AuiNotebook w/ 1 editor per page/tab
# v line/column indicator in statusbar -> 3rd field
# v ConvertEOLs(eolMode), like ConTEXT, Tools->Convert Text To...
# v use uihandler to enable/disable menu items: self.mb.Enable(evt.Id, True/False)
# v enable/disable menus View, Search, Format, Macro
# v modal wx.Dialog
# v self.appname + filename in main caption
# v self.ed -> Editor: should be instance of Editor
# v missing statusbar msgs, like Ln/Col indicator
# v STATISTICS (like ConTEXT example)
# v View->Menu Icons DOES NOT SHOW CHECK MARK, it doesn't need one!
# v REFACTOR CODE TO NOTEBOOK ARCHITECTURE, MULTIPLE TABS( =~ DOCS):
# v Function List in side panel => VerSplitter
# v blink statustext while recording -> 2nd statfield
# v show 'Top Line' text while scrolling -> 2nd statfield/hovering @top right
# v various DEBUG enhancements: AUCPL, SCMOD, FIND, KEYBD
# v oeuf...
# v set last find string to selected text
# v add doxygen '\' commands: \example \internal
# v Code Context (like IDLE) in top, Editor in bottom window => HorSplitter
# v add Code Context menu items and methods: view/swap
# v remove SetClientSize functions: splitterwindows size children fine now
# v submenu items ALWAYS SHOW ICONS: solved in BuildSubMenu, added parm2 = icons
# v close unfocused page/tab selects focused tab: solved in PageClose
# v split config into 2 modules: debug, language
# v styling per lexer/language: 1st basic setup
# v find next/prev now uses findflg: solved in SearchFindNext/Prev
# v HighlightMatches: saves last find string
# v language indicator in statusbar -> 5th field
# v styling per lexer/language: refactor 1st basic setup
# v DEBUG: show only active options
# v DEBUG: _dbg_MENU, show all items with accelerator keys
# v find flags indicator in statusbar -> 5th field
# v move 7 menu items (tb, sb, sp, ssp, cc, scc, fs) from View to new Window menu
# v add 3 menu items and methods to Search menu: Case, Word, Backwards
# v also check Case, Word and Backwards menu item from FindReplaceDialog._destroy
# v SearchFindNext/Prev sets findflg down/up; starts FindReplaceDialog when empty find string
# v add DELAY['MSG'], DELAY['SPL']: timeout for SBF['MSG'][0] and splash screen
# v REGEX implementation: 1st setup commented out; search: 'REGEX'
# v add 3 icons for case conversion in Format menu
# v refactor menu and statusbar constant prefixes to 'MB_' and 'SB_'
# v replace 4 FindMenuItem method calls: TOOLBAR, STATUSBAR, SIDE_PANEL, CODE_CONTEXT
# v add 4 MB_WIN_ constants: TOOLBAR, STATUSBAR, SIDE_PANEL, CODE_CONTEXT
# v unresponsive page tab - next to dragged tab - after horizontal drag: solved in PageEndDrag
# v add module: images, for embedded images
# v refactor images to PyEmbeddedImage
# v remove use of 'icoPath', 'appIcon', 'os.path.join': now PyEmbeddedImage
# v add BoxSizer to CodeContext for auto sizing its StaticText control
# v shorten CONSTANTS (see config) in all modules: mnemonic naming convention
# v reformat '#TODO/FIX/NOTE/DONE' task tags to start of line
# v FileSave, FileSaveAs: add functionality, also testing
# v add 'pathdata' list to Editor class (doc object)
# v add method _split_path
# v module name change: FIX_PreferencesDialog -> preferences
# v rename menu: Window -> Layout
# v add method LayoutFileTabs to Layout menu
# v style BORDER_SUNKEN for Notebook, FunctionList and CodeContext
# v set (in)active colours for Notebook tabs
# v add method _update_page_tabs, insert new tab and update page/tooltip/app titles
# v new DEBUG['STACK']: indented call levels in '_dbg_whoami'
# v hide toolbar/statusbar leaves empty space: SOLVED by 'SendSizeEvent'
# v refactor 'mb.Check' code block to ternary operator: 'x if True else y' (where viable)
# v refactor '_getPagDoc' and calling code: return when no document open, 'if not doc: return'
# v add 2 methods to Layout menu: LayoutRuler, LayoutRulerSwap
# v add class MidSplitter to support ruler Layout
# v add 3 methods to File menu: FileInsertFile, FileAppendFile, FileWriteBlockToFile
# v file menu ACTIONS, open/save/.../doc.LoadFile(fnm), doc.SaveFile(fnm)
# v move 3 menu items from Edit and View to Layout menu: Tooltips, Menu Icons, Preferences
# v add DropFiles to MainWindow, supports drag and drop
# v add method _open_files: dedupe DropFiles, FileOpen
# v add method _set_language_styling: dedupe FileNew, LanguageSetStyling, _open_files, Editor
# v add method to Layout menu: FileTabIcons toggles display of file tab icons
# v now 1 file extension icon available for file tab: 'file_ext_'
# v moved ContextMenu: Editor -> MainWindow
# v integrate PageMenu from Notebook into ContextMenu and refactor code
# v add method TextChanged to Editor: update mod indicator '*' immediately
# v add method SplitEditor to notebook ctx menu: 2nd view of edited file, 1st basic setup
# v ContextMenu: syntax highlighting menu on right click in statusbar SBF['LNG'][0] field
# v from main to module: gui -> menu, toolbar and statusbar global methods
# v " " " " : editor -> class Editor
# v " " " " : find -> class FindReplaceDialog
# v " " " " : notebook -> class Notebook
# v " " " " : splitter -> classes Ver/Hor/MidSplitter
# v " " " " : codecontext -> class CodeContext
# v " " " " : sidepanel -> classes SidePanel, FunctionList, TreeCtrl
# v add method _get_file_icon: return file tab icon for extension
# v add check marks to toolbar context menu
# v add 5 config processing functions: CfgDefault, CfgCreate, CfgRead, CfgWrite, CfgApply
# v add 2 UI handlers for doc modified and find string: UpdateUIMod, UpdateUIFnd
# v add method _not_implemented: print unimplemented functionality
# v add document and bookmark panels to side panel and View menu
# v add exit_called: discard FileClose actions when called from FileExit
# v add method EditHighlightMatches to editor context menu: calls HighlightMatches
# v ContextMenu: editor margin menu on right click in margin
# v add method _top_line_tooltip to replace _hover_text (used when scrolling)
# v add 4 data type checking functions: is_bool, is_int, is_float, is_list
# v remove CFG_FIL_USE constant: ALWAYS use config file or create default
# v DEBUG: _dbg_BOOKMARK, show bookmarks per file
# v renamed modules to lowercase: codecontext, oeuf, gui, sidepanel
# v removed module oeuf_egami, moved contents to oeuf module
# v submenu item itself DOES NOT SHOW ICON, e.g. see View->Highlighting submenu
# v add module: constant, for global variables; moved globals from config module
#########################
#TODO, use magic names and metadata
#INFO, URL=https://stackoverflow.com/questions/1523427/what-is-the-common-header-format-of-python-files/1523456
__all__ = ['package', 'module', 'class', 'method', 'function', 'variable', '...']
__credits__ = ['Neil Hodgson', 'Robin Dunn', 'Andrea Gavana', 'Mike Driscoll', 'Cody Precord', ]
__version__ = '0.90'
__author__ = 'TSN'
__license__ = 'GPL'
__status__ = 'Development'
#INFO, grep -oh '__[A-Za-z_][A-Za-z_0-9]*__' d:\Dev\Python27\Lib\*.py | sort | uniq
#INFO, - list all Python magic names, documented or not, from Lib directory
#INFO, URL=https://stackoverflow.com/questions/8920341/finding-a-list-of-all-double-underscore-variables
# timing
from lib.util import now_
# from lib.util import Timer, now_
startup_time = session_time = now_()
# tmr = Timer()
# tmr.start()
#NOTE, workaround: enables main module startup outside its own directory
import os
import sys
# print(os.getcwd())
os.chdir(os.path.dirname(os.path.realpath(sys.argv[0])))
# print(os.getcwd())
from lib.constant import (
appFull, LOC, TIM_FMT,
# SsnCreate, SsnRead, SsnWrite, # SsnApply,
FAST_EXIT,
TASKBAR_ICO, HELP_CONTEXT,
APP_REDIRECT, APP_REDIRECT_FNM, APP_INSPECTION, APP_INSP,
DELAY,
# FNL_IMG_ICO_USE, FNL_FIL_USE, FNL_FIL_NAME, FNL_TREE_EXPAND,
# ICO_GO_FORWARD, ICO_GO_DOWN, ICO_NORMAL_FILE, ICO_GREEN, ICO_YELLOW,
# ICO_RED, INFINITY,
appName, appIcon, fno, FNM_CHARS, URL_CHARS,
SASH, CRLF, CR, LF,
# NO_ACT, NO_TXT, NO_ICO, NO_TYP, NO_UIH,
MB, TB, TBX, SBF, SBF_CPY, SBX, DMX,
SPT, TXT_NIL,
FOL_STY_NIL, FOL_STY_SQR, FOL_STYLE,
MGN, MRK,
)
from lib.debug import (
DEBUG, _dbg_CONFIG,
DbgCreate, DbgRead,
_dbg_STARTUP, _dbg_EVENT, _dbg_FILE_HISTORY, _dbg_POSITION_HISTORY, _dbg_CTXTTB,
_dbg_MODEVTMASK, _dbg_RMTWS, _dbg_FOCUS, _dbg_TRACEFUNC,
_dbg_BOOKMARK, _dbg_CLRDB, _dbg_SCINTILLA_CMDS, _dbg_whoami, _dbg_funcname
)
from lib.config import (
CfgCreate, CfgRead, CfgWrite, CfgApply, noit, cnuf,
)
cnuf()
from lib.editor import Editor
from lib.find import FindReplaceDialog
from lib.gui import (
SetupMenu, BuildMainMenu, RebuildMainMenu, AttachRecentFilesMenu,
SetupToolBar, BuildToolBar, SetupStatusBar,
SystemTrayMenu
)
from lib.images import catalog as PNG # embedded images
from lib.language import (
LANG, file_exts, SyntaxStyling,
LngCreate, LngRead, # LngWrite, LngApply,
)
from lib.notebook import Notebook
from lib.oeuf import Oeuf
from lib.preferences import Preferences
from lib.searchpanel import SearchPanel
from lib.sidepanel import SidePanel
from lib.splitter import (
WinSplitter #, VerSplitter, HorSplitter, MidSplitter, EdtSplitter
)
from lib.util import (
Freeze, Thaw
)
from lib.external.codecontext import CodeContext
from lib.external.ruler import RulerCtrl as Ruler
# import macro
# import menu
#INFO, URL=http://pythonhosted.org/Autologging/examples.html
# from autologging import traced, TRACE
#TODO, Use logging Module integrated with current DEBUG
# import logging, sys
# logging.basicConfig(level=TRACE, stream=sys.stdout,
# format='%(levelname)s:%(name)s:%(funcName)s:%(message)s')
# import lib.__future__.memory_footprint_object as mem
from beeprint import pp
from datetime import datetime as dtm
#NOTE, introspection, pretty printing
# from inspect import getmembers
from pprint import pprint
from shutil import copyfile
from subprocess import check_output
from wx.lib.agw import advancedsplash as splash
from wx.lib.agw import shortcuteditor as SCE
#FIX, pyclbr includes class 'art' from import below in classbrowser
# from wx.lib.agw.artmanager import ArtManager as art
# import wx.adv as adv
#DONE, wx.lib.mixins.inspection crashed Python, now works 2017-08-25 18:18:27
import wx.lib.mixins.inspection as inspection
# import wx.lib.multisash as sash
import ctypes
import webbrowser
import wx
import wx.stc as stc
#FIX, enable/disable paste on menu, toolbar, context menu
#NOTE, DataObject not used yet
# class DataObject(wx.DataObject):
# """class DataObject"""
# def __init__(self, value=''):
# wx.DataObject.__init__(self)
# self.formats = [wx.DataFormat(wx.DF_TEXT)]
# self.data = value
# def GetAllFormats(self, d):
# return self.formats
# def GetFormatCount(self, d):
# return len(self.formats)
# def GetPreferredFormat(self, d):
# return self.formats[0]
# def GetDataSize(self, format):
# # On windows strlen is used internally for stock string types
# # to calculate the size, so we need to make room for and add
# # a null character at the end for our custom format using
# # wx.DF_TEXT.
# return len(self.data)+1
#INFO, URL=http://pythonhosted.org/Autologging/examples.html
# @traced
class MainWindow(wx.Frame):
"""class MainWindow"""
def __init__(self, *args, **kwargs):
if DEBUG['STACK']: print(_dbg_whoami())
super(MainWindow, self).__init__(*args, **kwargs)
#INFO, => prints 'wxFrame'
# print('MainWindow.ClassName:', self.ClassName)
self.exit_called = False # discard FileClose actions when called from FileExit
self.multi_clipboard = None # multiple selection clipboard
# self.findtxt = str(cfg['Search']['FindText']) # find string
# self.repltxt = str(cfg['Search']['ReplaceText']) # replace string
# #TODO, implement incremental search, see ConTEXT
# self.incrtxt = str(cfg['Search']['IncrementalText']) # incremental string
# self.findflg = int(cfg['Search']['FindFlags']) # find flags, default: search forward
self.findreg = cfg['Search']['FindRegex'] # find: regular expression
self.findwrp = cfg['Search']['FindWrap'] # find: wrap to top/bottom
self.dlg_fnd = None # find dialog object
# TOOLBAR
toolbar = SetupToolBar(self)
self.tb = BuildToolBar(self, toolbar)
#FIX, RULER testing
# self.rlr = Ruler(self)
# self.SendSizeEvent()
# STATUSBAR
self.sb = SetupStatusBar(self)
self.SetStatusBar(self.sb)
self._push_statustext('Welcome to ' + appFull)
# SPLITTERS
# horizontal (versp/SearchPanel)
self.schsp = WinSplitter('SCH', self)
# vertical (horsp/SidePanel)
self.versp = WinSplitter('VER', self.schsp)
# horizontal (CodeContext/midsp)
self.horsp = WinSplitter('HOR', self.versp)
# mid horizontal (Ruler/Notebook)
self.midsp = WinSplitter('MID', self.horsp)
# CODE CONTEXT, RULER and NOTEBOOK
#FIX, decide on CodeContext parms, now just 2: parent, doc
#FIX, parm2 (doc) = None, for now...??
self.schtopPanel = self.versp
self.schbotPanel = self.sch = SearchPanel(self.schsp)
self.topPanel = self.ccx = CodeContext(self.horsp)
self.midtopPanel = self.rlr = Ruler(self.midsp, offset=0)
# self.midtopPanel = self.rlr = Ruler(self.midsp)
self.midbotPanel = self.nb = Notebook(self.midsp)
self.bottomPanel = self.midsp
self.leftPanel = self.horsp
# # vertical (editor/2nd view)
# self.edtsp = EdtSplitter(self.nb)
# dummy1 = wx.Panel(self.edtsp)
# dummy2 = wx.Panel(self.edtsp)
# SIDE PANEL
#FIX, parm2 (doc) = None, for now...??
self.rightPanel = self.spn = SidePanel(self.versp)
#FIX, 'CodeContext' in top left as tiny widget
#NOTE, workaround: split/unsplit immediately...
self.schsp.SplitHorizontally(self.schtopPanel, self.schbotPanel, SASH['SCH'][self.sch.mode])
self.versp.SplitVertically(self.leftPanel, self.rightPanel, -SASH['VER'])
self.horsp.SplitHorizontally(self.topPanel, self.bottomPanel, SASH['HOR'])
self.midsp.SplitHorizontally(self.midtopPanel, self.midbotPanel, SASH['MID'])
# self.edtsp.SplitVertically(dummy1, dummy2, SASH['EDT'])
self.schsp.Unsplit(self.schbotPanel)
self.versp.Unsplit(self.rightPanel)
self.horsp.Unsplit(self.topPanel)
self.midsp.Unsplit(self.midtopPanel)
# self.edtsp.Unsplit(dummy2)
# del dummy1, dummy2
# MENUBAR, main, context, recent file history and system tray menus
MNU, CTX = SetupMenu(self)
self.menu, self.ctx, self.icons, self.hlp = MNU, CTX, cfg['Layout']['MenuIcons'], cfg['Layout']['MenuHelpText']
self.mb = BuildMainMenu(self, self.menu, icons=self.icons, hlp=self.hlp)
AttachRecentFilesMenu(self, recent_list)
if cfg['General']['SystemTrayMenu']:
self.stm = SystemTrayMenu(self)
# self.Bind(wx.EVT_IDLE, self.OnIdle)
self.Bind(wx.EVT_SIZE, self.Refresh)
self.Bind(wx.EVT_CLOSE, self.FileExit)
self.Bind(wx.EVT_DROP_FILES, self.DropFiles)
self.Bind(wx.EVT_MAXIMIZE, self.Maximize)
###############################################################################
###############################################################################
# self.Bind(wx.EVT_PAINT, self.Paint)
#
# def Paint(self, evt):
# dc = wx.PaintDC(self.leftPanel)
# dc.SetBackground(wx.Brush("WHITE"))
# dc.Clear()
#
# dc.SetFont(wx.Font(16, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, True))
# dc.DrawText("Bitmap alpha blending (on all ports but gtk+ 1.2)", 25, 25)
#
# bmp = wx.Bitmap(PNG['toucan.png'])
#
# dc.DrawBitmap(bmp, 25, 100, True)
#
# # dc.SetFont(self.Font)
# # y = 75
# # for line in range(10):
# # y += dc.CharHeight + 5
# # dc.DrawText(msg, 200, y)
# dc.DrawBitmap(bmp, 250, 100, True)
###############################################################################
###############################################################################
#FIX, decorator for _getPagDoc
def _getdoc(arg):
def decorator(fnc):
def wrapper(self, *args, **kwargs):
print('decorator [_getdoc] for [%s]' % fnc.__name__)
_dbg_funcname(arg)
__, doc = self._getPagDoc()
if not doc: return
fnc(self, doc, *args, **kwargs)
return wrapper
return decorator
# @_getdoc
# def OnIdle(self, doc, evt):
def OnIdle(self, evt):
__, doc = self._getPagDoc()
if not doc: return
if DEBUG['IDLE']: print('OnIdle:', end='')
if DEBUG['IDLE'] > 1: _dbg_EVENT(evt)
if DEBUG['IDLE']: print('[%s] - [%s]' % (doc.filename, doc.dirname))
@staticmethod
def Refresh(evt):
if DEBUG['BASIC'] > 1: print('Refresh: ', end='')
if DEBUG['BASIC'] > 1: _dbg_EVENT(evt)
# if DEBUG['BASIC'] < 2: print()
# print('Frame Min = %s' % self.MinSize)
# print('Frame Max = %s' % self.MaxSize)
# print('Frame Best = %s' % self.BestSize)
# print('ToolB Min = %s' % self.tb.MinSize)
# print('ToolB Max = %s' % self.tb.MaxSize)
# print('ToolB Best = %s' % self.tb.BestSize)
# print('%s' % '-'*10)
# resize splitter client area
# self.versp.SetClientSize(self.ClientSize)
# self.horsp.SetClientSize(self.ClientSize)
# resize notebook client area
# self.nb.SetClientSize(self.ClientSize)
# self.bottomPanel.SetClientSize(self.ClientSize)
if evt:
evt.Skip()
def UpdateUIDoc(self, evt):
if DEBUG['UPDUI']: print('UpdateUIDoc: ', end='')
if DEBUG['UPDUI'] > 1: _dbg_EVENT(evt)
#TODO, hide full menus when NO document open
# for m in range(12):
# self.mb.EnableTop(m, False if m not in [0, 5, 9, 10, 11] and not self.nb.PageCount else True)
if self.nb.PageCount: # when document open
__, doc = self._getPagDoc()
if not doc: return
#NOTE, using '[Margin][LeftWidth] = 4' in 'SPyE.cfg' to left align ruler
# update ruler alignment when visible
if self.midsp.IsSplit():
doc.new_XOffset = doc.XOffset
if doc.old_XOffset != doc.new_XOffset:
delta = doc.old_XOffset - doc.new_XOffset
self.rlr.set_offset(self.rlr.offset + delta)
doc.old_XOffset = doc.new_XOffset
evt.Enable(True)
# caret position history
self.mb.Enable(MB['JMP_BCK'], True if cfg['CaretPositionHistory']['Enable'] else False)
self.mb.Enable(MB['JMP_FWD'], True if cfg['CaretPositionHistory']['Enable'] else False)
# enable swap when split window visible
self.mb.Enable(MB['LAY_SCS'], True if self.schsp.IsSplit() else False)
self.mb.Enable(MB['LAY_RLS'], True if self.midsp.IsSplit() else False)
self.mb.Enable(MB['LAY_SPS'], True if self.versp.IsSplit() else False)
self.mb.Enable(MB['LAY_CCS'], True if self.horsp.IsSplit() else False)
#DONE, disable 'FileTabIcons' when 'FileTabs' not checked
self.mb.Enable(MB['LAY_FTI'], True if self.mb.IsChecked(MB['LAY_FTB']) else False)
# check open documents for change on disk for reload
# if not app.focus:
# return
# else:
# self._detect_file_change()
#FIX, message never shows, see 'EditUndo/EditRedo'
self.mb.Enable(MB['EDT_UDO'], True if doc.CanUndo() else False)
self.mb.Enable(MB['EDT_RDO'], True if doc.CanRedo() else False)
#DONE, enable/disable undo/redo buttons on toolbar, too
self.tb.EnableTool(TB['UDO'], True if doc.CanUndo() else False)
self.tb.EnableTool(TB['RDO'], True if doc.CanRedo() else False)
#########################################################################
#########################################################################
#FIX, enable/disable paste on menu, toolbar, context menu
self.mb.Enable(MB['EDT_CUT'], True if doc.CanCut() else False)
self.mb.Enable(MB['EDT_CPY'], True if doc.CanCopy() else False)
self.mb.Enable(MB['EDT_PST'], True if doc.CanPaste() else False)
#NOTE, it seems Windows and Scintilla have a SEPARATE clipboard!
#INFO, use 'ECHO OFF|CLIP' in CMD to clear Windows clipboard
#INFO, URL=https://github.com/wxWidgets/Phoenix/blob/master/unittests/test_dataobj.py
# clp = wx.Clipboard()
# clp.Open()
# clp.Clear()
# data = DataObject()
# # data = wx.DataObjectSimple(format=wx.DF_TEXT)
# clp.GetData(data)
# print(data.GetDataSize(wx.DF_TEXT))
# self.mb.Enable(MB['EDT_PST'], True if data.GetDataSize(wx.DF_TEXT) > 2 else False)
#########################################################################
#########################################################################
else:
evt.Enable(False)
def UpdateUIFnd(self, evt):
if DEBUG['UPDUI']: print('UpdateUIFnd: ', end='')
if DEBUG['UPDUI'] > 1: _dbg_EVENT(evt)
if self.nb.PageCount: # when document open, empty find string?
evt.Enable(True if self.findtxt else False)
else:
evt.Enable(False)
def UpdateUIHst(self, evt):
if DEBUG['UPDUI']: print('UpdateUIHst: ', end='')
if DEBUG['UPDUI'] > 1: _dbg_EVENT(evt)
# recent file history items?
self.mb.Enable(MB['HST_RCF'], True if self.hist.Count else False)
self.mb.Enable(MB['HST_RCA'], True if self.hist.Count else False)
self.mb.Enable(MB['HST_CLI'], True if self.hist.Count else False)
def UpdateUIMac(self, evt):
if DEBUG['UPDUI']: print('UpdateUIMac: ', end='')
if DEBUG['UPDUI'] > 1: _dbg_EVENT(evt)
if self.nb.PageCount: # when document open, macro recording?
__, doc = self._getPagDoc()
if not doc: return
if doc.recording:
evt.Enable(True if evt.Id in [MB['MAC_STP'], MB['MAC_TST']] else False)
else:
evt.Enable(False if evt.Id == MB['MAC_STP'] else True)
if not len(doc._macro) and evt.Id in [MB['MAC_PLY'], MB['MAC_PLM']]:
evt.Enable(False)
else:
evt.Enable(False)
def UpdateUIMod(self, evt):
if DEBUG['UPDUI']: print('UpdateUIMod: ', end='')
if DEBUG['UPDUI'] > 1: _dbg_EVENT(evt)
if self.nb.PageCount: # when document open, modified?
__, doc = self._getPagDoc()
if not doc: return
evt.Enable(True if doc.IsModified() else False)
else:
evt.Enable(False)
def UpdateUISel(self, evt):
if DEBUG['UPDUI']: print('UpdateUISel: ', end='')
if DEBUG['UPDUI'] > 1: _dbg_EVENT(evt)
if self.nb.PageCount: # when document open, text selected?
__, doc = self._getPagDoc()
if not doc: return
cnt, sel = doc.Selections, doc.GetSelection()
if cnt > 1 or sel[0] != sel[1]:
evt.Enable(True)
else:
evt.Enable(False)
else:
evt.Enable(False)
def DropFiles(self, evt):
_dbg_funcname()
if DEBUG['BASIC']: print(' ', evt.Files)
if DEBUG['BASIC']: print(' ', evt.NumberOfFiles)
filelist = [[fnm, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] for fnm in evt.Files]
self._open_files(filelist)
#FIX, direct call from 'Bind(wx.EVT_MAXIMIZE, ...') does NOT force default sash position
def Maximize(self, evt):
_dbg_funcname()
# print('Maximize')
#TODO, needs better coding...
if self.versp.IsSplit():
self.LayoutSidePanel(evt)
self.LayoutSidePanel(evt)
def FileNew(self, evt):
_dbg_funcname()
global fno
if DEBUG['STACK']: print(_dbg_whoami())
if DEBUG['FILE'] > 1: print(' IN: cwd [%s]' % os.getcwd())
fno += 1
dnm = ''
pfx = cfg['General']['NewFilePrefix']
fnm = pfx + str(fno)
fbs = ''
ext = ''
if DEBUG['FILE']: print(' [%s]' % (fnm))
self.Freeze() # avoid flicker
#FIX, SPLIT_EDITOR
# # vertical (editor/2nd view)
# self.edtsp = EdtSplitter(self.nb)
# doc = Editor(self.edtsp, [dnm, fnm, fbs, ext])
doc = Editor(self.nb, [dnm, fnm, fbs, ext])
# get language based on menu selection
lang = [m for m in LANG if self.mb.IsChecked(m[4])]
doc._set_language_styling(lang)
self._update_page_tabs(doc, newtab=True)
# multi = sash.MultiSash(self.nb.CurrentPage, -1, pos=(0,0), size=doc.ClientSize)
# multi.SetDefaultChildClass(Editor)
# dcp = doc.spt[SPT['DCM']]
# if dcp:
# dcm = dcp.ctrl
# if dcm.IsFrozen():
# dcm.Thaw()
# self.Freeze()
# # dcm.Freeze()
# self.Thaw()
#FIX, error 'wx._core.wxAssertionError: C++ assertion "m_freezeCount" failed at ..\..\src\common\wincmn.cpp(1257) in wxWindowBase::Thaw(): Thaw() without matching Freeze()'
#INFO, occurs when selecting 'FileNew' while 'DocumentMap' visible
self.Thaw()
if DEBUG['SCMOD']: _dbg_MODEVTMASK(doc)
if DEBUG['FILE'] > 1: print(' OUT: cwd [%s]' % os.getcwd())
def FileOpen(self, evt):
_dbg_funcname()
if DEBUG['FILE'] > 1: print(' IN: cwd [%s]' % os.getcwd())
sty = wx.FD_OPEN | wx.FD_CHANGE_DIR | wx.FD_PREVIEW | wx.FD_FILE_MUST_EXIST | wx.FD_MULTIPLE
dlg = wx.FileDialog(self, 'Open', os.getcwd(), '', file_exts, sty)
if dlg.ShowModal() != wx.ID_OK:
if DEBUG['FILE']: print(' Cancel')
else:
filelist = [[fnm, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] for fnm in dlg.Paths]
# timing
if DEBUG['TIMER']: open_time = now_()
self._open_files(filelist)
# timing
if DEBUG['TIMER']:
open_time = now_() - open_time
print(' open_time: %6d ms' % (open_time))
if DEBUG['FILE'] > 1: print(' OUT: cwd [%s]' % os.getcwd())
dlg.Destroy()
def FileReopenClosedFromHistory(self, evt): #, all_files=False
_dbg_funcname()
_id = evt.Id
cnt = self.hist.Count
if _id == MB['HST_RCA'] and cfg['RecentFilesHistory']['ReopenConfirm']:
msg = 'Reopen ' + str(cnt) + ' file(s) from recent files history?'
ans = self._msg_box(self, 'WARN_ASK', msg)
if ans != wx.ID_YES:
return
# walk file history
for fileNum in range(cnt):
fnm = self.hist.GetHistoryFile(fileNum)
# file already open?
opened = False
for j in range(self.nb.PageCount):
pag = self.nb.GetPage(j)
# print('{}\n {}\n---'.format(fnm, pag.pathname))
if fnm == pag.pathname:
opened = True
# print(' opened = True')
break
# print('\n \n')
if not opened:
filelist = [[fnm, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
self._open_files(filelist)
fbs = os.path.basename(fnm)
self._push_statustext('Reopening closed file [%s] from recent file history' % fbs)
# quit if 1 file selected
if _id == MB['HST_RCF']:
break
def FileOpenFromHistory(self, evt):
_dbg_funcname()
fileNum = evt.Id - wx.ID_FILE1
fnm = self.hist.GetHistoryFile(fileNum)
self.hist.AddFileToHistory(fnm) # move up the list
filelist = [[fnm, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
self._open_files(filelist)
fbs = os.path.basename(fnm)
self._push_statustext('Opening file [%s] from recent file history' % fbs)
def FileClearHistory(self, evt):
_dbg_funcname()
cnt = self.hist.Count
for i in range(cnt):
self.hist.RemoveFileFromHistory(0)
self._push_statustext('Cleared %d recent file history items' % cnt)
#INFO, URL=https://gist.github.com/jbjornson/1186126
def FileOpenAtCursor(self, evt):
_dbg_funcname()
__, doc = self._getPagDoc()
if not doc: return
if DEBUG['FILE']: print(' ------')
fnm = ''
vld = False
sel = doc.SelectedText
if DEBUG['FILE']: print(' Select' if sel else ' Parse')
if sel:
if CRLF in sel:
if DEBUG['FILE']: print(' SKIP : multiple lines selected')
return
else:
if DEBUG['FILE']: print(' sel : [%s]' % sel)
fnm = sel
else:
# get potential filename from line
lin, pos = doc.CurLine
lin = lin.rstrip() # remove newline
left = lin[0:pos]
right = lin[pos:]
if DEBUG['FILE']: print(' line : [%s]\n pos : [%s]' % (lin, pos))
if DEBUG['FILE']: print(' left : [%s]\n right: [%s]' % (left, right))
# walk left until invalid
txt = ''
for c in reversed(left):
if c in FNM_CHARS:
txt = c + txt
else:
break
left = txt
# walk right until invalid
txt = ''
for c in right:
if c in FNM_CHARS:
txt += c
else:
break
right = txt
# get filename
fnm = left + right
fbs = os.path.basename(fnm)
vld = os.path.isfile(fnm)
if DEBUG['FILE']: print(' fnm : [%s]' % fnm)
if DEBUG['FILE']: print(' fbs : [%s]' % fbs)
if DEBUG['FILE']: print(' valid: [%s]' % vld)
if vld:
self._push_statustext('Opening file [%s] at cursor' % fbs)
filelist = [[fnm, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
self._open_files(filelist)
else:
#####################
# temporary code
#####################
#FIX, create function to show error msg in statusbar
bg = self.sb.BackgroundColour
self.sb.SetBackgroundColour(cfg['Statusbar']['ErrorBackColour'])
self._push_statustext('Invalid filename at cursor')
self.sb.SetBackgroundColour(bg)
# self._set_statustext('Invalid filename at cursor')
#####################
# temporary code
#####################
#TODO, open URL at cursor, possibly integrate with 'FileOpenAtCursor' later
#INFO, URL=https://stackoverflow.com/questions/1547899/which-characters-make-a-url-invalid/1547940#1547940
def URLOpenAtCursor(self, evt):
_dbg_funcname()
__, doc = self._getPagDoc()
if not doc: return
if DEBUG['URL']: print(' ------')
url = ''
# vld = False
sel = doc.SelectedText
if DEBUG['URL']: print(' Select' if sel else ' Parse')
if sel:
if CRLF in sel:
if DEBUG['URL']: print(' SKIP : multiple lines selected')
return
else:
if DEBUG['URL']: print(' sel : [%s]' % sel)
url = sel
else:
# get potential URL from current line
lin, pos = doc.CurLine
lin = lin.rstrip() # remove newline
# parse line for valid URI scheme
schemes = ('http://', 'https://', 'ftp://', 'ftps://', 'file:///', 'file://', 'mailto:')
# if any(s in lin for s in schemes):
# print('*** valid URI scheme ***')
# else:
# print('!!! INVALID URI scheme !!!')
# return
idx = -1
for s in schemes:
if s in lin:
idx = lin.find(s)
sch = s
break
if DEBUG['URL']: print(' URI scheme: [%s]' % (s if idx != -1 else 'NOT found'))
# scheme not found OR caret before URI scheme?
if idx == -1 or pos < idx:
return
left = lin[0:pos]
right = lin[pos:]
if DEBUG['URL']: print(' line : [%s]' % (lin))
if DEBUG['URL']: print(' pos : [%d]\n idx : [%d]' % (pos, idx))
if DEBUG['URL']: print(' left : [%s]\n right: [%s]' % (left, right))
# walk left until invalid OR at 1st pos of URI scheme
txt = ''
for c in reversed(left):
if DEBUG['URL'] > 1: print(' txt: [%s]' % (txt))
# at 1st pos of URI scheme?
if len(txt) == pos - idx:
if DEBUG['URL']: print(' SKIP : at 1st pos of URI scheme [%s]' % (sch))
break
if c in URL_CHARS:
txt = c + txt
else:
break
left = txt
# walk right until invalid
txt = ''
for c in right:
if c in URL_CHARS:
txt += c
else:
break
right = txt
# get URL
url = left + right
if DEBUG['URL']: print(' URL : [%s]' % (url))
# fbs = os.path.basename(fnm)
# vld = os.path.isfile(fnm)
# if DEBUG['URL']: print(' fnm : [%s]' % fnm)
# if DEBUG['URL']: print(' fbs : [%s]' % fbs)
# if DEBUG['URL']: print(' valid: [%s]' % vld)
#####################
# temporary code
#####################
vld = True
#####################
# temporary code
#####################
if vld:
self._push_statustext('Opening URL [%s] at cursor' % url)
webbrowser.open(url)
# filelist = [[fnm, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
# self._open_files(filelist)
else:
#####################
# temporary code