123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518 |
- # winout.py
- #
- # generic "output window"
- #
- # This Window will detect itself closing, and recreate next time output is
- # written to it.
- # This has the option of writing output at idle time (by hooking the
- # idle message, and queueing output) or writing as each
- # write is executed.
- # Updating the window directly gives a jerky appearance as many writes
- # take place between commands, and the windows scrolls, and updates etc
- # Updating at idle-time may defer all output of a long process, giving the
- # appearence nothing is happening.
- # There is a compromise "line" mode, which will output whenever
- # a complete line is available.
- # behaviour depends on self.writeQueueing
- # This module is thread safe - output can originate from any thread. If any thread
- # other than the main thread attempts to print, it is always queued until next idle time
- import sys, string, re
- from pywin.mfc import docview
- from pywin.framework import app, window
- import win32ui, win32api, win32con
- import queue
- debug = lambda msg: None
- ##debug=win32ui.OutputDebugString
- ##import win32trace;win32trace.InitWrite() # for debugging - delete me!
- ##debug = win32trace.write
- class flags:
- # queueing of output.
- WQ_NONE = 0
- WQ_LINE = 1
- WQ_IDLE = 2
- #WindowOutputDocumentParent=docview.RichEditDoc
- #WindowOutputDocumentParent=docview.Document
- import pywin.scintilla.document
- from pywin.scintilla import scintillacon
- from pywin import default_scintilla_encoding
- WindowOutputDocumentParent=pywin.scintilla.document.CScintillaDocument
- class WindowOutputDocument(WindowOutputDocumentParent):
- def SaveModified(self):
- return 1 # say it is OK to destroy my document
- def OnSaveDocument( self, fileName ):
- win32ui.SetStatusText("Saving file...",1)
- try:
- self.SaveFile(fileName)
- except IOError as details:
- win32ui.MessageBox("Error - could not save file\r\n\r\n%s"%details)
- return 0
- win32ui.SetStatusText("Ready")
- return 1
- class WindowOutputFrame(window.MDIChildWnd):
- def __init__(self, wnd = None):
- window.MDIChildWnd.__init__(self, wnd)
- self.HookMessage(self.OnSizeMove, win32con.WM_SIZE)
- self.HookMessage(self.OnSizeMove, win32con.WM_MOVE)
- def LoadFrame( self, idResource, style, wndParent, context ):
- self.template = context.template
- return self._obj_.LoadFrame(idResource, style, wndParent, context)
-
- def PreCreateWindow(self, cc):
- cc = self._obj_.PreCreateWindow(cc)
- if self.template.defSize and self.template.defSize[0] != self.template.defSize[1]:
- rect = app.RectToCreateStructRect(self.template.defSize)
- cc = cc[0], cc[1], cc[2], cc[3], rect, cc[5], cc[6], cc[7], cc[8]
- return cc
- def OnSizeMove(self, msg):
- # so recreate maintains position.
- # Need to map coordinates from the
- # frame windows first child.
- mdiClient = self.GetParent()
- self.template.defSize = mdiClient.ScreenToClient(self.GetWindowRect())
- def OnDestroy(self, message):
- self.template.OnFrameDestroy(self)
- return 1
- class WindowOutputViewImpl:
- def __init__(self):
- self.patErrorMessage=re.compile('\W*File "(.*)", line ([0-9]+)')
- self.template = self.GetDocument().GetDocTemplate()
- def HookHandlers(self):
- # Hook for the right-click menu.
- self.HookMessage(self.OnRClick,win32con.WM_RBUTTONDOWN)
- def OnDestroy(self, msg):
- self.template.OnViewDestroy(self)
- def OnInitialUpdate(self):
- self.RestoreKillBuffer()
- self.SetSel(-2) # end of buffer
- def GetRightMenuItems(self):
- ret = []
- flags=win32con.MF_STRING|win32con.MF_ENABLED
- ret.append((flags, win32ui.ID_EDIT_COPY, '&Copy'))
- ret.append((flags, win32ui.ID_EDIT_SELECT_ALL, '&Select all'))
- return ret
- #
- # Windows command handlers, virtuals, etc.
- #
- def OnRClick(self,params):
- paramsList = self.GetRightMenuItems()
- menu = win32ui.CreatePopupMenu()
- for appendParams in paramsList:
- if type(appendParams)!=type(()):
- appendParams = (appendParams,)
- menu.AppendMenu(*appendParams)
- menu.TrackPopupMenu(params[5]) # track at mouse position.
- return 0
- # as this is often used as an output window, exeptions will often
- # be printed. Therefore, we support this functionality at this level.
- # Returns TRUE if the current line is an error message line, and will
- # jump to it. FALSE if no error (and no action taken)
- def HandleSpecialLine(self):
- from . import scriptutils
- line = self.GetLine()
- if line[:11]=="com_error: ":
- # An OLE Exception - pull apart the exception
- # and try and locate a help file.
- try:
- import win32api, win32con
- det = eval(line[line.find(":")+1:].strip())
- win32ui.SetStatusText("Opening help file on OLE error...");
- from . import help
- help.OpenHelpFile(det[2][3],win32con.HELP_CONTEXT, det[2][4])
- return 1
- except win32api.error as details:
- win32ui.SetStatusText("The help file could not be opened - %s" % details.strerror)
- return 1
- except:
- win32ui.SetStatusText("Line is a COM error, but no WinHelp details can be parsed");
- # Look for a Python traceback.
- matchResult = self.patErrorMessage.match(line)
- if matchResult is None:
- # No match - try the previous line
- lineNo = self.LineFromChar()
- if lineNo > 0:
- line = self.GetLine(lineNo-1)
- matchResult = self.patErrorMessage.match(line)
- if matchResult is not None:
- # we have an error line.
- fileName = matchResult.group(1)
- if fileName[0]=="<":
- win32ui.SetStatusText("Can not load this file")
- return 1 # still was an error message.
- else:
- lineNoString = matchResult.group(2)
- # Attempt to locate the file (in case it is a relative spec)
- fileNameSpec = fileName
- fileName = scriptutils.LocatePythonFile(fileName)
- if fileName is None:
- # Dont force update, so it replaces the idle prompt.
- win32ui.SetStatusText("Cant locate the file '%s'" % (fileNameSpec), 0)
- return 1
- win32ui.SetStatusText("Jumping to line "+lineNoString+" of file "+fileName,1)
- if not scriptutils.JumpToDocument(fileName, int(lineNoString)):
- win32ui.SetStatusText("Could not open %s" % fileName)
- return 1 # still was an error message.
- return 1
- return 0 # not an error line
- def write(self, msg):
- return self.template.write(msg)
- def writelines(self, lines):
- for line in lines:
- self.write(line)
- def flush(self):
- self.template.flush()
- class WindowOutputViewRTF(docview.RichEditView, WindowOutputViewImpl):
- def __init__(self, doc):
- docview.RichEditView.__init__(self, doc)
- WindowOutputViewImpl.__init__(self)
- def OnInitialUpdate(self):
- WindowOutputViewImpl.OnInitialUpdate(self)
- return docview.RichEditView.OnInitialUpdate(self)
- def OnDestroy(self, msg):
- WindowOutputViewImpl.OnDestroy(self, msg)
- docview.RichEditView.OnDestroy(self, msg)
- def HookHandlers(self):
- WindowOutputViewImpl.HookHandlers(self)
- # Hook for finding and locating error messages
- self.HookMessage(self.OnLDoubleClick,win32con.WM_LBUTTONDBLCLK)
- # docview.RichEditView.HookHandlers(self)
- def OnLDoubleClick(self,params):
- if self.HandleSpecialLine():
- return 0 # dont pass on
- return 1 # pass it on by default.
-
- def RestoreKillBuffer(self):
- if len(self.template.killBuffer):
- self.StreamIn(win32con.SF_RTF, self._StreamRTFIn)
- self.template.killBuffer = []
- def SaveKillBuffer(self):
- self.StreamOut(win32con.SF_RTFNOOBJS, self._StreamRTFOut)
- def _StreamRTFOut(self, data):
- self.template.killBuffer.append(data)
- return 1 # keep em coming!
- def _StreamRTFIn(self, bytes):
- try:
- item = self.template.killBuffer[0]
- self.template.killBuffer.remove(item)
- if bytes < len(item):
- print("Warning - output buffer not big enough!")
- return item
- except IndexError:
- return None
- def dowrite(self, str):
- self.SetSel(-2)
- self.ReplaceSel(str)
- import pywin.scintilla.view
- class WindowOutputViewScintilla(pywin.scintilla.view.CScintillaView, WindowOutputViewImpl):
- def __init__(self, doc):
- pywin.scintilla.view.CScintillaView.__init__(self, doc)
- WindowOutputViewImpl.__init__(self)
- def OnInitialUpdate(self):
- pywin.scintilla.view.CScintillaView.OnInitialUpdate(self)
- self.SCISetMarginWidth(3)
- WindowOutputViewImpl.OnInitialUpdate(self)
- def OnDestroy(self, msg):
- WindowOutputViewImpl.OnDestroy(self, msg)
- pywin.scintilla.view.CScintillaView.OnDestroy(self, msg)
- def HookHandlers(self):
- WindowOutputViewImpl.HookHandlers(self)
- pywin.scintilla.view.CScintillaView.HookHandlers(self)
- self.GetParent().HookNotify(self.OnScintillaDoubleClick, scintillacon.SCN_DOUBLECLICK)
- ## self.HookMessage(self.OnLDoubleClick,win32con.WM_LBUTTONDBLCLK)
- def OnScintillaDoubleClick(self, std, extra):
- self.HandleSpecialLine()
- ## def OnLDoubleClick(self,params):
- ## return 0 # never dont pass on
- def RestoreKillBuffer(self):
- assert len(self.template.killBuffer) in [0,1], "Unexpected killbuffer contents"
- if self.template.killBuffer:
- self.SCIAddText(self.template.killBuffer[0])
- self.template.killBuffer = []
- def SaveKillBuffer(self):
- self.template.killBuffer = [self.GetTextRange(0,-1)]
- def dowrite(self, str):
- end = self.GetTextLength()
- atEnd = end==self.GetSel()[0]
- self.SCIInsertText(str, end)
- if atEnd:
- self.SetSel(self.GetTextLength())
- def SetWordWrap(self, bWrapOn = 1):
- if bWrapOn:
- wrap_mode = scintillacon.SC_WRAP_WORD
- else:
- wrap_mode = scintillacon.SC_WRAP_NONE
- self.SCISetWrapMode(wrap_mode)
- def _MakeColorizer(self):
- return None # No colorizer for me!
- WindowOutputView = WindowOutputViewScintilla
- # The WindowOutput class is actually an MFC template. This is a conventient way of
- # making sure that my state can exist beyond the life of the windows themselves.
- # This is primarily to support the functionality of a WindowOutput window automatically
- # being recreated if necessary when written to.
- class WindowOutput(docview.DocTemplate):
- """ Looks like a general Output Window - text can be written by the 'write' method.
- Will auto-create itself on first write, and also on next write after being closed """
- softspace=1
- def __init__(self, title=None, defSize=None, queueing = flags.WQ_LINE, \
- bAutoRestore = 1, style=None,
- makeDoc = None, makeFrame = None, makeView = None):
- """ init the output window -
- Params
- title=None -- What is the title of the window
- defSize=None -- What is the default size for the window - if this
- is a string, the size will be loaded from the ini file.
- queueing = flags.WQ_LINE -- When should output be written
- bAutoRestore=1 -- Should a minimized window be restored.
- style -- Style for Window, or None for default.
- makeDoc, makeFrame, makeView -- Classes for frame, view and window respectively.
- """
- if makeDoc is None: makeDoc = WindowOutputDocument
- if makeFrame is None: makeFrame = WindowOutputFrame
- if makeView is None: makeView = WindowOutputViewScintilla
- docview.DocTemplate.__init__(self, win32ui.IDR_PYTHONTYPE, \
- makeDoc, makeFrame, makeView)
- self.SetDocStrings("\nOutput\n\nText Documents (*.txt)\n.txt\n\n\n")
- win32ui.GetApp().AddDocTemplate(self)
- self.writeQueueing = queueing
- self.errorCantRecreate = 0
- self.killBuffer=[]
- self.style = style
- self.bAutoRestore = bAutoRestore
- self.title = title
- self.bCreating = 0
- self.interruptCount = 0
- if type(defSize)==type(''): # is a string - maintain size pos from ini file.
- self.iniSizeSection = defSize
- self.defSize = app.LoadWindowSize(defSize)
- self.loadedSize = self.defSize
- else:
- self.iniSizeSection = None
- self.defSize=defSize
- self.currentView = None
- self.outputQueue = queue.Queue(-1)
- self.mainThreadId = win32api.GetCurrentThreadId()
- self.idleHandlerSet = 0
- self.SetIdleHandler()
- def __del__(self):
- self.Close()
- def Create(self, title=None, style = None):
- self.bCreating = 1
- if title: self.title = title
- if style: self.style = style
- doc=self.OpenDocumentFile()
- if doc is None: return
- self.currentView = doc.GetFirstView()
- self.bCreating = 0
- if self.title: doc.SetTitle(self.title)
- def Close(self):
- self.RemoveIdleHandler()
- try:
- parent = self.currentView.GetParent()
- except (AttributeError, win32ui.error): # Already closed
- return
- parent.DestroyWindow()
- def SetTitle(self, title):
- self.title = title
- if self.currentView: self.currentView.GetDocument().SetTitle(self.title)
- def OnViewDestroy(self, view):
- self.currentView.SaveKillBuffer()
- self.currentView = None
- def OnFrameDestroy(self, frame):
- if self.iniSizeSection:
- # use GetWindowPlacement(), as it works even when min'd or max'd
- newSize = frame.GetWindowPlacement()[4]
- if self.loadedSize!=newSize:
- app.SaveWindowSize(self.iniSizeSection, newSize)
- def SetIdleHandler(self):
- if not self.idleHandlerSet:
- debug("Idle handler set\n")
- win32ui.GetApp().AddIdleHandler(self.QueueIdleHandler)
- self.idleHandlerSet = 1
- def RemoveIdleHandler(self):
- if self.idleHandlerSet:
- debug("Idle handler reset\n")
- if (win32ui.GetApp().DeleteIdleHandler(self.QueueIdleHandler)==0):
- debug('Error deleting idle handler\n')
- self.idleHandlerSet = 0
- def RecreateWindow(self):
- if self.errorCantRecreate:
- debug("Error = not trying again")
- return 0
- try:
- # This will fail if app shutting down
- win32ui.GetMainFrame().GetSafeHwnd()
- self.Create()
- return 1
- except (win32ui.error, AttributeError):
- self.errorCantRecreate = 1
- debug("Winout can not recreate the Window!\n")
- return 0
- # this handles the idle message, and does the printing.
- def QueueIdleHandler(self,handler,count):
- try:
- bEmpty = self.QueueFlush(20)
- # If the queue is empty, then we are back to idle and restart interrupt logic.
- if bEmpty: self.interruptCount = 0
- except KeyboardInterrupt:
- # First interrupt since idle we just pass on.
- # later ones we dump the queue and give up.
- self.interruptCount = self.interruptCount + 1
- if self.interruptCount > 1:
- # Drop the queue quickly as the user is already annoyed :-)
- self.outputQueue = queue.Queue(-1)
- print("Interrupted.")
- bEmpty = 1
- else:
- raise # re-raise the error so the users exception filters up.
- return not bEmpty # More to do if not empty.
- # Returns true if the Window needs to be recreated.
- def NeedRecreateWindow(self):
- try:
- if self.currentView is not None and self.currentView.IsWindow():
- return 0
- except (win32ui.error, AttributeError): # Attribute error if the win32ui object has died.
- pass
- return 1
- # Returns true if the Window is OK (either cos it was, or because it was recreated
- def CheckRecreateWindow(self):
- if self.bCreating: return 1
- if not self.NeedRecreateWindow():
- return 1
- if self.bAutoRestore:
- if self.RecreateWindow():
- return 1
- return 0
- def QueueFlush(self, max = None):
- # Returns true if the queue is empty after the flush
- # debug("Queueflush - %d, %d\n" % (max, self.outputQueue.qsize()))
- if self.bCreating: return 1
- items = []
- rc = 0
- while max is None or max > 0:
- try:
- item = self.outputQueue.get_nowait()
- items.append(item)
- except queue.Empty:
- rc = 1
- break
- if max is not None:
- max = max - 1
- if len(items) != 0:
- if not self.CheckRecreateWindow():
- debug(":Recreate failed!\n")
- return 1 # In trouble - so say we have nothing to do.
- win32ui.PumpWaitingMessages() # Pump paint messages
- self.currentView.dowrite(''.join(items))
- return rc
- def HandleOutput(self,message):
- # debug("QueueOutput on thread %d, flags %d with '%s'...\n" % (win32api.GetCurrentThreadId(), self.writeQueueing, message ))
- self.outputQueue.put(message)
- if win32api.GetCurrentThreadId() != self.mainThreadId:
- pass
- # debug("not my thread - ignoring queue options!\n")
- elif self.writeQueueing==flags.WQ_LINE:
- pos = message.rfind('\n')
- if pos>=0:
- # debug("Line queueing - forcing flush\n")
- self.QueueFlush()
- return
- elif self.writeQueueing==flags.WQ_NONE:
- # debug("WQ_NONE - flushing!\n")
- self.QueueFlush()
- return
- # Let our idle handler get it - wake it up
- try:
- win32ui.GetMainFrame().PostMessage(win32con.WM_USER) # Kick main thread off.
- except win32ui.error:
- # This can happen as the app is shutting down, so we send it to the C++ debugger
- win32api.OutputDebugString(message)
- # delegate certain fns to my view.
- def writelines(self, lines):
- for line in lines:
- self.write(line)
- def write(self,message):
- self.HandleOutput(message)
-
- def flush(self):
- self.QueueFlush()
- def HandleSpecialLine(self):
- self.currentView.HandleSpecialLine()
- def RTFWindowOutput(*args, **kw):
- kw['makeView'] = WindowOutputViewRTF
- return WindowOutput(*args, **kw)
- def thread_test(o):
- for i in range(5):
- o.write("Hi from thread %d\n" % (win32api.GetCurrentThreadId()))
- win32api.Sleep(100)
-
- def test():
- w = WindowOutput(queueing=flags.WQ_IDLE)
- w.write("First bit of text\n")
- import _thread
- for i in range(5):
- w.write("Hello from the main thread\n")
- _thread.start_new(thread_test, (w,))
- for i in range(2):
- w.write("Hello from the main thread\n")
- win32api.Sleep(50)
- return w
- if __name__=='__main__':
- test()
|