]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/py/shell.py
Comment icon
[wxWidgets.git] / wxPython / wx / py / shell.py
index 2232fd59b041682790d685ef3ea2df4a054106b1..ec8d7f4dae2f618b5548a73fb35610105c1947fb 100644 (file)
@@ -31,7 +31,7 @@ NAVKEYS = (wx.WXK_END, wx.WXK_LEFT, wx.WXK_RIGHT,
            wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PRIOR, wx.WXK_NEXT)
 
 
-class ShellFrame(frame.Frame):
+class ShellFrame(frame.Frame, frame.ShellFrameMixin):
     """Frame containing the shell component."""
 
     name = 'Shell Frame'
@@ -40,19 +40,31 @@ class ShellFrame(frame.Frame):
     def __init__(self, parent=None, id=-1, title='PyShell',
                  pos=wx.DefaultPosition, size=wx.DefaultSize,
                  style=wx.DEFAULT_FRAME_STYLE, locals=None,
-                 InterpClass=None, *args, **kwds):
+                 InterpClass=None,
+                 config=None, dataDir=None,
+                 *args, **kwds):
         """Create ShellFrame instance."""
         frame.Frame.__init__(self, parent, id, title, pos, size, style)
+        frame.ShellFrameMixin.__init__(self, config, dataDir)
+
+        if size == wx.DefaultSize:
+            self.SetSize((750, 525))
+
         intro = 'PyShell %s - The Flakiest Python Shell' % VERSION
-        intro += '\nSponsored by Orbtech - ' + \
-                 'Your source for Python programming expertise.'
         self.SetStatusText(intro.replace('\n', ', '))
         self.shell = Shell(parent=self, id=-1, introText=intro,
                            locals=locals, InterpClass=InterpClass,
+                           startupScript=self.startupScript,
+                           execStartupScript=self.execStartupScript,
                            *args, **kwds)
+
         # Override the shell so that status messages go to the status bar.
         self.shell.setStatusText = self.SetStatusText
 
+        self.shell.SetFocus()
+        self.LoadSettings()
+
+
     def OnClose(self, event):
         """Event handler for closing."""
         # This isn't working the way I want, but I'll leave it for now.
@@ -60,6 +72,7 @@ class ShellFrame(frame.Frame):
             if event.CanVeto():
                 event.Veto(True)
         else:
+            self.SaveSettings()
             self.shell.destroy()
             self.Destroy()
 
@@ -72,30 +85,43 @@ class ShellFrame(frame.Frame):
                'the other half is still in the oven.\n\n' + \
                'Shell Revision: %s\n' % self.shell.revision + \
                'Interpreter Revision: %s\n\n' % self.shell.interp.revision + \
+               'Platform: %s\n' % sys.platform + \
                'Python Version: %s\n' % sys.version.split()[0] + \
                'wxPython Version: %s\n' % wx.VERSION_STRING + \
-               'Platform: %s\n' % sys.platform
+               ('\t(%s)\n' % ", ".join(wx.PlatformInfo[1:])) 
         dialog = wx.MessageDialog(self, text, title,
                                   wx.OK | wx.ICON_INFORMATION)
         dialog.ShowModal()
         dialog.Destroy()
 
 
-class ShellFacade:
-    """Simplified interface to all shell-related functionality.
+    def OnHelp(self, event):
+        """Show a help dialog."""
+        frame.ShellFrameMixin.OnHelp(self, event)
 
-    This is a semi-transparent facade, in that all attributes of other
-    are accessible, even though only some are visible to the user."""
 
-    name = 'Shell Interface'
-    revision = __revision__
+    def LoadSettings(self):
+        if self.config is not None:
+            frame.ShellFrameMixin.LoadSettings(self)
+            frame.Frame.LoadSettings(self, self.config)
+            self.shell.LoadSettings(self.config)
 
-    def __init__(self, other):
-        """Create a ShellFacade instance."""
-        d = self.__dict__
-        d['other'] = other
-        d['helpText'] = \
-"""
+    def SaveSettings(self, force=False):
+        if self.config is not None:
+            frame.ShellFrameMixin.SaveSettings(self)
+            if self.autoSaveSettings or force:
+                frame.Frame.SaveSettings(self, self.config)
+                self.shell.SaveSettings(self.config)
+
+    def DoSaveSettings(self):
+        if self.config is not None:
+            self.SaveSettings(force=True)
+            self.config.Flush()
+        
+
+
+
+HELP_TEXT = """\
 * Key bindings:
 Home              Go to the beginning of the command or line.
 Shift+Home        Select to the beginning of the command or line.
@@ -103,6 +129,7 @@ Shift+End         Select to the end of the line.
 End               Go to the end of the line.
 Ctrl+C            Copy selected text, removing prompts.
 Ctrl+Shift+C      Copy selected text, retaining prompts.
+Alt+C             Copy to the clipboard, including prefixed prompts.
 Ctrl+X            Cut selected text.
 Ctrl+V            Paste from clipboard.
 Ctrl+Shift+V      Paste and run multiple commands from clipboard.
@@ -118,8 +145,31 @@ Ctrl+Enter        Insert new line into multiline command.
 Ctrl+]            Increase font size.
 Ctrl+[            Decrease font size.
 Ctrl+=            Default font size.
+Ctrl-Space        Show Auto Completion.
+Ctrl-Alt-Space    Show Call Tip.
+Shift+Enter       Complete Text from History.
+Ctrl+F            Search 
+F3                Search next
+Ctrl+H            "hide" lines containing selection / "unhide"
+F12               on/off "free-edit" mode
 """
 
+class ShellFacade:
+    """Simplified interface to all shell-related functionality.
+
+    This is a semi-transparent facade, in that all attributes of other
+    are accessible, even though only some are visible to the user."""
+
+    name = 'Shell Interface'
+    revision = __revision__
+
+    def __init__(self, other):
+        """Create a ShellFacade instance."""
+        d = self.__dict__
+        d['other'] = other
+        d['helpText'] = HELP_TEXT
+        d['this'] = other.this
+
     def help(self):
         """Display some useful information about how to use the shell."""
         self.write(self.helpText)
@@ -150,6 +200,7 @@ Ctrl+=            Default font size.
             'autoCompleteIncludeDouble',
             'autoCompleteIncludeMagic',
             'autoCompleteIncludeSingle',
+            'callTipInsert',
             'clear',
             'pause',
             'prompt',
@@ -166,6 +217,7 @@ Ctrl+=            Default font size.
         return list
 
 
+
 class Shell(editwindow.EditWindow):
     """Shell based on StyledTextCtrl."""
 
@@ -174,26 +226,32 @@ class Shell(editwindow.EditWindow):
 
     def __init__(self, parent, id=-1, pos=wx.DefaultPosition,
                  size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
-                 introText='', locals=None, InterpClass=None, *args, **kwds):
+                 introText='', locals=None, InterpClass=None,
+                 startupScript=None, execStartupScript=True,
+                 *args, **kwds):
         """Create Shell instance."""
         editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
         self.wrap()
         if locals is None:
             import __main__
             locals = __main__.__dict__
+
         # Grab these so they can be restored by self.redirect* methods.
         self.stdin = sys.stdin
         self.stdout = sys.stdout
         self.stderr = sys.stderr
+
         # Import a default interpreter class if one isn't provided.
         if InterpClass == None:
             from interpreter import Interpreter
         else:
             Interpreter = InterpClass
+
         # Create a replacement for stdin.
         self.reader = PseudoFileIn(self.readline, self.readlines)
         self.reader.input = ''
         self.reader.isreading = False
+
         # Set up the interpreter.
         self.interp = Interpreter(locals=locals,
                                   rawin=self.raw_input,
@@ -201,15 +259,20 @@ class Shell(editwindow.EditWindow):
                                   stdout=PseudoFileOut(self.writeOut),
                                   stderr=PseudoFileErr(self.writeErr),
                                   *args, **kwds)
+
         # Set up the buffer.
         self.buffer = Buffer()
+
         # Find out for which keycodes the interpreter will autocomplete.
         self.autoCompleteKeys = self.interp.getAutoCompleteKeys()
+
         # Keep track of the last non-continuation prompt positions.
         self.promptPosStart = 0
         self.promptPosEnd = 0
+
         # Keep track of multi-line commands.
         self.more = False
+
         # Create the command history.  Commands are added into the
         # front of the list (ie. at index 0) as they are entered.
         # self.historyIndex is the current position in the history; it
@@ -219,23 +282,51 @@ class Shell(editwindow.EditWindow):
         # command, not in the history.
         self.history = []
         self.historyIndex = -1
+
+        #seb add mode for "free edit"
+        self.noteMode = 0
+        self.MarkerDefine(0,stc.STC_MARK_ROUNDRECT)  # marker for hidden
+        self.searchTxt = ""
+
         # Assign handlers for keyboard events.
-        wx.EVT_CHAR(self, self.OnChar)
-        wx.EVT_KEY_DOWN(self, self.OnKeyDown)
+        self.Bind(wx.EVT_CHAR, self.OnChar)
+        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
+
         # Assign handler for idle time.
         self.waiting = False
-        wx.EVT_IDLE(self, self.OnIdle)
+        self.Bind(wx.EVT_IDLE, self.OnIdle)
+
         # Display the introductory banner information.
         self.showIntro(introText)
+
         # Assign some pseudo keywords to the interpreter's namespace.
         self.setBuiltinKeywords()
+
         # Add 'shell' to the interpreter's local namespace.
         self.setLocalShell()
+
+        ## NOTE:  See note at bottom of this file...
+        ## #seb: File drag and drop
+        ## self.SetDropTarget( FileDropTarget(self) )
+
         # Do this last so the user has complete control over their
         # environment.  They can override anything they want.
-        self.execStartupScript(self.interp.startupScript)
+        if execStartupScript:
+            if startupScript is None:
+                startupScript = os.environ.get('PYTHONSTARTUP')
+            self.execStartupScript(startupScript)
+        else:
+            self.prompt()
+        
         wx.CallAfter(self.ScrollToLine, 0)
 
+
+    def clearHistory(self):
+        self.history = []
+        self.historyIndex = -1
+        dispatcher.send(signal="Shell.clearHistory")
+
+
     def destroy(self):
         del self.interp
 
@@ -252,46 +343,50 @@ class Shell(editwindow.EditWindow):
     def showIntro(self, text=''):
         """Display introductory text in the shell."""
         if text:
-            if not text.endswith(os.linesep):
-                text += os.linesep
             self.write(text)
         try:
-            self.write(self.interp.introText)
+            if self.interp.introText:
+                if text and not text.endswith(os.linesep):
+                    self.write(os.linesep)
+                self.write(self.interp.introText)
         except AttributeError:
             pass
 
     def setBuiltinKeywords(self):
         """Create pseudo keywords as part of builtins.
 
-        This sets `close`, `exit` and `quit` to a helpful string.
+        This sets "close", "exit" and "quit" to a helpful string.
         """
         import __builtin__
         __builtin__.close = __builtin__.exit = __builtin__.quit = \
             'Click on the close button to leave the application.'
 
+
     def quit(self):
         """Quit the application."""
-
         # XXX Good enough for now but later we want to send a close event.
-
         # In the close event handler we can make sure they want to
         # quit.  Other applications, like PythonCard, may choose to
         # hide rather than quit so we should just post the event and
         # let the surrounding app decide what it wants to do.
         self.write('Click on the close button to leave the application.')
 
+
     def setLocalShell(self):
         """Add 'shell' to locals as reference to ShellFacade instance."""
         self.interp.locals['shell'] = ShellFacade(other=self)
 
+
     def execStartupScript(self, startupScript):
         """Execute the user's PYTHONSTARTUP script if they have one."""
         if startupScript and os.path.isfile(startupScript):
             text = 'Startup script executed: ' + startupScript
             self.push('print %r; execfile(%r)' % (text, startupScript))
+            self.interp.startupScript = startupScript
         else:
             self.push('')
 
+
     def about(self):
         """Display information about Py."""
         text = """
@@ -301,26 +396,33 @@ Py Shell Revision: %s
 Py Interpreter Revision: %s
 Python Version: %s
 wxPython Version: %s
+wxPython PlatformInfo: %s
 Platform: %s""" % \
         (__author__, VERSION, self.revision, self.interp.revision,
-         sys.version.split()[0], wx.VERSION_STRING, sys.platform)
+         sys.version.split()[0], wx.VERSION_STRING, str(wx.PlatformInfo),
+         sys.platform)
         self.write(text.strip())
 
+
     def OnChar(self, event):
         """Keypress event handler.
 
         Only receives an event if OnKeyDown calls event.Skip() for the
         corresponding event."""
 
+        if self.noteMode:
+            event.Skip()
+            return
+
         # Prevent modification of previously submitted
         # commands/responses.
         if not self.CanEdit():
             return
-        key = event.KeyCode()
+        key = event.GetKeyCode()
         currpos = self.GetCurrentPos()
         stoppos = self.promptPosEnd
         # Return (Enter) needs to be ignored in this handler.
-        if key == wx.WXK_RETURN:
+        if key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
             pass
         elif key in self.autoCompleteKeys:
             # Usually the dot (period) key activates auto completion.
@@ -342,19 +444,21 @@ Platform: %s""" % \
             self.ReplaceSelection('')
             command = self.GetTextRange(stoppos, currpos) + '('
             self.write('(')
-            self.autoCallTipShow(command)
+            self.autoCallTipShow(command, self.GetCurrentPos() == self.GetTextLength())
         else:
             # Allow the normal event handling to take place.
             event.Skip()
 
+
     def OnKeyDown(self, event):
         """Key down event handler."""
 
-        key = event.KeyCode()
+        key = event.GetKeyCode()
         # If the auto-complete window is up let it do its thing.
         if self.AutoCompActive():
             event.Skip()
             return
+        
         # Prevent modification of previously submitted
         # commands/responses.
         controlDown = event.ControlDown()
@@ -363,54 +467,122 @@ Platform: %s""" % \
         currpos = self.GetCurrentPos()
         endpos = self.GetTextLength()
         selecting = self.GetSelectionStart() != self.GetSelectionEnd()
+        
+        if controlDown and shiftDown and key in (ord('F'), ord('f')): 
+            li = self.GetCurrentLine()
+            m = self.MarkerGet(li)
+            if m & 1<<0:
+                startP = self.PositionFromLine(li)
+                self.MarkerDelete(li, 0)
+                maxli = self.GetLineCount()
+                li += 1 # li stayed visible as header-line
+                li0 = li 
+                while li<maxli and self.GetLineVisible(li) == 0:
+                    li += 1
+                endP = self.GetLineEndPosition(li-1)
+                self.ShowLines(li0, li-1)
+                self.SetSelection( startP, endP ) # select reappearing text to allow "hide again"
+                return
+            startP,endP = self.GetSelection()
+            endP-=1
+            startL,endL = self.LineFromPosition(startP), self.LineFromPosition(endP)
+
+            if endL == self.LineFromPosition(self.promptPosEnd): # never hide last prompt
+                endL -= 1
+
+            m = self.MarkerGet(startL)
+            self.MarkerAdd(startL, 0)
+            self.HideLines(startL+1,endL)
+            self.SetCurrentPos( startP ) # to ensure caret stays visible !
+
+        if key == wx.WXK_F12: #seb
+            if self.noteMode:
+                # self.promptPosStart not used anyway - or ? 
+                self.promptPosEnd = self.PositionFromLine( self.GetLineCount()-1 ) + len(str(sys.ps1))
+                self.GotoLine(self.GetLineCount())
+                self.GotoPos(self.promptPosEnd)
+                self.prompt()  #make sure we have a prompt
+                self.SetCaretForeground("black")
+                self.SetCaretWidth(1)    #default
+                self.SetCaretPeriod(500) #default
+            else:
+                self.SetCaretForeground("red")
+                self.SetCaretWidth(4)
+                self.SetCaretPeriod(0) #steady
+
+            self.noteMode = not self.noteMode
+            return
+        if self.noteMode:
+            event.Skip()
+            return
+
         # Return (Enter) is used to submit a command to the
         # interpreter.
-        if not controlDown and key == wx.WXK_RETURN:
+        if (not controlDown and not shiftDown and not altDown) and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
             if self.CallTipActive():
                 self.CallTipCancel()
             self.processLine()
-        # Ctrl+Return (Cntrl+Enter) is used to insert a line break.
-        elif controlDown and key == wx.WXK_RETURN:
+            
+        # Complete Text (from already typed words)    
+        elif shiftDown and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
+            self.OnShowCompHistory()
+            
+        # Ctrl+Return (Ctrl+Enter) is used to insert a line break.
+        elif controlDown and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
             if self.CallTipActive():
                 self.CallTipCancel()
             if currpos == endpos:
                 self.processLine()
             else:
                 self.insertLineBreak()
+                
         # Let Ctrl-Alt-* get handled normally.
         elif controlDown and altDown:
             event.Skip()
+            
         # Clear the current, unexecuted command.
         elif key == wx.WXK_ESCAPE:
             if self.CallTipActive():
                 event.Skip()
             else:
                 self.clearCommand()
+
+        # Clear the current command
+        elif key == wx.WXK_BACK and controlDown and shiftDown:
+            self.clearCommand()
+
         # Increase font size.
-        elif controlDown and key in (ord(']'),):
+        elif controlDown and key in (ord(']'), wx.WXK_NUMPAD_ADD):
             dispatcher.send(signal='FontIncrease')
+
         # Decrease font size.
-        elif controlDown and key in (ord('['),):
+        elif controlDown and key in (ord('['), wx.WXK_NUMPAD_SUBTRACT):
             dispatcher.send(signal='FontDecrease')
+
         # Default font size.
-        elif controlDown and key in (ord('='),):
+        elif controlDown and key in (ord('='), wx.WXK_NUMPAD_DIVIDE):
             dispatcher.send(signal='FontDefault')
+
         # Cut to the clipboard.
         elif (controlDown and key in (ord('X'), ord('x'))) \
-        or (shiftDown and key == wx.WXK_DELETE):
+                 or (shiftDown and key == wx.WXK_DELETE):
             self.Cut()
+
         # Copy to the clipboard.
         elif controlDown and not shiftDown \
-            and key in (ord('C'), ord('c'), wx.WXK_INSERT):
+                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
             self.Copy()
+
         # Copy to the clipboard, including prompts.
         elif controlDown and shiftDown \
-            and key in (ord('C'), ord('c'), wx.WXK_INSERT):
+                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
             self.CopyWithPrompts()
+
         # Copy to the clipboard, including prefixed prompts.
         elif altDown and not controlDown \
-            and key in (ord('C'), ord('c'), wx.WXK_INSERT):
+                 and key in (ord('C'), ord('c'), wx.WXK_INSERT):
             self.CopyWithPromptsPrefixed()
+
         # Home needs to be aware of the prompt.
         elif key == wx.WXK_HOME:
             home = self.promptPosEnd
@@ -421,6 +593,7 @@ Platform: %s""" % \
                     self.EnsureCaretVisible()
             else:
                 event.Skip()
+
         #
         # The following handlers modify text, so we need to see if
         # there is a selection that includes text prior to the prompt.
@@ -428,58 +601,121 @@ Platform: %s""" % \
         # Don't modify a selection with text prior to the prompt.
         elif selecting and key not in NAVKEYS and not self.CanEdit():
             pass
+
         # Paste from the clipboard.
         elif (controlDown and not shiftDown and key in (ord('V'), ord('v'))) \
                  or (shiftDown and not controlDown and key == wx.WXK_INSERT):
             self.Paste()
+
+        # manually invoke AutoComplete and Calltips
+        elif controlDown and key == wx.WXK_SPACE:
+            self.OnCallTipAutoCompleteManually(shiftDown)
+
         # Paste from the clipboard, run commands.
         elif controlDown and shiftDown and key in (ord('V'), ord('v')):
             self.PasteAndRun()
+            
         # Replace with the previous command from the history buffer.
         elif (controlDown and key == wx.WXK_UP) \
                  or (altDown and key in (ord('P'), ord('p'))):
             self.OnHistoryReplace(step=+1)
+            
         # Replace with the next command from the history buffer.
         elif (controlDown and key == wx.WXK_DOWN) \
                  or (altDown and key in (ord('N'), ord('n'))):
             self.OnHistoryReplace(step=-1)
+            
         # Insert the previous command from the history buffer.
         elif (shiftDown and key == wx.WXK_UP) and self.CanEdit():
             self.OnHistoryInsert(step=+1)
+            
         # Insert the next command from the history buffer.
         elif (shiftDown and key == wx.WXK_DOWN) and self.CanEdit():
             self.OnHistoryInsert(step=-1)
+            
         # Search up the history for the text in front of the cursor.
         elif key == wx.WXK_F8:
             self.OnHistorySearch()
+            
         # Don't backspace over the latest non-continuation prompt.
         elif key == wx.WXK_BACK:
             if selecting and self.CanEdit():
                 event.Skip()
             elif currpos > self.promptPosEnd:
                 event.Skip()
+                
         # Only allow these keys after the latest prompt.
         elif key in (wx.WXK_TAB, wx.WXK_DELETE):
             if self.CanEdit():
                 event.Skip()
+                
         # Don't toggle between insert mode and overwrite mode.
         elif key == wx.WXK_INSERT:
             pass
+        
         # Don't allow line deletion.
         elif controlDown and key in (ord('L'), ord('l')):
             pass
+
         # Don't allow line transposition.
         elif controlDown and key in (ord('T'), ord('t')):
             pass
+
         # Basic navigation keys should work anywhere.
         elif key in NAVKEYS:
             event.Skip()
+
         # Protect the readonly portion of the shell.
         elif not self.CanEdit():
             pass
+
         else:
             event.Skip()
 
+
+    def OnShowCompHistory(self):
+        """Show possible autocompletion Words from already typed words."""
+        
+        #copy from history
+        his = self.history[:]
+        
+        #put together in one string
+        joined = " ".join (his)
+        import re
+
+        #sort out only "good" words
+        newlist = re.split("[ \.\[\]=}(\)\,0-9\"]", joined)
+        
+        #length > 1 (mix out "trash")
+        thlist = []
+        for i in newlist:
+            if len (i) > 1:
+                thlist.append (i)
+        
+        #unique (no duplicate words
+        #oneliner from german python forum => unique list
+        unlist = [thlist[i] for i in xrange(len(thlist)) if thlist[i] not in thlist[:i]]
+            
+        #sort lowercase
+        unlist.sort(lambda a, b: cmp(a.lower(), b.lower()))
+        
+        #this is more convenient, isn't it?
+        self.AutoCompSetIgnoreCase(True)
+        
+        #join again together in a string
+        stringlist = " ".join(unlist)
+
+        #pos von 0 noch ausrechnen
+
+        #how big is the offset?
+        cpos = self.GetCurrentPos() - 1
+        while chr (self.GetCharAt (cpos)).isalnum():
+            cpos -= 1
+            
+        #the most important part
+        self.AutoCompShow(self.GetCurrentPos() - cpos -1, stringlist)
+
+
     def clearCommand(self):
         """Delete the current, unexecuted command."""
         startpos = self.promptPosEnd
@@ -588,6 +824,7 @@ Platform: %s""" % \
                 self.write(os.linesep)
             else:
                 self.push(command)
+                wx.FutureCall(1, self.EnsureCaretVisible)
         # Or replace the current command with the other command.
         else:
             # If the line contains a command (even an invalid one).
@@ -664,9 +901,10 @@ Platform: %s""" % \
             text = text[ps2size:]
         return text
 
-    def push(self, command):
+    def push(self, command, silent = False):
         """Send command to the interpreter for execution."""
-        self.write(os.linesep)
+        if not silent:
+            self.write(os.linesep)
         busy = wx.BusyCursor()
         self.waiting = True
         self.more = self.interp.push(command)
@@ -674,7 +912,8 @@ Platform: %s""" % \
         del busy
         if not self.more:
             self.addHistory(command.rstrip())
-        self.prompt()
+        if not silent:
+            self.prompt()
 
     def addHistory(self, command):
         """Add command to the command history."""
@@ -685,6 +924,7 @@ Platform: %s""" % \
         if command != '' \
         and (len(self.history) == 0 or command != self.history[0]):
             self.history.insert(0, command)
+            dispatcher.send(signal="Shell.addHistory", command=command)
 
     def write(self, text):
         """Display text in the shell.
@@ -816,7 +1056,7 @@ Platform: %s""" % \
         finally:
             file.close()
 
-    def autoCompleteShow(self, command):
+    def autoCompleteShow(self, command, offset = 0):
         """Display auto-completion popup list."""
         self.AutoCompSetAutoHide(self.autoCompleteAutoHide)
         self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
@@ -826,19 +1066,19 @@ Platform: %s""" % \
                     includeDouble=self.autoCompleteIncludeDouble)
         if list:
             options = ' '.join(list)
-            offset = 0
+            #offset = 0
             self.AutoCompShow(offset, options)
 
-    def autoCallTipShow(self, command):
+    def autoCallTipShow(self, command, insertcalltip = True, forceCallTip = False):
         """Display argument spec and docstring in a popup window."""
         if self.CallTipActive():
             self.CallTipCancel()
         (name, argspec, tip) = self.interp.getCallTip(command)
         if tip:
             dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip)
-        if not self.autoCallTip:
+        if not self.autoCallTip and not forceCallTip:
             return
-        if argspec:
+        if argspec and insertcalltip and self.callTipInsert:
             startpos = self.GetCurrentPos()
             self.write(argspec + ')')
             endpos = self.GetCurrentPos()
@@ -851,6 +1091,53 @@ Platform: %s""" % \
             # fallback.
             tippos = max(tippos, fallback)
             self.CallTipShow(tippos, tip)
+    
+    def OnCallTipAutoCompleteManually (self, shiftDown):
+        """AutoComplete and Calltips manually."""
+        if self.AutoCompActive():
+            self.AutoCompCancel()
+        currpos = self.GetCurrentPos()
+        stoppos = self.promptPosEnd
+
+        cpos = currpos
+        #go back until '.' is found
+        pointavailpos = -1
+        while cpos >= stoppos:
+            if self.GetCharAt(cpos) == ord ('.'):
+                pointavailpos = cpos
+                break
+            cpos -= 1
+
+        #word from non whitespace until '.'
+        if pointavailpos != -1:
+            #look backward for first whitespace char
+            textbehind = self.GetTextRange (pointavailpos + 1, currpos)
+            pointavailpos += 1
+
+            if not shiftDown:
+                #call AutoComplete
+                stoppos = self.promptPosEnd
+                textbefore = self.GetTextRange(stoppos, pointavailpos)
+                self.autoCompleteShow(textbefore, len (textbehind))
+            else:
+                #call CallTips
+                cpos = pointavailpos
+                begpos = -1
+                while cpos > stoppos:
+                    if chr(self.GetCharAt(cpos)).isspace():
+                        begpos = cpos
+                        break
+                    cpos -= 1
+                if begpos == -1:
+                    begpos = cpos
+                ctips = self.GetTextRange (begpos, currpos)
+                ctindex = ctips.find ('(')
+                if ctindex != -1 and not self.CallTipActive():
+                    #insert calltip, if current pos is '(', otherwise show it only
+                    self.autoCallTipShow(ctips[:ctindex + 1], 
+                        self.GetCharAt(currpos - 1) == ord('(') and self.GetCurrentPos() == self.GetTextLength(),
+                        True)
+                
 
     def writeOut(self, text):
         """Replacement for stdout."""
@@ -973,52 +1260,64 @@ Platform: %s""" % \
                     self.write(command)
             wx.TheClipboard.Close()
 
+
     def PasteAndRun(self):
         """Replace selection with clipboard contents, run commands."""
+        text = ''
         if wx.TheClipboard.Open():
-            ps1 = str(sys.ps1)
-            ps2 = str(sys.ps2)
             if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
                 data = wx.TextDataObject()
                 if wx.TheClipboard.GetData(data):
-                    endpos = self.GetTextLength()
-                    self.SetCurrentPos(endpos)
-                    startpos = self.promptPosEnd
-                    self.SetSelection(startpos, endpos)
-                    self.ReplaceSelection('')
                     text = data.GetText()
-                    text = text.lstrip()
-                    text = self.fixLineEndings(text)
-                    text = self.lstripPrompt(text)
-                    text = text.replace(os.linesep + ps1, '\n')
-                    text = text.replace(os.linesep + ps2, '\n')
-                    text = text.replace(os.linesep, '\n')
-                    lines = text.split('\n')
-                    commands = []
-                    command = ''
-                    for line in lines:
-                        if line.strip() == ps2.strip():
-                            # If we are pasting from something like a
-                            # web page that drops the trailing space
-                            # from the ps2 prompt of a blank line.
-                            line = ''
-                        if line.strip() != '' and line.lstrip() == line:
-                            # New command.
-                            if command:
-                                # Add the previous command to the list.
-                                commands.append(command)
-                            # Start a new command, which may be multiline.
-                            command = line
-                        else:
-                            # Multiline command. Add to the command.
-                            command += '\n'
-                            command += line
-                    commands.append(command)
-                    for command in commands:
-                        command = command.replace('\n', os.linesep + ps2)
-                        self.write(command)
-                        self.processLine()
             wx.TheClipboard.Close()
+        if text:
+            self.Execute(text)
+            
+
+    def Execute(self, text):
+        """Replace selection with text and run commands."""
+        ps1 = str(sys.ps1)
+        ps2 = str(sys.ps2)
+        endpos = self.GetTextLength()
+        self.SetCurrentPos(endpos)
+        startpos = self.promptPosEnd
+        self.SetSelection(startpos, endpos)
+        self.ReplaceSelection('')
+        text = text.lstrip()
+        text = self.fixLineEndings(text)
+        text = self.lstripPrompt(text)
+        text = text.replace(os.linesep + ps1, '\n')
+        text = text.replace(os.linesep + ps2, '\n')
+        text = text.replace(os.linesep, '\n')
+        lines = text.split('\n')
+        commands = []
+        command = ''
+        for line in lines:
+            if line.strip() == ps2.strip():
+                # If we are pasting from something like a
+                # web page that drops the trailing space
+                # from the ps2 prompt of a blank line.
+                line = ''
+            lstrip = line.lstrip()
+            if line.strip() != '' and lstrip == line and \
+                    lstrip[:4] not in ['else','elif'] and \
+                    lstrip[:6] != 'except':
+                # New command.
+                if command:
+                    # Add the previous command to the list.
+                    commands.append(command)
+                # Start a new command, which may be multiline.
+                command = line
+            else:
+                # Multiline command. Add to the command.
+                command += '\n'
+                command += line
+        commands.append(command)
+        for command in commands:
+            command = command.replace('\n', os.linesep + ps2)
+            self.write(command)
+            self.processLine()
+
 
     def wrap(self, wrap=True):
         """Sets whether text is word wrapped."""
@@ -1033,3 +1332,101 @@ Platform: %s""" % \
         This number of points is added to the size of all fonts.  It
         may be positive to magnify or negative to reduce."""
         self.SetZoom(points)
+
+
+
+    def LoadSettings(self, config):
+        self.autoComplete              = config.ReadBool('Options/AutoComplete', True)
+        self.autoCompleteIncludeMagic  = config.ReadBool('Options/AutoCompleteIncludeMagic', True)
+        self.autoCompleteIncludeSingle = config.ReadBool('Options/AutoCompleteIncludeSingle', True)
+        self.autoCompleteIncludeDouble = config.ReadBool('Options/AutoCompleteIncludeDouble', True)
+
+        self.autoCallTip = config.ReadBool('Options/AutoCallTip', True)
+        self.callTipInsert = config.ReadBool('Options/CallTipInsert', True)
+        self.SetWrapMode(config.ReadBool('View/WrapMode', True))
+
+        useAA = config.ReadBool('Options/UseAntiAliasing', self.GetUseAntiAliasing())
+        self.SetUseAntiAliasing(useAA)
+        self.lineNumbers = config.ReadBool('View/ShowLineNumbers', True)
+        self.setDisplayLineNumbers (self.lineNumbers)
+        zoom = config.ReadInt('View/Zoom/Shell', -99)
+        if zoom != -99:
+            self.SetZoom(zoom)
+
+
+    
+    def SaveSettings(self, config):
+        config.WriteBool('Options/AutoComplete', self.autoComplete)
+        config.WriteBool('Options/AutoCompleteIncludeMagic', self.autoCompleteIncludeMagic)
+        config.WriteBool('Options/AutoCompleteIncludeSingle', self.autoCompleteIncludeSingle)
+        config.WriteBool('Options/AutoCompleteIncludeDouble', self.autoCompleteIncludeDouble)
+        config.WriteBool('Options/AutoCallTip', self.autoCallTip)
+        config.WriteBool('Options/CallTipInsert', self.callTipInsert)
+        config.WriteBool('Options/UseAntiAliasing', self.GetUseAntiAliasing())
+        config.WriteBool('View/WrapMode', self.GetWrapMode())
+        config.WriteBool('View/ShowLineNumbers', self.lineNumbers)
+        config.WriteInt('View/Zoom/Shell', self.GetZoom())
+
+
+
+## NOTE: The DnD of file names is disabled until I can figure out how
+## best to still allow DnD of text.
+
+
+## #seb : File drag and drop
+## class FileDropTarget(wx.FileDropTarget):
+##     def __init__(self, obj):
+##         wx.FileDropTarget.__init__(self)
+##         self.obj = obj
+##     def OnDropFiles(self, x, y, filenames):
+##         if len(filenames) == 1:
+##             txt = 'r\"%s\"' % filenames[0]
+##         else:
+##             txt = '( '
+##             for f in filenames:
+##                 txt += 'r\"%s\" , ' % f
+##             txt += ')'
+##         self.obj.AppendText(txt)
+##         pos = self.obj.GetCurrentPos()
+##         self.obj.SetCurrentPos( pos )
+##         self.obj.SetSelection( pos, pos )
+
+
+
+## class TextAndFileDropTarget(wx.DropTarget):
+##     def __init__(self, shell):
+##         wx.DropTarget.__init__(self)
+##         self.shell = shell
+##         self.compdo = wx.DataObjectComposite()
+##         self.textdo = wx.TextDataObject()
+##         self.filedo = wx.FileDataObject()
+##         self.compdo.Add(self.textdo)
+##         self.compdo.Add(self.filedo, True)
+        
+##         self.SetDataObject(self.compdo)
+                
+##     def OnDrop(self, x, y):
+##         return True
+    
+##     def OnData(self, x, y, result):
+##         self.GetData()
+##         if self.textdo.GetTextLength() > 1:
+##             text = self.textdo.GetText()
+##             # *** Do somethign with the dragged text here...
+##             self.textdo.SetText('')
+##         else:
+##             filenames = str(self.filename.GetFilenames())
+##             if len(filenames) == 1:
+##                 txt = 'r\"%s\"' % filenames[0]
+##             else:
+##                 txt = '( '
+##                 for f in filenames:
+##                     txt += 'r\"%s\" , ' % f
+##                 txt += ')'
+##             self.shell.AppendText(txt)
+##             pos = self.shell.GetCurrentPos()
+##             self.shell.SetCurrentPos( pos )
+##             self.shell.SetSelection( pos, pos )
+
+##         return result
+