symbian-qemu-0.9.1-12/python-2.6.1/Lib/idlelib/AutoCompleteWindow.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     1 """
       
     2 An auto-completion window for IDLE, used by the AutoComplete extension
       
     3 """
       
     4 from Tkinter import *
       
     5 from MultiCall import MC_SHIFT
       
     6 import AutoComplete
       
     7 
       
     8 HIDE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-hide>>"
       
     9 HIDE_SEQUENCES = ("<FocusOut>", "<ButtonPress>")
       
    10 KEYPRESS_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keypress>>"
       
    11 # We need to bind event beyond <Key> so that the function will be called
       
    12 # before the default specific IDLE function
       
    13 KEYPRESS_SEQUENCES = ("<Key>", "<Key-BackSpace>", "<Key-Return>", "<Key-Tab>",
       
    14                       "<Key-Up>", "<Key-Down>", "<Key-Home>", "<Key-End>",
       
    15                       "<Key-Prior>", "<Key-Next>")
       
    16 KEYRELEASE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keyrelease>>"
       
    17 KEYRELEASE_SEQUENCE = "<KeyRelease>"
       
    18 LISTUPDATE_SEQUENCE = "<B1-ButtonRelease>"
       
    19 WINCONFIG_SEQUENCE = "<Configure>"
       
    20 DOUBLECLICK_SEQUENCE = "<B1-Double-ButtonRelease>"
       
    21 
       
    22 class AutoCompleteWindow:
       
    23 
       
    24     def __init__(self, widget):
       
    25         # The widget (Text) on which we place the AutoCompleteWindow
       
    26         self.widget = widget
       
    27         # The widgets we create
       
    28         self.autocompletewindow = self.listbox = self.scrollbar = None
       
    29         # The default foreground and background of a selection. Saved because
       
    30         # they are changed to the regular colors of list items when the
       
    31         # completion start is not a prefix of the selected completion
       
    32         self.origselforeground = self.origselbackground = None
       
    33         # The list of completions
       
    34         self.completions = None
       
    35         # A list with more completions, or None
       
    36         self.morecompletions = None
       
    37         # The completion mode. Either AutoComplete.COMPLETE_ATTRIBUTES or
       
    38         # AutoComplete.COMPLETE_FILES
       
    39         self.mode = None
       
    40         # The current completion start, on the text box (a string)
       
    41         self.start = None
       
    42         # The index of the start of the completion
       
    43         self.startindex = None
       
    44         # The last typed start, used so that when the selection changes,
       
    45         # the new start will be as close as possible to the last typed one.
       
    46         self.lasttypedstart = None
       
    47         # Do we have an indication that the user wants the completion window
       
    48         # (for example, he clicked the list)
       
    49         self.userwantswindow = None
       
    50         # event ids
       
    51         self.hideid = self.keypressid = self.listupdateid = self.winconfigid \
       
    52         = self.keyreleaseid = self.doubleclickid                         = None
       
    53         # Flag set if last keypress was a tab
       
    54         self.lastkey_was_tab = False
       
    55 
       
    56     def _change_start(self, newstart):
       
    57         min_len = min(len(self.start), len(newstart))
       
    58         i = 0
       
    59         while i < min_len and self.start[i] == newstart[i]:
       
    60             i += 1
       
    61         if i < len(self.start):
       
    62             self.widget.delete("%s+%dc" % (self.startindex, i),
       
    63                                "%s+%dc" % (self.startindex, len(self.start)))
       
    64         if i < len(newstart):
       
    65             self.widget.insert("%s+%dc" % (self.startindex, i),
       
    66                                newstart[i:])
       
    67         self.start = newstart
       
    68 
       
    69     def _binary_search(self, s):
       
    70         """Find the first index in self.completions where completions[i] is
       
    71         greater or equal to s, or the last index if there is no such
       
    72         one."""
       
    73         i = 0; j = len(self.completions)
       
    74         while j > i:
       
    75             m = (i + j) // 2
       
    76             if self.completions[m] >= s:
       
    77                 j = m
       
    78             else:
       
    79                 i = m + 1
       
    80         return min(i, len(self.completions)-1)
       
    81 
       
    82     def _complete_string(self, s):
       
    83         """Assuming that s is the prefix of a string in self.completions,
       
    84         return the longest string which is a prefix of all the strings which
       
    85         s is a prefix of them. If s is not a prefix of a string, return s."""
       
    86         first = self._binary_search(s)
       
    87         if self.completions[first][:len(s)] != s:
       
    88             # There is not even one completion which s is a prefix of.
       
    89             return s
       
    90         # Find the end of the range of completions where s is a prefix of.
       
    91         i = first + 1
       
    92         j = len(self.completions)
       
    93         while j > i:
       
    94             m = (i + j) // 2
       
    95             if self.completions[m][:len(s)] != s:
       
    96                 j = m
       
    97             else:
       
    98                 i = m + 1
       
    99         last = i-1
       
   100 
       
   101         if first == last: # only one possible completion
       
   102             return self.completions[first]
       
   103 
       
   104         # We should return the maximum prefix of first and last
       
   105         first_comp = self.completions[first]
       
   106         last_comp = self.completions[last]
       
   107         min_len = min(len(first_comp), len(last_comp))
       
   108         i = len(s)
       
   109         while i < min_len and first_comp[i] == last_comp[i]:
       
   110             i += 1
       
   111         return first_comp[:i]
       
   112 
       
   113     def _selection_changed(self):
       
   114         """Should be called when the selection of the Listbox has changed.
       
   115         Updates the Listbox display and calls _change_start."""
       
   116         cursel = int(self.listbox.curselection()[0])
       
   117 
       
   118         self.listbox.see(cursel)
       
   119 
       
   120         lts = self.lasttypedstart
       
   121         selstart = self.completions[cursel]
       
   122         if self._binary_search(lts) == cursel:
       
   123             newstart = lts
       
   124         else:
       
   125             min_len = min(len(lts), len(selstart))
       
   126             i = 0
       
   127             while i < min_len and lts[i] == selstart[i]:
       
   128                 i += 1
       
   129             newstart = selstart[:i]
       
   130         self._change_start(newstart)
       
   131 
       
   132         if self.completions[cursel][:len(self.start)] == self.start:
       
   133             # start is a prefix of the selected completion
       
   134             self.listbox.configure(selectbackground=self.origselbackground,
       
   135                                    selectforeground=self.origselforeground)
       
   136         else:
       
   137             self.listbox.configure(selectbackground=self.listbox.cget("bg"),
       
   138                                    selectforeground=self.listbox.cget("fg"))
       
   139             # If there are more completions, show them, and call me again.
       
   140             if self.morecompletions:
       
   141                 self.completions = self.morecompletions
       
   142                 self.morecompletions = None
       
   143                 self.listbox.delete(0, END)
       
   144                 for item in self.completions:
       
   145                     self.listbox.insert(END, item)
       
   146                 self.listbox.select_set(self._binary_search(self.start))
       
   147                 self._selection_changed()
       
   148 
       
   149     def show_window(self, comp_lists, index, complete, mode, userWantsWin):
       
   150         """Show the autocomplete list, bind events.
       
   151         If complete is True, complete the text, and if there is exactly one
       
   152         matching completion, don't open a list."""
       
   153         # Handle the start we already have
       
   154         self.completions, self.morecompletions = comp_lists
       
   155         self.mode = mode
       
   156         self.startindex = self.widget.index(index)
       
   157         self.start = self.widget.get(self.startindex, "insert")
       
   158         if complete:
       
   159             completed = self._complete_string(self.start)
       
   160             self._change_start(completed)
       
   161             i = self._binary_search(completed)
       
   162             if self.completions[i] == completed and \
       
   163                (i == len(self.completions)-1 or
       
   164                 self.completions[i+1][:len(completed)] != completed):
       
   165                 # There is exactly one matching completion
       
   166                 return
       
   167         self.userwantswindow = userWantsWin
       
   168         self.lasttypedstart = self.start
       
   169 
       
   170         # Put widgets in place
       
   171         self.autocompletewindow = acw = Toplevel(self.widget)
       
   172         # Put it in a position so that it is not seen.
       
   173         acw.wm_geometry("+10000+10000")
       
   174         # Make it float
       
   175         acw.wm_overrideredirect(1)
       
   176         try:
       
   177             # This command is only needed and available on Tk >= 8.4.0 for OSX
       
   178             # Without it, call tips intrude on the typing process by grabbing
       
   179             # the focus.
       
   180             acw.tk.call("::tk::unsupported::MacWindowStyle", "style", acw._w,
       
   181                         "help", "noActivates")
       
   182         except TclError:
       
   183             pass
       
   184         self.scrollbar = scrollbar = Scrollbar(acw, orient=VERTICAL)
       
   185         self.listbox = listbox = Listbox(acw, yscrollcommand=scrollbar.set,
       
   186                                          exportselection=False, bg="white")
       
   187         for item in self.completions:
       
   188             listbox.insert(END, item)
       
   189         self.origselforeground = listbox.cget("selectforeground")
       
   190         self.origselbackground = listbox.cget("selectbackground")
       
   191         scrollbar.config(command=listbox.yview)
       
   192         scrollbar.pack(side=RIGHT, fill=Y)
       
   193         listbox.pack(side=LEFT, fill=BOTH, expand=True)
       
   194 
       
   195         # Initialize the listbox selection
       
   196         self.listbox.select_set(self._binary_search(self.start))
       
   197         self._selection_changed()
       
   198 
       
   199         # bind events
       
   200         self.hideid = self.widget.bind(HIDE_VIRTUAL_EVENT_NAME,
       
   201                                        self.hide_event)
       
   202         for seq in HIDE_SEQUENCES:
       
   203             self.widget.event_add(HIDE_VIRTUAL_EVENT_NAME, seq)
       
   204         self.keypressid = self.widget.bind(KEYPRESS_VIRTUAL_EVENT_NAME,
       
   205                                            self.keypress_event)
       
   206         for seq in KEYPRESS_SEQUENCES:
       
   207             self.widget.event_add(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
       
   208         self.keyreleaseid = self.widget.bind(KEYRELEASE_VIRTUAL_EVENT_NAME,
       
   209                                              self.keyrelease_event)
       
   210         self.widget.event_add(KEYRELEASE_VIRTUAL_EVENT_NAME,KEYRELEASE_SEQUENCE)
       
   211         self.listupdateid = listbox.bind(LISTUPDATE_SEQUENCE,
       
   212                                          self.listselect_event)
       
   213         self.winconfigid = acw.bind(WINCONFIG_SEQUENCE, self.winconfig_event)
       
   214         self.doubleclickid = listbox.bind(DOUBLECLICK_SEQUENCE,
       
   215                                           self.doubleclick_event)
       
   216 
       
   217     def winconfig_event(self, event):
       
   218         if not self.is_active():
       
   219             return
       
   220         # Position the completion list window
       
   221         text = self.widget
       
   222         text.see(self.startindex)
       
   223         x, y, cx, cy = text.bbox(self.startindex)
       
   224         acw = self.autocompletewindow
       
   225         acw_width, acw_height = acw.winfo_width(), acw.winfo_height()
       
   226         text_width, text_height = text.winfo_width(), text.winfo_height()
       
   227         new_x = text.winfo_rootx() + min(x, max(0, text_width - acw_width))
       
   228         new_y = text.winfo_rooty() + y
       
   229         if (text_height - (y + cy) >= acw_height # enough height below
       
   230             or y < acw_height): # not enough height above
       
   231             # place acw below current line
       
   232             new_y += cy
       
   233         else:
       
   234             # place acw above current line
       
   235             new_y -= acw_height
       
   236         acw.wm_geometry("+%d+%d" % (new_x, new_y))
       
   237 
       
   238     def hide_event(self, event):
       
   239         if not self.is_active():
       
   240             return
       
   241         self.hide_window()
       
   242 
       
   243     def listselect_event(self, event):
       
   244         if not self.is_active():
       
   245             return
       
   246         self.userwantswindow = True
       
   247         cursel = int(self.listbox.curselection()[0])
       
   248         self._change_start(self.completions[cursel])
       
   249 
       
   250     def doubleclick_event(self, event):
       
   251         # Put the selected completion in the text, and close the list
       
   252         cursel = int(self.listbox.curselection()[0])
       
   253         self._change_start(self.completions[cursel])
       
   254         self.hide_window()
       
   255 
       
   256     def keypress_event(self, event):
       
   257         if not self.is_active():
       
   258             return
       
   259         keysym = event.keysym
       
   260         if hasattr(event, "mc_state"):
       
   261             state = event.mc_state
       
   262         else:
       
   263             state = 0
       
   264         if keysym != "Tab":
       
   265             self.lastkey_was_tab = False
       
   266         if (len(keysym) == 1 or keysym in ("underscore", "BackSpace")
       
   267             or (self.mode==AutoComplete.COMPLETE_FILES and keysym in
       
   268                 ("period", "minus"))) \
       
   269            and not (state & ~MC_SHIFT):
       
   270             # Normal editing of text
       
   271             if len(keysym) == 1:
       
   272                 self._change_start(self.start + keysym)
       
   273             elif keysym == "underscore":
       
   274                 self._change_start(self.start + '_')
       
   275             elif keysym == "period":
       
   276                 self._change_start(self.start + '.')
       
   277             elif keysym == "minus":
       
   278                 self._change_start(self.start + '-')
       
   279             else:
       
   280                 # keysym == "BackSpace"
       
   281                 if len(self.start) == 0:
       
   282                     self.hide_window()
       
   283                     return
       
   284                 self._change_start(self.start[:-1])
       
   285             self.lasttypedstart = self.start
       
   286             self.listbox.select_clear(0, int(self.listbox.curselection()[0]))
       
   287             self.listbox.select_set(self._binary_search(self.start))
       
   288             self._selection_changed()
       
   289             return "break"
       
   290 
       
   291         elif keysym == "Return":
       
   292             self.hide_window()
       
   293             return
       
   294 
       
   295         elif (self.mode == AutoComplete.COMPLETE_ATTRIBUTES and keysym in
       
   296               ("period", "space", "parenleft", "parenright", "bracketleft",
       
   297                "bracketright")) or \
       
   298              (self.mode == AutoComplete.COMPLETE_FILES and keysym in
       
   299               ("slash", "backslash", "quotedbl", "apostrophe")) \
       
   300              and not (state & ~MC_SHIFT):
       
   301             # If start is a prefix of the selection, but is not '' when
       
   302             # completing file names, put the whole
       
   303             # selected completion. Anyway, close the list.
       
   304             cursel = int(self.listbox.curselection()[0])
       
   305             if self.completions[cursel][:len(self.start)] == self.start \
       
   306                and (self.mode==AutoComplete.COMPLETE_ATTRIBUTES or self.start):
       
   307                 self._change_start(self.completions[cursel])
       
   308             self.hide_window()
       
   309             return
       
   310 
       
   311         elif keysym in ("Home", "End", "Prior", "Next", "Up", "Down") and \
       
   312              not state:
       
   313             # Move the selection in the listbox
       
   314             self.userwantswindow = True
       
   315             cursel = int(self.listbox.curselection()[0])
       
   316             if keysym == "Home":
       
   317                 newsel = 0
       
   318             elif keysym == "End":
       
   319                 newsel = len(self.completions)-1
       
   320             elif keysym in ("Prior", "Next"):
       
   321                 jump = self.listbox.nearest(self.listbox.winfo_height()) - \
       
   322                        self.listbox.nearest(0)
       
   323                 if keysym == "Prior":
       
   324                     newsel = max(0, cursel-jump)
       
   325                 else:
       
   326                     assert keysym == "Next"
       
   327                     newsel = min(len(self.completions)-1, cursel+jump)
       
   328             elif keysym == "Up":
       
   329                 newsel = max(0, cursel-1)
       
   330             else:
       
   331                 assert keysym == "Down"
       
   332                 newsel = min(len(self.completions)-1, cursel+1)
       
   333             self.listbox.select_clear(cursel)
       
   334             self.listbox.select_set(newsel)
       
   335             self._selection_changed()
       
   336             self._change_start(self.completions[newsel])
       
   337             return "break"
       
   338 
       
   339         elif (keysym == "Tab" and not state):
       
   340             if self.lastkey_was_tab:
       
   341                 # two tabs in a row; insert current selection and close acw
       
   342                 cursel = int(self.listbox.curselection()[0])
       
   343                 self._change_start(self.completions[cursel])
       
   344                 self.hide_window()
       
   345                 return "break"
       
   346             else:
       
   347                 # first tab; let AutoComplete handle the completion
       
   348                 self.userwantswindow = True
       
   349                 self.lastkey_was_tab = True
       
   350                 return
       
   351 
       
   352         elif reduce(lambda x, y: x or y,
       
   353                     [keysym.find(s) != -1 for s in ("Shift", "Control", "Alt",
       
   354                                                     "Meta", "Command", "Option")
       
   355                      ]):
       
   356             # A modifier key, so ignore
       
   357             return
       
   358 
       
   359         else:
       
   360             # Unknown event, close the window and let it through.
       
   361             self.hide_window()
       
   362             return
       
   363 
       
   364     def keyrelease_event(self, event):
       
   365         if not self.is_active():
       
   366             return
       
   367         if self.widget.index("insert") != \
       
   368            self.widget.index("%s+%dc" % (self.startindex, len(self.start))):
       
   369             # If we didn't catch an event which moved the insert, close window
       
   370             self.hide_window()
       
   371 
       
   372     def is_active(self):
       
   373         return self.autocompletewindow is not None
       
   374 
       
   375     def complete(self):
       
   376         self._change_start(self._complete_string(self.start))
       
   377         # The selection doesn't change.
       
   378 
       
   379     def hide_window(self):
       
   380         if not self.is_active():
       
   381             return
       
   382 
       
   383         # unbind events
       
   384         for seq in HIDE_SEQUENCES:
       
   385             self.widget.event_delete(HIDE_VIRTUAL_EVENT_NAME, seq)
       
   386         self.widget.unbind(HIDE_VIRTUAL_EVENT_NAME, self.hideid)
       
   387         self.hideid = None
       
   388         for seq in KEYPRESS_SEQUENCES:
       
   389             self.widget.event_delete(KEYPRESS_VIRTUAL_EVENT_NAME, seq)
       
   390         self.widget.unbind(KEYPRESS_VIRTUAL_EVENT_NAME, self.keypressid)
       
   391         self.keypressid = None
       
   392         self.widget.event_delete(KEYRELEASE_VIRTUAL_EVENT_NAME,
       
   393                                  KEYRELEASE_SEQUENCE)
       
   394         self.widget.unbind(KEYRELEASE_VIRTUAL_EVENT_NAME, self.keyreleaseid)
       
   395         self.keyreleaseid = None
       
   396         self.listbox.unbind(LISTUPDATE_SEQUENCE, self.listupdateid)
       
   397         self.listupdateid = None
       
   398         self.autocompletewindow.unbind(WINCONFIG_SEQUENCE, self.winconfigid)
       
   399         self.winconfigid = None
       
   400 
       
   401         # destroy widgets
       
   402         self.scrollbar.destroy()
       
   403         self.scrollbar = None
       
   404         self.listbox.destroy()
       
   405         self.listbox = None
       
   406         self.autocompletewindow.destroy()
       
   407         self.autocompletewindow = None