Lightningbeam/PyGUI-2.5.3/GUI/Generic/Document.py

322 lines
11 KiB
Python

#
# Python GUI - Documents - Generic
#
import os, tempfile
from GUI import export
from GUI.Alerts import confirm, confirm_or_cancel
from GUI.Properties import overridable_property
from GUI import Model
from GUI import MessageHandler
from GUI.Files import FileRef, DirRef
from GUI.FileDialogs import request_new_file
from GUI import application
from GUI.Exceptions import Cancel, UnimplementedMethod, ApplicationError
from GUI.Printing import PageSetup, present_page_setup_dialog
_next_doc_number = 1 # Counter for generating default titles
class Document(Model, MessageHandler):
"""A Document represents an
application data structure that can be stored in a file. It
implements the standard parts of asking the user for file names and
reading and writing files.
Each Document can have one or more windows associated with it. When
the last window belonging to a document is closed, the document itself
is closed.
A Document provides support for keeping track of whether it has been
edited, and asking the user whether to save changes when it is
closed."""
# The following attribute prevents a Document that is the parent
# of a Model from being pickled along with that Model.
pickle_as_parent_model = False
needs_saving = overridable_property('needs_saving',
"True if the document has been edited and needs to be saved.")
file = overridable_property('file',
"""FileRef of the file that the document was read from or last written
to, or None. Changing this causes update_title to be called.""")
file_type = overridable_property('file_type',
"""FileType specifying the type of file handled by this document.""")
title = overridable_property('title',
"""The title of the document. Changing this causes update_title of each
associated window to be called.""")
windows = overridable_property('windows',
"List of windows associated with the document. Do not modify directly.")
page_setup = overridable_property('page_setup',
"The PageSetup to be used for printing this document.")
binary = True # True if files are to be opened in binary mode
_file_type = None # Type of file to create when saving
_needs_saving = 0 # True if has been edited
_file = None # FileRef of associated file, if any
_title = None # Title for use in window banners, etc.
_windows = None # List of associated windows
_page_setup = None # Document-specific PageSetup instance
#
# Initialisation and destruction
#
def __init__(self, **kwds):
self._windows = []
Model.__init__(self, **kwds)
application()._add_document(self)
def destroy(self):
"""Destroy any associated windows, then destroy document contents."""
#print "Document.destroy:", self ###
for win in self._windows[:]:
win.destroy()
application()._remove_document(self)
self.destroy_contents()
Model.destroy(self)
#
# Properties
#
def get_needs_saving(self):
return self._needs_saving
def set_needs_saving(self, x):
if self._needs_saving <> x:
self._needs_saving = x
for window in self._windows:
window._document_needs_saving(x)
def get_file(self):
return self._file
def set_file(self, x):
self._file = x
if x is not None:
application()._last_directory = x.dir
self.update_title()
def get_file_type(self):
return self._file_type
def set_file_type(self, x):
self._file_type = x
def get_title(self):
t = self._title
if t == None:
t = self.make_title()
self._title = t
return t
def set_title(self, x):
self._title = x
for win in self._windows:
win.update_title()
def get_windows(self):
return self._windows
def get_page_setup(self):
ps = self._page_setup
if not ps:
ps = PageSetup()
self._page_setup = ps
return ps
def set_page_setup(self, ps):
self._page_setup = ps
#
# Methods
#
def changed(self):
"Set the needs_saving property to true."
self.needs_saving = 1
def new_contents(self):
"""Should initialise the document to the appropriate state following a New
command."""
pass
def read_contents(self, file):
"""Should initialise the document's contents by reading it from the given
file object."""
raise UnimplementedMethod(self, 'read_contents')
def write_contents(self, file):
"""Should write the document's contents to the given file object."""
raise UnimplementedMethod(self, 'write_contents')
def destroy_contents(self):
"""Called when the contents of the document are about to be discarded.
If the contents contains any Model objects, they should be destroyed."""
def save_changes(self):
"""If the document has been edited, ask the user whether to save changes,
and do so if requested."""
if self._needs_saving:
result = confirm_or_cancel('Save changes to "%s"?' % self.title,
"Save", "Don't Save", "Cancel")
if result < 0:
raise Cancel
if result:
self.save_cmd()
def save_cmd(self):
"""Implements the standard Save command. Writes the document to its
associated file, asking the user for one first if necessary."""
if self._file == None:
self.get_new_file_name()
try:
self.write()
except EnvironmentError, e:
raise ApplicationError("Unable to save '%s'." % self._file.name, e)
def save_as_cmd(self):
"""Implements the standard Save As... command. Asks the user for a new file
and writes the document to it."""
self.get_new_file_name()
self.save_cmd()
def revert_cmd(self):
"""Implements the standard Revert command. Discards the current contents
of the document and re-reads it from the associated file."""
if self._file != None:
if confirm(
'Revert to the last saved version of "%s"?' % self.title,
"Revert", "Cancel"):
self.destroy_contents()
self.read()
def close_cmd(self):
"""Implements the standard Close command. Asks whether to save any
changes, then destroys the document."""
self.save_changes()
self.destroy()
def page_setup_cmd(self):
if present_page_setup_dialog(self.page_setup):
self.changed()
def make_title(self):
"""Generates a title for the document. If associated with a file,
uses its last pathname component, else generates 'Untitled-n'."""
global _next_doc_number
if self._file != None:
return os.path.basename(self._file)
else:
n = _next_doc_number
_next_doc_number = n + 1
return "Untitled-%d" % n
def update_title(self):
"""Called when the file property changes, to update the
title property appropriately."""
file = self._file
if file:
self.title = file.name
def get_default_save_directory(self):
"""Called when the user is about to be asked for a location in which
to save a document that has not been saved before, to find a default
directory for request_new_file(). Should return a DirRef or FileRef,
or None if there is no particular preferred location."""
return None
def get_default_save_filename(self):
"""Called when the user is about to be asked for a location in which
to save a document that has not been saved before, to find a default
file name for request_new_file(). Should return a suggested file name,
or an empty string to require the user to enter a file name."""
return ""
#
# Internal methods
#
def get_new_file_name(self):
"""Ask the user for a new file and associate the document with it."""
old_file = self.file
if old_file:
old_name = old_file.name
old_dir = old_file.dir
else:
old_name = self.get_default_save_filename()
old_dir = self.get_default_save_directory()
#print "Document.get_new_file_name: old_dir =", old_dir, "old_name =", old_name ###
new_file = request_new_file(
#'Save "%s" as:' % self.title,
default_dir = old_dir,
default_name = old_name,
file_type = self.file_type or application().save_file_type)
if new_file is None:
raise Cancel()
self.file = new_file
def read(self):
"""Read the document from its currently associated file. The
document must be associated with a file and not have any existing
contents when this is called."""
if self.binary:
mode = "rb"
else:
mode = "rU"
file = self.file.open(mode)
try:
self.read_contents(file)
finally:
file.close()
self.needs_saving = 0
def write(self):
"""Write the document to its currently associated file. The
document must be associated with a file when this is called.
The document is initially written to a temporary file which
is then renamed, so if writing fails part way through, the
original file is undisturbed."""
if self.binary:
mode = "wb"
else:
mode = "w"
dir_path = self.file.dir.path
fd, temp_path = tempfile.mkstemp(dir = dir_path, text = not self.binary)
file = os.fdopen(fd, mode)
try:
try:
self.write_contents(file)
finally:
file.close()
except EnvironmentError:
os.unlink(fd)
raise
path = self.file.path
try:
os.unlink(path)
except EnvironmentError:
pass
os.rename(temp_path, path)
self.needs_saving = 0
def setup_menus(self, m):
#print "Document.setup_menus" ###
if self._needs_saving or not self._file:
m.save_cmd.enabled = 1
if self._needs_saving and self._file:
m.revert_cmd.enabled = 1
m.save_as_cmd.enabled = 1
m.page_setup_cmd.enabled = 1
def next_handler(self):
return application()
export(Document)