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