Editing text with an external editor in Python

gschemenauer3 at gmail.com gschemenauer3 at gmail.com
Mon Sep 1 22:24:13 EDT 2014


On Monday, September 1, 2014 11:11:34 AM UTC-5, Steven D'Aprano wrote:
> Python's input() or raw_input() function is good for getting a single line
> 
> of text from the user. But what if you want a more substantial chunk of
> 
> text from the user? Here's how to call out to an external editor such as
> 
> ed, nano, vim, emacs, and even GUI text editors:
> 
> 
> 
> import tempfile
> 
> 
> 
> def edit(editor, content=''):
> 
>     f = tempfile.NamedTemporaryFile(mode='w+')
> 
>     if content:
> 
>         f.write(content)
> 
>         f.flush()
> 
>     command = editor + " " + f.name
> 
>     status = os.system(command)
> 
>     f.seek(0, 0)
> 
>     text = f.read()
> 
>     f.close()
> 
>     assert not os.path.exists(f.name)
> 
>     return (status, text)
> 
> 
> 
> 
> 
> Anyone able to test it on Windows for me please?
> 
> 
> 
> 
> 
> More here: 
> 
> 
> 
> https://code.activestate.com/recipes/578926/
> 
> 
> 
> 
> 
> -- 
> 
> Steven

here's a full blown text editor (GUI) that gets a "substantial chunk of text" from the user.  It's almost finished but is perfectly functional right now.  Two files:

main file:

#!/usr/bin/env python

import os
import sys
import glob
import webbrowser

from GtkApp       import *
from GPYedit_conf import Preferences

APPLICATION_NAME = 'GPYedit'

class GPYedit(GtkApp_Toplevel):

      # For each tab in the notebook, we will store
      # our data representing each file (or empty buffer)
      # in a list.  Each item is a dictionary keeping track of the:
      #   - Python file object
      #   - Three components of editing area: scrolled window, textview, and buffer (per tab)
      #   - Full pathname of the file being edited
      #   - Text shown in the notebook widget tabs

      open_files = [ ]


      # Keep track of which buffer we're dealing with.
      # Each time the notebook page is switched, this number
      # will change (see 'on_nb_page_switched' callback).  This value 
      # is used as the index into the open files list to get at the
      # file-specific information and widgets.

      current_tab = 0


      # User preferences will be accessible through this attribute.
      # The Preferences class will be initialized from directives
      # found in the gpyedit_settings.ini file.

      preferences = Preferences()

      def __init__(this):
            """
            This is where it all starts.  Begin by setting
            the window geometry and title and decide whether
            to create a new empty file or use the arguments provided.

            """
            GtkApp_Toplevel.__init__(this)
            this.window.set_title("GPYedit")
            (width, height) = GPYedit.preferences.get_window_dimensions()
            this.window.set_default_size(width, height)
            this.build_GUI()
            if len(sys.argv) > 1:
                  names = sys.argv[1:]
                  for name in names:
                        if os.path.exists(name) and os.path.isfile(name):
                              this.tab_new_from_contents(name)
                        else:
                              print 'File "' + name + '" doesn\'t exist.'
            else:
                  this.create_new_file()


      def build_GUI(this):
            """
            Create the main interface components.
            These are
               - vbox: Main vertical box for laying out widgets
               - menu_bar: self explanatory
               - notebook: The tabbed container holding our file buffers
            """
            this.vbox = gtk.VBox(False, 0)
            this.menu_bar = gtk.MenuBar()
            this.notebook = gtk.Notebook()
            this.notebook.set_scrollable(True)
            this.notebook.connect("switch-page", this.on_nb_page_switch)
            this.create_menus()
            this.create_toolbar()
            this.vbox.pack_start(this.notebook, True, True, 0)
            this.window.add(this.vbox)


      def create_new_file(this, menu_item = None):
            """
            Create a blank buffer with no associated Python file object or name (yet)
            NOTE: menu_item is a parameter here because
            this method will be used as a signal handler
            also for the File menu 'new' item and the prototype
            for that handler requires this parameter.  It is not used though.

            """
            (scrolled_win, textview, buf) = this.editing_area_new()
            label = gtk.Label("Untitled")
            edit_area = { 'scrolled_window': scrolled_win, 'textview': textview, 'buffer': buf }
            # Store everything we know
            GPYedit.open_files.append({'file_object': None,
                                       'edit_area':   edit_area,
                                       'filename':    None,
                                       'label':       label})
            index = this.notebook.append_page(scrolled_win, label)
            this.notebook.show_all()
            return GPYedit.open_files[index]


      def tab_new_from_contents(this, filename):
            """
            Open a new tab and dump the contents of a file
            into it.

            """
            if this.check_for_used_file_name(filename):
                  return

            (scrolled_win, textview, buf) = this.editing_area_new()
            fobj = open(filename, 'r+w')
            data = fobj.read()
            if data != '': buf.set_text(data)
            buf.set_modified(False)
            label = gtk.Label(os.path.basename(filename))
            edit_area = { 'scrolled_window': scrolled_win, 'textview': textview, 'buffer': buf }
            # Store everything we know
            GPYedit.open_files.append({'file_object': fobj,
                                       'edit_area':   edit_area,
                                       'filename':    filename,
                                       'label':       label})
            index = this.notebook.append_page(scrolled_win, label)
            this.notebook.show_all()
            return GPYedit.open_files[index]


      def open_file(this, menu_item = None):
            """
            Open a file.  The action performed depends on whether
            the current tab with focus has an associated file name.
            If it is an empty buffer, then the name the user selects
            in the file selector is opened in this tab otherwise
            a new one is created to house the new file contents.

            """
            # May need to revise a little bit.  What happens when there
            # is no tab?

            error = False

            chooser = gtk.FileChooserDialog("Open A File", this.window)
            chooser.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
            chooser.add_button(gtk.STOCK_OPEN, gtk.RESPONSE_OK)
            response = chooser.run()
            if response == gtk.RESPONSE_OK:
                  filename = chooser.get_filename()
                  if os.path.exists(filename) and os.path.isfile(filename):
                        if this.check_for_used_file_name(filename):
                              # Throw error dialog box?
                              gtk.Widget.destroy(chooser)
                              return
                        try:
                              data = GPYedit.open_files[GPYedit.current_tab]
                        except IndexError:
                              # If there are no tabs, we can't grab the file data
                              # so make sure there's something to work with.
                              data = this.create_new_file()
                        if data['filename'] is None and not data['edit_area']['buffer'].get_modified():
                              obj = open(filename, 'r+w')
                              contents = obj.read()
                              data['file_object'] = obj
                              data['edit_area']['buffer'].set_text(contents)   # Insertion..
                              data['edit_area']['buffer'].set_modified(False)  # ..But no user interaction (yet)
                              data['filename'] = filename
                              data['label'] = os.path.basename(data['filename'])
                              this.notebook.set_tab_label_text(data['edit_area']['scrolled_window'], data['label'])
                              GPYedit.open_files[GPYedit.current_tab] = data
                              this.set_window_title(filename)
                        else:
                              data = this.tab_new_from_contents(filename)
                  else:
                        error = gtk.MessageDialog(parent = this.window,
                                                  type = gtk.MESSAGE_ERROR,
                                                  buttons = gtk.BUTTONS_OK,
                                                  message_format = "The file '" + filename + "' doesn't exist!")

            gtk.Widget.destroy(chooser)

            if error:
                  error.run()
                  error.destroy()


      def close_file(this, menu_item = None):
            """
            Close a file.  Determines whether the 'file' to be closed
            is just a scratch buffer with some text in it or if it has
            a file name (in which case there is an associated Python file object).
            If the buffer has been modified since it was either opened or the user
            typed some text in, give them a chance to save the data to a file on disk
            before removing the tab from the notebook widget.
            """
            if this.notebook.get_n_pages() == 0:
                  return
            current_file = GPYedit.open_files[GPYedit.current_tab]
            if current_file['edit_area']['buffer'].get_modified() == True:
                  prompt = gtk.Dialog("Unsaved Changes", this.window)
                  prompt.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                     gtk.STOCK_CLEAR, gtk.RESPONSE_NO,
                                     gtk.STOCK_YES, gtk.RESPONSE_YES)
                  content_area = prompt.get_content_area()
                  #name = this.notebook.get_tab_label_text(current_file['edit_area']['scrolled_window'])
                  if current_file['filename'] is None:
                        name = 'Untitled'
                  else:
                        name = os.path.basename(current_file['filename'])
                  label = gtk.Label("Save changes to '" + name + "'?")
                  content_area.pack_start(label, True, True, 0)
                  prompt.show_all()
                  response = prompt.run()
                  prompt.destroy()
                  if response == gtk.RESPONSE_CANCEL:
                        return
                  elif response == gtk.RESPONSE_NO:
                        if current_file['file_object']:
                              current_file['file_object'].close()
                  elif response == gtk.RESPONSE_YES:
                        (start, end) = current_file['edit_area']['buffer'].get_bounds()
                        if current_file['filename'] is None:
                              (action, selected_filename) = this.run_save_as_dialog()
                              if action == gtk.RESPONSE_OK and selected_filename is not None:
                                    this.save_file(selected_filename,
                                                   current_file['edit_area']['buffer'].get_text(start, end))
                        else:
                              this.save_file()
            else:
                  if current_file['filename'] is not None:
                        current_file['file_object'].close()

            tab_to_remove = GPYedit.current_tab
            this.notebook.remove_page(tab_to_remove)
            if this.notebook.get_n_pages() == 0:
                  this.set_window_title(alt_title = APPLICATION_NAME)
            del GPYedit.open_files[tab_to_remove]


      def save_file(this, filename = None, data = ''):
            """
            Write the contents of a buffer to a file on disk.
            """
            if this.notebook.get_n_pages() == 0:
                  return

            current_file = GPYedit.open_files[GPYedit.current_tab]

            (start, end) = current_file['edit_area']['buffer'].get_bounds()

            text_to_write = current_file['edit_area']['buffer'].get_text(start, end)

            if filename is not None:
                  if len(data) > 0:
                        obj = open(filename, 'w')
                        obj.write(data)
                        obj.close()
                  else:
                        # Filename given but no data passed. Dump contents of current buffer
                        # into a file specified by 'filename' argument.
                        obj = open(filename, 'w')
                        obj.write(text_to_write)
                        obj.close()
            else:
                  # Filename to save to was not provided.
                  # If the current buffer has no file name, then show a "Save As"
                  # dialog and prompt them to specify a file name.
                  if current_file['filename'] is None:
                        (action, selected_filename) = this.run_save_as_dialog()
                        if action == gtk.RESPONSE_OK:
                              current_file['filename'] = selected_filename
                              current_file['file_object'] = open(selected_filename, 'w+')
                              current_file['file_object'].write(text_to_write)
                              current_file['label'].set_text(os.path.basename(selected_filename))
                              current_file['edit_area']['buffer'].set_modified(False)
                              GPYedit.open_files[GPYedit.current_tab] = current_file
                              this.set_window_title(selected_filename)
                  else:
                        # Current buffer has associated file name.
                        # Save to that file.
                        curr_file_obj = current_file['file_object']
                        curr_file_obj.truncate(0)
                        curr_file_obj.seek(0)
                        curr_file_obj.write(text_to_write)
                        current_file['edit_area']['buffer'].set_modified(False)


      def set_window_title(this, filename = None, alt_title = ''):
            """
            Set the window title to a specific string with alt_title
            or work with an expected file name.  The format for showing
            the title information is:
               filename (directory path) - application name
            """
            if alt_title:
                  this.window.set_title(alt_title)
            elif filename is None:
                  this.window.set_title("Untitled - " + APPLICATION_NAME)   # Default title
            else:
                  (dirpath, fname) = os.path.split(filename)
                  this.window.set_title(fname + " (" + dirpath + ") - " + APPLICATION_NAME)


      def select_all(this):
            """
            Select all the text in the editing area.
            """
            current_file = GPYedit.open_files[GPYedit.current_tab]
            buf = current_file['edit_area']['buffer']
            (start, end) = buf.get_bounds()
            buf.select_range(start, end)


      def copy_to_clipboard(this):
            """
            Copy some selected text to the clipboard.
            """
            current_file = GPYedit.open_files[GPYedit.current_tab]
            clipboard = gtk.clipboard_get()
            current_file['edit_area']['buffer'].copy_clipboard(clipboard)


      def popup_search_box(this, menu_item = None):
            """
            Display the search dialog.
            """
            dial = gtk.Dialog("Find and Replace", this.window)
            dial.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                             gtk.STOCK_CONVERT, gtk.RESPONSE_APPLY,
                             gtk.STOCK_FIND, gtk.RESPONSE_OK)
            dial.set_response_sensitive(gtk.RESPONSE_OK, False)
            dial.set_response_sensitive(gtk.RESPONSE_APPLY, False)
            table = gtk.Table(4, 2, False)
            table.set_row_spacings(8)
            table.set_col_spacings(8)
            find_label = gtk.Label("Search for:")
            find_label.set_alignment(0, 0.5)
            replace_label = gtk.Label("Replace with:")
            replace_label.set_alignment(0, 0.5)
            find_entry = gtk.Entry()
            replace_entry = gtk.Entry()
            case_sens = gtk.CheckButton("Case sensitive")
            replace_all = gtk.CheckButton("Replace all occurences")
            table.attach(find_label, 0, 1, 0, 1)
            table.attach(find_entry, 1, 2, 0, 1)
            table.attach(replace_label, 0, 1, 1, 2)
            table.attach(replace_entry, 1, 2, 1, 2)
            table.attach(case_sens, 0, 2, 2, 3)
            table.attach(replace_all, 0, 2, 3, 4)
            content_area = dial.get_content_area()
            content_area.pack_start(table)
            table.set_border_width(8)
            find_entry.connect("insert-text", this.search_buttons_sensitive, dial)
            find_entry.connect("backspace", this.search_buttons_insensitive, dial)
            dt_id = find_entry.connect("delete-text", this.search_buttons_insensitive_del_text, dial)
            find_entry.set_data('del_text_sig_id', dt_id)
            widgets = {'find_entry': find_entry,
                       'replace_entry': replace_entry,
                       'match_case': case_sens,
                       'replace_all': replace_all}
            dial.connect("response", this.search_dialog_response, widgets)
            dial.show_all()
            dial.run()


      def search_dialog_response(this, dialog, response, widgets):
            """
            Process the response returned from the search and replace dialog.
            """
            if response == gtk.RESPONSE_OK:
                  this.document_search(widgets)
            elif response == gtk.RESPONSE_CANCEL:
                  dialog.destroy()
            elif response == gtk.RESPONSE_APPLY:
                  this.document_replace(widgets)


      def document_search(this, widgets):
            """
            Function not finished
            By default, do a forward search on the document in
            the tab with focus.  To work, this function should
            get these widgets:
               - find entry text box with the search text
               - replace entry with replacement text
               - match case checkbox
               - text buffer for current file
            """
            if widgets['match_case'].get_active():
                  case_sensitive = True
            else:
                  case_sensitive = False

            current_file = GPYedit.open_files[GPYedit.current_tab]

            tbuffer = current_file['edit_area']['buffer']
            start_iter = tbuffer.get_start_iter()
            search_text = widgets['find_entry'].get_text()
            (begin, end) = start_iter.forward_search(search_text, gtk.TEXT_SEARCH_TEXT_ONLY)
            tbuffer.select_range(begin, end)
            
            
      def document_replace(this, widgets):
            """
            Find a search string and replace it with new text.
            By default, only one replacement is done but with
            the 'replace all occurences' checkbox selected then
            it will perform a global search and replace.
            """
            current_file = GPYedit.open_files[GPYedit.current_tab]
            tbuffer = current_file['edit_area']['buffer']
            start_iter = tbuffer.get_start_iter()
            search_text = widgets['find_entry'].get_text()
            if widgets['replace_entry'].get_text_length() > 0:
                  (lower, upper) = tbuffer.get_bounds()
                  bufdata = tbuffer.get_text(lower, upper)
                  replace_str = widgets['replace_entry'].get_text()
                  if widgets['replace_all'].get_active():
                        # REPLACE ALL
                        updated_text = bufdata.replace(search_text, replace_str)
                  else:
                        # REPLACE ONCE
                        updated_text = bufdata.replace(search_text, replace_str, 1)
                  tbuffer.set_text(updated_text)
                  tbuffer.set_modified(False)
            else:
                  # ERROR: NO REPLACEMENT TEXT GIVEN
                  pass


      def search_buttons_sensitive(this, editable, new_text, new_text_length, pos, search_dialog):
            """
            Determine whether the buttons should be sensitive, thereby
            allowing the user to search, if there is text in the search box.
            """
            if editable.get_text_length() > 0:
                  return
            if new_text_length > 0:
                  search_dialog.set_response_sensitive(gtk.RESPONSE_OK, True)
                  search_dialog.set_response_sensitive(gtk.RESPONSE_APPLY, True)


      def search_buttons_insensitive(this, editable, search_dialog):
            """
            Make the search buttons insensitive when there is no
            text in the search box.
            """
            if editable.get_text_length() == 1:
                  search_dialog.set_response_sensitive(gtk.RESPONSE_OK, False)
                  search_dialog.set_response_sensitive(gtk.RESPONSE_APPLY, False)

      def search_buttons_insensitive_del_text(this, editable, start, end, search_dialog):
            """
            Similar to search_buttons_insensitive(), except that this
            handler is connected for the 'delete-text' signal.  It allows
            a user to highlight some text and delete it all at once.  In the
            case where they select and delete everything in the search box, the
            buttons along the bottom of the search dialog should become unusable.
            """
            if editable.get_text_length() > 0:
                  editable.handler_block(editable.get_data('del_text_sig_id'))
                  editable.delete_text(start, end)
                  editable.handler_unblock(editable.get_data('del_text_sig_id'))

            if editable.get_text_length() == 0:
                  search_dialog.set_response_sensitive(gtk.RESPONSE_OK, False)
                  search_dialog.set_response_sensitive(gtk.RESPONSE_APPLY, False)


      def check_for_used_file_name(this, name):
            """
            Any given file should only be opened in one tab.
            This method returns True if a specified file's name
            is already in use.
            """
            for element in GPYedit.open_files:
                  values = element.values()
                  if name in values:
                        return True


      def delete_event(this, widget, event, data = None):
            """
            Override method to close all files if a user clicks the 'X'
            button to close the text editor without first manually closing
            them via the File > Close menu option. Just explicitly cleaning up.
            """
            for element in GPYedit.open_files:
                  file_to_clean_up = element['file_object']
                  if file_to_clean_up:
                        file_to_clean_up.close()


      def run_save_as_dialog(this):
            """
            Display a Save As dialog box and allow the user to specify
            a file name to save to.
            """
            save_as = gtk.FileChooserDialog("Save As",
                                            this.window,
                                            gtk.FILE_CHOOSER_ACTION_SAVE,
                                            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                             gtk.STOCK_SAVE, gtk.RESPONSE_OK))
            action_id = save_as.run()
            save_as.hide()
            if action_id == gtk.RESPONSE_OK:
                  ret_val = (action_id, save_as.get_filename())
            else:
                  ret_val = (action_id, None)
            save_as.destroy()
            return ret_val


      def editing_area_new(this):
            """
            Build the set of widgets necessary to allow
            for the editing of text.  This includes:
               - scrolled window: allow viewing area to scroll
               - text view: widget to edit text
            """
            scrolled_win = gtk.ScrolledWindow()
            scrolled_win.set_shadow_type(gtk.SHADOW_ETCHED_IN)
            scrolled_win.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
            scrolled_win.set_border_width(3)
            textview = gtk.TextView()
            textview.set_left_margin(3)
            textview.set_right_margin(3)
            textview.set_pixels_above_lines(1)
            buf = textview.get_buffer()
            scrolled_win.add(textview)

            textview.modify_base(gtk.STATE_NORMAL,
               gtk.gdk.Color(GPYedit.preferences.get_background_color()))

            textview.modify_text(gtk.STATE_NORMAL,
               gtk.gdk.Color(GPYedit.preferences.get_foreground_color()))

            # Set the default font used for editing

            textview.modify_font(
               pango.FontDescription(GPYedit.preferences.get_font()))

            # Return a tuple of the three elements.
            # Note that the scrolled window itself holds the
            # textview which in turn contains the buffer.  But
            # this helps avoid having to extract the children of
            # the scrolled window every time we need access to either
            # the view or the buffer inside it.

            return (scrolled_win, textview, buf)


      def create_toolbar(this):
            """
            Create toolbar and buttons before packing into
            the main window.
            """
            this.toolbar = gtk.Toolbar()

            # Make toolbar widget buttons
            this.tb_new = gtk.ToolButton(gtk.STOCK_NEW)
            this.tb_open = gtk.ToolButton(gtk.STOCK_OPEN)
            this.tb_save = gtk.ToolButton(gtk.STOCK_SAVE)
            this.tb_save_as = gtk.ToolButton(gtk.STOCK_SAVE_AS)

            # Insert buttons into toolbar
            this.toolbar.insert(this.tb_new, 0)
            this.toolbar.insert(this.tb_open, 1)
            this.toolbar.insert(this.tb_save, 2)
            this.toolbar.insert(this.tb_save_as, 3)

            this.view_menu_toolbar.connect("toggled", this.toggle_tb_visible)

            # Tool bar 'new' button creates a new file.  The method signature
            # doesn't match the required parameters for this signal though so
            # we use a sort of pass-through function to get there.
            this.tb_new.connect("clicked",  lambda tool_item: this.create_new_file())
            this.tb_open.connect("clicked", lambda tool_item: this.open_file())
            this.tb_save.connect("clicked", lambda tool_item: this.save_file())

            # Pack toolbar into window vbox
            this.vbox.pack_start(this.toolbar, False, False, 0)


      def toggle_tb_visible(this, widget, data = None):
            """
            Callback to control visiblity of the toolbar
            """
            if widget.get_active():
                  this.toolbar.show()
            else:
                  this.toolbar.hide()


      def on_nb_page_switch(this, notebook, page, page_num):
            """
            Each time the user selects a tab to work with, change
            the internal tab indicator so that it can be used to get
            the relevant data associated with that tab.  This is a callback
            and there is no need to call directly.  See GTK+ 'switch-page' signal.
            """
            GPYedit.current_tab = page_num

            file_info = GPYedit.open_files[GPYedit.current_tab]

            if file_info['filename'] is not None:
                  this.set_window_title(file_info['filename'])
            else:
                  this.set_window_title()


      def open_by_pattern(this):
            """
            Open all files that match a shell-style pattern.
            """
            items = glob.glob(os.environ['HOME'] + os.sep + '*.php')
            for item in items:
                  this.tab_new_from_contents(item)
                  


      def create_menus(this):
            """
            Create the menu bar and associated menu items.
            """
            accel_group = gtk.AccelGroup()

            # Associate with main window
            this.window.add_accel_group(accel_group)

            # Create File menu
            this.file_menu = gtk.Menu()
            this.file_menu.set_accel_group(accel_group)
            this.file_menu_item = gtk.MenuItem("File")
            this.file_menu_item.set_submenu(this.file_menu)

            # Create menu items
            this.file_menu_new = gtk.ImageMenuItem(gtk.STOCK_NEW)
            this.file_menu_new.add_accelerator("activate", accel_group, ord('n'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
            this.file_menu_open = gtk.ImageMenuItem(gtk.STOCK_OPEN)
            this.file_menu_open.add_accelerator("activate", accel_group, ord('o'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
            this.file_menu_open_files_by_pattern = gtk.MenuItem("Open Files By Pattern")
            this.file_menu_save = gtk.ImageMenuItem(gtk.STOCK_SAVE)
            this.file_menu_save.add_accelerator("activate", accel_group, ord('s'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
            this.file_menu_save_as = gtk.ImageMenuItem(gtk.STOCK_SAVE_AS)
            this.file_menu_save_as.add_accelerator("activate", accel_group, ord('s'), gtk.gdk.SHIFT_MASK | gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
            this.file_menu_close = gtk.ImageMenuItem(gtk.STOCK_CLOSE)
            this.file_menu_close.add_accelerator("activate", accel_group, ord('w'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
            this.file_menu_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT)
            this.file_menu_quit.add_accelerator("activate", accel_group, ord('q'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)

            # Add them to File menu
            this.file_menu.append(this.file_menu_new)
            this.file_menu.append(this.file_menu_open)
            this.file_menu.append(this.file_menu_open_files_by_pattern)
            this.file_menu.append(this.file_menu_save)
            this.file_menu.append(this.file_menu_save_as)
            this.file_menu.append(this.file_menu_close)
            this.file_menu.append(this.file_menu_quit)

            # Connect signals
            this.file_menu_new.connect("activate", this.create_new_file)
            this.file_menu_open.connect("activate", this.open_file)
            this.file_menu_open_files_by_pattern.connect("activate", lambda menu_item: this.open_by_pattern())
            this.file_menu_save.connect("activate", lambda menu_item: this.save_file())
            this.file_menu_close.connect("activate", this.close_file)
            this.file_menu_quit.connect("activate", gtk.main_quit)

            # Create Edit menu
            this.edit_menu = gtk.Menu()
            this.edit_menu.set_accel_group(accel_group)
            this.edit_menu_item = gtk.MenuItem("Edit")
            this.edit_menu_item.set_submenu(this.edit_menu)

            # Create menu items
            this.edit_menu_cut = gtk.ImageMenuItem(gtk.STOCK_CUT)
            this.edit_menu_cut.add_accelerator("activate", accel_group, ord('x'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
            this.edit_menu_copy = gtk.ImageMenuItem(gtk.STOCK_COPY)
            this.edit_menu_copy.add_accelerator("activate", accel_group, ord('c'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
            this.edit_menu_paste = gtk.ImageMenuItem(gtk.STOCK_PASTE)
            this.edit_menu_paste.add_accelerator("activate", accel_group, ord('v'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
            this.edit_menu_select_all = gtk.MenuItem("Select All")
            this.edit_menu_select_all.add_accelerator("activate", accel_group, ord('a'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
            this.edit_menu_preferences = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)

            # Add them to Edit menu
            this.edit_menu.append(this.edit_menu_cut)
            this.edit_menu.append(this.edit_menu_copy)
            this.edit_menu.append(this.edit_menu_paste)
            this.edit_menu.append(this.edit_menu_select_all)
            this.edit_menu.append(this.edit_menu_preferences)

            # Connect signals
            this.edit_menu_copy.connect("activate", lambda menu_item: this.copy_to_clipboard())
            this.edit_menu_select_all.connect("activate", lambda menu_item: this.select_all())

            # Create View menu
            this.view_menu = gtk.Menu()
            this.view_menu_item = gtk.MenuItem("View")
            this.view_menu_item.set_submenu(this.view_menu)

            # Create menu items
            this.view_menu_toolbar = gtk.CheckMenuItem("Toolbar")
            this.view_menu_toolbar.set_active(True)
            this.view_menu_file_explorer_pane = gtk.CheckMenuItem("File Browser Pane")

            # Add them to View menu
            this.view_menu.append(this.view_menu_toolbar)
            this.view_menu.append(this.view_menu_file_explorer_pane)

            # Create Search menu
            this.search_menu = gtk.Menu()
            this.search_menu_item = gtk.MenuItem("Search")
            this.search_menu_item.set_submenu(this.search_menu)

            # Create menu items
            this.search_menu_s_and_r = gtk.ImageMenuItem(gtk.STOCK_FIND_AND_REPLACE)

            # Add them to Search menu
            this.search_menu.append(this.search_menu_s_and_r)

            # Connect signals
            this.search_menu_s_and_r.connect("activate", this.popup_search_box)

            # Create Help menu
            this.help_menu = gtk.Menu()
            this.help_menu_item = gtk.MenuItem("Help")
            this.help_menu_item.set_submenu(this.help_menu)

            # Create menu items
            this.help_menu_about = gtk.ImageMenuItem(gtk.STOCK_HELP)

            # Add them to Help menu
            this.help_menu.append(this.help_menu_about)

            # Add menus to the menubar
            this.menu_bar.append(this.file_menu_item)
            this.menu_bar.append(this.edit_menu_item)
            this.menu_bar.append(this.view_menu_item)
            this.menu_bar.append(this.search_menu_item)
            this.menu_bar.append(this.help_menu_item)

            # Pack menu bar into main window
            this.vbox.pack_start(this.menu_bar, False, False, 0)


########## Main ##########

if __name__ == "__main__": GPYedit().main()

##########################


second file:


import os

from utils import find_file

CONFIG_FILE = "gpyedit_settings.ini"

class Preferences:

      """
      This class holds and manages the user's preferences which
      will be stored permanently in the gpyedit_settings.ini file.
      """

      settings = \
       {
         "window_width":     779,
         "window_height":    419,
         "font_face":        "monospace",
         "background_color": "#FFFFFF",
         "foreground_color": "#000000"
       }

      def __init__(this):
            """
            Read the configuration file and set up the
            preferences so that they are ready to be accessed
            by the main application.

            """
            if not os.path.exists(CONFIG_FILE): config_file_location = find_file(CONFIG_FILE, os.environ["HOME"])
            else:
                  config_file_location = os.getcwd() + os.sep + CONFIG_FILE

            if config_file_location is None: return  # Configuration not found. Use default settings
            else:
                  this.process_options(open(config_file_location, "r").readlines())


      def process_options(this, config):
            """
            Parse configuration options in the gpyedit_settings.ini file.
            """
            for option in config:
                  data = option.split(":")      # Get [option, value]
                  if len(data) != 2: continue
                  (opt, val) = (data[0].strip(), data[1].strip())
                  for setting in this.settings.keys():
                        if opt == setting:
                              this.settings[setting] = val


      def get_font(this):
            """
            Return the font that should be used for editing text.
            """
            return Preferences.settings["font_face"]


      def get_background_color(this):
            """
            Return the background color of the editing area.
            """
            return Preferences.settings["background_color"]


      def get_foreground_color(this):
            """
            Return the foreground color of the editing area.
            """
            return Preferences.settings["foreground_color"]


      def get_window_dimensions(this):
            """
            Retrieve the toplevel window dimensions as a width and height
            """
            return (int(this.settings["window_width"]), int(this.settings["window_height"]))


      def run_dialog(this):
            """
            Create the preferences dialog box accessible through Edit > Preferences in
            the menu bar.
            """
            
---------------------------------------------

maybe that'll help you see how it's done.



More information about the Python-list mailing list