123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332 |
- # We no longer support the old, non-colour editor!
- from pywin.mfc import docview, object
- from pywin.framework.editor import GetEditorOption
- import win32ui
- import os
- import win32con
- import string
- import traceback
- import win32api
- import shutil
- BAK_NONE=0
- BAK_DOT_BAK=1
- BAK_DOT_BAK_TEMP_DIR=2
- BAK_DOT_BAK_BAK_DIR=3
- MSG_CHECK_EXTERNAL_FILE = win32con.WM_USER+1999 ## WARNING: Duplicated in editor.py and coloreditor.py
- import pywin.scintilla.document
- ParentEditorDocument=pywin.scintilla.document.CScintillaDocument
- class EditorDocumentBase(ParentEditorDocument):
- def __init__(self, template):
- self.bAutoReload = GetEditorOption("Auto Reload", 1)
- self.bDeclinedReload = 0 # Has the user declined to reload.
- self.fileStat = None
- self.bReportedFileNotFound = 0
- # what sort of bak file should I create.
- # default to write to %temp%/bak/filename.ext
- self.bakFileType=GetEditorOption("Backup Type", BAK_DOT_BAK_BAK_DIR)
- self.watcherThread = FileWatchingThread(self)
- self.watcherThread.CreateThread()
- # Should I try and use VSS integration?
- self.scModuleName=GetEditorOption("Source Control Module", "")
- self.scModule = None # Loaded when first used.
- ParentEditorDocument.__init__(self, template, template.CreateWin32uiDocument())
- def OnCloseDocument(self ):
- self.watcherThread.SignalStop()
- return self._obj_.OnCloseDocument()
- # def OnOpenDocument(self, name):
- # rc = ParentEditorDocument.OnOpenDocument(self, name)
- # self.GetFirstView()._SetLoadedText(self.text)
- # self._DocumentStateChanged()
- # return rc
- def OnSaveDocument( self, fileName ):
- win32ui.SetStatusText("Saving file...",1)
- # rename to bak if required.
- dir, basename = os.path.split(fileName)
- if self.bakFileType==BAK_DOT_BAK:
- bakFileName=dir+'\\'+os.path.splitext(basename)[0]+'.bak'
- elif self.bakFileType==BAK_DOT_BAK_TEMP_DIR:
- bakFileName=win32api.GetTempPath()+'\\'+os.path.splitext(basename)[0]+'.bak'
- elif self.bakFileType==BAK_DOT_BAK_BAK_DIR:
- tempPath=os.path.join(win32api.GetTempPath(),'bak')
- try:
- os.mkdir(tempPath,0)
- except os.error:
- pass
- bakFileName=os.path.join(tempPath,basename)
- try:
- os.unlink(bakFileName) # raise NameError if no bakups wanted.
- except (os.error, NameError):
- pass
- try:
- # Do a copy as it might be on different volumes,
- # and the file may be a hard-link, causing the link
- # to follow the backup.
- shutil.copy2(fileName, bakFileName)
- except (os.error, NameError, IOError):
- pass
- try:
- self.SaveFile(fileName)
- except IOError, details:
- win32ui.MessageBox("Error - could not save file\r\n\r\n%s"%details)
- return 0
- except (UnicodeEncodeError, LookupError), details:
- rc = win32ui.MessageBox("Encoding failed: \r\n%s"%details +
- '\r\nPlease add desired source encoding as first line of file, eg \r\n' +
- '# -*- coding: mbcs -*-\r\n\r\n' +
- 'If you continue, the file will be saved as binary and will\r\n' +
- 'not be valid in the declared encoding.\r\n\r\n' +
- 'Save the file as binary with an invalid encoding?',
- "File save failed",
- win32con.MB_YESNO | win32con.MB_DEFBUTTON2)
- if rc==win32con.IDYES:
- try:
- self.SaveFile(fileName, encoding="latin-1")
- except IOError, details:
- win32ui.MessageBox("Error - could not save file\r\n\r\n%s"%details)
- return 0
- else:
- return 0
- self.SetModifiedFlag(0) # No longer dirty
- self.bDeclinedReload = 0 # They probably want to know if it changes again!
- win32ui.AddToRecentFileList(fileName)
- self.SetPathName(fileName)
- win32ui.SetStatusText("Ready")
- self._DocumentStateChanged()
- return 1
- def FinalizeViewCreation(self, view):
- ParentEditorDocument.FinalizeViewCreation(self, view)
- if view == self.GetFirstView():
- self._DocumentStateChanged()
- if view.bFolding and GetEditorOption("Fold On Open", 0):
- view.FoldTopLevelEvent()
- def HookViewNotifications(self, view):
- ParentEditorDocument.HookViewNotifications(self, view)
- # Support for reloading the document from disk - presumably after some
- # external application has modified it (or possibly source control has
- # checked it out.
- def ReloadDocument(self):
- """Reloads the document from disk. Assumes the file has
- been saved and user has been asked if necessary - it just does it!
- """
- win32ui.SetStatusText("Reloading document. Please wait...", 1)
- self.SetModifiedFlag(0)
- # Loop over all views, saving their state, then reload the document
- views = self.GetAllViews()
- states = []
- for view in views:
- try:
- info = view._PrepareUserStateChange()
- except AttributeError: # Not our editor view?
- info = None
- states.append(info)
- self.OnOpenDocument(self.GetPathName())
- for view, info in zip(views, states):
- if info is not None:
- view._EndUserStateChange(info)
- self._DocumentStateChanged()
- win32ui.SetStatusText("Document reloaded.")
- # Reloading the file
- def CheckExternalDocumentUpdated(self):
- if self.bDeclinedReload or not self.GetPathName():
- return
- try:
- newstat = os.stat(self.GetPathName())
- except os.error, exc:
- if not self.bReportedFileNotFound:
- print "The file '%s' is open for editing, but\nchecking it for changes caused the error: %s" % (self.GetPathName(), exc.strerror)
- self.bReportedFileNotFound = 1
- return
- if self.bReportedFileNotFound:
- print "The file '%s' has re-appeared - continuing to watch for changes..." % (self.GetPathName(),)
- self.bReportedFileNotFound = 0 # Once found again we want to start complaining.
- changed = (self.fileStat is None) or \
- self.fileStat[0] != newstat[0] or \
- self.fileStat[6] != newstat[6] or \
- self.fileStat[8] != newstat[8] or \
- self.fileStat[9] != newstat[9]
- if changed:
- question = None
- if self.IsModified():
- question = "%s\r\n\r\nThis file has been modified outside of the source editor.\r\nDo you want to reload it and LOSE THE CHANGES in the source editor?" % self.GetPathName()
- mbStyle = win32con.MB_YESNO | win32con.MB_DEFBUTTON2 # Default to "No"
- else:
- if not self.bAutoReload:
- question = "%s\r\n\r\nThis file has been modified outside of the source editor.\r\nDo you want to reload it?" % self.GetPathName()
- mbStyle = win32con.MB_YESNO # Default to "Yes"
- if question:
- rc = win32ui.MessageBox(question, None, mbStyle)
- if rc!=win32con.IDYES:
- self.bDeclinedReload = 1
- return
- self.ReloadDocument()
- def _DocumentStateChanged(self):
- """Called whenever the documents state (on disk etc) has been changed
- by the editor (eg, as the result of a save operation)
- """
- if self.GetPathName():
- try:
- self.fileStat = os.stat(self.GetPathName())
- except os.error:
- self.fileStat = None
- else:
- self.fileStat = None
- self.watcherThread._DocumentStateChanged()
- self._UpdateUIForState()
- self._ApplyOptionalToViews("_UpdateUIForState")
- self._ApplyOptionalToViews("SetReadOnly", self._IsReadOnly())
- self._ApplyOptionalToViews("SCISetSavePoint")
- # Allow the debugger to reset us too.
- import pywin.debugger
- if pywin.debugger.currentDebugger is not None:
- pywin.debugger.currentDebugger.UpdateDocumentLineStates(self)
-
- # Read-only document support - make it obvious to the user
- # that the file is read-only.
- def _IsReadOnly(self):
- return self.fileStat is not None and (self.fileStat[0] & 128)==0
- def _UpdateUIForState(self):
- """Change the title to reflect the state of the document -
- eg ReadOnly, Dirty, etc
- """
- filename = self.GetPathName()
- if not filename: return # New file - nothing to do
- try:
- # This seems necessary so the internal state of the window becomes
- # "visible". without it, it is still shown, but certain functions
- # (such as updating the title) dont immediately work?
- self.GetFirstView().ShowWindow(win32con.SW_SHOW)
- title = win32ui.GetFileTitle(filename)
- except win32ui.error:
- title = filename
- if self._IsReadOnly():
- title = title + " (read-only)"
- self.SetTitle(title)
- def MakeDocumentWritable(self):
- pretend_ss = 0 # Set to 1 to test this without source safe :-)
- if not self.scModuleName and not pretend_ss: # No Source Control support.
- win32ui.SetStatusText("Document is read-only, and no source-control system is configured")
- win32api.MessageBeep()
- return 0
- # We have source control support - check if the user wants to use it.
- msg = "Would you like to check this file out?"
- defButton = win32con.MB_YESNO
- if self.IsModified():
- msg = msg + "\r\n\r\nALL CHANGES IN THE EDITOR WILL BE LOST"
- defButton = win32con.MB_YESNO
- if win32ui.MessageBox(msg, None, defButton)!=win32con.IDYES:
- return 0
- if pretend_ss:
- print "We are only pretending to check it out!"
- win32api.SetFileAttributes(self.GetPathName(), win32con.FILE_ATTRIBUTE_NORMAL)
- self.ReloadDocument()
- return 1
-
- # Now call on the module to do it.
- if self.scModule is None:
- try:
- self.scModule = __import__(self.scModuleName)
- for part in self.scModuleName.split('.')[1:]:
- self.scModule = getattr(self.scModule, part)
- except:
- traceback.print_exc()
- print "Error loading source control module."
- return 0
-
- if self.scModule.CheckoutFile(self.GetPathName()):
- self.ReloadDocument()
- return 1
- return 0
- def CheckMakeDocumentWritable(self):
- if self._IsReadOnly():
- return self.MakeDocumentWritable()
- return 1
- def SaveModified(self):
- # Called as the document is closed. If we are about
- # to prompt for a save, bring the document to the foreground.
- if self.IsModified():
- frame = self.GetFirstView().GetParentFrame()
- try:
- frame.MDIActivate()
- frame.AutoRestore()
- except:
- print "Could not bring document to foreground"
- return self._obj_.SaveModified()
- # NOTE - I DONT use the standard threading module,
- # as this waits for all threads to terminate at shutdown.
- # When using the debugger, it is possible shutdown will
- # occur without Pythonwin getting a complete shutdown,
- # so we deadlock at the end - threading is waiting for
- import pywin.mfc.thread
- import win32event
- class FileWatchingThread(pywin.mfc.thread.WinThread):
- def __init__(self, doc):
- self.doc = doc
- self.adminEvent = win32event.CreateEvent(None, 0, 0, None)
- self.stopEvent = win32event.CreateEvent(None, 0, 0, None)
- self.watchEvent = None
- pywin.mfc.thread.WinThread.__init__(self)
- def _DocumentStateChanged(self):
- win32event.SetEvent(self.adminEvent)
- def RefreshEvent(self):
- self.hwnd = self.doc.GetFirstView().GetSafeHwnd()
- if self.watchEvent is not None:
- win32api.FindCloseChangeNotification(self.watchEvent)
- self.watchEvent = None
- path = self.doc.GetPathName()
- if path: path = os.path.dirname(path)
- if path:
- filter = win32con.FILE_NOTIFY_CHANGE_FILE_NAME | \
- win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES | \
- win32con.FILE_NOTIFY_CHANGE_LAST_WRITE
- try:
- self.watchEvent = win32api.FindFirstChangeNotification(path, 0, filter)
- except win32api.error, exc:
- print "Can not watch file", path, "for changes -", exc.strerror
- def SignalStop(self):
- win32event.SetEvent(self.stopEvent)
- def Run(self):
- while 1:
- handles = [self.stopEvent, self.adminEvent]
- if self.watchEvent is not None:
- handles.append(self.watchEvent)
- rc = win32event.WaitForMultipleObjects(handles, 0, win32event.INFINITE)
- if rc == win32event.WAIT_OBJECT_0:
- break
- elif rc == win32event.WAIT_OBJECT_0+1:
- self.RefreshEvent()
- else:
- win32api.PostMessage(self.hwnd, MSG_CHECK_EXTERNAL_FILE, 0, 0)
- try:
- # If the directory has been removed underneath us, we get this error.
- win32api.FindNextChangeNotification(self.watchEvent)
- except win32api.error, exc:
- print "Can not watch file", self.doc.GetPathName(), "for changes -", exc.strerror
- break
- # close a circular reference
- self.doc = None
- if self.watchEvent:
- win32api.FindCloseChangeNotification(self.watchEvent)
|