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. -- https://mail.python.org/mailman/listinfo/python-list