예제 #1
0
 def _create_statusbar(self):
     self.status_bar = MultiStatusBar(self.top)
     if macosxSupport.runningAsOSXApp():
         # Insert some padding to avoid obscuring some of the statusbar
         # by the resize widget.
         self.status_bar.set_label('_padding1', '    ', side=RIGHT)
     self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
     self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
     self.status_bar.pack(side=BOTTOM, fill=X)
예제 #2
0
 def _create_statusbar(self):
     self.status_bar = MultiStatusBar(self.top)
     if macosxSupport.runningAsOSXApp():
         # Insert some padding to avoid obscuring some of the statusbar
         # by the resize widget.
         self.status_bar.set_label('_padding1', '    ', side=RIGHT)
     self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
     self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
     self.status_bar.pack(side=BOTTOM, fill=X)
예제 #3
0
class EditorWindow(object):
    from ColorDelegator import ColorDelegator # overridden by PyShell
    from UndoDelegator import UndoDelegator   # overridden by PyShell

    help_url = None
    menu_specs = [
        ("file", "_File"),
        ("edit", "_Edit"),
        ("format", "F_ormat"),
        ("run", "_Run"),
        ("options", "_Options"),
        ("windows", "_Windows"),
        ("help", "_Help"),
    ]

    if macosxSupport.runningAsOSXApp():
        del menu_specs[-3]
        menu_specs[-2] = ("windows", "_Window")

    def __init__(self, flist=None, filename=None, key=None, root=None,
       start_page=EditorPage):
        if EditorWindow.help_url is None:
            dochome =  os.path.join(sys.prefix, 'Doc', 'index.html')
            if sys.platform.count('linux'):
                # look for html docs in a couple of standard places
                pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
                if os.path.isdir('/var/www/html/python/'):  # "python2" rpm
                    dochome = '/var/www/html/python/index.html'
                else:
                    basepath = '/usr/share/doc/'  # standard location
                    dochome = os.path.join(basepath, pyver,
                                           'Doc', 'index.html')
            elif sys.platform[:3] == 'win':
                chmfile = os.path.join(sys.prefix, 'Doc',
                                       'Python%d%d.chm' % sys.version_info[:2])
                if os.path.isfile(chmfile):
                    dochome = chmfile

            elif macosxSupport.runningAsOSXApp():
                # documentation is stored inside the python framework
                dochome = os.path.join(sys.prefix,
                        'Resources/English.lproj/Documentation/index.html')

            dochome = os.path.normpath(dochome)
            if os.path.isfile(dochome):
                EditorWindow.help_url = dochome
                if sys.platform == 'darwin':
                    # Safari requires real file:-URLs
                    EditorWindow.help_url = 'file://' + EditorWindow.help_url
            else:
                EditorWindow.help_url = "http://www.python.org/doc/current"

        self.flist = flist
        root = root or flist.root
        self.root = root
        try:
            sys.ps1
        except AttributeError:
            sys.ps1 = '>>> '
        self.menubar = Menu(root)
        self.top = WindowList.ListedToplevel(root, menu=self.menubar)
        if flist:
            self.tkinter_vars = flist.vars
            # self.top.instance_dict makes flist.inversedict avalable to
            # configDialog.py so it can access all EditorWindow instaces
            self.top.instance_dict = flist.inversedict
        else:
            self.tkinter_vars = {}  # keys: Tkinter event names
                                    # values: Tkinter variable instances
            self.top.instance_dict = {}
        self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
            'recent-files.lst')

        if flist:
            flist.inversedict[self] = key
            if key:
                flist.dict[key] = self

        self.menudict = None

        # create a Notebook where the text pages for this EditorWindow will
        # reside
        self.text_notebook = TabbedPageSet(self.top)
        self.text_notebook.pack(fill=BOTH, expand=True)
        self.text_notebook.bind('<<NotebookTabChanged>>', self._update_controls)
        self.new_tab(filename=filename, load_ext=False, ptype=start_page)
        self.text = self.current_page.text # XXX
        self.top.focused_widget = self.text
        self.top.bind('<<tab-closed>>', self._post_tab_close)

        # The following "width" attribute is used by PyShell, so keep it here
        self.width = idleConf.GetOption('main', 'EditorPage', 'width')

        self.top.protocol("WM_DELETE_WINDOW", self.close)
        self.top.bind("<<close-window>>", self.close_event)

        self._create_statusbar()
        self.top.after_idle(self.set_line_and_column)

        # usetabs true  -> literal tab characters are used by indent and
        #                  dedent cmds, possibly mixed with spaces if
        #                  indentwidth is not a multiple of tabwidth,
        #                  which will cause Tabnanny to nag!
        #         false -> tab characters are converted to spaces by indent
        #                  and dedent cmds, and ditto TAB keystrokes
        # Although use-spaces=0 can be configured manually in config-main.def,
        # configuration of tabs v. spaces is not supported in the configuration
        # dialog.  IDLE promotes the preferred Python indentation: use spaces!
        usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces',
            type='bool')
        self.usetabs = not usespaces

        # tabwidth is the display width of a literal tab character.
        # CAUTION:  telling Tk to use anything other than its default
        # tab setting causes it to use an entirely different tabbing algorithm,
        # treating tab stops as fixed distances from the left margin.
        # Nobody expects this, so for now tabwidth should never be changed.
        self.tabwidth = 8    # must remain 8 until Tk is fixed.

        # indentwidth is the number of screen characters per indent level.
        # The recommended Python indentation is four spaces.
        self.indentwidth = self.tabwidth
        self.set_notabs_indentwidth()

        # If context_use_ps1 is true, parsing searches back for a ps1 line;
        # else searches for a popular (if, def, ...) Python stmt.
        self.context_use_ps1 = False

        # When searching backwards for a reliable place to begin parsing,
        # first start num_context_lines[0] lines back, then
        # num_context_lines[1] lines back if that didn't work, and so on.
        # The last value should be huge (larger than the # of lines in a
        # conceivable file).
        # Making the initial values larger slows things down more often.
        self.num_context_lines = 50, 500, 5000000

        if hasattr(self, 'ispythonsource'): # PyShell
            self.set_indentation_params(self.ispythonsource(filename))
        else:
            self.set_indentation_params(
                self.current_page.ispythonsource(filename))

        self.extensions = {}
        self._load_extensions()

        menu = self.menudict.get('windows')
        if menu:
            end = menu.index("end")
            if end is None:
                end = -1
            if end >= 0:
                menu.add_separator()
                end = end + 1
            self.wmenu_end = end
            WindowList.register_callback(self.postwindowsmenu)

        # Some abstractions so IDLE extensions are cross-IDE
        self.askyesno = tkMessageBox.askyesno
        self.askinteger = tkSimpleDialog.askinteger
        self.showerror = tkMessageBox.showerror

    @property
    def current_page(self):
        """Return the active EditorPage in EditorWindow."""
        if not self.text_notebook.pages: # no pages available
            return None
        curr_tab = self.text_notebook.select()
        if not curr_tab:
            return None

        if TTK:
            page = self.text_notebook.pages[self.text_notebook.tab(
                curr_tab)['text']].editpage
        else:
            page = self.text_notebook.pages[curr_tab].editpage
        return page

    def remove_tab_controls(self):
        """Remove tab area and most tab bindings from this window."""
        if TTK:
            self.text_notebook['style'] = 'PyShell.TNotebook'
            style = Style(self.top)
            style.layout('PyShell.TNotebook.Tab', [('null', '')])
        else:
            self.text_notebook._tab_set.grid_forget()

        # remove commands related to tab
        if 'file' in self.menudict:
            menu = self.menudict['file']
            curr_entry = None
            i = 0
            while True:
                last_entry, curr_entry = curr_entry, menu.entryconfigure(i)
                if last_entry == curr_entry:
                    # no more menu entries
                    break

                if 'label' in curr_entry and 'Tab' in curr_entry['label'][-1]:
                    if 'Close' not in ' '.join(curr_entry['label'][-1]):
                        menu.delete(i)
                i += 1

        self.current_page.text.unbind('<<new-tab>>')
        # close-tab is still available!

    def short_title(self):
        # overriden by PyShell
        return self.current_page.short_title()

    def next_tab(self, event):
        """Show next tab if not in the last tab already."""
        index = self.text_notebook.index(self.text_notebook.select())
        if index == len(self.text_notebook.tabs()) - 1:
            return
        self.text_notebook.select(index + 1)

    def prev_tab(self, event):
        """Show the previous tab if not in the first tab already."""
        index = self.text_notebook.index(self.text_notebook.select())
        if index == 0:
            return
        self.text_notebook.select(index - 1)

    def new_tab(self, event=None, filename=None, load_ext=True, ptype=None):
        """Create a new EditorPage and insert it into the notebook."""
        page_title = "#%d" % (len(self.text_notebook.pages) + 1)
        page = self.text_notebook.add_page(page_title)

        vbar = Scrollbar(page.frame, name='vbar')
        hbar = Scrollbar(page.frame, name='hbar', orient='horizontal')
        hbar.set(0, 0)
        vbar.set(0, 0)
        ptype = ptype or EditorPage
        page.editpage = ptype(page.frame, self, title=page_title,
            name='text', padx=5, wrap='none')

        firstpage = False # don't update window's title
        if self.menudict is None:
            # This EditorWindow is being created now, perform the following
            # tasks before.
            firstpage = True # will cause window's title to be updated
            self.menudict = {}
            self._createmenubar(page.editpage.text)
            # Create the recent files submenu
            self.recent_files_menu = Menu(self.menubar)
            self.menudict['file'].insert_cascade(3, label='Recent Files',
                underline=0, menu=self.recent_files_menu)
            self.update_recent_files_list()

        # pack widgets
        text = page.editpage.text
        vbar['command'] = text.yview
        hbar['command'] = text.xview
        text['yscrollcommand'] = vbar.set
        text['xscrollcommand'] = hbar.set
        vbar.pack(side=RIGHT, fill=Y)
        hbar.pack(side=BOTTOM, fill=X)
        fontWeight = 'normal'
        if idleConf.GetOption('main', 'EditorPage', 'font-bold', type='bool'):
            fontWeight = 'bold'
        text.config(font=(idleConf.GetOption('main', 'EditorPage', 'font'),
            idleConf.GetOption('main', 'EditorPage', 'font-size'),
            fontWeight))
        text.pack(side=TOP, fill=BOTH, expand=1)
        text.focus_set()

        self.apply_bindings(tab=page)
        if load_ext:
            self._load_extensions()

        # select the just created page
        self.text_notebook.select(len(self.text_notebook.pages) - 1)

        page.editpage.post_init(filename=filename,
            update_window_title=firstpage)

        self.top.event_generate('<<tab-created>>')
        return "break"

    def new_callback(self, event, page):
        dirname, basename = page.io.defaultfilename()
        self.flist.new(dirname)
        return "break"

    def set_line_and_column(self, event=None):
        # Used by PyShell too
        curr_page = self.current_page
        if not curr_page:
            return

        line, column = curr_page.text.index(INSERT).split('.')
        self.status_bar.set_label('column', 'Col: %s' % column)
        self.status_bar.set_label('line', 'Ln: %s' % line)

    def postwindowsmenu(self):
        # Only called when Windows menu exists
        menu = self.menudict['windows']
        end = menu.index("end")
        if end is None:
            end = -1
        if end > self.wmenu_end:
            menu.delete(self.wmenu_end+1, end)
        WindowList.add_windows_to_menu(menu)

    def newline_and_indent_event(self, event):
        """Call newline_and_indent_event on current EditorPage."""
        self.current_page.newline_and_indent_event(event)

    def get_selection_indices(self):
        """Call get_selection_indices on current EditorPage."""
        return self.current_page.get_selection_indices()

    def build_char_in_string_func(self, startindex):
        """Call build_char_in_string_func on current EditorPage."""
        return self.current_page.build_char_in_string_func(startindex)

    def gotoline(self, lineno):
        page = self.current_page
        text = page.text

        if lineno is not None and lineno > 0:
            text.mark_set("insert", "%d.0" % lineno)
            text.tag_remove("sel", "1.0", "end")
            text.tag_add("sel", "insert", "insert +1l")
            page.center()

    def close_hook(self):
        if self.flist:
            self.flist.unregister_maybe_terminate(self)
            self.flist = None

    def set_close_hook(self, close_hook):
        self.close_hook = close_hook

    def set_theme(self, ttkstyle):
        # called from configDialog.py
        ttkstyle.theme_use(idleConf.GetOption('main', 'Theme', 'displaytheme'))

    def ResetColorizer(self):
        "Update the colour theme"
        # Called from self.filename_change_hook and from configDialog.py
        for page in self.text_notebook.pages.itervalues():
            page.editpage.reset_colorizer()

    def ResetFont(self):
        "Update the text widgets' font if it is changed"
        # Called from configDialog.py
        fontWeight = 'normal'
        if idleConf.GetOption('main', 'EditorPage', 'font-bold', type='bool'):
            fontWeight = 'bold'

        for page in self.text_notebook.pages.itervalues():
            text = page.editpage.text
            text.config(font=(idleConf.GetOption('main', 'EditorPage', 'font'),
                idleConf.GetOption('main', 'EditorPage', 'font-size'),
                fontWeight))

    def RemoveKeybindings(self):
        "Remove the keybindings before they are changed."
        # Called from configDialog.py
        Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()

        for page in self.text_notebook.pages.itervalues():
            text = page.editpage.text
            for event, keylist in keydefs.items():
                text.event_delete(event, *keylist)

        for extensionName in self._get_standard_extension_names():
            xkeydefs = idleConf.GetExtensionBindings(extensionName)
            if xkeydefs:
                for page in self.text_notebook.pages.itervalues():
                    text = page.editpage.text
                    for event, keylist in xkeydefs.items():
                        text.event_delete(event, *keylist)

    def ApplyKeybindings(self):
        "Update the keybindings after they are changed"
        # Called from configDialog.py
        Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
        self.apply_bindings()
        for extensionName in self._get_standard_extension_names():
            xkeydefs = idleConf.GetExtensionBindings(extensionName)
            if xkeydefs:
                self.apply_bindings(xkeydefs)
        #update menu accelerators
        menuEventDict = {}
        for menu in Bindings.menudefs:
            menuEventDict[menu[0]] = {}
            for item in menu[1]:
                if item:
                    menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
        for menubarItem in self.menudict.keys():
            menu = self.menudict[menubarItem]
            end = menu.index(END) + 1
            for index in range(0, end):
                if menu.type(index) == 'command':
                    accel = menu.entrycget(index, 'accelerator')
                    if accel:
                        itemName = menu.entrycget(index, 'label')
                        event = ''
                        if menuEventDict.has_key(menubarItem):
                            if menuEventDict[menubarItem].has_key(itemName):
                                event = menuEventDict[menubarItem][itemName]
                        if event:
                            accel = get_accelerator(keydefs, event)
                            menu.entryconfig(index, accelerator=accel)

    def reset_help_menu_entries(self):
        "Update the additional help entries on the Help menu"
        help_list = idleConf.GetAllExtraHelpSourcesList()
        helpmenu = self.menudict['help']
        # first delete the extra help entries, if any
        helpmenu_length = helpmenu.index(END)
        if helpmenu_length > self.base_helpmenu_length:
            helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
        # then rebuild them
        if help_list:
            helpmenu.add_separator()
            for entry in help_list:
                cmd = self.__extra_help_callback(entry[1])
                helpmenu.add_command(label=entry[0], command=cmd)
        # and update the menu dictionary
        self.menudict['help'] = helpmenu

    def set_notabs_indentwidth(self):
        "Update the indentwidth if changed and not using tabs in this window"
        # Called from configDialog.py
        if not self.usetabs:
            self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
                                                  type='int')

    def update_recent_files_list(self, new_file=None):
        "Load and update the recent files list and menus"
        # IOBinding calls this
        rf_list = []
        if os.path.exists(self.recent_files_path):
            rf_list_file = open(self.recent_files_path,'r')
            try:
                rf_list = rf_list_file.readlines()
            finally:
                rf_list_file.close()
        if new_file:
            new_file = os.path.abspath(new_file) + '\n'
            if new_file in rf_list:
                rf_list.remove(new_file)  # move to top
            rf_list.insert(0, new_file)
        # clean and save the recent files list
        bad_paths = []
        for path in rf_list:
            if '\0' in path or not os.path.exists(path[0:-1]):
                bad_paths.append(path)
        rf_list = [path for path in rf_list if path not in bad_paths]
        ulchars = "1234567890ABCDEFGHIJK"
        rf_list = rf_list[0:len(ulchars)]
        rf_file = open(self.recent_files_path, 'w')
        try:
            rf_file.writelines(rf_list)
        finally:
            rf_file.close()
        # for each edit window instance, construct the recent files menu
        for instance in self.top.instance_dict.keys():
            menu = instance.recent_files_menu
            menu.delete(1, END)  # clear, and rebuild:
            for i, file in enumerate(rf_list):
                file_name = file[0:-1]  # zap \n
                # make unicode string to display non-ASCII chars correctly
                ufile_name = filename_to_unicode(file_name)
                callback = instance.__recent_file_callback(file_name)
                menu.add_command(label=ulchars[i] + " " + ufile_name,
                                 command=callback,
                                 underline=0)

    def get_geometry(self):
        "Return (width, height, x, y)"
        geom = self.top.wm_geometry()
        m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
        tuple = (map(int, m.groups()))
        return tuple

    def close_event(self, event):
        self.close()

    def close(self):
        to_check = self.text_notebook.pages.copy()

        while to_check:
            curr_tab = self.text_notebook.select()
            if TTK:
                page_name = self.text_notebook.tab(curr_tab)['text']
            else:
                page_name = curr_tab
            page = to_check.pop(page_name)
            editpage = page.editpage
            reply = editpage.close_tab()
            if reply == "cancel":
                break

    def _close(self):
        WindowList.unregister_callback(self.postwindowsmenu)
        self._unload_extensions()
        self.tkinter_vars = None

        for page in self.text_notebook.pages.itervalues():
            page.editpage.close()

        self.top.destroy()
        if self.close_hook:
            # unless override: unregister from flist, terminate if last window
            self.close_hook()

    def apply_bindings(self, keydefs=None, tab=None):
        if keydefs is None:
            keydefs = Bindings.default_keydefs

        if tab:
            iter_over = [tab]
        else:
            iter_over = self.text_notebook.pages.itervalues()

        for page in iter_over:
            text = page.editpage.text
            text.keydefs = keydefs
            for event, keylist in keydefs.items():
                if keylist:
                    text.event_add(event, *keylist)

    def getvar(self, name):
        var = self.get_var_obj(name)
        if var:
            value = var.get()
            return value
        else:
            raise NameError, name

    def setvar(self, name, value, vartype=None):
        var = self.get_var_obj(name, vartype)
        if var:
            var.set(value)
        else:
            raise NameError, name

    def get_var_obj(self, name, vartype=None, text=None):
        var = self.tkinter_vars.get(name)
        if not var and vartype:
            # create a Tkinter variable object with self.text as master:
            self.tkinter_vars[name] = var = vartype(text or self.text)
        return var

    # Tk implementations of "virtual text methods" -- each platform
    # reusing IDLE's support code needs to define these for its GUI's
    # flavor of widget.

    # Return the text widget's current view of what a tab stop means
    # (equivalent width in spaces).
    def get_tabwidth(self): # XXX depends on self.text
        current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
        return int(current)

    # Set the text widget's current view of what a tab stop means.
    def set_tabwidth(self, newtabwidth): # XXX depends on self.text
        text = self.text
        if self.get_tabwidth() != newtabwidth:
            pixels = text.tk.call("font", "measure", text["font"],
                                  "-displayof", text.master,
                                  "n" * newtabwidth)
            text.configure(tabs=pixels)

    # If ispythonsource and guess are true, guess a good value for
    # indentwidth based on file content (if possible), and if
    # indentwidth != tabwidth set usetabs false.
    # In any case, adjust the Text widget's view of what a tab
    # character means.
    def set_indentation_params(self, ispythonsource, guess=True):
        if guess and ispythonsource:
            i = self.guess_indent()
            if 2 <= i <= 8:
                self.indentwidth = i
            if self.indentwidth != self.tabwidth:
                self.usetabs = False
        self.set_tabwidth(self.tabwidth)

    # Guess indentwidth from text content.
    # Return guessed indentwidth.  This should not be believed unless
    # it's in a reasonable range (e.g., it will be 0 if no indented
    # blocks are found).
    def guess_indent(self): # XXX depends on self.text
        opener, indented = IndentSearcher(self.text, self.tabwidth).run()
        if opener and indented:
            raw, indentsmall = classifyws(opener, self.tabwidth)
            raw, indentlarge = classifyws(indented, self.tabwidth)
        else:
            indentsmall = indentlarge = 0
        return indentlarge - indentsmall

    # Private methods/attributes

    # extensions won't have more than one instance per window
    _unique_extensions = ['CodeContext', 'ScriptBinding', 'FormatParagraph']

    def _unload_extensions(self):
        for ins in self.extensions.values():
            if hasattr(ins, "close"):
                ins.close()
        self.extensions = {}

    def _load_extension(self, name, tab):
        ext_loaded = self.extensions.get(name)

        try:
            mod = __import__(name, globals(), locals(), [])
        except ImportError:
            print "\nFailed to import extension: ", name
            return

        keydefs = idleConf.GetExtensionBindings(name)

        if name not in self._unique_extensions or not ext_loaded:
            # create a new instance
            cls = getattr(mod, name)
            ins = cls(tab.editpage)
            self.extensions.setdefault(name, []).append(ins)
            if not ext_loaded:
                # create new items in menu only if this is the first time this
                # extension is being loaded in this window
                if hasattr(cls, "menudefs"):
                    self._fill_menus(cls.menudefs, keydefs)
        elif name in self._unique_extensions and ext_loaded:
            # get an existing instance
            ins = self.extensions[name][0]

        if keydefs:
            self.apply_bindings(keydefs, tab)
            for vevent in keydefs.keys():
                methodname = vevent.replace("-", "_")
                while methodname[:1] == '<':
                    methodname = methodname[1:]
                while methodname[-1:] == '>':
                    methodname = methodname[:-1]
                methodname = methodname + "_event"
                if hasattr(ins, methodname):
                    tab.editpage.text.bind(vevent, getattr(ins, methodname))

    def _load_extensions(self):
        self._load_standard_extensions(self.text_notebook.last_page())

    def _load_standard_extensions(self, tab):
        for name in self._get_standard_extension_names():
            try:
                self._load_extension(name, tab)
            except:
                print "Failed to load extension", repr(name)
                traceback.print_exc()

    def _get_standard_extension_names(self):
        return idleConf.GetExtensions(editor_only=True)

    def _post_tab_close(self, event):
        if not self.current_page:
            # no tabs now, close window
            self._close()
            return

    def _update_controls(self, event):
        curr_page = self.current_page
        if not curr_page:
            return

        self.text = curr_page.text
        curr_page.saved_change_hook(True, False) # update window title
        curr_page.text.focus_set()
        self.set_line_and_column()

        # update references in extensions that are loaded only once
        for ext in self._unique_extensions:
            if ext not in self.extensions:
                continue
            ext = self.extensions[ext][0]
            ext.editpage = curr_page

    def _create_statusbar(self):
        self.status_bar = MultiStatusBar(self.top)
        if macosxSupport.runningAsOSXApp():
            # Insert some padding to avoid obscuring some of the statusbar
            # by the resize widget.
            self.status_bar.set_label('_padding1', '    ', side=RIGHT)
        self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
        self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
        self.status_bar.pack(side=BOTTOM, fill=X)

    def _createmenubar(self, text):
        mbar = self.menubar
        menudict = self.menudict
        for name, label in self.menu_specs:
            underline, label = prepstr(label)
            menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
            mbar.add_cascade(label=label, menu=menu, underline=underline)

        if sys.platform == 'darwin' and '.framework' in sys.executable:
            # Insert the application menu
            menudict['application'] = menu = Menu(mbar, name='apple')
            mbar.add_cascade(label='IDLE', menu=menu)

        self._fill_menus(text=text)
        self.base_helpmenu_length = self.menudict['help'].index(END)
        self.reset_help_menu_entries()

    def _fill_menus(self, menudefs=None, keydefs=None, text=None):
        """Add appropriate entries to the menus and submenus

        Menus that are absent or None in self.menudict are ignored.
        """
        if menudefs is None:
            menudefs = Bindings.menudefs
        if keydefs is None:
            keydefs = Bindings.default_keydefs
        menudict = self.menudict
        for mname, entrylist in menudefs:
            menu = menudict.get(mname)
            if not menu:
                continue
            for entry in entrylist:
                if not entry:
                    menu.add_separator()
                else:
                    label, eventname = entry
                    checkbutton = (label[:1] == '!')
                    if checkbutton:
                        label = label[1:]
                    underline, label = prepstr(label)
                    accelerator = get_accelerator(keydefs, eventname)
                    def command(eventname=eventname):
                        self.text.event_generate(eventname)
                    if checkbutton:
                        var = self.get_var_obj(eventname, BooleanVar, text)
                        menu.add_checkbutton(label=label, underline=underline,
                            command=command, accelerator=accelerator,
                            variable=var)
                    else:
                        menu.add_command(label=label, underline=underline,
                            command=command, accelerator=accelerator)

    def __recent_file_callback(self, file_name):
        def open_recent_file(fn_closure=file_name):
            self.current_page.io.open(editFile=fn_closure)
        return open_recent_file

    def __extra_help_callback(self, helpfile):
        "Create a callback with the helpfile value frozen at definition time"
        def display_extra_help(helpfile=helpfile):
            if not helpfile.startswith(('www', 'http')):
                url = os.path.normpath(helpfile)
            if sys.platform[:3] == 'win':
                os.startfile(helpfile)
            else:
                webbrowser.open(helpfile)
        return display_extra_help
예제 #4
0
class EditorWindow(object):
    from ColorDelegator import ColorDelegator  # overridden by PyShell
    from UndoDelegator import UndoDelegator  # overridden by PyShell

    help_url = None
    menu_specs = [
        ("file", "_File"),
        ("edit", "_Edit"),
        ("format", "F_ormat"),
        ("run", "_Run"),
        ("options", "_Options"),
        ("windows", "_Windows"),
        ("help", "_Help"),
    ]

    if macosxSupport.runningAsOSXApp():
        del menu_specs[-3]
        menu_specs[-2] = ("windows", "_Window")

    def __init__(self,
                 flist=None,
                 filename=None,
                 key=None,
                 root=None,
                 start_page=EditorPage):
        if EditorWindow.help_url is None:
            dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
            if sys.platform.count('linux'):
                # look for html docs in a couple of standard places
                pyver = 'python-docs-' + '%s.%s.%s' % sys.version_info[:3]
                if os.path.isdir('/var/www/html/python/'):  # "python2" rpm
                    dochome = '/var/www/html/python/index.html'
                else:
                    basepath = '/usr/share/doc/'  # standard location
                    dochome = os.path.join(basepath, pyver, 'Doc',
                                           'index.html')
            elif sys.platform[:3] == 'win':
                chmfile = os.path.join(sys.prefix, 'Doc',
                                       'Python%d%d.chm' % sys.version_info[:2])
                if os.path.isfile(chmfile):
                    dochome = chmfile

            elif macosxSupport.runningAsOSXApp():
                # documentation is stored inside the python framework
                dochome = os.path.join(
                    sys.prefix,
                    'Resources/English.lproj/Documentation/index.html')

            dochome = os.path.normpath(dochome)
            if os.path.isfile(dochome):
                EditorWindow.help_url = dochome
                if sys.platform == 'darwin':
                    # Safari requires real file:-URLs
                    EditorWindow.help_url = 'file://' + EditorWindow.help_url
            else:
                EditorWindow.help_url = "http://www.python.org/doc/current"

        self.flist = flist
        root = root or flist.root
        self.root = root
        try:
            sys.ps1
        except AttributeError:
            sys.ps1 = '>>> '
        self.menubar = Menu(root)
        self.top = WindowList.ListedToplevel(root, menu=self.menubar)
        if flist:
            self.tkinter_vars = flist.vars
            # self.top.instance_dict makes flist.inversedict avalable to
            # configDialog.py so it can access all EditorWindow instaces
            self.top.instance_dict = flist.inversedict
        else:
            self.tkinter_vars = {}  # keys: Tkinter event names
            # values: Tkinter variable instances
            self.top.instance_dict = {}
        self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
                                              'recent-files.lst')

        if flist:
            flist.inversedict[self] = key
            if key:
                flist.dict[key] = self

        self.menudict = None

        # create a Notebook where the text pages for this EditorWindow will
        # reside
        self.text_notebook = TabbedPageSet(self.top)
        self.text_notebook.pack(fill=BOTH, expand=True)
        self.text_notebook.bind('<<NotebookTabChanged>>',
                                self._update_controls)
        self.new_tab(filename=filename, load_ext=False, ptype=start_page)
        self.text = self.current_page.text  # XXX
        self.top.focused_widget = self.text
        self.top.bind('<<tab-closed>>', self._post_tab_close)

        # The following "width" attribute is used by PyShell, so keep it here
        self.width = idleConf.GetOption('main', 'EditorPage', 'width')

        self.top.protocol("WM_DELETE_WINDOW", self.close)
        self.top.bind("<<close-window>>", self.close_event)

        self._create_statusbar()
        self.top.after_idle(self.set_line_and_column)

        # usetabs true  -> literal tab characters are used by indent and
        #                  dedent cmds, possibly mixed with spaces if
        #                  indentwidth is not a multiple of tabwidth,
        #                  which will cause Tabnanny to nag!
        #         false -> tab characters are converted to spaces by indent
        #                  and dedent cmds, and ditto TAB keystrokes
        # Although use-spaces=0 can be configured manually in config-main.def,
        # configuration of tabs v. spaces is not supported in the configuration
        # dialog.  IDLE promotes the preferred Python indentation: use spaces!
        usespaces = idleConf.GetOption('main',
                                       'Indent',
                                       'use-spaces',
                                       type='bool')
        self.usetabs = not usespaces

        # tabwidth is the display width of a literal tab character.
        # CAUTION:  telling Tk to use anything other than its default
        # tab setting causes it to use an entirely different tabbing algorithm,
        # treating tab stops as fixed distances from the left margin.
        # Nobody expects this, so for now tabwidth should never be changed.
        self.tabwidth = 8  # must remain 8 until Tk is fixed.

        # indentwidth is the number of screen characters per indent level.
        # The recommended Python indentation is four spaces.
        self.indentwidth = self.tabwidth
        self.set_notabs_indentwidth()

        # If context_use_ps1 is true, parsing searches back for a ps1 line;
        # else searches for a popular (if, def, ...) Python stmt.
        self.context_use_ps1 = False

        # When searching backwards for a reliable place to begin parsing,
        # first start num_context_lines[0] lines back, then
        # num_context_lines[1] lines back if that didn't work, and so on.
        # The last value should be huge (larger than the # of lines in a
        # conceivable file).
        # Making the initial values larger slows things down more often.
        self.num_context_lines = 50, 500, 5000000

        if hasattr(self, 'ispythonsource'):  # PyShell
            self.set_indentation_params(self.ispythonsource(filename))
        else:
            self.set_indentation_params(
                self.current_page.ispythonsource(filename))

        self.extensions = {}
        self._load_extensions()

        menu = self.menudict.get('windows')
        if menu:
            end = menu.index("end")
            if end is None:
                end = -1
            if end >= 0:
                menu.add_separator()
                end = end + 1
            self.wmenu_end = end
            WindowList.register_callback(self.postwindowsmenu)

        # Some abstractions so IDLE extensions are cross-IDE
        self.askyesno = tkMessageBox.askyesno
        self.askinteger = tkSimpleDialog.askinteger
        self.showerror = tkMessageBox.showerror

    @property
    def current_page(self):
        """Return the active EditorPage in EditorWindow."""
        if not self.text_notebook.pages:  # no pages available
            return None
        curr_tab = self.text_notebook.select()
        if not curr_tab:
            return None

        if TTK:
            page = self.text_notebook.pages[self.text_notebook.tab(curr_tab)
                                            ['text']].editpage
        else:
            page = self.text_notebook.pages[curr_tab].editpage
        return page

    def remove_tab_controls(self):
        """Remove tab area and most tab bindings from this window."""
        if TTK:
            self.text_notebook['style'] = 'PyShell.TNotebook'
            style = Style(self.top)
            style.layout('PyShell.TNotebook.Tab', [('null', '')])
        else:
            self.text_notebook._tab_set.grid_forget()

        # remove commands related to tab
        if 'file' in self.menudict:
            menu = self.menudict['file']
            curr_entry = None
            i = 0
            while True:
                last_entry, curr_entry = curr_entry, menu.entryconfigure(i)
                if last_entry == curr_entry:
                    # no more menu entries
                    break

                if 'label' in curr_entry and 'Tab' in curr_entry['label'][-1]:
                    if 'Close' not in ' '.join(curr_entry['label'][-1]):
                        menu.delete(i)
                i += 1

        self.current_page.text.unbind('<<new-tab>>')
        # close-tab is still available!

    def short_title(self):
        # overriden by PyShell
        return self.current_page.short_title()

    def next_tab(self, event):
        """Show next tab if not in the last tab already."""
        index = self.text_notebook.index(self.text_notebook.select())
        if index == len(self.text_notebook.tabs()) - 1:
            return
        self.text_notebook.select(index + 1)

    def prev_tab(self, event):
        """Show the previous tab if not in the first tab already."""
        index = self.text_notebook.index(self.text_notebook.select())
        if index == 0:
            return
        self.text_notebook.select(index - 1)

    def new_tab(self, event=None, filename=None, load_ext=True, ptype=None):
        """Create a new EditorPage and insert it into the notebook."""
        page_title = "#%d" % (len(self.text_notebook.pages) + 1)
        page = self.text_notebook.add_page(page_title)

        vbar = Scrollbar(page.frame, name='vbar')
        hbar = Scrollbar(page.frame, name='hbar', orient='horizontal')
        hbar.set(0, 0)
        vbar.set(0, 0)
        ptype = ptype or EditorPage
        page.editpage = ptype(page.frame,
                              self,
                              title=page_title,
                              name='text',
                              padx=5,
                              wrap='none')

        firstpage = False  # don't update window's title
        if self.menudict is None:
            # This EditorWindow is being created now, perform the following
            # tasks before.
            firstpage = True  # will cause window's title to be updated
            self.menudict = {}
            self._createmenubar(page.editpage.text)
            # Create the recent files submenu
            self.recent_files_menu = Menu(self.menubar)
            self.menudict['file'].insert_cascade(3,
                                                 label='Recent Files',
                                                 underline=0,
                                                 menu=self.recent_files_menu)
            self.update_recent_files_list()

        # pack widgets
        text = page.editpage.text
        vbar['command'] = text.yview
        hbar['command'] = text.xview
        text['yscrollcommand'] = vbar.set
        text['xscrollcommand'] = hbar.set
        vbar.pack(side=RIGHT, fill=Y)
        hbar.pack(side=BOTTOM, fill=X)
        fontWeight = 'normal'
        if idleConf.GetOption('main', 'EditorPage', 'font-bold', type='bool'):
            fontWeight = 'bold'
        text.config(
            font=(idleConf.GetOption('main', 'EditorPage', 'font'),
                  idleConf.GetOption('main', 'EditorPage', 'font-size'),
                  fontWeight))
        text.pack(side=TOP, fill=BOTH, expand=1)
        text.focus_set()

        self.apply_bindings(tab=page)
        if load_ext:
            self._load_extensions()

        # select the just created page
        self.text_notebook.select(len(self.text_notebook.pages) - 1)

        page.editpage.post_init(filename=filename,
                                update_window_title=firstpage)

        self.top.event_generate('<<tab-created>>')
        return "break"

    def new_callback(self, event, page):
        dirname, basename = page.io.defaultfilename()
        self.flist.new(dirname)
        return "break"

    def set_line_and_column(self, event=None):
        # Used by PyShell too
        curr_page = self.current_page
        if not curr_page:
            return

        line, column = curr_page.text.index(INSERT).split('.')
        self.status_bar.set_label('column', 'Col: %s' % column)
        self.status_bar.set_label('line', 'Ln: %s' % line)

    def postwindowsmenu(self):
        # Only called when Windows menu exists
        menu = self.menudict['windows']
        end = menu.index("end")
        if end is None:
            end = -1
        if end > self.wmenu_end:
            menu.delete(self.wmenu_end + 1, end)
        WindowList.add_windows_to_menu(menu)

    def newline_and_indent_event(self, event):
        """Call newline_and_indent_event on current EditorPage."""
        self.current_page.newline_and_indent_event(event)

    def get_selection_indices(self):
        """Call get_selection_indices on current EditorPage."""
        return self.current_page.get_selection_indices()

    def build_char_in_string_func(self, startindex):
        """Call build_char_in_string_func on current EditorPage."""
        return self.current_page.build_char_in_string_func(startindex)

    def gotoline(self, lineno):
        page = self.current_page
        text = page.text

        if lineno is not None and lineno > 0:
            text.mark_set("insert", "%d.0" % lineno)
            text.tag_remove("sel", "1.0", "end")
            text.tag_add("sel", "insert", "insert +1l")
            page.center()

    def close_hook(self):
        if self.flist:
            self.flist.unregister_maybe_terminate(self)
            self.flist = None

    def set_close_hook(self, close_hook):
        self.close_hook = close_hook

    def set_theme(self, ttkstyle):
        # called from configDialog.py
        ttkstyle.theme_use(idleConf.GetOption('main', 'Theme', 'displaytheme'))

    def ResetColorizer(self):
        "Update the colour theme"
        # Called from self.filename_change_hook and from configDialog.py
        for page in self.text_notebook.pages.itervalues():
            page.editpage.reset_colorizer()

    def ResetFont(self):
        "Update the text widgets' font if it is changed"
        # Called from configDialog.py
        fontWeight = 'normal'
        if idleConf.GetOption('main', 'EditorPage', 'font-bold', type='bool'):
            fontWeight = 'bold'

        for page in self.text_notebook.pages.itervalues():
            text = page.editpage.text
            text.config(
                font=(idleConf.GetOption('main', 'EditorPage', 'font'),
                      idleConf.GetOption('main', 'EditorPage', 'font-size'),
                      fontWeight))

    def RemoveKeybindings(self):
        "Remove the keybindings before they are changed."
        # Called from configDialog.py
        Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()

        for page in self.text_notebook.pages.itervalues():
            text = page.editpage.text
            for event, keylist in keydefs.items():
                text.event_delete(event, *keylist)

        for extensionName in self._get_standard_extension_names():
            xkeydefs = idleConf.GetExtensionBindings(extensionName)
            if xkeydefs:
                for page in self.text_notebook.pages.itervalues():
                    text = page.editpage.text
                    for event, keylist in xkeydefs.items():
                        text.event_delete(event, *keylist)

    def ApplyKeybindings(self):
        "Update the keybindings after they are changed"
        # Called from configDialog.py
        Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
        self.apply_bindings()
        for extensionName in self._get_standard_extension_names():
            xkeydefs = idleConf.GetExtensionBindings(extensionName)
            if xkeydefs:
                self.apply_bindings(xkeydefs)
        #update menu accelerators
        menuEventDict = {}
        for menu in Bindings.menudefs:
            menuEventDict[menu[0]] = {}
            for item in menu[1]:
                if item:
                    menuEventDict[menu[0]][prepstr(item[0])[1]] = item[1]
        for menubarItem in self.menudict.keys():
            menu = self.menudict[menubarItem]
            end = menu.index(END) + 1
            for index in range(0, end):
                if menu.type(index) == 'command':
                    accel = menu.entrycget(index, 'accelerator')
                    if accel:
                        itemName = menu.entrycget(index, 'label')
                        event = ''
                        if menuEventDict.has_key(menubarItem):
                            if menuEventDict[menubarItem].has_key(itemName):
                                event = menuEventDict[menubarItem][itemName]
                        if event:
                            accel = get_accelerator(keydefs, event)
                            menu.entryconfig(index, accelerator=accel)

    def reset_help_menu_entries(self):
        "Update the additional help entries on the Help menu"
        help_list = idleConf.GetAllExtraHelpSourcesList()
        helpmenu = self.menudict['help']
        # first delete the extra help entries, if any
        helpmenu_length = helpmenu.index(END)
        if helpmenu_length > self.base_helpmenu_length:
            helpmenu.delete((self.base_helpmenu_length + 1), helpmenu_length)
        # then rebuild them
        if help_list:
            helpmenu.add_separator()
            for entry in help_list:
                cmd = self.__extra_help_callback(entry[1])
                helpmenu.add_command(label=entry[0], command=cmd)
        # and update the menu dictionary
        self.menudict['help'] = helpmenu

    def set_notabs_indentwidth(self):
        "Update the indentwidth if changed and not using tabs in this window"
        # Called from configDialog.py
        if not self.usetabs:
            self.indentwidth = idleConf.GetOption('main',
                                                  'Indent',
                                                  'num-spaces',
                                                  type='int')

    def update_recent_files_list(self, new_file=None):
        "Load and update the recent files list and menus"
        # IOBinding calls this
        rf_list = []
        if os.path.exists(self.recent_files_path):
            rf_list_file = open(self.recent_files_path, 'r')
            try:
                rf_list = rf_list_file.readlines()
            finally:
                rf_list_file.close()
        if new_file:
            new_file = os.path.abspath(new_file) + '\n'
            if new_file in rf_list:
                rf_list.remove(new_file)  # move to top
            rf_list.insert(0, new_file)
        # clean and save the recent files list
        bad_paths = []
        for path in rf_list:
            if '\0' in path or not os.path.exists(path[0:-1]):
                bad_paths.append(path)
        rf_list = [path for path in rf_list if path not in bad_paths]
        ulchars = "1234567890ABCDEFGHIJK"
        rf_list = rf_list[0:len(ulchars)]
        rf_file = open(self.recent_files_path, 'w')
        try:
            rf_file.writelines(rf_list)
        finally:
            rf_file.close()
        # for each edit window instance, construct the recent files menu
        for instance in self.top.instance_dict.keys():
            menu = instance.recent_files_menu
            menu.delete(1, END)  # clear, and rebuild:
            for i, file in enumerate(rf_list):
                file_name = file[0:-1]  # zap \n
                # make unicode string to display non-ASCII chars correctly
                ufile_name = filename_to_unicode(file_name)
                callback = instance.__recent_file_callback(file_name)
                menu.add_command(label=ulchars[i] + " " + ufile_name,
                                 command=callback,
                                 underline=0)

    def get_geometry(self):
        "Return (width, height, x, y)"
        geom = self.top.wm_geometry()
        m = re.match(r"(\d+)x(\d+)\+(-?\d+)\+(-?\d+)", geom)
        tuple = (map(int, m.groups()))
        return tuple

    def close_event(self, event):
        self.close()

    def close(self):
        to_check = self.text_notebook.pages.copy()

        while to_check:
            curr_tab = self.text_notebook.select()
            if TTK:
                page_name = self.text_notebook.tab(curr_tab)['text']
            else:
                page_name = curr_tab
            page = to_check.pop(page_name)
            editpage = page.editpage
            reply = editpage.close_tab()
            if reply == "cancel":
                break

    def _close(self):
        WindowList.unregister_callback(self.postwindowsmenu)
        self._unload_extensions()
        self.tkinter_vars = None

        for page in self.text_notebook.pages.itervalues():
            page.editpage.close()

        self.top.destroy()
        if self.close_hook:
            # unless override: unregister from flist, terminate if last window
            self.close_hook()

    def apply_bindings(self, keydefs=None, tab=None):
        if keydefs is None:
            keydefs = Bindings.default_keydefs

        if tab:
            iter_over = [tab]
        else:
            iter_over = self.text_notebook.pages.itervalues()

        for page in iter_over:
            text = page.editpage.text
            text.keydefs = keydefs
            for event, keylist in keydefs.items():
                if keylist:
                    text.event_add(event, *keylist)

    def getvar(self, name):
        var = self.get_var_obj(name)
        if var:
            value = var.get()
            return value
        else:
            raise NameError, name

    def setvar(self, name, value, vartype=None):
        var = self.get_var_obj(name, vartype)
        if var:
            var.set(value)
        else:
            raise NameError, name

    def get_var_obj(self, name, vartype=None, text=None):
        var = self.tkinter_vars.get(name)
        if not var and vartype:
            # create a Tkinter variable object with self.text as master:
            self.tkinter_vars[name] = var = vartype(text or self.text)
        return var

    # Tk implementations of "virtual text methods" -- each platform
    # reusing IDLE's support code needs to define these for its GUI's
    # flavor of widget.

    # Return the text widget's current view of what a tab stop means
    # (equivalent width in spaces).
    def get_tabwidth(self):  # XXX depends on self.text
        current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
        return int(current)

    # Set the text widget's current view of what a tab stop means.
    def set_tabwidth(self, newtabwidth):  # XXX depends on self.text
        text = self.text
        if self.get_tabwidth() != newtabwidth:
            pixels = text.tk.call("font", "measure", text["font"],
                                  "-displayof", text.master, "n" * newtabwidth)
            text.configure(tabs=pixels)

    # If ispythonsource and guess are true, guess a good value for
    # indentwidth based on file content (if possible), and if
    # indentwidth != tabwidth set usetabs false.
    # In any case, adjust the Text widget's view of what a tab
    # character means.
    def set_indentation_params(self, ispythonsource, guess=True):
        if guess and ispythonsource:
            i = self.guess_indent()
            if 2 <= i <= 8:
                self.indentwidth = i
            if self.indentwidth != self.tabwidth:
                self.usetabs = False
        self.set_tabwidth(self.tabwidth)

    # Guess indentwidth from text content.
    # Return guessed indentwidth.  This should not be believed unless
    # it's in a reasonable range (e.g., it will be 0 if no indented
    # blocks are found).
    def guess_indent(self):  # XXX depends on self.text
        opener, indented = IndentSearcher(self.text, self.tabwidth).run()
        if opener and indented:
            raw, indentsmall = classifyws(opener, self.tabwidth)
            raw, indentlarge = classifyws(indented, self.tabwidth)
        else:
            indentsmall = indentlarge = 0
        return indentlarge - indentsmall

    # Private methods/attributes

    # extensions won't have more than one instance per window
    _unique_extensions = ['CodeContext', 'ScriptBinding', 'FormatParagraph']

    def _unload_extensions(self):
        for ins in self.extensions.values():
            if hasattr(ins, "close"):
                ins.close()
        self.extensions = {}

    def _load_extension(self, name, tab):
        ext_loaded = self.extensions.get(name)

        try:
            mod = __import__(name, globals(), locals(), [])
        except ImportError:
            print "\nFailed to import extension: ", name
            return

        keydefs = idleConf.GetExtensionBindings(name)

        if name not in self._unique_extensions or not ext_loaded:
            # create a new instance
            cls = getattr(mod, name)
            ins = cls(tab.editpage)
            self.extensions.setdefault(name, []).append(ins)
            if not ext_loaded:
                # create new items in menu only if this is the first time this
                # extension is being loaded in this window
                if hasattr(cls, "menudefs"):
                    self._fill_menus(cls.menudefs, keydefs)
        elif name in self._unique_extensions and ext_loaded:
            # get an existing instance
            ins = self.extensions[name][0]

        if keydefs:
            self.apply_bindings(keydefs, tab)
            for vevent in keydefs.keys():
                methodname = vevent.replace("-", "_")
                while methodname[:1] == '<':
                    methodname = methodname[1:]
                while methodname[-1:] == '>':
                    methodname = methodname[:-1]
                methodname = methodname + "_event"
                if hasattr(ins, methodname):
                    tab.editpage.text.bind(vevent, getattr(ins, methodname))

    def _load_extensions(self):
        self._load_standard_extensions(self.text_notebook.last_page())

    def _load_standard_extensions(self, tab):
        for name in self._get_standard_extension_names():
            try:
                self._load_extension(name, tab)
            except:
                print "Failed to load extension", repr(name)
                traceback.print_exc()

    def _get_standard_extension_names(self):
        return idleConf.GetExtensions(editor_only=True)

    def _post_tab_close(self, event):
        if not self.current_page:
            # no tabs now, close window
            self._close()
            return

    def _update_controls(self, event):
        curr_page = self.current_page
        if not curr_page:
            return

        self.text = curr_page.text
        curr_page.saved_change_hook(True, False)  # update window title
        curr_page.text.focus_set()
        self.set_line_and_column()

        # update references in extensions that are loaded only once
        for ext in self._unique_extensions:
            if ext not in self.extensions:
                continue
            ext = self.extensions[ext][0]
            ext.editpage = curr_page

    def _create_statusbar(self):
        self.status_bar = MultiStatusBar(self.top)
        if macosxSupport.runningAsOSXApp():
            # Insert some padding to avoid obscuring some of the statusbar
            # by the resize widget.
            self.status_bar.set_label('_padding1', '    ', side=RIGHT)
        self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
        self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
        self.status_bar.pack(side=BOTTOM, fill=X)

    def _createmenubar(self, text):
        mbar = self.menubar
        menudict = self.menudict
        for name, label in self.menu_specs:
            underline, label = prepstr(label)
            menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
            mbar.add_cascade(label=label, menu=menu, underline=underline)

        if sys.platform == 'darwin' and '.framework' in sys.executable:
            # Insert the application menu
            menudict['application'] = menu = Menu(mbar, name='apple')
            mbar.add_cascade(label='IDLE', menu=menu)

        self._fill_menus(text=text)
        self.base_helpmenu_length = self.menudict['help'].index(END)
        self.reset_help_menu_entries()

    def _fill_menus(self, menudefs=None, keydefs=None, text=None):
        """Add appropriate entries to the menus and submenus

        Menus that are absent or None in self.menudict are ignored.
        """
        if menudefs is None:
            menudefs = Bindings.menudefs
        if keydefs is None:
            keydefs = Bindings.default_keydefs
        menudict = self.menudict
        for mname, entrylist in menudefs:
            menu = menudict.get(mname)
            if not menu:
                continue
            for entry in entrylist:
                if not entry:
                    menu.add_separator()
                else:
                    label, eventname = entry
                    checkbutton = (label[:1] == '!')
                    if checkbutton:
                        label = label[1:]
                    underline, label = prepstr(label)
                    accelerator = get_accelerator(keydefs, eventname)

                    def command(eventname=eventname):
                        self.text.event_generate(eventname)

                    if checkbutton:
                        var = self.get_var_obj(eventname, BooleanVar, text)
                        menu.add_checkbutton(label=label,
                                             underline=underline,
                                             command=command,
                                             accelerator=accelerator,
                                             variable=var)
                    else:
                        menu.add_command(label=label,
                                         underline=underline,
                                         command=command,
                                         accelerator=accelerator)

    def __recent_file_callback(self, file_name):
        def open_recent_file(fn_closure=file_name):
            self.current_page.io.open(editFile=fn_closure)

        return open_recent_file

    def __extra_help_callback(self, helpfile):
        "Create a callback with the helpfile value frozen at definition time"

        def display_extra_help(helpfile=helpfile):
            if not helpfile.startswith(('www', 'http')):
                url = os.path.normpath(helpfile)
            if sys.platform[:3] == 'win':
                os.startfile(helpfile)
            else:
                webbrowser.open(helpfile)

        return display_extra_help